Skip to content

Commit 4f8fbd3

Browse files
committed
crypto/x509: add directory name constraints (WIP)
It is still missing tests. Fixes #15196
1 parent e25aa4f commit 4f8fbd3

File tree

2 files changed

+82
-19
lines changed

2 files changed

+82
-19
lines changed

src/crypto/x509/verify.go

Lines changed: 48 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"
@@ -516,6 +518,34 @@ func matchDomainConstraint(domain, constraint string) (bool, error) {
516518
return true, nil
517519
}
518520

521+
func matchDirNameConstraint(dirname pkix.RDNSequence, constraint *pkix.RDNSequence) (bool, error) {
522+
523+
if len(*constraint) > len(dirname) {
524+
return false, nil
525+
}
526+
for i, rdn := range *constraint {
527+
if len(rdn) > len(dirname[i]) {
528+
return false, nil
529+
}
530+
for j, const_tv := range rdn {
531+
dirname_tv := dirname[i][j]
532+
if len(const_tv.Type) != len(dirname_tv.Type) {
533+
return false, nil
534+
}
535+
for k, _ := range const_tv.Type {
536+
if const_tv.Type[k] != dirname_tv.Type[k] {
537+
return false, nil
538+
}
539+
}
540+
if const_tv.Value != dirname_tv.Value {
541+
return false, nil
542+
}
543+
}
544+
}
545+
546+
return true, nil
547+
}
548+
519549
// checkNameConstraints checks that c permits a child certificate to claim the
520550
// given name, of type nameType. The argument parsedName contains the parsed
521551
// form of name, suitable for passing to the match function. The total number
@@ -623,6 +653,24 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
623653
}
624654

625655
checkNameConstraints := (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints()
656+
if checkNameConstraints {
657+
var leafSubject pkix.RDNSequence
658+
659+
// leaf.Subject.ToRDNSequence cannot be used as it ignores unknown RDN
660+
if rest, err := asn1.Unmarshal(leaf.RawSubject, &leafSubject); err != nil {
661+
return err
662+
} else if len(rest) != 0 {
663+
return errors.New("x509: trailing data after X.509 subject")
664+
}
665+
666+
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "directory Name", leaf.Subject.String(), leafSubject,
667+
func(parsedName, constraint interface{}) (bool, error) {
668+
return matchDirNameConstraint(parsedName.(pkix.RDNSequence), constraint.(*pkix.RDNSequence))
669+
}, c.PermittedDirNames, c.ExcludedDirNames); err != nil {
670+
return err
671+
}
672+
}
673+
626674
if checkNameConstraints && leaf.commonNameAsHostname() {
627675
// This is the deprecated, legacy case of depending on the commonName as
628676
// a hostname. We don't enforce name constraints against the CN, but

src/crypto/x509/x509.go

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,8 @@ type Certificate struct {
748748

749749
// Name constraints
750750
PermittedDNSDomainsCritical bool // if true then the name constraints are marked critical.
751+
PermittedDirNames []*pkix.RDNSequence
752+
ExcludedDirNames []*pkix.RDNSequence
751753
PermittedDNSDomains []string
752754
ExcludedDNSDomains []string
753755
PermittedIPRanges []*net.IPNet
@@ -1211,27 +1213,28 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
12111213
return false, errors.New("x509: empty name constraints extension")
12121214
}
12131215

1214-
getValues := func(subtrees cryptobyte.String) (dnsNames []string, ips []*net.IPNet, emails, uriDomains []string, err error) {
1216+
getValues := func(subtrees cryptobyte.String) (dirNames []*pkix.RDNSequence, dnsNames []string, ips []*net.IPNet, emails, uriDomains []string, err error) {
12151217
for !subtrees.Empty() {
12161218
var seq, value cryptobyte.String
12171219
var tag cryptobyte_asn1.Tag
12181220
if !subtrees.ReadASN1(&seq, cryptobyte_asn1.SEQUENCE) ||
12191221
!seq.ReadAnyASN1(&value, &tag) {
1220-
return nil, nil, nil, nil, fmt.Errorf("x509: invalid NameConstraints extension")
1222+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: invalid NameConstraints extension")
12211223
}
12221224

12231225
var (
1224-
dnsTag = cryptobyte_asn1.Tag(2).ContextSpecific()
1225-
emailTag = cryptobyte_asn1.Tag(1).ContextSpecific()
1226-
ipTag = cryptobyte_asn1.Tag(7).ContextSpecific()
1227-
uriTag = cryptobyte_asn1.Tag(6).ContextSpecific()
1226+
dirNameTag = cryptobyte_asn1.Tag(4).ContextSpecific().Constructed()
1227+
dnsTag = cryptobyte_asn1.Tag(2).ContextSpecific()
1228+
emailTag = cryptobyte_asn1.Tag(1).ContextSpecific()
1229+
ipTag = cryptobyte_asn1.Tag(7).ContextSpecific()
1230+
uriTag = cryptobyte_asn1.Tag(6).ContextSpecific()
12281231
)
12291232

12301233
switch tag {
12311234
case dnsTag:
12321235
domain := string(value)
12331236
if err := isIA5String(domain); err != nil {
1234-
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
1237+
return nil, nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
12351238
}
12361239

12371240
trimmedDomain := domain
@@ -1243,10 +1246,22 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
12431246
trimmedDomain = trimmedDomain[1:]
12441247
}
12451248
if _, ok := domainToReverseLabels(trimmedDomain); !ok {
1246-
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain)
1249+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain)
12471250
}
12481251
dnsNames = append(dnsNames, domain)
12491252

1253+
case dirNameTag:
1254+
1255+
var dirName pkix.RDNSequence
1256+
1257+
if rest, err := asn1.Unmarshal(value, &dirName); err != nil {
1258+
return nil, nil, nil, nil, nil, err
1259+
} else if len(rest) != 0 {
1260+
return nil, nil, nil, nil, nil, errors.New("x509: trailing data after dirname constraint")
1261+
}
1262+
1263+
dirNames = append(dirNames, &dirName)
1264+
12501265
case ipTag:
12511266
l := len(value)
12521267
var ip, mask []byte
@@ -1261,26 +1276,26 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
12611276
mask = value[16:]
12621277

12631278
default:
1264-
return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained value of length %d", l)
1279+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained value of length %d", l)
12651280
}
12661281

12671282
if !isValidIPMask(mask) {
1268-
return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained invalid mask %x", mask)
1283+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained invalid mask %x", mask)
12691284
}
12701285

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

12731288
case emailTag:
12741289
constraint := string(value)
12751290
if err := isIA5String(constraint); err != nil {
1276-
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
1291+
return nil, nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
12771292
}
12781293

12791294
// If the constraint contains an @ then
12801295
// it specifies an exact mailbox name.
12811296
if strings.Contains(constraint, "@") {
12821297
if _, ok := parseRFC2821Mailbox(constraint); !ok {
1283-
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
1298+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
12841299
}
12851300
} else {
12861301
// Otherwise it's a domain name.
@@ -1289,19 +1304,19 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
12891304
domain = domain[1:]
12901305
}
12911306
if _, ok := domainToReverseLabels(domain); !ok {
1292-
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
1307+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
12931308
}
12941309
}
12951310
emails = append(emails, constraint)
12961311

