Skip to content

Commit bfa016d

Browse files
committed
pq handshake
1 parent c46c75e commit bfa016d

File tree

7 files changed

+296
-38
lines changed

7 files changed

+296
-38
lines changed

crypto/s2n_ecc_evp.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,11 @@ static int s2n_ecc_evp_compute_shared_secret(EVP_PKEY *own_key, EVP_PKEY *peer_p
253253
int s2n_ecc_evp_generate_ephemeral_key(struct s2n_ecc_evp_params *ecc_evp_params)
254254
{
255255
POSIX_ENSURE_REF(ecc_evp_params->negotiated_curve);
256+
257+
/* Skip ECDHE key generation for MLKEM placeholder curve */
258+
if (ecc_evp_params->negotiated_curve == &s2n_ecc_curve_mlkem_placeholder) {
259+
return S2N_SUCCESS;
260+
}
256261
S2N_ERROR_IF(ecc_evp_params->evp_pkey != NULL, S2N_ERR_ECDHE_GEN_KEY);
257262
S2N_ERROR_IF(s2n_ecc_evp_generate_own_key(ecc_evp_params->negotiated_curve, &ecc_evp_params->evp_pkey) != 0,
258263
S2N_ERR_ECDHE_GEN_KEY);
@@ -423,6 +428,12 @@ int s2n_ecc_evp_write_params_point(struct s2n_ecc_evp_params *ecc_evp_params, st
423428
{
424429
POSIX_ENSURE_REF(ecc_evp_params);
425430
POSIX_ENSURE_REF(ecc_evp_params->negotiated_curve);
431+
432+
/* Skip EC point write for MLKEM placeholder curve */
433+
if (ecc_evp_params->negotiated_curve == &s2n_ecc_curve_mlkem_placeholder) {
434+
return S2N_SUCCESS;
435+
}
436+
426437
POSIX_ENSURE_REF(ecc_evp_params->evp_pkey);
427438
POSIX_ENSURE_REF(out);
428439

tests/unit/s2n_tls13_pq_handshake_test.c

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,11 @@ int s2n_test_tls13_pq_handshake(const struct s2n_security_policy *client_sec_pol
112112
const struct s2n_security_policy *server_sec_policy, const struct s2n_kem_group *expected_kem_group,
113113
const struct s2n_ecc_named_curve *expected_curve, bool hrr_expected, bool len_prefix_expected)
114114
{
115+
/* Skip XOR check for pure MLKEM placeholder, otherwise enforce XOR */
115116
/* XOR check: can expect to negotiate either a KEM group, or a classic EC curve, but not both/neither */
116-
POSIX_ENSURE((expected_kem_group == NULL) != (expected_curve == NULL), S2N_ERR_SAFETY);
117+
if (!(expected_kem_group && expected_kem_group->curve == &s2n_ecc_curve_mlkem_placeholder)) {
118+
POSIX_ENSURE((expected_kem_group != NULL) != (expected_curve != NULL), S2N_ERR_SAFETY);
119+
}
117120

118121
/* Set up connections */
119122
struct s2n_connection *client_conn = NULL, *server_conn = NULL;
@@ -458,18 +461,6 @@ int main()
458461
.tls13_pq_hybrid_draft_revision = 5
459462
};
460463

461-
// const struct s2n_kem_group *mlkem1024_pure_test_groups[] = {
462-
// &s2n_pure_mlkem_1024,
463-
// };
464-
465-
// const struct s2n_kem_preferences mlkem1024_pure_test_prefs = {
466-
// .kem_count = 0,
467-
// .kems = NULL,
468-
// .tls13_kem_group_count = s2n_array_len(mlkem1024_pure_test_groups),
469-
// .tls13_kem_groups = mlkem1024_pure_test_groups,
470-
// .tls13_pq_hybrid_draft_revision = 5
471-
// };
472-
473464
const struct s2n_security_policy mlkem1024_test_policy = {
474465
.minimum_protocol_version = S2N_TLS13,
475466
.cipher_preferences = &cipher_preferences_20190801,
@@ -478,13 +469,25 @@ int main()
478469
.ecc_preferences = &s2n_ecc_preferences_20240603,
479470
};
480471

481-
// const struct s2n_security_policy mlkem1024_pure_test_policy = {
482-
// .minimum_protocol_version = S2N_TLS13,
483-
// .cipher_preferences = &cipher_preferences_20190801,
484-
// .kem_preferences = &mlkem1024_pure_test_prefs,
485-
// .signature_preferences = &s2n_signature_preferences_20200207,
486-
// .ecc_preferences = &s2n_ecc_preferences_null,
487-
// };
472+
const struct s2n_kem_group *mlkem1024_pure_test_groups[] = {
473+
&s2n_pure_mlkem_1024,
474+
};
475+
476+
const struct s2n_kem_preferences mlkem1024_pure_test_prefs = {
477+
.kem_count = 0,
478+
.kems = NULL,
479+
.tls13_kem_group_count = s2n_array_len(mlkem1024_pure_test_groups),
480+
.tls13_kem_groups = mlkem1024_pure_test_groups,
481+
.tls13_pq_hybrid_draft_revision = 5
482+
};
483+
484+
const struct s2n_security_policy mlkem1024_pure_test_policy = {
485+
.minimum_protocol_version = S2N_TLS13,
486+
.cipher_preferences = &cipher_preferences_20190801,
487+
.kem_preferences = &mlkem1024_pure_test_prefs,
488+
.signature_preferences = &s2n_signature_preferences_20200207,
489+
.ecc_preferences = &s2n_ecc_preferences_pure_mlkem,
490+
};
488491

489492
const struct s2n_security_policy ecc_retry_policy = {
490493
.minimum_protocol_version = security_policy_pq_tls_1_0_2020_12.minimum_protocol_version,
@@ -550,13 +553,16 @@ int main()
550553
* unavailable, we must downgrade the assertions to Kyber or EC. */
551554
const struct s2n_kem_group *null_if_no_mlkem_768 = &s2n_x25519_mlkem_768;
552555
const struct s2n_kem_group *null_if_no_mlkem_1024 = &s2n_secp384r1_mlkem_1024;
553-
// const struct s2n_kem_group *null_if_no_pure_mlkem_1024 = &s2n_pure_mlkem_1024;
556+
const struct s2n_kem_group *null_if_no_pure_mlkem_1024 = &s2n_pure_mlkem_1024;
554557
const struct s2n_ecc_named_curve *ec_if_no_mlkem = NULL;
558+
const struct s2n_ecc_named_curve *ec_if_no_pure_mlkem = &s2n_ecc_curve_mlkem_placeholder;
559+
555560
if (!s2n_libcrypto_supports_mlkem()) {
556561
null_if_no_mlkem_768 = NULL;
557562
null_if_no_mlkem_1024 = NULL;
558-
// null_if_no_pure_mlkem_1024 = NULL;
563+
null_if_no_pure_mlkem_1024 = NULL;
559564
ec_if_no_mlkem = default_curve;
565+
ec_if_no_pure_mlkem = default_curve;
560566
}
561567

562568
/* Test vectors that expect to negotiate PQ assume that PQ is enabled in s2n.
@@ -793,15 +799,15 @@ int main()
793799
.len_prefix_expected = false,
794800
},
795801

796-
// /* Confirm that pure MLKEM1024 is negotiable */
797-
// {
798-
// .client_policy = &mlkem1024_pure_test_policy,
799-
// .server_policy = &mlkem1024_pure_test_policy,
800-
// .expected_kem_group = null_if_no_pure_mlkem_1024,
801-
// .expected_curve = NULL,
802-
// .hrr_expected = false,
803-
// .len_prefix_expected = false,
804-
// },
802+
/* Confirm that pure MLKEM1024 is negotiable */
803+
{
804+
.client_policy = &mlkem1024_pure_test_policy,
805+
.server_policy = &mlkem1024_pure_test_policy,
806+
.expected_kem_group = null_if_no_pure_mlkem_1024,
807+
.expected_curve = NULL,
808+
.hrr_expected = false,
809+
.len_prefix_expected = false,
810+
},
805811
};
806812

807813
for (size_t i = 0; i < s2n_array_len(test_vectors); i++) {

tls/extensions/s2n_client_key_share.c

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ static int s2n_generate_default_ecc_key_share(struct s2n_connection *conn, struc
6767
POSIX_GUARD(s2n_connection_get_ecc_preferences(conn, &ecc_pref));
6868
POSIX_ENSURE_REF(ecc_pref);
6969

70+
/* Skip if the highest-priority curve is the mlkem_placeholder */
71+
if (ecc_pref->count > 0 && ecc_pref->ecc_curves[0] == &s2n_ecc_curve_mlkem_placeholder) {
72+
printf(" DEBUG: Skipping standalone ECC key share for MLKEM placeholder\n");
73+
return S2N_SUCCESS;
74+
}
75+
7076
/* We only ever send a single EC key share: either the share requested by the server
7177
* during a retry, or the most preferred share according to local preferences.
7278
*/
@@ -135,6 +141,32 @@ static int s2n_generate_pq_hybrid_key_share(struct s2n_stuffer *out, struct s2n_
135141
return S2N_SUCCESS;
136142
}
137143

144+
static int s2n_generate_pure_pq_key_share(struct s2n_stuffer *out,
145+
struct s2n_kem_group_params *kem_group_params)
146+
{
147+
POSIX_ENSURE_REF(out);
148+
POSIX_ENSURE_REF(kem_group_params);
149+
POSIX_ENSURE_REF(kem_group_params->kem_group);
150+
POSIX_ENSURE_REF(kem_group_params->kem_group->kem);
151+
152+
/* Write group ID */
153+
POSIX_GUARD(s2n_stuffer_write_uint16(out, kem_group_params->kem_group->iana_id));
154+
155+
/* Reserve space for total share size */
156+
struct s2n_stuffer_reservation total_share_size = { 0 };
157+
POSIX_GUARD(s2n_stuffer_reserve_uint16(out, &total_share_size));
158+
159+
/* Write KEM public key */
160+
struct s2n_kem_params *kem_params = &kem_group_params->kem_params;
161+
kem_params->kem = kem_group_params->kem_group->kem;
162+
POSIX_GUARD(s2n_kem_send_public_key(out, kem_params));
163+
164+
/* Fill in the reserved size field */
165+
POSIX_GUARD(s2n_stuffer_write_vector_size(&total_share_size));
166+
167+
return S2N_SUCCESS;
168+
}
169+
138170
static int s2n_generate_default_pq_hybrid_key_share(struct s2n_connection *conn, struct s2n_stuffer *out)
139171
{
140172
POSIX_ENSURE_REF(conn);
@@ -187,7 +219,14 @@ static int s2n_generate_default_pq_hybrid_key_share(struct s2n_connection *conn,
187219
client_params->kem_params.len_prefixed = s2n_tls13_client_must_use_hybrid_kem_length_prefix(kem_pref);
188220
}
189221

190-
POSIX_GUARD(s2n_generate_pq_hybrid_key_share(out, client_params));
222+
/* Special case: Pure MLKEM (no ECC portion) */
223+
if (client_params->kem_group == &s2n_pure_mlkem_1024 ||
224+
client_params->kem_group->curve == &s2n_ecc_curve_mlkem_placeholder) {
225+
printf(" DEBUG: Skipping ECC portion for Pure MLKEM\n");
226+
POSIX_GUARD(s2n_generate_pure_pq_key_share(out, client_params));
227+
} else {
228+
POSIX_GUARD(s2n_generate_pq_hybrid_key_share(out, client_params));
229+
}
191230

192231
return S2N_SUCCESS;
193232
}
@@ -223,6 +262,12 @@ static int s2n_client_key_share_parse_ecc(struct s2n_stuffer *key_share, const s
223262
POSIX_ENSURE_REF(curve);
224263
POSIX_ENSURE_REF(ecc_params);
225264

265+
if (curve == &s2n_ecc_curve_mlkem_placeholder) {
266+
printf("DEBUG: Skipping KeyShare parse for MLKEM placeholder\n");
267+
ecc_params->negotiated_curve = curve;
268+
return S2N_SUCCESS;
269+
}
270+
226271
struct s2n_blob point_blob = { 0 };
227272
POSIX_GUARD(s2n_ecc_evp_read_params_point(key_share, curve->share_size, &point_blob));
228273

@@ -236,6 +281,56 @@ static int s2n_client_key_share_parse_ecc(struct s2n_stuffer *key_share, const s
236281
return S2N_SUCCESS;
237282
}
238283

284+
static int s2n_client_key_share_recv_pure_kem(
285+
struct s2n_connection *conn,
286+
struct s2n_stuffer *key_share,
287+
uint16_t kem_group_iana_id)
288+
{
289+
POSIX_ENSURE_REF(conn);
290+
POSIX_ENSURE_REF(key_share);
291+
292+
if (kem_group_iana_id != s2n_pure_mlkem_1024.iana_id) {
293+
return S2N_SUCCESS; /* not a pure KEM share */
294+
}
295+
296+
printf(" DEBUG: Entering pure MLKEM recv: named_group=%u, expected_iana=%u\n",
297+
kem_group_iana_id, s2n_pure_mlkem_1024.iana_id);
298+
299+
struct s2n_kem_group_params *client_params = &conn->kex_params.client_kem_group_params;
300+
client_params->kem_group = &s2n_pure_mlkem_1024;
301+
client_params->ecc_params.negotiated_curve = &s2n_ecc_curve_mlkem_placeholder;
302+
client_params->kem_params.kem = s2n_pure_mlkem_1024.kem;
303+
304+
uint16_t actual_share_size = key_share->blob.size;
305+
uint16_t unprefixed_size = client_params->kem_params.kem->public_key_length;
306+
uint16_t prefixed_size = S2N_SIZE_OF_KEY_SHARE_SIZE + unprefixed_size;
307+
308+
printf(" DEBUG: actual_share_size=%u, unprefixed_size=%u, prefixed_size=%u\n",
309+
actual_share_size, unprefixed_size, prefixed_size);
310+
311+
if (actual_share_size != unprefixed_size && actual_share_size != prefixed_size) {
312+
printf(" DEBUG: Share size mismatch — ignoring key share\n");
313+
return S2N_SUCCESS;
314+
}
315+
316+
client_params->kem_params.len_prefixed = (actual_share_size == prefixed_size);
317+
printf(" DEBUG: len_prefixed=%d\n", client_params->kem_params.len_prefixed);
318+
319+
POSIX_GUARD(s2n_kem_recv_public_key(key_share, &client_params->kem_params));
320+
printf(" DEBUG: KEM public key parsed, size=%u\n",
321+
client_params->kem_params.public_key.size);
322+
323+
printf(" DEBUG: key_share.read_cursor=%u, key_share.write_cursor=%u\n",
324+
key_share->read_cursor, key_share->write_cursor);
325+
326+
printf(" DEBUG: remaining bytes in key_share=%u\n",
327+
s2n_stuffer_data_available(key_share));
328+
POSIX_ENSURE(s2n_stuffer_data_available(key_share) == 0, S2N_ERR_BAD_MESSAGE);
329+
330+
printf(" DEBUG: Finished pure MLKEM recv successfully\n");
331+
return S2N_SUCCESS;
332+
}
333+
239334
static int s2n_client_key_share_recv_ecc(struct s2n_connection *conn, struct s2n_stuffer *key_share, uint16_t curve_iana_id)
240335
{
241336
POSIX_ENSURE_REF(conn);
@@ -286,6 +381,11 @@ static int s2n_client_key_share_recv_ecc(struct s2n_connection *conn, struct s2n
286381

287382
DEFER_CLEANUP(struct s2n_ecc_evp_params new_client_params = { 0 }, s2n_ecc_evp_params_free);
288383

384+
if (curve == &s2n_ecc_curve_mlkem_placeholder) {
385+
printf("DEBUG: Skipping ECC recv for MLKEM placeholder\n");
386+
return S2N_SUCCESS;
387+
}
388+
289389
POSIX_GUARD(s2n_client_key_share_parse_ecc(key_share, curve, &new_client_params));
290390
/* negotiated_curve will be NULL if the key share was not parsed successfully */
291391
if (!new_client_params.negotiated_curve) {
@@ -312,6 +412,11 @@ static int s2n_client_key_share_recv_hybrid_partial_ecc(struct s2n_stuffer *key_
312412
POSIX_ENSURE(ec_share_size == kem_group->curve->share_size, S2N_ERR_SIZE_MISMATCH);
313413
}
314414

415+
if (kem_group->curve == &s2n_ecc_curve_mlkem_placeholder) {
416+
printf("DEBUG: Skipping Hybrid ECC recv for MLKEM placeholder\n");
417+
return S2N_SUCCESS;
418+
}
419+
315420
POSIX_GUARD(s2n_client_key_share_parse_ecc(key_share, kem_group->curve, &new_client_params->ecc_params));
316421

317422
/* If we were unable to parse the EC portion of the share, negotiated_curve
@@ -453,8 +558,26 @@ static int s2n_client_key_share_recv(struct s2n_connection *conn, struct s2n_stu
453558
POSIX_GUARD(s2n_stuffer_skip_write(&key_share, share_size));
454559
keyshare_count++;
455560

456-
/* Try to parse the share as ECC, then as PQ/hybrid; will ignore
457-
* shares for unrecognized groups. */
561+
if (named_group == s2n_pure_mlkem_1024.iana_id) {
562+
printf("DEBUG[key_share_recv]: Detected pure MLKEM group, calling pure_kem recv\n");
563+
POSIX_GUARD(s2n_client_key_share_recv_pure_kem(conn, &key_share, named_group));
564+
565+
printf("DEBUG[key_share_recv]: After pure_kem -> extension_read=%u extension_write=%u extension_available=%u\n",
566+
extension->read_cursor, extension->write_cursor, s2n_stuffer_data_available(extension));
567+
568+
if (s2n_stuffer_data_available(extension) > 0) {
569+
printf("DEBUG[key_share_recv]: Leftover bytes in extension: ");
570+
for (uint32_t i = 0; i < s2n_stuffer_data_available(extension); i++) {
571+
uint8_t b = extension->blob.data[extension->read_cursor + i];
572+
printf("%02x ", b);
573+
}
574+
printf("\n");
575+
}
576+
577+
printf("DEBUG[key_share_recv]: pure_kem recv success\n");
578+
continue; /* skip ECC/hybrid parsing */
579+
}
580+
458581
POSIX_GUARD(s2n_client_key_share_recv_ecc(conn, &key_share, named_group));
459582
POSIX_GUARD(s2n_client_key_share_recv_pq_hybrid(conn, &key_share, named_group));
460583
}

tls/extensions/s2n_client_supported_groups.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ static int s2n_client_supported_groups_send(struct s2n_connection *conn, struct
7373

7474
/* Then send curve list */
7575
for (size_t i = 0; i < ecc_pref->count; i++) {
76-
POSIX_GUARD(s2n_stuffer_write_uint16(out, ecc_pref->ecc_curves[i]->iana_id));
76+
POSIX_GUARD(s2n_stuffer_write_uint16(out, ecc_pref->ecc_curves[i]->iana_id));
7777
}
7878

7979
POSIX_GUARD(s2n_stuffer_write_vector_size(&group_list_len));

0 commit comments

Comments
 (0)