// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package x509

import (
	
	
	
	
	
	
	
)

// This file contains the data structures and functions necessary for
// efficiently checking X.509 name constraints. The method for constraint
// checking implemented in this file is based on a technique originally
// described by davidben@google.com.
//
// The basic concept is based on the fact that constraints describe possibly
// overlapping subtrees that we need to match against. If sorted in lexicographic
// order, and then pruned, removing any subtrees that overlap with preceding
// subtrees, a simple binary search can be used to find the nearest matching
// prefix. This reduces the complexity of name constraint checking from
// quadratic to log linear complexity.
//
// A close reading of RFC 5280 may suggest that constraints could also be
// implemented as a trie (or radix tree), which would present the possibility of
// doing construction and matching in linear time, but the memory cost of
// implementing them is actually quite high, and in the worst case (where each
// node has a high number of children) can be abused to require a program to use
// significant amounts of memory. The log linear approach taken here is
// extremely cheap in terms of memory because we directly alias the already
// parsed constraints, thus avoiding the need to do significant additional
// allocations.
//
// The basic data structure is nameConstraintsSet, which implements the sorting,
// pruning, and querying of the prefix sets.
//
// In order to check IP, DNS, URI, and email constraints, we need to use two
// different techniques, one for IP addresses, which is quite simple, and one
// for DNS names, which additionally compose the portions of URIs and emails we
// care about (technically we also need some special logic for email addresses
// as well for when constraints comprise of full email addresses) which is
// slightly more complex.
//
// IP addresses use two nameConstraintsSets, one for IPv4 addresses and one for
// IPv6 addresses, with no additional logic.
//
// DNS names require some extra logic in order to handle the distinctions
// between permitted and excluded subtrees, as well as for wildcards, and the
// semantics of leading period constraints (i.e. '.example.com'). This logic is
// implemented in the dnsConstraints type.
//
// Email addresses also require some additional logic, which does not make use
// of nameConstraintsSet, to handle constraints which define full email
// addresses (i.e. 'test@example.com'). For bare domain constraints, we use the
// dnsConstraints type described above, querying the domain portion of the email
// address. For full email addresses, we also hold a map of email addresses that
// map the local portion of the email to the domain. When querying full email
// addresses we then check if the local portion of the email is present in the
// map, and if so case insensitively compare the domain portion of the
// email.

type nameConstraintsSet[ *net.IPNet | string,  net.IP | string] struct {
	set []
}

// sortAndPrune sorts the constraints using the provided comparison function, and then
// prunes any constraints that are subsets of preceding constraints using the
// provided subset function.
func ( *nameConstraintsSet[, ]) ( func(, ) int,  func(, ) bool) {
	if len(.set) < 2 {
		return
	}

	slices.SortFunc(.set, )

	if len(.set) < 2 {
		return
	}
	 := 1
	for  := 1;  < len(.set); ++ {
		if !(.set[-1], .set[]) {
			.set[] = .set[]
			++
		}
	}
	.set = .set[:]
}

// search does a binary search over the constraints set for the provided value
// s, using the provided comparison function cmp to find the lower bound, and
// the match function to determine if the found constraint is a prefix of s. If
// a matching constraint is found, it is returned along with true. If no
// matching constraint is found, the zero value of T and false are returned.
func ( *nameConstraintsSet[, ]) ( ,  func(, ) int,  func(, ) bool) ( ,  bool) {
	if len(.set) == 0 {
		return , false
	}
	// Look for the lower bound of s in the set.
	,  := slices.BinarySearchFunc(.set, , )
	// If we found an exact match, return it
	if  {
		return .set[], true
	}

	if  < 0 {
		return , false
	}

	var  
	if  == 0 {
		 = .set[0]
	} else {
		 = .set[-1]
	}
	if (, ) {
		return , true
	}
	return , false
}

func ipNetworkSubset(,  *net.IPNet) bool {
	if !.Contains(.IP) {
		return false
	}
	 := make(net.IP, len(.IP))
	for  := range .IP {
		[] = .IP[] | (^.Mask[])
	}
	return .Contains()
}

func ipNetworkCompare(,  *net.IPNet) int {
	 := bytes.Compare(.IP, .IP)
	if  != 0 {
		return 
	}
	return bytes.Compare(.Mask, .Mask)
}

func ipBinarySearch( *net.IPNet,  net.IP) int {
	return bytes.Compare(.IP, )
}

