summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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),
+ OSSL_HPKE_INFOHASH_LABEL,
+ (unsigned char *)info, infolen) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ ks_contextlen = 1 + 2 * halflen;
+ /* Extract and Expand variously... */
+ psk_hashlen = halflen;
+ if (ossl_hpke_labeled_extract(kctx, psk_hash, psk_hashlen,
+ NULL, 0, OSSL_HPKE_SEC51LABEL,
+ suitebuf, sizeof(suitebuf),
+ OSSL_HPKE_PSK_HASH_LABEL,
+ ctx->psk, ctx->psklen) != 1) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ secretlen = kdf_info->Nh;
+ if (secretlen > OSSL_HPKE_MAXSIZE) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+ goto err;
+ }
+ if (ossl_hpke_labele