diff options
| -rw-r--r-- | src/crypto/x509/constraints.go | 62 | ||||
| -rw-r--r-- | src/crypto/x509/name_constraints_test.go | 33 | ||||
| -rw-r--r-- | src/crypto/x509/verify.go | 4 |
3 files changed, 67 insertions, 32 deletions
diff --git a/src/crypto/x509/constraints.go b/src/crypto/x509/constraints.go index 5addedcf77..3c260a9b96 100644 --- a/src/crypto/x509/constraints.go +++ b/src/crypto/x509/constraints.go @@ -58,11 +58,11 @@ import ( // 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. +// address. For full email addresses, we also hold a map of email addresses with +// the domain portion of the email lowercased, since it is case insensitive. When +// looking up an email address in the constraint set, we first check the full +// email address map, and if we don't find anything, we check the domain portion +// of the email address against the dnsConstraints. type nameConstraintsSet[T *net.IPNet | string, V net.IP | string] struct { set []T @@ -387,16 +387,22 @@ func (dnc *dnsConstraints) query(s string) (string, bool) { type emailConstraints struct { dnsConstraints interface{ query(string) (string, bool) } - fullEmails map[string]string + // fullEmails is map of rfc2821Mailboxs that are fully specified in the + // constraints, which we need to check for separately since they don't + // follow the same matching rules as the domain-based constraints. The + // domain portion of the rfc2821Mailbox has been lowercased, since the + // domain portion is case insensitive. When checking the map for an email, + // the domain portion of the query should also be lowercased. + fullEmails map[rfc2821Mailbox]struct{} } func newEmailConstraints(l []string, permitted bool) interface { - query(parsedEmail) (string, bool) + query(rfc2821Mailbox) (string, bool) } { if len(l) == 0 { return nil } - exactMap := map[string]string{} + exactMap := map[rfc2821Mailbox]struct{}{} var domains []string for _, c := range l { if !strings.ContainsRune(c, '@') { @@ -411,7 +417,8 @@ func newEmailConstraints(l []string, permitted bool) interface { // certificate since parsing. continue } - exactMap[parsed.local] = parsed.domain + parsed.domain = strings.ToLower(parsed.domain) + exactMap[parsed] = struct{}{} } ec := &emailConstraints{ fullEmails: exactMap, @@ -422,16 +429,16 @@ func newEmailConstraints(l []string, permitted bool) interface { return ec } -func (ec *emailConstraints) query(s parsedEmail) (string, bool) { - if len(ec.fullEmails) > 0 && strings.ContainsRune(s.email, '@') { - if domain, ok := ec.fullEmails[s.mailbox.local]; ok && strings.EqualFold(domain, s.mailbox.domain) { - return ec.fullEmails[s.email] + "@" + s.mailbox.domain, true +func (ec *emailConstraints) query(s rfc2821Mailbox) (string, bool) { + if len(ec.fullEmails) > 0 { + if _, ok := ec.fullEmails[s]; ok { + return fmt.Sprintf("%s@%s", s.local, s.domain), true } } if ec.dnsConstraints == nil { return "", false } - constraint, found := ec.dnsConstraints.query(s.mailbox.domain) + constraint, found := ec.dnsConstraints.query(s.domain) return constraint, found } @@ -441,7 +448,7 @@ type constraints[T any, V any] struct { excluded interface{ query(V) (T, bool) } } -func checkConstraints[T string | *net.IPNet, V any, P string | net.IP | parsedURI | parsedEmail](c constraints[T, V], s V, p P) error { +func checkConstraints[T string | *net.IPNet, V any, P string | net.IP | parsedURI | rfc2821Mailbox](c constraints[T, V], s V, p P) error { if c.permitted != nil { if _, found := c.permitted.query(s); !found { return fmt.Errorf("%s %q is not permitted by any constraint", c.constraintType, p) @@ -459,13 +466,13 @@ type chainConstraints struct { ip constraints[*net.IPNet, net.IP] dns constraints[string, string] uri constraints[string, string] - email constraints[string, parsedEmail] + email constraints[string, rfc2821Mailbox] index int next *chainConstraints } -func (cc *chainConstraints) check(dns []string, uris []parsedURI, emails []parsedEmail, ips []net.IP) error { +func (cc *chainConstraints) check(dns []string, uris []parsedURI, emails []rfc2821Mailbox, ips []net.IP) error { for _, ip := range ips { if err := checkConstraints(cc.ip, ip, ip); err != nil { return err @@ -488,8 +495,8 @@ func (cc *chainConstraints) check(dns []string, uris []parsedURI, emails []parse } } for _, e := range emails { - if !domainNameValid(e.mailbox.domain, false) { - return fmt.Errorf("x509: cannot parse rfc822Name %q", e.mailbox) + if !domainNameValid(e.domain, false) { + return fmt.Errorf("x509: cannot parse rfc822Name %q", e) } if err := checkConstraints(cc.email, e, e); err != nil { return err @@ -509,7 +516,7 @@ func checkChainConstraints(chain []*Certificate) error { ip: constraints[*net.IPNet, net.IP]{"IP address", newIPNetConstraints(c.PermittedIPRanges), newIPNetConstraints(c.ExcludedIPRanges)}, dns: constraints[string, string]{"DNS name", newDNSConstraints(c.PermittedDNSDomains, true), newDNSConstraints(c.ExcludedDNSDomains, false)}, uri: constraints[string, string]{"URI", newDNSConstraints(c.PermittedURIDomains, true), newDNSConstraints(c.ExcludedURIDomains, false)}, - email: constraints[string, parsedEmail]{"email address", newEmailConstraints(c.PermittedEmailAddresses, true), newEmailConstraints(c.ExcludedEmailAddresses, false)}, + email: constraints[string, rfc2821Mailbox]{"email address", newEmailConstraints(c.PermittedEmailAddresses, true), newEmailConstraints(c.ExcludedEmailAddresses, false)}, index: i, } if currentConstraints == nil { @@ -592,24 +599,15 @@ func parseURIs(uris []*url.URL) ([]parsedURI, error) { return parsed, nil } -type parsedEmail struct { - email string - mailbox *rfc2821Mailbox -} - -func (e parsedEmail) String() string { - return e.mailbox.local + "@" + e.mailbox.domain -} - -func parseMailboxes(emails []string) ([]parsedEmail, error) { - parsed := make([]parsedEmail, 0, len(emails)) +func parseMailboxes(emails []string) ([]rfc2821Mailbox, error) { + parsed := make([]rfc2821Mailbox, 0, len(emails)) for _, email := range emails { mailbox, ok := parseRFC2821Mailbox(email) if !ok { return nil, fmt.Errorf("cannot parse rfc822Name %q", email) } mailbox.domain = strings.ToLower(mailbox.domain) - parsed = append(parsed, parsedEmail{strings.ToLower(email), &mailbox}) + parsed = append(parsed, mailbox) } return parsed, nil } diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go index 9a796e100d..b325c8edb9 100644 --- a/src/crypto/x509/name_constraints_test.go +++ b/src/crypto/x509/name_constraints_test.go @@ -1612,6 +1612,39 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:testexample.com"}, }, }, + { + name: "excluded email constraint, multiple email with matching local portion", + roots: []constraintsSpec{ + { + bad: []string{"email:a@example.com", "email:a@test.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"email:a@example.com"}, + }, + expectedError: "\"a@example.com\" is excluded by constraint \"a@example.com\"", + }, + { + name: "email_case_check", + roots: []constraintsSpec{ + { + ok: []string{"email:a@example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"email:a@ExAmple.com"}, + }, + }, } func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) { diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index 1301390bee..3d9c115dba 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -253,6 +253,10 @@ type rfc2821Mailbox struct { local, domain string } +func (s rfc2821Mailbox) String() string { + return fmt.Sprintf("%s@%s", s.local, s.domain) +} + // parseRFC2821Mailbox parses an email address into local and domain parts, // based on the ABNF for a “Mailbox” from RFC 2821. According to RFC 5280, // Section 4.2.1.6 that's correct for an rfc822Name from a certificate: “The |