func ipMatch( *net.IPNet,  net.IP) bool {
	return .Contains()
}

type ipConstraints struct {
	// NOTE: we could store IP network prefixes as a pre-processed byte slice
	// (i.e. by masking the IP) and doing the byte prefix checking using faster
	// techniques, but this would require allocating new byte slices, which is
	// likely significantly more expensive than just operating on the
	// pre-allocated *net.IPNet and net.IP objects directly.

	ipv4 *nameConstraintsSet[*net.IPNet, net.IP]
	ipv6 *nameConstraintsSet[*net.IPNet, net.IP]
}

func newIPNetConstraints( []*net.IPNet) interface {
	(net.IP) (*net.IPNet, bool)
} {
	if len() == 0 {
		return nil
	}
	var ,  []*net.IPNet
	for ,  := range  {
		if len(.IP) == net.IPv4len {
			 = append(, )
		} else {
			 = append(, )
		}
	}
	var ,  *nameConstraintsSet[*net.IPNet, net.IP]
	if len() > 0 {
		 = &nameConstraintsSet[*net.IPNet, net.IP]{
			set: ,
		}
		.sortAndPrune(ipNetworkCompare, ipNetworkSubset)
	}
	if len() > 0 {
		 = &nameConstraintsSet[*net.IPNet, net.IP]{
			set: ,
		}
		.sortAndPrune(ipNetworkCompare, ipNetworkSubset)
	}
	return &ipConstraints{ipv4: , ipv6: }
}

func ( *ipConstraints) ( net.IP) (*net.IPNet, bool) {
	var  *nameConstraintsSet[*net.IPNet, net.IP]
	if len() == net.IPv4len {
		 = .ipv4
	} else {
		 = .ipv6
	}
	if  == nil {
		return nil, false
	}
	return .search(, ipBinarySearch, ipMatch)
}

// dnsHasSuffix case-insensitively checks if DNS name b is a label suffix of DNS
// name a, meaning that example.com is not considered a suffix of
// testexample.com, but is a suffix of test.example.com.
//
// dnsHasSuffix supports the URI "leading period" constraint semantics, which
// while not explicitly defined for dNSNames in RFC 5280, are widely supported
// (see errata 5997). In particular, a constraint of ".example.com" is
// considered to only match subdomains of example.com, but not example.com
// itself.
//
// a and b must both be non-empty strings representing (mostly) valid DNS names.
func dnsHasSuffix(,  string) bool {
	 := len()
	 := len()
	if  >  {
		return false
	}
	 :=  - 1
	 :=  - 
	for ;  >= 0; -- {
		,  := [], [-()]
		if  ==  {
			continue
		}
		if  <  {
			,  = , 
		}
		if 'A' <=  &&  <= 'Z' &&  == +'a'-'A' {
			continue
		}
		return false
	}

	if [0] != '.' &&  >  && [--1] != '.' {
		return false
	}

	return true
}

// dnsCompareTable contains the ASCII alphabet mapped from a characters index in
// the table to its lowercased form.
var dnsCompareTable [256]byte

func init() {
	// NOTE: we don't actually need the
	// full alphabet, but calculating offsets would be more expensive than just
	// having redundant characters.
	for  := 0;  < 256; ++ {
		 := byte()
		if 'A' <=  &&  <= 'Z' {
			// Lowercase uppercase characters A-Z.
			 += 'a' - 'A'
		}
		dnsCompareTable[] = 
	}
	// Set the period character to 0 so that we get the right sorting behavior.
	//
	// In particular, we need the period character to sort before the only
	// other valid DNS name character which isn't a-z or 0-9, the hyphen,
	// otherwise a name with a dash would be incorrectly sorted into the middle
	// of another tree.
	//
	// For example, imagine a certificate with the constraints "a.com", "a.a.com", and
	// "a-a.com". These would sort as "a.com", "a-a.com", "a.a.com", which would break
	// the pruning step since we wouldn't see that "a.a.com" is a subset of "a.com".
	// Sorting the period before the hyphen ensures that "a.a.com" sorts before "a-a.com".
	dnsCompareTable['.'] = 0
}

