diff --git a/include/picotls.h b/include/picotls.h index f3e783e1..727ac608 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -160,6 +160,8 @@ extern "C" { #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 205500ad..01af6a89 100644 --- a/include/picotls/openssl.h +++ b/include/picotls/openssl.h @@ -69,6 +69,12 @@ extern ptls_key_exchange_algorithm_t ptls_openssl_x25519; #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 2833c32b..fb9c3f96 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 +#endif #ifdef PTLS_HAVE_AEGIS #include "./libaegis.h" #endif @@ -706,6 +709,135 @@ static int evp_keyex_exchange(ptls_key_exchange_algorithm_t *algo, ptls_iovec_t #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 @@ ptls_key_exchange_algorithm_t ptls_openssl_x25519 = {.id = PTLS_GROUP_X25519, .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 1bfd80e3..dc52bae0 100644 --- a/t/cli.c +++ b/t/cli.c @@ -408,6 +408,9 @@ static void usage(const char *cmd) #endif #if PTLS_OPENSSL_HAVE_X25519 ", X25519" +#endif +#if PTLS_OPENSSL_HAVE_X25519MLKEM768 + ", X5519MLKEM768" #endif "\n" "Supported signature algorithms: rsa, secp256r1" @@ -559,6 +562,9 @@ int main(int argc, char **argv) #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 b8fe3917..4accc2b0 100644 --- a/t/openssl.c +++ b/t/openssl.c @@ -141,6 +141,10 @@ static void test_key_exchanges(void) 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)