From 316df9351a38ac87b6b7b2dbb0b5df75e223fa64 Mon Sep 17 00:00:00 2001 From: Alex Weibel Date: Tue, 1 Oct 2024 15:40:28 -0700 Subject: [PATCH] Add initial support for MLKEM768 --- crypto/s2n_pq.c | 12 + crypto/s2n_pq.h | 1 + ...generate_pq_hybrid_tls13_handshake_kats.py | 31 +++ .../s2n_client_key_share_extension_pq_test.c | 221 ++++++++++++------ tests/unit/s2n_kem_preferences_test.c | 17 ++ tests/unit/s2n_pq_kem_test.c | 5 +- tests/unit/s2n_security_policies_test.c | 17 +- .../s2n_server_key_share_extension_test.c | 26 ++- .../s2n_tls13_hybrid_shared_secret_test.c | 82 ++++++- tests/unit/s2n_tls13_pq_handshake_test.c | 41 ++++ tls/extensions/s2n_client_key_share.c | 64 +++-- tls/extensions/s2n_server_key_share.c | 76 ++++-- tls/s2n_kem.c | 65 +++++- tls/s2n_kem.h | 42 ++-- tls/s2n_kem_preferences.c | 16 +- tls/s2n_kem_preferences.h | 1 + tls/s2n_tls13_handshake.c | 10 +- tls/s2n_tls_parameters.h | 2 + 18 files changed, 557 insertions(+), 172 deletions(-) diff --git a/crypto/s2n_pq.c b/crypto/s2n_pq.c index ed902095adb..cb9795311a3 100644 --- a/crypto/s2n_pq.c +++ b/crypto/s2n_pq.c @@ -33,3 +33,15 @@ bool s2n_pq_is_enabled() { return s2n_libcrypto_supports_evp_kem(); } + +bool s2n_libcrypto_supports_mlkem() +{ + /* S2N_LIBCRYPTO_SUPPORTS_MLKEM will be auto-detected and #defined if + * ./tests/features/S2N_LIBCRYPTO_SUPPORTS_MLKEM.c successfully compiles + */ +#if defined(S2N_LIBCRYPTO_SUPPORTS_MLKEM) + return true; +#else + return false; +#endif +} diff --git a/crypto/s2n_pq.h b/crypto/s2n_pq.h index 650f1c11344..089362569d1 100644 --- a/crypto/s2n_pq.h +++ b/crypto/s2n_pq.h @@ -23,3 +23,4 @@ bool s2n_pq_is_enabled(void); bool s2n_libcrypto_supports_evp_kem(void); +bool s2n_libcrypto_supports_mlkem(void); diff --git a/tests/unit/kats/generate_pq_hybrid_tls13_handshake_kats.py b/tests/unit/kats/generate_pq_hybrid_tls13_handshake_kats.py index f7608aa2ab2..6cae0545e8a 100755 --- a/tests/unit/kats/generate_pq_hybrid_tls13_handshake_kats.py +++ b/tests/unit/kats/generate_pq_hybrid_tls13_handshake_kats.py @@ -208,6 +208,34 @@ "pq_shared_secret": "B10F7394926AD3B49C5D62D5AEB531D5757538BCC0DA9E550D438F1B61BD7419", "transcript_hash": "35412cebcf35cb8a7af8f78278a486fc798f8702eaebd067c97acb27bffe13524d8426a4ed57956b4fd0ffdc4c90be52", }, + { + "group_name": "X25519MLKEM768", + "cipher_suite": "TLS_AES_128_GCM_SHA256", + "ec_shared_secret": "519be87fa0599077e5673d6f2d910aa150d7fef783c5e1491961fdf63b255910", + "pq_shared_secret": "B408D5D115713F0A93047DBBEA832E4340787686D59A9A2D106BD662BA0AA035", + "transcript_hash": "f5f7f7867668be4b792159d4d194a03ec5cfa238b6409b5ca2ddccfddcc92a2b", + }, + { + "group_name": "X25519MLKEM768", + "cipher_suite": "TLS_AES_256_GCM_SHA384", + "ec_shared_secret": "519be87fa0599077e5673d6f2d910aa150d7fef783c5e1491961fdf63b255910", + "pq_shared_secret": "B408D5D115713F0A93047DBBEA832E4340787686D59A9A2D106BD662BA0AA035", + "transcript_hash": "35412cebcf35cb8a7af8f78278a486fc798f8702eaebd067c97acb27bffe13524d8426a4ed57956b4fd0ffdc4c90be52", + }, + { + "group_name": "SecP256r1MLKEM768", + "cipher_suite": "TLS_AES_128_GCM_SHA256", + "ec_shared_secret": "9348e27655539e08fffe46b35f863dd634e7437cc6bc11c7d329ef5484ec3b60", + "pq_shared_secret": "B408D5D115713F0A93047DBBEA832E4340787686D59A9A2D106BD662BA0AA035", + "transcript_hash": "f5f7f7867668be4b792159d4d194a03ec5cfa238b6409b5ca2ddccfddcc92a2b", + }, + { + "group_name": "SecP256r1MLKEM768", + "cipher_suite": "TLS_AES_256_GCM_SHA384", + "ec_shared_secret": "9348e27655539e08fffe46b35f863dd634e7437cc6bc11c7d329ef5484ec3b60", + "pq_shared_secret": "B408D5D115713F0A93047DBBEA832E4340787686D59A9A2D106BD662BA0AA035", + "transcript_hash": "35412cebcf35cb8a7af8f78278a486fc798f8702eaebd067c97acb27bffe13524d8426a4ed57956b4fd0ffdc4c90be52", + }, ] @@ -233,6 +261,9 @@ def hkdf_expand_label(key: bytes, label: str, context: bytes, hash_alg: str): def compute_secrets(input_vector: dict): shared_secret = bytes.fromhex(input_vector["ec_shared_secret"] + input_vector["pq_shared_secret"]) + if (input_vector["group_name"] == "X25519MLKEM768"): + shared_secret = bytes.fromhex(input_vector["pq_shared_secret"] + input_vector["ec_shared_secret"]) + hash_alg = input_vector["cipher_suite"].split("_")[-1].lower() zeros = bytearray([0] * hashlib.new(hash_alg).digest_size) transcript_hash = bytes.fromhex(input_vector["transcript_hash"]) diff --git a/tests/unit/s2n_client_key_share_extension_pq_test.c b/tests/unit/s2n_client_key_share_extension_pq_test.c index 9768cbb4109..b14ee879bf6 100644 --- a/tests/unit/s2n_client_key_share_extension_pq_test.c +++ b/tests/unit/s2n_client_key_share_extension_pq_test.c @@ -200,21 +200,29 @@ int main() EXPECT_SUCCESS(s2n_stuffer_read_uint16(&key_share_extension, &sent_hybrid_share_size)); EXPECT_EQUAL(sent_hybrid_share_size, expected_hybrid_share_size); + uint16_t expected_first_share_size = test_kem_group->curve->share_size; + uint16_t expected_second_share_size = test_kem_group->kem->public_key_length; + + if (kem_group_params->kem_group->send_kem_first) { + expected_first_share_size = test_kem_group->kem->public_key_length; + expected_second_share_size = test_kem_group->curve->share_size; + } + if (len_prefixed) { - uint16_t hybrid_ecc_share_size = 0; - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&key_share_extension, &hybrid_ecc_share_size)); - EXPECT_EQUAL(hybrid_ecc_share_size, test_kem_group->curve->share_size); + uint16_t actual_first_share_size = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&key_share_extension, &actual_first_share_size)); + EXPECT_EQUAL(actual_first_share_size, expected_first_share_size); } - EXPECT_SUCCESS(s2n_stuffer_skip_read(&key_share_extension, test_kem_group->curve->share_size)); + EXPECT_SUCCESS(s2n_stuffer_skip_read(&key_share_extension, expected_first_share_size)); if (len_prefixed) { - uint16_t hybrid_pq_share_size = 0; - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&key_share_extension, &hybrid_pq_share_size)); - EXPECT_EQUAL(hybrid_pq_share_size, test_kem_group->kem->public_key_length); + uint16_t actual_second_share_size = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&key_share_extension, &actual_second_share_size)); + EXPECT_EQUAL(actual_second_share_size, expected_second_share_size); } - EXPECT_SUCCESS(s2n_stuffer_skip_read(&key_share_extension, test_kem_group->kem->public_key_length)); + EXPECT_SUCCESS(s2n_stuffer_skip_read(&key_share_extension, expected_second_share_size)); - /* Assert that the ECC key share is correct: IANA ID || size || share */ + /* After PQ KeyShare, assert that the ECC key share is correct: IANA ID || size || share */ uint16_t ecc_iana_value = 0, ecc_share_size = 0; EXPECT_SUCCESS(s2n_stuffer_read_uint16(&key_share_extension, &ecc_iana_value)); EXPECT_EQUAL(ecc_iana_value, ecc_pref->ecc_curves[0]->iana_id); @@ -308,19 +316,27 @@ int main() EXPECT_SUCCESS(s2n_stuffer_read_uint16(&key_share_extension, &sent_hybrid_share_size)); EXPECT_EQUAL(sent_hybrid_share_size, expected_hybrid_share_size); + uint16_t expected_first_share_size = negotiated_kem_group->curve->share_size; + uint16_t expected_second_share_size = negotiated_kem_group->kem->public_key_length; + + if (negotiated_kem_group->send_kem_first) { + expected_first_share_size = negotiated_kem_group->kem->public_key_length; + expected_second_share_size = negotiated_kem_group->curve->share_size; + } + if (len_prefixed) { - uint16_t hybrid_ecc_share_size = 0; - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&key_share_extension, &hybrid_ecc_share_size)); - EXPECT_EQUAL(hybrid_ecc_share_size, negotiated_kem_group->curve->share_size); + uint16_t actual_first_share_size = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&key_share_extension, &actual_first_share_size)); + EXPECT_EQUAL(actual_first_share_size, expected_first_share_size); } - EXPECT_SUCCESS(s2n_stuffer_skip_read(&key_share_extension, negotiated_kem_group->curve->share_size)); + EXPECT_SUCCESS(s2n_stuffer_skip_read(&key_share_extension, expected_first_share_size)); if (len_prefixed) { - uint16_t hybrid_pq_share_size = 0; - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&key_share_extension, &hybrid_pq_share_size)); - EXPECT_EQUAL(hybrid_pq_share_size, negotiated_kem_group->kem->public_key_length); + uint16_t actual_second_share_size = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&key_share_extension, &actual_second_share_size)); + EXPECT_EQUAL(actual_second_share_size, expected_second_share_size); } - EXPECT_SUCCESS(s2n_stuffer_skip_read(&key_share_extension, negotiated_kem_group->kem->public_key_length)); + EXPECT_SUCCESS(s2n_stuffer_skip_read(&key_share_extension, expected_second_share_size)); /* If all the sizes/bytes were correctly written, there should be nothing left over */ EXPECT_EQUAL(s2n_stuffer_data_available(&key_share_extension), 0); @@ -342,11 +358,11 @@ int main() EXPECT_SUCCESS(s2n_connection_get_kem_preferences(conn, &kem_pref)); EXPECT_NOT_NULL(kem_pref); - struct s2n_stuffer first_extension = { 0 }, second_extension = { 0 }; - EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&first_extension, MEM_FOR_EXTENSION)); - EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&second_extension, MEM_FOR_EXTENSION)); + struct s2n_stuffer init_extension = { 0 }, retry_extension = { 0 }; + EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&init_extension, MEM_FOR_EXTENSION)); + EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&retry_extension, MEM_FOR_EXTENSION)); - EXPECT_SUCCESS(s2n_client_key_share_extension.send(conn, &first_extension)); + EXPECT_SUCCESS(s2n_client_key_share_extension.send(conn, &init_extension)); conn->kex_params.server_kem_group_params.kem_group = conn->kex_params.client_kem_group_params.kem_group; conn->kex_params.server_kem_group_params.ecc_params.negotiated_curve = @@ -358,62 +374,86 @@ int main() EXPECT_SUCCESS(s2n_set_connection_hello_retry_flags(conn)); conn->early_data_state = S2N_EARLY_DATA_REJECTED; - EXPECT_SUCCESS(s2n_client_key_share_extension.send(conn, &second_extension)); + EXPECT_SUCCESS(s2n_client_key_share_extension.send(conn, &retry_extension)); /* Read the total length of both extensions. * The first keys extension contains multiple shares, so should be longer than the second. */ - uint16_t first_sent_key_shares_size = 0, second_sent_key_shares_size = 0; - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&first_extension, &first_sent_key_shares_size)); - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&second_extension, &second_sent_key_shares_size)); - EXPECT_EQUAL(first_sent_key_shares_size, s2n_stuffer_data_available(&first_extension)); - EXPECT_EQUAL(second_sent_key_shares_size, s2n_stuffer_data_available(&second_extension)); - EXPECT_TRUE(second_sent_key_shares_size < first_sent_key_shares_size); + uint16_t init_sent_key_shares_size = 0, retry_sent_key_shares_size = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&init_extension, &init_sent_key_shares_size)); + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&retry_extension, &retry_sent_key_shares_size)); + EXPECT_EQUAL(init_sent_key_shares_size, s2n_stuffer_data_available(&init_extension)); + EXPECT_EQUAL(retry_sent_key_shares_size, s2n_stuffer_data_available(&retry_extension)); + EXPECT_TRUE(retry_sent_key_shares_size < init_sent_key_shares_size); /* Read the iana of the first share. * Both shares should contain the same iana, and it should be equal to the server's chosen kem group. */ - uint16_t first_sent_hybrid_iana_id = 0, second_sent_hybrid_iana_id = 0; - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&first_extension, &first_sent_hybrid_iana_id)); - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&second_extension, &second_sent_hybrid_iana_id)); - EXPECT_EQUAL(first_sent_hybrid_iana_id, conn->kex_params.server_kem_group_params.kem_group->iana_id); - EXPECT_EQUAL(first_sent_hybrid_iana_id, second_sent_hybrid_iana_id); + uint16_t init_sent_hybrid_iana_id = 0, retry_sent_hybrid_iana_id = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&init_extension, &init_sent_hybrid_iana_id)); + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&retry_extension, &retry_sent_hybrid_iana_id)); + EXPECT_EQUAL(init_sent_hybrid_iana_id, conn->kex_params.server_kem_group_params.kem_group->iana_id); + EXPECT_EQUAL(init_sent_hybrid_iana_id, retry_sent_hybrid_iana_id); /* Read the total share size, including both ecc and kem. - * The first extension contains multiple shares, so should contain more data than the share size. - * The second extension only contains one share, so should contain only the share size. */ - uint16_t first_total_hybrid_share_size = 0, second_total_hybrid_share_size = 0; - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&first_extension, &first_total_hybrid_share_size)); - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&second_extension, &second_total_hybrid_share_size)); - EXPECT_TRUE(first_total_hybrid_share_size < s2n_stuffer_data_available(&first_extension)); - EXPECT_EQUAL(second_total_hybrid_share_size, s2n_stuffer_data_available(&second_extension)); + * The initial extension contains multiple shares, so should contain more data than the share size. + * The ClientHelloRetry extension only contains one share, so should contain only the share size. */ + uint16_t init_total_hybrid_share_size = 0, retry_total_hybrid_share_size = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&init_extension, &init_total_hybrid_share_size)); + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&retry_extension, &retry_total_hybrid_share_size)); + EXPECT_TRUE(init_total_hybrid_share_size < s2n_stuffer_data_available(&init_extension)); + EXPECT_EQUAL(retry_total_hybrid_share_size, s2n_stuffer_data_available(&retry_extension)); if (len_prefixed) { /* Read the ecc share size. * The ecc share should be identical for both, so the size should be the same. */ - uint16_t first_ecc_share_size = 0, second_ecc_share_size = 0; - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&first_extension, &first_ecc_share_size)); - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&second_extension, &second_ecc_share_size)); - EXPECT_EQUAL(first_ecc_share_size, second_ecc_share_size); + uint16_t init_ecc_share_size = 0, retry_ecc_share_size = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&init_extension, &init_ecc_share_size)); + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&retry_extension, &retry_ecc_share_size)); + EXPECT_EQUAL(init_ecc_share_size, retry_ecc_share_size); } /* Read the ecc share. * The ecc share should be identical for both. */ struct s2n_kem_group_params *kem_group_params = &conn->kex_params.client_kem_group_params; int ecc_share_size = kem_group_params->ecc_params.negotiated_curve->share_size; - uint8_t *first_ecc_share_data = NULL, *second_ecc_share_data = NULL; - EXPECT_NOT_NULL(first_ecc_share_data = s2n_stuffer_raw_read(&first_extension, ecc_share_size)); - EXPECT_NOT_NULL(second_ecc_share_data = s2n_stuffer_raw_read(&second_extension, ecc_share_size)); - EXPECT_BYTEARRAY_EQUAL(first_ecc_share_data, second_ecc_share_data, ecc_share_size); - - if (len_prefixed) { - /* The pq share should take up the rest of the key share. - * For now the pq share is different between extensions, so we can't assert anything else. */ - uint16_t second_pq_share_size = 0; - EXPECT_SUCCESS(s2n_stuffer_read_uint16(&second_extension, &second_pq_share_size)); - EXPECT_EQUAL(second_pq_share_size, s2n_stuffer_data_available(&second_extension)); + int pq_share_size = kem_group_params->kem_group->kem->public_key_length; + + if (!kem_group_params->kem_group->send_kem_first) { + uint8_t *init_ecc_share_data = NULL, *retry_ecc_share_data = NULL; + EXPECT_NOT_NULL(init_ecc_share_data = s2n_stuffer_raw_read(&init_extension, ecc_share_size)); + EXPECT_NOT_NULL(retry_ecc_share_data = s2n_stuffer_raw_read(&retry_extension, ecc_share_size)); + EXPECT_BYTEARRAY_EQUAL(init_ecc_share_data, retry_ecc_share_data, ecc_share_size); + + if (len_prefixed) { + /* The pq share should take up the rest of the key share. + * For now the pq share is different between extensions, so we can't assert anything else. */ + uint16_t retry_pq_share_size = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&retry_extension, &retry_pq_share_size)); + EXPECT_EQUAL(retry_pq_share_size, pq_share_size); + EXPECT_EQUAL(retry_pq_share_size, s2n_stuffer_data_available(&retry_extension)); + } + } else { + uint8_t *init_pq_share_data = NULL, *retry_pq_share_data = NULL; + EXPECT_NOT_NULL(init_pq_share_data = s2n_stuffer_raw_read(&init_extension, pq_share_size)); + EXPECT_NOT_NULL(retry_pq_share_data = s2n_stuffer_raw_read(&retry_extension, pq_share_size)); + + if (len_prefixed) { + /* The pq share should take up the rest of the key share. + * For now the pq share is different between extensions, so we can't assert anything else. */ + uint16_t init_ecc_share_size = 0; + uint16_t retry_ecc_share_size = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&init_extension, &init_ecc_share_size)); + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&retry_extension, &retry_ecc_share_size)); + EXPECT_EQUAL(init_ecc_share_size, retry_ecc_share_size); + } + EXPECT_EQUAL(ecc_share_size, s2n_stuffer_data_available(&retry_extension)); + uint8_t *init_ecc_share_data = NULL, *retry_ecc_share_data = NULL; + EXPECT_NOT_NULL(init_ecc_share_data = s2n_stuffer_raw_read(&init_extension, ecc_share_size)); + EXPECT_NOT_NULL(retry_ecc_share_data = s2n_stuffer_raw_read(&retry_extension, ecc_share_size)); + EXPECT_BYTEARRAY_EQUAL(init_ecc_share_data, retry_ecc_share_data, ecc_share_size); } - EXPECT_SUCCESS(s2n_stuffer_free(&first_extension)); - EXPECT_SUCCESS(s2n_stuffer_free(&second_extension)); + EXPECT_SUCCESS(s2n_stuffer_free(&init_extension)); + EXPECT_SUCCESS(s2n_stuffer_free(&retry_extension)); EXPECT_SUCCESS(s2n_connection_free(conn)); } } @@ -824,9 +864,18 @@ int main() EXPECT_SUCCESS(s2n_client_key_share_extension.recv(server_conn, &key_share_extension)); EXPECT_EQUAL(s2n_stuffer_data_available(&key_share_extension), 0); - /* Should have chosen curve 1, because curve 0 was malformed */ + /* Normally will chose group 1, because group 0 was malformed */ + const struct s2n_kem_group *expected_server_selection = kem_group1; + + /* However s2n_client_key_share_extension.recv will not reject a corrupted unprefixed + * X25519MLKEM768 KeyShare. Errors will not be seen until the share is attempted to + * be used later in the handshake */ + if (kem_group0 == &s2n_x25519_mlkem_768 && len_prefixed == 0) { + expected_server_selection = kem_group0; + } + struct s2n_kem_group_params *server_params = &server_conn->kex_params.client_kem_group_params; - EXPECT_EQUAL(server_params->kem_group, kem_group1); + EXPECT_EQUAL(server_params->kem_group, expected_server_selection); EXPECT_NOT_NULL(server_params->kem_params.public_key.data); EXPECT_NOT_NULL(server_params->ecc_params.evp_pkey); @@ -920,18 +969,31 @@ static int s2n_copy_pq_share(struct s2n_stuffer *from, struct s2n_blob *to, cons POSIX_ENSURE_REF(to); POSIX_ENSURE_REF(kem_group); - int keyshare_extension_offset = 10; + int keyshare_extension_offset = 6; + int init_read_pos = from->read_cursor; + + POSIX_GUARD(s2n_alloc(to, kem_group->kem->public_key_length)); + + /* Skip mandatory offset */ + POSIX_GUARD(s2n_stuffer_skip_read(from, keyshare_extension_offset)); - if (!len_prefixed) { - keyshare_extension_offset -= (2 * S2N_SIZE_OF_KEY_SHARE_SIZE); + /* Skip first len prefix if needed */ + if (len_prefixed) { + POSIX_GUARD(s2n_stuffer_skip_read(from, S2N_SIZE_OF_KEY_SHARE_SIZE)); } - POSIX_GUARD(s2n_alloc(to, kem_group->kem->public_key_length)); - /* Skip all the two-byte IDs/sizes and the ECC portion of the share */ - POSIX_GUARD(s2n_stuffer_skip_read(from, keyshare_extension_offset + kem_group->curve->share_size)); - POSIX_GUARD(s2n_stuffer_read(from, to)); - POSIX_GUARD(s2n_stuffer_rewind_read(from, keyshare_extension_offset + kem_group->curve->share_size + kem_group->kem->public_key_length)); + if (kem_group->send_kem_first) { + POSIX_GUARD(s2n_stuffer_read(from, to)); + } else { + POSIX_GUARD(s2n_stuffer_skip_read(from, kem_group->curve->share_size)); + if (len_prefixed) { + POSIX_GUARD(s2n_stuffer_skip_read(from, S2N_SIZE_OF_KEY_SHARE_SIZE)); + } + POSIX_GUARD(s2n_stuffer_read(from, to)); + } + + from->read_cursor = init_read_pos; return S2N_SUCCESS; } @@ -955,14 +1017,25 @@ static int s2n_generate_pq_hybrid_key_share_for_test(struct s2n_stuffer *out, st ecc_params->negotiated_curve = kem_group->curve; struct s2n_kem_params *kem_params = &kem_group_params->kem_params; - if (kem_params->len_prefixed) { - POSIX_GUARD(s2n_stuffer_write_uint16(out, ecc_params->negotiated_curve->share_size)); - } - POSIX_GUARD(s2n_ecc_evp_generate_ephemeral_key(ecc_params)); - POSIX_GUARD(s2n_ecc_evp_write_params_point(ecc_params, out)); + if (kem_group_params->kem_group->send_kem_first) { + kem_params->kem = kem_group->kem; + POSIX_GUARD(s2n_kem_send_public_key(out, kem_params)); + + if (kem_params->len_prefixed) { + POSIX_GUARD(s2n_stuffer_write_uint16(out, ecc_params->negotiated_curve->share_size)); + } + POSIX_GUARD(s2n_ecc_evp_generate_ephemeral_key(ecc_params)); + POSIX_GUARD(s2n_ecc_evp_write_params_point(ecc_params, out)); + } else { + if (kem_params->len_prefixed) { + POSIX_GUARD(s2n_stuffer_write_uint16(out, ecc_params->negotiated_curve->share_size)); + } + POSIX_GUARD(s2n_ecc_evp_generate_ephemeral_key(ecc_params)); + POSIX_GUARD(s2n_ecc_evp_write_params_point(ecc_params, out)); - kem_params->kem = kem_group->kem; - POSIX_GUARD(s2n_kem_send_public_key(out, kem_params)); + kem_params->kem = kem_group->kem; + POSIX_GUARD(s2n_kem_send_public_key(out, kem_params)); + } POSIX_GUARD(s2n_stuffer_write_vector_size(&total_share_size)); diff --git a/tests/unit/s2n_kem_preferences_test.c b/tests/unit/s2n_kem_preferences_test.c index fe2ee05e35a..996e0c9fdf1 100644 --- a/tests/unit/s2n_kem_preferences_test.c +++ b/tests/unit/s2n_kem_preferences_test.c @@ -25,6 +25,8 @@ int main(int argc, char **argv) BEGIN_TEST(); EXPECT_SUCCESS(s2n_disable_tls13_in_test()); + EXPECT_FALSE(s2n_kem_preferences_includes_tls13_kem_group(&kem_preferences_null, TLS_PQ_KEM_GROUP_ID_SECP256R1_MLKEM_768)); + EXPECT_FALSE(s2n_kem_preferences_includes_tls13_kem_group(&kem_preferences_null, TLS_PQ_KEM_GROUP_ID_X25519_MLKEM_768)); EXPECT_FALSE(s2n_kem_preferences_includes_tls13_kem_group(&kem_preferences_null, TLS_PQ_KEM_GROUP_ID_X25519_KYBER_512_R3)); EXPECT_FALSE(s2n_kem_preferences_includes_tls13_kem_group(&kem_preferences_null, TLS_PQ_KEM_GROUP_ID_X25519_KYBER_768_R3)); EXPECT_FALSE(s2n_kem_preferences_includes_tls13_kem_group(&kem_preferences_null, TLS_PQ_KEM_GROUP_ID_SECP256R1_KYBER_512_R3)); @@ -34,6 +36,8 @@ int main(int argc, char **argv) { const struct s2n_kem_group *test_kem_groups[] = { + &s2n_secp256r1_mlkem_768, + &s2n_x25519_mlkem_768, &s2n_secp256r1_kyber_512_r3, &s2n_x25519_kyber_512_r3, &s2n_secp384r1_kyber_768_r3, @@ -49,6 +53,8 @@ int main(int argc, char **argv) .tls13_kem_groups = test_kem_groups, }; + EXPECT_TRUE(s2n_kem_preferences_includes_tls13_kem_group(&test_prefs, TLS_PQ_KEM_GROUP_ID_SECP256R1_MLKEM_768)); + EXPECT_TRUE(s2n_kem_preferences_includes_tls13_kem_group(&test_prefs, TLS_PQ_KEM_GROUP_ID_X25519_MLKEM_768)); EXPECT_TRUE(s2n_kem_preferences_includes_tls13_kem_group(&test_prefs, TLS_PQ_KEM_GROUP_ID_X25519_KYBER_512_R3)); EXPECT_TRUE(s2n_kem_preferences_includes_tls13_kem_group(&test_prefs, TLS_PQ_KEM_GROUP_ID_X25519_KYBER_768_R3)); EXPECT_TRUE(s2n_kem_preferences_includes_tls13_kem_group(&test_prefs, TLS_PQ_KEM_GROUP_ID_SECP256R1_KYBER_512_R3)); @@ -69,6 +75,15 @@ int main(int argc, char **argv) EXPECT_FALSE(s2n_kem_group_is_available(&s2n_x25519_kyber_512_r3)); EXPECT_FALSE(s2n_kem_group_is_available(&s2n_x25519_kyber_768_r3)); } + + if (s2n_libcrypto_supports_mlkem()) { + EXPECT_TRUE(s2n_kem_group_is_available(&s2n_secp256r1_mlkem_768)); + if (s2n_is_evp_apis_supported()) { + EXPECT_TRUE(s2n_kem_group_is_available(&s2n_x25519_mlkem_768)); + } else { + EXPECT_FALSE(s2n_kem_group_is_available(&s2n_x25519_mlkem_768)); + } + } } else { EXPECT_FALSE(s2n_kem_group_is_available(&s2n_secp256r1_kyber_512_r3)); EXPECT_FALSE(s2n_kem_group_is_available(&s2n_x25519_kyber_512_r3)); @@ -76,6 +91,8 @@ int main(int argc, char **argv) EXPECT_FALSE(s2n_kem_group_is_available(&s2n_secp256r1_kyber_768_r3)); EXPECT_FALSE(s2n_kem_group_is_available(&s2n_secp384r1_kyber_768_r3)); EXPECT_FALSE(s2n_kem_group_is_available(&s2n_secp521r1_kyber_1024_r3)); + EXPECT_FALSE(s2n_kem_group_is_available(&s2n_secp256r1_mlkem_768)); + EXPECT_FALSE(s2n_kem_group_is_available(&s2n_x25519_mlkem_768)); } }; diff --git a/tests/unit/s2n_pq_kem_test.c b/tests/unit/s2n_pq_kem_test.c index 44d1da247cc..bc6ea9919da 100644 --- a/tests/unit/s2n_pq_kem_test.c +++ b/tests/unit/s2n_pq_kem_test.c @@ -24,6 +24,7 @@ #include "utils/s2n_safety.h" static const struct s2n_kem *test_vectors[] = { + &s2n_mlkem_768, &s2n_kyber_512_r3, &s2n_kyber_768_r3, &s2n_kyber_1024_r3, @@ -47,6 +48,8 @@ int main() for (size_t i = 0; i < s2n_array_len(test_vectors); i++) { const struct s2n_kem *kem = test_vectors[i]; + bool expect_success = s2n_libcrypto_supports_evp_kem() + && ((kem != &s2n_mlkem_768) || s2n_libcrypto_supports_mlkem()); DEFER_CLEANUP(struct s2n_blob public_key = { 0 }, s2n_free); EXPECT_SUCCESS(s2n_alloc(&public_key, kem->public_key_length)); @@ -63,7 +66,7 @@ int main() DEFER_CLEANUP(struct s2n_blob ciphertext = { 0 }, s2n_free); EXPECT_SUCCESS(s2n_alloc(&ciphertext, kem->ciphertext_length)); - if (s2n_pq_is_enabled()) { + if (expect_success) { /* Test a successful round-trip: keygen->enc->dec */ EXPECT_PQ_KEM_SUCCESS(kem->generate_keypair(kem, public_key.data, private_key.data)); EXPECT_PQ_KEM_SUCCESS(kem->encapsulate(kem, ciphertext.data, client_shared_secret.data, public_key.data)); diff --git a/tests/unit/s2n_security_policies_test.c b/tests/unit/s2n_security_policies_test.c index 3533a5373b4..1be47dacf9e 100644 --- a/tests/unit/s2n_security_policies_test.c +++ b/tests/unit/s2n_security_policies_test.c @@ -198,15 +198,24 @@ int main(int argc, char **argv) EXPECT_EQUAL(1, security_policy->kem_preferences->kem_count); EXPECT_NOT_NULL(security_policy->kem_preferences->kems); EXPECT_EQUAL(&s2n_kyber_512_r3, security_policy->kem_preferences->kems[0]); - EXPECT_EQUAL(security_policy->kem_preferences->tls13_kem_groups, pq_kem_groups_r3_2023_06); + EXPECT_EQUAL(security_policy->kem_preferences->tls13_kem_groups, pq_kem_groups_2024_10_all); /* All supported kem groups should be in the preference list, but not all of them may be available. */ - EXPECT_EQUAL(6, security_policy->kem_preferences->tls13_kem_group_count); + EXPECT_EQUAL(8, security_policy->kem_preferences->tls13_kem_group_count); uint32_t available_groups = 0; EXPECT_OK(s2n_kem_preferences_groups_available(security_policy->kem_preferences, &available_groups)); if (s2n_libcrypto_supports_evp_kem() && s2n_is_evp_apis_supported()) { - EXPECT_EQUAL(6, available_groups); + if (s2n_libcrypto_supports_mlkem()) { + EXPECT_EQUAL(8, available_groups); + } else { + EXPECT_EQUAL(6, available_groups); + } + } else if (s2n_libcrypto_supports_evp_kem() && !s2n_is_evp_apis_supported()) { - EXPECT_EQUAL(4, available_groups); + if (s2n_libcrypto_supports_mlkem()) { + EXPECT_EQUAL(5, available_groups); + } else { + EXPECT_EQUAL(4, available_groups); + } } else { EXPECT_EQUAL(0, available_groups); } diff --git a/tests/unit/s2n_server_key_share_extension_test.c b/tests/unit/s2n_server_key_share_extension_test.c index 499b61cfe93..b0b9275282b 100644 --- a/tests/unit/s2n_server_key_share_extension_test.c +++ b/tests/unit/s2n_server_key_share_extension_test.c @@ -885,15 +885,25 @@ int main(int argc, char **argv) S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, kem_group->iana_id, uint16); S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, expected_hybrid_share_size, uint16); - if (len_prefixed) { - S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, kem_group->curve->share_size, uint16); - } - EXPECT_SUCCESS(s2n_stuffer_skip_read(&stuffer, kem_group->curve->share_size)); - - if (len_prefixed) { - S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, kem_group->kem->ciphertext_length, uint16); + if (kem_group->send_kem_first) { + if (len_prefixed) { + S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, kem_group->kem->ciphertext_length, uint16); + } + EXPECT_SUCCESS(s2n_stuffer_skip_read(&stuffer, kem_group->kem->ciphertext_length)); + if (len_prefixed) { + S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, kem_group->curve->share_size, uint16); + } + S2N_STUFFER_LENGTH_WRITTEN_EXPECT_EQUAL(&stuffer, kem_group->curve->share_size); + } else { + if (len_prefixed) { + S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, kem_group->curve->share_size, uint16); + } + EXPECT_SUCCESS(s2n_stuffer_skip_read(&stuffer, kem_group->curve->share_size)); + if (len_prefixed) { + S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, kem_group->kem->ciphertext_length, uint16); + } + S2N_STUFFER_LENGTH_WRITTEN_EXPECT_EQUAL(&stuffer, kem_group->kem->ciphertext_length); } - S2N_STUFFER_LENGTH_WRITTEN_EXPECT_EQUAL(&stuffer, kem_group->kem->ciphertext_length); EXPECT_NULL(conn->kex_params.server_ecc_evp_params.negotiated_curve); EXPECT_EQUAL(server_params->kem_group, kem_group); diff --git a/tests/unit/s2n_tls13_hybrid_shared_secret_test.c b/tests/unit/s2n_tls13_hybrid_shared_secret_test.c index 0d99e96a2b5..8c26973cac0 100644 --- a/tests/unit/s2n_tls13_hybrid_shared_secret_test.c +++ b/tests/unit/s2n_tls13_hybrid_shared_secret_test.c @@ -137,14 +137,16 @@ struct hybrid_test_vector { #define KYBER512R3_SECRET "0A6925676F24B22C286F4C81A4224CEC506C9B257D480E02E3B49F44CAA3237F" #define KYBER768R3_SECRET "914CB67FE5C38E73BF74181C0AC50428DEDF7750A98058F7D536708774535B29" #define KYBER1024R3_SECRET "B10F7394926AD3B49C5D62D5AEB531D5757538BCC0DA9E550D438F1B61BD7419" +#define MLKEM768_SECRET "B408D5D115713F0A93047DBBEA832E4340787686D59A9A2D106BD662BA0AA035" -/* Hybrid shared secrets are the concatenation: ECDHE || PQ */ #define X25519_KYBER512R3_HYBRID_SECRET (X25519_SHARED_SECRET KYBER512R3_SECRET) #define X25519_KYBER768R3_HYBRID_SECRET (X25519_SHARED_SECRET KYBER768R3_SECRET) #define SECP256R1_KYBER512R3_HYBRID_SECRET (SECP256R1_SHARED_SECRET KYBER512R3_SECRET) #define SECP256R1_KYBER768R3_HYBRID_SECRET (SECP256R1_SHARED_SECRET KYBER768R3_SECRET) #define SECP384R1_KYBER768R3_HYBRID_SECRET (SECP384R1_SHARED_SECRET KYBER768R3_SECRET) #define SECP521R1_KYBER1024R3_HYBRID_SECRET (SECP521R1_SHARED_SECRET KYBER1024R3_SECRET) +#define X25519_MLKEM768_HYBRID_SECRET (MLKEM768_SECRET X25519_SHARED_SECRET) +#define SECP256R1_MLKEM768_HYBRID_SECRET (SECP256R1_SHARED_SECRET MLKEM768_SECRET) /* The expected traffic secrets were calculated from an independent Python implementation located in the KAT directory, * using the ECDHE & PQ secrets defined above. */ @@ -178,6 +180,16 @@ struct hybrid_test_vector { #define AES_256_SECP521R1_KYBER1024R3_CLIENT_TRAFFIC_SECRET "660838cb79c4852258346112f481b75463b39aec83b961cd999741d720b18c95df0c3eabc1ec6b1505703ce1925bf396" #define AES_256_SECP521R1_KYBER1024R3_SERVER_TRAFFIC_SECRET "19cb80a0d66c0e616891370273b92cf700d1cf32146be6402eb3de62eab6d1ce2d259b404ff29249e8c2af6df416d503" +#define AES_128_SECP256R1_MLKEM768_CLIENT_TRAFFIC_SECRET "e3b086562f8dc237a9dc8710f345821c871417bd57a64a1966860f1f06bcd5dc" +#define AES_128_SECP256R1_MLKEM768_SERVER_TRAFFIC_SECRET "eb3f47d5cc09234957543e1160dde10cc86b817f31c43d5e8af8cdd6167b0336" +#define AES_256_SECP256R1_MLKEM768_CLIENT_TRAFFIC_SECRET "9e65803eeb8324eb5faea82be52c266e0bf8ac398f091db73a48e68ee2ff0a91915b3f1f4e9907e33543a9ebb1f7a748" +#define AES_256_SECP256R1_MLKEM768_SERVER_TRAFFIC_SECRET "cb8fc8707f294e3ab9b98f0d873b1e1c5d740ecd254c67fcca44b5444742bf958102be17beb5c89ae08b8b31191d9137" + +#define AES_128_X25519_MLKEM768_CLIENT_TRAFFIC_SECRET "8bf7f5f36cdece4ca1439e14e9b585cd5c2c11753ce53733da771c89ba7d8162" +#define AES_128_X25519_MLKEM768_SERVER_TRAFFIC_SECRET "c9221c9f9fad66ac7ae568e46695229eaf95196819c2bb997469f010075b953e" +#define AES_256_X25519_MLKEM768_CLIENT_TRAFFIC_SECRET "44eb9e15ef082936fe7a2c169be644ff16b47fb2a91f7223069cbd8d9b063a034f0936234e60a733a30db6d7226d984d" +#define AES_256_X25519_MLKEM768_SERVER_TRAFFIC_SECRET "852b46f0e3cdc222badc0b85f4cfb4f332c2d8ea8c9695d6024e129b5056d2c534191ee76bff50148f19a88f81897112" + /* A fake transcript string to hash when deriving handshake secrets */ #define FAKE_TRANSCRIPT "client_hello || server_hello" @@ -385,6 +397,70 @@ int main(int argc, char **argv) .expected_server_traffic_secret = &aes_256_x25519_kyber768r3_server_secret, }; + S2N_BLOB_FROM_HEX(mlkem768_secret, MLKEM768_SECRET); + S2N_BLOB_FROM_HEX(secp256r1_mlkem768_hybrid_secret, SECP256R1_MLKEM768_HYBRID_SECRET); + S2N_BLOB_FROM_HEX(x25519_mlkem768_hybrid_secret, X25519_MLKEM768_HYBRID_SECRET); + + S2N_BLOB_FROM_HEX(aes_128_secp256r1_mlkem768_client_secret, AES_128_SECP256R1_MLKEM768_CLIENT_TRAFFIC_SECRET); + S2N_BLOB_FROM_HEX(aes_128_secp256r1_mlkem768_server_secret, AES_128_SECP256R1_MLKEM768_SERVER_TRAFFIC_SECRET); + + const struct hybrid_test_vector aes_128_sha_256_secp256r1_mlkem768_vector = { + .cipher_suite = &s2n_tls13_aes_128_gcm_sha256, + .transcript = FAKE_TRANSCRIPT, + .kem_group = &s2n_secp256r1_mlkem_768, + .client_ecc_key = CLIENT_SECP256R1_PRIV_KEY, + .server_ecc_key = SERVER_SECP256R1_PRIV_KEY, + .pq_secret = &mlkem768_secret, + .expected_hybrid_secret = &secp256r1_mlkem768_hybrid_secret, + .expected_client_traffic_secret = &aes_128_secp256r1_mlkem768_client_secret, + .expected_server_traffic_secret = &aes_128_secp256r1_mlkem768_server_secret, + }; + + S2N_BLOB_FROM_HEX(aes_256_secp256r1_mlkem768_client_secret, AES_256_SECP256R1_MLKEM768_CLIENT_TRAFFIC_SECRET); + S2N_BLOB_FROM_HEX(aes_256_secp256r1_mlkem768_server_secret, AES_256_SECP256R1_MLKEM768_SERVER_TRAFFIC_SECRET); + + const struct hybrid_test_vector aes_256_sha_384_secp256r1_mlkem768_vector = { + .cipher_suite = &s2n_tls13_aes_256_gcm_sha384, + .transcript = FAKE_TRANSCRIPT, + .kem_group = &s2n_secp256r1_mlkem_768, + .client_ecc_key = CLIENT_SECP256R1_PRIV_KEY, + .server_ecc_key = SERVER_SECP256R1_PRIV_KEY, + .pq_secret = &mlkem768_secret, + .expected_hybrid_secret = &secp256r1_mlkem768_hybrid_secret, + .expected_client_traffic_secret = &aes_256_secp256r1_mlkem768_client_secret, + .expected_server_traffic_secret = &aes_256_secp256r1_mlkem768_server_secret, + }; + + S2N_BLOB_FROM_HEX(aes_128_x25519_mlkem768_client_secret, AES_128_X25519_MLKEM768_CLIENT_TRAFFIC_SECRET); + S2N_BLOB_FROM_HEX(aes_128_x25519_mlkem768_server_secret, AES_128_X25519_MLKEM768_SERVER_TRAFFIC_SECRET); + + const struct hybrid_test_vector aes_128_sha_256_x25519_mlkem768_vector = { + .cipher_suite = &s2n_tls13_aes_128_gcm_sha256, + .transcript = FAKE_TRANSCRIPT, + .kem_group = &s2n_x25519_mlkem_768, + .client_ecc_key = CLIENT_X25519_PRIV_KEY, + .server_ecc_key = SERVER_X25519_PRIV_KEY, + .pq_secret = &mlkem768_secret, + .expected_hybrid_secret = &x25519_mlkem768_hybrid_secret, + .expected_client_traffic_secret = &aes_128_x25519_mlkem768_client_secret, + .expected_server_traffic_secret = &aes_128_x25519_mlkem768_server_secret, + }; + + S2N_BLOB_FROM_HEX(aes_256_x25519_mlkem768_client_secret, AES_256_X25519_MLKEM768_CLIENT_TRAFFIC_SECRET); + S2N_BLOB_FROM_HEX(aes_256_x25519_mlkem768_server_secret, AES_256_X25519_MLKEM768_SERVER_TRAFFIC_SECRET); + + const struct hybrid_test_vector aes_256_sha_384_x25519_mlkem768_vector = { + .cipher_suite = &s2n_tls13_aes_256_gcm_sha384, + .transcript = FAKE_TRANSCRIPT, + .kem_group = &s2n_x25519_mlkem_768, + .client_ecc_key = CLIENT_X25519_PRIV_KEY, + .server_ecc_key = SERVER_X25519_PRIV_KEY, + .pq_secret = &mlkem768_secret, + .expected_hybrid_secret = &x25519_mlkem768_hybrid_secret, + .expected_client_traffic_secret = &aes_256_x25519_mlkem768_client_secret, + .expected_server_traffic_secret = &aes_256_x25519_mlkem768_server_secret, + }; + const struct hybrid_test_vector *all_test_vectors[] = { &aes_128_sha_256_secp256r1_kyber512r3_vector, &aes_256_sha_384_secp256r1_kyber512r3_vector, @@ -398,6 +474,10 @@ int main(int argc, char **argv) &aes_256_sha_384_secp521r1_kyber1024r3_vector, &aes_128_sha_256_x25519_kyber768r3_vector, &aes_256_sha_384_x25519_kyber768r3_vector, + &aes_128_sha_256_secp256r1_mlkem768_vector, + &aes_256_sha_384_secp256r1_mlkem768_vector, + &aes_128_sha_256_x25519_mlkem768_vector, + &aes_256_sha_384_x25519_mlkem768_vector, }; EXPECT_EQUAL(s2n_array_len(all_test_vectors), (2 * S2N_KEM_GROUPS_COUNT)); diff --git a/tests/unit/s2n_tls13_pq_handshake_test.c b/tests/unit/s2n_tls13_pq_handshake_test.c index 31450fcf28e..03c808986ac 100644 --- a/tests/unit/s2n_tls13_pq_handshake_test.c +++ b/tests/unit/s2n_tls13_pq_handshake_test.c @@ -406,6 +406,27 @@ int main() .ecc_preferences = &s2n_ecc_preferences_20201021, }; + const struct s2n_kem_group *mlkem768_test_groups[] = { + &s2n_x25519_mlkem_768, + &s2n_secp256r1_mlkem_768, + }; + + const struct s2n_kem_preferences mlkem768_test_prefs = { + .kem_count = 0, + .kems = NULL, + .tls13_kem_group_count = s2n_array_len(mlkem768_test_groups), + .tls13_kem_groups = mlkem768_test_groups, + .tls13_pq_hybrid_draft_revision = 5 + }; + + const struct s2n_security_policy mlkem768_test_policy = { + .minimum_protocol_version = S2N_TLS13, + .cipher_preferences = &cipher_preferences_20190801, + .kem_preferences = &mlkem768_test_prefs, + .signature_preferences = &s2n_signature_preferences_20200207, + .ecc_preferences = &s2n_ecc_preferences_20240603, + }; + const struct s2n_security_policy ecc_retry_policy = { .minimum_protocol_version = security_policy_pq_tls_1_0_2020_12.minimum_protocol_version, .cipher_preferences = security_policy_pq_tls_1_0_2020_12.cipher_preferences, @@ -429,6 +450,17 @@ int main() bool len_prefix_expected; }; + /* ML-KEM is only available on newer versions of AWS-LC. If it's + * unavailable, we must downgrade the assertions to Kyber or EC. */ + const struct s2n_kem_group *kyber_if_no_mlkem = &s2n_x25519_mlkem_768; + const struct s2n_kem_group *null_if_no_mlkem = &s2n_x25519_mlkem_768; + const struct s2n_ecc_named_curve *ec_if_no_mlkem = NULL; + if (!s2n_libcrypto_supports_mlkem()) { + kyber_if_no_mlkem = &s2n_secp256r1_kyber_768_r3; + null_if_no_mlkem = NULL; + ec_if_no_mlkem = &s2n_ecc_curve_secp256r1; + } + /* Test vectors that expect to negotiate PQ assume that PQ is enabled in s2n. * If PQ is disabled, the expected negotiation outcome is overridden below * before performing the handshake test. */ @@ -642,6 +674,15 @@ int main() .hrr_expected = true, .len_prefix_expected = true, }, + /* Confirm that MLKEM768 is negotiable */ + { + .client_policy = &mlkem768_test_policy, + .server_policy = &mlkem768_test_policy, + .expected_kem_group = null_if_no_mlkem, + .expected_curve = default_curve, + .hrr_expected = false, + .len_prefix_expected = false, + } }; for (size_t i = 0; i < s2n_array_len(test_vectors); i++) { diff --git a/tls/extensions/s2n_client_key_share.c b/tls/extensions/s2n_client_key_share.c index 912b58d4f18..04e4218c0d9 100644 --- a/tls/extensions/s2n_client_key_share.c +++ b/tls/extensions/s2n_client_key_share.c @@ -122,8 +122,13 @@ static int s2n_generate_pq_hybrid_key_share(struct s2n_stuffer *out, struct s2n_ struct s2n_kem_params *kem_params = &kem_group_params->kem_params; kem_params->kem = kem_group->kem; - POSIX_GUARD_RESULT(s2n_ecdhe_send_public_key(ecc_params, out, kem_params->len_prefixed)); - POSIX_GUARD(s2n_kem_send_public_key(out, kem_params)); + if (kem_group_params->kem_group->send_kem_first) { + POSIX_GUARD(s2n_kem_send_public_key(out, kem_params)); + POSIX_GUARD_RESULT(s2n_ecdhe_send_public_key(ecc_params, out, kem_params->len_prefixed)); + } else { + POSIX_GUARD_RESULT(s2n_ecdhe_send_public_key(ecc_params, out, kem_params->len_prefixed)); + POSIX_GUARD(s2n_kem_send_public_key(out, kem_params)); + } POSIX_GUARD(s2n_stuffer_write_vector_size(&total_share_size)); @@ -294,6 +299,25 @@ static int s2n_client_key_share_recv_ecc(struct s2n_connection *conn, struct s2n return S2N_SUCCESS; } +static int s2n_client_key_share_recv_hybrid_partial_ecc(struct s2n_stuffer *key_share, struct s2n_kem_group_params *new_client_params) +{ + const struct s2n_kem_group *kem_group = new_client_params->kem_group; + + if (new_client_params->kem_params.len_prefixed) { + uint16_t ec_share_size = 0; + POSIX_GUARD(s2n_stuffer_read_uint16(key_share, &ec_share_size)); + POSIX_ENSURE(ec_share_size == kem_group->curve->share_size, S2N_ERR_SIZE_MISMATCH); + } + + POSIX_GUARD(s2n_client_key_share_parse_ecc(key_share, kem_group->curve, &new_client_params->ecc_params)); + + /* If we were unable to parse the EC portion of the share, negotiated_curve + * will be NULL, and we should ignore the entire key share. */ + POSIX_ENSURE_REF(new_client_params->ecc_params.negotiated_curve); + + return S2N_SUCCESS; +} + static int s2n_client_key_share_recv_pq_hybrid(struct s2n_connection *conn, struct s2n_stuffer *key_share, uint16_t kem_group_iana_id) { POSIX_ENSURE_REF(conn); @@ -360,33 +384,29 @@ static int s2n_client_key_share_recv_pq_hybrid(struct s2n_connection *conn, stru bool is_hybrid_share_length_prefixed = (actual_hybrid_share_size == prefixed_hybrid_share_size); - if (is_hybrid_share_length_prefixed) { - /* Ignore KEM groups with unexpected ECC share sizes */ - uint16_t ec_share_size = 0; - POSIX_GUARD(s2n_stuffer_read_uint16(key_share, &ec_share_size)); - if (ec_share_size != kem_group->curve->share_size) { - return S2N_SUCCESS; - } - } - DEFER_CLEANUP(struct s2n_kem_group_params new_client_params = { 0 }, s2n_kem_group_free); new_client_params.kem_group = kem_group; /* Need to save whether the client included the length prefix so that we can match their behavior in our response. */ new_client_params.kem_params.len_prefixed = is_hybrid_share_length_prefixed; - - POSIX_GUARD(s2n_client_key_share_parse_ecc(key_share, kem_group->curve, &new_client_params.ecc_params)); - /* If we were unable to parse the EC portion of the share, negotiated_curve - * will be NULL, and we should ignore the entire key share. */ - if (!new_client_params.ecc_params.negotiated_curve) { - return S2N_SUCCESS; - } + new_client_params.kem_params.kem = kem_group->kem; /* Note: the PQ share size is validated in s2n_kem_recv_public_key() */ - /* Ignore groups with PQ public keys we can't parse */ - new_client_params.kem_params.kem = kem_group->kem; - if (s2n_kem_recv_public_key(key_share, &new_client_params.kem_params) != S2N_SUCCESS) { - return S2N_SUCCESS; + /* Ignore PQ and ECC groups with public keys we can't parse */ + if (kem_group->send_kem_first) { + if (s2n_kem_recv_public_key(key_share, &new_client_params.kem_params) != S2N_SUCCESS) { + return S2N_SUCCESS; + } + if (s2n_client_key_share_recv_hybrid_partial_ecc(key_share, &new_client_params) != S2N_SUCCESS) { + return S2N_SUCCESS; + } + } else { + if (s2n_client_key_share_recv_hybrid_partial_ecc(key_share, &new_client_params) != S2N_SUCCESS) { + return S2N_SUCCESS; + } + if (s2n_kem_recv_public_key(key_share, &new_client_params.kem_params) != S2N_SUCCESS) { + return S2N_SUCCESS; + } } POSIX_GUARD(s2n_kem_group_free(client_params)); diff --git a/tls/extensions/s2n_server_key_share.c b/tls/extensions/s2n_server_key_share.c index ba01b57b410..6d97c1eaf81 100644 --- a/tls/extensions/s2n_server_key_share.c +++ b/tls/extensions/s2n_server_key_share.c @@ -34,6 +34,22 @@ const s2n_extension_type s2n_server_key_share_extension = { .if_missing = s2n_extension_noop_if_missing, }; +static int s2n_server_key_share_send_hybrid_partial_ecc(struct s2n_connection *conn, struct s2n_stuffer *out) +{ + struct s2n_kem_group_params *server_kem_group_params = &conn->kex_params.server_kem_group_params; + struct s2n_kem_params *client_kem_params = &conn->kex_params.client_kem_group_params.kem_params; + + struct s2n_ecc_evp_params *server_ecc_params = &server_kem_group_params->ecc_params; + POSIX_ENSURE_REF(server_ecc_params->negotiated_curve); + if (client_kem_params->len_prefixed) { + POSIX_GUARD(s2n_stuffer_write_uint16(out, server_ecc_params->negotiated_curve->share_size)); + } + POSIX_GUARD(s2n_ecc_evp_generate_ephemeral_key(server_ecc_params)); + POSIX_GUARD(s2n_ecc_evp_write_params_point(server_ecc_params, out)); + + return S2N_SUCCESS; +} + static int s2n_server_key_share_generate_pq_hybrid(struct s2n_connection *conn, struct s2n_stuffer *out) { POSIX_ENSURE_REF(out); @@ -43,6 +59,7 @@ static int s2n_server_key_share_generate_pq_hybrid(struct s2n_connection *conn, struct s2n_kem_group_params *server_kem_group_params = &conn->kex_params.server_kem_group_params; struct s2n_kem_params *client_kem_params = &conn->kex_params.client_kem_group_params.kem_params; + POSIX_ENSURE_REF(client_kem_params->public_key.data); POSIX_ENSURE_REF(server_kem_group_params->kem_group); POSIX_GUARD(s2n_stuffer_write_uint16(out, server_kem_group_params->kem_group->iana_id)); @@ -50,20 +67,17 @@ static int s2n_server_key_share_generate_pq_hybrid(struct s2n_connection *conn, struct s2n_stuffer_reservation total_share_size = { 0 }; POSIX_GUARD(s2n_stuffer_reserve_uint16(out, &total_share_size)); - struct s2n_ecc_evp_params *server_ecc_params = &server_kem_group_params->ecc_params; - POSIX_ENSURE_REF(server_ecc_params->negotiated_curve); - if (client_kem_params->len_prefixed) { - POSIX_GUARD(s2n_stuffer_write_uint16(out, server_ecc_params->negotiated_curve->share_size)); - } - POSIX_GUARD(s2n_ecc_evp_generate_ephemeral_key(server_ecc_params)); - POSIX_GUARD(s2n_ecc_evp_write_params_point(server_ecc_params, out)); - - POSIX_ENSURE_REF(client_kem_params->public_key.data); /* s2n_kem_send_ciphertext() will generate the PQ shared secret and use * the client's public key to encapsulate; the PQ shared secret will be * stored in client_kem_params, and will be used during the hybrid shared * secret derivation. */ - POSIX_GUARD(s2n_kem_send_ciphertext(out, client_kem_params)); + if (server_kem_group_params->kem_group->send_kem_first) { + POSIX_GUARD(s2n_kem_send_ciphertext(out, client_kem_params)); + POSIX_GUARD(s2n_server_key_share_send_hybrid_partial_ecc(conn, out)); + } else { + POSIX_GUARD(s2n_server_key_share_send_hybrid_partial_ecc(conn, out)); + POSIX_GUARD(s2n_kem_send_ciphertext(out, client_kem_params)); + } POSIX_GUARD(s2n_stuffer_write_vector_size(&total_share_size)); return S2N_SUCCESS; @@ -157,6 +171,27 @@ static int s2n_server_key_share_send(struct s2n_connection *conn, struct s2n_stu return S2N_SUCCESS; } +static int s2n_server_key_share_recv_hybrid_partial_ecc(struct s2n_connection *conn, struct s2n_stuffer *extension) +{ + struct s2n_kem_params *client_kem_params = &conn->kex_params.client_kem_group_params.kem_params; + struct s2n_kem_group_params *server_kem_group_params = &conn->kex_params.server_kem_group_params; + uint16_t expected_ecc_share_size = server_kem_group_params->kem_group->curve->share_size; + + /* Parse ECC key share */ + if (client_kem_params->len_prefixed) { + uint16_t actual_ecc_share_size = 0; + POSIX_GUARD(s2n_stuffer_read_uint16(extension, &actual_ecc_share_size)); + POSIX_ENSURE(actual_ecc_share_size == expected_ecc_share_size, S2N_ERR_BAD_KEY_SHARE); + } + + struct s2n_blob point_blob = { 0 }; + POSIX_ENSURE(s2n_ecc_evp_read_params_point(extension, expected_ecc_share_size, &point_blob) == S2N_SUCCESS, S2N_ERR_BAD_KEY_SHARE); + POSIX_ENSURE(s2n_ecc_evp_parse_params_point(&point_blob, &server_kem_group_params->ecc_params) == S2N_SUCCESS, S2N_ERR_BAD_KEY_SHARE); + POSIX_ENSURE(server_kem_group_params->ecc_params.evp_pkey != NULL, S2N_ERR_BAD_KEY_SHARE); + + return S2N_SUCCESS; +} + static int s2n_server_key_share_recv_pq_hybrid(struct s2n_connection *conn, uint16_t named_group_iana, struct s2n_stuffer *extension) { @@ -211,23 +246,14 @@ static int s2n_server_key_share_recv_pq_hybrid(struct s2n_connection *conn, uint /* Don't need to call s2n_is_tls13_hybrid_kem_length_prefixed() to set client_kem_params->len_prefixed since we are * the client, and server-side should auto-detect hybrid share size and match our behavior. */ - /* Parse ECC key share */ - uint16_t expected_ecc_share_size = server_kem_group_params->kem_group->curve->share_size; - if (client_kem_params->len_prefixed) { - uint16_t actual_ecc_share_size = 0; - POSIX_GUARD(s2n_stuffer_read_uint16(extension, &actual_ecc_share_size)); - POSIX_ENSURE(actual_ecc_share_size == expected_ecc_share_size, S2N_ERR_BAD_KEY_SHARE); + if (!server_kem_group_params->kem_group->send_kem_first) { + POSIX_ENSURE(s2n_server_key_share_recv_hybrid_partial_ecc(conn, extension) == S2N_SUCCESS, S2N_ERR_BAD_KEY_SHARE); + POSIX_ENSURE(s2n_kem_recv_ciphertext(extension, client_kem_params) == S2N_SUCCESS, S2N_ERR_BAD_KEY_SHARE); + } else { + POSIX_ENSURE(s2n_kem_recv_ciphertext(extension, client_kem_params) == S2N_SUCCESS, S2N_ERR_BAD_KEY_SHARE); + POSIX_ENSURE(s2n_server_key_share_recv_hybrid_partial_ecc(conn, extension) == S2N_SUCCESS, S2N_ERR_BAD_KEY_SHARE); } - struct s2n_blob point_blob = { 0 }; - POSIX_ENSURE(s2n_ecc_evp_read_params_point(extension, expected_ecc_share_size, &point_blob) == S2N_SUCCESS, S2N_ERR_BAD_KEY_SHARE); - POSIX_ENSURE(s2n_ecc_evp_parse_params_point(&point_blob, &server_kem_group_params->ecc_params) == S2N_SUCCESS, S2N_ERR_BAD_KEY_SHARE); - POSIX_ENSURE(server_kem_group_params->ecc_params.evp_pkey != NULL, S2N_ERR_BAD_KEY_SHARE); - - /* Parse the PQ KEM key share */ - POSIX_ENSURE(s2n_kem_recv_ciphertext(extension, client_kem_params) == S2N_SUCCESS, - S2N_ERR_BAD_KEY_SHARE); - return S2N_SUCCESS; } diff --git a/tls/s2n_kem.c b/tls/s2n_kem.c index 13ea2c7c129..65bf9d951db 100644 --- a/tls/s2n_kem.c +++ b/tls/s2n_kem.c @@ -23,7 +23,18 @@ #include "utils/s2n_mem.h" #include "utils/s2n_safety.h" -/* The KEM IDs and names come from https://tools.ietf.org/html/draft-campagna-tls-bike-sike-hybrid */ +const struct s2n_kem s2n_mlkem_768 = { + .name = "mlkem768", + .kem_nid = S2N_NID_MLKEM768, + .kem_extension_id = 0, /* This is not used in TLS 1.2's KEM extension */ + .public_key_length = S2N_MLKEM_768_PUBLIC_KEY_BYTES, + .private_key_length = S2N_MLKEM_768_SECRET_KEY_BYTES, + .shared_secret_key_length = S2N_MLKEM_768_SHARED_SECRET_BYTES, + .ciphertext_length = S2N_MLKEM_768_CIPHERTEXT_BYTES, + .generate_keypair = &s2n_evp_kem_generate_keypair, + .encapsulate = &s2n_evp_kem_encapsulate, + .decapsulate = &s2n_evp_kem_decapsulate, +}; const struct s2n_kem s2n_kyber_512_r3 = { .name = "kyber512r3", @@ -84,17 +95,38 @@ const struct s2n_iana_to_kem kem_mapping[1] = { * https://github.com/open-quantum-safe/oqs-provider/blob/main/oqs-template/oqs-kem-info.md * and * https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml + */ + +/* + * ML-KEM based hybrid KEMs as specified by IETF and registered in IANA. * - * The structure of the hybrid share is: - * size of ECC key share (2 bytes) - * || ECC key share (variable bytes) - * || size of PQ key share (2 bytes) - * || PQ key share (variable bytes) */ + * https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 + * https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/ + */ +const struct s2n_kem_group s2n_secp256r1_mlkem_768 = { + .name = "SecP256r1MLKEM768", + .iana_id = TLS_PQ_KEM_GROUP_ID_SECP256R1_MLKEM_768, + .curve = &s2n_ecc_curve_secp256r1, + .kem = &s2n_mlkem_768, + .send_kem_first = 0, +}; + +const struct s2n_kem_group s2n_x25519_mlkem_768 = { + .name = "X25519MLKEM768", + .iana_id = TLS_PQ_KEM_GROUP_ID_X25519_MLKEM_768, + .curve = &s2n_ecc_curve_x25519, + .kem = &s2n_mlkem_768, + /* ML-KEM KeyShare should always be sent first for X25519MLKEM768. + * https://datatracker.ietf.org/doc/html/draft-kwiatkowski-tls-ecdhe-mlkem-02#name-negotiated-groups */ + .send_kem_first = 1, +}; + const struct s2n_kem_group s2n_secp256r1_kyber_512_r3 = { .name = "secp256r1_kyber-512-r3", .iana_id = TLS_PQ_KEM_GROUP_ID_SECP256R1_KYBER_512_R3, .curve = &s2n_ecc_curve_secp256r1, .kem = &s2n_kyber_512_r3, + .send_kem_first = 0, }; const struct s2n_kem_group s2n_secp256r1_kyber_768_r3 = { @@ -102,6 +134,7 @@ const struct s2n_kem_group s2n_secp256r1_kyber_768_r3 = { .iana_id = TLS_PQ_KEM_GROUP_ID_SECP256R1_KYBER_768_R3, .curve = &s2n_ecc_curve_secp256r1, .kem = &s2n_kyber_768_r3, + .send_kem_first = 0, }; const struct s2n_kem_group s2n_secp384r1_kyber_768_r3 = { @@ -109,6 +142,7 @@ const struct s2n_kem_group s2n_secp384r1_kyber_768_r3 = { .iana_id = TLS_PQ_KEM_GROUP_ID_SECP384R1_KYBER_768_R3, .curve = &s2n_ecc_curve_secp384r1, .kem = &s2n_kyber_768_r3, + .send_kem_first = 0, }; const struct s2n_kem_group s2n_secp521r1_kyber_1024_r3 = { @@ -116,6 +150,7 @@ const struct s2n_kem_group s2n_secp521r1_kyber_1024_r3 = { .iana_id = TLS_PQ_KEM_GROUP_ID_SECP521R1_KYBER_1024_R3, .curve = &s2n_ecc_curve_secp521r1, .kem = &s2n_kyber_1024_r3, + .send_kem_first = 0, }; const struct s2n_kem_group s2n_x25519_kyber_512_r3 = { @@ -123,6 +158,7 @@ const struct s2n_kem_group s2n_x25519_kyber_512_r3 = { .iana_id = TLS_PQ_KEM_GROUP_ID_X25519_KYBER_512_R3, .curve = &s2n_ecc_curve_x25519, .kem = &s2n_kyber_512_r3, + .send_kem_first = 0, }; const struct s2n_kem_group s2n_x25519_kyber_768_r3 = { @@ -130,9 +166,12 @@ const struct s2n_kem_group s2n_x25519_kyber_768_r3 = { .iana_id = TLS_PQ_KEM_GROUP_ID_X25519_KYBER_768_R3, .curve = &s2n_ecc_curve_x25519, .kem = &s2n_kyber_768_r3, + .send_kem_first = 0, }; const struct s2n_kem_group *ALL_SUPPORTED_KEM_GROUPS[] = { + &s2n_x25519_mlkem_768, + &s2n_secp256r1_mlkem_768, &s2n_secp256r1_kyber_512_r3, &s2n_x25519_kyber_512_r3, &s2n_secp256r1_kyber_768_r3, @@ -424,16 +463,22 @@ int s2n_kem_recv_ciphertext(struct s2n_stuffer *in, struct s2n_kem_params *kem_p bool s2n_kem_group_is_available(const struct s2n_kem_group *kem_group) { - if (kem_group == NULL) { + /* Check for values that might be undefined when compiling for older libcrypto's */ + if (kem_group == NULL || kem_group->curve == NULL || kem_group->kem->kem_nid == NID_undef) { return false; } + bool available = s2n_libcrypto_supports_evp_kem(); /* x25519 based tls13_kem_groups require EVP_APIS_SUPPORTED */ - if (kem_group->curve == NULL) { - available = false; - } else if (kem_group->curve == &s2n_ecc_curve_x25519) { + if (kem_group->curve == &s2n_ecc_curve_x25519) { available &= s2n_is_evp_apis_supported(); } + + /* Only newer versions of libcrypto have ML-KEM support. */ + if (kem_group->kem == &s2n_mlkem_768) { + available &= s2n_libcrypto_supports_mlkem(); + } + return available; } diff --git a/tls/s2n_kem.h b/tls/s2n_kem.h index bb5ba5ff94a..7788b23a1a3 100644 --- a/tls/s2n_kem.h +++ b/tls/s2n_kem.h @@ -32,10 +32,16 @@ typedef uint16_t kem_ciphertext_key_size; #define OUT /* Indicates a function output */ #if defined(S2N_LIBCRYPTO_SUPPORTS_EVP_KEM) + #if defined(S2N_LIBCRYPTO_SUPPORTS_MLKEM) + #define S2N_NID_MLKEM768 NID_MLKEM768 + #else + #define S2N_NID_MLKEM768 NID_undef + #endif #define S2N_NID_KYBER512 NID_KYBER512_R3 #define S2N_NID_KYBER768 NID_KYBER768_R3 #define S2N_NID_KYBER1024 NID_KYBER1024_R3 #else + #define S2N_NID_MLKEM768 NID_undef #define S2N_NID_KYBER512 NID_undef #define S2N_NID_KYBER768 NID_undef #define S2N_NID_KYBER1024 NID_undef @@ -76,6 +82,10 @@ struct s2n_kem_group { uint16_t iana_id; const struct s2n_ecc_named_curve *curve; const struct s2n_kem *kem; + + /* Whether the PQ KeyShare should be sent before the ECC KeyShare. Only enabled for X25519MLKEM768. + * See: https://datatracker.ietf.org/doc/html/draft-kwiatkowski-tls-ecdhe-mlkem-02#name-negotiated-groups */ + bool send_kem_first; }; struct s2n_kem_group_params { @@ -84,20 +94,23 @@ struct s2n_kem_group_params { struct s2n_ecc_evp_params ecc_params; }; +extern const struct s2n_kem s2n_mlkem_768; extern const struct s2n_kem s2n_kyber_512_r3; extern const struct s2n_kem s2n_kyber_768_r3; extern const struct s2n_kem s2n_kyber_1024_r3; -#define S2N_KEM_GROUPS_COUNT 6 +#define S2N_KEM_GROUPS_COUNT 8 extern const struct s2n_kem_group *ALL_SUPPORTED_KEM_GROUPS[S2N_KEM_GROUPS_COUNT]; /* NIST curve KEM Groups */ +extern const struct s2n_kem_group s2n_secp256r1_mlkem_768; extern const struct s2n_kem_group s2n_secp256r1_kyber_512_r3; extern const struct s2n_kem_group s2n_secp256r1_kyber_768_r3; extern const struct s2n_kem_group s2n_secp384r1_kyber_768_r3; extern const struct s2n_kem_group s2n_secp521r1_kyber_1024_r3; /* x25519 KEM Groups */ +extern const struct s2n_kem_group s2n_x25519_mlkem_768; extern const struct s2n_kem_group s2n_x25519_kyber_512_r3; extern const struct s2n_kem_group s2n_x25519_kyber_768_r3; @@ -121,28 +134,11 @@ int s2n_kem_send_ciphertext(struct s2n_stuffer *out, struct s2n_kem_params *kem_ int s2n_kem_recv_ciphertext(struct s2n_stuffer *in, struct s2n_kem_params *kem_params); bool s2n_kem_group_is_available(const struct s2n_kem_group *kem_group); -/* The following are API signatures for PQ KEMs as defined by NIST. All functions return 0 - * on success, and !0 on failure. Avoid calling these functions directly within s2n. Instead, - * use s2n_kem_{generate_keypair, encapsulate, decapsulate}, or - * s2n_kem_{send_public_key, recv_public_key, send_ciphertext, recv_ciphertext}. - * - * int *_keypair(OUT pk, OUT sk) - Generate public/private keypair - * pk - generated public key - * sk - generated secret key - * - * int *_enc(OUT ct, OUT ss, IN pk) - Generate a shared secret and encapsulate it - * ct - key encapsulation message (ciphertext) - * ss - plaintext shared secret - * pk - public key to use for encapsulation - * - * int *_dec(OUT ss, IN ct, IN sk) - Decapsulate a key encapsulation message and recover the shared secret - * ss - plaintext shared secret - * ct - key encapsulation message (ciphertext) - * sk - secret key to use for decapsulation */ - -/* If s2n is compiled with support for PQ crypto, these functions will be defined in the respective KEM directories. - * If s2n is compiled without support for PQ, stubs of these functions are defined in s2n_kem.c. */ -/* sikep503r1 */ +/* mlkem768 */ +#define S2N_MLKEM_768_PUBLIC_KEY_BYTES 1184 +#define S2N_MLKEM_768_SECRET_KEY_BYTES 2400 +#define S2N_MLKEM_768_CIPHERTEXT_BYTES 1088 +#define S2N_MLKEM_768_SHARED_SECRET_BYTES 32 /* kyber512r3 */ #define S2N_KYBER_512_R3_PUBLIC_KEY_BYTES 800 diff --git a/tls/s2n_kem_preferences.c b/tls/s2n_kem_preferences.c index 7a30d34f3ee..57883f47a73 100644 --- a/tls/s2n_kem_preferences.c +++ b/tls/s2n_kem_preferences.c @@ -41,6 +41,18 @@ const struct s2n_kem_group *pq_kem_groups_r3_2023_12[] = { &s2n_secp256r1_kyber_512_r3, }; +/* Includes both Kyber and ML-KEM KEMs. */ +const struct s2n_kem_group *pq_kem_groups_2024_10_all[] = { + &s2n_x25519_mlkem_768, + &s2n_secp256r1_mlkem_768, + &s2n_secp256r1_kyber_768_r3, + &s2n_x25519_kyber_768_r3, + &s2n_secp384r1_kyber_768_r3, + &s2n_secp521r1_kyber_1024_r3, + &s2n_secp256r1_kyber_512_r3, + &s2n_x25519_kyber_512_r3, +}; + const struct s2n_kem_preferences kem_preferences_pq_tls_1_0_2021_05 = { .kem_count = s2n_array_len(pq_kems_r3_2021_05), .kems = pq_kems_r3_2021_05, @@ -78,8 +90,8 @@ const struct s2n_kem_preferences kem_preferences_pq_tls_1_3_2023_12 = { const struct s2n_kem_preferences kem_preferences_all = { .kem_count = s2n_array_len(pq_kems_r3_2021_05), .kems = pq_kems_r3_2021_05, - .tls13_kem_group_count = s2n_array_len(pq_kem_groups_r3_2023_06), - .tls13_kem_groups = pq_kem_groups_r3_2023_06, + .tls13_kem_group_count = s2n_array_len(pq_kem_groups_2024_10_all), + .tls13_kem_groups = pq_kem_groups_2024_10_all, .tls13_pq_hybrid_draft_revision = 5 }; diff --git a/tls/s2n_kem_preferences.h b/tls/s2n_kem_preferences.h index 0d10b45a08c..48a4908e243 100644 --- a/tls/s2n_kem_preferences.h +++ b/tls/s2n_kem_preferences.h @@ -43,6 +43,7 @@ extern const struct s2n_kem *pq_kems_r3_2021_05[]; extern const struct s2n_kem_group *pq_kem_groups_r3_2021_05[]; extern const struct s2n_kem_group *pq_kem_groups_r3_2023_06[]; +extern const struct s2n_kem_group *pq_kem_groups_2024_10_all[]; extern const struct s2n_kem_preferences kem_preferences_pq_tls_1_0_2021_05; extern const struct s2n_kem_preferences kem_preferences_pq_tls_1_0_2023_01; diff --git a/tls/s2n_tls13_handshake.c b/tls/s2n_tls13_handshake.c index 27069ba9263..b9f2adf70c2 100644 --- a/tls/s2n_tls13_handshake.c +++ b/tls/s2n_tls13_handshake.c @@ -117,8 +117,14 @@ int s2n_tls13_compute_pq_hybrid_shared_secret(struct s2n_connection *conn, struc POSIX_GUARD(s2n_alloc(shared_secret, hybrid_shared_secret_size)); struct s2n_stuffer stuffer_combiner = { 0 }; POSIX_GUARD(s2n_stuffer_init(&stuffer_combiner, shared_secret)); - POSIX_GUARD(s2n_stuffer_write(&stuffer_combiner, &ecdhe_shared_secret)); - POSIX_GUARD(s2n_stuffer_write(&stuffer_combiner, pq_shared_secret)); + + if (negotiated_kem_group->send_kem_first) { + POSIX_GUARD(s2n_stuffer_write(&stuffer_combiner, pq_shared_secret)); + POSIX_GUARD(s2n_stuffer_write(&stuffer_combiner, &ecdhe_shared_secret)); + } else { + POSIX_GUARD(s2n_stuffer_write(&stuffer_combiner, &ecdhe_shared_secret)); + POSIX_GUARD(s2n_stuffer_write(&stuffer_combiner, pq_shared_secret)); + } return S2N_SUCCESS; } diff --git a/tls/s2n_tls_parameters.h b/tls/s2n_tls_parameters.h index a35b8d1d721..b030c3c8b2c 100644 --- a/tls/s2n_tls_parameters.h +++ b/tls/s2n_tls_parameters.h @@ -68,6 +68,8 @@ * https://github.com/open-quantum-safe/oqs-provider/blob/main/oqs-template/oqs-kem-info.md and * https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml */ +#define TLS_PQ_KEM_GROUP_ID_SECP256R1_MLKEM_768 0x11EB +#define TLS_PQ_KEM_GROUP_ID_X25519_MLKEM_768 0x11EC #define TLS_PQ_KEM_GROUP_ID_X25519_KYBER_512_R3 0x2F39 #define TLS_PQ_KEM_GROUP_ID_SECP256R1_KYBER_512_R3 0x2F3A #define TLS_PQ_KEM_GROUP_ID_SECP384R1_KYBER_768_R3 0x2F3C