Skip to content

crypto/x509: NameConstraintsWithoutSANs when checking signing certificate #24151

Closed
@thsnr

Description

@thsnr

What did you do?

A CA which issues personal signing certificates has specified X.509 Name Constraints to exclude any DNS names and IP addresses:

X509v3 Name Constraints:
    Excluded:
      DNS:""
      IP:0.0.0.0/0.0.0.0
      IP:0:0:0:0:0:0:0:0/0:0:0:0:0:0:0:0

This is good practice to protect against misissued certificates.

Attempt to verify a test certificate issued by that CA: https://play.golang.org/p/y4l1JJqDQPs

What did you expect to see?

I expected the verification to succeed as it did in go 1.9 and earlier.

What did you see instead?

Starting from go 1.10, verification fails with NameConstraintsWithoutSANs:

x509: issuer has name constraints but leaf doesn't have a SAN extension

It is true that the signing certificates do not contain SAN extensions, because they have no need for one. This error did not trigger before, because when verifying a signing certificate, no DNS name is specified. But as stated in the change log for go 1.10:

Certificate.Verify now enforces the name constraints for all names contained in the certificate, not just the one name that a client has asked about.

I believe this is a bug, because RFC 5280 Section 4.2.1.10 regarding Name Constraints states:

Restrictions apply only when the specified name form is present. If no name of the type is in the certificate, the certificate is acceptable.

I understand this behavior is there for cases where we encounter a legacy TLS server certificate which relies on the Common Name as the hostname, but other certificates are now also hit by this. Maybe Certificate.Verify should distinguish between TLS server certificates and other X.509 certificates and have NameConstraintsWithoutSANs only trigger for the first ones?

Does this issue reproduce with the latest release (go1.10)?

Yes, go 1.10 is where it was introduced.

System details

go version go1.10rc2 linux/amd64
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/tiit/.cache/go-build"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/tiit/go"
GORACE=""
GOROOT="/usr/lib/go-1.10"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go-1.10/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build045278460=/tmp/go-build -gno-record-gcc-switches"
GOROOT/bin/go version: go version go1.10rc2 linux/amd64
GOROOT/bin/go tool compile -V: compile version go1.10rc2
uname -sr: Linux 4.14.0-3-amd64
Distributor ID:	Debian
Description:	Debian GNU/Linux testing (buster)
Release:	testing
Codename:	buster
/lib/x86_64-linux-gnu/libc.so.6: GNU C Library (Debian GLIBC 2.26-6) stable release version 2.26, by Roland McGrath et al.
gdb --version: GNU gdb (Debian 7.12-6+b1) 7.12.0.20161007-git

Activity

added
NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.
on Feb 27, 2018
agl

agl commented on Feb 27, 2018

@agl
Contributor

If there are no SANs in the certificate, then how are you assigning names to the leafs? crypto/x509 is intended to implement the WebPKI and, there, using common names as hostnames has been deprecated for years and support is being dropped in major clients.

thsnr

thsnr commented on Feb 27, 2018

@thsnr
Author

The name is specified in the Subject. For the example test certificate it is:

C = EE, O = ESTEID, OU = digital signature, CN = "\C5\BDAIKOVSKI,IGOR,37101010021", SN = \C5\BDAIKOVSKI, GN = IGOR, serialNumber = 37101010021

As mentioned, these are personal signing certificates (nonRepudiation/contentCommitment; specifically, signing certificates of the Estonian national ID-card) and not server certificates, so the Common Name is not used as a hostname, but just to identify the signing person.

I understand that the primary use case is TLS and WebPKI, but before this change, the package could also be successfully used for other RFC-compliant X.509 certificates.

agl

agl commented on Feb 27, 2018

@agl
Contributor

Do the in-use Estonian ID cards also have this property, or is this just a test CA?

thsnr

thsnr commented on Feb 27, 2018

@thsnr
Author

Both the in-use and test chains have this property. Example OpenSSL output from actual chain:
ca.txt
cert.txt

agl

agl commented on Feb 27, 2018

@agl
Contributor

