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

Closed
thsnr opened this issue Feb 27, 2018 · 65 comments
Closed

crypto/x509: NameConstraintsWithoutSANs when checking signing certificate #24151

thsnr opened this issue Feb 27, 2018 · 65 comments
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Milestone

Comments

@thsnr
Copy link

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
@FiloSottile FiloSottile added the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Feb 27, 2018
@agl
Copy link
Contributor

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 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 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 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 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 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 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

@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 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 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 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 commented Mar 22, 2018

@agl Any updates on this?

@agl
Copy link
Contributor

agl commented Mar 22, 2018

I believe the fix for this is scheduled for 1.10.1.

@rsc rsc added the Proposal-Crypto Proposal related to crypto packages or other security issues label Mar 26, 2018
@andybons
Copy link
Member

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

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

@agl
Copy link
Contributor

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

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
Contributor

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

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

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

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

@agl
Copy link
Contributor

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.

@shibe2
Copy link

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
Contributor

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 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 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 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

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 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 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 commented Jul 18, 2020

According to the spec, the name constraints from issuers 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.

@FiloSottile
Copy link
Contributor

The change (almost surprisingly) landed in Go 1.15, but I know of a few large companies using the opt-out while they transition. Let's keep it around until Go 1.17, and announce it will be removed in the Go 1.16 release notes.

@gopherbot
Copy link

Change https://golang.org/cl/266539 mentions this issue: doc/go1.16: pre-announce GODEBUG=x509ignoreCN=0 removal in Go 1.17

gopherbot pushed a commit that referenced this issue Dec 4, 2020
For #40700
Updates #24151

Change-Id: Id63dcaad238f7534bfce8902b8cb3efd8db5942d
Reviewed-on: https://go-review.googlesource.com/c/go/+/266539
Trust: Filippo Valsorda <filippo@golang.org>
Trust: Katie Hockman <katie@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
@odeke-em
Copy link
Member

@FiloSottile in #24151 (comment) you mentioned that we should document removal in Go1.16 release notes, then keep this issue around/open for Go1.17. With that I am going to move this milestone forward to Go1.17, but please feel free to revert or close if you need to. cc-ing @toothrot for release tracking/visibility.

@ianlancetaylor
Copy link
Contributor

Is something supposed to change for 1.17?

@FiloSottile
Copy link
Contributor

Yeah, we are removing the GODEBUG flag.

@gopherbot
Copy link

Change https://golang.org/cl/315209 mentions this issue: crypto/x509: remove GODEBUG=x509ignoreCN=0 flag

@golang golang locked and limited conversation to collaborators May 8, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests