Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: AWS-CRT-SDK-TLSv1.3-2025-PQ-KX-Required
min version: TLS1.3
rules:
- Perfect Forward Secrecy: yes
- FIPS 140-3 (2019): no
cipher suites:
- TLS_AES_128_GCM_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
signature schemes:
- mldsa44
- mldsa65
- mldsa87
- ecdsa_sha256
- ecdsa_sha384
- ecdsa_sha512
- rsa_pss_pss_sha256
- rsa_pss_pss_sha384
- rsa_pss_pss_sha512
- rsa_pss_rsae_sha256
- rsa_pss_rsae_sha384
- rsa_pss_rsae_sha512
- rsa_pkcs1_sha256
- rsa_pkcs1_sha384
- rsa_pkcs1_sha512
curves:
pq:
- revision: 5
- kems:
- kem groups:
-- X25519MLKEM768
-- SecP256r1MLKEM768
16 changes: 16 additions & 0 deletions tests/unit/s2n_security_policies_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,22 @@ int main(int argc, char **argv)
EXPECT_FAILURE_WITH_ERRNO(s2n_find_security_policy_from_version("PQ-SIKE-TEST-TLS-1-0-2020-02", &security_policy), S2N_ERR_DEPRECATED_SECURITY_POLICY);
}

/* Test PQ KeyExchange Required policies */
{
EXPECT_SUCCESS(s2n_find_security_policy_from_version("AWS-CRT-SDK-TLSv1.3-2025-PQ-KX-Required", &security_policy));

if (s2n_libcrypto_supports_mlkem()) {
EXPECT_SUCCESS(s2n_security_policy_is_available(&security_policy_aws_crt_sdk_tls_13_06_25_pq_kx_required));
} else {
s2n_error expected_error = S2N_ERR_API_UNSUPPORTED_BY_LIBCRYPTO;
if (security_policy_aws_crt_sdk_tls_13_06_25_pq_kx_required.minimum_protocol_version > s2n_get_highest_fully_supported_tls_version()) {
expected_error = S2N_ERR_PROTOCOL_VERSION_UNSUPPORTED;
}
EXPECT_FAILURE_WITH_ERRNO(s2n_security_policy_is_available(&security_policy_aws_crt_sdk_tls_13_06_25_pq_kx_required), expected_error);
EXPECT_FAILURE_WITH_ERRNO(s2n_config_set_cipher_preferences(NULL, "AWS-CRT-SDK-TLSv1.3-2025-PQ-KX-Required"), expected_error);
}
}

