crypto/x509: fix name constraints handling.

This change brings the behaviour of X.509 name constraints into line
with NSS[1]. In this area, the behavior specified by the RFC and by NIST
differs and this code follows the NIST behaviour.

[1] https://github.com/servo/nss/blob/master/lib/certdb/genname.c

Fixes #16347, fixes #14833.

Change-Id: I5acd1970041291c2e3936f5b1fd36f2a0338e613
Reviewed-on: https://go-review.googlesource.com/30155
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Adam Langley 2016-09-30 16:54:54 -07:00
parent 2d573eee8a
commit e4dafa3262
2 changed files with 52 additions and 6 deletions

View file

@ -154,6 +154,31 @@ const (
rootCertificate
)
func matchNameConstraint(domain, constraint string) bool {
// The meaning of zero length constraints is not specified, but this
// code follows NSS and accepts them as valid for everything.
if len(constraint) == 0 {
return true
}
if len(domain) < len(constraint) {
return false
}
prefixLen := len(domain) - len(constraint)
if !strings.EqualFold(domain[prefixLen:], constraint) {
return false
}
if prefixLen == 0 {
return true
}
isSubdomain := domain[prefixLen-1] == '.'
constraintHasLeadingDot := constraint[0] == '.'
return isSubdomain != constraintHasLeadingDot
}
// isValid performs validity checks on the c.
func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *VerifyOptions) error {
now := opts.CurrentTime
@ -166,12 +191,9 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
if len(c.PermittedDNSDomains) > 0 {
ok := false
for _, domain := range c.PermittedDNSDomains {
if opts.DNSName == domain ||
(strings.HasSuffix(opts.DNSName, domain) &&
len(opts.DNSName) >= 1+len(domain) &&
opts.DNSName[len(opts.DNSName)-len(domain)-1] == '.') {
ok = true
for _, constraint := range c.PermittedDNSDomains {
ok = matchNameConstraint(opts.DNSName, constraint)
if ok {
break
}
}

View file

@ -1164,6 +1164,30 @@ func TestUnknownAuthorityError(t *testing.T) {
}
}
var nameConstraintTests = []struct {
constraint, domain string
shouldMatch bool
}{
{"", "anything.com", true},
{"example.com", "example.com", true},
{"example.com", "ExAmPle.coM", true},
{"example.com", "exampl1.com", false},
{"example.com", "www.ExAmPle.coM", true},
{"example.com", "notexample.com", false},
{".example.com", "example.com", false},
{".example.com", "www.example.com", true},
{".example.com", "www..example.com", false},
}
func TestNameConstraints(t *testing.T) {
for i, test := range nameConstraintTests {
result := matchNameConstraint(test.domain, test.constraint)
if result != test.shouldMatch {
t.Errorf("unexpected result for test #%d: domain=%s, constraint=%s, result=%t", i, test.domain, test.constraint, result)
}
}
}
const selfSignedWithCommonName = `-----BEGIN CERTIFICATE-----
MIIDCjCCAfKgAwIBAgIBADANBgkqhkiG9w0BAQsFADAaMQswCQYDVQQKEwJjYTEL
MAkGA1UEAxMCY2EwHhcNMTYwODI4MTcwOTE4WhcNMjEwODI3MTcwOTE4WjAcMQsw