summaryrefslogtreecommitdiffstats
path: root/providers/implementations/kem
diff options
context:
space:
mode:
authorslontis <shane.lontis@oracle.com>2022-08-26 11:54:35 +1000
committerHugo Landau <hlandau@openssl.org>2022-09-23 09:24:47 +0100
commit78c44b05945be07eae86f0164b9b777e2de2295b (patch)
tree1c2f721a3bc8405b86f6aac30326265609de7968 /providers/implementations/kem
parent257cade411ef9217305c5db47f40e5dacdb99c71 (diff)
Add HPKE DHKEM provider support for EC, X25519 and X448.
The code is derived from @sftcd's work in PR #17172. This PR puts the DHKEM algorithms into the provider layer as KEM algorithms for EC and ECX. This PR only implements the DHKEM component of HPKE as specified in RFC 9180. crypto/hpke/hpke_util.c has been added for fuctions that will be shared between DHKEM and HPKE. API's for EVP_PKEY_auth_encapsulate_init() and EVP_PKEY_auth_decapsulate_init() have been added to support authenticated encapsulation. auth_init() functions were chosen rather that a EVP_PKEY_KEM_set_auth() interface to support future algorithms that could possibly need different init functions. Internal code has been refactored, so that it can be shared between the DHKEM and other systems. Since DHKEM operates on low level keys it needs to be able to do low level ECDH and ECXDH calls without converting the keys back into EVP_PKEY/EVP_PKEY_CTX form. See ossl_ecx_compute_key(), ossl_ec_public_from_private() DHKEM requires API's to derive a key using a seed (IKM). This did not sit well inside the DHKEM itself as dispatch functions. This functionality fits better inside the EC and ECX keymanagers keygen, since they are just variations of keygen where the private key is generated in a different manner. This should mainly be used for testing purposes. See ossl_ec_generate_key_dhkem(). It supports this by allowing a settable param to be passed to keygen (See OSSL_PKEY_PARAM_DHKEM_IKM). The keygen calls code within ec and ecx dhkem implementation to handle this. See ossl_ecx_dhkem_derive_private() and ossl_ec_dhkem_derive_private(). These 2 functions are also used by the EC/ECX DHKEM implementations to generate the sender ephemeral keys. Reviewed-by: Hugo Landau <hlandau@openssl.org> Reviewed-by: Matt Caswell <matt@openssl.org> (Merged from https://github.com/openssl/openssl/pull/19068)
Diffstat (limited to 'providers/implementations/kem')
-rw-r--r--providers/implementations/kem/build.info5
-rw-r--r--providers/implementations/kem/ec_kem.c841
-rw-r--r--providers/implementations/kem/eckem.h14
-rw-r--r--providers/implementations/kem/ecx_kem.c706
-rw-r--r--providers/implementations/kem/kem_util.c45
5 files changed, 1611 insertions, 0 deletions
diff --git a/providers/implementations/kem/build.info b/providers/implementations/kem/build.info
index dbb1b7d750..6addb9b2c7 100644
--- a/providers/implementations/kem/build.info
+++ b/providers/implementations/kem/build.info
@@ -2,5 +2,10 @@
# switch each to the Legacy provider when needed.
$RSA_KEM_GOAL=../../libdefault.a ../../libfips.a
+$EC_KEM_GOAL=../../libdefault.a
SOURCE[$RSA_KEM_GOAL]=rsa_kem.c
+
+IF[{- !$disabled{ec} -}]
+ SOURCE[$EC_KEM_GOAL]=ecx_kem.c kem_util.c ec_kem.c
+ENDIF
diff --git a/providers/implementations/kem/ec_kem.c b/providers/implementations/kem/ec_kem.c
new file mode 100644
index 0000000000..57dcea4196
--- /dev/null
+++ b/providers/implementations/kem/ec_kem.c
@@ -0,0 +1,841 @@
+/*
+ * 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
+ */
+
+/*
+ * 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 "crypto/hpke.h"
+#include "crypto/ec.h"
+#include "prov/ecx.h"
+#include "eckem.h"
+
+/*
+ * Used to store constants from Section 7.1 "Table 2 KEM IDs"
+ * and the bitmask for curves described in Section 7.1.3 DeriveKeyPair
+ */
+typedef struct {
+ const char *curve;
+ const char *kdfdigestname;
+ uint16_t kemid;
+ size_t secretlen; /* Nsecret = Nh */
+ size_t encodedpublen;
+ size_t encodedprivlen;
+ uint8_t bitmask;
+} DHKEM_ALG;
+
+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 DHKEM_ALG *alg;
+} 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;
+
+/* See Section 7.1 "Table 2 KEM IDs" */
+static const DHKEM_ALG dhkem_alg[] = {
+ { "P-256", "SHA256", 0x0010, 32, 65, 32, 0xFF },
+ { "P-384", "SHA384", 0x0011, 48, 97, 48, 0xFF },
+ { "P-521", "SHA512", 0x0012, 64, 133, 66, 0x01 },
+ { NULL }
+};
+
+/* Return an object containing KEM constants associated with a EC curve name */
+static const DHKEM_ALG *dhkem_ec_find_alg(const char *curve)
+{
+ int i;
+
+ for (i = 0; dhkem_alg[i].curve != NULL; ++i) {
+ if (OPENSSL_strcasecmp(curve, dhkem_alg[i].curve) == 0)
+ return &dhkem_alg[i];
+ }
+ ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CURVE);
+ return NULL;
+}
+
+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->alg = dhkem_ec_find_alg(curve);
+ if (ctx->alg == 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);
+
+ 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 (params == NULL)
+ 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[5];
+ uint8_t prk[EVP_MAX_MD_SIZE];
+ size_t prklen = okmlen;
+ int ret;
+
+ if (prklen > sizeof(prk))
+ return 0;
+
+ ossl_dhkem_getsuiteid(suiteid, kemid);
+
+ ret = ossl_hpke_labeled_extract(kctx, prk, prklen,
+ NULL, 0, suiteid, sizeof(suiteid),
+ OSSL_DHKEM_LABEL_EAE_PRK, dhkm, dhkmlen)
+ && ossl_hpke_labeled_expand(kctx, okm, okmlen, prk, prklen,
+ 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[5];
+ unsigned char prk[OSSL_HPKE_MAX_SECRET];
+ unsigned char privbuf[OSSL_HPKE_MAX_PRIVATE];
+ const BIGNUM *order;
+ unsigned char counter = 0;
+ const DHKEM_ALG *alg;
+ const char *curve = ec_curvename_get0(ec);
+
+ if (curve == NULL)
+ return -2;
+
+ alg = dhkem_ec_find_alg(curve);
+ if (alg == NULL)
+ return -2;
+
+ kdfctx = ossl_kdf_ctx_create("HKDF", alg->kdfdigestname,
+ 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 < alg->encodedprivlen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_INPUT_LENGTH,
+ "ikm length is :%zu, should be at least %zu",
+ ikmlen, alg->encodedprivlen);
+ goto err;
+ }
+
+ ossl_dhkem_getsuiteid(suiteid, alg->kemid);
+
+ if (!ossl_hpke_labeled_extract(kdfctx, prk, alg->secretlen,
+ NULL, 0, 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, alg->encodedprivlen,
+ prk, alg->secretlen,
+ suiteid, sizeof(suiteid),
+ OSSL_DHKEM_LABEL_CANDIDATE,
+ &counter, 1))
+ goto err;
+ privbuf[0] &= alg->bitmask;
+ if (BN_bin2bn(privbuf, alg->encodedprivlen, 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->alg->encodedprivlen;
+ 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;
+ size_t encodedpublen = ctx->alg->encodedpublen;
+ size_t encodedprivlen = ctx->alg->encodedprivlen;
+ 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, ctx->alg->encodedpublen);
+ memcpy(kemctx + ctx->alg->encodedpublen, recipient_pub,
+ ctx->alg->encodedpublen);
+ if (auth)
+ memcpy(kemctx + 2 * encodedpublen, sender_authpub, encodedpublen);
+ kdfctx = ossl_kdf_ctx_create(ctx->kdfname, ctx->alg->kdfdigestname,
+ ctx->libctx, ctx->propq);
+ if (kdfctx == NULL)
+ goto err;
+ if (!dhkem_extract_and_expand(kdfctx, secret, ctx->alg->secretlen,
+ ctx->alg->kemid, 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;
+
+ if (enc == NULL) {
+ if (enclen == NULL && secretlen == NULL)
+ return 0;
+ if (enclen != NULL)
+ *enclen = ctx->alg->encodedpublen;
+ if (secretlen != NULL)
+ *secretlen = ctx->alg->secretlen;
+ return 1;
+ }
+
+ if (*secretlen < ctx->alg->secretlen) {
+ ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*secretlen too small");
+ return 0;
+ }
+ if (*enclen < ctx->alg->encodedpublen) {
+ 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 != ctx->alg->encodedpublen
+ || 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 = ctx->alg->secretlen;
+ 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;
+ unsigned char recipient_pub[OSSL_HPKE_MAX_PUBLIC];
+ size_t recipient_publen;
+ size_t encodedpublen = ctx->alg->encodedpublen;
+
+ if (secret == NULL) {
+ *secretlen = ctx->alg->secretlen;
+ return 1;
+ }
+
+ if (*secretlen < ctx->alg->secretlen) {
+ 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 = ctx->alg->secretlen;
+ 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 },
+ { 0, NULL }
+};
diff --git a/providers/implementations/kem/eckem.h b/providers/implementations/kem/eckem.h
new file mode 100644
index 0000000000..44fdde852b
--- /dev/null
+++ b/providers/implementations/kem/eckem.h
@@ -0,0 +1,14 @@
+/*
+ * 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);
+void ossl_dhkem_getsuiteid(unsigned char suiteid[5], uint16_t kemid);
diff --git a/providers/implementations/kem/ecx_kem.c b/providers/implementations/kem/ecx_kem.c
new file mode 100644
index 0000000000..979035fa1c
--- /dev/null
+++ b/providers/implementations/kem/ecx_kem.c
@@ -0,0 +1,706 @@
+/*
+ * 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
+ */
+
+/*
+ * 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 "crypto/hpke.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
+
+typedef struct {
+ ECX_KEY *recipient_key;
+ ECX_KEY *sender_authkey;
+ OSSL_LIB_CTX *libctx;
+ char *propq;
+ unsigned int mode;
+ unsigned int op;
+ uint16_t kemid;
+ unsigned char *ikm;
+ size_t ikmlen;
+ const char *kdfname;
+ const char *kdfdigestname;
+ size_t sharedsecretlen;
+ size_t keylen;
+} 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;