-
Notifications
You must be signed in to change notification settings - Fork 747
feat: Ability to set "strongly preferred" groups #5634
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
Conversation
1ff3582 to
906ffeb
Compare
c2b318a to
8746fc1
Compare
170c634 to
52ba908
Compare
| * without sending HRR. (The case of both being non-NULL should never occur, and | ||
| * is an error.) */ | ||
| POSIX_ENSURE((server_curve == NULL) != (server_kem_group == NULL), S2N_ERR_ECDHE_UNSUPPORTED_CURVE); | ||
| POSIX_ENSURE((server_curve == NULL) != (server_kem_group == NULL), S2N_ERR_INVALID_SUPPORTED_GROUP_STATE); |
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.
The PR description for this feature is really light. Can you explain how you've implemented "strongly preferred groups" there? How does this feature interact with our existing behavior of doing HRRs when clients support PQ but don't send a PQ keyshare? Is it the same mechanism used here or not?
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.
Can you explain how you've implemented "strongly preferred groups" there? ç
Added more detailed description to the PR. Overall the mechanism is described in #5602.
Can you explain how you've implemented "strongly preferred groups" there? How does this feature interact with our existing behavior of doing HRRs when clients support PQ but don't send a PQ keyshare?
I added one new check for a mutually supported strongly-preferred group before the rest of s2n's existing SupportedGroup negotiation logic.
After this change, s2n's new negotiation logic will be:
- Strongly-Preferred Groups (using server preference order)
- Any PQ SupportedGroup in 1-RTT (using server preference order)
- Any PQ SupportedGroup in 2-RTT (using server preference order)
- Any ECDHE Group in 1-RTT (using server preference order)
- Any mutually supported group in 2-RTT (using server preference order)
Is it the same mechanism used here or not?
Yes, it's a new check before all the same mechanisms are used. All of this KeyShare negotiation logic exists in the function int s2n_extensions_server_key_share_select(struct s2n_connection *conn). Each of these 5 cases is annotated with a code comment. s2n_extensions_server_key_share_select() is responsible for updating and setting exactly one of conn->kex_params.server_ecc_evp_params.negotiated_curve and conn->kex_params.server_kem_group_params.kem_group to the SupportedGroup that s2n would like to negotiate, and (optionally) calling s2n_set_hello_retry_required(conn) if selecting that SupportedGroup would require a 2-RTT handshake.
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.
Couldn't you reuse the strongly-preferred group mechanism for the PQ groups? You would have to edit all existing PQ policies to include strongly-preferred groups(and probably include a unit test making sure future PQ policies add their PQ groups to the strongly-preferred groups mechanism), but it would be cleaner than having 5 distinct prioritizations.
Then I think it would look like:
- Any Strongly-Preferred Group 1 RTT or 2 RTT regardless (using server preference order)
- Any mutually supported group in 1-RTT (using server preference order)
- Any mutually supported group in 2-RTT (using server preference order)
I haven't quite thought through the consequences of this design, but I'm curious as to why you chose to go with a separate prioritization method rather than merging with the PQ prioritization design.
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.
Merging the "Strongly Preferred" and "PQ" groups would result in different behavior that we wouldn't want. If we added x25519MLKEM768 to the strongly preferred list, but a client sent p256MLKEM768 (or any other PQ) KeyShare, we'd throw away the p256MLKEM768 KeyShare and perform a 2-RTT handshake with x25519MLKEM768.
The "strongly preferred" mechanism is a heavy-handed hammer for forcing a specific small subset of SupportedGroups to be negotiated for compliance-related security policies (like CNSA/FIPS) that we are willing to always take the 2-RTT performance impact for, while still offering backwards compatibility as a last resort to avoid breaking clients.
Our default negotiation mechanism for everything else without a strongly preferred preference list is that we will accept any PQ KeyShare in 1-RTT, and will prioritize a 2-RTT PQ KeyShare over a 1-RTT ECDHE KeyShare.
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.
Then I think it would look like:
- Any Strongly-Preferred Group 1 RTT or 2 RTT regardless (using server preference order)
- Any mutually supported group in 1-RTT (using server preference order)
- Any mutually supported group in 2-RTT (using server preference order)
This logic has a problem in the following case:
Server Preference:
- MLKEM1024, secp384r1, secp256r1
- strongly_preferred: MLKEM1024, secp384r1
Client Hello:
key_share: p384
supported_group: MLKEM1024, p384, p256
We'd negotiate p384 in 1-RTT instead of MLKEM1024 in 2-RTT
8d5d00a to
f8670b5
Compare
| BEGIN_TEST(); | ||
|
|
||
| /* Enforce minimum requirements on all security policies that have strongly preferred groups */ | ||
| for (size_t policy_index = 0; security_policy_selection[policy_index].version != NULL; policy_index++) { |
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 think that (for now) we should enforce that the strongly_preferred_groups feature is only ever used on completely ECC policies.
Trying to reason through PQ preferences vs strongly preferred groups is a bit 😵
I agree with Madeleine that the path forward to have some unified way of enforcing the "compliance/alignment" preferences AND PQ preferences. I think this is pretty closely related (imo) of unifying ECC/KEM handling for key exchange. And I think this would also be the time to tackle generating multiple key shares for the client.
But I think that the approach in the current PR is sufficient to satisfying our goals for the ECC case.
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 think that will also make some of the strongly preferred group logic a bit easier to handle, since we currently store PQ group and ECC groups in separate lists.
Co-authored-by: Jou Ho <[email protected]>
f8670b5 to
efd62de
Compare
jmayclin
left a comment
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.
A few nits.
I think generally I'd prefer to remove the PQ-only stuff since we aren't allowing that rn. Was the thought to keep it in in case it's used in the future?
|
|
||
| POSIX_ENSURE_REF(security_policy->kem_preferences); | ||
|
|
||
| /* Temporarily require that policies either have PQ or strongly supported groups, but not both. */ |
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.
| /* Temporarily require that policies either have PQ or strongly supported groups, but not both. */ | |
| /* Temporarily require that policies don't allow both PQ and strongly supported groups. */ |
| for (size_t i = 0; i < security_policy->kem_preferences->tls13_kem_group_count; i++) { | ||
| const struct s2n_kem_group *kem_group = security_policy->kem_preferences->tls13_kem_groups[i]; | ||
| POSIX_ENSURE_REF(kem_group); | ||
| ordered_supported_groups[ordered_supported_group_count++] = kem_group->iana_id; | ||
| } |
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 think we can remove this now that we've enforcing no PQ.
| for (size_t i = 0; i < security_policy->kem_preferences->tls13_kem_group_count; i++) { | |
| const struct s2n_kem_group *kem_group = security_policy->kem_preferences->tls13_kem_groups[i]; | |
| POSIX_ENSURE_REF(kem_group); | |
| ordered_supported_groups[ordered_supported_group_count++] = kem_group->iana_id; | |
| } |
| } | ||
|
|
||
| END_TEST(); | ||
| } No newline at end of file |
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.
| } | |
| } | |
| const uint16_t cnsa_1_2_supported_group_iana_ids[] = { | ||
| TLS_PQ_KEM_GROUP_ID_MLKEM_1024, | ||
| TLS_EC_CURVE_SECP_384_R1 | ||
| }; | ||
|
|
||
| const uint16_t cnsa_2_supported_group_iana_ids[] = { | ||
| TLS_PQ_KEM_GROUP_ID_MLKEM_1024 | ||
| }; |
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.
Since these can't be used for now, I'd vote to nuke em
| const uint16_t cnsa_1_2_supported_group_iana_ids[] = { | |
| TLS_PQ_KEM_GROUP_ID_MLKEM_1024, | |
| TLS_EC_CURVE_SECP_384_R1 | |
| }; | |
| const uint16_t cnsa_2_supported_group_iana_ids[] = { | |
| TLS_PQ_KEM_GROUP_ID_MLKEM_1024 | |
| }; |
| const struct s2n_supported_group_preferences cnsa_1_2_strong_preference = { | ||
| .count = s2n_array_len(cnsa_1_2_supported_group_iana_ids), | ||
| .iana_ids = cnsa_1_2_supported_group_iana_ids | ||
| }; | ||
|
|
||
| const struct s2n_supported_group_preferences cnsa_2_strong_preference = { | ||
| .count = s2n_array_len(cnsa_2_supported_group_iana_ids), | ||
| .iana_ids = cnsa_2_supported_group_iana_ids | ||
| }; |
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.
| const struct s2n_supported_group_preferences cnsa_1_2_strong_preference = { | |
| .count = s2n_array_len(cnsa_1_2_supported_group_iana_ids), | |
| .iana_ids = cnsa_1_2_supported_group_iana_ids | |
| }; | |
| const struct s2n_supported_group_preferences cnsa_2_strong_preference = { | |
| .count = s2n_array_len(cnsa_2_supported_group_iana_ids), | |
| .iana_ids = cnsa_2_supported_group_iana_ids | |
| }; |
| for (int j = 0; j < S2N_KEM_GROUPS_COUNT && !matched_strongly_preferred_iana; j++) { | ||
| const struct s2n_kem_group *mutually_supported_kem_group = conn->kex_params.mutually_supported_kem_groups[j]; | ||
| if (mutually_supported_kem_group == NULL) { | ||
| break; /* Reached end of mutually supported KEM Groups */ | ||
| } | ||
| if (strongly_preferred_iana == mutually_supported_kem_group->iana_id) { | ||
| matched_strongly_preferred_iana = true; | ||
| strongly_preferred_kem_group = mutually_supported_kem_group; | ||
|
|
||
| /* Check if we can negotiate our strongly preferred KEM Group in 1-RTT */ | ||
| if (client_kem_group != NULL && (strongly_preferred_iana == client_kem_group->iana_id)) { | ||
| need_hrr_for_strongly_preferred_group = false; | ||
| } else { | ||
| need_hrr_for_strongly_preferred_group = true; | ||
| } | ||
| } | ||
| } |
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.
| for (int j = 0; j < S2N_KEM_GROUPS_COUNT && !matched_strongly_preferred_iana; j++) { | |
| const struct s2n_kem_group *mutually_supported_kem_group = conn->kex_params.mutually_supported_kem_groups[j]; | |
| if (mutually_supported_kem_group == NULL) { | |
| break; /* Reached end of mutually supported KEM Groups */ | |
| } | |
| if (strongly_preferred_iana == mutually_supported_kem_group->iana_id) { | |
| matched_strongly_preferred_iana = true; | |
| strongly_preferred_kem_group = mutually_supported_kem_group; | |
| /* Check if we can negotiate our strongly preferred KEM Group in 1-RTT */ | |
| if (client_kem_group != NULL && (strongly_preferred_iana == client_kem_group->iana_id)) { | |
| need_hrr_for_strongly_preferred_group = false; | |
| } else { | |
| need_hrr_for_strongly_preferred_group = true; | |
| } | |
| } | |
| } | |
| /* strongly preferred groups and not allowed on policies that support PQ, so we don't check KEMs */ |
Goal
Resolves #5602
Adds a new strongly preferred groups field to s2n_security_policy. This preference list represents the set of SupportedGroups that s2n's TLS server is willing to perform a 2-RTT handshake for if that SupportedGroup was sent in the SupportedGroups extension, but not the KeyShare extension.
If an s2n security policy contains strongly preferred groups, then s2n's usual negotiation logic to prefer KeyShares that are negotiable in 1-RTT when possible is bypassed, and the first mutually supported strongly-preferred SupportedGroup will be selected regardless if it is negotiable in 1-RTT or 2-RTT. If no mutually supported strongly-preferred SupportedGroup exists, the s2n's default logic is skipped.
After this change, s2n's new negotiation logic will be:
1. Any Strongly-Preferred Group (using server preference order)
2. Any PQ SupportedGroup in 1-RTT (using server preference order)
3. Any PQ SupportedGroup in 2-RTT (using server preference order)
4. Any ECDHE Group in 1-RTT (using server preference order)
5. Any mutually supported group in 2-RTT (using server preference order)
Why
Needed to strongly prefer p384 for CNSA policies to force a 2-RTT handshake to select p384 even if p256 is negotiable in 1-RTT. With this logic, TLS Clients that do support p384 will be always be upgraded to p384, and clients that only support p256 will continue negotiating p256 without causing breakages. Then once this CNSA transition policy is deployed we can measure how many TLS connections do not support p384 and we can work on tracking down and fixing those clients.
How
Adds a new field to s2n_security_policy that is checked in
s2n_extensions_server_key_share_select()that overrides s2n's default KeyShare negotiation logic as per the description in #5602.Callouts
PR is based on top of #5615
Testing
Unit tests confirm new negotiation behavior for CNSA transition policies
Related
Resolves #5602
release summary: Added support for Security Policies to have "strongly preferred" SupportedGroups
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.