/* Test common known good cipher suites for expected configuration */
{
EXPECT_SUCCESS(s2n_find_security_policy_from_version("default", &security_policy));
Expand Down
141 changes: 119 additions & 22 deletions tests/unit/s2n_tls13_pq_handshake_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,37 @@ const struct s2n_ecc_named_curve *s2n_get_predicted_negotiated_ecdhe_curve(const
return NULL;
}

int s2n_test_tls13_pq_handshake_cleanup(struct s2n_stuffer *client_to_server, struct s2n_stuffer *server_to_client,
struct s2n_connection *client_conn, struct s2n_connection *server_conn, struct s2n_cert_chain_and_key *chain_and_key,
struct s2n_config *client_config, struct s2n_config *server_config)
{
POSIX_GUARD(s2n_stuffer_free(client_to_server));
POSIX_GUARD(s2n_stuffer_free(server_to_client));

POSIX_GUARD(s2n_connection_free(client_conn));
POSIX_GUARD(s2n_connection_free(server_conn));

POSIX_GUARD(s2n_cert_chain_and_key_free(chain_and_key));
POSIX_GUARD(s2n_config_free(server_config));
POSIX_GUARD(s2n_config_free(client_config));

return S2N_SUCCESS;
}

int s2n_test_tls13_pq_handshake(const struct s2n_security_policy *client_sec_policy,
const struct s2n_security_policy *server_sec_policy, const struct s2n_kem_group *expected_kem_group,
const struct s2n_ecc_named_curve *expected_curve, bool hrr_expected, bool len_prefix_expected)
const struct s2n_security_policy *server_sec_policy, const bool expected_handshake_success, const int expected_s2nerrno,
Copy link
Contributor

@jmayclin jmayclin Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like the number of arguments in this method is reaching a bit of a tipping point with the number of arguments. I think we have some more general methods for "handshake two connections" and I wonder if that might be neater?

  1. construct configs from supplied security policy
  2. negotiate with s2n_negotiate_test_server_and_client
  3. assert negotiation succeeded or expected errno
  4. check HRR status from the handshake type API
  5. check negotiated group with the key exchange group API

I'm not sure that's the best solution, but ideally we'd find something a bit neater?

const struct s2n_kem_group *expected_kem_group, const struct s2n_ecc_named_curve *expected_curve, bool hrr_expected, bool len_prefix_expected)
{
/* XOR check: can expect to negotiate either a KEM group, or a classic EC curve, but not both/neither */
POSIX_ENSURE((expected_kem_group == NULL) != (expected_curve == NULL), S2N_ERR_SAFETY);
if (expected_handshake_success) {
POSIX_ENSURE((expected_kem_group == NULL) != (expected_curve == NULL), S2N_ERR_SAFETY);
} else {
/* If we expect the handshake to fail, then none of the expected success values should be set */
POSIX_ENSURE_EQ(expected_kem_group, NULL);
POSIX_ENSURE_EQ(expected_curve, NULL);
POSIX_ENSURE_EQ(hrr_expected, false);
POSIX_ENSURE_EQ(len_prefix_expected, false);
}

/* Set up connections */
struct s2n_connection *client_conn = NULL, *server_conn = NULL;
Expand Down Expand Up @@ -156,9 +181,13 @@ int s2n_test_tls13_pq_handshake(const struct s2n_security_policy *client_sec_pol

/* Server reads ClientHello */
POSIX_ENSURE_EQ(s2n_conn_get_current_message_type(server_conn), CLIENT_HELLO);
POSIX_GUARD(s2n_handshake_read_io(server_conn));
POSIX_ENSURE_EQ((expected_handshake_success ? S2N_SUCCESS : S2N_FAILURE), s2n_handshake_read_io(server_conn));

POSIX_ENSURE_EQ(server_conn->actual_protocol_version, S2N_TLS13); /* Server is now on TLS13 */
if (expected_handshake_success) {
POSIX_ENSURE_EQ(server_conn->actual_protocol_version, S2N_TLS13); /* Server is now on TLS13 */
} else {
POSIX_ENSURE_EQ(expected_s2nerrno, s2n_errno);
}

/* Assert that the server chose the correct group */
if (expected_kem_group) {
Expand All @@ -180,12 +209,14 @@ int s2n_test_tls13_pq_handshake(const struct s2n_security_policy *client_sec_pol

/* Server sends ServerHello or HRR */
POSIX_GUARD(s2n_conn_set_handshake_type(server_conn));
POSIX_ENSURE_EQ(hrr_expected, s2n_handshake_type_check_tls13_flag(server_conn, HELLO_RETRY_REQUEST));
POSIX_GUARD(s2n_handshake_write_io(server_conn));

/* Server sends CCS */
POSIX_ENSURE_EQ(s2n_conn_get_current_message_type(server_conn), SERVER_CHANGE_CIPHER_SPEC);
POSIX_GUARD(s2n_handshake_write_io(server_conn));
if (expected_handshake_success) {
POSIX_ENSURE_EQ(hrr_expected, s2n_handshake_type_check_tls13_flag(server_conn, HELLO_RETRY_REQUEST));
POSIX_ENSURE_EQ(s2n_conn_get_current_message_type(server_conn), SERVER_CHANGE_CIPHER_SPEC);
POSIX_GUARD(s2n_handshake_write_io(server_conn));
}

if (hrr_expected) {
/* Client reads HRR */
Expand Down Expand Up @@ -219,11 +250,28 @@ int s2n_test_tls13_pq_handshake(const struct s2n_security_policy *client_sec_pol

/* Client reads ServerHello */
POSIX_ENSURE_EQ(s2n_conn_get_current_message_type(client_conn), SERVER_HELLO);
POSIX_GUARD(s2n_handshake_read_io(client_conn));
POSIX_ENSURE_EQ((expected_handshake_success ? S2N_SUCCESS : S2N_FAILURE), s2n_handshake_read_io(client_conn));

/* We've gotten far enough in the handshake that both client and server should have
* derived the shared secrets, so we don't send/receive any more messages. */

if (!expected_handshake_success) {
POSIX_ENSURE_EQ(NULL, client_conn->kex_params.server_kem_group_params.kem_group);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely following what these asserts actually mean beyond the S2N_ERR_ECDHE_UNSUPPORTED_CURVE err. Maybe we could add a comment about why this deeper assert is necessary?

POSIX_ENSURE_EQ(NULL, client_conn->kex_params.server_kem_group_params.kem_params.kem);
POSIX_ENSURE_EQ(NULL, client_conn->kex_params.server_kem_group_params.ecc_params.negotiated_curve);
POSIX_ENSURE_EQ(NULL, client_conn->kex_params.server_ecc_evp_params.negotiated_curve);

POSIX_ENSURE_EQ(NULL, server_conn->kex_params.server_kem_group_params.kem_group);
POSIX_ENSURE_EQ(NULL, server_conn->kex_params.server_kem_group_params.kem_params.kem);
POSIX_ENSURE_EQ(NULL, server_conn->kex_params.server_kem_group_params.ecc_params.negotiated_curve);
POSIX_ENSURE_EQ(NULL, server_conn->kex_params.server_ecc_evp_params.negotiated_curve);

POSIX_GUARD(s2n_test_tls13_pq_handshake_cleanup(&client_to_server, &server_to_client, client_conn, server_conn,
chain_and_key, client_config, server_config));

return S2N_SUCCESS;
}

/* Assert that the correct group was negotiated (we re-check the server group to assert that
* nothing unexpected changed between then and now while e.g. processing HRR) */
if (expected_kem_group) {
Expand Down Expand Up @@ -314,15 +362,8 @@ int s2n_test_tls13_pq_handshake(const struct s2n_security_policy *client_sec_pol
POSIX_ENSURE_EQ(0, memcmp(server_secrets->server_handshake_secret, client_secrets->server_handshake_secret, size));

/* Clean up */
POSIX_GUARD(s2n_stuffer_free(&client_to_server));
POSIX_GUARD(s2n_stuffer_free(&server_to_client));

POSIX_GUARD(s2n_connection_free(client_conn));
POSIX_GUARD(s2n_connection_free(server_conn));

POSIX_GUARD(s2n_cert_chain_and_key_free(chain_and_key));
POSIX_GUARD(s2n_config_free(server_config));
POSIX_GUARD(s2n_config_free(client_config));
POSIX_GUARD(s2n_test_tls13_pq_handshake_cleanup(&client_to_server, &server_to_client, client_conn, server_conn,
chain_and_key, client_config, server_config));

return S2N_SUCCESS;
}
Expand Down Expand Up @@ -472,6 +513,8 @@ int main()
/* Self talk test with each TLS 1.3 KemGroup we support */
for (size_t i = 0; i < S2N_KEM_GROUPS_COUNT; i++) {
const struct s2n_kem_group *kem_group = ALL_SUPPORTED_KEM_GROUPS[i];
bool expected_handshake_success = true;
int expected_err_code = S2N_SUCCESS;

if (kem_group == NULL || !s2n_kem_group_is_available(kem_group)) {
continue;
Expand Down Expand Up @@ -502,7 +545,7 @@ int main()
.len_prefix_expected = false,
};

EXPECT_SUCCESS(s2n_test_tls13_pq_handshake(test_vec.client_policy, test_vec.server_policy,
EXPECT_SUCCESS(s2n_test_tls13_pq_handshake(test_vec.client_policy, test_vec.server_policy, expected_handshake_success, expected_err_code,
test_vec.expected_kem_group, test_vec.expected_curve, test_vec.hrr_expected, test_vec.len_prefix_expected));
}

Expand All @@ -519,6 +562,16 @@ int main()
* If PQ is disabled, the expected negotiation outcome is overridden below
* before performing the handshake test. */
const struct pq_handshake_test_vector test_vectors[] = {
#if defined(S2N_LIBCRYPTO_SUPPORTS_MLKEM)
{
.client_policy = &security_policy_aws_crt_sdk_tls_13_06_25_pq_kx_required,
.server_policy = &security_policy_aws_crt_sdk_tls_13_06_25_pq_kx_required,
.expected_kem_group = &s2n_x25519_mlkem_768,
.expected_curve = NULL,
.hrr_expected = false,
.len_prefix_expected = false,
},
#endif
{
.client_policy = &security_policy_pq_tls_1_3_2023_06_01,
.server_policy = &security_policy_pq_tls_1_0_2021_05_24,
Expand Down Expand Up @@ -744,13 +797,14 @@ int main()
const struct pq_handshake_test_vector *vector = &test_vectors[i];
const struct s2n_security_policy *client_policy = vector->client_policy;
const struct s2n_security_policy *server_policy = vector->server_policy;
bool expected_handshake_success = true;
int expected_err_code = S2N_SUCCESS;
const struct s2n_kem_group *kem_group = vector->expected_kem_group;
const struct s2n_ecc_named_curve *curve = vector->expected_curve;
bool hrr_expected = vector->hrr_expected;
bool len_prefix_expected = vector->len_prefix_expected;

if (!s2n_pq_is_enabled()) {
EXPECT_TRUE(client_policy->ecc_preferences->count > 0);
if (!s2n_pq_is_enabled() && (client_policy->ecc_preferences->count > 0)) {
const struct s2n_ecc_named_curve *client_default = client_policy->ecc_preferences->ecc_curves[0];
const struct s2n_ecc_named_curve *predicted_curve = s2n_get_predicted_negotiated_ecdhe_curve(client_policy, server_policy);

Expand Down Expand Up @@ -787,7 +841,50 @@ int main()
POSIX_ENSURE_EQ(kem_group->iana_id, predicted_kem_group->iana_id);
}

EXPECT_SUCCESS(s2n_test_tls13_pq_handshake(client_policy, server_policy, kem_group, curve, hrr_expected, len_prefix_expected));
EXPECT_SUCCESS(s2n_test_tls13_pq_handshake(client_policy, server_policy, expected_handshake_success, expected_err_code, kem_group, curve, hrr_expected, len_prefix_expected));
}

const struct pq_handshake_test_vector expected_failures[] = {
#if !defined(S2N_LIBCRYPTO_SUPPORTS_MLKEM)
{
.client_policy = &security_policy_aws_crt_sdk_tls_13_06_25_pq_kx_required,
.server_policy = &security_policy_aws_crt_sdk_tls_13_06_25_pq_kx_required,
.expected_kem_group = NULL,
.expected_curve = NULL,
.hrr_expected = false,
.len_prefix_expected = false,
},
#endif
{
.client_policy = &security_policy_aws_crt_sdk_tls_13_06_25_pq_kx_required,
.server_policy = &security_policy_aws_crt_sdk_tls_12_06_23,
.expected_kem_group = NULL,
.expected_curve = NULL,
.hrr_expected = false,
.len_prefix_expected = false,
},
{
.client_policy = &security_policy_aws_crt_sdk_tls_12_06_23,
.server_policy = &security_policy_aws_crt_sdk_tls_13_06_25_pq_kx_required,
.expected_kem_group = NULL,
.expected_curve = NULL,
.hrr_expected = false,
.len_prefix_expected = false,
},
};

for (size_t i = 0; i < s2n_array_len(expected_failures); i++) {
const struct pq_handshake_test_vector *vector = &expected_failures[i];
const struct s2n_security_policy *client_policy = vector->client_policy;
const struct s2n_security_policy *server_policy = vector->server_policy;
bool expected_handshake_success = false;
int expected_err_code = S2N_ERR_ECDHE_UNSUPPORTED_CURVE;
const struct s2n_kem_group *kem_group = vector->expected_kem_group;
const struct s2n_ecc_named_curve *curve = vector->expected_curve;
bool hrr_expected = vector->hrr_expected;
bool len_prefix_expected = vector->len_prefix_expected;

EXPECT_SUCCESS(s2n_test_tls13_pq_handshake(client_policy, server_policy, expected_handshake_success, expected_err_code, kem_group, curve, hrr_expected, len_prefix_expected));
}

END_TEST();
Expand Down
6 changes: 6 additions & 0 deletions tls/extensions/s2n_client_key_share.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ static int s2n_generate_default_ecc_key_share(struct s2n_connection *conn, struc
* during a retry, or the most preferred share according to local preferences.
*/
struct s2n_ecc_evp_params *client_params = &conn->kex_params.client_ecc_evp_params;

if (ecc_pref->count == 0) {
client_params->negotiated_curve = NULL;
return S2N_SUCCESS;
}

if (s2n_is_hello_retry_handshake(conn)) {
const struct s2n_ecc_named_curve *server_curve = conn->kex_params.server_ecc_evp_params.negotiated_curve;

Expand Down
18 changes: 11 additions & 7 deletions tls/s2n_client_hello.c
Original file line number Diff line number Diff line change
Expand Up @@ -551,14 +551,18 @@ int s2n_process_client_hello(struct s2n_connection *conn)
const struct s2n_ecc_preferences *ecc_pref = NULL;
POSIX_GUARD(s2n_connection_get_ecc_preferences(conn, &ecc_pref));
POSIX_ENSURE_REF(ecc_pref);
POSIX_ENSURE_GT(ecc_pref->count, 0);
if (s2n_ecc_preferences_includes_curve(ecc_pref, TLS_EC_CURVE_SECP_256_R1)) {
conn->kex_params.server_ecc_evp_params.negotiated_curve = &s2n_ecc_curve_secp256r1;

if (ecc_pref->count == 0) {
conn->kex_params.server_ecc_evp_params.negotiated_curve = NULL;
} else {
/* If P-256 isn't allowed by the current security policy, instead choose
* the first / most preferred curve.
*/
conn->kex_params.server_ecc_evp_params.negotiated_curve = ecc_pref->ecc_curves[0];
if (s2n_ecc_preferences_includes_curve(ecc_pref, TLS_EC_CURVE_SECP_256_R1)) {
conn->kex_params.server_ecc_evp_params.negotiated_curve = &s2n_ecc_curve_secp256r1;
} else {
/* If P-256 isn't allowed by the current security policy, instead choose
* the first / most preferred curve.
*/
conn->kex_params.server_ecc_evp_params.negotiated_curve = ecc_pref->ecc_curves[0];
}
}

POSIX_GUARD(s2n_extension_list_process(S2N_EXTENSION_LIST_CLIENT_HELLO, conn, &conn->client_hello.extensions));
Expand Down
Loading
Loading