-
Notifications
You must be signed in to change notification settings - Fork 18k
crypto/x509: http.Response.TLS.VerifiedChains behavior changed in go1.9 #24685
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
Comments
The more recent behavior is not wrong, as the certificate is both leaf and root. If anything, 1.8 was wrong not to catch that. I am not sure if we try to make VerifiedChains (which is just the return value of x509.Certificate.Verify) include all possible chains, but we don't promise that in the docs. Can you tell us more about why you need the full chain? |
Sure, that code example is extracted from gitlab-runner We perform a full CA chain verification and extraction on the host performing API calls to GitLab server; then we dump such chain inside the CI executor environment, which may be a container, a shell, a virtual machine even on a different host. When we upgraded to go 1.9 users started reporting the inability to clone |
@FiloSottile This is right, when the certificate found locally is a self-signed certificate. But @nolith mentioned a case, where user has a leaf certificate, that is not self-signed, stored in a system certificate directory. And this ends with a situation, where a For Git for example this is not enough to verify the requested HTTPS server, since there is no CA (self-signed) certificate available in the chain, that would verify site's certificate. Since I was already checking the internals of SSL verification in Go in context of other problem, I've decided to compare 1.8.x and 1.9.x to understand what caused this issue.
func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) { The part of the code that forces the leaf certificate to be returned as the chain (no matter if it's self-signed or not) is this This code was added with 8ad70a5 and it exists since August 2016. If this exists in that form for so long, but we've been hit by this after migrating to 1.9.x, I started to search what caused it. And I found it: e83bcd9#diff-7d6780a874c9e5d3777b2e560f75ec5dL32. Before this change, if a system CA file was found, then the e83bcd9 changed it, so both - system default CA file, and system default CAs directory - are loaded. It was done because:
Which seems to be a bug fix, so definitely something needed. I've tested a quick hack (bringing back the Conclusions: Loading both CA file and CAs directory is something that is expected. So change between 1.8 and 1.9 is valid. Also probably expected is that the certificate is used directly, if it's already loaded in roots pool. What I would propose here is to use the certificate directly only if it's self-signed. If not by default, then at least to make it configurable. For some cases a leaf, non-self-signed certificate is just unusable. |
Yes, that’s correct, but there is no rule saying roots need to be self-signed. The WebPKI is complex and it involves many cross-signatures, so it’s not uncommon to have what would be an intermediate elsewhere in a root CA pool. So if I understand correctly, the issue is that git does not work if the leaf is passed as a root? That sounds like a git bug to me. |
@FiloSottile Git uses According to
If the leaf certificate is not a root for itself (which is a case only of self-signed certificates), the above is not fulfilled. If I understand the documentation correctly, the chain configured by The general idea of PKI is that we don't need to know each one certificate - we only need to know and trust to a CA certificates that will sign the leaf ones. If we can't provide a full chain, then sure, let's provide at least the leaf certificate itself if it's in systems CA store. It will work for at least some software. But if we can, why to force a one-element chain with the leaf certificate instead of the full chain that really verifies the validity of it? 8ad70a5 pointed that I have a PoC of such change. I can open a PR and share it if there is a will of going in that direction. |
I've just had a chance to catch up with this discussion and examine the behavior with libcurl with OpenSSL. As @tmaczukin mentioned, curl will fail TLS verification if you provide the output of Is the Go implementation assuming that all certificates in If treating self-signed certificates as anchors isn't the right behavior, could we do as @tmaczukin suggests and make it possible for Go to continue verifying the chain if it finds a leaf certificate that happens to be in the root store? |
@FiloSottile , whats your view on provided arguments above? It is a bug, that Go doesn't always produce chain which can be verified by OpenSSL. |
I recently revisited this problem and there's been some changes since this was last discussed. curl not treating intermediate certificates in the trust store as trust-anchors is only true if the ssl backend is openssl.
The change in go1.9 made the behaviour similar to the majority of ssl backends (though openssl has yet to make git requires at least It would be nice if an option passed to |
The nature of trust anchors is subtle: at their core, they are just a Subject and a public key. Then, you might want to attach metadata to them, or even custom policies, such as "can only issue for these names", "can only issue for these purposes", and "can only issue until this date". The temptation to encode trust anchors as certificates is strong, and most libraries (Go and OpenSSL included) do that, but there is no consensus on how to do that. Proper root stores (Windows, macOS, ...) gave up on separating the trust anchor metadata and the verifier code, which is why we are moving to invoking the platform verifiers. Go has only one bit of metadata: trusted ( /cc @golang/security |
In the past, the runner needed to resolve a full TLS certificate chain, including the self-signed root, in order for Git clones to work over HTTPS. Go 1.9 changed the behavior to present a partial certificate chain if a trusted intermediate certificate were placed in the system certificate directory (golang/go#24685). https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/1581 worked around that change by restoring the Go 1.8 behavior of presenting the full chain in `CI_SERVER_TLS_CA_FILE`. libcurl v7.68 has since fixed the behavior to trust a certificate authority that is not self-signed (curl/curl@94f1f77). As a result, the need to resolve the full chain is no longer necessary. As long as there is a trusted certificate authority in the chain, the TLS connection can proceed. Go 1.18 modified `Certificate.Verify` to use the macOS and Windows-specific platform APIs. As a result, a root certificate signed with a SHA-1 certificate will be rejected, which prevents the runner from generating `CI_SERVER_TLS_CA_FILE`. This may cause Git clones to fail. This commit adds a feature flag, `FF_RESOLVE_FULL_TLS_CHAIN`, that is enabled by default. This flag makes it possible to disable this resolving of the full certificate chain. On most platforms, this can be disabled safely, assuming Git and other clients are compiled with an updated libcurl version. Relates to https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29373
Hello, I've found a behavior change in go1.9.x
What version of Go are you using (
go version
)?this is a comparison between
go version go1.9.5 linux/amd64
,go version go1.10.1 linux/amd64
andgo version go1.8.7 linux/amd64
Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?It affects linux, it can be reproduced using
golang
docker imagesWhat did you do?
I prepared a small POC with automated execution in CI.
https://gitlab.com/nolith-tests/go-lang-tls-connection-state/blob/master/main.go
The relevant part is in this function
This function will perform HTTPS HEAD request and then dump the
VerifiedChains
,in a vanilla Linux environment, this program behaves exactly the same if compiled with go1.8.7 or go1.9.5
But when the leaf certificate is added to
/etc/ssl/certs/
go1.8 will still dump the whole chain,but go1.9 will print only the leaf certificate skipping the rest of the chain.
The test has been automated with this
.gitlab-ci.yml
configurationWhat did you expect to see?
I did expect to have the same content in
http.Response.TLS.VerifiedChains
regardless of go version.What did you see instead?
go 1.8.7 output
go 1.9.5 output
go 1.10.1 output
compare 1.8.7 and 1.9.5 outputs
The text was updated successfully, but these errors were encountered: