From 5a2fdf68454ee956ecbd347636311e82e8fd3e51 Mon Sep 17 00:00:00 2001 From: Alex Weibel Date: Fri, 11 Oct 2024 12:42:29 -0700 Subject: [PATCH] Add initial support for MLKEM768 (without any new Security Policies) (#4816) --- ...generate_pq_hybrid_tls13_handshake_kats.py | 31 +++ .../s2n_client_key_share_extension_pq_test.c | 221 ++++++++++++------ tests/unit/s2n_kem_preferences_test.c | 28 ++- tests/unit/s2n_pq_kem_test.c | 32 +-- tests/unit/s2n_security_policies_test.c | 16 +- .../s2n_server_key_share_extension_test.c | 16 +- .../s2n_tls13_hybrid_shared_secret_test.c | 82 ++++++- tests/unit/s2n_tls13_pq_handshake_test.c | 77 ++++++ tls/extensions/s2n_client_key_share.c | 67 ++++-- tls/extensions/s2n_server_key_share.c | 84 +++++-- tls/s2n_kem.c | 84 +++++-- tls/s2n_kem.h | 43 ++-- tls/s2n_kem_preferences.c | 6 +- tls/s2n_tls13_handshake.c | 10 +- tls/s2n_tls_parameters.h | 2 + 15 files changed, 602 insertions(+), 197 deletions(-) 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 4521f38ea1c..d103de55632 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..465b2032298 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)); @@ -33,22 +35,15 @@ int main(int argc, char **argv) EXPECT_FALSE(s2n_kem_preferences_includes_tls13_kem_group(&kem_preferences_null, TLS_PQ_KEM_GROUP_ID_SECP521R1_KYBER_1024_R3)); { - const struct s2n_kem_group *test_kem_groups[] = { - &s2n_secp256r1_kyber_512_r3, - &s2n_x25519_kyber_512_r3, - &s2n_secp384r1_kyber_768_r3, - &s2n_secp256r1_kyber_768_r3, - &s2n_x25519_kyber_768_r3, - &s2n_secp521r1_kyber_1024_r3, - }; - const struct s2n_kem_preferences test_prefs = { .kem_count = 0, .kems = NULL, - .tls13_kem_group_count = s2n_array_len(test_kem_groups), - .tls13_kem_groups = test_kem_groups, + .tls13_kem_group_count = S2N_KEM_GROUPS_COUNT, + .tls13_kem_groups = ALL_SUPPORTED_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 +64,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 +80,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..355dbd0b83a 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, @@ -63,25 +64,26 @@ int main() DEFER_CLEANUP(struct s2n_blob ciphertext = { 0 }, s2n_free); EXPECT_SUCCESS(s2n_alloc(&ciphertext, kem->ciphertext_length)); - if (s2n_pq_is_enabled()) { - /* 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)); - EXPECT_PQ_KEM_SUCCESS(kem->decapsulate(kem, server_shared_secret.data, ciphertext.data, private_key.data)); - EXPECT_BYTEARRAY_EQUAL(server_shared_secret.data, client_shared_secret.data, kem->shared_secret_key_length); - - /* By design, if an invalid private key + ciphertext pair is provided to decapsulate(), - * the function should still succeed (return S2N_SUCCESS); however, the shared secret - * that was "decapsulated" will be a garbage random value. */ - ciphertext.data[0] ^= 1; /* Flip a bit to invalidate the ciphertext */ - - EXPECT_PQ_KEM_SUCCESS(kem->decapsulate(kem, server_shared_secret.data, ciphertext.data, private_key.data)); - EXPECT_BYTEARRAY_NOT_EQUAL(server_shared_secret.data, client_shared_secret.data, kem->shared_secret_key_length); - } else { + if (!s2n_kem_is_available(kem)) { EXPECT_FAILURE_WITH_ERRNO(kem->generate_keypair(kem, public_key.data, private_key.data), S2N_ERR_UNIMPLEMENTED); EXPECT_FAILURE_WITH_ERRNO(kem->encapsulate(kem, ciphertext.data, client_shared_secret.data, public_key.data), S2N_ERR_UNIMPLEMENTED); EXPECT_FAILURE_WITH_ERRNO(kem->decapsulate(kem, server_shared_secret.data, ciphertext.data, private_key.data), S2N_ERR_UNIMPLEMENTED); + continue; } + + /* 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)); + EXPECT_PQ_KEM_SUCCESS(kem->decapsulate(kem, server_shared_secret.data, ciphertext.data, private_key.data)); + EXPECT_BYTEARRAY_EQUAL(server_shared_secret.data, client_shared_secret.data, kem->shared_secret_key_length); + + /* By design, if an invalid private key + ciphertext pair is provided to decapsulate(), + * the function should still succeed (return S2N_SUCCESS); however, the shared secret + * that was "decapsulated" will be a garbage random value. */ + ciphertext.data[0] ^= 1; /* Flip a bit to invalidate the ciphertext */ + + EXPECT_PQ_KEM_SUCCESS(kem->decapsulate(kem, server_shared_secret.data, ciphertext.data, private_key.data)); + EXPECT_BYTEARRAY_NOT_EQUAL(server_shared_secret.data, client_shared_secret.data, kem->shared_secret_key_length); } END_TEST(); diff --git a/tests/unit/s2n_security_policies_test.c b/tests/unit/s2n_security_policies_test.c index 1bd0c86bcde..efaf6901642 100644 --- a/tests/unit/s2n_security_policies_test.c +++ b/tests/unit/s2n_security_policies_test.c @@ -198,15 +198,23 @@ 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, ALL_SUPPORTED_KEM_GROUPS); /* 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(S2N_KEM_GROUPS_COUNT, 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(S2N_KEM_GROUPS_COUNT, 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..e5323f5adef 100644 --- a/tests/unit/s2n_server_key_share_extension_test.c +++ b/tests/unit/s2n_server_key_share_extension_test.c @@ -885,15 +885,23 @@ 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); + uint16_t expected_first_share_size = kem_group->curve->share_size; + uint16_t expected_second_share_size = kem_group->kem->ciphertext_length; + + if (kem_group->send_kem_first) { + expected_first_share_size = kem_group->kem->ciphertext_length; + expected_second_share_size = kem_group->curve->share_size; + } + if (len_prefixed) { - S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, kem_group->curve->share_size, uint16); + S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, expected_first_share_size, uint16); } - EXPECT_SUCCESS(s2n_stuffer_skip_read(&stuffer, kem_group->curve->share_size)); + EXPECT_SUCCESS(s2n_stuffer_skip_read(&stuffer, expected_first_share_size)); if (len_prefixed) { - S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, kem_group->kem->ciphertext_length, uint16); + S2N_STUFFER_READ_EXPECT_EQUAL(&stuffer, expected_second_share_size, uint16); } - S2N_STUFFER_LENGTH_WRITTEN_EXPECT_EQUAL(&stuffer, kem_group->kem->ciphertext_length); + S2N_STUFFER_LENGTH_WRITTEN_EXPECT_EQUAL(&stuffer, expected_second_share_size); 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..d64b1145347 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,52 @@ int main() bool len_prefix_expected; }; + /* 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]; + + if (kem_group == NULL || !s2n_kem_group_is_available(kem_group)) { + continue; + } + + const struct s2n_kem_preferences singleton_test_pref = { + .kem_count = 0, + .kems = NULL, + .tls13_kem_group_count = 1, + .tls13_kem_groups = &kem_group, + .tls13_pq_hybrid_draft_revision = 5 + }; + + const struct s2n_security_policy singleton_test_policy = { + .minimum_protocol_version = S2N_TLS13, + .cipher_preferences = &cipher_preferences_20190801, + .kem_preferences = &singleton_test_pref, + .signature_preferences = &s2n_signature_preferences_20200207, + .ecc_preferences = &s2n_ecc_preferences_20240603, + }; + + const struct pq_handshake_test_vector test_vec = { + .client_policy = &singleton_test_policy, + .server_policy = &singleton_test_policy, + .expected_kem_group = kem_group, + .expected_curve = NULL, + .hrr_expected = false, + .len_prefix_expected = false, + }; + + EXPECT_SUCCESS(s2n_test_tls13_pq_handshake(test_vec.client_policy, test_vec.server_policy, + test_vec.expected_kem_group, test_vec.expected_curve, test_vec.hrr_expected, test_vec.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 *null_if_no_mlkem = &s2n_x25519_mlkem_768; + const struct s2n_ecc_named_curve *ec_if_no_mlkem = NULL; + if (!s2n_libcrypto_supports_mlkem()) { + null_if_no_mlkem = NULL; + ec_if_no_mlkem = default_curve; + } + /* 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 +709,16 @@ 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 = ec_if_no_mlkem, + .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..11c6ec79408 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->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,28 @@ 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) +{ + POSIX_ENSURE_REF(new_client_params); + const struct s2n_kem_group *kem_group = new_client_params->kem_group; + POSIX_ENSURE_REF(kem_group); + POSIX_ENSURE_REF(kem_group->curve); + + 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 +387,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..df3edec80c7 100644 --- a/tls/extensions/s2n_server_key_share.c +++ b/tls/extensions/s2n_server_key_share.c @@ -34,6 +34,25 @@ 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) +{ + POSIX_ENSURE_REF(conn); + POSIX_ENSURE_REF(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 +62,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 +70,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 +174,32 @@ 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) +{ + POSIX_ENSURE_REF(conn); + POSIX_ENSURE_REF(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; + const struct s2n_kem_group *server_kem_group = server_kem_group_params->kem_group; + POSIX_ENSURE_REF(server_kem_group); + uint16_t expected_ecc_share_size = server_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 +254,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..ca71e4f9cfb 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,15 +166,18 @@ 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_secp256r1_kyber_512_r3, - &s2n_x25519_kyber_512_r3, + &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_x25519_kyber_768_r3, + &s2n_secp256r1_kyber_512_r3, + &s2n_x25519_kyber_512_r3, }; /* Helper safety macro to call the NIST PQ KEM functions. The NIST @@ -422,18 +461,35 @@ int s2n_kem_recv_ciphertext(struct s2n_stuffer *in, struct s2n_kem_params *kem_p return S2N_SUCCESS; } -bool s2n_kem_group_is_available(const struct s2n_kem_group *kem_group) +bool s2n_kem_is_available(const struct s2n_kem *kem) { - if (kem_group == NULL) { + if (kem == NULL || kem->kem_nid == NID_undef) { return false; } + bool available = s2n_libcrypto_supports_evp_kem(); + /* Only newer versions of libcrypto have ML-KEM support. */ + if (kem == &s2n_mlkem_768) { + available &= s2n_libcrypto_supports_mlkem(); + } + + return available; +} + +bool s2n_kem_group_is_available(const struct s2n_kem_group *kem_group) +{ + /* Check for values that might be undefined when compiling for older libcrypto's */ + if (kem_group == NULL || kem_group->curve == NULL || kem_group->kem == NULL) { + return false; + } + + bool available = s2n_kem_is_available(kem_group->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(); } + return available; } diff --git a/tls/s2n_kem.h b/tls/s2n_kem.h index bb5ba5ff94a..52590223fb1 100644 --- a/tls/s2n_kem.h +++ b/tls/s2n_kem.h @@ -41,6 +41,12 @@ typedef uint16_t kem_ciphertext_key_size; #define S2N_NID_KYBER1024 NID_undef #endif +#if defined(S2N_LIBCRYPTO_SUPPORTS_MLKEM) + #define S2N_NID_MLKEM768 NID_MLKEM768 +#else + #define S2N_NID_MLKEM768 NID_undef +#endif + struct s2n_kem { const char *name; int kem_nid; @@ -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; @@ -119,30 +132,14 @@ int s2n_kem_send_public_key(struct s2n_stuffer *out, struct s2n_kem_params *kem_ int s2n_kem_recv_public_key(struct s2n_stuffer *in, struct s2n_kem_params *kem_params); int s2n_kem_send_ciphertext(struct s2n_stuffer *out, struct s2n_kem_params *kem_params); int s2n_kem_recv_ciphertext(struct s2n_stuffer *in, struct s2n_kem_params *kem_params); +bool s2n_kem_is_available(const struct s2n_kem *kem); 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..f87339b4403 100644 --- a/tls/s2n_kem_preferences.c +++ b/tls/s2n_kem_preferences.c @@ -15,6 +15,8 @@ #include "tls/s2n_kem_preferences.h" +#include "tls/s2n_kem.h" + const struct s2n_kem *pq_kems_r3_2021_05[] = { /* Round 3 Algorithms */ &s2n_kyber_512_r3, @@ -78,8 +80,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_KEM_GROUPS_COUNT, + .tls13_kem_groups = ALL_SUPPORTED_KEM_GROUPS, .tls13_pq_hybrid_draft_revision = 5 }; 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