This is fairly dodgy by the Estonian ID system: they're setting DNS constraints but expecting those constraints not to apply to the CN (where DNS names historically went) and yet are also not including the SAN extension to signal that the certificate is new enough to know not to wedge DNS names into the CN.

A workaround for this would look like ignoring name constraints when no SANs exist if no |DNSName| is requested in the VerifyOptions. It's a hack, but it is, admittedly only a small one. I'll code it up and see how it looks.

thsnr

thsnr commented on Feb 27, 2018

@thsnr
Author

Would this break the explicit goal set by Go 1.10?

As a result, after a certificate has been validated, now it can be trusted in its entirety. It is no longer necessary to revalidate the certificate for each additional name or key usage.

As I understand, the idea is that now I can do Certificate.Verify with an empty DNSName which checks all the names on the certificate and later just call Certificate.VerifyHostname with any hostname without having to reverify. With the proposed hack, if presented with a legacy TLS certificate that has no SAN and a Common Name which does NOT satisfy the Name Constraints of the issuer, then both Certificate.Verify and Certificate.VerifyHostname will succeed.

agl

agl commented on Feb 27, 2018

@agl
Contributor

Yes, that's a good point—it would break that.

We could add a flag on the Certificate to indicate that the CN shouldn't be used by a later VerifyHostname, but that seems fragile and surprising. We could drop support for stuffing DNS names in the CN, but that's likely to cause many more problems.

Unless someone has a clever suggestion, it's not clear to me that we should change anything here.

martinpaljak

martinpaljak commented on Feb 28, 2018

@martinpaljak

@agl Nowhere in https://golang.org/pkg/crypto/x509/ does it read that this package is (solely) about WebPKI. Might want to clarify that in the docs?

thsnr

thsnr commented on Feb 28, 2018

@thsnr
Author

@agl What about moving the NameConstraintsWithoutSANs check into Certificate.VerifyHostname?

We do not do any Name Constraints validation in Certificate.isValid if no SAN is present, but return NameConstraintsWithoutSANs from Certificate.VerifyHostname if we end up matching against the Common Name and the new Certificate.PermittedDNSDomains or Certificate.ExcludedDNSDomains fields are not empty.

This would also work if only Certificate.Verify is called with a DNSName, because all it does is call Certificate.VerifyHostname after Certificate.isValid.

EDIT: After reading the RFC in more detail, the following requirement should be kept in mind if considering my proposed solution:

Legacy implementations exist where an electronic mail address is embedded in the subject distinguished name in an attribute of type emailAddress (Section 4.1.2.6). When constraints are imposed on the rfc822Name name form, but the certificate does not include a subject alternative name, the rfc822Name constraint MUST be applied to the attribute of type emailAddress in the subject distinguished name.

The Estonian ID system does not set any constraints on the email, but this could affect some other CAs.

agl

agl commented on Feb 28, 2018

@agl
Contributor

@agl What about moving the NameConstraintsWithoutSANs check into Certificate.VerifyHostname?

VerifyHostname takes only a leaf certificate, but the property of whether or not constraints apply is a property of the validation chain. (I.e. the root certificate can trigger it.)

My best idea for this is to allow constraints without SANs if the CN doesn't parse as a valid DNS name, and have VerifyHostname ignore the CN if it doesn't parse.

thsnr

thsnr commented on Feb 28, 2018

@thsnr
Author

Ah, of course. For some reason I thought that the new *DNSDomains fields would include data from the entire chain, but it makes much more sense that they only reflect the extensions on the certificate itself. Maybe set some (unexported?) flag on the leaf Certificate showing if there were any Name Constraints in the chain?

Otherwise checking the CN would seem to work. Could consulting the Extended Key Usage for Server Authentication help in some way?

thsnr

thsnr commented on Mar 22, 2018

@thsnr
Author

@agl Any updates on this?

agl

agl commented on Mar 22, 2018

@agl
Contributor

I believe the fix for this is scheduled for 1.10.1.

110 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsFixThe path to resolution is known, but the work has not been done.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @briansmith@agl@martinpaljak@rsc@andybons

        Issue actions

          crypto/x509: NameConstraintsWithoutSANs when checking signing certificate · Issue #24151 · golang/go