Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto/x509: NameConstraintsWithoutSANs when checking signing certificate #24151

Open
thsnr opened this issue Feb 27, 2018 · 59 comments
Open

crypto/x509: NameConstraintsWithoutSANs when checking signing certificate #24151

thsnr opened this issue Feb 27, 2018 · 59 comments
Assignees
Labels
Milestone

Comments

@thsnr
Copy link

@thsnr thsnr commented Feb 27, 2018

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
@agl
Copy link
Contributor

@agl agl commented 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
Copy link
Author

@thsnr thsnr commented Feb 27, 2018

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
Copy link
Contributor

@agl agl commented Feb 27, 2018

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

@thsnr
Copy link
Author

@thsnr thsnr commented Feb 27, 2018

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

@agl
Copy link
Contributor

@agl agl commented 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
Copy link
Author

@thsnr thsnr commented Feb 27, 2018

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
Copy link
Contributor

@agl agl commented 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
Copy link

@martinpaljak martinpaljak commented 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
Copy link
Author

@thsnr thsnr commented Feb 28, 2018

@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
Copy link
Contributor

@agl agl commented Feb 28, 2018

@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
Copy link
Author

@thsnr thsnr commented 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
Copy link
Author

@thsnr thsnr commented Mar 22, 2018

@agl Any updates on this?

@agl
Copy link
Contributor

@agl agl commented Mar 22, 2018

I believe the fix for this is scheduled for 1.10.1.

@rsc rsc added the Proposal-Crypto label Mar 26, 2018
@andybons
Copy link
Member

@andybons andybons commented Mar 26, 2018

@agl @FiloSottile can you dupe this into the 1.10.1 fix?

@andybons andybons added this to the Go1.10.1 milestone Mar 26, 2018
@FiloSottile
Copy link
Member

@FiloSottile FiloSottile commented Mar 26, 2018

AFAICT this one doesn't have a fix yet, and it still doesn't work on tip.

@agl
Copy link
Contributor

@agl agl commented Mar 26, 2018

Sorry, when I said above that "I believe the fix for this is scheduled for 1.10.1", I was confusing this with #23995 and didn't notice until Filippo just said that we didn't have a fix. I don't think we've actually decided whether we want to do something here.

@andybons
Copy link
Member

@andybons andybons commented Mar 27, 2018

OK. Then this is outside the 1.10.1 window. Moving to 1.11 for now.

@andybons andybons modified the milestones: Go1.10.1, Go1.11 Mar 27, 2018
@FiloSottile
Copy link
Member

@FiloSottile FiloSottile commented Mar 27, 2018

Should we consider dropping support for hostnames in CN altogether? The browsers managed to.

@FiloSottile FiloSottile modified the milestones: Go1.11, Go1.10.2 Mar 27, 2018
@svenheiberg
Copy link

@svenheiberg svenheiberg commented Mar 28, 2018

So far we have seen three potential solutions to this.

a) Drop support for hostnames in CN altogether.

b) Allow for NameConstraints without SANs in Certificate.isValid. Instead set a flag in the leaf Certificate struct noting that there were NameConstraints in the chain. Later, when calling Certificate.VerifyHostName (either directly or via Certificate.Verify with a DNSName) on the leaf, return NameConstraintsWithoutSANs if the flag is set and there are no SANs. This works because Certificate.VerifyHostname is never called for signing certificates.

c) Allow for NameConstraints without SANs in Certificate.isValid ONLY IF the CN of the leaf Certificate does not parse as a valid DNS name. Otherwise return NameConstraintsWithoutSANs. Later, when calling Certificate.VerifyHostname, ignore the CN if it does not parse. This works because signing certificates usually do not have names that parse as DNS names.

From the perspective of current issue all these solutions are equally good. Is there a reason not to implement one of those for go 1.11?

@user8547
Copy link

@user8547 user8547 commented Mar 28, 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.

@agl, can you assume that if CA uses DNS constraints then the certificates issued by that CA are new enough to know not to wedge DNS names into the CN?

Would such assumption lead to much simpler fix?

@gopherbot
Copy link

@gopherbot gopherbot commented Apr 1, 2018

Change https://golang.org/cl/103868 mentions this issue: crypto/x509: allow non-DNS name constraints without SANs

@agl
Copy link
Contributor

@agl agl commented Apr 1, 2018

Should we consider dropping support for hostnames in CN altogether? The browsers managed to.

That would be nice. However, 1.10 has been overly "exciting" w.r.t. certificate validation so I'm dialing towards being more conservative at the moment.

I think of all the options enumerated by @svenheiberg, I like (c) the most. However, the example certificate in given above has a CN of PIKMA,TIIT,<REDACTED>. It's not clear that's an invalid DNS name. We are quite accepting of DNS names and only ban absolute names, empty labels, and bytes 0–33, 126–255. There are a bunch of more restrictive rules in RFCs about the format of hostnames, but they have rotted over time and aren't actually true in practice any longer.

