Skip to content

Commit

Permalink
crypto/x509: add directory name constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
luizluca committed Feb 27, 2023
1 parent de4748c commit 7d66a6a
Show file tree
Hide file tree
Showing 5 changed files with 1,453 additions and 23 deletions.
51 changes: 32 additions & 19 deletions src/crypto/x509/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -515,27 +515,40 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
return false, errors.New("x509: empty name constraints extension")
}

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

var (
dnsTag = cryptobyte_asn1.Tag(2).ContextSpecific()
emailTag = cryptobyte_asn1.Tag(1).ContextSpecific()
ipTag = cryptobyte_asn1.Tag(7).ContextSpecific()
uriTag = cryptobyte_asn1.Tag(6).ContextSpecific()
dirNameTag = cryptobyte_asn1.Tag(4).ContextSpecific().Constructed()
dnsTag = cryptobyte_asn1.Tag(2).ContextSpecific()
emailTag = cryptobyte_asn1.Tag(1).ContextSpecific()
ipTag = cryptobyte_asn1.Tag(7).ContextSpecific()
uriTag = cryptobyte_asn1.Tag(6).ContextSpecific()
)

switch tag {
case dirNameTag:

var dirName pkix.RDNSequence

if rest, err := asn1.Unmarshal(value, &dirName); err != nil {
return nil, nil, nil, nil, nil, err
} else if len(rest) != 0 {
return nil, nil, nil, nil, nil, errors.New("x509: trailing data after dirname constraint")
}

dirNames = append(dirNames, dirName)

case dnsTag:
domain := string(value)
if err := isIA5String(domain); err != nil {
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
return nil, nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
}

trimmedDomain := domain
Expand All @@ -547,7 +560,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
trimmedDomain = trimmedDomain[1:]
}
if _, ok := domainToReverseLabels(trimmedDomain); !ok {
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain)
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain)
}
dnsNames = append(dnsNames, domain)

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

default:
return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained value of length %d", l)
return nil, nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained value of length %d", l)
}

if !isValidIPMask(mask) {
return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained invalid mask %x", mask)
return nil, nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained invalid mask %x", mask)
}

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

case emailTag:
constraint := string(value)
if err := isIA5String(constraint); err != nil {
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
return nil, nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
}

// If the constraint contains an @ then
// it specifies an exact mailbox name.
if strings.Contains(constraint, "@") {
if _, ok := parseRFC2821Mailbox(constraint); !ok {
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
}
} else {
// Otherwise it's a domain name.
Expand All @@ -593,19 +606,19 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
domain = domain[1:]
}
if _, ok := domainToReverseLabels(domain); !ok {
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
}
}
emails = append(emails, constraint)

case uriTag:
domain := string(value)
if err := isIA5String(domain); err != nil {
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
return nil, nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
}

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

trimmedDomain := domain
Expand All @@ -617,7 +630,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
trimmedDomain = trimmedDomain[1:]
}
if _, ok := domainToReverseLabels(trimmedDomain); !ok {
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain)
return nil, nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain)
}
uriDomains = append(uriDomains, domain)

Expand All @@ -626,13 +639,13 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
}
}

return dnsNames, ips, emails, uriDomains, nil
return dirNames, dnsNames, ips, emails, uriDomains, nil
}

if out.PermittedDNSDomains, out.PermittedIPRanges, out.PermittedEmailAddresses, out.PermittedURIDomains, err = getValues(permitted); err != nil {
if out.PermittedDirNames, out.PermittedDNSDomains, out.PermittedIPRanges, out.PermittedEmailAddresses, out.PermittedURIDomains, err = getValues(permitted); err != nil {
return false, err
}
if out.ExcludedDNSDomains, out.ExcludedIPRanges, out.ExcludedEmailAddresses, out.ExcludedURIDomains, err = getValues(excluded); err != nil {
if out.ExcludedDirNames, out.ExcludedDNSDomains, out.ExcludedIPRanges, out.ExcludedEmailAddresses, out.ExcludedURIDomains, err = getValues(excluded); err != nil {
return false, err
}
out.PermittedDNSDomainsCritical = e.Critical
Expand Down
49 changes: 49 additions & 0 deletions src/crypto/x509/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"bytes"
"crypto"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"net"
Expand Down Expand Up @@ -493,6 +494,34 @@ func matchDomainConstraint(domain, constraint string) (bool, error) {
return true, nil
}

func matchDirNameConstraint(dirname pkix.RDNSequence, constraint pkix.RDNSequence) (bool, error) {

if len(constraint) > len(dirname) {
return false, nil
}
for i, rdn := range constraint {
if len(rdn) > len(dirname[i]) {
return false, nil
}
for j, const_tv := range rdn {
dirname_tv := dirname[i][j]
if len(const_tv.Type) != len(dirname_tv.Type) {
return false, nil
}
for k, _ := range const_tv.Type {
if const_tv.Type[k] != dirname_tv.Type[k] {
return false, nil
}
}
if const_tv.Value != dirname_tv.Value {
return false, nil
}
}
}

return true, nil
}

// checkNameConstraints checks that c permits a child certificate to claim the
// given name, of type nameType. The argument parsedName contains the parsed
// form of name, suitable for passing to the match function. The total number
Expand Down Expand Up @@ -599,6 +628,26 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
leaf = currentChain[0]
}

if (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints() {
for _, cert := range currentChain {
var subject pkix.RDNSequence

// cert.Subject.ToRDNSequence cannot be used as it ignores unknown RDN
if rest, err := asn1.Unmarshal(cert.RawSubject, &subject); err != nil {
return err
} else if len(rest) != 0 {
return errors.New("x509: trailing data after X.509 subject")
}

if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "directory Name", cert.Subject.String(), subject,
func(parsedName, constraint interface{}) (bool, error) {
return matchDirNameConstraint(parsedName.(pkix.RDNSequence), constraint.(pkix.RDNSequence))
}, c.PermittedDirNames, c.ExcludedDirNames); err != nil {
return err
}
}
}

if (certType == intermediateCertificate || certType == rootCertificate) &&
c.hasNameConstraints() {
toCheck := []*Certificate{}
Expand Down
Loading

0 comments on commit 7d66a6a

Please sign in to comment.