aboutsummaryrefslogtreecommitdiff
path: root/crypto/openssl/providers/implementations/kem
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/openssl/providers/implementations/kem')
-rw-r--r--crypto/openssl/providers/implementations/kem/build.info26
-rw-r--r--crypto/openssl/providers/implementations/kem/ec_kem.c815
-rw-r--r--crypto/openssl/providers/implementations/kem/eckem.h13
-rw-r--r--crypto/openssl/providers/implementations/kem/ecx_kem.c705
-rw-r--r--crypto/openssl/providers/implementations/kem/kem_util.c37
-rw-r--r--crypto/openssl/providers/implementations/kem/ml_kem_kem.c268
-rw-r--r--crypto/openssl/providers/implementations/kem/mlx_kem.c341
-rw-r--r--crypto/openssl/providers/implementations/kem/rsa_kem.c449
-rw-r--r--crypto/openssl/providers/implementations/kem/template_kem.c193
9 files changed, 2847 insertions, 0 deletions
diff --git a/crypto/openssl/providers/implementations/kem/build.info b/crypto/openssl/providers/implementations/kem/build.info
new file mode 100644
index 000000000000..05b8b185127d
--- /dev/null
+++ b/crypto/openssl/providers/implementations/kem/build.info
@@ -0,0 +1,26 @@
+# We make separate GOAL variables for each algorithm, to make it easy to
+# switch each to the Legacy provider when needed.
+
+$RSA_KEM_GOAL=../../libdefault.a ../../libfips.a
+$EC_KEM_GOAL=../../libdefault.a
+$TEMPLATE_KEM_GOAL=../../libtemplate.a
+$ML_KEM_GOAL=../../libdefault.a ../../libfips.a
+$TLS_ML_KEM_HYBRID_GOAL=../../libdefault.a ../../libfips.a
+
+SOURCE[$RSA_KEM_GOAL]=rsa_kem.c
+
+IF[{- !$disabled{ec} -}]
+ SOURCE[$EC_KEM_GOAL]=kem_util.c ec_kem.c
+ IF[{- !$disabled{ecx} -}]
+ SOURCE[$EC_KEM_GOAL]=ecx_kem.c
+ ENDIF
+ENDIF
+
+IF[{- !$disabled{'ml-kem'} -}]
+ IF[{- !$disabled{ec} -}]
+ SOURCE[$TLS_ML_KEM_HYBRID_GOAL]=mlx_kem.c
+ ENDIF
+ SOURCE[$ML_KEM_GOAL] = ml_kem_kem.c
+ENDIF
+
+SOURCE[$TEMPLATE_KEM_GOAL]=template_kem.c
diff --git a/crypto/openssl/providers/implementations/kem/ec_kem.c b/crypto/openssl/providers/implementations/kem/ec_kem.c
new file mode 100644
index 000000000000..975f41144ad0
--- /dev/null
+++ b/crypto/openssl/providers/implementations/kem/ec_kem.c
@@ -0,0 +1,815 @@
+/*
+ * Copyright 2022-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+/*
+ * The following implementation is part of RFC 9180 related to DHKEM using
+ * EC keys (i.e. P-256, P-384 and P-521)
+ * References to Sections in the comments below refer to RFC 9180.
+ */
+
+#include "internal/deprecated.h"
+
+#include <openssl/crypto.h>
+#include <openssl/evp.h>
+#include <openssl/core_dispatch.h>
+#include <openssl/core_names.h>
+#include <openssl/ec.h>
+#include <openssl/params.h>
+#include <openssl/err.h>
+#include <openssl/proverr.h>
+#include <openssl/kdf.h>
+#include <openssl/rand.h>
+#include "prov/provider_ctx.h"
+#include "prov/implementations.h"
+#include "prov/securitycheck.h"
+#include "prov/providercommon.h"
+
+#include <openssl/hpke.h>
+#include "internal/hpke_util.h"
+#include "crypto/ec.h"
+#include "prov/ecx.h"
+#include "eckem.h"
+
+typedef struct {
+ EC_KEY *recipient_key;
+ EC_KEY *sender_authkey;
+ OSSL_LIB_CTX *libctx;
+ char *propq;
+ unsigned int mode;
+ unsigned int op;
+ unsigned char *ikm;
+ size_t ikmlen;
+ const char *kdfname;
+ const OSSL_HPKE_KEM_INFO *info;
+} PROV_EC_CTX;
+
+static OSSL_FUNC_kem_newctx_fn eckem_newctx;
+static OSSL_FUNC_kem_encapsulate_init_fn eckem_encapsulate_init;
+static OSSL_FUNC_kem_auth_encapsulate_init_fn eckem_auth_encapsulate_init;
+static OSSL_FUNC_kem_encapsulate_fn eckem_encapsulate;
+static OSSL_FUNC_kem_decapsulate_init_fn eckem_decapsulate_init;
+static OSSL_FUNC_kem_auth_decapsulate_init_fn eckem_auth_decapsulate_init;
+static OSSL_FUNC_kem_decapsulate_fn eckem_decapsulate;
+static OSSL_FUNC_kem_freectx_fn eckem_freectx;
+static OSSL_FUNC_kem_set_ctx_params_fn eckem_set_ctx_params;
+static OSSL_FUNC_kem_settable_ctx_params_fn eckem_settable_ctx_params;
+
+/* ASCII: "KEM", in hex for EBCDIC compatibility */
+static const char LABEL_KEM[] = "\x4b\x45\x4d";
+
+static int eckey_check(const EC_KEY *ec, int requires_privatekey)
+{
+ int rv = 0;
+ BN_CTX *bnctx = NULL;
+ BIGNUM *rem = NULL;
+ const BIGNUM *priv = EC_KEY_get0_private_key(ec);
+ const EC_POINT *pub = EC_KEY_get0_public_key(ec);
+
+ /* Keys always require a public component */
+ if (pub == NULL) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_NOT_A_PUBLIC_KEY);
+ return 0;
+ }
+ if (priv == NULL) {
+ return (requires_privatekey == 0);
+ } else {
+ /* If there is a private key, check that is non zero (mod order) */
+ const EC_GROUP *group = EC_KEY_get0_group(ec);
+ const BIGNUM *order = EC_GROUP_get0_order(group);
+
+ bnctx = BN_CTX_new_ex(ossl_ec_key_get_libctx(ec));
+ rem = BN_new();
+
+ if (order != NULL && rem != NULL && bnctx != NULL) {
+ rv = BN_mod(rem, priv, order, bnctx)
+ && !BN_is_zero(rem);
+ }
+ }
+ BN_free(rem);
+ BN_CTX_free(bnctx);
+ return rv;
+}
+
+/* Returns NULL if the curve is not supported */
+static const char *ec_curvename_get0(const EC_KEY *ec)
+{
+ const EC_GROUP *group = EC_KEY_get0_group(ec);
+
+ return EC_curve_nid2nist(EC_GROUP_get_curve_name(group));
+}
+
+/*
+ * Set the recipient key, and free any existing key.
+ * ec can be NULL.
+ * The ec key may have only a private or public component
+ * (but it must have a group).
+ */
+static int recipient_key_set(PROV_EC_CTX *ctx, EC_KEY *ec)
+{
+ EC_KEY_free(ctx->recipient_key);
+ ctx->recipient_key = NULL;
+
+ if (ec != NULL) {
+ const char *curve = ec_curvename_get0(ec);
+
+ if (curve == NULL)
+ return -2;
+ ctx->info = ossl_HPKE_KEM_INFO_find_curve(curve);
+ if (ctx->info == NULL)
+ return -2;
+ if (!EC_KEY_up_ref(ec))
+ return 0;
+ ctx->recipient_key = ec;
+ ctx->kdfname = "HKDF";
+ }
+ return 1;
+}
+
+/*
+ * Set the senders auth key, and free any existing auth key.
+ * ec can be NULL.
+ */
+static int sender_authkey_set(PROV_EC_CTX *ctx, EC_KEY *ec)
+{
+ EC_KEY_free(ctx->sender_authkey);
+ ctx->sender_authkey = NULL;
+
+ if (ec != NULL) {
+ if (!EC_KEY_up_ref(ec))
+ return 0;
+ ctx->sender_authkey = ec;
+ }
+ return 1;
+}
+
+/*
+ * Serializes a encoded public key buffer into a EC public key.
+ * Params:
+ * in Contains the group.
+ * pubbuf The encoded public key buffer
+ * Returns: The created public EC key, or NULL if there is an error.
+ */
+static EC_KEY *eckey_frompub(EC_KEY *in,
+ const unsigned char *pubbuf, size_t pubbuflen)
+{
+ EC_KEY *key;
+
+ key = EC_KEY_new_ex(ossl_ec_key_get_libctx(in), ossl_ec_key_get0_propq(in));
+ if (key == NULL)
+ goto err;
+ if (!EC_KEY_set_group(key, EC_KEY_get0_group(in)))
+ goto err;
+ if (!EC_KEY_oct2key(key, pubbuf, pubbuflen, NULL))
+ goto err;
+ return key;
+err:
+ EC_KEY_free(key);
+ return NULL;
+}
+
+/*
+ * Deserialises a EC public key into a encoded byte array.
+ * Returns: 1 if successful or 0 otherwise.
+ */
+static int ecpubkey_todata(const EC_KEY *ec, unsigned char *out, size_t *outlen,
+ size_t maxoutlen)
+{
+ const EC_POINT *pub;
+ const EC_GROUP *group;
+
+ group = EC_KEY_get0_group(ec);
+ pub = EC_KEY_get0_public_key(ec);
+ *outlen = EC_POINT_point2oct(group, pub, POINT_CONVERSION_UNCOMPRESSED,
+ out, maxoutlen, NULL);
+ return *outlen != 0;
+}
+
+static void *eckem_newctx(void *provctx)
+{
+ PROV_EC_CTX *ctx = OPENSSL_zalloc(sizeof(PROV_EC_CTX));
+
+ if (ctx == NULL)
+ return NULL;
+ ctx->libctx = PROV_LIBCTX_OF(provctx);
+ ctx->mode = KEM_MODE_DHKEM;
+
+ return ctx;
+}
+
+static void eckem_freectx(void *vectx)
+{
+ PROV_EC_CTX *ctx = (PROV_EC_CTX *)vectx;
+
+ OPENSSL_clear_free(ctx->ikm, ctx->ikmlen);
+ recipient_key_set(ctx, NULL);
+ sender_authkey_set(ctx, NULL);
+ OPENSSL_free(ctx);
+}
+
+static int ossl_ec_match_params(const EC_KEY *key1, const EC_KEY *key2)
+{
+ int ret;
+ BN_CTX *ctx = NULL;
+ const EC_GROUP *group1 = EC_KEY_get0_group(key1);
+ const EC_GROUP *group2 = EC_KEY_get0_group(key2);
+
+ ctx = BN_CTX_new_ex(ossl_ec_key_get_libctx(key1));
+ if (ctx == NULL)
+ return 0;
+
+ ret = group1 != NULL
+ && group2 != NULL
+ && EC_GROUP_cmp(group1, group2, ctx) == 0;
+ if (!ret)
+ ERR_raise(ERR_LIB_PROV, PROV_R_MISMATCHING_DOMAIN_PARAMETERS);
+ BN_CTX_free(ctx);
+ return ret;
+}
+
+static int eckem_init(void *vctx, int operation, void *vec, void *vauth,
+ const OSSL_PARAM params[])
+{
+ int rv;
+ PROV_EC_CTX *ctx = (PROV_EC_CTX *)vctx;
+ EC_KEY *ec = vec;
+ EC_KEY *auth = vauth;
+
+ if (!ossl_prov_is_running())
+ return 0;
+
+ if (!eckey_check(ec, operation == EVP_PKEY_OP_DECAPSULATE))
+ return 0;
+ rv = recipient_key_set(ctx, ec);
+ if (rv <= 0)
+ return rv;
+
+ if (auth != NULL) {
+ if (!ossl_ec_match_params(ec, auth)
+ || !eckey_check(auth, operation == EVP_PKEY_OP_ENCAPSULATE)
+ || !sender_authkey_set(ctx, auth))
+ return 0;
+ }
+
+ ctx->op = operation;
+ return eckem_set_ctx_params(vctx, params);
+}
+
+static int eckem_encapsulate_init(void *vctx, void *vec,
+ const OSSL_PARAM params[])
+{
+ return eckem_init(vctx, EVP_PKEY_OP_ENCAPSULATE, vec, NULL, params);
+}
+
+static int eckem_decapsulate_init(void *vctx, void *vec,
+ const OSSL_PARAM params[])
+{
+ return eckem_init(vctx, EVP_PKEY_OP_DECAPSULATE, vec, NULL, params);
+}
+
+static int eckem_auth_encapsulate_init(void *vctx, void *vecx, void *vauthpriv,
+ const OSSL_PARAM params[])
+{
+ return eckem_init(vctx, EVP_PKEY_OP_ENCAPSULATE, vecx, vauthpriv, params);
+}
+
+static int eckem_auth_decapsulate_init(void *vctx, void *vecx, void *vauthpub,
+ const OSSL_PARAM params[])
+{
+ return eckem_init(vctx, EVP_PKEY_OP_DECAPSULATE, vecx, vauthpub, params);
+}
+
+static int eckem_set_ctx_params(void *vctx, const OSSL_PARAM params[])
+{
+ PROV_EC_CTX *ctx = (PROV_EC_CTX *)vctx;
+ const OSSL_PARAM *p;
+ int mode;
+
+ if (ossl_param_is_empty(params))
+ return 1;
+
+ p = OSSL_PARAM_locate_const(params, OSSL_KEM_PARAM_IKME);
+ if (p != NULL) {
+ void *tmp = NULL;
+ size_t tmplen = 0;
+
+ if (p->data != NULL && p->data_size != 0) {
+ if (!OSSL_PARAM_get_octet_string(p, &tmp, 0, &tmplen))
+ return 0;
+ }
+ OPENSSL_clear_free(ctx->ikm, ctx->ikmlen);
+ /* Set the ephemeral seed */
+ ctx->ikm = tmp;
+ ctx->ikmlen = tmplen;
+ }
+
+ p = OSSL_PARAM_locate_const(params, OSSL_KEM_PARAM_OPERATION);
+ if (p != NULL) {
+ if (p->data_type != OSSL_PARAM_UTF8_STRING)
+ return 0;
+ mode = ossl_eckem_modename2id(p->data);
+ if (mode == KEM_MODE_UNDEFINED)
+ return 0;
+ ctx->mode = mode;
+ }
+ return 1;
+}
+
+static const OSSL_PARAM known_settable_eckem_ctx_params[] = {
+ OSSL_PARAM_utf8_string(OSSL_KEM_PARAM_OPERATION, NULL, 0),
+ OSSL_PARAM_octet_string(OSSL_KEM_PARAM_IKME, NULL, 0),
+ OSSL_PARAM_END
+};
+
+static const OSSL_PARAM *eckem_settable_ctx_params(ossl_unused void *vctx,
+ ossl_unused void *provctx)
+{
+ return known_settable_eckem_ctx_params;
+}
+
+/*
+ * See Section 4.1 DH-Based KEM (DHKEM) ExtractAndExpand
+ */
+static int dhkem_extract_and_expand(EVP_KDF_CTX *kctx,
+ unsigned char *okm, size_t okmlen,
+ uint16_t kemid,
+ const unsigned char *dhkm, size_t dhkmlen,
+ const unsigned char *kemctx,
+ size_t kemctxlen)
+{
+ uint8_t suiteid[2];
+ uint8_t prk[EVP_MAX_MD_SIZE];
+ size_t prklen = okmlen;
+ int ret;
+
+ if (prklen > sizeof(prk))
+ return 0;
+
+ suiteid[0] = (kemid >> 8) & 0xff;
+ suiteid[1] = kemid & 0xff;
+
+ ret = ossl_hpke_labeled_extract(kctx, prk, prklen,
+ NULL, 0, LABEL_KEM, suiteid, sizeof(suiteid),
+ OSSL_DHKEM_LABEL_EAE_PRK, dhkm, dhkmlen)
+ && ossl_hpke_labeled_expand(kctx, okm, okmlen, prk, prklen,
+ LABEL_KEM, suiteid, sizeof(suiteid),
+ OSSL_DHKEM_LABEL_SHARED_SECRET,
+ kemctx, kemctxlen);
+ OPENSSL_cleanse(prk, prklen);
+ return ret;
+}
+
+/*
+ * See Section 7.1.3 DeriveKeyPair.
+ *
+ * This function is used by ec keygen.
+ * (For this reason it does not use any of the state stored in PROV_EC_CTX).
+ *
+ * Params:
+ * ec An initialized ec key.
+ * priv The buffer to store the generated private key into (it is assumed
+ * this is of length alg->encodedprivlen).
+ * ikm buffer containing the input key material (seed). This must be set.
+ * ikmlen size of the ikm buffer in bytes
+ * Returns:
+ * 1 if successful or 0 otherwise.
+ */
+int ossl_ec_dhkem_derive_private(EC_KEY *ec, BIGNUM *priv,
+ const unsigned char *ikm, size_t ikmlen)
+{
+ int ret = 0;
+ EVP_KDF_CTX *kdfctx = NULL;
+ uint8_t suiteid[2];
+ unsigned char prk[OSSL_HPKE_MAX_SECRET];
+ unsigned char privbuf[OSSL_HPKE_MAX_PRIVATE];
+ const BIGNUM *order;
+ unsigned char counter = 0;
+ const char *curve = ec_curvename_get0(ec);
+ const OSSL_HPKE_KEM_INFO *info;
+
+ if (curve == NULL)
+ return -2;
+
+ info = ossl_HPKE_KEM_INFO_find_curve(curve);
+ if (info == NULL)
+ return -2;
+
+ kdfctx = ossl_kdf_ctx_create("HKDF", info->mdname,
+ ossl_ec_key_get_libctx(ec),
+ ossl_ec_key_get0_propq(ec));
+ if (kdfctx == NULL)
+ return 0;
+
+ /* ikmlen should have a length of at least Nsk */
+ if (ikmlen < info->Nsk) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_INPUT_LENGTH,
+ "ikm length is :%zu, should be at least %zu",
+ ikmlen, info->Nsk);
+ goto err;
+ }
+
+ suiteid[0] = info->kem_id / 256;
+ suiteid[1] = info->kem_id % 256;
+
+ if (!ossl_hpke_labeled_extract(kdfctx, prk, info->Nsecret,
+ NULL, 0, LABEL_KEM, suiteid, sizeof(suiteid),
+ OSSL_DHKEM_LABEL_DKP_PRK, ikm, ikmlen))
+ goto err;
+
+ order = EC_GROUP_get0_order(EC_KEY_get0_group(ec));
+ do {
+ if (!ossl_hpke_labeled_expand(kdfctx, privbuf, info->Nsk,
+ prk, info->Nsecret,
+ LABEL_KEM, suiteid, sizeof(suiteid),
+ OSSL_DHKEM_LABEL_CANDIDATE,
+ &counter, 1))
+ goto err;
+ privbuf[0] &= info->bitmask;
+ if (BN_bin2bn(privbuf, info->Nsk, priv) == NULL)
+ goto err;
+ if (counter == 0xFF) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GENERATE_KEY);
+ goto err;
+ }
+ counter++;
+ } while (BN_is_zero(priv) || BN_cmp(priv, order) >= 0);
+ ret = 1;
+err:
+ OPENSSL_cleanse(prk, sizeof(prk));
+ OPENSSL_cleanse(privbuf, sizeof(privbuf));
+ EVP_KDF_CTX_free(kdfctx);
+ return ret;
+}
+
+/*
+ * Do a keygen operation without having to use EVP_PKEY.
+ * Params:
+ * ctx Context object
+ * ikm The seed material - if this is NULL, then a random seed is used.
+ * Returns:
+ * The generated EC key, or NULL on failure.
+ */
+static EC_KEY *derivekey(PROV_EC_CTX *ctx,
+ const unsigned char *ikm, size_t ikmlen)
+{
+ int ret = 0;
+ EC_KEY *key;
+ unsigned char *seed = (unsigned char *)ikm;
+ size_t seedlen = ikmlen;
+ unsigned char tmpbuf[OSSL_HPKE_MAX_PRIVATE];
+
+ key = EC_KEY_new_ex(ctx->libctx, ctx->propq);
+ if (key == NULL)
+ goto err;
+ if (!EC_KEY_set_group(key, EC_KEY_get0_group(ctx->recipient_key)))
+ goto err;
+
+ /* Generate a random seed if there is no input ikm */
+ if (seed == NULL || seedlen == 0) {
+ seedlen = ctx->info->Nsk;
+ if (seedlen > sizeof(tmpbuf))
+ goto err;
+ if (RAND_priv_bytes_ex(ctx->libctx, tmpbuf, seedlen, 0) <= 0)
+ goto err;
+ seed = tmpbuf;
+ }
+ ret = ossl_ec_generate_key_dhkem(key, seed, seedlen);
+err:
+ if (seed != ikm)
+ OPENSSL_cleanse(seed, seedlen);
+ if (ret <= 0) {
+ EC_KEY_free(key);
+ key = NULL;
+ }
+ return key;
+}
+
+/*
+ * Before doing a key exchange the public key of the peer needs to be checked
+ * Note that the group check is not done here as we have already checked
+ * that it only uses one of the approved curve names when the key was set.
+ *
+ * Returns 1 if the public key is valid, or 0 if it fails.
+ */
+static int check_publickey(const EC_KEY *pub)
+{
+ int ret = 0;
+ BN_CTX *bnctx = BN_CTX_new_ex(ossl_ec_key_get_libctx(pub));
+
+ if (bnctx == NULL)
+ return 0;
+ ret = ossl_ec_key_public_check(pub, bnctx);
+ BN_CTX_free(bnctx);
+
+ return ret;
+}
+
+/*
+ * Do an ecdh key exchange.
+ * dhkm = DH(sender, peer)
+ *
+ * NOTE: Instead of using EVP_PKEY_derive() API's, we use EC_KEY operations
+ * to avoid messy conversions back to EVP_PKEY.
+ *
+ * Returns the size of the secret if successful, or 0 otherwise,
+ */
+static int generate_ecdhkm(const EC_KEY *sender, const EC_KEY *peer,
+ unsigned char *out, size_t maxout,
+ unsigned int secretsz)
+{
+ const EC_GROUP *group = EC_KEY_get0_group(sender);
+ size_t secretlen = (EC_GROUP_get_degree(group) + 7) / 8;
+
+ if (secretlen != secretsz || secretlen > maxout) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "secretsz invalid");
+ return 0;
+ }
+
+ if (!check_publickey(peer))
+ return 0;
+ return ECDH_compute_key(out, secretlen, EC_KEY_get0_public_key(peer),
+ sender, NULL) > 0;
+}
+
+/*
+ * Derive a secret using ECDH (code is shared by the encap and decap)
+ *
+ * dhkm = Concat(ecdh(privkey1, peerkey1), ecdh(privkey2, peerkey2)
+ * kemctx = Concat(sender_pub, recipient_pub, ctx->sender_authkey)
+ * secret = dhkem_extract_and_expand(kemid, dhkm, kemctx);
+ *
+ * Params:
+ * ctx Object that contains algorithm state and constants.
+ * secret The returned secret (with a length ctx->alg->secretlen bytes).
+ * privkey1 A private key used for ECDH key derivation.
+ * peerkey1 A public key used for ECDH key derivation with privkey1
+ * privkey2 A optional private key used for a second ECDH key derivation.
+ * It can be NULL.
+ * peerkey2 A optional public key used for a second ECDH key derivation
+ * with privkey2,. It can be NULL.
+ * sender_pub The senders public key in encoded form.
+ * recipient_pub The recipients public key in encoded form.
+ * Notes:
+ * The second ecdh() is only used for the HPKE auth modes when both privkey2
+ * and peerkey2 are non NULL (i.e. ctx->sender_authkey is not NULL).
+ */
+static int derive_secret(PROV_EC_CTX *ctx, unsigned char *secret,
+ const EC_KEY *privkey1, const EC_KEY *peerkey1,
+ const EC_KEY *privkey2, const EC_KEY *peerkey2,
+ const unsigned char *sender_pub,
+ const unsigned char *recipient_pub)
+{
+ int ret = 0;
+ EVP_KDF_CTX *kdfctx = NULL;
+ unsigned char sender_authpub[OSSL_HPKE_MAX_PUBLIC];
+ unsigned char dhkm[OSSL_HPKE_MAX_PRIVATE * 2];
+ unsigned char kemctx[OSSL_HPKE_MAX_PUBLIC * 3];
+ size_t sender_authpublen;
+ size_t kemctxlen = 0, dhkmlen = 0;
+ const OSSL_HPKE_KEM_INFO *info = ctx->info;
+ size_t encodedpublen = info->Npk;
+ size_t encodedprivlen = info->Nsk;
+ int auth = ctx->sender_authkey != NULL;
+
+ if (!generate_ecdhkm(privkey1, peerkey1, dhkm, sizeof(dhkm), encodedprivlen))
+ goto err;
+ dhkmlen = encodedprivlen;
+ kemctxlen = 2 * encodedpublen;
+
+ /* Concat the optional second ECDH (used for Auth) */
+ if (auth) {
+ /* Get the public key of the auth sender in encoded form */
+ if (!ecpubkey_todata(ctx->sender_authkey, sender_authpub,
+ &sender_authpublen, sizeof(sender_authpub)))
+ goto err;
+ if (sender_authpublen != encodedpublen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY,
+ "Invalid sender auth public key");
+ goto err;
+ }
+ if (!generate_ecdhkm(privkey2, peerkey2,
+ dhkm + dhkmlen, sizeof(dhkm) - dhkmlen,
+ encodedprivlen))
+ goto err;
+ dhkmlen += encodedprivlen;
+ kemctxlen += encodedpublen;
+ }
+ if (kemctxlen > sizeof(kemctx))
+ goto err;
+
+ /* kemctx is the concat of both sides encoded public key */
+ memcpy(kemctx, sender_pub, info->Npk);
+ memcpy(kemctx + info->Npk, recipient_pub, info->Npk);
+ if (auth)
+ memcpy(kemctx + 2 * encodedpublen, sender_authpub, encodedpublen);
+ kdfctx = ossl_kdf_ctx_create(ctx->kdfname, info->mdname,
+ ctx->libctx, ctx->propq);
+ if (kdfctx == NULL)
+ goto err;
+ if (!dhkem_extract_and_expand(kdfctx, secret, info->Nsecret,
+ info->kem_id, dhkm, dhkmlen,
+ kemctx, kemctxlen))
+ goto err;
+ ret = 1;
+err:
+ OPENSSL_cleanse(dhkm, dhkmlen);
+ EVP_KDF_CTX_free(kdfctx);
+ return ret;
+}
+
+/*
+ * Do a DHKEM encapsulate operation.
+ *
+ * See Section 4.1 Encap() and AuthEncap()
+ *
+ * Params:
+ * ctx A context object holding the recipients public key and the
+ * optional senders auth private key.
+ * enc A buffer to return the senders ephemeral public key.
+ * Setting this to NULL allows the enclen and secretlen to return
+ * values, without calculating the secret.
+ * enclen Passes in the max size of the enc buffer and returns the
+ * encoded public key length.
+ * secret A buffer to return the calculated shared secret.
+ * secretlen Passes in the max size of the secret buffer and returns the
+ * secret length.
+ * Returns: 1 on success or 0 otherwise.
+ */
+static int dhkem_encap(PROV_EC_CTX *ctx,
+ unsigned char *enc, size_t *enclen,
+ unsigned char *secret, size_t *secretlen)
+{
+ int ret = 0;
+ EC_KEY *sender_ephemkey = NULL;
+ unsigned char sender_pub[OSSL_HPKE_MAX_PUBLIC];
+ unsigned char recipient_pub[OSSL_HPKE_MAX_PUBLIC];
+ size_t sender_publen, recipient_publen;
+ const OSSL_HPKE_KEM_INFO *info = ctx->info;
+
+ if (enc == NULL) {
+ if (enclen == NULL && secretlen == NULL)
+ return 0;
+ if (enclen != NULL)
+ *enclen = info->Nenc;
+ if (secretlen != NULL)
+ *secretlen = info->Nsecret;
+ return 1;
+ }
+
+ if (*secretlen < info->Nsecret) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*secretlen too small");
+ return 0;
+ }
+ if (*enclen < info->Nenc) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*enclen too small");
+ return 0;
+ }
+
+ /* Create an ephemeral key */
+ sender_ephemkey = derivekey(ctx, ctx->ikm, ctx->ikmlen);
+ if (sender_ephemkey == NULL)
+ goto err;
+ if (!ecpubkey_todata(sender_ephemkey, sender_pub, &sender_publen,
+ sizeof(sender_pub))
+ || !ecpubkey_todata(ctx->recipient_key, recipient_pub,
+ &recipient_publen, sizeof(recipient_pub)))
+ goto err;
+
+ if (sender_publen != info->Npk
+ || recipient_publen != sender_publen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY, "Invalid public key");
+ goto err;
+ }
+
+ if (!derive_secret(ctx, secret,
+ sender_ephemkey, ctx->recipient_key,
+ ctx->sender_authkey, ctx->recipient_key,
+ sender_pub, recipient_pub))
+ goto err;
+
+ /* Return the senders ephemeral public key in encoded form */
+ memcpy(enc, sender_pub, sender_publen);
+ *enclen = sender_publen;
+ *secretlen = info->Nsecret;
+ ret = 1;
+err:
+ EC_KEY_free(sender_ephemkey);
+ return ret;
+}
+
+/*
+ * Do a DHKEM decapsulate operation.
+ * See Section 4.1 Decap() and Auth Decap()
+ *
+ * Params:
+ * ctx A context object holding the recipients private key and the
+ * optional senders auth public key.
+ * secret A buffer to return the calculated shared secret. Setting this to
+ * NULL can be used to return the secretlen.
+ * secretlen Passes in the max size of the secret buffer and returns the
+ * secret length.
+ * enc A buffer containing the senders ephemeral public key that was returned
+ * from dhkem_encap().
+ * enclen The length in bytes of enc.
+ * Returns: 1 If the shared secret is returned or 0 on error.
+ */
+static int dhkem_decap(PROV_EC_CTX *ctx,
+ unsigned char *secret, size_t *secretlen,
+ const unsigned char *enc, size_t enclen)
+{
+ int ret = 0;
+ EC_KEY *sender_ephempubkey = NULL;
+ const OSSL_HPKE_KEM_INFO *info = ctx->info;
+ unsigned char recipient_pub[OSSL_HPKE_MAX_PUBLIC];
+ size_t recipient_publen;
+ size_t encodedpublen = info->Npk;
+
+ if (secret == NULL) {
+ *secretlen = info->Nsecret;
+ return 1;
+ }
+
+ if (*secretlen < info->Nsecret) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*secretlen too small");
+ return 0;
+ }
+ if (enclen != encodedpublen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY, "Invalid enc public key");
+ return 0;
+ }
+
+ sender_ephempubkey = eckey_frompub(ctx->recipient_key, enc, enclen);
+ if (sender_ephempubkey == NULL)
+ goto err;
+ if (!ecpubkey_todata(ctx->recipient_key, recipient_pub, &recipient_publen,
+ sizeof(recipient_pub)))
+ goto err;
+ if (recipient_publen != encodedpublen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY, "Invalid recipient public key");
+ goto err;
+ }
+
+ if (!derive_secret(ctx, secret,
+ ctx->recipient_key, sender_ephempubkey,
+ ctx->recipient_key, ctx->sender_authkey,
+ enc, recipient_pub))
+ goto err;
+ *secretlen = info->Nsecret;
+ ret = 1;
+err:
+ EC_KEY_free(sender_ephempubkey);
+ return ret;
+}
+
+static int eckem_encapsulate(void *vctx, unsigned char *out, size_t *outlen,
+ unsigned char *secret, size_t *secretlen)
+{
+ PROV_EC_CTX *ctx = (PROV_EC_CTX *)vctx;
+
+ switch (ctx->mode) {
+ case KEM_MODE_DHKEM:
+ return dhkem_encap(ctx, out, outlen, secret, secretlen);
+ default:
+ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_MODE);
+ return -2;
+ }
+}
+
+static int eckem_decapsulate(void *vctx, unsigned char *out, size_t *outlen,
+ const unsigned char *in, size_t inlen)
+{
+ PROV_EC_CTX *ctx = (PROV_EC_CTX *)vctx;
+
+ switch (ctx->mode) {
+ case KEM_MODE_DHKEM:
+ return dhkem_decap(ctx, out, outlen, in, inlen);
+ default:
+ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_MODE);
+ return -2;
+ }
+}
+
+const OSSL_DISPATCH ossl_ec_asym_kem_functions[] = {
+ { OSSL_FUNC_KEM_NEWCTX, (void (*)(void))eckem_newctx },
+ { OSSL_FUNC_KEM_ENCAPSULATE_INIT,
+ (void (*)(void))eckem_encapsulate_init },
+ { OSSL_FUNC_KEM_ENCAPSULATE, (void (*)(void))eckem_encapsulate },
+ { OSSL_FUNC_KEM_DECAPSULATE_INIT,
+ (void (*)(void))eckem_decapsulate_init },
+ { OSSL_FUNC_KEM_DECAPSULATE, (void (*)(void))eckem_decapsulate },
+ { OSSL_FUNC_KEM_FREECTX, (void (*)(void))eckem_freectx },
+ { OSSL_FUNC_KEM_SET_CTX_PARAMS,
+ (void (*)(void))eckem_set_ctx_params },
+ { OSSL_FUNC_KEM_SETTABLE_CTX_PARAMS,
+ (void (*)(void))eckem_settable_ctx_params },
+ { OSSL_FUNC_KEM_AUTH_ENCAPSULATE_INIT,
+ (void (*)(void))eckem_auth_encapsulate_init },
+ { OSSL_FUNC_KEM_AUTH_DECAPSULATE_INIT,
+ (void (*)(void))eckem_auth_decapsulate_init },
+ OSSL_DISPATCH_END
+};
diff --git a/crypto/openssl/providers/implementations/kem/eckem.h b/crypto/openssl/providers/implementations/kem/eckem.h
new file mode 100644
index 000000000000..2e46a0f2ffab
--- /dev/null
+++ b/crypto/openssl/providers/implementations/kem/eckem.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#define KEM_MODE_UNDEFINED 0
+#define KEM_MODE_DHKEM 1
+
+int ossl_eckem_modename2id(const char *name);
diff --git a/crypto/openssl/providers/implementations/kem/ecx_kem.c b/crypto/openssl/providers/implementations/kem/ecx_kem.c
new file mode 100644
index 000000000000..823662dd37c3
--- /dev/null
+++ b/crypto/openssl/providers/implementations/kem/ecx_kem.c
@@ -0,0 +1,705 @@
+/*
+ * Copyright 2022-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+/*
+ * The following implementation is part of RFC 9180 related to DHKEM using
+ * ECX keys (i.e. X25519 and X448)
+ * References to Sections in the comments below refer to RFC 9180.
+ */
+
+#include "internal/deprecated.h"
+
+#include <string.h>
+#include <openssl/crypto.h>
+#include <openssl/evp.h>
+#include <openssl/core_dispatch.h>
+#include <openssl/core_names.h>
+#include <openssl/params.h>
+#include <openssl/kdf.h>
+#include <openssl/err.h>
+#include <openssl/sha.h>
+#include <openssl/rand.h>
+#include <openssl/proverr.h>
+#include "prov/provider_ctx.h"
+#include "prov/implementations.h"
+#include "prov/securitycheck.h"
+#include "prov/providercommon.h"
+#include "prov/ecx.h"
+#include "crypto/ecx.h"
+#include <openssl/hpke.h>
+#include "internal/hpke_util.h"
+#include "eckem.h"
+
+#define MAX_ECX_KEYLEN X448_KEYLEN
+
+/* KEM identifiers from Section 7.1 "Table 2 KEM IDs" */
+#define KEMID_X25519_HKDF_SHA256 0x20
+#define KEMID_X448_HKDF_SHA512 0x21
+
+/* ASCII: "KEM", in hex for EBCDIC compatibility */
+static const char LABEL_KEM[] = "\x4b\x45\x4d";
+
+typedef struct {
+ ECX_KEY *recipient_key;
+ ECX_KEY *sender_authkey;
+ OSSL_LIB_CTX *libctx;
+ char *propq;
+ unsigned int mode;
+ unsigned int op;
+ unsigned char *ikm;
+ size_t ikmlen;
+ const char *kdfname;
+ const OSSL_HPKE_KEM_INFO *info;
+} PROV_ECX_CTX;
+
+static OSSL_FUNC_kem_newctx_fn ecxkem_newctx;
+static OSSL_FUNC_kem_encapsulate_init_fn ecxkem_encapsulate_init;
+static OSSL_FUNC_kem_encapsulate_fn ecxkem_encapsulate;
+static OSSL_FUNC_kem_decapsulate_init_fn ecxkem_decapsulate_init;
+static OSSL_FUNC_kem_decapsulate_fn ecxkem_decapsulate;
+static OSSL_FUNC_kem_freectx_fn ecxkem_freectx;
+static OSSL_FUNC_kem_set_ctx_params_fn ecxkem_set_ctx_params;
+static OSSL_FUNC_kem_auth_encapsulate_init_fn ecxkem_auth_encapsulate_init;
+static OSSL_FUNC_kem_auth_decapsulate_init_fn ecxkem_auth_decapsulate_init;
+
+/*
+ * Set KEM values as specified in Section 7.1 "Table 2 KEM IDs"
+ * There is only one set of values for X25519 and X448.
+ * Additional values could be set via set_params if required.
+ */
+static const OSSL_HPKE_KEM_INFO *get_kem_info(ECX_KEY *ecx)
+{
+ const char *name = NULL;
+
+ if (ecx->type == ECX_KEY_TYPE_X25519)
+ name = SN_X25519;
+ else
+ name = SN_X448;
+ return ossl_HPKE_KEM_INFO_find_curve(name);
+}
+
+/*
+ * Set the recipient key, and free any existing key.
+ * ecx can be NULL. The ecx key may have only a private or public component.
+ */
+static int recipient_key_set(PROV_ECX_CTX *ctx, ECX_KEY *ecx)
+{
+ ossl_ecx_key_free(ctx->recipient_key);
+ ctx->recipient_key = NULL;
+ if (ecx != NULL) {
+ ctx->info = get_kem_info(ecx);
+ if (ctx->info == NULL)
+ return -2;
+ ctx->kdfname = "HKDF";
+ if (!ossl_ecx_key_up_ref(ecx))
+ return 0;
+ ctx->recipient_key = ecx;
+ }
+ return 1;
+}
+
+/*
+ * Set the senders auth key, and free any existing auth key.
+ * ecx can be NULL.
+ */
+static int sender_authkey_set(PROV_ECX_CTX *ctx, ECX_KEY *ecx)
+{
+ ossl_ecx_key_free(ctx->sender_authkey);
+ ctx->sender_authkey = NULL;
+
+ if (ecx != NULL) {
+ if (!ossl_ecx_key_up_ref(ecx))
+ return 0;
+ ctx->sender_authkey = ecx;
+ }
+ return 1;
+}
+
+/*
+ * Serialize a public key from byte array's for the encoded public keys.
+ * ctx is used to access the key type.
+ * Returns: The created ECX_KEY or NULL on error.
+ */
+static ECX_KEY *ecxkey_pubfromdata(PROV_ECX_CTX *ctx,
+ const unsigned char *pubbuf, size_t pubbuflen)
+{
+ ECX_KEY *ecx = NULL;
+ OSSL_PARAM params[2], *p = params;
+
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY,
+ (char *)pubbuf, pubbuflen);
+ *p = OSSL_PARAM_construct_end();
+
+ ecx = ossl_ecx_key_new(ctx->libctx, ctx->recipient_key->type, 1, ctx->propq);
+ if (ecx == NULL)
+ return NULL;
+ if (ossl_ecx_key_fromdata(ecx, params, 0) <= 0) {
+ ossl_ecx_key_free(ecx);
+ ecx = NULL;
+ }
+ return ecx;
+}
+
+static unsigned char *ecx_pubkey(ECX_KEY *ecx)
+{
+ if (ecx == NULL || !ecx->haspubkey) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_NOT_A_PUBLIC_KEY);
+ return 0;
+ }
+ return ecx->pubkey;
+}
+
+static void *ecxkem_newctx(void *provctx)
+{
+ PROV_ECX_CTX *ctx = OPENSSL_zalloc(sizeof(PROV_ECX_CTX));
+
+ if (ctx == NULL)
+ return NULL;
+ ctx->libctx = PROV_LIBCTX_OF(provctx);
+ ctx->mode = KEM_MODE_DHKEM;
+
+ return ctx;
+}
+
+static void ecxkem_freectx(void *vectx)
+{
+ PROV_ECX_CTX *ctx = (PROV_ECX_CTX *)vectx;
+
+ OPENSSL_clear_free(ctx->ikm, ctx->ikmlen);
+ recipient_key_set(ctx, NULL);
+ sender_authkey_set(ctx, NULL);
+ OPENSSL_free(ctx);
+}
+
+static int ecx_match_params(const ECX_KEY *key1, const ECX_KEY *key2)
+{
+ return (key1->type == key2->type && key1->keylen == key2->keylen);
+}
+
+static int ecx_key_check(const ECX_KEY *ecx, int requires_privatekey)
+{
+ if (ecx->privkey == NULL)
+ return (requires_privatekey == 0);
+ return 1;
+}
+
+static int ecxkem_init(void *vecxctx, int operation, void *vecx, void *vauth,
+ ossl_unused const OSSL_PARAM params[])
+{
+ int rv;
+ PROV_ECX_CTX *ctx = (PROV_ECX_CTX *)vecxctx;
+ ECX_KEY *ecx = vecx;
+ ECX_KEY *auth = vauth;
+
+ if (!ossl_prov_is_running())
+ return 0;
+
+ if (!ecx_key_check(ecx, operation == EVP_PKEY_OP_DECAPSULATE))
+ return 0;
+ rv = recipient_key_set(ctx, ecx);
+ if (rv <= 0)
+ return rv;
+
+ if (auth != NULL) {
+ if (!ecx_match_params(auth, ctx->recipient_key)
+ || !ecx_key_check(auth, operation == EVP_PKEY_OP_ENCAPSULATE)
+ || !sender_authkey_set(ctx, auth))
+ return 0;
+ }
+
+ ctx->op = operation;
+ return ecxkem_set_ctx_params(vecxctx, params);
+}
+
+static int ecxkem_encapsulate_init(void *vecxctx, void *vecx,
+ const OSSL_PARAM params[])
+{
+ return ecxkem_init(vecxctx, EVP_PKEY_OP_ENCAPSULATE, vecx, NULL, params);
+}
+
+static int ecxkem_decapsulate_init(void *vecxctx, void *vecx,
+ const OSSL_PARAM params[])
+{
+ return ecxkem_init(vecxctx, EVP_PKEY_OP_DECAPSULATE, vecx, NULL, params);
+}
+
+static int ecxkem_auth_encapsulate_init(void *vctx, void *vecx, void *vauthpriv,
+ const OSSL_PARAM params[])
+{
+ return ecxkem_init(vctx, EVP_PKEY_OP_ENCAPSULATE, vecx, vauthpriv, params);
+}
+
+static int ecxkem_auth_decapsulate_init(void *vctx, void *vecx, void *vauthpub,
+ const OSSL_PARAM params[])
+{
+ return ecxkem_init(vctx, EVP_PKEY_OP_DECAPSULATE, vecx, vauthpub, params);
+}
+
+static int ecxkem_set_ctx_params(void *vctx, const OSSL_PARAM params[])
+{
+ PROV_ECX_CTX *ctx = (PROV_ECX_CTX *)vctx;
+ const OSSL_PARAM *p;
+ int mode;
+
+ if (ctx == NULL)
+ return 0;
+ if (ossl_param_is_empty(params))
+ return 1;
+
+ p = OSSL_PARAM_locate_const(params, OSSL_KEM_PARAM_IKME);
+ if (p != NULL) {
+ void *tmp = NULL;
+ size_t tmplen = 0;
+
+ if (p->data != NULL && p->data_size != 0) {
+ if (!OSSL_PARAM_get_octet_string(p, &tmp, 0, &tmplen))
+ return 0;
+ }
+ OPENSSL_clear_free(ctx->ikm, ctx->ikmlen);
+ ctx->ikm = tmp;
+ ctx->ikmlen = tmplen;
+ }
+ p = OSSL_PARAM_locate_const(params, OSSL_KEM_PARAM_OPERATION);
+ if (p != NULL) {
+ if (p->data_type != OSSL_PARAM_UTF8_STRING)
+ return 0;
+ mode = ossl_eckem_modename2id(p->data);
+ if (mode == KEM_MODE_UNDEFINED)
+ return 0;
+ ctx->mode = mode;
+ }
+ return 1;
+}
+
+static const OSSL_PARAM known_settable_ecxkem_ctx_params[] = {
+ OSSL_PARAM_utf8_string(OSSL_KEM_PARAM_OPERATION, NULL, 0),
+ OSSL_PARAM_octet_string(OSSL_KEM_PARAM_IKME, NULL, 0),
+ OSSL_PARAM_END
+};
+
+static const OSSL_PARAM *ecxkem_settable_ctx_params(ossl_unused void *vctx,
+ ossl_unused void *provctx)
+{
+ return known_settable_ecxkem_ctx_params;
+}
+
+/*
+ * See Section 4.1 DH-Based KEM (DHKEM) ExtractAndExpand
+ */
+static int dhkem_extract_and_expand(EVP_KDF_CTX *kctx,
+ unsigned char *okm, size_t okmlen,
+ uint16_t kemid,
+ const unsigned char *dhkm, size_t dhkmlen,
+ const unsigned char *kemctx,
+ size_t kemctxlen)
+{
+ uint8_t suiteid[2];
+ uint8_t prk[EVP_MAX_MD_SIZE];
+ size_t prklen = okmlen; /* Nh */
+ int ret;
+
+ if (prklen > sizeof(prk))
+ return 0;
+
+ suiteid[0] = (kemid >> 8) &0xff;
+ suiteid[1] = kemid & 0xff;
+
+ ret = ossl_hpke_labeled_extract(kctx, prk, prklen,
+ NULL, 0, LABEL_KEM, suiteid, sizeof(suiteid),
+ OSSL_DHKEM_LABEL_EAE_PRK, dhkm, dhkmlen)
+ && ossl_hpke_labeled_expand(kctx, okm, okmlen, prk, prklen,
+ LABEL_KEM, suiteid, sizeof(suiteid),
+ OSSL_DHKEM_LABEL_SHARED_SECRET,
+ kemctx, kemctxlen);
+ OPENSSL_cleanse(prk, prklen);
+ return ret;
+}
+
+/*
+ * See Section 7.1.3 DeriveKeyPair.
+ *
+ * This function is used by ecx keygen.
+ * (For this reason it does not use any of the state stored in PROV_ECX_CTX).
+ *
+ * Params:
+ * ecx An initialized ecx key.
+ * privout The buffer to store the generated private key into (it is assumed
+ * this is of length ecx->keylen).
+ * ikm buffer containing the input key material (seed). This must be non NULL.
+ * ikmlen size of the ikm buffer in bytes
+ * Returns:
+ * 1 if successful or 0 otherwise.
+ */
+int ossl_ecx_dhkem_derive_private(ECX_KEY *ecx, unsigned char *privout,
+ const unsigned char *ikm, size_t ikmlen)
+{
+ int ret = 0;
+ EVP_KDF_CTX *kdfctx = NULL;
+ unsigned char prk[EVP_MAX_MD_SIZE];
+ uint8_t suiteid[2];
+ const OSSL_HPKE_KEM_INFO *info = get_kem_info(ecx);
+
+ /* ikmlen should have a length of at least Nsk */
+ if (ikmlen < info->Nsk) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_INPUT_LENGTH,
+ "ikm length is :%zu, should be at least %zu",
+ ikmlen, info->Nsk);
+ goto err;
+ }
+
+ kdfctx = ossl_kdf_ctx_create("HKDF", info->mdname, ecx->libctx, ecx->propq);
+ if (kdfctx == NULL)
+ return 0;
+
+ suiteid[0] = info->kem_id / 256;
+ suiteid[1] = info->kem_id % 256;
+
+ if (!ossl_hpke_labeled_extract(kdfctx, prk, info->Nsecret,
+ NULL, 0, LABEL_KEM, suiteid, sizeof(suiteid),
+ OSSL_DHKEM_LABEL_DKP_PRK, ikm, ikmlen))
+ goto err;
+
+ if (!ossl_hpke_labeled_expand(kdfctx, privout, info->Nsk, prk, info->Nsecret,
+ LABEL_KEM, suiteid, sizeof(suiteid),
+ OSSL_DHKEM_LABEL_SK, NULL, 0))
+ goto err;
+ ret = 1;
+err:
+ OPENSSL_cleanse(prk, sizeof(prk));
+ EVP_KDF_CTX_free(kdfctx);
+ return ret;
+}
+
+/*
+ * Do a keygen operation without having to use EVP_PKEY.
+ * Params:
+ * ctx Context object
+ * ikm The seed material - if this is NULL, then a random seed is used.
+ * Returns:
+ * The generated ECX key, or NULL on failure.
+ */
+static ECX_KEY *derivekey(PROV_ECX_CTX *ctx,
+ const unsigned char *ikm, size_t ikmlen)
+{
+ int ok = 0;
+ ECX_KEY *key;
+ unsigned char *privkey;
+ unsigned char *seed = (unsigned char *)ikm;
+ size_t seedlen = ikmlen;
+ unsigned char tmpbuf[OSSL_HPKE_MAX_PRIVATE];
+ const OSSL_HPKE_KEM_INFO *info = ctx->info;
+
+ key = ossl_ecx_key_new(ctx->libctx, ctx->recipient_key->type, 0, ctx->propq);
+ if (key == NULL)
+ return NULL;
+ privkey = ossl_ecx_key_allocate_privkey(key);
+ if (privkey == NULL)
+ goto err;
+
+ /* Generate a random seed if there is no input ikm */
+ if (seed == NULL || seedlen == 0) {
+ if (info->Nsk > sizeof(tmpbuf))
+ goto err;
+ if (RAND_priv_bytes_ex(ctx->libctx, tmpbuf, info->Nsk, 0) <= 0)
+ goto err;
+ seed = tmpbuf;
+ seedlen = info->Nsk;
+ }
+ if (!ossl_ecx_dhkem_derive_private(key, privkey, seed, seedlen))
+ goto err;
+ if (!ossl_ecx_public_from_private(key))
+ goto err;
+ key->haspubkey = 1;
+ ok = 1;
+err:
+ if (!ok) {
+ ossl_ecx_key_free(key);
+ key = NULL;
+ }
+ if (seed != ikm)
+ OPENSSL_cleanse(seed, seedlen);
+ return key;
+}
+
+/*
+ * Do an ecxdh key exchange.
+ * dhkm = DH(sender, peer)
+ *
+ * NOTE: Instead of using EVP_PKEY_derive() API's, we use ECX_KEY operations
+ * to avoid messy conversions back to EVP_PKEY.
+ *
+ * Returns the size of the secret if successful, or 0 otherwise,
+ */
+static int generate_ecxdhkm(const ECX_KEY *sender, const ECX_KEY *peer,
+ unsigned char *out, size_t maxout,
+ unsigned int secretsz)
+{
+ size_t len = 0;
+
+ /* NOTE: ossl_ecx_compute_key checks for shared secret being all zeros */
+ return ossl_ecx_compute_key((ECX_KEY *)peer, (ECX_KEY *)sender,
+ sender->keylen, out, &len, maxout);
+}
+
+/*
+ * Derive a secret using ECXDH (code is shared by the encap and decap)
+ *
+ * dhkm = Concat(ecxdh(privkey1, peerkey1), ecdh(privkey2, peerkey2)
+ * kemctx = Concat(sender_pub, recipient_pub, ctx->sender_authkey)
+ * secret = dhkem_extract_and_expand(kemid, dhkm, kemctx);
+ *
+ * Params:
+ * ctx Object that contains algorithm state and constants.
+ * secret The returned secret (with a length ctx->alg->secretlen bytes).
+ * privkey1 A private key used for ECXDH key derivation.
+ * peerkey1 A public key used for ECXDH key derivation with privkey1
+ * privkey2 A optional private key used for a second ECXDH key derivation.
+ * It can be NULL.
+ * peerkey2 A optional public key used for a second ECXDH key derivation
+ * with privkey2,. It can be NULL.
+ * sender_pub The senders public key in encoded form.
+ * recipient_pub The recipients public key in encoded form.
+ * Notes:
+ * The second ecdh() is only used for the HPKE auth modes when both privkey2
+ * and peerkey2 are non NULL (i.e. ctx->sender_authkey is not NULL).
+ */
+static int derive_secret(PROV_ECX_CTX *ctx, unsigned char *secret,
+ const ECX_KEY *privkey1, const ECX_KEY *peerkey1,
+ const ECX_KEY *privkey2, const ECX_KEY *peerkey2,
+ const unsigned char *sender_pub,
+ const unsigned char *recipient_pub)
+{
+ int ret = 0;
+ EVP_KDF_CTX *kdfctx = NULL;
+ unsigned char *sender_authpub = NULL;
+ unsigned char dhkm[MAX_ECX_KEYLEN * 2];
+ unsigned char kemctx[MAX_ECX_KEYLEN * 3];
+ size_t kemctxlen = 0, dhkmlen = 0;
+ const OSSL_HPKE_KEM_INFO *info = ctx->info;
+ int auth = ctx->sender_authkey != NULL;
+ size_t encodedkeylen = info->Npk;
+
+ if (!generate_ecxdhkm(privkey1, peerkey1, dhkm, sizeof(dhkm), encodedkeylen))
+ goto err;
+ dhkmlen = encodedkeylen;
+
+ /* Concat the optional second ECXDH (used for Auth) */
+ if (auth) {
+ if (!generate_ecxdhkm(privkey2, peerkey2,
+ dhkm + dhkmlen, sizeof(dhkm) - dhkmlen,
+ encodedkeylen))
+ goto err;
+ /* Get the public key of the auth sender in encoded form */
+ sender_authpub = ecx_pubkey(ctx->sender_authkey);
+ if (sender_authpub == NULL)
+ goto err;
+ dhkmlen += encodedkeylen;
+ }
+ kemctxlen = encodedkeylen + dhkmlen;
+ if (kemctxlen > sizeof(kemctx))
+ goto err;
+
+ /* kemctx is the concat of both sides encoded public key */
+ memcpy(kemctx, sender_pub, encodedkeylen);
+ memcpy(kemctx + encodedkeylen, recipient_pub, encodedkeylen);
+ if (auth)
+ memcpy(kemctx + 2 * encodedkeylen, sender_authpub, encodedkeylen);
+ kdfctx = ossl_kdf_ctx_create(ctx->kdfname, info->mdname,
+ ctx->libctx, ctx->propq);
+ if (kdfctx == NULL)
+ goto err;
+ if (!dhkem_extract_and_expand(kdfctx, secret, info->Nsecret,
+ info->kem_id, dhkm, dhkmlen,
+ kemctx, kemctxlen))
+ goto err;
+ ret = 1;
+err:
+ OPENSSL_cleanse(dhkm, dhkmlen);
+ EVP_KDF_CTX_free(kdfctx);
+ return ret;
+}
+
+/*
+ * Do a DHKEM encapsulate operation.
+ *
+ * See Section 4.1 Encap() and AuthEncap()
+ *
+ * Params:
+ * ctx A context object holding the recipients public key and the
+ * optional senders auth private key.
+ * enc A buffer to return the senders ephemeral public key.
+ * Setting this to NULL allows the enclen and secretlen to return
+ * values, without calculating the secret.
+ * enclen Passes in the max size of the enc buffer and returns the
+ * encoded public key length.
+ * secret A buffer to return the calculated shared secret.
+ * secretlen Passes in the max size of the secret buffer and returns the
+ * secret length.
+ * Returns: 1 on success or 0 otherwise.
+ */
+static int dhkem_encap(PROV_ECX_CTX *ctx,
+ unsigned char *enc, size_t *enclen,
+ unsigned char *secret, size_t *secretlen)
+{
+ int ret = 0;
+ ECX_KEY *sender_ephemkey = NULL;
+ unsigned char *sender_ephempub, *recipient_pub;
+ const OSSL_HPKE_KEM_INFO *info = ctx->info;
+
+ if (enc == NULL) {
+ if (enclen == NULL && secretlen == NULL)
+ return 0;
+ if (enclen != NULL)
+ *enclen = info->Nenc;
+ if (secretlen != NULL)
+ *secretlen = info->Nsecret;
+ return 1;
+ }
+
+ if (*secretlen < info->Nsecret) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*secretlen too small");
+ return 0;
+ }
+ if (*enclen < info->Nenc) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*enclen too small");
+ return 0;
+ }
+
+ /* Create an ephemeral key */
+ sender_ephemkey = derivekey(ctx, ctx->ikm, ctx->ikmlen);
+
+ sender_ephempub = ecx_pubkey(sender_ephemkey);
+ recipient_pub = ecx_pubkey(ctx->recipient_key);
+ if (sender_ephempub == NULL || recipient_pub == NULL)
+ goto err;
+
+ if (!derive_secret(ctx, secret,
+ sender_ephemkey, ctx->recipient_key,
+ ctx->sender_authkey, ctx->recipient_key,
+ sender_ephempub, recipient_pub))
+ goto err;
+
+ /* Return the public part of the ephemeral key */
+ memcpy(enc, sender_ephempub, info->Nenc);
+ *enclen = info->Nenc;
+ *secretlen = info->Nsecret;
+ ret = 1;
+err:
+ ossl_ecx_key_free(sender_ephemkey);
+ return ret;
+}
+
+/*
+ * Do a DHKEM decapsulate operation.
+ * See Section 4.1 Decap() and Auth Decap()
+ *
+ * Params:
+ * ctx A context object holding the recipients private key and the
+ * optional senders auth public key.
+ * secret A buffer to return the calculated shared secret. Setting this to
+ * NULL can be used to return the secretlen.
+ * secretlen Passes in the max size of the secret buffer and returns the
+ * secret length.
+ * enc A buffer containing the senders ephemeral public key that was returned
+ * from dhkem_encap().
+ * enclen The length in bytes of enc.
+ * Returns: 1 If the shared secret is returned or 0 on error.
+ */
+static int dhkem_decap(PROV_ECX_CTX *ctx,
+ unsigned char *secret, size_t *secretlen,
+ const unsigned char *enc, size_t enclen)
+{
+ int ret = 0;
+ ECX_KEY *recipient_privkey = ctx->recipient_key;
+ ECX_KEY *sender_ephempubkey = NULL;
+ const OSSL_HPKE_KEM_INFO *info = ctx->info;
+ unsigned char *recipient_pub;
+
+ if (secret == NULL) {
+ *secretlen = info->Nsecret;
+ return 1;
+ }
+ if (*secretlen < info->Nsecret) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*secretlen too small");
+ return 0;
+ }
+ if (enclen != info->Nenc) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY, "Invalid enc public key");
+ return 0;
+ }
+
+ /* Get the public part of the ephemeral key created by encap */
+ sender_ephempubkey = ecxkey_pubfromdata(ctx, enc, enclen);
+ if (sender_ephempubkey == NULL)
+ goto err;
+
+ recipient_pub = ecx_pubkey(recipient_privkey);
+ if (recipient_pub == NULL)
+ goto err;
+
+ if (!derive_secret(ctx, secret,
+ ctx->recipient_key, sender_ephempubkey,
+ ctx->recipient_key, ctx->sender_authkey,
+ enc, recipient_pub))
+ goto err;
+
+ *secretlen = info->Nsecret;
+ ret = 1;
+err:
+ ossl_ecx_key_free(sender_ephempubkey);
+ return ret;
+}
+
+static int ecxkem_encapsulate(void *vctx, unsigned char *out, size_t *outlen,
+ unsigned char *secret, size_t *secretlen)
+{
+ PROV_ECX_CTX *ctx = (PROV_ECX_CTX *)vctx;
+
+ switch (ctx->mode) {
+ case KEM_MODE_DHKEM:
+ return dhkem_encap(ctx, out, outlen, secret, secretlen);
+ default:
+ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_MODE);
+ return -2;
+ }
+}
+
+static int ecxkem_decapsulate(void *vctx, unsigned char *out, size_t *outlen,
+ const unsigned char *in, size_t inlen)
+{
+ PROV_ECX_CTX *ctx = (PROV_ECX_CTX *)vctx;
+
+ switch (ctx->mode) {
+ case KEM_MODE_DHKEM:
+ return dhkem_decap(vctx, out, outlen, in, inlen);
+ default:
+ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_MODE);
+ return -2;
+ }
+}
+
+const OSSL_DISPATCH ossl_ecx_asym_kem_functions[] = {
+ { OSSL_FUNC_KEM_NEWCTX, (void (*)(void))ecxkem_newctx },
+ { OSSL_FUNC_KEM_ENCAPSULATE_INIT,
+ (void (*)(void))ecxkem_encapsulate_init },
+ { OSSL_FUNC_KEM_ENCAPSULATE, (void (*)(void))ecxkem_encapsulate },
+ { OSSL_FUNC_KEM_DECAPSULATE_INIT,
+ (void (*)(void))ecxkem_decapsulate_init },
+ { OSSL_FUNC_KEM_DECAPSULATE, (void (*)(void))ecxkem_decapsulate },
+ { OSSL_FUNC_KEM_FREECTX, (void (*)(void))ecxkem_freectx },
+ { OSSL_FUNC_KEM_SET_CTX_PARAMS,
+ (void (*)(void))ecxkem_set_ctx_params },
+ { OSSL_FUNC_KEM_SETTABLE_CTX_PARAMS,
+ (void (*)(void))ecxkem_settable_ctx_params },
+ { OSSL_FUNC_KEM_AUTH_ENCAPSULATE_INIT,
+ (void (*)(void))ecxkem_auth_encapsulate_init },
+ { OSSL_FUNC_KEM_AUTH_DECAPSULATE_INIT,
+ (void (*)(void))ecxkem_auth_decapsulate_init },
+ OSSL_DISPATCH_END
+};
diff --git a/crypto/openssl/providers/implementations/kem/kem_util.c b/crypto/openssl/providers/implementations/kem/kem_util.c
new file mode 100644
index 000000000000..1fd52e1c2d52
--- /dev/null
+++ b/crypto/openssl/providers/implementations/kem/kem_util.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <string.h> /* for memcpy() */
+#include <openssl/core_names.h>
+#include <openssl/crypto.h>
+#include "eckem.h"
+
+typedef struct {
+ unsigned int id;
+ const char *mode;
+} KEM_MODE;
+
+static const KEM_MODE eckem_modename_id_map[] = {
+ { KEM_MODE_DHKEM, OSSL_KEM_PARAM_OPERATION_DHKEM },
+ { 0, NULL }
+};
+
+int ossl_eckem_modename2id(const char *name)
+{
+ size_t i;
+
+ if (name == NULL)
+ return KEM_MODE_UNDEFINED;
+
+ for (i = 0; eckem_modename_id_map[i].mode != NULL; ++i) {
+ if (OPENSSL_strcasecmp(name, eckem_modename_id_map[i].mode) == 0)
+ return eckem_modename_id_map[i].id;
+ }
+ return KEM_MODE_UNDEFINED;
+}
diff --git a/crypto/openssl/providers/implementations/kem/ml_kem_kem.c b/crypto/openssl/providers/implementations/kem/ml_kem_kem.c
new file mode 100644
index 000000000000..27aa3b819836
--- /dev/null
+++ b/crypto/openssl/providers/implementations/kem/ml_kem_kem.c
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <string.h>
+#include <openssl/crypto.h>
+#include <openssl/evp.h>
+#include <openssl/core_dispatch.h>
+#include <openssl/core_names.h>
+#include <openssl/params.h>
+#include <openssl/err.h>
+#include <openssl/proverr.h>
+#include "crypto/ml_kem.h"
+#include "prov/provider_ctx.h"
+#include "prov/implementations.h"
+#include "prov/securitycheck.h"
+#include "prov/providercommon.h"
+
+static OSSL_FUNC_kem_newctx_fn ml_kem_newctx;
+static OSSL_FUNC_kem_freectx_fn ml_kem_freectx;
+static OSSL_FUNC_kem_encapsulate_init_fn ml_kem_encapsulate_init;
+static OSSL_FUNC_kem_encapsulate_fn ml_kem_encapsulate;
+static OSSL_FUNC_kem_decapsulate_init_fn ml_kem_decapsulate_init;
+static OSSL_FUNC_kem_decapsulate_fn ml_kem_decapsulate;
+static OSSL_FUNC_kem_set_ctx_params_fn ml_kem_set_ctx_params;
+static OSSL_FUNC_kem_settable_ctx_params_fn ml_kem_settable_ctx_params;
+
+typedef struct {
+ ML_KEM_KEY *key;
+ uint8_t entropy_buf[ML_KEM_RANDOM_BYTES];
+ uint8_t *entropy;
+ int op;
+} PROV_ML_KEM_CTX;
+
+static void *ml_kem_newctx(void *provctx)
+{
+ PROV_ML_KEM_CTX *ctx;
+
+ if ((ctx = OPENSSL_malloc(sizeof(*ctx))) == NULL)
+ return NULL;
+
+ ctx->key = NULL;
+ ctx->entropy = NULL;
+ ctx->op = 0;
+ return ctx;
+}
+
+static void ml_kem_freectx(void *vctx)
+{
+ PROV_ML_KEM_CTX *ctx = vctx;
+
+ if (ctx->entropy != NULL)
+ OPENSSL_cleanse(ctx->entropy, ML_KEM_RANDOM_BYTES);
+ OPENSSL_free(ctx);
+}
+
+static int ml_kem_init(void *vctx, int op, void *key,
+ const OSSL_PARAM params[])
+{
+ PROV_ML_KEM_CTX *ctx = vctx;
+
+ if (!ossl_prov_is_running())
+ return 0;
+ ctx->key = key;
+ ctx->op = op;
+ return ml_kem_set_ctx_params(vctx, params);
+}
+
+static int ml_kem_encapsulate_init(void *vctx, void *vkey,
+ const OSSL_PARAM params[])
+{
+ ML_KEM_KEY *key = vkey;
+
+ if (!ossl_ml_kem_have_pubkey(key)) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+ return 0;
+ }
+ return ml_kem_init(vctx, EVP_PKEY_OP_ENCAPSULATE, key, params);
+}
+
+static int ml_kem_decapsulate_init(void *vctx, void *vkey,
+ const OSSL_PARAM params[])
+{
+ ML_KEM_KEY *key = vkey;
+
+ if (!ossl_ml_kem_have_prvkey(key)) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+ return 0;
+ }
+ return ml_kem_init(vctx, EVP_PKEY_OP_DECAPSULATE, key, params);
+}
+
+static int ml_kem_set_ctx_params(void *vctx, const OSSL_PARAM params[])
+{
+ PROV_ML_KEM_CTX *ctx = vctx;
+ const OSSL_PARAM *p;
+
+ if (ctx == NULL)
+ return 0;
+
+ if (ctx->op == EVP_PKEY_OP_DECAPSULATE && ctx->entropy != NULL) {
+ /* Decapsulation is deterministic */
+ OPENSSL_cleanse(ctx->entropy, ML_KEM_RANDOM_BYTES);
+ ctx->entropy = NULL;
+ }
+
+ if (ossl_param_is_empty(params))
+ return 1;
+
+ /* Encapsulation ephemeral input key material "ikmE" */
+ if (ctx->op == EVP_PKEY_OP_ENCAPSULATE
+ && (p = OSSL_PARAM_locate_const(params, OSSL_KEM_PARAM_IKME)) != NULL) {
+ size_t len = ML_KEM_RANDOM_BYTES;
+
+ ctx->entropy = ctx->entropy_buf;
+ if (OSSL_PARAM_get_octet_string(p, (void **)&ctx->entropy,
+ len, &len)
+ && len == ML_KEM_RANDOM_BYTES)
+ return 1;
+
+ /* Possibly, but much less likely wrong type */
+ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_SEED_LENGTH);
+ ctx->entropy = NULL;
+ return 0;
+ }
+
+ return 1;
+}
+
+static const OSSL_PARAM *ml_kem_settable_ctx_params(ossl_unused void *vctx,
+ ossl_unused void *provctx)
+{
+ static const OSSL_PARAM params[] = {
+ OSSL_PARAM_octet_string(OSSL_KEM_PARAM_IKME, NULL, 0),
+ OSSL_PARAM_END
+ };
+
+ return params;
+}
+
+static int ml_kem_encapsulate(void *vctx, unsigned char *ctext, size_t *clen,
+ unsigned char *shsec, size_t *slen)
+{
+ PROV_ML_KEM_CTX *ctx = vctx;
+ ML_KEM_KEY *key = ctx->key;
+ const ML_KEM_VINFO *v;
+ size_t encap_clen;
+ size_t encap_slen;
+ int ret = 0;
+
+ if (!ossl_ml_kem_have_pubkey(key)) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+ goto end;
+ }
+ v = ossl_ml_kem_key_vinfo(key);
+ encap_clen = v->ctext_bytes;
+ encap_slen = ML_KEM_SHARED_SECRET_BYTES;
+
+ if (ctext == NULL) {
+ if (clen == NULL && slen == NULL)
+ return 0;
+ if (clen != NULL)
+ *clen = encap_clen;
+ if (slen != NULL)
+ *slen = encap_slen;
+ return 1;
+ }
+ if (shsec == NULL) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_OUTPUT_BUFFER,
+ "NULL shared-secret buffer");
+ goto end;
+ }
+
+ if (clen == NULL) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER,
+ "null ciphertext input/output length pointer");
+ goto end;
+ } else if (*clen < encap_clen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
+ "ciphertext buffer too small");
+ goto end;
+ } else {
+ *clen = encap_clen;
+ }
+
+ if (slen == NULL) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER,
+ "null shared secret input/output length pointer");
+ goto end;
+ } else if (*slen < encap_slen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
+ "shared-secret buffer too small");
+ goto end;
+ } else {
+ *slen = encap_slen;
+ }
+
+ if (ctx->entropy != NULL)
+ ret = ossl_ml_kem_encap_seed(ctext, encap_clen, shsec, encap_slen,
+ ctx->entropy, ML_KEM_RANDOM_BYTES, key);
+ else
+ ret = ossl_ml_kem_encap_rand(ctext, encap_clen, shsec, encap_slen, key);
+
+ end:
+ /*
+ * One shot entropy, each encapsulate call must either provide a new
+ * "ikmE", or else will use a random value. If a caller sets an explicit
+ * ikmE once for testing, and later performs multiple encapsulations
+ * without again calling encapsulate_init(), these should not share the
+ * original entropy.
+ */
+ if (ctx->entropy != NULL) {
+ OPENSSL_cleanse(ctx->entropy, ML_KEM_RANDOM_BYTES);
+ ctx->entropy = NULL;
+ }
+ return ret;
+}
+
+static int ml_kem_decapsulate(void *vctx, uint8_t *shsec, size_t *slen,
+ const uint8_t *ctext, size_t clen)
+{
+ PROV_ML_KEM_CTX *ctx = vctx;
+ ML_KEM_KEY *key = ctx->key;
+ size_t decap_slen = ML_KEM_SHARED_SECRET_BYTES;
+
+ if (!ossl_ml_kem_have_prvkey(key)) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+ return 0;
+ }
+
+ if (shsec == NULL) {
+ if (slen == NULL)
+ return 0;
+ *slen = ML_KEM_SHARED_SECRET_BYTES;
+ return 1;
+ }
+
+ /* For now tolerate newly-deprecated NULL length pointers. */
+ if (slen == NULL) {
+ slen = &decap_slen;
+ } else if (*slen < decap_slen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
+ "shared-secret buffer too small");
+ return 0;
+ } else {
+ *slen = decap_slen;
+ }
+
+ /* ML-KEM decap handles incorrect ciphertext lengths internally */
+ return ossl_ml_kem_decap(shsec, decap_slen, ctext, clen, key);
+}
+
+const OSSL_DISPATCH ossl_ml_kem_asym_kem_functions[] = {
+ { OSSL_FUNC_KEM_NEWCTX, (OSSL_FUNC) ml_kem_newctx },
+ { OSSL_FUNC_KEM_ENCAPSULATE_INIT, (OSSL_FUNC) ml_kem_encapsulate_init },
+ { OSSL_FUNC_KEM_ENCAPSULATE, (OSSL_FUNC) ml_kem_encapsulate },
+ { OSSL_FUNC_KEM_DECAPSULATE_INIT, (OSSL_FUNC) ml_kem_decapsulate_init },
+ { OSSL_FUNC_KEM_DECAPSULATE, (OSSL_FUNC) ml_kem_decapsulate },
+ { OSSL_FUNC_KEM_FREECTX, (OSSL_FUNC) ml_kem_freectx },
+ { OSSL_FUNC_KEM_SET_CTX_PARAMS, (OSSL_FUNC) ml_kem_set_ctx_params },
+ { OSSL_FUNC_KEM_SETTABLE_CTX_PARAMS, (OSSL_FUNC) ml_kem_settable_ctx_params },
+ OSSL_DISPATCH_END
+};
diff --git a/crypto/openssl/providers/implementations/kem/mlx_kem.c b/crypto/openssl/providers/implementations/kem/mlx_kem.c
new file mode 100644
index 000000000000..197c345d85cb
--- /dev/null
+++ b/crypto/openssl/providers/implementations/kem/mlx_kem.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/core_dispatch.h>
+#include <openssl/core_names.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/params.h>
+#include <openssl/proverr.h>
+#include <openssl/rand.h>
+#include "prov/implementations.h"
+#include "prov/mlx_kem.h"
+#include "prov/provider_ctx.h"
+#include "prov/providercommon.h"
+
+static OSSL_FUNC_kem_newctx_fn mlx_kem_newctx;
+static OSSL_FUNC_kem_freectx_fn mlx_kem_freectx;
+static OSSL_FUNC_kem_encapsulate_init_fn mlx_kem_encapsulate_init;
+static OSSL_FUNC_kem_encapsulate_fn mlx_kem_encapsulate;
+static OSSL_FUNC_kem_decapsulate_init_fn mlx_kem_decapsulate_init;
+static OSSL_FUNC_kem_decapsulate_fn mlx_kem_decapsulate;
+static OSSL_FUNC_kem_set_ctx_params_fn mlx_kem_set_ctx_params;
+static OSSL_FUNC_kem_settable_ctx_params_fn mlx_kem_settable_ctx_params;
+
+typedef struct {
+ OSSL_LIB_CTX *libctx;
+ MLX_KEY *key;
+ int op;
+} PROV_MLX_KEM_CTX;
+
+static void *mlx_kem_newctx(void *provctx)
+{
+ PROV_MLX_KEM_CTX *ctx;
+
+ if ((ctx = OPENSSL_malloc(sizeof(*ctx))) == NULL)
+ return NULL;
+
+ ctx->libctx = PROV_LIBCTX_OF(provctx);
+ ctx->key = NULL;
+ ctx->op = 0;
+ return ctx;
+}
+
+static void mlx_kem_freectx(void *vctx)
+{
+ OPENSSL_free(vctx);
+}
+
+static int mlx_kem_init(void *vctx, int op, void *key,
+ ossl_unused const OSSL_PARAM params[])
+{
+ PROV_MLX_KEM_CTX *ctx = vctx;
+
+ if (!ossl_prov_is_running())
+ return 0;
+ ctx->key = key;
+ ctx->op = op;
+ return 1;
+}
+
+static int
+mlx_kem_encapsulate_init(void *vctx, void *vkey, const OSSL_PARAM params[])
+{
+ MLX_KEY *key = vkey;
+
+ if (!mlx_kem_have_pubkey(key)) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+ return 0;
+ }
+ return mlx_kem_init(vctx, EVP_PKEY_OP_ENCAPSULATE, key, params);
+}
+
+static int
+mlx_kem_decapsulate_init(void *vctx, void *vkey, const OSSL_PARAM params[])
+{
+ MLX_KEY *key = vkey;
+
+ if (!mlx_kem_have_prvkey(key)) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+ return 0;
+ }
+ return mlx_kem_init(vctx, EVP_PKEY_OP_DECAPSULATE, key, params);
+}
+
+static const OSSL_PARAM *mlx_kem_settable_ctx_params(ossl_unused void *vctx,
+ ossl_unused void *provctx)
+{
+ static const OSSL_PARAM params[] = { OSSL_PARAM_END };
+
+ return params;
+}
+
+static int
+mlx_kem_set_ctx_params(void *vctx, const OSSL_PARAM params[])
+{
+ return 1;
+}
+
+static int mlx_kem_encapsulate(void *vctx, unsigned char *ctext, size_t *clen,
+ unsigned char *shsec, size_t *slen)
+{
+ MLX_KEY *key = ((PROV_MLX_KEM_CTX *) vctx)->key;
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *xkey = NULL;
+ size_t encap_clen;
+ size_t encap_slen;
+ uint8_t *cbuf;
+ uint8_t *sbuf;
+ int ml_kem_slot = key->xinfo->ml_kem_slot;
+ int ret = 0;
+
+ if (!mlx_kem_have_pubkey(key)) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+ goto end;
+ }
+ encap_clen = key->minfo->ctext_bytes + key->xinfo->pubkey_bytes;
+ encap_slen = ML_KEM_SHARED_SECRET_BYTES + key->xinfo->shsec_bytes;
+
+ if (ctext == NULL) {
+ if (clen == NULL && slen == NULL)
+ return 0;
+ if (clen != NULL)
+ *clen = encap_clen;
+ if (slen != NULL)
+ *slen = encap_slen;
+ return 1;
+ }
+ if (shsec == NULL) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_OUTPUT_BUFFER,
+ "null shared-secret output buffer");
+ return 0;
+ }
+
+ if (clen == NULL) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER,
+ "null ciphertext input/output length pointer");
+ return 0;
+ } else if (*clen < encap_clen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
+ "ciphertext buffer too small");
+ return 0;
+ } else {
+ *clen = encap_clen;
+ }
+
+ if (slen == NULL) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER,
+ "null shared secret input/output length pointer");
+ return 0;
+ } else if (*slen < encap_slen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
+ "shared-secret buffer too small");
+ return 0;
+ } else {
+ *slen = encap_slen;
+ }
+
+ /* ML-KEM encapsulation */
+ encap_clen = key->minfo->ctext_bytes;
+ encap_slen = ML_KEM_SHARED_SECRET_BYTES;
+ cbuf = ctext + ml_kem_slot * key->xinfo->pubkey_bytes;
+ sbuf = shsec + ml_kem_slot * key->xinfo->shsec_bytes;
+ ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->mkey, key->propq);
+ if (ctx == NULL
+ || EVP_PKEY_encapsulate_init(ctx, NULL) <= 0
+ || EVP_PKEY_encapsulate(ctx, cbuf, &encap_clen, sbuf, &encap_slen) <= 0)
+ goto end;
+ if (encap_clen != key->minfo->ctext_bytes) {
+ ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+ "unexpected %s ciphertext output size: %lu",
+ key->minfo->algorithm_name, (unsigned long) encap_clen);
+ goto end;
+ }
+ if (encap_slen != ML_KEM_SHARED_SECRET_BYTES) {
+ ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+ "unexpected %s shared secret output size: %lu",
+ key->minfo->algorithm_name, (unsigned long) encap_slen);
+ goto end;
+ }
+ EVP_PKEY_CTX_free(ctx);
+
+ /*-
+ * ECDHE encapsulation
+ *
+ * Generate own ephemeral private key and add its public key to ctext.
+ *
+ * Note, we could support a settable parameter that sets an extant ECDH
+ * keypair as the keys to use in encap, making it possible to reuse the
+ * same (TLS client) ECDHE keypair for both the classical EC keyshare and a
+ * corresponding ECDHE + ML-KEM keypair. But the TLS layer would then need
+ * know that this is a hybrid, and that it can partly reuse the same keys
+ * as another group for which a keyshare will be sent. Deferred until we
+ * support generating multiple keyshares, there's a workable keyshare
+ * prediction specification, and the optimisation is justified.
+ */
+ cbuf = ctext + (1 - ml_kem_slot) * key->minfo->ctext_bytes;
+ encap_clen = key->xinfo->pubkey_bytes;
+ ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->xkey, key->propq);
+ if (ctx == NULL
+ || EVP_PKEY_keygen_init(ctx) <= 0
+ || EVP_PKEY_keygen(ctx, &xkey) <= 0
+ || EVP_PKEY_get_octet_string_param(xkey, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY,
+ cbuf, encap_clen, &encap_clen) <= 0)
+ goto end;
+ if (encap_clen != key->xinfo->pubkey_bytes) {
+ ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+ "unexpected %s public key output size: %lu",
+ key->xinfo->algorithm_name, (unsigned long) encap_clen);
+ goto end;
+ }
+ EVP_PKEY_CTX_free(ctx);
+
+ /* Derive the ECDH shared secret */
+ encap_slen = key->xinfo->shsec_bytes;
+ sbuf = shsec + (1 - ml_kem_slot) * ML_KEM_SHARED_SECRET_BYTES;
+ ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, xkey, key->propq);
+ if (ctx == NULL
+ || EVP_PKEY_derive_init(ctx) <= 0
+ || EVP_PKEY_derive_set_peer(ctx, key->xkey) <= 0
+ || EVP_PKEY_derive(ctx, sbuf, &encap_slen) <= 0)
+ goto end;
+ if (encap_slen != key->xinfo->shsec_bytes) {
+ ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+ "unexpected %s shared secret output size: %lu",
+ key->xinfo->algorithm_name, (unsigned long) encap_slen);
+ goto end;
+ }
+
+ ret = 1;
+ end:
+ EVP_PKEY_free(xkey);
+ EVP_PKEY_CTX_free(ctx);
+ return ret;
+}
+
+static int mlx_kem_decapsulate(void *vctx, uint8_t *shsec, size_t *slen,
+ const uint8_t *ctext, size_t clen)
+{
+ MLX_KEY *key = ((PROV_MLX_KEM_CTX *) vctx)->key;
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *xkey = NULL;
+ const uint8_t *cbuf;
+ uint8_t *sbuf;
+ size_t decap_slen = ML_KEM_SHARED_SECRET_BYTES + key->xinfo->shsec_bytes;
+ size_t decap_clen = key->minfo->ctext_bytes + key->xinfo->pubkey_bytes;
+ int ml_kem_slot = key->xinfo->ml_kem_slot;
+ int ret = 0;
+
+ if (!mlx_kem_have_prvkey(key)) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+ return 0;
+ }
+
+ if (shsec == NULL) {
+ if (slen == NULL)
+ return 0;
+ *slen = decap_slen;
+ return 1;
+ }
+
+ /* For now tolerate newly-deprecated NULL length pointers. */
+ if (slen == NULL) {
+ slen = &decap_slen;
+ } else if (*slen < decap_slen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
+ "shared-secret buffer too small");
+ return 0;
+ } else {
+ *slen = decap_slen;
+ }
+ if (clen != decap_clen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_WRONG_CIPHERTEXT_SIZE,
+ "wrong decapsulation input ciphertext size: %lu",
+ (unsigned long) clen);
+ return 0;
+ }
+
+ /* ML-KEM decapsulation */
+ decap_clen = key->minfo->ctext_bytes;
+ decap_slen = ML_KEM_SHARED_SECRET_BYTES;
+ cbuf = ctext + ml_kem_slot * key->xinfo->pubkey_bytes;
+ sbuf = shsec + ml_kem_slot * key->xinfo->shsec_bytes;
+ ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->mkey, key->propq);
+ if (ctx == NULL
+ || EVP_PKEY_decapsulate_init(ctx, NULL) <= 0
+ || EVP_PKEY_decapsulate(ctx, sbuf, &decap_slen, cbuf, decap_clen) <= 0)
+ goto end;
+ if (decap_slen != ML_KEM_SHARED_SECRET_BYTES) {
+ ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+ "unexpected %s shared secret output size: %lu",
+ key->minfo->algorithm_name, (unsigned long) decap_slen);
+ goto end;
+ }
+ EVP_PKEY_CTX_free(ctx);
+
+ /* ECDH decapsulation */
+ decap_clen = key->xinfo->pubkey_bytes;
+ decap_slen = key->xinfo->shsec_bytes;
+ cbuf = ctext + (1 - ml_kem_slot) * key->minfo->ctext_bytes;
+ sbuf = shsec + (1 - ml_kem_slot) * ML_KEM_SHARED_SECRET_BYTES;
+ ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->xkey, key->propq);
+ if (ctx == NULL
+ || (xkey = EVP_PKEY_new()) == NULL
+ || EVP_PKEY_copy_parameters(xkey, key->xkey) <= 0
+ || EVP_PKEY_set1_encoded_public_key(xkey, cbuf, decap_clen) <= 0
+ || EVP_PKEY_derive_init(ctx) <= 0
+ || EVP_PKEY_derive_set_peer(ctx, xkey) <= 0
+ || EVP_PKEY_derive(ctx, sbuf, &decap_slen) <= 0)
+ goto end;
+ if (decap_slen != key->xinfo->shsec_bytes) {
+ ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+ "unexpected %s shared secret output size: %lu",
+ key->xinfo->algorithm_name, (unsigned long) decap_slen);
+ goto end;
+ }
+
+ ret = 1;
+ end:
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(xkey);
+ return ret;
+}
+
+const OSSL_DISPATCH ossl_mlx_kem_asym_kem_functions[] = {
+ { OSSL_FUNC_KEM_NEWCTX, (OSSL_FUNC) mlx_kem_newctx },
+ { OSSL_FUNC_KEM_ENCAPSULATE_INIT, (OSSL_FUNC) mlx_kem_encapsulate_init },
+ { OSSL_FUNC_KEM_ENCAPSULATE, (OSSL_FUNC) mlx_kem_encapsulate },
+ { OSSL_FUNC_KEM_DECAPSULATE_INIT, (OSSL_FUNC) mlx_kem_decapsulate_init },
+ { OSSL_FUNC_KEM_DECAPSULATE, (OSSL_FUNC) mlx_kem_decapsulate },
+ { OSSL_FUNC_KEM_FREECTX, (OSSL_FUNC) mlx_kem_freectx },
+ { OSSL_FUNC_KEM_SET_CTX_PARAMS, (OSSL_FUNC) mlx_kem_set_ctx_params },
+ { OSSL_FUNC_KEM_SETTABLE_CTX_PARAMS, (OSSL_FUNC) mlx_kem_settable_ctx_params },
+ OSSL_DISPATCH_END
+};
diff --git a/crypto/openssl/providers/implementations/kem/rsa_kem.c b/crypto/openssl/providers/implementations/kem/rsa_kem.c
new file mode 100644
index 000000000000..7494dcc0107f
--- /dev/null
+++ b/crypto/openssl/providers/implementations/kem/rsa_kem.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2020-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+/*
+ * RSA low level APIs are deprecated for public use, but still ok for
+ * internal use.
+ */
+#include "internal/deprecated.h"
+#include "internal/nelem.h"
+#include <openssl/crypto.h>
+#include <openssl/evp.h>
+#include <openssl/core_dispatch.h>
+#include <openssl/core_names.h>
+#include <openssl/rsa.h>
+#include <openssl/params.h>
+#include <openssl/err.h>
+#include <openssl/proverr.h>
+#include "crypto/rsa.h"
+#include "prov/provider_ctx.h"
+#include "prov/providercommon.h"
+#include "prov/implementations.h"
+#include "prov/securitycheck.h"
+
+static OSSL_FUNC_kem_newctx_fn rsakem_newctx;
+static OSSL_FUNC_kem_encapsulate_init_fn rsakem_encapsulate_init;
+static OSSL_FUNC_kem_encapsulate_fn rsakem_generate;
+static OSSL_FUNC_kem_decapsulate_init_fn rsakem_decapsulate_init;
+static OSSL_FUNC_kem_decapsulate_fn rsakem_recover;
+static OSSL_FUNC_kem_freectx_fn rsakem_freectx;
+static OSSL_FUNC_kem_dupctx_fn rsakem_dupctx;
+static OSSL_FUNC_kem_get_ctx_params_fn rsakem_get_ctx_params;
+static OSSL_FUNC_kem_gettable_ctx_params_fn rsakem_gettable_ctx_params;
+static OSSL_FUNC_kem_set_ctx_params_fn rsakem_set_ctx_params;
+static OSSL_FUNC_kem_settable_ctx_params_fn rsakem_settable_ctx_params;
+
+/*
+ * Only the KEM for RSASVE as defined in SP800-56b r2 is implemented
+ * currently.
+ */
+#define KEM_OP_UNDEFINED -1
+#define KEM_OP_RSASVE 0
+
+/*
+ * What's passed as an actual key is defined by the KEYMGMT interface.
+ * We happen to know that our KEYMGMT simply passes RSA structures, so
+ * we use that here too.
+ */
+typedef struct {
+ OSSL_LIB_CTX *libctx;
+ RSA *rsa;
+ int op;
+ OSSL_FIPS_IND_DECLARE
+} PROV_RSA_CTX;
+
+static const OSSL_ITEM rsakem_opname_id_map[] = {
+ { KEM_OP_RSASVE, OSSL_KEM_PARAM_OPERATION_RSASVE },
+};
+
+static int name2id(const char *name, const OSSL_ITEM *map, size_t sz)
+{
+ size_t i;
+
+ if (name == NULL)
+ return -1;
+
+ for (i = 0; i < sz; ++i) {
+ if (OPENSSL_strcasecmp(map[i].ptr, name) == 0)
+ return map[i].id;
+ }
+ return -1;
+}
+
+static int rsakem_opname2id(const char *name)
+{
+ return name2id(name, rsakem_opname_id_map, OSSL_NELEM(rsakem_opname_id_map));
+}
+
+static void *rsakem_newctx(void *provctx)
+{
+ PROV_RSA_CTX *prsactx;
+
+ if (!ossl_prov_is_running())
+ return NULL;
+
+ prsactx = OPENSSL_zalloc(sizeof(PROV_RSA_CTX));
+ if (prsactx == NULL)
+ return NULL;
+ prsactx->libctx = PROV_LIBCTX_OF(provctx);
+ prsactx->op = KEM_OP_RSASVE;
+ OSSL_FIPS_IND_INIT(prsactx)
+
+ return prsactx;
+}
+
+static void rsakem_freectx(void *vprsactx)
+{
+ PROV_RSA_CTX *prsactx = (PROV_RSA_CTX *)vprsactx;
+
+ RSA_free(prsactx->rsa);
+ OPENSSL_free(prsactx);
+}
+
+static void *rsakem_dupctx(void *vprsactx)
+{
+ PROV_RSA_CTX *srcctx = (PROV_RSA_CTX *)vprsactx;
+ PROV_RSA_CTX *dstctx;
+
+ if (!ossl_prov_is_running())
+ return NULL;
+
+ dstctx = OPENSSL_zalloc(sizeof(*srcctx));
+ if (dstctx == NULL)
+ return NULL;
+
+ *dstctx = *srcctx;
+ if (dstctx->rsa != NULL && !RSA_up_ref(dstctx->rsa)) {
+ OPENSSL_free(dstctx);
+ return NULL;
+ }
+ return dstctx;
+}
+
+static int rsakem_init(void *vprsactx, void *vrsa,
+ const OSSL_PARAM params[], int operation,
+ const char *desc)
+{
+ PROV_RSA_CTX *prsactx = (PROV_RSA_CTX *)vprsactx;
+ int protect = 0;
+
+ if (!ossl_prov_is_running())
+ return 0;
+
+ if (prsactx == NULL || vrsa == NULL)
+ return 0;
+
+ if (!ossl_rsa_key_op_get_protect(vrsa, operation, &protect))
+ return 0;
+ if (!RSA_up_ref(vrsa))
+ return 0;
+ RSA_free(prsactx->rsa);
+ prsactx->rsa = vrsa;
+
+ OSSL_FIPS_IND_SET_APPROVED(prsactx)
+ if (!rsakem_set_ctx_params(prsactx, params))
+ return 0;
+#ifdef FIPS_MODULE
+ if (!ossl_fips_ind_rsa_key_check(OSSL_FIPS_IND_GET(prsactx),
+ OSSL_FIPS_IND_SETTABLE0, prsactx->libctx,
+ prsactx->rsa, desc, protect))
+ return 0;
+#endif
+ return 1;
+}
+
+static int rsakem_encapsulate_init(void *vprsactx, void *vrsa,
+ const OSSL_PARAM params[])
+{
+ return rsakem_init(vprsactx, vrsa, params, EVP_PKEY_OP_ENCAPSULATE,
+ "RSA Encapsulate Init");
+}
+
+static int rsakem_decapsulate_init(void *vprsactx, void *vrsa,
+ const OSSL_PARAM params[])
+{
+ return rsakem_init(vprsactx, vrsa, params, EVP_PKEY_OP_DECAPSULATE,
+ "RSA Decapsulate Init");
+}
+
+static int rsakem_get_ctx_params(void *vprsactx, OSSL_PARAM *params)
+{
+ PROV_RSA_CTX *ctx = (PROV_RSA_CTX *)vprsactx;
+
+ if (ctx == NULL)
+ return 0;
+
+ if (!OSSL_FIPS_IND_GET_CTX_PARAM(ctx, params))
+ return 0;
+ return 1;
+}
+
+static const OSSL_PARAM known_gettable_rsakem_ctx_params[] = {
+ OSSL_FIPS_IND_GETTABLE_CTX_PARAM()
+ OSSL_PARAM_END
+};
+
+static const OSSL_PARAM *rsakem_gettable_ctx_params(ossl_unused void *vprsactx,
+ ossl_unused void *provctx)
+{
+ return known_gettable_rsakem_ctx_params;
+}
+
+static int rsakem_set_ctx_params(void *vprsactx, const OSSL_PARAM params[])
+{
+ PROV_RSA_CTX *prsactx = (PROV_RSA_CTX *)vprsactx;
+ const OSSL_PARAM *p;
+ int op;
+
+ if (prsactx == NULL)
+ return 0;
+ if (ossl_param_is_empty(params))
+ return 1;
+
+ if (!OSSL_FIPS_IND_SET_CTX_PARAM(prsactx, OSSL_FIPS_IND_SETTABLE0, params,
+ OSSL_KEM_PARAM_FIPS_KEY_CHECK))
+ return 0;
+ p = OSSL_PARAM_locate_const(params, OSSL_KEM_PARAM_OPERATION);
+ if (p != NULL) {
+ if (p->data_type != OSSL_PARAM_UTF8_STRING)
+ return 0;
+ op = rsakem_opname2id(p->data);
+ if (op < 0)
+ return 0;
+ prsactx->op = op;
+ }
+ return 1;
+}
+
+static const OSSL_PARAM known_settable_rsakem_ctx_params[] = {
+ OSSL_PARAM_utf8_string(OSSL_KEM_PARAM_OPERATION, NULL, 0),
+ OSSL_FIPS_IND_SETTABLE_CTX_PARAM(OSSL_KEM_PARAM_FIPS_KEY_CHECK)
+ OSSL_PARAM_END
+};
+
+static const OSSL_PARAM *rsakem_settable_ctx_params(ossl_unused void *vprsactx,
+ ossl_unused void *provctx)
+{
+ return known_settable_rsakem_ctx_params;
+}
+
+/*
+ * NIST.SP.800-56Br2
+ * 7.2.1.2 RSASVE Generate Operation (RSASVE.GENERATE).
+ *
+ * Generate a random in the range 1 < z < (n – 1)
+ */
+static int rsasve_gen_rand_bytes(RSA *rsa_pub,
+ unsigned char *out, int outlen)
+{
+ int ret = 0;
+ BN_CTX *bnctx;
+ BIGNUM *z, *nminus3;
+
+ bnctx = BN_CTX_secure_new_ex(ossl_rsa_get0_libctx(rsa_pub));
+ if (bnctx == NULL)
+ return 0;
+
+ /*
+ * Generate a random in the range 1 < z < (n – 1).
+ * Since BN_priv_rand_range_ex() returns a value in range 0 <= r < max
+ * We can achieve this by adding 2.. but then we need to subtract 3 from
+ * the upper bound i.e: 2 + (0 <= r < (n - 3))
+ */
+ BN_CTX_start(bnctx);
+ nminus3 = BN_CTX_get(bnctx);
+ z = BN_CTX_get(bnctx);
+ ret = (z != NULL
+ && (BN_copy(nminus3, RSA_get0_n(rsa_pub)) != NULL)
+ && BN_sub_word(nminus3, 3)
+ && BN_priv_rand_range_ex(z, nminus3, 0, bnctx)
+ && BN_add_word(z, 2)
+ && (BN_bn2binpad(z, out, outlen) == outlen));
+ BN_CTX_end(bnctx);
+ BN_CTX_free(bnctx);
+ return ret;
+}
+
+/*
+ * NIST.SP.800-56Br2
+ * 7.2.1.2 RSASVE Generate Operation (RSASVE.GENERATE).
+ */
+static int rsasve_generate(PROV_RSA_CTX *prsactx,
+ unsigned char *out, size_t *outlen,
+ unsigned char *secret, size_t *secretlen)
+{
+ int ret;
+ size_t nlen;
+
+ /* Step (1): nlen = Ceil(len(n)/8) */
+ nlen = RSA_size(prsactx->rsa);
+
+ if (out == NULL) {
+ if (nlen == 0) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY);
+ return 0;
+ }
+ if (outlen == NULL && secretlen == NULL)
+ return 0;
+ if (outlen != NULL)
+ *outlen = nlen;
+ if (secretlen != NULL)
+ *secretlen = nlen;
+ return 1;
+ }
+
+ /*
+ * If outlen is specified, then it must report the length
+ * of the out buffer on input so that we can confirm
+ * its size is sufficent for encapsulation
+ */
+ if (outlen != NULL && *outlen < nlen) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_OUTPUT_LENGTH);
+ return 0;
+ }
+
+ /*
+ * Step (2): Generate a random byte string z of nlen bytes where
+ * 1 < z < n - 1
+ */
+ if (!rsasve_gen_rand_bytes(prsactx->rsa, secret, nlen))
+ return 0;
+
+ /* Step(3): out = RSAEP((n,e), z) */
+ ret = RSA_public_encrypt(nlen, secret, out, prsactx->rsa, RSA_NO_PADDING);
+ if (ret) {
+ ret = 1;
+ if (outlen != NULL)
+ *outlen = nlen;
+ if (secretlen != NULL)
+ *secretlen = nlen;
+ } else {
+ OPENSSL_cleanse(secret, nlen);
+ }
+ return ret;
+}
+
+/**
+ * rsasve_recover - Recovers a secret value from ciphertext using an RSA
+ * private key. Once, recovered, the secret value is considered to be a
+ * shared secret. Algorithm is preformed as per
+ * NIST SP 800-56B Rev 2
+ * 7.2.1.3 RSASVE Recovery Operation (RSASVE.RECOVER).
+ *
+ * This function performs RSA decryption using the private key from the
+ * provided RSA context (`prsactx`). It takes the input ciphertext, decrypts
+ * it, and writes the decrypted message to the output buffer.
+ *
+ * @prsactx: The RSA context containing the private key.
+ * @out: The output buffer to store the decrypted message.
+ * @outlen: On input, the size of the output buffer. On successful
+ * completion, the actual length of the decrypted message.
+ * @in: The input buffer containing the ciphertext to be decrypted.
+ * @inlen: The length of the input ciphertext in bytes.
+ *
+ * Returns 1 on success, or 0 on error. In case of error, appropriate
+ * error messages are raised using the ERR_raise function.
+ */
+static int rsasve_recover(PROV_RSA_CTX *prsactx,
+ unsigned char *out, size_t *outlen,
+ const unsigned char *in, size_t inlen)
+{
+ size_t nlen;
+ int ret;
+
+ /* Step (1): get the byte length of n */
+ nlen = RSA_size(prsactx->rsa);
+
+ if (out == NULL) {
+ if (nlen == 0) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY);
+ return 0;
+ }
+ *outlen = nlen;
+ return 1;
+ }
+
+ /*
+ * Step (2): check the input ciphertext 'inlen' matches the nlen
+ * and that outlen is at least nlen bytes
+ */
+ if (inlen != nlen) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_BAD_LENGTH);
+ return 0;
+ }
+
+ /*
+ * If outlen is specified, then it must report the length
+ * of the out buffer, so that we can confirm that it is of
+ * sufficient size to hold the output of decapsulation
+ */
+ if (outlen != NULL && *outlen < nlen) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_OUTPUT_LENGTH);
+ return 0;
+ }
+
+ /* Step (3): out = RSADP((n,d), in) */
+ ret = RSA_private_decrypt(inlen, in, out, prsactx->rsa, RSA_NO_PADDING);
+ if (ret > 0 && outlen != NULL)
+ *outlen = ret;
+ return ret > 0;
+}
+
+static int rsakem_generate(void *vprsactx, unsigned char *out, size_t *outlen,
+ unsigned char *secret, size_t *secretlen)
+{
+ PROV_RSA_CTX *prsactx = (PROV_RSA_CTX *)vprsactx;
+
+ if (!ossl_prov_is_running())
+ return 0;
+
+ switch (prsactx->op) {
+ case KEM_OP_RSASVE:
+ return rsasve_generate(prsactx, out, outlen, secret, secretlen);
+ default:
+ return -2;
+ }
+}
+
+static int rsakem_recover(void *vprsactx, unsigned char *out, size_t *outlen,
+ const unsigned char *in, size_t inlen)
+{
+ PROV_RSA_CTX *prsactx = (PROV_RSA_CTX *)vprsactx;
+
+ if (!ossl_prov_is_running())
+ return 0;
+
+ switch (prsactx->op) {
+ case KEM_OP_RSASVE:
+ return rsasve_recover(prsactx, out, outlen, in, inlen);
+ default:
+ return -2;
+ }
+}
+
+const OSSL_DISPATCH ossl_rsa_asym_kem_functions[] = {
+ { OSSL_FUNC_KEM_NEWCTX, (void (*)(void))rsakem_newctx },
+ { OSSL_FUNC_KEM_ENCAPSULATE_INIT,
+ (void (*)(void))rsakem_encapsulate_init },
+ { OSSL_FUNC_KEM_ENCAPSULATE, (void (*)(void))rsakem_generate },
+ { OSSL_FUNC_KEM_DECAPSULATE_INIT,
+ (void (*)(void))rsakem_decapsulate_init },
+ { OSSL_FUNC_KEM_DECAPSULATE, (void (*)(void))rsakem_recover },
+ { OSSL_FUNC_KEM_FREECTX, (void (*)(void))rsakem_freectx },
+ { OSSL_FUNC_KEM_DUPCTX, (void (*)(void))rsakem_dupctx },
+ { OSSL_FUNC_KEM_GET_CTX_PARAMS,
+ (void (*)(void))rsakem_get_ctx_params },
+ { OSSL_FUNC_KEM_GETTABLE_CTX_PARAMS,
+ (void (*)(void))rsakem_gettable_ctx_params },
+ { OSSL_FUNC_KEM_SET_CTX_PARAMS,
+ (void (*)(void))rsakem_set_ctx_params },
+ { OSSL_FUNC_KEM_SETTABLE_CTX_PARAMS,
+ (void (*)(void))rsakem_settable_ctx_params },
+ OSSL_DISPATCH_END
+};
diff --git a/crypto/openssl/providers/implementations/kem/template_kem.c b/crypto/openssl/providers/implementations/kem/template_kem.c
new file mode 100644
index 000000000000..c621a31a9c35
--- /dev/null
+++ b/crypto/openssl/providers/implementations/kem/template_kem.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <string.h>
+#include <openssl/crypto.h>
+#include <openssl/evp.h>
+#include <openssl/core_dispatch.h>
+#include <openssl/core_names.h>
+#include <openssl/params.h>
+#include <openssl/err.h>
+#include <openssl/proverr.h>
+#include "prov/provider_ctx.h"
+#include "prov/implementations.h"
+#include "prov/securitycheck.h"
+#include "prov/providercommon.h"
+
+extern const OSSL_DISPATCH ossl_template_asym_kem_functions[];
+
+#define BUFSIZE 1000
+#if defined(NDEBUG) || defined(OPENSSL_NO_STDIO)
+static void debug_print(char *fmt, ...)
+{
+}
+
+#else
+static void debug_print(char *fmt, ...)
+{
+ char out[BUFSIZE];
+ va_list argptr;
+
+ va_start(argptr, fmt);
+ vsnprintf(out, BUFSIZE, fmt, argptr);
+ va_end(argptr);
+ if (getenv("TEMPLATEKEM"))
+ fprintf(stderr, "TEMPLATE_KEM: %s", out);
+}
+#endif
+
+typedef struct {
+ OSSL_LIB_CTX *libctx;
+ /* some algorithm-specific key struct */
+ int op;
+} PROV_TEMPLATE_CTX;
+
+static OSSL_FUNC_kem_newctx_fn template_newctx;
+static OSSL_FUNC_kem_encapsulate_init_fn template_encapsulate_init;
+static OSSL_FUNC_kem_encapsulate_fn template_encapsulate;
+static OSSL_FUNC_kem_decapsulate_init_fn template_decapsulate_init;
+static OSSL_FUNC_kem_decapsulate_fn template_decapsulate;
+static OSSL_FUNC_kem_freectx_fn template_freectx;
+static OSSL_FUNC_kem_set_ctx_params_fn template_set_ctx_params;
+static OSSL_FUNC_kem_settable_ctx_params_fn template_settable_ctx_params;
+
+static void *template_newctx(void *provctx)
+{
+ PROV_TEMPLATE_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx));
+
+ debug_print("newctx called\n");
+ if (ctx == NULL)
+ return NULL;
+ ctx->libctx = PROV_LIBCTX_OF(provctx);
+
+ debug_print("newctx returns %p\n", ctx);
+ return ctx;
+}
+
+static void template_freectx(void *vctx)
+{
+ PROV_TEMPLATE_CTX *ctx = (PROV_TEMPLATE_CTX *)vctx;
+
+ debug_print("freectx %p\n", ctx);
+ OPENSSL_free(ctx);
+}
+
+static int template_init(void *vctx, int operation, void *vkey, void *vauth,
+ ossl_unused const OSSL_PARAM params[])
+{
+ PROV_TEMPLATE_CTX *ctx = (PROV_TEMPLATE_CTX *)vctx;
+
+ debug_print("init %p / %p\n", ctx, vkey);
+ if (!ossl_prov_is_running())
+ return 0;
+
+ /* check and fill in reference to key */
+ ctx->op = operation;
+ debug_print("init OK\n");
+ return 1;
+}
+
+static int template_encapsulate_init(void *vctx, void *vkey,
+ const OSSL_PARAM params[])
+{
+ return template_init(vctx, EVP_PKEY_OP_ENCAPSULATE, vkey, NULL, params);
+}
+
+static int template_decapsulate_init(void *vctx, void *vkey,
+ const OSSL_PARAM params[])
+{
+ return template_init(vctx, EVP_PKEY_OP_DECAPSULATE, vkey, NULL, params);
+}
+
+static int template_set_ctx_params(void *vctx, const OSSL_PARAM params[])
+{
+ PROV_TEMPLATE_CTX *ctx = (PROV_TEMPLATE_CTX *)vctx;
+
+ debug_print("set ctx params %p\n", ctx);
+ if (ctx == NULL)
+ return 0;
+ if (ossl_param_is_empty(params))
+ return 1;
+
+ debug_print("set ctx params OK\n");
+ return 1;
+}
+
+static const OSSL_PARAM known_settable_template_ctx_params[] = {
+ /* possibly more params */
+ OSSL_PARAM_END
+};
+
+static const OSSL_PARAM *template_settable_ctx_params(ossl_unused void *vctx,
+ ossl_unused void *provctx)
+{
+ return known_settable_template_ctx_params;
+}
+
+static int template_encapsulate(void *vctx, unsigned char *out, size_t *outlen,
+ unsigned char *secret, size_t *secretlen)
+{
+ debug_print("encaps %p to %p\n", vctx, out);
+
+ /* add algorithm-specific length checks */
+
+ if (outlen != NULL)
+ *outlen = 0; /* replace with real encapsulated data length */
+ if (secretlen != NULL)
+ *secretlen = 0; /* replace with real shared secret length */
+
+ if (out == NULL) {
+ if (outlen != NULL && secretlen != NULL)
+ debug_print("encaps outlens set to %zu and %zu\n", *outlen, *secretlen);
+ return 1;
+ }
+
+ /* check key and perform real KEM operation */
+
+ debug_print("encaps OK\n");
+ return 1;
+}
+
+static int template_decapsulate(void *vctx, unsigned char *out, size_t *outlen,
+ const unsigned char *in, size_t inlen)
+{
+ debug_print("decaps %p to %p inlen at %zu\n", vctx, out, inlen);
+
+ /* add algorithm-specific length checks */
+
+ if (outlen != NULL)
+ *outlen = 0; /* replace with shared secret length */
+
+ if (out == NULL) {
+ if (outlen != NULL)
+ debug_print("decaps outlen set to %zu \n", *outlen);
+ return 1;
+ }
+
+ /* check key and perform real decaps operation */
+
+ debug_print("decaps OK\n");
+ return 1;
+}
+
+const OSSL_DISPATCH ossl_template_asym_kem_functions[] = {
+ { OSSL_FUNC_KEM_NEWCTX, (void (*)(void))template_newctx },
+ { OSSL_FUNC_KEM_ENCAPSULATE_INIT,
+ (void (*)(void))template_encapsulate_init },
+ { OSSL_FUNC_KEM_ENCAPSULATE, (void (*)(void))template_encapsulate },
+ { OSSL_FUNC_KEM_DECAPSULATE_INIT,
+ (void (*)(void))template_decapsulate_init },
+ { OSSL_FUNC_KEM_DECAPSULATE, (void (*)(void))template_decapsulate },
+ { OSSL_FUNC_KEM_FREECTX, (void (*)(void))template_freectx },
+ { OSSL_FUNC_KEM_SET_CTX_PARAMS,
+ (void (*)(void))template_set_ctx_params },
+ { OSSL_FUNC_KEM_SETTABLE_CTX_PARAMS,
+ (void (*)(void))template_settable_ctx_params },
+ OSSL_DISPATCH_END
+};