So, if the REDACTED part always contains a space then it would happen to work. I guess we could also ban commas, but it feels like we're really crafting a special case for these Estonian certificates in that case.

Thus I'm wondering about an option (d): require SANs for DNS name constraints only. (I think @user8547 might have been suggesting this just above.) The point of the error is to ensure that we don't have a gap between VerifyHostname and Verify, and we already ignore CN for IP addresses.

https://go-review.googlesource.com/c/go/+/103868 to do that.

@FiloSottile FiloSottile added this to the Go1.15 milestone Apr 29, 2020
@gopherbot
Copy link

@gopherbot gopherbot commented May 1, 2020

Change https://golang.org/cl/231379 mentions this issue: crypto/x509: ignore Common Name by default

@gopherbot
Copy link

@gopherbot gopherbot commented May 1, 2020

Change https://golang.org/cl/231381 mentions this issue: crypto/x509: treat hostnames with colons as invalid

@gopherbot
Copy link

@gopherbot gopherbot commented May 5, 2020

Change https://golang.org/cl/231380 mentions this issue: crypto/x509: treat certificate names with trailing dots as invalid

@gopherbot
Copy link

@gopherbot gopherbot commented May 7, 2020

Change https://golang.org/cl/231378 mentions this issue: crypto/x509: require perfect matches for invalid hostnames

gopherbot pushed a commit that referenced this issue May 8, 2020
When the input or SAN dNSNames are not valid hostnames, the specs don't
define what should happen, because this should ideally never happen, so
everything we do is undefined behavior. Browsers get to just return an
error, because browsers can assume that the resolving layer is DNS. We
can't, names can be resolved by anything implementing a Dial function,
and the crypto/x509 APIs can also be used directly without actual
networks in sight.

Trying to process invalid hostnames leads to issues like #27591 where
wildcards glob stuff they aren't expected to, because wildcards are only
defined on hostnames.

Try to rationalize the behavior like this: if both the VerifyHostname
input and the SAN dNSNames are a valid hostname, follow the specs;
otherwise, only accept perfect 1:1 case-insensitive matches (without
wildcards or trailing dot processing).

This should allow us to keep supporting weird names, with less
unexpected side-effects from undefined behavior. Also, it's a rule, even
if completely made up, so something we can reason about and code against.

The commonName field does allow any string, but no specs define how to
process it. Processing it differently from dNSNames would be confusing,
and allowing it to match invalid hostnames is incompatible with Name
Constraint processing (#24151).

This does encourage invalid dNSNames, regrettably, but we need some way
for the standard API to match weird names, and the alternative of
keeping CN alive sounds less appealing.

Fixes #27591

Change-Id: Id2d515f068a17ff796a32b30733abe44ad4f0339
Reviewed-on: https://go-review.googlesource.com/c/go/+/231378
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
gopherbot pushed a commit that referenced this issue May 8, 2020
Common Name has been deprecated for 20 years, and has horrible
interactions with Name Constraints. The browsers managed to drop it last
year, let's try flicking the switch to disabled by default.

Return helpful errors for things that would get unbroken by flipping the
switch back with the environment variable.

Had to refresh a test certificate that was too old to have SANs.

Updates #24151

Change-Id: I2ab78577fd936ba67969d3417284dbe46e4ae02f
Reviewed-on: https://go-review.googlesource.com/c/go/+/231379
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
gopherbot pushed a commit that referenced this issue May 8, 2020
Trailing dots are not allowed in certificate fields like CN and SANs
(while they are allowed and ignored as inputs to verification APIs).
Move to considering names with trailing dots in certificates as invalid
hostnames.

Following the rule of CL 231378, these invalid names lose wildcard
processing, but can still match if there is a 1:1 match, trailing dot
included, with the VerifyHostname input.

They also become ignored Common Name values regardless of the
GODEBUG=x509ignoreCN=X value, because we have to ignore invalid
hostnames in Common Name for #24151. The error message automatically
accounts for this, and doesn't suggest the environment variable. You
don't get to use a legacy deprecated field AND invalid hostnames.

(While at it, also consider wildcards in VerifyHostname inputs as
invalid hostnames, not that it should change any observed behavior.)

Change-Id: Iecdee8927df50c1d9daf904776b051de9f5e76ad
Reviewed-on: https://go-review.googlesource.com/c/go/+/231380
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
gopherbot pushed a commit that referenced this issue May 8, 2020
Colons are port separators, so it's risky to allow them in hostnames.
Per the CL 231377 rule, if we at least consider them invalid we will not
apply wildcard processing to them, making behavior a little more
predictable.

We were considering hostnames with colons valid (against spec) because
that meant we'd not ignore them in Common Name. (There was at least
one deployment that was putting colons in Common Name and expecting it
to verify.)

Now that Common Name is ignored by default, those clients will break
again, so it's a good time to drop the exception. Hopefully they moved
to SANs, where invalid hostnames are checked 1:1 (ignoring wildcards)
but still work. (If they didn't, this change means they can't use
GODEBUG=x509ignoreCN=0 to opt back in, but again you don't get to use a
legacy deprecated field AND invalid hostnames.)

