implement X25519MLKEM768
diff --git a/include/picotls.h b/include/picotls.h
index f3e783e..727ac60 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -160,6 +160,8 @@
#define PTLS_GROUP_NAME_X25519 "x25519"
#define PTLS_GROUP_X448 30
#define PTLS_GROUP_NAME_X448 "x448"
+#define PTLS_GROUP_X25519MLKEM768 4588
+#define PTLS_GROUP_NAME_X25519MLKEM768 "X25519MLKEM768"
/* signature algorithms */
#define PTLS_SIGNATURE_RSA_PKCS1_SHA1 0x0201
diff --git a/include/picotls/openssl.h b/include/picotls/openssl.h
index 205500a..01af6a8 100644
--- a/include/picotls/openssl.h
+++ b/include/picotls/openssl.h
@@ -69,6 +69,12 @@
#define PTLS_OPENSSL_HAVE_X25519 0
#define PTLS_OPENSSL_HAS_X25519 0 /* deprecated; use HAVE_ */
#endif
+#ifdef OPENSSL_IS_BORINGSSL
+#define PTLS_OPENSSL_HAVE_X25519MLKEM768 1
+extern ptls_key_exchange_algorithm_t ptls_openssl_x25519mlkem768;
+#else
+#define PTLS_OPENSSL_HAVE_X25519MLKEM768 0
+#endif
/* when boringssl is used, existence of libdecrepit is assumed */
#if !defined(OPENSSL_NO_BF) || defined(OPENSSL_IS_BORINGSSL)
diff --git a/lib/openssl.c b/lib/openssl.c
index 2833c32..fb9c3f9 100644
--- a/lib/openssl.c
+++ b/lib/openssl.c
@@ -52,6 +52,9 @@
#ifdef OPENSSL_IS_BORINGSSL
#include "./chacha20poly1305.h"
#endif
+#if PTLS_OPENSSL_HAVE_X25519MLKEM768
+#include <openssl/mlkem.h>
+#endif
#ifdef PTLS_HAVE_AEGIS
#include "./libaegis.h"
#endif
@@ -706,6 +709,135 @@
#endif
+#if PTLS_OPENSSL_HAVE_X25519MLKEM768
+
+struct st_x25519mlkem768_context_t {
+ ptls_key_exchange_context_t super;
+ uint8_t pubkey[MLKEM768_PUBLIC_KEY_BYTES + X25519_PUBLIC_VALUE_LEN];
+ struct {
+ uint8_t x25519[X25519_PRIVATE_KEY_LEN];
+ struct MLKEM768_private_key mlkem;
+ } privkey;
+};
+
+static int x25519mlkem768_on_exchange(ptls_key_exchange_context_t **_ctx, int release, ptls_iovec_t *secret,
+ ptls_iovec_t ciphertext)
+{
+ struct st_x25519mlkem768_context_t *ctx = (void *)*_ctx;
+ int ret;
+
+ if (secret == NULL) {
+ ret = 0;
+ goto Exit;
+ }
+
+ *secret = ptls_iovec_init(NULL, 0);
+
+ /* validate length */
+ if (ciphertext.len != MLKEM768_CIPHERTEXT_BYTES + X25519_PUBLIC_VALUE_LEN) {
+ ret = PTLS_ALERT_DECRYPT_ERROR;
+ goto Exit;
+ }
+
+ /* appsocate memory */
+ secret->len = MLKEM_SHARED_SECRET_BYTES + X25519_SHARED_KEY_LEN;
+ if ((secret->base = malloc(secret->len)) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+
+ /* run key exchange */
+ if (!MLKEM768_decap(secret->base, ciphertext.base, MLKEM768_CIPHERTEXT_BYTES, &ctx->privkey.mlkem) ||
+ !X25519(secret->base + MLKEM_SHARED_SECRET_BYTES, ctx->privkey.x25519, ciphertext.base + MLKEM768_CIPHERTEXT_BYTES)) {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ ret = 0;
+
+Exit:
+ if (secret != NULL && ret != 0) {
+ free(secret->base);
+ *secret = ptls_iovec_init(NULL, 0);
+ }
+ if (release) {
+ ptls_clear_memory(&ctx->privkey, sizeof(ctx->privkey));
+ free(ctx);
+ *_ctx = NULL;
+ }
+ return ret;
+}
+
+static int x25519mlkem768_create(ptls_key_exchange_algorithm_t *algo, ptls_key_exchange_context_t **_ctx)
+{
+ struct st_x25519mlkem768_context_t *ctx = NULL;
+
+ if ((ctx = malloc(sizeof(*ctx))) == NULL)
+ return PTLS_ERROR_NO_MEMORY;
+
+ ctx->super = (ptls_key_exchange_context_t){algo, ptls_iovec_init(ctx->pubkey, sizeof(ctx->pubkey)), x25519mlkem768_on_exchange};
+ MLKEM768_generate_key(ctx->pubkey, NULL, &ctx->privkey.mlkem);
+ X25519_keypair(ctx->pubkey + MLKEM768_PUBLIC_KEY_BYTES, ctx->privkey.x25519);
+
+ *_ctx = &ctx->super;
+ return 0;
+}
+
+static int x25519mlkem768_exchange(ptls_key_exchange_algorithm_t *algo, ptls_iovec_t *ciphertext, ptls_iovec_t *secret,
+ ptls_iovec_t peerkey)
+{
+ struct {
+ CBS cbs;
+ struct MLKEM768_public_key key;
+ } mlkem_peer;
+ uint8_t x25519_privkey[X25519_PRIVATE_KEY_LEN];
+ int ret;
+
+ *ciphertext = ptls_iovec_init(NULL, 0);
+ *secret = ptls_iovec_init(NULL, 0);
+
+ /* validate input length */
+ if (peerkey.len != MLKEM768_PUBLIC_KEY_BYTES + X25519_PUBLIC_VALUE_LEN) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+
+ /* allocate memory */
+ ciphertext->len = MLKEM768_CIPHERTEXT_BYTES + X25519_PUBLIC_VALUE_LEN;
+ if ((ciphertext->base = malloc(ciphertext->len)) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+ secret->len = MLKEM_SHARED_SECRET_BYTES + X25519_SHARED_KEY_LEN;
+ if ((secret->base = malloc(secret->len)) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+
+ /* run key exchange */
+ CBS_init(&mlkem_peer.cbs, peerkey.base, MLKEM768_PUBLIC_KEY_BYTES);
+ X25519_keypair(ciphertext->base + MLKEM768_CIPHERTEXT_BYTES, x25519_privkey);
+ if (!MLKEM768_parse_public_key(&mlkem_peer.key, &mlkem_peer.cbs) ||
+ !X25519(secret->base + MLKEM_SHARED_SECRET_BYTES, x25519_privkey, peerkey.base + MLKEM768_PUBLIC_KEY_BYTES)) {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ MLKEM768_encap(ciphertext->base, secret->base, &mlkem_peer.key);
+
+ ret = 0;
+
+Exit:
+ if (ret != 0) {
+ free(ciphertext->base);
+ *ciphertext = ptls_iovec_init(NULL, 0);
+ free(secret->base);
+ *secret = ptls_iovec_init(NULL, 0);
+ }
+ ptls_clear_memory(&x25519_privkey, sizeof(x25519_privkey));
+ return ret;
+}
+
+#endif
+
int ptls_openssl_create_key_exchange(ptls_key_exchange_context_t **ctx, EVP_PKEY *pkey)
{
int ret, id;
@@ -2063,6 +2195,12 @@
.exchange = evp_keyex_exchange,
.data = NID_X25519};
#endif
+#if PTLS_OPENSSL_HAVE_X25519MLKEM768
+ptls_key_exchange_algorithm_t ptls_openssl_x25519mlkem768 = {.id = PTLS_GROUP_X25519MLKEM768,
+ .name = PTLS_GROUP_NAME_X25519MLKEM768,
+ .create = x25519mlkem768_create,
+ .exchange = x25519mlkem768_exchange};
+#endif
ptls_key_exchange_algorithm_t *ptls_openssl_key_exchanges[] = {&ptls_openssl_secp256r1, NULL};
ptls_cipher_algorithm_t ptls_openssl_aes128ecb = {
"AES128-ECB", PTLS_AES128_KEY_SIZE, PTLS_AES_BLOCK_SIZE, 0 /* iv size */, sizeof(struct cipher_context_t),
diff --git a/t/cli.c b/t/cli.c
index 1bfd80e..dc52bae 100644
--- a/t/cli.c
+++ b/t/cli.c
@@ -409,6 +409,9 @@
#if PTLS_OPENSSL_HAVE_X25519
", X25519"
#endif
+#if PTLS_OPENSSL_HAVE_X25519MLKEM768
+ ", X5519MLKEM768"
+#endif
"\n"
"Supported signature algorithms: rsa, secp256r1"
#if PTLS_OPENSSL_HAVE_SECP384R1
@@ -559,6 +562,9 @@
#if PTLS_OPENSSL_HAVE_X25519
MATCH(x25519);
#endif
+#if PTLS_OPENSSL_HAVE_X25519MLKEM768
+ MATCH(x25519mlkem768);
+#endif
#undef MATCH
if (algo == NULL) {
fprintf(stderr, "could not find key exchange: %s\n", optarg);
diff --git a/t/openssl.c b/t/openssl.c
index b8fe391..4accc2b 100644
--- a/t/openssl.c
+++ b/t/openssl.c
@@ -141,6 +141,10 @@
subtest("x25519-to-minicrypto", test_key_exchange, &ptls_openssl_x25519, &ptls_minicrypto_x25519);
subtest("x25519-from-minicrypto", test_key_exchange, &ptls_minicrypto_x25519, &ptls_openssl_x25519);
#endif
+
+#if PTLS_OPENSSL_HAVE_X25519MLKEM768
+ subtest("x25519mlkem768", test_key_exchange, &ptls_openssl_x25519mlkem768, &ptls_openssl_x25519mlkem768);
+#endif
}
static void test_sign_verify(EVP_PKEY *key, const ptls_openssl_signature_scheme_t *schemes)