Description
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
agl commentedon Feb 27, 2018
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 commentedon Feb 27, 2018
The name is specified in the Subject. For the example test certificate it is:
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 commentedon Feb 27, 2018
Do the in-use Estonian ID cards also have this property, or is this just a test CA?
thsnr commentedon Feb 27, 2018
Both the in-use and test chains have this property. Example OpenSSL output from actual chain:
ca.txt
cert.txt
agl commentedon Feb 27, 2018
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 commentedon Feb 27, 2018
Would this break the explicit goal set by Go 1.10?
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 callCertificate.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 bothCertificate.Verify
andCertificate.VerifyHostname
will succeed.agl commentedon Feb 27, 2018
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 commentedon Feb 28, 2018
@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 commentedon Feb 28, 2018
@agl What about moving the
NameConstraintsWithoutSANs
check intoCertificate.VerifyHostname
?We do not do any Name Constraints validation in
Certificate.isValid
if no SAN is present, but returnNameConstraintsWithoutSANs
fromCertificate.VerifyHostname
if we end up matching against the Common Name and the newCertificate.PermittedDNSDomains
orCertificate.ExcludedDNSDomains
fields are not empty.This would also work if only
Certificate.Verify
is called with aDNSName
, because all it does is callCertificate.VerifyHostname
afterCertificate.isValid
.EDIT: After reading the RFC in more detail, the following requirement should be kept in mind if considering my proposed solution:
The Estonian ID system does not set any constraints on the email, but this could affect some other CAs.
agl commentedon Feb 28, 2018
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 commentedon Feb 28, 2018
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 commentedon Mar 22, 2018
@agl Any updates on this?
agl commentedon Mar 22, 2018
I believe the fix for this is scheduled for 1.10.1.
110 remaining items