12971312
case uriTag:
12981313
domain := string(value)
12991314
if err := isIA5String(domain); err != nil {
1300-
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
1315+
return nil, nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
13011316
}
13021317

13031318
if net.ParseIP(domain) != nil {
1304-
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", domain)
1319+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", domain)
13051320
}
13061321

13071322
trimmedDomain := domain
@@ -1313,7 +1328,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
13131328
trimmedDomain = trimmedDomain[1:]
13141329
}
13151330
if _, ok := domainToReverseLabels(trimmedDomain); !ok {
1316-
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain)
1331+
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain)
13171332
}
13181333
uriDomains = append(uriDomains, domain)
13191334

@@ -1322,13 +1337,13 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
13221337
}
13231338
}
13241339

1325-
return dnsNames, ips, emails, uriDomains, nil
1340+
return dirNames, dnsNames, ips, emails, uriDomains, nil
13261341
}
13271342

1328-
if out.PermittedDNSDomains, out.PermittedIPRanges, out.PermittedEmailAddresses, out.PermittedURIDomains, err = getValues(permitted); err != nil {
1343+
if out.PermittedDirNames, out.PermittedDNSDomains, out.PermittedIPRanges, out.PermittedEmailAddresses, out.PermittedURIDomains, err = getValues(permitted); err != nil {
13291344
return false, err
13301345
}
1331-
if out.ExcludedDNSDomains, out.ExcludedIPRanges, out.ExcludedEmailAddresses, out.ExcludedURIDomains, err = getValues(excluded); err != nil {
1346+
if out.ExcludedDirNames, out.ExcludedDNSDomains, out.ExcludedIPRanges, out.ExcludedEmailAddresses, out.ExcludedURIDomains, err = getValues(excluded); err != nil {
13321347
return false, err
13331348
}
13341349
out.PermittedDNSDomainsCritical = e.Critical

0 commit comments

Comments
 (0)