// dnsCompare is a case-insensitive reversed implementation of strings.Compare
// that operates from the end to the start of the strings. This is more
// efficient that allocating reversed version of a and b and using
// strings.Compare directly (even though it is highly optimized).
//
// NOTE: this function treats the period character ('.') as sorting above every
// other character, which is necessary for us to properly sort names into their
// correct order. This is further discussed in the init function above.
func dnsCompare(,  string) int {
	 := len() - 1
	 := len() - 1

	for  >= 0 &&  >= 0 {
		 := dnsCompareTable[[]]
		 := dnsCompareTable[[]]
		if  ==  {
			--
			--
			continue
		}
		 := 1
		if  <  {
			 = -1
		}
		return 
	}

	 := 0
	if  <  {
		 = -1
	} else if  <  {
		 = 1
	}
	return 
}

type dnsConstraints struct {
	// all lets us short circuit the query logic if we see a zero length
	// constraint which permits or excludes everything.
	all bool

	// permitted indicates if these constraints are for permitted or excluded
	// names.
	permitted bool

	constraints *nameConstraintsSet[string, string]

	// parentConstraints contains a subset of constraints which are used for
	// wildcard SAN queries, which are constructed by removing the first label
	// from the constraints in constraints. parentConstraints is only populated
	// if permitted is false.
	parentConstraints map[string]string
}

func newDNSConstraints( []string,  bool) interface{ (string) (string, bool) } {
	if len() == 0 {
		return nil
	}
	for ,  := range  {
		if len() == 0 {
			return &dnsConstraints{all: true}
		}
	}
	 := slices.Clone()

	 := &dnsConstraints{
		constraints: &nameConstraintsSet[string, string]{
			set: ,
		},
		permitted: ,
	}

	.constraints.sortAndPrune(dnsCompare, dnsHasSuffix)

	if ! {
		 := map[string]string{}
		for ,  := range .constraints.set {
			 := trimFirstLabel()
			if  == "" {
				continue
			}
			[] = 
		}
		if len() > 0 {
			.parentConstraints = 
		}
	}

	return 
}

func ( *dnsConstraints) ( string) (string, bool) {
	if .all {
		return "", true
	}

	,  := .constraints.search(, dnsCompare, dnsHasSuffix)
	if  {
		return , true
	}

	if !.permitted && [0] == '*' {
		 := trimFirstLabel()
		if ,  := .parentConstraints[];  {
			return , true
		}
	}
	return "", false
}

type emailConstraints struct {
	dnsConstraints interface{ query(string) (string, bool) }

	fullEmails map[string]string
}

func newEmailConstraints( []string,  bool) interface {
	(parsedEmail) (string, bool)
} {
	if len() == 0 {
		return nil
	}
	 := map[string]string{}
	var  []string
	for ,  := range  {
		if !strings.ContainsRune(, '@') {
			 = append(, )
			continue
		}
		,  := parseRFC2821Mailbox()
		if ! {
			// We've already parsed these addresses in parseCertificate, and
			// treat failures as a hard failure for parsing. The only way we can
			// get a parse failure here is if the caller has mutated the
			// certificate since parsing.
			continue
		}
		[.local] = .domain
	}
	 := &emailConstraints{
		fullEmails: ,
	}
	if len() > 0 {
		.dnsConstraints = newDNSConstraints(, )
	}
	return 
}

func ( *emailConstraints) ( parsedEmail) (string, bool) {
	if len(.fullEmails) > 0 && strings.ContainsRune(.email, '@') {
		if ,  := .fullEmails[.mailbox.local];  && strings.EqualFold(, .mailbox.domain) {
			return .fullEmails[.email] + "@" + .mailbox.domain, true
		}
	}
	if .dnsConstraints == nil {
		return "", false
	}
	,  := .dnsConstraints.query(.mailbox.domain)
	return , 
}

type constraints[ any,  any] struct {
	constraintType string
	permitted      interface{ query() (, bool) }
	excluded       interface{ query() (, bool) }
}

func checkConstraints[ string | *net.IPNet,  any,  string | net.IP | parsedURI | parsedEmail]( constraints[, ],  ,  ) error {
	if .permitted != nil {
		if ,  := .permitted.query(); ! {
			return fmt.Errorf("%s %q is not permitted by any constraint", .constraintType, )
		}
	}
	if .excluded != nil {
		if ,  := .excluded.query();  {
			return fmt.Errorf("%s %q is excluded by constraint %q", .constraintType, , )
		}
	}
	return nil
}

type chainConstraints struct {
	ip    constraints[*net.IPNet, net.IP]
	dns   constraints[string, string]
	uri   constraints[string, string]
	email constraints[string, parsedEmail]

	index int
	next  *chainConstraints
}

