Skip to content

Commit a17dc52

Browse files
committed
crypto/x509: add directory name constraints
Fixes golang#15196
1 parent 4aa1efe commit a17dc52

File tree

5 files changed

+1454
-23
lines changed

5 files changed

+1454
-23
lines changed

src/crypto/x509/parser.go

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -528,27 +528,40 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
528528
return false, errors.New("x509: empty name constraints extension")
529529
}
530530

531-
getValues := func(subtrees cryptobyte.String) (dnsNames []string, ips []*net.IPNet, emails, uriDomains []string, err error) {
531+
getValues := func(subtrees cryptobyte.String) (dirNames []pkix.RDNSequence, dnsNames []string, ips []*net.IPNet, emails, uriDomains []string, err error) {
532532
for !subtrees.Empty() {
533533
var seq, value cryptobyte.String
534534
var tag cryptobyte_asn1.Tag
535535
if !subtrees.ReadASN1(&seq, cryptobyte_asn1.SEQUENCE) ||
536536
!seq.ReadAnyASN1(&value, &tag) {
537-
return nil, nil, nil, nil, fmt.Errorf("x509: invalid NameConstraints extension")
537+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: invalid NameConstraints extension")
538538
}
539539

540540
var (
541-
dnsTag = cryptobyte_asn1.Tag(2).ContextSpecific()
542-
emailTag = cryptobyte_asn1.Tag(1).ContextSpecific()
543-
ipTag = cryptobyte_asn1.Tag(7).ContextSpecific()
544-
uriTag = cryptobyte_asn1.Tag(6).ContextSpecific()
541+
dirNameTag = cryptobyte_asn1.Tag(4).ContextSpecific().Constructed()
542+
dnsTag = cryptobyte_asn1.Tag(2).ContextSpecific()
543+
emailTag = cryptobyte_asn1.Tag(1).ContextSpecific()
544+
ipTag = cryptobyte_asn1.Tag(7).ContextSpecific()
545+
uriTag = cryptobyte_asn1.Tag(6).ContextSpecific()
545546
)
546547

547548
switch tag {
549+
case dirNameTag:
550+
551+
var dirName pkix.RDNSequence
552+
553+
if rest, err := asn1.Unmarshal(value, &dirName); err != nil {
554+
return nil, nil, nil, nil, nil, err
555+
} else if len(rest) != 0 {
556+
return nil, nil, nil, nil, nil, errors.New("x509: trailing data after dirname constraint")
557+
}
558+
559+
dirNames = append(dirNames, dirName)
560+
548561
case dnsTag:
549562
domain := string(value)
550563
if err := isIA5String(domain); err != nil {
551-
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
564+
return nil, nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
552565
}
553566

554567
trimmedDomain := domain
@@ -560,7 +573,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
560573
trimmedDomain = trimmedDomain[1:]
561574
}
562575
if _, ok := domainToReverseLabels(trimmedDomain); !ok {
563-
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain)
576+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain)
564577
}
565578
dnsNames = append(dnsNames, domain)
566579

@@ -578,26 +591,26 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
578591
mask = value[16:]
579592

580593
default:
581-
return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained value of length %d", l)
594+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained value of length %d", l)
582595
}
583596

584597
if !isValidIPMask(mask) {
585-
return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained invalid mask %x", mask)
598+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained invalid mask %x", mask)
586599
}
587600

588601
ips = append(ips, &net.IPNet{IP: net.IP(ip), Mask: net.IPMask(mask)})
589602

590603
case emailTag:
591604
constraint := string(value)
592605
if err := isIA5String(constraint); err != nil {
593-
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
606+
return nil, nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
594607
}
595608

596609
// If the constraint contains an @ then
597610
// it specifies an exact mailbox name.
598611
if strings.Contains(constraint, "@") {
599612
if _, ok := parseRFC2821Mailbox(constraint); !ok {
600-
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
613+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
601614
}
602615
} else {
603616
// Otherwise it's a domain name.
@@ -606,19 +619,19 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
606619
domain = domain[1:]
607620
}
608621
if _, ok := domainToReverseLabels(domain); !ok {
609-
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
622+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
610623
}
611624
}
612625
emails = append(emails, constraint)
613626

614627
case uriTag:
615628
domain := string(value)
616629
if err := isIA5String(domain); err != nil {
617-
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
630+
return nil, nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
618631
}
619632