Updates #24151

Change-Id: Id44b4fecb2d620480acdfc65fea1473f7abbca7f
Reviewed-on: https://go-review.googlesource.com/c/go/+/231381
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
@FiloSottile
Copy link
Member

@FiloSottile FiloSottile commented May 8, 2020

All Go 1.15 changes landed. Moving to Go 1.16 to drop the opt-out.

@shibe2
Copy link

@shibe2 shibe2 commented Jul 16, 2020

I get NameConstraintsWithoutSANs when verifying TLS client certificate. I have ClientAuth = VerifyClientCertIfGiven. Issuer certificate has name constraints, and leaf certificate doesn't have name constraints or alternative names. I use common names as usernames. Usernames are often match hostname syntax, so the invalid hostname trick doesn't work for me. What should I do? If I'm to add alternative names to client certificates, what kind should it be?

@FiloSottile
Copy link
Member

@FiloSottile FiloSottile commented Jul 16, 2020

@shibe2 I think the best solution for you would be to set GODEBUG=x509ignoreCN=1 which will suppress the error, and check the Common Name manually in VerifyPeerCertificate.

@shibe2
Copy link

@shibe2 shibe2 commented Jul 16, 2020

@FiloSottile Yeah, x509ignoreCN works for me. I take it, my certificates are correct then. Maybe the problem in Go is reliance on VerifyHostname functions that check only the leaf certificate, while to support name constraints, the full chain should be checked from root to leaf for a particular name.

I don't need to verify CN at handshake; I optionally use it after the secure connection is established.

@FiloSottile
Copy link
Member

@FiloSottile FiloSottile commented Jul 16, 2020

Your certificates are correct.

VerifyHostname not looking at the path is ok because we check name constraints at path verification time, making sure that all leaf SANs are allowed by the chain's name constraints.

The problem is that name constraints don't apply to CN, but Go currently considers CN a hostname. We are trying to fix that in Go 1.15 but a lot of things put hostnames in CN.

@shibe2
Copy link

@shibe2 shibe2 commented Jul 17, 2020

If one of the names fails name constraints, is only that name invalid or the whole certificate?

If an intermediate certificate has an invalid (because of constraints) alternative name, does that invalidate the leaf certificate even if all its names are valid?

If a certificate contains an alternative name of unknown type, and there is a constraint of that type, should the certificate be rejected altogether?

@agl
Copy link
Contributor

@agl agl commented Jul 17, 2020

If one of the names fails name constraints, is only that name invalid or the whole certificate?

The dominant model, which Go follows (now), is that a certificate is verified in the abstract, not in the context of a specific name. Thus the whole certificate should be invalid.

If an intermediate certificate has an invalid (because of constraints) alternative name, does that invalidate the leaf certificate even if all its names are valid?

Intermediates generally shouldn't have SANs. I don't think they should invalidate a chain if they do even if they don't match constraints. But you're getting into areas where I bet different libraries disagree.

If a certificate contains an alternative name of unknown type, and there is a constraint of that type, should the certificate be rejected altogether?

If a parent certificate has constraints of an unknown type, and the extension is critical, then that's a parse error. If not critical, then the constraint is ignored.

@shibe2
Copy link

@shibe2 shibe2 commented Jul 17, 2020

DirectoryName constraint does apply to subject DN. Also, like with DNS name and CN, rfc822Name constraint applies to emailAddress in DN in the absence of SAN.

@gopherbot
Copy link

@gopherbot gopherbot commented Jul 17, 2020

Change https://golang.org/cl/243221 mentions this issue: doc/go1.15: surface the crypto/x509 Common Name deprecation note

@agl
Copy link
Contributor

@agl agl commented Jul 17, 2020

like with DNS name and CN, rfc822Name constraint applies to emailAddress in DN in the absence of SAN.

The interaction of constraints and CN is a historical problem because of the legacy of repurposing the CN as a DNS name. Once that is eliminated, DNS constraints should only apply to DNS SANs in the same way that email constraints only apply to email SANs. (There is some historical practice of putting email addresses in a special type in a DN, but that's ignorable now and Go does ignore it.)

@shibe2
Copy link

@shibe2 shibe2 commented Jul 18, 2020

I did some tests for DNS and email constraints applied to SAN and dirname constraints applied to subject. Most implementations reject all certificates, including intermediates, that have any failing name. One exception is Wget, who doesn't check subjects and intermediate certificates. Go 1.15 beta 1 doesn't check intermediates, and it doesn't support dirname constraints.

@briansmith
Copy link

@briansmith briansmith commented Jul 18, 2020

According to the spec, the name constraints from issues apply to all certificates, including intermediates, issued by it, transitively.

Most implementations reject all certificates, including intermediates, that have any failing name.

That is the right thing to do.

Go 1.15 beta 1 doesn't check intermediates

If true, that seems wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.