func ( *chainConstraints) ( []string,  []parsedURI,  []parsedEmail,  []net.IP) error {
	for ,  := range  {
		if  := checkConstraints(.ip, , );  != nil {
			return 
		}
	}
	for ,  := range  {
		if !domainNameValid(, false) {
			return fmt.Errorf("x509: cannot parse dnsName %q", )
		}
		if  := checkConstraints(.dns, , );  != nil {
			return 
		}
	}
	for ,  := range  {
		if !domainNameValid(.domain, false) {
			return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", )
		}
		if  := checkConstraints(.uri, .domain, );  != nil {
			return 
		}
	}
	for ,  := range  {
		if !domainNameValid(.mailbox.domain, false) {
			return fmt.Errorf("x509: cannot parse rfc822Name %q", .mailbox)
		}
		if  := checkConstraints(.email, , );  != nil {
			return 
		}
	}
	return nil
}

func checkChainConstraints( []*Certificate) error {
	var  *chainConstraints
	var  *chainConstraints
	for ,  := range  {
		if !.hasNameConstraints() {
			continue
		}
		 := &chainConstraints{
			ip:    constraints[*net.IPNet, net.IP]{"IP address", newIPNetConstraints(.PermittedIPRanges), newIPNetConstraints(.ExcludedIPRanges)},
			dns:   constraints[string, string]{"DNS name", newDNSConstraints(.PermittedDNSDomains, true), newDNSConstraints(.ExcludedDNSDomains, false)},
			uri:   constraints[string, string]{"URI", newDNSConstraints(.PermittedURIDomains, true), newDNSConstraints(.ExcludedURIDomains, false)},
			email: constraints[string, parsedEmail]{"email address", newEmailConstraints(.PermittedEmailAddresses, true), newEmailConstraints(.ExcludedEmailAddresses, false)},
			index: ,
		}
		if  == nil {
			 = 
			 = 
		} else if  != nil {
			.next = 
			 = 
		}
	}
	if  == nil {
		return nil
	}

	for ,  := range  {
		if !.hasSANExtension() {
			continue
		}
		if  >= .index {
			for .index <=  {
				if .next == nil {
					return nil
				}
				 = .next
			}
		}

		,  := parseURIs(.URIs)
		if  != nil {
			return 
		}
		,  := parseMailboxes(.EmailAddresses)
		if  != nil {
			return 
		}

		for  := ;  != nil;  = .next {
			if  := .check(.DNSNames, , , .IPAddresses);  != nil {
				return 
			}
		}
	}

	return nil
}

type parsedURI struct {
	uri    *url.URL
	domain string
}

func ( parsedURI) () string {
	return .uri.String()
}

func parseURIs( []*url.URL) ([]parsedURI, error) {
	 := make([]parsedURI, 0, len())
	for ,  := range  {
		 := strings.ToLower(.Host)
		if len() == 0 {
			return nil, fmt.Errorf("URI with empty host (%q) cannot be matched against constraints", .String())
		}
		if strings.Contains(, ":") && !strings.HasSuffix(, "]") {
			var  error
			, _,  = net.SplitHostPort(.Host)
			if  != nil {
				return nil, fmt.Errorf("cannot parse URI host %q: %v", .Host, )
			}
		}

		// netip.ParseAddr will reject the URI IPv6 literal form "[...]", so we
		// check if _either_ the string parses as an IP, or if it is enclosed in
		// square brackets.
		if ,  := netip.ParseAddr();  == nil || (strings.HasPrefix(, "[") && strings.HasSuffix(, "]")) {
			return nil, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", .String())
		}

		 = append(, parsedURI{, })
	}
	return , nil
}

type parsedEmail struct {
	email   string
	mailbox *rfc2821Mailbox
}

func ( parsedEmail) () string {
	return .mailbox.local + "@" + .mailbox.domain
}

func parseMailboxes( []string) ([]parsedEmail, error) {
	 := make([]parsedEmail, 0, len())
	for ,  := range  {
		,  := parseRFC2821Mailbox()
		if ! {
			return nil, fmt.Errorf("cannot parse rfc822Name %q", )
		}
		.domain = strings.ToLower(.domain)
		 = append(, parsedEmail{strings.ToLower(), &})
	}
	return , nil
}

func trimFirstLabel( string) string {
	 := strings.IndexByte(, '.')
	if  < 0 {
		// Constraint is a single label, we cannot trim it.
		return ""
	}
	return [:]
}