620633
if net.ParseIP(domain) != nil {
621-
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", domain)
634+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", domain)
622635
}
623636

624637
trimmedDomain := domain
@@ -630,7 +643,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
630643
trimmedDomain = trimmedDomain[1:]
631644
}
632645
if _, ok := domainToReverseLabels(trimmedDomain); !ok {
633-
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain)
646+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain)
634647
}
635648
uriDomains = append(uriDomains, domain)
636649

@@ -639,13 +652,13 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
639652
}
640653
}
641654

642-
return dnsNames, ips, emails, uriDomains, nil
655+
return dirNames, dnsNames, ips, emails, uriDomains, nil
643656
}
644657

645-
if out.PermittedDNSDomains, out.PermittedIPRanges, out.PermittedEmailAddresses, out.PermittedURIDomains, err = getValues(permitted); err != nil {
658+
if out.PermittedDirNames, out.PermittedDNSDomains, out.PermittedIPRanges, out.PermittedEmailAddresses, out.PermittedURIDomains, err = getValues(permitted); err != nil {
646659
return false, err
647660
}
648-
if out.ExcludedDNSDomains, out.ExcludedIPRanges, out.ExcludedEmailAddresses, out.ExcludedURIDomains, err = getValues(excluded); err != nil {
661+
if out.ExcludedDirNames, out.ExcludedDNSDomains, out.ExcludedIPRanges, out.ExcludedEmailAddresses, out.ExcludedURIDomains, err = getValues(excluded); err != nil {
649662
return false, err
650663
}
651664
out.PermittedDNSDomainsCritical = e.Critical

src/crypto/x509/verify.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package x509
66

77
import (
88
"bytes"
9+
"crypto/x509/pkix"
10+
"encoding/asn1"
911
"errors"
1012
"fmt"
1113
"net"
@@ -491,6 +493,34 @@ func matchDomainConstraint(domain, constraint string) (bool, error) {
491493
return true, nil
492494
}
493495

496+
func matchDirNameConstraint(dirname pkix.RDNSequence, constraint pkix.RDNSequence) (bool, error) {
497+
498+
if len(constraint) > len(dirname) {
499+
return false, nil
500+
}
501+
for i, rdn := range constraint {
502+
if len(rdn) > len(dirname[i]) {
503+
return false, nil
504+
}
505+
for j, const_tv := range rdn {
506+
dirname_tv := dirname[i][j]
507+
if len(const_tv.Type) != len(dirname_tv.Type) {
508+
return false, nil
509+
}
510+
for k, _ := range const_tv.Type {
511+
if const_tv.Type[k] != dirname_tv.Type[k] {
512+
return false, nil
513+
}
514+
}
515+
if const_tv.Value != dirname_tv.Value {
516+
return false, nil
517+
}
518+
}
519+
}
520+
521+
return true, nil
522+
}
523+
494524
// checkNameConstraints checks that c permits a child certificate to claim the
495525
// given name, of type nameType. The argument parsedName contains the parsed
496526
// form of name, suitable for passing to the match function. The total number
@@ -597,6 +627,26 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
597627
leaf = currentChain[0]
598628
}
599629

630+
if (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints() {
631+
for _, cert := range currentChain {
632+
var subject pkix.RDNSequence
633+
634+
// cert.Subject.ToRDNSequence cannot be used as it ignores unknown RDN
635+
if rest, err := asn1.Unmarshal(cert.RawSubject, &subject); err != nil {
636+
return err
637+
} else if len(rest) != 0 {
638+
return errors.New("x509: trailing data after X.509 subject")
639+
}
640+
641+
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "directory Name", cert.Subject.String(), subject,
642+
func(parsedName, constraint interface{}) (bool, error) {
643+
return matchDirNameConstraint(parsedName.(pkix.RDNSequence), constraint.(pkix.RDNSequence))
644+
}, c.PermittedDirNames, c.ExcludedDirNames); err != nil {
645+
return err
646+
}
647+
}
648+
}
649+
600650
if (certType == intermediateCertificate || certType == rootCertificate) &&
601651
c.hasNameConstraints() && leaf.hasSANExtension() {
602652
err := forEachSAN(leaf.getSANExtension(), func(tag int, data []byte) error {

0 commit comments

Comments
 (0)