-
Notifications
You must be signed in to change notification settings - Fork 570
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
[TLS 1.3] Codepoints for ECDH w/ Brainpool (RFC 8734) #3810
base: master
Are you sure you want to change the base?
Conversation
c63a9aa
to
c16b9cb
Compare
c16b9cb
to
837453c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really unfortunate and unnecessarily complicates things. Just one comment on the curve string to group params matching.
@@ -10,7 +10,7 @@ signature_hashes = SHA-512 SHA-384 SHA-256 | |||
macs = AEAD SHA-384 SHA-256 | |||
key_exchange_methods = ECDH DH ECDHE_PSK | |||
signature_methods = ECDSA RSA DSA | |||
key_exchange_groups = brainpool512r1 brainpool384r1 brainpool256r1 secp521r1 secp384r1 secp256r1 ffdhe/ietf/4096 ffdhe/ietf/3072 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO the policy files should not be touched by this change. Users may find it confusing that brainpool curves are present twice here each, I think it would become a common source for error. "brainpool512r1" should be matched by the code to the corresponding code point automatically, just as Group_Params::to_algorithm_spec()
does the other way around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fully agree on the potential end user confusion. Though, I'm somewhat on the fence whether users may still need the additional flexibility in configuration that it brings.
Just to make sure we're on the same page: You're suggesting, that brainpool384r1
(for instance) should translate into advertising both code points (and potentially offering key share values for both in a TLS 1.3 ClientHello), right?
On the one hand, this would somewhat complicate the logic of the code point selection and introduce a potential source of bugs (as we will likely need to handle this Brainpool-specific special case in more than one place). On the other hand, it would prevent users from explicitly choosing one code point over the other.
This is explicitly not meant to be an objection, though. However, if we introduce the additional logic, I want to suggest a compromise: Let's add the following configuration options:
brainpool*r1
(the existing name) - add both code pointsbrainpool*r1_legacy
- add the old code points onlybrainpool*r1_tls13
- add the new code points only
That approach would also make the "special case" more explicit in the code base. The existing name could map to some special Group_Params_Code::BRAINPOOL_META_VALUE
which we can handle explicitly in a special case (and map to ::BRAINPOOL_TLS12
and ::BRAINPOOL_TLS13
).
Whatever we do: its a mess. 😢
What a totally unnecessary mess this is. Alas. One concern I have here is that we can't quite treat these two points as equivalent. In particular, IIUC, technically speaking if a client sends a 1.2+1.3 client hello that contains just a 1.2 brainpool curve ID, we must not use brainpool with 1.3. I'm not sure what the best fix is. One interesting "out" is that afaik RFC 8734 does not prohibit using the 1.3 brainpool IDs in TLS 1.2 also. But that will cause interop problems with stacks which only support the 1.2 IDs. |
What I think would be the best for users is to avoid having them to deal with the fact that there are two points at all. For the user, the only selection he should make is between the three |
I just looked at how OpenSSL solved this mess, and it seems they first implemented the approach I suggest, and then later refactored to what essentially this PR implements. |
FWIW: We tested this with OpenSSL 3.2.0 and it works with both TLS 1.2 and 1.3. Botan TLS policy:
OpenSSL CLI commands: # TLS 1.3
openssl s_client -connect localhost:50447 -debug -curves brainpoolP256r1tls13:brainpoolP256r1:secp256r1 -tls1_3
# TLS 1.2
openssl s_client -connect localhost:50447 -debug -curves brainpoolP256r1tls13:brainpoolP256r1:secp256r1 -tls1_2 |
What happens with these
|
Those cases don't result in a successful handshake. OpenSSL doesn't seem to offer the groups cross-version. Perhaps we should also try with OpenSSL as the server. Whether they accept TLS 1.3 with the legacy ID and vice versa.
|
By not successful do you mean the handshake ends up using P256? Or an alert occurs? |
In my particular example, it failed with "no common group". But I configured the botan-based server to exclusively support brainpool. Otherwise, it would just have used P256. |
This was required because the brainpool curves have two distinct sets of code points. For TLS 1.2 (and earlier) they are defined in RFC 7027 and for TLS 1.3 in RFC 8734.
Turns out that an OpenSSL server won't negotiate a connection with TLS 1.3 and the legacy code points either. I tested with a Botan client that is configured to insist on TLS 1.3 but that offers the legacy brainpool code points only. The OpenSSL server is configured to allow both TLS 1.2 and 1.3 and supports both the new and old brainpool code points. This server rejects the handshake with "no suitable key share". FWIW: OpenSSL prior to 3.2.0 -- that doesn't support the new code points yet -- also rejects negotiating brainpool (using the old code points) in TLS 1.3. |
837453c
to
72a9810
Compare
Yeah, I agree, that would be great from a user's perspective. And the experiments with OpenSSL above also support such an approach. I'll see tomorrow, if there's a reasonable way to implement it like this. I'm hoping to centralize the necessary mappings somehow. If we have to scatter them across the TLS implementation(s), it's too big of a mess with lots of room for future fuck-up, all for too little user benefit, in my opinion. |
The more I look at that, the worse it gets, frankly. Handling all the edge cases properly, requires special stuff in several locations of the code base. Those special cases are also non-trivial to test. With our current test infrastructure we would probably end up building end-to-end tests against OpenSSL 3.2+. When trying to build this as an abstract policy, one needs to mentally mix in hybrid key exchange algorithms, which we currently support in TLS 1.3 but not 1.2. I.e. that must not be offered when also offering TLS 1.2. In contrast to brainpool, where just the offered code point changes contextually. What @securitykernel suggested is neat for sure: i.e. hide this complexity from the user by transparently juggling the right code points depending on the available protocol version. Though, that's a completely new concept (i.e. contextually mapping one NamedGroup to one, the other or both code points, and vice versa) in our policy architecture on top of the whole mess this already is. As an illustration, here are some combinations to keep in mind when implementing this one way or the other:
All of that extra plumbing for the slim use case that Brainpool is at the end of the day. Long story short, I'd like to suggest to KISS! Let's introduce the new code points and don't do any additional handling or validation. That will result in Botan allowing to mix and match protocol version and code point revisions. And this might result in interop issues with other implementations. But it gives the library user full flexibility to work around interop issues for their specific use case. @securitykernel @randombit What do you think? |
IETF deprecated the code points for the brainpool curves that were used in TLS 1.2 and earlier. Those were initially defined in RFC 7027 (26-28). Therefore, for TLS 1.3 new code points were proposed in RFC 8734 (31-33). 😓
This basically introduced the new code points as aliases for the old ones (similarly to what we've done with some yet-to-be-defined code points for some hybrid algorithms).
Technically, for TLS 1.3 handshakes we should ensure that the new ones are used and vice versa. However, I think, for interoperability's sake, we should just let the user configure whatever combination they want.Also, I added
TLS::Group_Params::usable_in_version(Protocol_Version)
that returnsfalse
when the key exchange group is not compatible with the version parameter. Currently, that filters out the described brainpool constellations and also hybrid key exchanges in TLS 1.2. However, the filter is applied only when creating Client Hello messages. I.e. if we send a Client Hello that insists on TLS 1.3, only the new brainpool code points will be advertised and vice versa. A Client Hello that allows both protocol version may also advertise both brainpool code point families.I'm open for better suggestions, of course.