summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Farrell <stephen.farrell@cs.tcd.ie>2022-11-22 02:42:04 +0000
committerMatt Caswell <matt@openssl.org>2022-11-25 16:26:55 +0000
commitad062480f7490197b174edad8625ce40d74f6e68 (patch)
treef4ac43084558412509820b4d167b4c2906f5cfb2
parent0dbd3a81e46dd7ea9f7832307fdd0b2ac207a5bf (diff)
Implements Hybrid Public Key Encryption (HPKE) as per RFC9180.
This supports all the modes, suites and export mechanisms defined in RFC9180 and should be relatively easily extensible if/as new suites are added. The APIs are based on the pseudo-code from the RFC, e.g. OSS_HPKE_encap() roughly maps to SetupBaseS(). External APIs are defined in include/openssl/hpke.h and documented in doc/man3/OSSL_HPKE_CTX_new.pod. Tests (test/hpke_test.c) include verifying a number of the test vectors from the RFC as well as round-tripping for all the modes and suites. We have demonstrated interoperability with other HPKE implementations via a fork [1] that implements TLS Encrypted ClientHello (ECH) which uses HPKE. @slontis provided huge help in getting this done and this makes extensive use of the KEM handling code from his PR#19068. [1] https://github.com/sftcd/openssl/tree/ECH-draft-13c Reviewed-by: Tomas Mraz <tomas@openssl.org> Reviewed-by: Shane Lontis <shane.lontis@oracle.com> Reviewed-by: Matt Caswell <matt@openssl.org> (Merged from https://github.com/openssl/openssl/pull/17172)
-rw-r--r--CHANGES.md9
-rw-r--r--crypto/err/openssl.txt2
-rw-r--r--crypto/hpke/build.info2
-rw-r--r--crypto/hpke/hpke.c1439
-rw-r--r--crypto/hpke/hpke_util.c377
-rw-r--r--doc/build.info6
-rw-r--r--doc/man3/OSSL_HPKE_CTX_new.pod538
-rw-r--r--include/crypto/hpke.h47
-rw-r--r--include/internal/hpke_util.h100
-rw-r--r--include/openssl/hpke.h144
-rw-r--r--include/openssl/proverr.h4
-rw-r--r--providers/common/include/prov/proverr.h2
-rw-r--r--providers/common/provider_err.c4
-rw-r--r--providers/implementations/kem/ec_kem.c125
-rw-r--r--providers/implementations/kem/eckem.h1
-rw-r--r--providers/implementations/kem/ecx_kem.c114
-rw-r--r--providers/implementations/kem/kem_util.c8
-rw-r--r--test/build.info6
-rw-r--r--test/hpke_test.c1921
-rw-r--r--test/recipes/30-test_hpke.t20
-rw-r--r--util/libcrypto.num20
21 files changed, 4678 insertions, 211 deletions
diff --git a/CHANGES.md b/CHANGES.md
index ede13f7d79..1e91bcda2d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -24,6 +24,15 @@ OpenSSL 3.2
### Changes between 3.0 and 3.2 [xx XXX xxxx]
+ * Added support for Hybrid Public Key Encryption (HPKE) as defined
+ in RFC9180. HPKE is required for TLS Encrypted ClientHello (ECH),
+ Message Layer Security (MLS) and other IETF specifications.
+ HPKE can also be used by other applications that require
+ encrypting "to" an ECDH public key. External APIs are defined in
+ include/openssl/hpke.h and documented in doc/man3/OSSL_HPKE_CTX_new.pod
+
+ *Stephen Farrell*
+
* Add support for certificate compression (RFC8879), including
library support for Brotli and Zstandard compression.
diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt
index cc4194ce77..a205124a38 100644
--- a/crypto/err/openssl.txt
+++ b/crypto/err/openssl.txt
@@ -1020,6 +1020,7 @@ PROV_R_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE:165:\
PROV_R_INDICATOR_INTEGRITY_FAILURE:210:indicator integrity failure
PROV_R_INSUFFICIENT_DRBG_STRENGTH:181:insufficient drbg strength
PROV_R_INVALID_AAD:108:invalid aad
+PROV_R_INVALID_AEAD:231:invalid aead
PROV_R_INVALID_CONFIG_DATA:211:invalid config data
PROV_R_INVALID_CONSTANT_LENGTH:157:invalid constant length
PROV_R_INVALID_CURVE:176:invalid curve
@@ -1031,6 +1032,7 @@ PROV_R_INVALID_DIGEST_SIZE:218:invalid digest size
PROV_R_INVALID_INPUT_LENGTH:230:invalid input length
PROV_R_INVALID_ITERATION_COUNT:123:invalid iteration count
PROV_R_INVALID_IV_LENGTH:109:invalid iv length
+PROV_R_INVALID_KDF:232:invalid kdf
PROV_R_INVALID_KEY:158:invalid key
PROV_R_INVALID_KEY_LENGTH:105:invalid key length
PROV_R_INVALID_MAC:151:invalid mac
diff --git a/crypto/hpke/build.info b/crypto/hpke/build.info
index f096cf170b..033c243dac 100644
--- a/crypto/hpke/build.info
+++ b/crypto/hpke/build.info
@@ -1,5 +1,5 @@
LIBS=../../libcrypto
-$COMMON=hpke_util.c
+$COMMON=hpke_util.c hpke.c
SOURCE[../../libcrypto]=$COMMON
diff --git a/crypto/hpke/hpke.c b/crypto/hpke/hpke.c
new file mode 100644
index 0000000000..78341d358f
--- /dev/null
+++ b/crypto/hpke/hpke.c
@@ -0,0 +1,1439 @@
+/*
+ * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the OpenSSL license (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
+ */
+
+/* An OpenSSL-based HPKE implementation of RFC9180 */
+
+#include <string.h>
+#include <openssl/rand.h>
+#include <openssl/kdf.h>
+#include <openssl/core_names.h>
+#include <openssl/hpke.h>
+#include <openssl/sha.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include "internal/hpke_util.h"
+#include "internal/nelem.h"
+
+/** default buffer size for keys and internal buffers we use */
+#define OSSL_HPKE_MAXSIZE 512
+
+/* Define HPKE labels from RFC9180 in hex for EBCDIC compatibility */
+/* "HPKE" - "suite_id" label for section 5.1 */
+static const char OSSL_HPKE_SEC51LABEL[] = "\x48\x50\x4b\x45";
+/* "psk_id_hash" - in key_schedule_context */
+static const char OSSL_HPKE_PSKIDHASH_LABEL[] = "\x70\x73\x6b\x5f\x69\x64\x5f\x68\x61\x73\x68";
+/* "info_hash" - in key_schedule_context */
+static const char OSSL_HPKE_INFOHASH_LABEL[] = "\x69\x6e\x66\x6f\x5f\x68\x61\x73\x68";
+/* "base_nonce" - base nonce calc label */
+static const char OSSL_HPKE_NONCE_LABEL[] = "\x62\x61\x73\x65\x5f\x6e\x6f\x6e\x63\x65";
+/* "exp" - internal exporter secret generation label */
+static const char OSSL_HPKE_EXP_LABEL[] = "\x65\x78\x70";
+/* "sec" - external label for exporting secret */
+static const char OSSL_HPKE_EXP_SEC_LABEL[] = "\x73\x65\x63";
+/* "key" - label for use when generating key from shared secret */
+static const char OSSL_HPKE_KEY_LABEL[] = "\x6b\x65\x79";
+/* "psk_hash" - for hashing PSK */
+static const char OSSL_HPKE_PSK_HASH_LABEL[] = "\x70\x73\x6b\x5f\x68\x61\x73\x68";
+/* "secret" - for generating shared secret */
+static const char OSSL_HPKE_SECRET_LABEL[] = "\x73\x65\x63\x72\x65\x74";
+
+/**
+ * @brief sender or receiver context
+ */
+struct ossl_hpke_ctx_st
+{
+ OSSL_LIB_CTX *libctx; /* library context */
+ char *propq; /* properties */
+ int mode; /* HPKE mode */
+ OSSL_HPKE_SUITE suite; /* suite */
+ uint64_t seq; /* aead sequence number */
+ unsigned char *shared_secret; /* KEM output, zz */
+ size_t shared_secretlen;
+ unsigned char *key; /* final aead key */
+ size_t keylen;
+ unsigned char *nonce; /* aead base nonce */
+ size_t noncelen;
+ unsigned char *exportersec; /* exporter secret */
+ size_t exporterseclen;
+ char *pskid; /* PSK stuff */
+ unsigned char *psk;
+ size_t psklen;
+ EVP_PKEY *authpriv; /* sender's authentication private key */
+ unsigned char *authpub; /* auth public key */
+ size_t authpublen;
+ unsigned char *ikme; /* IKM for sender deterministic key gen */
+ size_t ikmelen;
+};
+
+/**
+ * @brief check if KEM uses NIST curve or not
+ * @param kem_id is the externally supplied kem_id
+ * @return 1 for NIST curves, 0 for other
+ */
+static int hpke_kem_id_nist_curve(uint16_t kem_id)
+{
+ const OSSL_HPKE_KEM_INFO *kem_info;
+
+ kem_info = ossl_HPKE_KEM_INFO_find_id(kem_id);
+ return kem_info != NULL && kem_info->groupname != NULL;
+}
+
+/**
+ * @brief wrapper to import NIST curve public key as easily as x25519/x448
+ * @param libctx is the context to use
+ * @param propq is a properties string
+ * @param gname is the curve groupname
+ * @param buf is the binary buffer with the (uncompressed) public value
+ * @param buflen is the length of the private key buffer
+ * @return a working EVP_PKEY * or NULL
+ *
+ * Note that this could be a useful function to make public in
+ * future, but would likely require a name change.
+ */
+static EVP_PKEY *evp_pkey_new_raw_nist_public_key(OSSL_LIB_CTX *libctx,
+ const char *propq,
+ const char *gname,
+ const unsigned char *buf,
+ size_t buflen)
+{
+ OSSL_PARAM params[2];
+ EVP_PKEY *ret = NULL;
+ EVP_PKEY_CTX *cctx = EVP_PKEY_CTX_new_from_name(libctx, "EC", propq);
+
+ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
+ (char *)gname, 0);
+ params[1] = OSSL_PARAM_construct_end();
+ if (cctx == NULL
+ || EVP_PKEY_paramgen_init(cctx) <= 0
+ || EVP_PKEY_CTX_set_params(cctx, params) <= 0
+ || EVP_PKEY_paramgen(cctx, &ret) <= 0
+ || EVP_PKEY_set1_encoded_public_key(ret, buf, buflen) != 1) {
+ EVP_PKEY_CTX_free(cctx);
+ EVP_PKEY_free(ret);
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return NULL;
+ }
+ EVP_PKEY_CTX_free(cctx);
+ return ret;
+}
+
+/**
+ * @brief do the AEAD decryption
+ * @param libctx is the context to use
+ * @param propq is a properties string
+ * @param suite is the ciphersuite
+ * @param key is the secret
+ * @param keylen is the length of the secret
+ * @param iv is the initialisation vector
+ * @param ivlen is the length of the iv
+ * @param aad is the additional authenticated data
+ * @param aadlen is the length of the aad
+ * @param ct is the ciphertext buffer
+ * @param ctlen is the ciphertext length (including tag).
+ * @param pt is the output buffer
+ * @param ptlen input/output, better be big enough on input, exact on output
+ * @return 1 on success, 0 otherwise
+ */
+static int hpke_aead_dec(OSSL_LIB_CTX *libctx, const char *propq,
+ OSSL_HPKE_SUITE suite,
+ const unsigned char *key, size_t keylen,
+ const unsigned char *iv, size_t ivlen,
+ const unsigned char *aad, size_t aadlen,
+ const unsigned char *ct, size_t ctlen,
+ unsigned char *pt, size_t *ptlen)
+{
+ int erv = 0;
+ EVP_CIPHER_CTX *ctx = NULL;
+ int len = 0;
+ size_t taglen;
+ EVP_CIPHER *enc = NULL;
+ const OSSL_HPKE_AEAD_INFO *aead_info = NULL;
+
+ if (pt == NULL || ptlen == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ aead_info = ossl_HPKE_AEAD_INFO_find_id(suite.aead_id);
+ if (aead_info == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ taglen = aead_info->taglen;
+ if (ctlen <= taglen || *ptlen < ctlen - taglen) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ goto err;
+ }
+ /* Create and initialise the context */
+ if ((ctx = EVP_CIPHER_CTX_new()) == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ /* Initialise the encryption operation */
+ enc = EVP_CIPHER_fetch(libctx, aead_info->name, propq);
+ if (enc == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ if (EVP_DecryptInit_ex(ctx, enc, NULL, NULL, NULL) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ EVP_CIPHER_free(enc);
+ enc = NULL;
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, ivlen, NULL) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ /* Initialise key and IV */
+ if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ /* Provide AAD. */
+ if (aadlen != 0 && aad != NULL) {
+ if (EVP_DecryptUpdate(ctx, NULL, &len, aad, aadlen) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ }
+ if (EVP_DecryptUpdate(ctx, pt, &len, ct, ctlen - taglen) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ *ptlen = len;
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG,
+ taglen, (void *)(ct + ctlen - taglen))) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ /* Finalise decryption. */
+ if (EVP_DecryptFinal_ex(ctx, pt + len, &len) <= 0) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ erv = 1;
+
+err:
+ if (erv != 1)
+ OPENSSL_cleanse(pt, *ptlen);
+ EVP_CIPHER_CTX_free(ctx);
+ EVP_CIPHER_free(enc);
+ return erv;
+}
+
+/**
+ * @brief do AEAD encryption as per the RFC
+ * @param libctx is the context to use
+ * @param propq is a properties string
+ * @param suite is the ciphersuite
+ * @param key is the secret
+ * @param keylen is the length of the secret
+ * @param iv is the initialisation vector
+ * @param ivlen is the length of the iv
+ * @param aad is the additional authenticated data
+ * @param aadlen is the length of the aad
+ * @param pt is the plaintext buffer
+ * @param ptlen is the length of pt
+ * @param ct is the output buffer
+ * @param ctlen input/output, needs space for tag on input, exact on output
+ * @return 1 for success, 0 otherwise
+ */
+static int hpke_aead_enc(OSSL_LIB_CTX *libctx, const char *propq,
+ OSSL_HPKE_SUITE suite,
+ const unsigned char *key, size_t keylen,
+ const unsigned char *iv, size_t ivlen,
+ const unsigned char *aad, size_t aadlen,
+ const unsigned char *pt, size_t ptlen,
+ unsigned char *ct, size_t *ctlen)
+{
+ int erv = 0;
+ EVP_CIPHER_CTX *ctx = NULL;
+ int len;
+ size_t taglen = 0;
+ const OSSL_HPKE_AEAD_INFO *aead_info = NULL;
+ EVP_CIPHER *enc = NULL;
+ unsigned char tag[16];
+
+ if (ct == NULL || ctlen == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ aead_info = ossl_HPKE_AEAD_INFO_find_id(suite.aead_id);
+ if (aead_info == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ taglen = aead_info->taglen;
+ if (*ctlen <= taglen || ptlen > *ctlen - taglen) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ goto err;
+ }
+ /* Create and initialise the context */
+ if ((ctx = EVP_CIPHER_CTX_new()) == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ /* Initialise the encryption operation. */
+ enc = EVP_CIPHER_fetch(libctx, aead_info->name, propq);
+ if (enc == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ if (EVP_EncryptInit_ex(ctx, enc, NULL, NULL, NULL) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ EVP_CIPHER_free(enc);
+ enc = NULL;
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, ivlen, NULL) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ /* Initialise key and IV */
+ if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ /* Provide any AAD data. */
+ if (aadlen != 0 && aad != NULL) {
+ if (EVP_EncryptUpdate(ctx, NULL, &len, aad, aadlen) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ }
+ if (EVP_EncryptUpdate(ctx, ct, &len, pt, ptlen) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ *ctlen = len;
+ /* Finalise the encryption. */
+ if (EVP_EncryptFinal_ex(ctx, ct + len, &len) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ *ctlen += len;
+ /* Get tag. Not a duplicate so needs to be added to the ciphertext */
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, taglen, tag) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ memcpy(ct + *ctlen, tag, taglen);
+ *ctlen += taglen;
+ erv = 1;
+
+err:
+ if (erv != 1)
+ OPENSSL_cleanse(ct, *ctlen);
+ EVP_CIPHER_CTX_free(ctx);
+ EVP_CIPHER_free(enc);
+ return erv;
+}
+
+/**
+ * @brief check mode is in-range and supported
+ * @param mode is the caller's chosen mode
+ * @return 1 for good mode, 0 otherwise
+ */
+static int hpke_mode_check(unsigned int mode)
+{
+ switch (mode) {
+ case OSSL_HPKE_MODE_BASE:
+ case OSSL_HPKE_MODE_PSK:
+ case OSSL_HPKE_MODE_AUTH:
+ case OSSL_HPKE_MODE_PSKAUTH:
+ break;
+ default:
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * @brief check if a suite is supported locally
+ * @param suite is the suite to check
+ * @return 1 for good, 0 otherwise
+ */
+static int hpke_suite_check(OSSL_HPKE_SUITE suite)
+{
+ /* check KEM, KDF and AEAD are supported here */
+ if (ossl_HPKE_KEM_INFO_find_id(suite.kem_id) == NULL)
+ return 0;
+ if (ossl_HPKE_KDF_INFO_find_id(suite.kdf_id) == NULL)
+ return 0;
+ if (ossl_HPKE_AEAD_INFO_find_id(suite.aead_id) == NULL)
+ return 0;
+ return 1;
+}
+
+/*
+ * @brief randomly pick a suite
+ * @param libctx is the context to use
+ * @param propq is a properties string
+ * @param suite is the result
+ * @return 1 for success, 0 otherwise
+ */
+static int hpke_random_suite(OSSL_LIB_CTX *libctx,
+ const char *propq,
+ OSSL_HPKE_SUITE *suite)
+{
+ const OSSL_HPKE_KEM_INFO *kem_info = NULL;
+ const OSSL_HPKE_KDF_INFO *kdf_info = NULL;
+ const OSSL_HPKE_AEAD_INFO *aead_info = NULL;
+
+ /* random kem, kdf and aead */
+ kem_info = ossl_HPKE_KEM_INFO_find_random(libctx);
+ if (kem_info == NULL)
+ return 0;
+ suite->kem_id = kem_info->kem_id;
+ kdf_info = ossl_HPKE_KDF_INFO_find_random(libctx);
+ if (kdf_info == NULL)
+ return 0;
+ suite->kdf_id = kdf_info->kdf_id;
+ aead_info = ossl_HPKE_AEAD_INFO_find_random(libctx);
+ if (aead_info == NULL)
+ return 0;
+ suite->aead_id = aead_info->aead_id;
+ return 1;
+}
+
+/*
+ * @brief tell the caller how big the ciphertext will be
+ *
+ * AEAD algorithms add a tag for data authentication.
+ * Those are almost always, but not always, 16 octets
+ * long, and who knows what will be true in the future.
+ * So this function allows a caller to find out how
+ * much data expansion they will see with a given suite.
+ *
+ * "enc" is the name used in RFC9180 for the encapsulated
+ * public value of the sender, who calls OSSL_HPKE_seal(),
+ * that is sent to the recipient, who calls OSSL_HPKE_open().
+ *
+ * @param suite is the suite to be used
+ * @param enclen points to what will be enc length
+ * @param clearlen is the length of plaintext
+ * @param cipherlen points to what will be ciphertext length (including tag)
+ * @return 1 for success, 0 otherwise
+ */
+static int hpke_expansion(OSSL_HPKE_SUITE suite,
+ size_t *enclen,
+ size_t clearlen,
+ size_t *cipherlen)
+{
+ const OSSL_HPKE_AEAD_INFO *aead_info = NULL;
+ const OSSL_HPKE_KEM_INFO *kem_info = NULL;
+
+ if (cipherlen == NULL || enclen == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ if (hpke_suite_check(suite) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ return 0;
+ }
+ aead_info = ossl_HPKE_AEAD_INFO_find_id(suite.aead_id);
+ if (aead_info == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ *cipherlen = clearlen + aead_info->taglen;
+ kem_info = ossl_HPKE_KEM_INFO_find_id(suite.kem_id);
+ if (kem_info == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ *enclen = kem_info->Nenc;
+ return 1;
+}
+
+/*
+ * @brief expand and XOR the 64-bit unsigned seq with (nonce) buffer
+ * @param ctx is the HPKE context
+ * @param buf is the buffer for the XOR'd seq and nonce
+ * @param blen is the size of buf
+ * @return 0 for error, otherwise blen
+ */
+static size_t hpke_seqnonce2buf(OSSL_HPKE_CTX *ctx,
+ unsigned char *buf, size_t blen)
+{
+ size_t i;
+ uint64_t seq_copy;
+
+ if (ctx == NULL || blen < sizeof(seq_copy) || blen != ctx->noncelen)
+ return 0;
+ seq_copy = ctx->seq;
+ memset(buf, 0, blen);
+ for (i = 0; i < sizeof(seq_copy); i++) {
+ buf[blen - i - 1] = seq_copy & 0xff;
+ seq_copy >>= 8;
+ }
+ for (i = 0; i < blen; i++)
+ buf[i] ^= ctx->nonce[i];
+ return blen;
+}
+
+/*
+ * @brief call the underlying KEM to encap
+ * @param ctx is the OSSL_HPKE_CTX
+ * @param enc is a buffer for the sender's ephemeral public value
+ * @param enclen is the size of enc on input, number of octets used on ouptut
+ * @param pub is the recipient's public value
+ * @param publen is the length of pub
+ * @return 1 for success, 0 for error
+ */
+static int hpke_encap(OSSL_HPKE_CTX *ctx, unsigned char *enc, size_t *enclen,
+ const unsigned char *pub, size_t publen)
+{
+ int erv = 0;
+ OSSL_PARAM params[3], *p = params;
+ size_t lsslen = 0;
+ EVP_PKEY_CTX *pctx = NULL;
+ EVP_PKEY *pkR = NULL;
+ const OSSL_HPKE_KEM_INFO *kem_info = NULL;
+
+ if (ctx == NULL || enc == NULL || enclen == NULL || *enclen == 0
+ || pub == NULL || publen == 0) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ if (ctx->shared_secret != NULL) {
+ /* only run the KEM once per OSSL_HPKE_CTX */
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+ return 0;
+ }
+ kem_info = ossl_HPKE_KEM_INFO_find_id(ctx->suite.kem_id);
+ if (kem_info == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ if (hpke_kem_id_nist_curve(ctx->suite.kem_id) == 1) {
+ pkR = evp_pkey_new_raw_nist_public_key(ctx->libctx, ctx->propq,
+ kem_info->groupname,
+ pub, publen);
+ } else {
+ pkR = EVP_PKEY_new_raw_public_key_ex(ctx->libctx,
+ kem_info->keytype,
+ ctx->propq, pub, publen);
+ }
+ if (pkR == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ pctx = EVP_PKEY_CTX_new_from_pkey(ctx->libctx, pkR, ctx->propq);
+ if (pctx == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KEM_PARAM_OPERATION,
+ OSSL_KEM_PARAM_OPERATION_DHKEM,
+ 0);
+ if (ctx->ikme != NULL) {
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_KEM_PARAM_IKME,
+ ctx->ikme, ctx->ikmelen);
+ }
+ *p = OSSL_PARAM_construct_end();
+ if (ctx->mode == OSSL_HPKE_MODE_AUTH
+ || ctx->mode == OSSL_HPKE_MODE_PSKAUTH) {
+ if (EVP_PKEY_auth_encapsulate_init(pctx, ctx->authpriv,
+ params) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ } else {
+ if (EVP_PKEY_encapsulate_init(pctx, params) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ }
+ if (EVP_PKEY_encapsulate(pctx, NULL, enclen, NULL, &lsslen) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ ctx->shared_secret = OPENSSL_malloc(lsslen);
+ if (ctx->shared_secret == NULL)
+ goto err;
+ ctx->shared_secretlen = lsslen;
+ if (EVP_PKEY_encapsulate(pctx, enc, enclen, ctx->shared_secret,
+ &ctx->shared_secretlen) != 1) {
+ ctx->shared_secretlen = 0;
+ OPENSSL_free(ctx->shared_secret);
+ ctx->shared_secret = NULL;
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ erv = 1;
+
+err:
+ EVP_PKEY_CTX_free(pctx);
+ EVP_PKEY_free(pkR);
+ return erv;
+}
+
+/*
+ * @brief call the underlying KEM to decap
+ * @param ctx is the OSSL_HPKE_CTX
+ * @param enc is a buffer for the sender's ephemeral public value
+ * @param enclen is the length of enc
+ * @param priv is the recipient's private value
+ * @return 1 for success, 0 for error
+ */
+static int hpke_decap(OSSL_HPKE_CTX *ctx,
+ const unsigned char *enc, size_t enclen,
+ EVP_PKEY *priv)
+{
+ int erv = 0;
+ EVP_PKEY_CTX *pctx = NULL;
+ EVP_PKEY *spub = NULL;
+ OSSL_PARAM params[2], *p = params;
+ size_t lsslen = 0;
+
+ if (ctx == NULL || enc == NULL || enclen == 0 || priv == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ if (ctx->shared_secret != NULL) {
+ /* only run the KEM once per OSSL_HPKE_CTX */
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+ return 0;
+ }
+ pctx = EVP_PKEY_CTX_new_from_pkey(ctx->libctx, priv, ctx->propq);
+ if (pctx == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KEM_PARAM_OPERATION,
+ OSSL_KEM_PARAM_OPERATION_DHKEM,
+ 0);
+ *p = OSSL_PARAM_construct_end();
+ if (ctx->mode == OSSL_HPKE_MODE_AUTH
+ || ctx->mode == OSSL_HPKE_MODE_PSKAUTH) {
+ const OSSL_HPKE_KEM_INFO *kem_info = NULL;
+
+ kem_info = ossl_HPKE_KEM_INFO_find_id(ctx->suite.kem_id);
+ if (kem_info == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ if (hpke_kem_id_nist_curve(ctx->suite.kem_id) == 1) {
+ spub = evp_pkey_new_raw_nist_public_key(ctx->libctx, ctx->propq,
+ kem_info->groupname,
+ ctx->authpub,
+ ctx->authpublen);
+ } else {
+ spub = EVP_PKEY_new_raw_public_key_ex(ctx->libctx,
+ kem_info->keytype,
+ ctx->propq,
+ ctx->authpub,
+ ctx->authpublen);
+ }
+ if (spub == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ if (EVP_PKEY_auth_decapsulate_init(pctx, spub, params) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ } else {
+ if (EVP_PKEY_decapsulate_init(pctx, params) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ }
+ if (EVP_PKEY_decapsulate(pctx, NULL, &lsslen, enc, enclen) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ ctx->shared_secret = OPENSSL_malloc(lsslen);
+ if (ctx->shared_secret == NULL)
+ goto err;
+ if (EVP_PKEY_decapsulate(pctx, ctx->shared_secret, &lsslen,
+ enc, enclen) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ ctx->shared_secretlen = lsslen;
+ erv = 1;
+
+err:
+ EVP_PKEY_CTX_free(pctx);
+ EVP_PKEY_free(spub);
+ if (erv == 0) {
+ OPENSSL_free(ctx->shared_secret);
+ ctx->shared_secret = NULL;
+ ctx->shared_secretlen = 0;
+ }
+ return erv;
+}
+
+/*
+ * @brief do "middle" of HPKE, between KEM and AEAD
+ * @param ctx is the OSSL_HPKE_CTX
+ * @param info is a buffer for the added binding information
+ * @param infolen is the length of info
+ * @return 0 for error, 1 for success
+ *
+ * This does all the HPKE extracts and expands as defined in RFC9180
+ * section 5.1, (badly termed there as a "key schedule") and sets the
+ * ctx fields for the shared_secret, nonce, key and exporter_secret
+ */
+static int hpke_do_middle(OSSL_HPKE_CTX *ctx,
+ const unsigned char *info, size_t infolen)
+{
+ int erv = 0;
+ size_t ks_contextlen = OSSL_HPKE_MAXSIZE;
+ unsigned char ks_context[OSSL_HPKE_MAXSIZE];
+ size_t halflen = 0;
+ size_t pskidlen = 0;
+ size_t psk_hashlen = OSSL_HPKE_MAXSIZE;
+ unsigned char psk_hash[OSSL_HPKE_MAXSIZE];
+ const OSSL_HPKE_AEAD_INFO *aead_info = NULL;
+ const OSSL_HPKE_KDF_INFO *kdf_info = NULL;
+ size_t secretlen = OSSL_HPKE_MAXSIZE;
+ unsigned char secret[OSSL_HPKE_MAXSIZE];
+ EVP_KDF_CTX *kctx = NULL;
+ unsigned char suitebuf[6];
+ const char *mdname = NULL;
+
+ /* only let this be done once */
+ if (ctx->exportersec != NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ if (ossl_HPKE_KEM_INFO_find_id(ctx->suite.kem_id) == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ aead_info = ossl_HPKE_AEAD_INFO_find_id(ctx->suite.aead_id);
+ if (aead_info == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ kdf_info = ossl_HPKE_KDF_INFO_find_id(ctx->suite.kdf_id);
+ if (kdf_info == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ mdname = kdf_info->mdname;
+ /* create key schedule context */
+ memset(ks_context, 0, sizeof(ks_context));
+ ks_context[0] = (unsigned char)(ctx->mode % 256);
+ ks_contextlen--; /* remaining space */
+ halflen = kdf_info->Nh;
+ if ((2 * halflen) > ks_contextlen) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ /* check a psk was set if in that mode */
+ if (ctx->mode == OSSL_HPKE_MODE_PSK
+ || ctx->mode == OSSL_HPKE_MODE_PSKAUTH) {
+ if (ctx->psk == NULL || ctx->psklen == 0 || ctx->pskid == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ }
+ kctx = ossl_kdf_ctx_create("HKDF", mdname, ctx->libctx, ctx->propq);
+ if (kctx == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ pskidlen = (ctx->psk == NULL ? 0 : strlen(ctx->pskid));
+ /* full suite details as per RFC9180 sec 5.1 */
+ suitebuf[0] = ctx->suite.kem_id / 256;
+ suitebuf[1] = ctx->suite.kem_id % 256;
+ suitebuf[2] = ctx->suite.kdf_id / 256;
+ suitebuf[3] = ctx->suite.kdf_id % 256;
+ suitebuf[4] = ctx->suite.aead_id / 256;
+ suitebuf[5] = ctx->suite.aead_id % 256;
+ if (ossl_hpke_labeled_extract(kctx, ks_context + 1, halflen,
+ NULL, 0, OSSL_HPKE_SEC51LABEL,
+ suitebuf, sizeof(suitebuf),
+ OSSL_HPKE_PSKIDHASH_LABEL,
+ (unsigned char *)ctx->pskid, pskidlen) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ if (ossl_hpke_labeled_extract(kctx, ks_context + 1 + halflen, halflen,
+ NULL, 0, OSSL_HPKE_SEC51LABEL,
+ suitebuf, sizeof(suitebuf),
+