diff options
Diffstat (limited to 'crypto/openssl/providers/implementations/kem')
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 +}; |