You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This was discovered during testing between a partner and Hashicorp. This partner's TLSv1.2-only (third-party) client refused to connect to a Vault Go TLS server.
Analysis of the Client Hello sent by this program showed that it sent the supported groups extension (expecting to enable forward-secrecy via ECDHE), but elided the (RFC 8422) Client Hello Point Format extension. This client was configured to only send ECDHE algorithms (e.g., TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), so the handshake failed with a cipher suite mismatch handshake exception. However, this client supports the uncompressed point format and so its expected the handshake should succeed.
In replicating this behavior against other popular open TLS servers (NSS, GnuTLS, and OpenSSL), Go was alone in refusing the handshake. All the others would accept the handshake and use uncompressed point format for ECHDE negotiation.
RFC 4492 specified that if this extension is missing, it means that only the uncompressed point format is supported, so interoperability with implementations that support the uncompressed format should work with or without the extension.
The reference to RFC 8422 and RFC 4492 comes via the Go source, implying that this behavior should've been supported by Go.
In particular, in this helper function (refactored from the pre-Go 1.14 code which had the same bug):
we see that the value for supportsPointFormat is strictly computed from the extension if it is non-empty and present. However, if the extension is elided altogether, it retains its default value of false. This is incorrect per the referenced RFCs.
(the hex result decodes to alert / Handshake Failure).
What did you expect to see?
Handshake success.
What did you see instead?
Handshake failure.
Possible Fixes
As I understand it, there's two ways of fixing this: we could use presence of Supported Groups (+ECDHE suites) as sufficient evidence towards ECDHE support for uncompressed point formats (easy) -- or prefer to negotiate a non-ECDHE cipher ahead of a ECDHE-cipher.
In the former, case this looks like changing the helper above to define supportsPointFormat := len(supportedPoints) == 0, to allow an unspecified extension to accept ECDHE negotiation automatically. This would fix the value for hs.ecdheOk and allow ECDHE to succeed unconditionally. The downside of this approach is that we could choose a ECDHE cipher when the client doesn't actually support Uncompressed point format, which I think is rarely the case (especially given both RFC 8422 and 4492 above). The client would then fail the handshake, and could probably provide justification to fix the client.
n.b.: during unmarshalling, we already check that the wire supportedPoints length is non-empty in the extension, so the len check is correct.
Otherwise, we could also condition it around having only ECDHE (and DHE? Hmmmm... ) cipher suites in the first case (i.e., something like len(supportedPoints) == 0 && len(nonECDHEClientHelloCipherSuites) == 0. The downside of this approach is that we could elide forward secrecy in the scenario when DHE isn't negotiated (impossible with a Go server AFAICT?) and the client was hoping for ECDHE. Instead, we'd end up on a legacy suite without Forward Secrecy, which is definitely less than ideal.
IMO, I'd prefer the first approach.
Either way, I'm happy to write a patch with test cases for this scenario. Let me know what the thoughts are w.r.t. approach.
The text was updated successfully, but these errors were encountered:
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes; it is present on
master
.What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
This was discovered during testing between a partner and Hashicorp. This partner's TLSv1.2-only (third-party) client refused to connect to a Vault Go TLS server.
Analysis of the Client Hello sent by this program showed that it sent the supported groups extension (expecting to enable forward-secrecy via ECDHE), but elided the (RFC 8422) Client Hello Point Format extension. This client was configured to only send ECDHE algorithms (e.g.,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
), so the handshake failed with a cipher suite mismatch handshake exception. However, this client supports the uncompressed point format and so its expected the handshake should succeed.In replicating this behavior against other popular open TLS servers (NSS, GnuTLS, and OpenSSL), Go was alone in refusing the handshake. All the others would accept the handshake and use uncompressed point format for ECHDE negotiation.
Per RFC 8422 Section 5.1.2:
The reference to RFC 8422 and RFC 4492 comes via the Go source, implying that this behavior should've been supported by Go.
In particular, in this helper function (refactored from the pre-Go 1.14 code which had the same bug):
go/src/crypto/tls/handshake_server.go
Lines 306 to 326 in 4248146
we see that the value for
supportsPointFormat
is strictly computed from the extension if it is non-empty and present. However, if the extension is elided altogether, it retains its default value offalse
. This is incorrect per the referenced RFCs.This is reproducible via the following Go program:
With the packet stream (see attached pcap from tlsfuzzer scenario above):
tlsfuzzer-repo-pcapng.zip
(the hex result decodes to alert / Handshake Failure).
What did you expect to see?
Handshake success.
What did you see instead?
Handshake failure.
Possible Fixes
As I understand it, there's two ways of fixing this: we could use presence of Supported Groups (+ECDHE suites) as sufficient evidence towards ECDHE support for uncompressed point formats (easy) -- or prefer to negotiate a non-ECDHE cipher ahead of a ECDHE-cipher.
supportsPointFormat := len(supportedPoints) == 0
, to allow an unspecified extension to accept ECDHE negotiation automatically. This would fix the value forhs.ecdheOk
and allow ECDHE to succeed unconditionally. The downside of this approach is that we could choose a ECDHE cipher when the client doesn't actually support Uncompressed point format, which I think is rarely the case (especially given both RFC 8422 and 4492 above). The client would then fail the handshake, and could probably provide justification to fix the client.len(supportedPoints) == 0 && len(nonECDHEClientHelloCipherSuites) == 0
. The downside of this approach is that we could elide forward secrecy in the scenario when DHE isn't negotiated (impossible with a Go server AFAICT?) and the client was hoping for ECDHE. Instead, we'd end up on a legacy suite without Forward Secrecy, which is definitely less than ideal.IMO, I'd prefer the first approach.
Either way, I'm happy to write a patch with test cases for this scenario. Let me know what the thoughts are w.r.t. approach.
The text was updated successfully, but these errors were encountered: