From f8fcf556caab3fb1fb9d9b496d2724345c90a3eb Mon Sep 17 00:00:00 2001 From: beck <> Date: Thu, 4 Dec 2025 21:03:42 +0000 Subject: Add a MLKEM768_X25519 hybrid key share. This implements the currently in use MLKEM768_X25519 hybrid key share as outlined in https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/ This commit does not yet wire this up to anything, that is done in follow on changes. ok tb@ jsing@ kenjiro@ --- src/lib/libssl/tls_key_share.c | 327 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 323 insertions(+), 4 deletions(-) (limited to 'src/lib/libssl/tls_key_share.c') diff --git a/src/lib/libssl/tls_key_share.c b/src/lib/libssl/tls_key_share.c index cf7b1da262..3f4c44f558 100644 --- a/src/lib/libssl/tls_key_share.c +++ b/src/lib/libssl/tls_key_share.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tls_key_share.c,v 1.8 2022/11/26 16:08:56 tb Exp $ */ +/* $OpenBSD: tls_key_share.c,v 1.9 2025/12/04 21:03:42 beck Exp $ */ /* * Copyright (c) 2020, 2021 Joel Sing * @@ -21,6 +21,7 @@ #include #include #include +#include #include "bytestring.h" #include "ssl_local.h" @@ -40,6 +41,19 @@ struct tls_key_share { uint8_t *x25519_public; uint8_t *x25519_private; uint8_t *x25519_peer_public; + + uint8_t *mlkem_public; + size_t mlkem_public_len; + MLKEM_private_key *mlkem_private; + MLKEM_public_key *mlkem_peer_public; + + /* The ciphertext from MLKEM_encap. */ + uint8_t *mlkem_encap; + size_t mlkem_encap_len; + + /* The shared secret from an ML-KEM encapsulation. */ + uint8_t *mlkem_shared_secret; + size_t mlkem_shared_secret_len; }; static struct tls_key_share * @@ -96,6 +110,12 @@ tls_key_share_free(struct tls_key_share *ks) freezero(ks->x25519_private, X25519_KEY_LENGTH); freezero(ks->x25519_peer_public, X25519_KEY_LENGTH); + freezero(ks->mlkem_public, ks->mlkem_public_len); + MLKEM_private_key_free(ks->mlkem_private); + MLKEM_public_key_free(ks->mlkem_peer_public); + freezero(ks->mlkem_encap, ks->mlkem_encap_len); + freezero(ks->mlkem_shared_secret, ks->mlkem_shared_secret_len); + freezero(ks, sizeof(*ks)); } @@ -230,7 +250,73 @@ tls_key_share_generate_x25519(struct tls_key_share *ks) return ret; } -int +static int +tls_key_share_generate_mlkem(struct tls_key_share *ks, int rank) +{ + MLKEM_private_key *private = NULL; + uint8_t *public = NULL; + size_t p_len = 0; + int ret = 0; + + if (ks->mlkem_public != NULL || ks->mlkem_private != NULL) + goto err; + + if ((private = MLKEM_private_key_new(rank)) == NULL) + goto err; + + if (!MLKEM_generate_key(private, &public, &p_len, NULL, NULL)) + goto err; + + ks->mlkem_public = public; + ks->mlkem_public_len = p_len; + ks->mlkem_private = private; + public = NULL; + private = NULL; + + ret = 1; + + err: + freezero(public, p_len); + MLKEM_private_key_free(private); + + return ret; +} + +static int +tls_key_share_client_generate_mlkem768x25519(struct tls_key_share *ks) +{ + if (!tls_key_share_generate_mlkem(ks, RANK768)) + return 0; + + if (!tls_key_share_generate_x25519(ks)) + return 0; + + return 1; +} + +static int +tls_key_share_server_generate_mlkem768x25519(struct tls_key_share *ks) +{ + if (ks->mlkem_private != NULL) + return 0; + + /* The server side needs the client's parsed share */ + + if (ks->x25519_peer_public == NULL) + return 0; + + if (ks->mlkem_peer_public == NULL) + return 0; + + if (!tls_key_share_generate_x25519(ks)) + return 0; + + return MLKEM_encap(ks->mlkem_peer_public, &ks->mlkem_encap, + &ks->mlkem_encap_len, &ks->mlkem_shared_secret, + &ks->mlkem_shared_secret_len); +} + +static int tls_key_share_generate(struct tls_key_share *ks) { if (ks->nid == NID_dhKeyAgreement) @@ -242,6 +328,24 @@ tls_key_share_generate(struct tls_key_share *ks) return tls_key_share_generate_ecdhe_ecp(ks); } +int +tls_key_share_client_generate(struct tls_key_share *ks) +{ + if (ks->nid == NID_X25519MLKEM768) + return tls_key_share_client_generate_mlkem768x25519(ks); + + return tls_key_share_generate(ks); +} + +int +tls_key_share_server_generate(struct tls_key_share *ks) +{ + if (ks->nid == NID_X25519MLKEM768) + return tls_key_share_server_generate_mlkem768x25519(ks); + + return tls_key_share_generate(ks); +} + static int tls_key_share_params_dhe(struct tls_key_share *ks, CBB *cbb) { @@ -287,6 +391,47 @@ tls_key_share_public_x25519(struct tls_key_share *ks, CBB *cbb) return CBB_add_bytes(cbb, ks->x25519_public, X25519_KEY_LENGTH); } +static int +tls_key_share_public_mlkem768x25519(struct tls_key_share *ks, CBB *cbb) +{ + uint8_t *mlkem_part; + size_t mlkem_part_len; + + if (ks->x25519_public == NULL) + return 0; + + /* + * https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/ + * Section 3.1.2: + * The server's key exchange value is the concatenation of an + * ML-KEM ciphertext returned from encapsulation to the client's + * encapsulation key, and the server's ephemeral X25519 share. + */ + mlkem_part = ks->mlkem_encap; + mlkem_part_len = ks->mlkem_encap_len; + + /* + * https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/ + * Section 3.1.1: + * The client's key_exchange value is the concatenation of the + * client's ML-KEM-768 encapsulation key and the client's X25519 + * ephemeral share. + */ + if (mlkem_part == NULL) { + mlkem_part = ks->mlkem_public; + mlkem_part_len = ks->mlkem_public_len; + } + + if (mlkem_part == NULL) + return 0; + + if (!CBB_add_bytes(cbb, mlkem_part, mlkem_part_len)) + return 0; + + /* Both the client and server send their x25519 public keys. */ + return CBB_add_bytes(cbb, ks->x25519_public, X25519_KEY_LENGTH); +} + int tls_key_share_public(struct tls_key_share *ks, CBB *cbb) { @@ -296,6 +441,9 @@ tls_key_share_public(struct tls_key_share *ks, CBB *cbb) if (ks->nid == NID_X25519) return tls_key_share_public_x25519(ks, cbb); + if (ks->nid == NID_X25519MLKEM768) + return tls_key_share_public_mlkem768x25519(ks, cbb); + return tls_key_share_public_ecdhe_ecp(ks, cbb); } @@ -325,7 +473,7 @@ tls_key_share_peer_params(struct tls_key_share *ks, CBS *cbs, return 0; return tls_key_share_peer_params_dhe(ks, cbs, decode_error, - invalid_params); + invalid_params); } static int @@ -383,7 +531,91 @@ tls_key_share_peer_public_x25519(struct tls_key_share *ks, CBS *cbs, return CBS_stow(cbs, &ks->x25519_peer_public, &out_len); } -int +static int +tls_key_share_client_peer_public_mlkem768x25519(struct tls_key_share *ks, + CBS *cbs, int *decode_error) +{ + CBS x25519_cbs, mlkem_ciphertext_cbs; + size_t out_len; + + if (ks->mlkem_shared_secret != NULL) + return 0; + + if (ks->mlkem_private == NULL) + return 0; + + if (!CBS_get_bytes(cbs, &mlkem_ciphertext_cbs, + MLKEM_private_key_ciphertext_length(ks->mlkem_private))) + return 0; + + if (!CBS_get_bytes(cbs, &x25519_cbs, X25519_KEY_LENGTH)) + return 0; + + if (CBS_len(cbs) != 0) + return 0; + + if (!CBS_stow(&x25519_cbs, &ks->x25519_peer_public, &out_len)) + return 0; + + if (!CBS_stow(&mlkem_ciphertext_cbs, &ks->mlkem_encap, &ks->mlkem_encap_len)) + return 0; + + return 1; +} + +static int +tls_key_share_server_peer_public_mlkem768x25519(struct tls_key_share *ks, + CBS *cbs, int *decode_error) +{ + CBS x25519_cbs, mlkem768_cbs; + size_t out_len; + + *decode_error = 0; + + /* The server should not have an mlkem private key */ + if (ks->mlkem_private != NULL) + return 0; + + if (ks->mlkem_shared_secret != NULL) + return 0; + + if (ks->mlkem_peer_public != NULL) + return 0; + + if (ks->x25519_peer_public != NULL) + return 0; + + /* Nein, ist nur normal (1024 ist gigantisch) */ + if ((ks->mlkem_peer_public = MLKEM_public_key_new(RANK768)) == NULL) + goto err; + + if (!CBS_get_bytes(cbs, &mlkem768_cbs, + MLKEM_public_key_encoded_length(ks->mlkem_peer_public))) + goto err; + + if (!CBS_get_bytes(cbs, &x25519_cbs, X25519_KEY_LENGTH)) + goto err; + + if (CBS_len(cbs) != 0) + goto err; + + if (!CBS_stow(&x25519_cbs, &ks->x25519_peer_public, &out_len)) + goto err; + + /* Poetische */ + if (!MLKEM_parse_public_key(ks->mlkem_peer_public, + CBS_data(&mlkem768_cbs), CBS_len(&mlkem768_cbs))) + goto err; + + return 1; + + err: + *decode_error = 1; + + return 0; +} + +static int tls_key_share_peer_public(struct tls_key_share *ks, CBS *cbs, int *decode_error, int *invalid_key) { @@ -402,6 +634,30 @@ tls_key_share_peer_public(struct tls_key_share *ks, CBS *cbs, int *decode_error, return tls_key_share_peer_public_ecdhe_ecp(ks, cbs); } +/* Called from client to process a server peer */ +int +tls_key_share_client_peer_public(struct tls_key_share *ks, CBS *cbs, + int *decode_error, int *invalid_key) +{ + if (ks->nid == NID_X25519MLKEM768) + return tls_key_share_client_peer_public_mlkem768x25519(ks, cbs, + decode_error); + + return tls_key_share_peer_public(ks, cbs, decode_error, invalid_key); +} + +/* Called from server to process a client peer */ +int +tls_key_share_server_peer_public(struct tls_key_share *ks, CBS *cbs, + int *decode_error, int *invalid_key) +{ + if (ks->nid == NID_X25519MLKEM768) + return tls_key_share_server_peer_public_mlkem768x25519(ks, cbs, + decode_error); + + return tls_key_share_peer_public(ks, cbs, decode_error, invalid_key); +} + static int tls_key_share_derive_dhe(struct tls_key_share *ks, uint8_t **shared_key, size_t *shared_key_len) @@ -451,6 +707,65 @@ tls_key_share_derive_x25519(struct tls_key_share *ks, return ret; } +/* + * https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/ + * Section 3.1.3: + * For X25519MLKEM768, the shared secret is the concatenation of the ML-KEM + * shared secret and the X25519 shared secret. + */ +static int +tls_key_share_derive_mlkem768x25519(struct tls_key_share *ks, + uint8_t **out_shared_key, size_t *out_shared_key_len) +{ + uint8_t *x25519_shared_key; + CBB cbb; + + memset(&cbb, 0, sizeof(cbb)); + + if (ks->x25519_private == NULL) + goto err; + + if (ks->x25519_peer_public == NULL) + goto err; + + if (ks->mlkem_shared_secret == NULL) { + if (ks->mlkem_private == NULL) + goto err; + + if (ks->mlkem_encap == NULL) + goto err; + + if (!MLKEM_decap(ks->mlkem_private, ks->mlkem_encap, + MLKEM_private_key_ciphertext_length(ks->mlkem_private), + &ks->mlkem_shared_secret, &ks->mlkem_shared_secret_len)) + goto err; + } + + if (!CBB_init(&cbb, ks->mlkem_shared_secret_len + X25519_KEY_LENGTH)) + goto err; + + if (!CBB_add_bytes(&cbb, ks->mlkem_shared_secret, + ks->mlkem_shared_secret_len)) + goto err; + + if (!CBB_add_space(&cbb, &x25519_shared_key, X25519_KEY_LENGTH)) + goto err; + + if (!X25519(x25519_shared_key, ks->x25519_private, + ks->x25519_peer_public)) + goto err; + + if (!CBB_finish(&cbb, out_shared_key, out_shared_key_len)) + goto err; + + return 1; + + err: + CBB_cleanup(&cbb); + + return 0; +} + int tls_key_share_derive(struct tls_key_share *ks, uint8_t **shared_key, size_t *shared_key_len) @@ -468,6 +783,10 @@ tls_key_share_derive(struct tls_key_share *ks, uint8_t **shared_key, return tls_key_share_derive_x25519(ks, shared_key, shared_key_len); + if (ks->nid == NID_X25519MLKEM768) + return tls_key_share_derive_mlkem768x25519(ks, shared_key, + shared_key_len); + return tls_key_share_derive_ecdhe_ecp(ks, shared_key, shared_key_len); } -- cgit v1.2.3-55-g6feb