Browse Source

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)
Stephen Farrell 1 year ago
parent
commit
ad062480f7

+ 9 - 0
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.
 

+ 2 - 0
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

+ 1 - 1
crypto/hpke/build.info

@@ -1,5 +1,5 @@
 LIBS=../../libcrypto
 
-$COMMON=hpke_util.c
+$COMMON=hpke_util.c hpke.c
 
 SOURCE[../../libcrypto]=$COMMON

+ 1439 - 0
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_labeled_extract(kctx, secret, secretlen,
+                                  ctx->shared_secret, ctx->shared_secretlen,
+                                  OSSL_HPKE_SEC51LABEL,
+                                  suitebuf, sizeof(suitebuf),
+                                  OSSL_HPKE_SECRET_LABEL,
+                                  ctx->psk, ctx->psklen) != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    if (ctx->suite.aead_id != OSSL_HPKE_AEAD_ID_EXPORTONLY) {
+        /* we only need nonce/key for non export AEADs */
+        ctx->noncelen = aead_info->Nn;
+        ctx->nonce = OPENSSL_malloc(ctx->noncelen);
+        if (ctx->nonce == NULL)
+            goto err;
+        if (ossl_hpke_labeled_expand(kctx, ctx->nonce, ctx->noncelen,
+                                     secret, secretlen, OSSL_HPKE_SEC51LABEL,
+                                     suitebuf, sizeof(suitebuf),
+                                     OSSL_HPKE_NONCE_LABEL,
+                                     ks_context, ks_contextlen) != 1) {
+            ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+            goto err;
+        }
+        ctx->keylen = aead_info->Nk;
+        ctx->key = OPENSSL_malloc(ctx->keylen);
+        if (ctx->key == NULL)
+            goto err;
+        if (ossl_hpke_labeled_expand(kctx, ctx->key, ctx->keylen,
+                                     secret, secretlen, OSSL_HPKE_SEC51LABEL,
+                                     suitebuf, sizeof(suitebuf),
+                                     OSSL_HPKE_KEY_LABEL,
+                                     ks_context, ks_contextlen) != 1) {
+            ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+            goto err;
+        }
+    }
+    ctx->exporterseclen = kdf_info->Nh;
+    ctx->exportersec = OPENSSL_malloc(ctx->exporterseclen);
+    if (ctx->exportersec == NULL)
+        goto err;
+    if (ossl_hpke_labeled_expand(kctx, ctx->exportersec, ctx->exporterseclen,
+                                 secret, secretlen, OSSL_HPKE_SEC51LABEL,
+                                 suitebuf, sizeof(suitebuf),
+                                 OSSL_HPKE_EXP_LABEL,
+                                 ks_context, ks_contextlen) != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    erv = 1;
+
+err:
+    OPENSSL_cleanse(ks_context, OSSL_HPKE_MAXSIZE);
+    OPENSSL_cleanse(psk_hash, OSSL_HPKE_MAXSIZE);
+    OPENSSL_cleanse(secret, OSSL_HPKE_MAXSIZE);
+    EVP_KDF_CTX_free(kctx);
+    return erv;
+}
+
+/*
+ * externally visible functions from below here, API documentation is
+ * in doc/man3/OSSL_HPKE_CTX_new.pod to avoid duplication
+ */
+
+OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite,
+                                 OSSL_LIB_CTX *libctx, const char *propq)
+{
+    OSSL_HPKE_CTX *ctx = NULL;
+
+    if (hpke_mode_check(mode) != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return NULL;
+    }
+    if (hpke_suite_check(suite) != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return NULL;
+    }
+    ctx = OPENSSL_zalloc(sizeof(*ctx));
+    if (ctx == NULL)
+        return NULL;
+    ctx->libctx = libctx;
+    if (propq != NULL) {
+        ctx->propq = OPENSSL_strdup(propq);
+        if (ctx->propq == NULL) {
+            OPENSSL_free(ctx);
+            return NULL;
+        }
+    }
+    ctx->mode = mode;
+    ctx->suite = suite;
+    return ctx;
+}
+
+void OSSL_HPKE_CTX_free(OSSL_HPKE_CTX *ctx)
+{
+    if (ctx == NULL)
+        return;
+    OPENSSL_free(ctx->propq);
+    OPENSSL_clear_free(ctx->exportersec, ctx->exporterseclen);
+    OPENSSL_free(ctx->pskid);
+    OPENSSL_clear_free(ctx->psk, ctx->psklen);
+    OPENSSL_clear_free(ctx->key, ctx->keylen);
+    OPENSSL_clear_free(ctx->nonce, ctx->noncelen);
+    OPENSSL_clear_free(ctx->shared_secret, ctx->shared_secretlen);
+    OPENSSL_clear_free(ctx->ikme, ctx->ikmelen);
+    EVP_PKEY_free(ctx->authpriv);
+    OPENSSL_free(ctx->authpub);
+
+    OPENSSL_free(ctx);
+    return;
+}
+
+int OSSL_HPKE_CTX_set1_psk(OSSL_HPKE_CTX *ctx,
+                           const char *pskid,
+                           const unsigned char *psk, size_t psklen)
+{
+    if (ctx == NULL || pskid == NULL || psk == NULL || psklen == 0) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if (psklen > OSSL_HPKE_MAX_PARMLEN) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+    if (strlen(pskid) > OSSL_HPKE_MAX_PARMLEN) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+    if (ctx->mode != OSSL_HPKE_MODE_PSK
+        && ctx->mode != OSSL_HPKE_MODE_PSKAUTH) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+    /* free previous values if any */
+    OPENSSL_clear_free(ctx->psk, ctx->psklen);
+    ctx->psk = OPENSSL_memdup(psk, psklen);
+    if (ctx->psk == NULL)
+        return 0;
+    ctx->psklen = psklen;
+    OPENSSL_free(ctx->pskid);
+    ctx->pskid = OPENSSL_strdup(pskid);
+    if (ctx->pskid == NULL) {
+        OPENSSL_clear_free(ctx->psk, ctx->psklen);
+        ctx->psk = NULL;
+        ctx->psklen = 0;
+        return 0;
+    }
+    return 1;
+}
+
+int OSSL_HPKE_CTX_set1_ikme(OSSL_HPKE_CTX *ctx,
+                            const unsigned char *ikme, size_t ikmelen)
+{
+    if (ctx == NULL || ikme == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if (ikmelen == 0 || ikmelen > OSSL_HPKE_MAX_PARMLEN) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+    OPENSSL_clear_free(ctx->ikme, ctx->ikmelen);
+    ctx->ikme = OPENSSL_memdup(ikme, ikmelen);
+    if (ctx->ikme == NULL)
+        return 0;
+    ctx->ikmelen = ikmelen;
+    return 1;
+}
+
+int OSSL_HPKE_CTX_set1_authpriv(OSSL_HPKE_CTX *ctx, EVP_PKEY *priv)
+{
+    if (ctx == NULL || priv == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if (ctx->mode != OSSL_HPKE_MODE_AUTH
+        && ctx->mode != OSSL_HPKE_MODE_PSKAUTH) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+    EVP_PKEY_free(ctx->authpriv);
+    ctx->authpriv = EVP_PKEY_dup(priv);
+    if (ctx->authpriv == NULL)
+        return 0;
+    return 1;
+}
+
+int OSSL_HPKE_CTX_set1_authpub(OSSL_HPKE_CTX *ctx,
+                               const unsigned char *pub, size_t publen)
+{
+    int erv = 0;
+    EVP_PKEY *pubp = NULL;
+    unsigned char *lpub = NULL;
+    size_t lpublen = 0;
+    const OSSL_HPKE_KEM_INFO *kem_info = NULL;
+
+    if (ctx == NULL || pub == NULL || publen == 0) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if (ctx->mode != OSSL_HPKE_MODE_AUTH
+        && ctx->mode != OSSL_HPKE_MODE_PSKAUTH) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+    /* check the value seems like a good public key for this kem */
+    kem_info = ossl_HPKE_KEM_INFO_find_id(ctx->suite.kem_id);
+    if (kem_info == NULL)
+        return 0;
+    if (hpke_kem_id_nist_curve(ctx->suite.kem_id) == 1) {
+        pubp = evp_pkey_new_raw_nist_public_key(ctx->libctx, ctx->propq,
+                                                kem_info->groupname,
+                                                pub, publen);
+    } else {
+        pubp = EVP_PKEY_new_raw_public_key_ex(ctx->libctx,
+                                              kem_info->keytype,
+                                              ctx->propq,
+                                              pub, publen);
+    }
+    if (pubp == NULL) {
+        /* can happen based on external input - buffer value may be garbage */
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        goto err;
+    }
+    /*
+     * extract out the public key in encoded form so we
+     * should be fine even if given compressed form
+     */
+    lpub = OPENSSL_malloc(OSSL_HPKE_MAXSIZE);
+    if (lpub == NULL)
+        goto err;
+    if (EVP_PKEY_get_octet_string_param(pubp,
+                                        OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY,
+                                        lpub, OSSL_HPKE_MAXSIZE, &lpublen)
+        != 1) {
+        OPENSSL_free(lpub);
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    /* free up old value */
+    OPENSSL_free(ctx->authpub);
+    ctx->authpub = lpub;
+    ctx->authpublen = lpublen;
+    erv = 1;
+
+err:
+    EVP_PKEY_free(pubp);
+    return erv;
+}
+
+int OSSL_HPKE_CTX_get_seq(OSSL_HPKE_CTX *ctx, uint64_t *seq)
+{
+    if (ctx == NULL || seq == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    *seq = ctx->seq;
+    return 1;
+}
+
+int OSSL_HPKE_CTX_set_seq(OSSL_HPKE_CTX *ctx, uint64_t seq)
+{
+    if (ctx == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    ctx->seq = seq;
+    return 1;
+}
+
+int OSSL_HPKE_encap(OSSL_HPKE_CTX *ctx,
+                    unsigned char *enc, size_t *enclen,
+                    const unsigned char *pub, size_t publen,
+                    const unsigned char *info, size_t infolen)
+{
+    int erv = 1;
+
+    if (ctx == NULL || enc == NULL || enclen == NULL || *enclen == 0
+        || pub == NULL || publen == 0) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if (infolen > OSSL_HPKE_MAX_INFOLEN) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+    if (ctx->shared_secret != NULL) {
+        /* only allow one encap per OSSL_HPKE_CTX */
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+        return 0;
+    }
+    if (hpke_encap(ctx, enc, enclen, pub, publen) != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+    /*
+     * note that the info is not part of the context as it
+     * only needs to be used once here so doesn't need to
+     * be stored
+     */
+    erv = hpke_do_middle(ctx, info, infolen);
+    return erv;
+}
+
+int OSSL_HPKE_decap(OSSL_HPKE_CTX *ctx,
+                    const unsigned char *enc, size_t enclen,
+                    EVP_PKEY *recippriv,
+                    const unsigned char *info, size_t infolen)
+{
+    int erv = 1;
+
+    if (ctx == NULL || enc == NULL || enclen == 0 || recippriv == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if (infolen > OSSL_HPKE_MAX_INFOLEN) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+    if (ctx->shared_secret != NULL) {
+        /* only allow one encap per OSSL_HPKE_CTX */
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+        return 0;
+    }
+    erv = hpke_decap(ctx, enc, enclen, recippriv);
+    if (erv != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+    /*
+     * note that the info is not part of the context as it
+     * only needs to be used once here so doesn't need to
+     * be stored
+     */
+    erv = hpke_do_middle(ctx, info, infolen);
+    return erv;
+}
+
+int OSSL_HPKE_seal(OSSL_HPKE_CTX *ctx,
+                   unsigned char *ct, size_t *ctlen,
+                   const unsigned char *aad, size_t aadlen,
+                   const unsigned char *pt, size_t ptlen)
+{
+    unsigned char seqbuf[OSSL_HPKE_MAX_NONCELEN];
+    size_t seqlen = 0;
+
+    if (ctx == NULL || ct == NULL || ctlen == NULL || *ctlen == 0
+        || pt == NULL || ptlen == 0) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if ((ctx->seq + 1) == 0) { /* wrap around imminent !!! */
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+        return 0;
+    }
+    if (ctx->key == NULL || ctx->nonce == NULL) {
+        /* need to have done an encap first, info can be NULL */
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+        return 0;
+    }
+    seqlen = hpke_seqnonce2buf(ctx, seqbuf, sizeof(seqbuf));
+    if (seqlen == 0) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+    if (hpke_aead_enc(ctx->libctx, ctx->propq, ctx->suite,
+                      ctx->key, ctx->keylen, seqbuf, ctx->noncelen,
+                      aad, aadlen, pt, ptlen, ct, ctlen) != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        OPENSSL_cleanse(seqbuf, sizeof(seqbuf));
+        return 0;
+    } else {
+        ctx->seq++;
+    }
+    OPENSSL_cleanse(seqbuf, sizeof(seqbuf));
+    return 1;
+}
+
+int OSSL_HPKE_open(OSSL_HPKE_CTX *ctx,
+                   unsigned char *pt, size_t *ptlen,
+                   const unsigned char *aad, size_t aadlen,
+                   const unsigned char *ct, size_t ctlen)
+{
+    unsigned char seqbuf[OSSL_HPKE_MAX_NONCELEN];
+    size_t seqlen = 0;
+
+    if (ctx == NULL || pt == NULL || ptlen == NULL || *ptlen == 0
+        || ct == NULL || ctlen == 0) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if ((ctx->seq + 1) == 0) { /* wrap around imminent !!! */
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+        return 0;
+    }
+    if (ctx->key == NULL || ctx->nonce == NULL) {
+        /* need to have done an encap first, info can be NULL */
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+        return 0;
+    }
+    seqlen = hpke_seqnonce2buf(ctx, seqbuf, sizeof(seqbuf));
+    if (seqlen == 0) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+    if (hpke_aead_dec(ctx->libctx, ctx->propq, ctx->suite,
+                      ctx->key, ctx->keylen, seqbuf, ctx->noncelen,
+                      aad, aadlen, ct, ctlen, pt, ptlen) != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        OPENSSL_cleanse(seqbuf, sizeof(seqbuf));
+        return 0;
+    }
+    ctx->seq++;
+    OPENSSL_cleanse(seqbuf, sizeof(seqbuf));
+    return 1;
+}
+
+int OSSL_HPKE_export(OSSL_HPKE_CTX *ctx,
+                     unsigned char *secret, size_t secretlen,
+                     const unsigned char *label, size_t labellen)
+{
+    int erv = 0;
+    EVP_KDF_CTX *kctx = NULL;
+    unsigned char suitebuf[6];
+    const char *mdname = NULL;
+    const OSSL_HPKE_KDF_INFO *kdf_info = NULL;
+
+    if (ctx == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if (labellen > OSSL_HPKE_MAX_PARMLEN) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+    if (ctx->exportersec == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+        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;
+    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;
+    }
+    /* full suiteid as per RFC9180 sec 5.3 */
+    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;
+    erv = ossl_hpke_labeled_expand(kctx, secret, secretlen,
+                                   ctx->exportersec, ctx->exporterseclen,
+                                   OSSL_HPKE_SEC51LABEL,
+                                   suitebuf, sizeof(suitebuf),
+                                   OSSL_HPKE_EXP_SEC_LABEL,
+                                   label, labellen);
+    EVP_KDF_CTX_free(kctx);
+    if (erv != 1)
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+    return erv;
+}
+
+int OSSL_HPKE_keygen(OSSL_HPKE_SUITE suite,
+                     unsigned char *pub, size_t *publen, EVP_PKEY **priv,
+                     const unsigned char *ikm, size_t ikmlen,
+                     OSSL_LIB_CTX *libctx, const char *propq)
+{
+    int erv = 0; /* Our error return value - 1 is success */
+    EVP_PKEY_CTX *pctx = NULL;
+    EVP_PKEY *skR = NULL;
+    const OSSL_HPKE_KEM_INFO *kem_info = NULL;
+    OSSL_PARAM params[3], *p = params;
+
+    if (pub == NULL || publen == NULL || *publen == 0 || priv == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if (hpke_suite_check(suite) != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+    if ((ikmlen > 0 && ikm == NULL)
+        || (ikmlen == 0 && ikm != NULL)
+        || ikmlen > OSSL_HPKE_MAX_PARMLEN) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+
+    kem_info = ossl_HPKE_KEM_INFO_find_id(suite.kem_id);
+    if (kem_info == NULL)
+        return 0;
+    if (hpke_kem_id_nist_curve(suite.kem_id) == 1) {
+        *p++ = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
+                                                (char *)kem_info->groupname, 0);
+        pctx = EVP_PKEY_CTX_new_from_name(libctx, "EC", propq);
+    } else {
+        pctx = EVP_PKEY_CTX_new_from_name(libctx, kem_info->keytype, propq);
+    }
+    if (pctx == NULL
+        || EVP_PKEY_keygen_init(pctx) <= 0) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    if (ikm != NULL)
+        *p++ = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_DHKEM_IKM,
+                                                 (char *)ikm, ikmlen);
+    *p = OSSL_PARAM_construct_end();
+    if (EVP_PKEY_CTX_set_params(pctx, params) <= 0) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    if (EVP_PKEY_generate(pctx, &skR) <= 0) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    EVP_PKEY_CTX_free(pctx);
+    pctx = NULL;
+    if (EVP_PKEY_get_octet_string_param(skR, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY,
+                                        pub, *publen, publen) != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    *priv = skR;
+    erv = 1;
+
+err:
+    if (erv != 1)
+        EVP_PKEY_free(skR);
+    EVP_PKEY_CTX_free(pctx);
+    return erv;
+}
+
+int OSSL_HPKE_suite_check(OSSL_HPKE_SUITE suite)
+{
+    return hpke_suite_check(suite);
+}
+
+int OSSL_HPKE_get_grease_value(OSSL_LIB_CTX *libctx, const char *propq,
+                               const OSSL_HPKE_SUITE *suite_in,
+                               OSSL_HPKE_SUITE *suite,
+                               unsigned char *enc,
+                               size_t *enclen,
+                               unsigned char *ct,
+                               size_t ctlen)
+{
+    OSSL_HPKE_SUITE chosen;
+    size_t plen = 0;
+    const OSSL_HPKE_KEM_INFO *kem_info = NULL;
+    const OSSL_HPKE_AEAD_INFO *aead_info = NULL;
+    EVP_PKEY *fakepriv = NULL;
+
+    if (enc == NULL || enclen == 0
+        || ct == NULL || ctlen == 0 || suite == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    if (suite_in == NULL) {
+        /* choose a random suite */
+        if (hpke_random_suite(libctx, propq, &chosen) != 1) {
+            ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+            goto err;
+        }
+    } else {
+        chosen = *suite_in;
+    }
+    kem_info = ossl_HPKE_KEM_INFO_find_id(chosen.kem_id);
+    if (kem_info == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    aead_info = ossl_HPKE_AEAD_INFO_find_id(chosen.aead_id);
+    if (aead_info == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    if (hpke_suite_check(chosen) != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    *suite = chosen;
+    /* make sure room for tag and one plaintext octet */
+    if (aead_info->taglen >= ctlen) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    /* publen */
+    plen = kem_info->Npk;
+    if (plen > *enclen) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    /*
+     * In order for our enc to look good for sure, we generate and then
+     * delete a real key for that curve - bit OTT but it ensures we do
+     * get the encoding right (e.g. 0x04 as 1st octet for NIST curves in
+     * uncompressed form) and that the value really does map to a point on
+     * the relevant curve.
+     */
+    if (OSSL_HPKE_keygen(chosen, enc, enclen, &fakepriv, NULL, 0,
+                         libctx, propq) != 1) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    EVP_PKEY_free(fakepriv);
+    if (RAND_bytes_ex(libctx, ct, ctlen, 0) <= 0) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    return 1;
+err:
+    return 0;
+}
+
+int OSSL_HPKE_str2suite(const char *str, OSSL_HPKE_SUITE *suite)
+{
+    return ossl_hpke_str2suite(str, suite);
+}
+
+size_t OSSL_HPKE_get_ciphertext_size(OSSL_HPKE_SUITE suite, size_t clearlen)
+{
+    size_t enclen = 0;
+    size_t cipherlen = 0;
+
+    if (hpke_expansion(suite, &enclen, clearlen, &cipherlen) != 1)
+        return 0;
+    return cipherlen;
+}
+
+size_t OSSL_HPKE_get_public_encap_size(OSSL_HPKE_SUITE suite)
+{
+    size_t enclen = 0;
+    size_t cipherlen = 0;
+    size_t clearlen = 16;
+
+    if (hpke_expansion(suite, &enclen, clearlen, &cipherlen) != 1)
+        return 0;
+    return enclen;
+}
+
+size_t OSSL_HPKE_get_recommended_ikmelen(OSSL_HPKE_SUITE suite)
+{
+    const OSSL_HPKE_KEM_INFO *kem_info = NULL;
+
+    if (hpke_suite_check(suite) != 1)
+        return 0;
+    kem_info = ossl_HPKE_KEM_INFO_find_id(suite.kem_id);
+    return kem_info->Nsk;
+}

+ 361 - 16
crypto/hpke/hpke_util.c

@@ -7,30 +7,239 @@
  * https://www.openssl.org/source/license.html
  */
 
+#include <string.h>
 #include <openssl/core_names.h>
 #include <openssl/kdf.h>
 #include <openssl/params.h>
 #include <openssl/err.h>
 #include <openssl/proverr.h>
-#include "crypto/hpke.h"
+#include <openssl/hpke.h>
+#include <openssl/sha.h>
+#include <openssl/rand.h>
+#include "crypto/ecx.h"
+#include "internal/hpke_util.h"
 #include "internal/packet.h"
+#include "internal/nelem.h"
 
 /*
- * The largest value happens inside dhkem_extract_and_expand
- * Which consists of a max dkmlen of 2*privkeylen + suiteid + small label
+ * Delimiter used in OSSL_HPKE_str2suite
  */
-#define LABELED_EXTRACT_SIZE (10 + 12 + 2 * OSSL_HPKE_MAX_PRIVATE)
+#define OSSL_HPKE_STR_DELIMCHAR ','
 
 /*
- * The largest value happens inside dhkem_extract_and_expand
- * Which consists of a prklen of secretlen + contextlen of 3 encoded public keys
- * + suiteid + small label
+ * table with identifier and synonym strings
+ * right now, there are 4 synonyms for each - a name, a hex string
+ * a hex string with a leading zero and a decimal string - more
+ * could be added but that seems like enough
  */
-#define LABELED_EXPAND_SIZE (LABELED_EXTRACT_SIZE + 3 * OSSL_HPKE_MAX_PUBLIC)
+typedef struct {
+    uint16_t id;
+    char *synonyms[4];
+} synonymttab_t;
 
+/* max length of string we'll try map to a suite */
+#define OSSL_HPKE_MAX_SUITESTR 38
+
+/* Define HPKE labels from RFC9180 in hex for EBCDIC compatibility */
 /* ASCII: "HPKE-v1", in hex for EBCDIC compatibility */
 static const char LABEL_HPKEV1[] = "\x48\x50\x4B\x45\x2D\x76\x31";
 
+/*
+ * Note that if additions are made to the set of IANA codepoints
+ * and the tables below, corresponding additions should also be
+ * made to the synonymtab tables a little further down so that
+ * OSSL_HPKE_str2suite() continues to function correctly.
+ *
+ * The canonical place to check for IANA registered codepoints
+ * is: https://www.iana.org/assignments/hpke/hpke.xhtml
+ */
+
+/*
+ * @brief table of KEMs
+ * See RFC9180 Section 7.1 "Table 2 KEM IDs"
+ */
+static const OSSL_HPKE_KEM_INFO hpke_kem_tab[] = {
+#ifndef OPENSSL_NO_EC
+    { OSSL_HPKE_KEM_ID_P256, "EC", OSSL_HPKE_KEMSTR_P256,
+      LN_sha256, SHA256_DIGEST_LENGTH, 65, 65, 32, 0xFF },
+    { OSSL_HPKE_KEM_ID_P384, "EC", OSSL_HPKE_KEMSTR_P384,
+      LN_sha384, SHA384_DIGEST_LENGTH, 97, 97, 48, 0xFF },
+    { OSSL_HPKE_KEM_ID_P521, "EC", OSSL_HPKE_KEMSTR_P521,
+      LN_sha512, SHA512_DIGEST_LENGTH, 133, 133, 66, 0x01 },
+    { OSSL_HPKE_KEM_ID_X25519, OSSL_HPKE_KEMSTR_X25519, NULL,
+      LN_sha256, SHA256_DIGEST_LENGTH,
+      X25519_KEYLEN, X25519_KEYLEN, X25519_KEYLEN, 0x00 },
+    { OSSL_HPKE_KEM_ID_X448, OSSL_HPKE_KEMSTR_X448, NULL,
+      LN_sha512, SHA512_DIGEST_LENGTH,
+      X448_KEYLEN, X448_KEYLEN, X448_KEYLEN, 0x00 }
+#else
+    { OSSL_HPKE_KEM_ID_RESERVED, NULL, NULL, NULL, 0, 0, 0, 0, 0x00 }
+#endif
+};
+
+/*
+ * @brief table of AEADs
+ * See RFC9180 Section 7.2 "Table 3 KDF IDs"
+ */
+static const OSSL_HPKE_AEAD_INFO hpke_aead_tab[] = {
+    { OSSL_HPKE_AEAD_ID_AES_GCM_128, LN_aes_128_gcm, 16, 16,
+      OSSL_HPKE_MAX_NONCELEN },
+    { OSSL_HPKE_AEAD_ID_AES_GCM_256, LN_aes_256_gcm, 16, 32,
+      OSSL_HPKE_MAX_NONCELEN },
+#ifndef OPENSSL_NO_CHACHA20
+# ifndef OPENSSL_NO_POLY1305
+    { OSSL_HPKE_AEAD_ID_CHACHA_POLY1305, LN_chacha20_poly1305, 16, 32,
+      OSSL_HPKE_MAX_NONCELEN },
+# endif
+    { OSSL_HPKE_AEAD_ID_EXPORTONLY, NULL, 0, 0, 0 }
+#endif
+};
+
+/*
+ * @brief table of KDFs
+ * See RFC9180 Section 7.3 "Table 5 AEAD IDs"
+ */
+static const OSSL_HPKE_KDF_INFO hpke_kdf_tab[] = {
+    { OSSL_HPKE_KDF_ID_HKDF_SHA256, LN_sha256, SHA256_DIGEST_LENGTH },
+    { OSSL_HPKE_KDF_ID_HKDF_SHA384, LN_sha384, SHA384_DIGEST_LENGTH },
+    { OSSL_HPKE_KDF_ID_HKDF_SHA512, LN_sha512, SHA512_DIGEST_LENGTH }
+};
+
+/**
+ * Synonym tables for KEMs, KDFs and AEADs: idea is to allow
+ * mapping strings to suites with a little flexibility in terms
+ * of allowing a name or a couple of forms of number (for
+ * the IANA codepoint). If new IANA codepoints are allocated
+ * then these tables should be updated at the same time as the
+ * others above.
+ *
+ * The function to use these is ossl_hpke_str2suite() further down
+ * this file and shouln't need modification so long as the table
+ * sizes (i.e. allow exactly 4 synonyms) don't change.
+ */
+static const synonymttab_t kemstrtab[] = {
+    {OSSL_HPKE_KEM_ID_P256,
+     {OSSL_HPKE_KEMSTR_P256, "0x10", "0x10", "16" }},
+    {OSSL_HPKE_KEM_ID_P384,
+     {OSSL_HPKE_KEMSTR_P384, "0x11", "0x11", "17" }},
+    {OSSL_HPKE_KEM_ID_P521,
+     {OSSL_HPKE_KEMSTR_P521, "0x12", "0x12", "18" }},
+    {OSSL_HPKE_KEM_ID_X25519,
+     {OSSL_HPKE_KEMSTR_X25519, "0x20", "0x20", "32" }},
+    {OSSL_HPKE_KEM_ID_X448,
+     {OSSL_HPKE_KEMSTR_X448, "0x21", "0x21", "33" }}
+};
+static const synonymttab_t kdfstrtab[] = {
+    {OSSL_HPKE_KDF_ID_HKDF_SHA256,
+     {OSSL_HPKE_KDFSTR_256, "0x1", "0x01", "1"}},
+    {OSSL_HPKE_KDF_ID_HKDF_SHA384,
+     {OSSL_HPKE_KDFSTR_384, "0x2", "0x02", "2"}},
+    {OSSL_HPKE_KDF_ID_HKDF_SHA512,
+     {OSSL_HPKE_KDFSTR_512, "0x3", "0x03", "3"}}
+};
+static const synonymttab_t aeadstrtab[] = {
+    {OSSL_HPKE_AEAD_ID_AES_GCM_128,
+     {OSSL_HPKE_AEADSTR_AES128GCM, "0x1", "0x01", "1"}},
+    {OSSL_HPKE_AEAD_ID_AES_GCM_256,
+     {OSSL_HPKE_AEADSTR_AES256GCM, "0x2", "0x02", "2"}},
+    {OSSL_HPKE_AEAD_ID_CHACHA_POLY1305,
+     {OSSL_HPKE_AEADSTR_CP, "0x3", "0x03", "3"}},
+    {OSSL_HPKE_AEAD_ID_EXPORTONLY,
+     {OSSL_HPKE_AEADSTR_EXP, "ff", "0xff", "255"}}
+};
+
+/* Return an object containing KEM constants associated with a EC curve name */
+const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_curve(const char *curve)
+{
+    int i, sz = OSSL_NELEM(hpke_kem_tab);
+
+    for (i = 0; i < sz; ++i) {
+        const char *group = hpke_kem_tab[i].groupname;
+
+        if (group == NULL)
+            group = hpke_kem_tab[i].keytype;
+        if (OPENSSL_strcasecmp(curve, group) == 0)
+            return &hpke_kem_tab[i];
+    }
+    ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CURVE);
+    return NULL;
+}
+
+const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_id(uint16_t kemid)
+{
+    int i, sz = OSSL_NELEM(hpke_kem_tab);
+
+    /*
+     * this check can happen if we're in a no-ec build and there are no
+     * KEMS available
+     */
+    if (kemid == OSSL_HPKE_KEM_ID_RESERVED) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CURVE);
+        return NULL;
+    }
+    for (i = 0; i != sz; ++i) {
+        if (hpke_kem_tab[i].kem_id == kemid)
+            return &hpke_kem_tab[i];
+    }
+    ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CURVE);
+    return NULL;
+}
+
+const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_random(OSSL_LIB_CTX *ctx)
+{
+    unsigned char rval = 0;
+    int sz = OSSL_NELEM(hpke_kem_tab);
+
+    if (RAND_bytes_ex(ctx, &rval, sizeof(rval), 0) <= 0)
+        return NULL;
+    return &hpke_kem_tab[rval % sz];
+}
+
+const OSSL_HPKE_KDF_INFO *ossl_HPKE_KDF_INFO_find_id(uint16_t kdfid)
+{
+    int i, sz = OSSL_NELEM(hpke_kdf_tab);
+
+    for (i = 0; i != sz; ++i) {
+        if (hpke_kdf_tab[i].kdf_id == kdfid)
+            return &hpke_kdf_tab[i];
+    }
+    ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KDF);
+    return NULL;
+}
+
+const OSSL_HPKE_KDF_INFO *ossl_HPKE_KDF_INFO_find_random(OSSL_LIB_CTX *ctx)
+{
+    unsigned char rval = 0;
+    int sz = OSSL_NELEM(hpke_kdf_tab);
+
+    if (RAND_bytes_ex(ctx, &rval, sizeof(rval), 0) <= 0)
+        return NULL;
+    return &hpke_kdf_tab[rval % sz];
+}
+
+const OSSL_HPKE_AEAD_INFO *ossl_HPKE_AEAD_INFO_find_id(uint16_t aeadid)
+{
+    int i, sz = OSSL_NELEM(hpke_aead_tab);
+
+    for (i = 0; i != sz; ++i) {
+        if (hpke_aead_tab[i].aead_id == aeadid)
+            return &hpke_aead_tab[i];
+    }
+    ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_AEAD);
+    return NULL;
+}
+
+const OSSL_HPKE_AEAD_INFO *ossl_HPKE_AEAD_INFO_find_random(OSSL_LIB_CTX *ctx)
+{
+    unsigned char rval = 0;
+    /* the minus 1 below is so we don't pick the EXPORTONLY codepoint */
+    int sz = OSSL_NELEM(hpke_aead_tab) - 1;
+
+    if (RAND_bytes_ex(ctx, &rval, sizeof(rval), 0) <= 0)
+        return NULL;
+    return &hpke_aead_tab[rval % sz];
+}
+
 static int kdf_derive(EVP_KDF_CTX *kctx,
                       unsigned char *out, size_t outlen, int mode,
                       const unsigned char *salt, size_t saltlen,
@@ -82,20 +291,34 @@ int ossl_hpke_kdf_expand(EVP_KDF_CTX *kctx,
 int ossl_hpke_labeled_extract(EVP_KDF_CTX *kctx,
                               unsigned char *prk, size_t prklen,
                               const unsigned char *salt, size_t saltlen,
+                              const char *protocol_label,
                               const unsigned char *suiteid, size_t suiteidlen,
                               const char *label,
                               const unsigned char *ikm, size_t ikmlen)
 {
     int ret = 0;
+    size_t label_hpkev1len = 0;
+    size_t protocol_labellen = 0;
+    size_t labellen = 0;
     size_t labeled_ikmlen = 0;
-    unsigned char labeled_ikm[LABELED_EXTRACT_SIZE];
+    unsigned char *labeled_ikm = NULL;
     WPACKET pkt;
 
+    label_hpkev1len = strlen(LABEL_HPKEV1);
+    protocol_labellen = strlen(protocol_label);
+    labellen = strlen(label);
+    labeled_ikmlen = label_hpkev1len + protocol_labellen
+        + suiteidlen + labellen + ikmlen;
+    labeled_ikm = OPENSSL_malloc(labeled_ikmlen);
+    if (labeled_ikm == NULL)
+        return 0;
+
     /* labeled_ikm = concat("HPKE-v1", suiteid, label, ikm) */
-    if (!WPACKET_init_static_len(&pkt, labeled_ikm, sizeof(labeled_ikm), 0)
-            || !WPACKET_memcpy(&pkt, LABEL_HPKEV1, strlen(LABEL_HPKEV1))
+    if (!WPACKET_init_static_len(&pkt, labeled_ikm, labeled_ikmlen, 0)
+            || !WPACKET_memcpy(&pkt, LABEL_HPKEV1, label_hpkev1len)
+            || !WPACKET_memcpy(&pkt, protocol_label, protocol_labellen)
             || !WPACKET_memcpy(&pkt, suiteid, suiteidlen)
-            || !WPACKET_memcpy(&pkt, label, strlen(label))
+            || !WPACKET_memcpy(&pkt, label, labellen)
             || !WPACKET_memcpy(&pkt, ikm, ikmlen)
             || !WPACKET_get_total_written(&pkt, &labeled_ikmlen)
             || !WPACKET_finish(&pkt)) {
@@ -108,6 +331,7 @@ int ossl_hpke_labeled_extract(EVP_KDF_CTX *kctx,
 end:
     WPACKET_cleanup(&pkt);
     OPENSSL_cleanse(labeled_ikm, labeled_ikmlen);
+    OPENSSL_free(labeled_ikm);
     return ret;
 }
 
@@ -117,21 +341,35 @@ end:
 int ossl_hpke_labeled_expand(EVP_KDF_CTX *kctx,
                              unsigned char *okm, size_t okmlen,
                              const unsigned char *prk, size_t prklen,
+                             const char *protocol_label,
                              const unsigned char *suiteid, size_t suiteidlen,
                              const char *label,
                              const unsigned char *info, size_t infolen)
 {
     int ret = 0;
+    size_t label_hpkev1len = 0;
+    size_t protocol_labellen = 0;
+    size_t labellen = 0;
     size_t labeled_infolen = 0;
-    unsigned char labeled_info[LABELED_EXPAND_SIZE];
+    unsigned char *labeled_info = NULL;
     WPACKET pkt;
 
+    label_hpkev1len = strlen(LABEL_HPKEV1);
+    protocol_labellen = strlen(protocol_label);
+    labellen = strlen(label);
+    labeled_infolen = 2 + okmlen + prklen + label_hpkev1len
+        + protocol_labellen + suiteidlen + labellen + infolen;
+    labeled_info = OPENSSL_malloc(labeled_infolen);
+    if (labeled_info == NULL)
+        return 0;
+
     /* labeled_info = concat(okmlen, "HPKE-v1", suiteid, label, info) */
-    if (!WPACKET_init_static_len(&pkt, labeled_info, sizeof(labeled_info), 0)
+    if (!WPACKET_init_static_len(&pkt, labeled_info, labeled_infolen, 0)
             || !WPACKET_put_bytes_u16(&pkt, okmlen)
-            || !WPACKET_memcpy(&pkt, LABEL_HPKEV1, strlen(LABEL_HPKEV1))
+            || !WPACKET_memcpy(&pkt, LABEL_HPKEV1, label_hpkev1len)
+            || !WPACKET_memcpy(&pkt, protocol_label, protocol_labellen)
             || !WPACKET_memcpy(&pkt, suiteid, suiteidlen)
-            || !WPACKET_memcpy(&pkt, label, strlen(label))
+            || !WPACKET_memcpy(&pkt, label, labellen)
             || !WPACKET_memcpy(&pkt, info, infolen)
             || !WPACKET_get_total_written(&pkt, &labeled_infolen)
             || !WPACKET_finish(&pkt)) {
@@ -143,6 +381,7 @@ int ossl_hpke_labeled_expand(EVP_KDF_CTX *kctx,
                                prk, prklen, labeled_info, labeled_infolen);
 end:
     WPACKET_cleanup(&pkt);
+    OPENSSL_free(labeled_info);
     return ret;
 }
 
@@ -173,3 +412,109 @@ EVP_KDF_CTX *ossl_kdf_ctx_create(const char *kdfname, const char *mdname,
     }
     return kctx;
 }
+
+/*
+ * @brief look for a label into the synonym tables, and return its id
+ * @param st is the string value
+ * @param synp is the synonyms labels array
+ * @param arrsize is the previous array size
+ * @return 0 when not found, else the matching item id.
+ */
+static uint16_t synonyms_name2id(const char *st, const synonymttab_t *synp,
+                                 size_t arrsize)
+{
+    size_t i, j;
+
+    for (i = 0; i < arrsize; ++i) {
+        for (j = 0; j < OSSL_NELEM(synp[i].synonyms); ++j) {
+            if (OPENSSL_strcasecmp(st, synp[i].synonyms[j]) == 0)
+                return synp[i].id;
+        }
+    }
+    return 0;
+}
+
+/*
+ * @brief map a string to a HPKE suite based on synonym tables
+ * @param str is the string value
+ * @param suite is the resulting suite
+ * @return 1 for success, otherwise failure
+ */
+int ossl_hpke_str2suite(const char *suitestr, OSSL_HPKE_SUITE *suite)
+{
+    uint16_t kem = 0, kdf = 0, aead = 0;
+    char *st = NULL, *instrcp = NULL;
+    size_t inplen;
+    int labels = 0, result = 0;
+    int delim_count = 0;
+
+    if (suitestr == NULL || suitestr[0] == 0x00 || suite == NULL) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    inplen = OPENSSL_strnlen(suitestr, OSSL_HPKE_MAX_SUITESTR);
+    if (inplen >= OSSL_HPKE_MAX_SUITESTR) {
+        ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+
+    /*
+     * we don't want a delimiter at the end of the string;
+     * strtok_r/s() doesn't care about that, so we should
+     */
+    if (suitestr[inplen - 1] == OSSL_HPKE_STR_DELIMCHAR)
+        return 0;
+    /* We want exactly two delimiters in the input string */
+    for (st = (char *)suitestr; *st != '\0'; st++) {
+        if (*st == OSSL_HPKE_STR_DELIMCHAR)
+            delim_count++;
+    }
+    if (delim_count != 2)
+        return 0;
+
+    /* Duplicate `suitestr` to allow its parsing  */
+    instrcp = OPENSSL_memdup(suitestr, inplen + 1);
+    if (instrcp == NULL)
+        goto fail;
+
+    /* See if it contains a mix of our strings and numbers */
+    st = instrcp;
+
+    while (st != NULL && labels < 3) {
+        char *cp = strchr(st, OSSL_HPKE_STR_DELIMCHAR);
+
+        /* add a NUL like strtok would if we're not at the end */
+        if (cp != NULL)
+            *cp = '\0';
+
+        /* check if string is known or number and if so handle appropriately */
+        if (labels == 0
+            && (kem = synonyms_name2id(st, kemstrtab,
+                                       OSSL_NELEM(kemstrtab))) == 0)
+            goto fail;
+        else if (labels == 1
+                 && (kdf = synonyms_name2id(st, kdfstrtab,
+                                            OSSL_NELEM(kdfstrtab))) == 0)
+            goto fail;
+        else if (labels == 2
+                 && (aead = synonyms_name2id(st, aeadstrtab,
+                                             OSSL_NELEM(aeadstrtab))) == 0)
+            goto fail;
+
+        if (cp == NULL)
+            st = NULL;
+        else
+            st = cp + 1;
+        ++labels;
+    }
+    if (st != NULL || labels != 3)
+        goto fail;
+    suite->kem_id = kem;
+    suite->kdf_id = kdf;
+    suite->aead_id = aead;
+    result = 1;
+
+fail:
+    OPENSSL_free(instrcp);
+    return result;
+}

+ 6 - 0
doc/build.info

@@ -1651,6 +1651,10 @@ DEPEND[html/man3/OSSL_ESS_check_signing_certs.html]=man3/OSSL_ESS_check_signing_
 GENERATE[html/man3/OSSL_ESS_check_signing_certs.html]=man3/OSSL_ESS_check_signing_certs.pod
 DEPEND[man/man3/OSSL_ESS_check_signing_certs.3]=man3/OSSL_ESS_check_signing_certs.pod
 GENERATE[man/man3/OSSL_ESS_check_signing_certs.3]=man3/OSSL_ESS_check_signing_certs.pod
+DEPEND[html/man3/OSSL_HPKE_CTX_new.html]=man3/OSSL_HPKE_CTX_new.pod
+GENERATE[html/man3/OSSL_HPKE_CTX_new.html]=man3/OSSL_HPKE_CTX_new.pod
+DEPEND[man/man3/OSSL_HPKE_CTX_new.3]=man3/OSSL_HPKE_CTX_new.pod
+GENERATE[man/man3/OSSL_HPKE_CTX_new.3]=man3/OSSL_HPKE_CTX_new.pod
 DEPEND[html/man3/OSSL_HTTP_REQ_CTX.html]=man3/OSSL_HTTP_REQ_CTX.pod
 GENERATE[html/man3/OSSL_HTTP_REQ_CTX.html]=man3/OSSL_HTTP_REQ_CTX.pod
 DEPEND[man/man3/OSSL_HTTP_REQ_CTX.3]=man3/OSSL_HTTP_REQ_CTX.pod
@@ -3188,6 +3192,7 @@ html/man3/OSSL_ENCODER_CTX.html \
 html/man3/OSSL_ENCODER_CTX_new_for_pkey.html \
 html/man3/OSSL_ENCODER_to_bio.html \
 html/man3/OSSL_ESS_check_signing_certs.html \
+html/man3/OSSL_HPKE_CTX_new.html \
 html/man3/OSSL_HTTP_REQ_CTX.html \
 html/man3/OSSL_HTTP_parse_url.html \
 html/man3/OSSL_HTTP_transfer.html \
@@ -3794,6 +3799,7 @@ man/man3/OSSL_ENCODER_CTX.3 \
 man/man3/OSSL_ENCODER_CTX_new_for_pkey.3 \
 man/man3/OSSL_ENCODER_to_bio.3 \
 man/man3/OSSL_ESS_check_signing_certs.3 \
+man/man3/OSSL_HPKE_CTX_new.3 \
 man/man3/OSSL_HTTP_REQ_CTX.3 \
 man/man3/OSSL_HTTP_parse_url.3 \
 man/man3/OSSL_HTTP_transfer.3 \

+ 538 - 0
doc/man3/OSSL_HPKE_CTX_new.pod

@@ -0,0 +1,538 @@
+=pod
+
+=head1 NAME
+
+OSSL_HPKE_CTX_new, OSSL_HPKE_CTX_free,
+OSSL_HPKE_encap, OSSL_HPKE_decap,
+OSSL_HPKE_seal, OSSL_HPKE_open, OSSL_HPKE_export,
+OSSL_HPKE_suite_check, OSSL_HPKE_str2suite,
+OSSL_HPKE_keygen, OSSL_HPKE_get_grease_value,
+OSSL_HPKE_get_ciphertext_size, OSSL_HPKE_get_public_encap_size,
+OSSL_HPKE_get_recommended_ikmelen,
+OSSL_HPKE_CTX_set1_psk, OSSL_HPKE_CTX_set1_ikme,
+OSSL_HPKE_CTX_set1_authpriv, OSSL_HPKE_CTX_set1_authpub,
+OSSL_HPKE_CTX_get_seq, OSSL_HPKE_CTX_set_seq
+- Hybrid Public Key Encryption (HPKE) functions
+
+=head1 SYNOPSIS
+
+ #include <openssl/hpke.h>
+
+ typedef struct {
+     uint16_t    kem_id;
+     uint16_t    kdf_id;
+     uint16_t    aead_id;
+ } OSSL_HPKE_SUITE;
+
+ OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite,
+                                  OSSL_LIB_CTX *libctx, const char *propq);
+ void OSSL_HPKE_CTX_free(OSSL_HPKE_CTX *ctx);
+
+ int OSSL_HPKE_encap(OSSL_HPKE_CTX *ctx,
+                     unsigned char *enc, size_t *enclen,
+                     const unsigned char *pub, size_t publen,
+                     const unsigned char *info, size_t infolen);
+ int OSSL_HPKE_seal(OSSL_HPKE_CTX *ctx,
+                    unsigned char *ct, size_t *ctlen,
+                    const unsigned char *aad, size_t aadlen,
+                    const unsigned char *pt, size_t ptlen);
+
+ int OSSL_HPKE_keygen(OSSL_HPKE_SUITE suite,
+                      unsigned char *pub, size_t *publen, EVP_PKEY **priv,
+                      const unsigned char *ikm, size_t ikmlen,
+                      OSSL_LIB_CTX *libctx, const char *propq);
+ int OSSL_HPKE_decap(OSSL_HPKE_CTX *ctx,
+                     const unsigned char *enc, size_t enclen,
+                     EVP_PKEY *recippriv,
+                     const unsigned char *info, size_t infolen);
+ int OSSL_HPKE_open(OSSL_HPKE_CTX *ctx,
+                    unsigned char *pt, size_t *ptlen,
+                    const unsigned char *aad, size_t aadlen,
+                    const unsigned char *ct, size_t ctlen);
+
+ int OSSL_HPKE_export(OSSL_HPKE_CTX *ctx,
+                      unsigned char *secret, size_t secretlen,
+                      const unsigned char *label, size_t labellen);
+
+ int OSSL_HPKE_CTX_set1_authpriv(OSSL_HPKE_CTX *ctx, EVP_PKEY *priv);
+ int OSSL_HPKE_CTX_set1_authpub(OSSL_HPKE_CTX *ctx,
+                                unsigned char *pub, size_t publen);
+ int OSSL_HPKE_CTX_set1_psk(OSSL_HPKE_CTX *ctx,
+                            const char *pskid,
+                            const unsigned char *psk, size_t psklen);
+
+ int OSSL_HPKE_CTX_get_seq(OSSL_HPKE_CTX *ctx, uint64_t *seq);
+ int OSSL_HPKE_CTX_set_seq(OSSL_HPKE_CTX *ctx, uint64_t seq);
+
+ int OSSL_HPKE_CTX_set1_ikme(OSSL_HPKE_CTX *ctx,
+                             const unsigned char *ikme, size_t ikmelen);
+
+ int OSSL_HPKE_suite_check(OSSL_HPKE_SUITE suite);
+ int OSSL_HPKE_get_grease_value(OSSL_LIB_CTX *libctx, const char *propq,
+                                const OSSL_HPKE_SUITE *suite_in,
+                                OSSL_HPKE_SUITE *suite,
+                                unsigned char *enc, size_t *enclen,
+                                unsigned char *ct, size_t ctlen);
+
+ int OSSL_HPKE_str2suite(const char *str, OSSL_HPKE_SUITE *suite);
+ size_t OSSL_HPKE_get_ciphertext_size(OSSL_HPKE_SUITE suite, size_t clearlen);
+ size_t OSSL_HPKE_get_public_encap_size(OSSL_HPKE_SUITE suite);
+ size_t OSSL_HPKE_get_recommended_ikmelen(OSSL_HPKE_SUITE suite);
+
+=head1 DESCRIPTION
+
+These functions provide an API for using the form of Hybrid Public Key
+Encryption (HPKE) defined in RFC9180. Understanding the HPKE specification
+is likely required before using these APIs.  HPKE is used by various
+other IETF specifications, including the TLS Encrypted Client
+Hello (ECH) specification and others.
+
+HPKE is a standardised, highly flexible construct for encrypting "to" a public
+key that supports combinations of a key encapsulation method (KEM), a key
+derivation function (KDF) and an authenticated encryption with additional data
+(AEAD) algorithm, with optional sender authentication.
+
+The sender and a receiver here will generally be using some application or
+protocol making use of HPKE. For example, with ECH,
+the sender will be a browser and the receiver will be a web server.
+
+=head2 Data Structures
+
+B<OSSL_HPKE_SUITE> is a structure that holds identifiers for the algorithms
+used for KEM, KDF and AEAD operations.
+
+B<OSSL_HPKE_CTX> is a context that maintains internal state as HPKE
+operations are carried out. Separate B<OSSL_HPKE_CTX> objects must be used for
+the sender and receiver. Attempting to use a single context for both will
+result in errors.
+
+=head2 OSSL_HPKE_SUITE Identifiers
+
+The identifiers used by B<OSSL_HPKE_SUITE> are:
+
+The KEM identifier I<kem_id> is one of the following:
+
+=over 4
+
+=item 0x10 B<OSSL_HPKE_KEM_ID_P256>
+
+=item 0x11 B<OSSL_HPKE_KEM_ID_P384>
+
+=item 0x12 B<OSSL_HPKE_KEM_ID_P521>
+
+=item 0x20 B<OSSL_HPKE_KEM_ID_X25519>
+
+=item 0x21 B<OSSL_HPKE_KEM_ID_X448>
+
+=back
+
+The KDF identifier I<kdf_id> is one of the following:
+
+=over 4
+
+=item 0x01 B<OSSL_HPKE_KDF_ID_HKDF_SHA256>
+
+=item 0x02 B<OSSL_HPKE_KDF_ID_HKDF_SHA384>
+
+=item 0x03 B<OSSL_HPKE_KDF_ID_HKDF_SHA512>
+
+=back
+
+The AEAD identifier I<aead_id> is one of the following:
+
+=over 4
+
+=item 0x01 B<OSSL_HPKE_AEAD_ID_AES_GCM_128>
+
+=item 0x02 B<OSSL_HPKE_AEAD_ID_AES_GCM_256>
+
+=item 0x03 B<OSSL_HPKE_AEAD_ID_CHACHA_POLY1305>
+
+=item 0xFFFF B<OSSL_HPKE_AEAD_ID_EXPORTONLY>
+
+The last identifier above indicates that AEAD operations are not needed.
+OSSL_HPKE_export() can be used, but OSSL_HPKE_open() and OSSL_HPKE_seal() will
+return an error if called with a context using that AEAD identifier.
+
+=back
+
+=head2 HPKE Modes
+
+HPKE supports the following variants of Authentication using a mode Identifier:
+
+=over 4
+
+=item B<OSSL_HPKE_MODE_BASE>, 0x00
+
+Authentication is not used.
+
+=item B<OSSL_HPKE_MODE_PSK>, 0x01
+
+Authenticates possession of a pre-shared key (PSK).
+
+=item B<OSSL_HPKE_MODE_AUTH>, 0x02
+
+Authenticates possession of a KEM-based sender private key.
+
+=item B<OSSL_HPKE_MODE_PSKAUTH>, 0x03
+
+A combination of B<OSSL_HPKE_MODE_PSK> and B<OSSL_HPKE_MODE_AUTH>.
+Both the PSK and the senders authentication public/private must be
+supplied before the encapsulation/decapsulation operation will work.
+
+=back
+
+For further information related to authentication see L</Pre-Shared Key HPKE modes>
+and L</Sender-authenticated HPKE Modes>.
+
+=head2 Parameter Size Limits
+
+In order to improve interoperability, RFC9180, section 7.2.1 suggests a
+RECOMMENDED maximum size of 64 octets for various input parameters.  In this
+implementation we apply a limit of 66 octets for the I<ikmlen>, I<psklen>, and
+I<labellen> parameters, and for the length of the string I<pskid> for HPKE
+functions below. The constant I<OSSL_HPKE_MAX_PARMLEN> is defined as the limit
+of this value.  (We chose 66 octets so that we can validate all the test
+vectors present in RFC9180, Appendix A.)
+
+While RFC9180 also RECOMMENDS a 64 octet limit for the I<infolen> parameter,
+that is not sufficient for TLS Encrypted ClientHello (ECH) processing, so we
+enforce a limit of I<OSSL_HPKE_MAX_INFOLEN> with a value of 1024 as the limit
+for the I<infolen> parameter.
+
+=head2 Context Construct/Free
+
+OSSL_HPKE_CTX_new() creates a B<OSSL_HPKE_CTX> context object used for subsequent
+HPKE operations, given a I<mode> (See L</HPKE Modes>) and
+I<suite> (see L</OSSL_HPKE_SUITE Identifiers>). The I<libctx> and I<propq>
+are used when fetching algorithms from providers and may be set to NULL.
+
+OSSL_HPKE_CTX_free() frees the I<ctx> B<OSSL_HPKE_CTX> that was created previously
+by a call to OSSL_HPKE_CTX_new().
+
+=head2 Sender APIs
+
+A sender's goal is to use HPKE to encrypt using a public key, via use of a
+KEM, then a KDF and finally an AEAD.  The first step is to encapsulate (using
+OSSL_HPKE_encap()) the sender's public value using the recipient's public key,
+(I<pub>) and to internally derive secrets. This produces the encapsulated public value
+(I<enc>) to be sent to the recipient in whatever protocol is using HPKE. Having done the
+encapsulation step, the sender can then make one or more calls to
+OSSL_HPKE_seal() to encrypt plaintexts using the secret stored within I<ctx>.
+
+OSSL_HPKE_encap() uses the HPKE context I<ctx>, the recipient public value
+I<pub> of size I<publen>, and an optional I<info> parameter of size I<infolen>,
+to produce the encapsulated public value I<enc>.
+On input I<enclen> should contain the maximum size of the I<enc> buffer, and returns
+the output size. An error will occur if the input I<enclen> is
+smaller than the value returned from OSSL_HPKE_get_public_encap_size().
+I<info> may be used to bind other protocol or application artefacts such as identifiers.
+Generally, the encapsulated public value I<enc> corresponds to a
+single-use ephemeral private value created as part of the encapsulation
+process. Only a single call to OSSL_HPKE_encap() is allowed for a given
+B<OSSL_HPKE_CTX>.
+
+OSSL_HPKE_seal() takes the B<OSSL_HPKE_CTX> context I<ctx>, the plaintext
+buffer I<pt> of size I<ptlen> and optional additional authenticated data buffer
+I<aad> of size I<aadlen>, and returns the ciphertext I<ct> of size I<ctlen>.
+On input I<ctlen> should contain the maximum size of the I<ct> buffer, and returns
+the output size. An error will occur if the input I<ctlen> is
+smaller than the value returned from OSSL_HPKE_get_public_encap_size().
+
+OSSL_HPKE_encap() must be called before the OSSL_HPKE_seal().  OSSL_HPKE_seal()
+may be called multiple times, with an internal "nonce" being incremented by one
+after each call.
+
+=head2 Recipient APIs
+
+Recipients using HPKE require a typically less ephemeral private value so that
+the public value can be distributed to potential senders via whatever protocol
+is using HPKE. For this reason, recipients will generally first generate a key
+pair and will need to manage their private key value using standard mechanisms
+outside the scope of this API. Private keys use normal L<EVP_PKEY(3)> pointers
+so normal private key management mechanisms can be used for the relevant
+values.
+
+In order to enable encapsulation, the recipient needs to make it's public value
+available to the sender. There is no generic HPKE format defined for that - the
+relevant formatting is intended to be defined by the application/protocols that
+makes use of HPKE. ECH for example defines an ECHConfig data structure that
+combines the public value with other ECH data items. Normal library functions
+must therefore be used to extract the public value in the required format based
+on the L<EVP_PKEY(3)> for the private value.
+
+OSSL_HPKE_keygen() provides a way for recipients to generate a key pair based
+on the HPKE I<suite> to be used. It returns a L<EVP_PKEY(3)> pointer
+for the private value I<priv> and a encoded public key I<pub> of size I<publen>.
+On input I<publen> should contain the maximum size of the I<pub> buffer, and
+returns the output size. An error will occur if the input I<publen> is too small.
+The I<libctx> and I<propq> are used when fetching algorithms from providers
+and may be set to NULL.
+The HPKE specification also defines a deterministic key generation scheme where
+the private value is derived from initial keying material (IKM), so
+OSSL_HPKE_keygen() also has an option to use that scheme, using the I<ikm>
+parameter of size I<ikmlen>. If either I<ikm> is NULL or I<ikmlen> is zero,
+then a randomly generated key for the relevant I<suite> will be produced.
+If required I<ikmlen> should be greater than or equal to
+OSSL_HPKE_get_recommended_ikmelen().
+
+OSSL_HPKE_decap() takes as input the sender's encapsulated public value
+produced by OSSL_HPKE_encap() (I<enc>) and the recipient's L<EVP_PKEY(3)>
+pointer (I<prov>), and then re-generates the internal secret derived by the
+sender. As before, an optional I<info> parameter allows binding that derived
+secret to other application/protocol artefacts. Only a single call to
+OSSL_HPKE_decap() is allowed for a given B<OSSL_HPKE_CTX>.
+
+OSSL_HPKE_open() is used by the recipient to decrypt the ciphertext I<ct> of
+size I<ctlen> using the I<ctx> and additional authenticated data I<aad> of
+size I<aadlen>, to produce the plaintext I<pt> of size I<ptlen>.
+On input I<ptlen> should contain the maximum size of the I<pt> buffer, and
+returns the output size. A I<pt> buffer that is the same size as the
+I<ct> buffer will suffice - generally the plaintext output will be
+a little smaller than the ciphertext input.
+An error will occur if the input I<ptlen> is too small.
+OSSL_HPKE_open() may be called multiple times, but as with OSSL_HPKE_seal()
+there is an internally incrementing nonce value so ciphertexts need to be
+presented in the same order as used by the OSSL_HPKE_seal().
+See L</Re-sequencing> if you need to process multiple ciphertexts in a
+different order.
+
+=head2 Exporting Secrets
+
+HPKE defines a way to produce exported secrets for use by the
+application.
+
+OSSL_HPKE_export() takes as input the B<OSSL_HPKE_CTX>, and an application
+supplied label I<label> of size I<labellen>, to produce a secret I<secret>
+of size I<secretlen>. The sender must first call OSSL_HPKE_encap(), and the
+receiver must call OSSL_HPKE_decap() in order to derive the same shared secret.
+
+Multiple calls to OSSL_HPKE_export() with the same inputs will produce the
+same secret.
+I<OSSL_HPKE_AEAD_ID_EXPORTONLY> may be used as the B<OSSL_HPKE_SUITE> I<aead_id>
+that is passed to OSSL_HPKE_CTX_new() if the user needs to produce a shared
+secret, but does not wish to perform HPKE encryption.
+
+=head2 Sender-authenticated HPKE Modes
+
+HPKE defines modes that support KEM-based sender-authentication
+B<OSSL_HPKE_MODE_AUTH> and B<OSSL_HPKE_MODE_PSKAUTH>. This works by binding
+the sender's authentication private/public values into the encapsulation and
+decapsulation operations. The key used for such modes must also use the same
+KEM as used for the overall exchange. OSSL_HPKE_keygen() can be used to
+generate the private value required.
+
+OSSL_HPKE_CTX_set1_authpriv() can be used by the sender to set the senders
+private I<priv> B<EVP_PKEY> key into the B<OSSL_HPKE_CTX> I<ctx> before calling
+OSSL_HPKE_encap().
+
+OSSL_HPKE_CTX_set1_authpub() can be used by the receiver to set the senders
+encoded pub key I<pub> of size I<publen> into the B<OSSL_HPKE_CTX> I<ctx> before
+calling OSSL_HPKE_decap().
+
+=head2 Pre-Shared Key HPKE modes
+
+HPKE also defines a symmetric equivalent to the authentication described above
+using a pre-shared key (PSK) and a PSK identifier. PSKs can be used with the
+B<OSSL_HPKE_MODE_PSK> and B<OSSL_HPKE_MODE_PSKAUTH> modes.
+
+OSSL_HPKE_CTX_set1_psk() sets the PSK identifier I<pskid> string, and PSK buffer
+I<psk> of size I<psklen> into the I<ctx>. If required this must be called
+before OSSL_HPKE_encap() or OSSL_HPKE_decap().
+As per RFC9180, if required, both I<psk> and I<pskid> must be set to non-NULL values.
+As PSKs are symmetric the same calls must happen on both sender and receiver
+sides.
+
+=head2 Deterministic key generation for senders
+
+Normally the senders ephemeral private key is generated randomly inside
+OSSL_HPKE_encap() and remains secret.
+OSSL_HPKE_CTX_set1_ikme() allows the user to override this behaviour by
+setting a deterministic input key material I<ikm> of size I<ikmlen> into
+the B<OSSL_HPKE_CTX> I<ctx>.
+If required OSSL_HPKE_CTX_set1_ikme() can optionally be called before
+OSSL_HPKE_encap().
+I<ikmlen> should be greater than or equal to OSSL_HPKE_get_recommended_ikmelen().
+
+It is generally undesirable to use OSSL_HPKE_CTX_set1_ikme(), since it
+exposes the relevant secret to the application rather then preserving it
+within the library, and is more likely to result in use of predictable values
+or values that leak.
+
+=head2 Re-sequencing
+
+Some protocols may have to deal with packet loss while still being able to
+decrypt arriving packets later. We provide a way to set the increment used for
+the nonce to the next subsequent call to OSSL_HPKE_seal() or OSSL_HPKE_open().
+The OSSL_HPKE_CTX_set_seq() API can be used for such purposes with the I<seq>
+parameter value resetting the internal nonce to be used for the next call.
+
+A baseline nonce value is established based on the encapsulation or
+decapsulation operation and is then incremented by 1 for each call to seal or
+open. (In other words, the I<seq> is a zero-based counter.)
+
+If a caller needs to determine how many calls to seal or open have been made
+the OSSL_HPKE_CTX_get_seq() API can be used to retrieve the increment (in the
+I<seq> output) that will be used in the next call to seal or open. That would
+return 0 before the first call a sender made to OSSL_HPKE_seal() and 1 after
+that first call.
+
+For compatibility with other implementations these I<seq> increments are
+represented as I<uint64_t>.
+
+Note that re-use of the same nonce and key with different plaintexts is very
+dangerous and can lead to loss of confidentiality. Applications therefore need
+to exercise extreme caution in using these APIs and would be better off avoiding
+them entirely.
+
+=head2 Protocol Convenience Functions
+
+Additional convenience APIs allow the caller to access internal details of
+local HPKE support and/or algorithms, such as parmameter lengths.
+
+OSSL_HPKE_suite_check() checks if a specific B<OSSL_HPKE_SUITE> I<suite>
+is supported locally.
+
+To assist with memory allocation, OSSL_HPKE_get_ciphertext_size() provides a
+way for the caller to know by how much ciphertext will be longer than a
+plaintext of length I<clearlen>.  (AEAD algorithms add a data integrity tag,
+so there is a small amount of ciphertext expansion.)
+
+OSSL_HPKE_get_public_encap_size() provides a way for senders to know how big
+the encapsulated public value will be for a given HPKE I<suite>.
+
+OSSL_HPKE_get_recommended_ikmelen() returns the recommended Input Key Material
+size (in bytes) for a given I<suite>. This is needed in cases where the same
+public value needs to be regenerated by a sender before calling OSSL_HPKE_seal().
+I<ikmlen> should be at least this size.
+
+OSSL_HPKE_get_grease_value() produces values of the appropriate length for a
+given I<suite_in> value (or a random value if I<suite_in> is NULL) so that a
+protocol using HPKE can send so-called GREASE (see RFC8701) values that are
+harder to distinguish from a real use of HPKE. The buffer sizes should
+be supplied on input. The output I<enc> value will have an appropriate
+length for I<suite_out> and a random value, and the I<ct> output will be
+a random value. The relevant sizes for buffers can be found using
+OSSL_HPKE_get_ciphertext_size() and OSSL_HPKE_get_public_encap_size().
+
+OSSL_HPKE_str2suite() maps input I<str> strings to an B<OSSL_HPKE_SUITE> object.
+The input I<str> should be a comma-separated string with a KEM,
+KDF and AEAD name in that order, for example "x25519,hkdf-sha256,aes128gcm".
+This can be used by command line tools that accept string form names for HPKE
+codepoints. Valid (case-insensitive) names are:
+"p256", "p384", "p521", "x25519" and "x448" for KEM,
+"hkdf-SHA256", "hkdf-SHA384" and "hkdf-SHA512" for KDF, and
+"aes-gcm-128", "aes-gcm-256" and "chacha20-poly1305" for AEAD.
+String variants of the numbers listed in L</OSSL_HPKE_SUITE Identifiers>
+can also be used.
+
+=head1 RETURN VALUES
+
+OSSL_HPKE_CTX_new() returns an OSSL_HPKE_CTX pointer or NULL on error.
+
+OSSL_HPKE_get_ciphertext_size(), OSSL_HPKE_get_public_encap_size(),
+OSSL_HPKE_get_recommended_ikmelen() all return a size_t with the
+relevant value or zero on error.
+
+All other functions return 1 for success or zero for error.
+
+=head1 EXAMPLES
+
+This example demonstrates a minimal round-trip using HPKE.
+
+    #include <stddef.h>
+    #include <string.h>
+    #include <openssl/hpke.h>
+    #include <openssl/evp.h>
+
+    /*
+     * this is big enough for this example, real code would need different
+     * handling
+     */
+    #define LBUFSIZE 48
+
+    /* Do a round-trip, generating a key, encrypting and decrypting */
+    int main(int argc, char **argv)
+    {
+        int ok = 0;
+        int hpke_mode = OSSL_HPKE_MODE_BASE;
+        OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
+        OSSL_HPKE_CTX *sctx = NULL, *rctx = NULL;
+        EVP_PKEY *priv = NULL;
+        unsigned char pub[LBUFSIZE];
+        size_t publen = sizeof(pub);
+        unsigned char enc[LBUFSIZE];
+        size_t enclen = sizeof(enc);
+        unsigned char ct[LBUFSIZE];
+        size_t ctlen = sizeof(ct);
+        unsigned char clear[LBUFSIZE];
+        size_t clearlen = sizeof(clear);
+        const unsigned char *pt = "a message not in a bottle";
+        size_t ptlen = strlen((char *)pt);
+        const unsigned char *info = "Some info";
+        size_t infolen = strlen((char *)info);
+        unsigned char aad[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+        size_t aadlen = sizeof(aad);
+
+        /*
+         * Generate receiver's key pair.
+         * The receiver gives this public key to the sender.
+         */
+        if (OSSL_HPKE_keygen(hpke_suite, pub, &publen, &priv,
+                             NULL, 0, NULL, NULL) != 1)
+            goto err;
+
+        /* sender's actions - encrypt data using the receivers public key */
+        if ((sctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite, NULL, NULL)) == NULL)
+            goto err;
+        if (OSSL_HPKE_encap(sctx, enc, &enclen, pub, publen, info, infolen) != 1)
+            goto err;
+        if (OSSL_HPKE_seal(sctx, ct, &ctlen, aad, aadlen, pt, ptlen) != 1)
+            goto err;
+
+        /* receiver's actions - decrypt data using the recievers private key */
+        if ((rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite, NULL, NULL)) == NULL)
+            goto err;
+        if (OSSL_HPKE_decap(rctx, enc, enclen, priv, info, infolen) != 1)
+            goto err;
+        if (OSSL_HPKE_open(rctx, clear, &clearlen, aad, aadlen, ct, ctlen) != 1)
+            goto err;
+        ok = 1;
+    err:
+        /* clean up */
+        printf(ok ? "All Good!\n" : "Error!\n");
+        OSSL_HPKE_CTX_free(rctx);
+        OSSL_HPKE_CTX_free(sctx);
+        EVP_PKEY_free(priv);
+        return 0;
+    }
+
+=head1 WARNINGS
+
+Note that the OSSL_HPKE_CTX_set_seq() API could be dangerous - if used with GCM
+that could lead to nonce-reuse, which is a known danger. So avoid that
+entirely, or be very very careful when using that API.
+
+Use of an IKM value for deterministic key generation (via
+OSSL_HPKE_CTX_set1_ikme() or OSSL_HPKE_keygen()) creates the potential for
+leaking keys (or IKM values). Only use that if really needed and if you
+understand how keys or IKM values could be abused.
+
+=head1 SEE ALSO
+
+The RFC9180 specification: https://datatracker.ietf.org/doc/rfc9180/
+
+=head1 HISTORY
+
+This functionality described here was added in OpenSSL 3.2.
+
+=head1 COPYRIGHT
+
+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
+L<https://www.openssl.org/source/license.html>.
+
+=cut

+ 0 - 47
include/crypto/hpke.h

@@ -1,47 +0,0 @@
-/*
- * 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
- */
-
-#ifndef OSSL_CRYPTO_HPKE_H
-# define OSSL_CRYPTO_HPKE_H
-# pragma once
-
-/* Constants from RFC 9180 Section 7.1 and 7.3 */
-#define OSSL_HPKE_MAX_SECRET 64
-#define OSSL_HPKE_MAX_PUBLIC 133
-#define OSSL_HPKE_MAX_PRIVATE 66
-#define OSSL_HPKE_MAX_NONCE 12
-#define OSSL_HPKE_MAX_KDF_INPUTLEN 64
-
-int ossl_hpke_kdf_extract(EVP_KDF_CTX *kctx,
-                          unsigned char *prk, size_t prklen,
-                          const unsigned char *salt, size_t saltlen,
-                          const unsigned char *ikm, size_t ikmlen);
-
-int ossl_hpke_kdf_expand(EVP_KDF_CTX *kctx,
-                         unsigned char *okm, size_t okmlen,
-                         const unsigned char *prk, size_t prklen,
-                         const unsigned char *info, size_t infolen);
-
-int ossl_hpke_labeled_extract(EVP_KDF_CTX *kctx,
-                              unsigned char *prk, size_t prklen,
-                              const unsigned char *salt, size_t saltlen,
-                              const unsigned char *suiteid, size_t suiteidlen,
-                              const char *label,
-                              const unsigned char *ikm, size_t ikmlen);
-int ossl_hpke_labeled_expand(EVP_KDF_CTX *kctx,
-                             unsigned char *okm, size_t okmlen,
-                             const unsigned char *prk, size_t prklen,
-                             const unsigned char *suiteid, size_t suiteidlen,
-                             const char *label,
-                             const unsigned char *info, size_t infolen);
-
-EVP_KDF_CTX *ossl_kdf_ctx_create(const char *kdfname, const char *mdname,
-                                 OSSL_LIB_CTX *libctx, const char *propq);
-
-#endif

+ 100 - 0
include/internal/hpke_util.h

@@ -0,0 +1,100 @@
+/*
+ * 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
+ */
+
+#ifndef OSSL_INTERNAL_HPKE_UTIL_H
+# define OSSL_INTERNAL_HPKE_UTIL_H
+# pragma once
+
+/* Constants from RFC 9180 Section 7.1 and 7.3 */
+# define OSSL_HPKE_MAX_SECRET 64
+# define OSSL_HPKE_MAX_PUBLIC 133
+# define OSSL_HPKE_MAX_PRIVATE 66
+# define OSSL_HPKE_MAX_KDF_INPUTLEN 64
+
+/*
+ * max length of a base-nonce (the Nn field from OSSL_HPKE_AEAD_INFO), this
+ * is used for a local stack array size
+ */
+# define OSSL_HPKE_MAX_NONCELEN 12
+
+/*
+ * @brief info about a KEM
+ * Used to store constants from Section 7.1 "Table 2 KEM IDs"
+ * and the bitmask for EC curves described in Section 7.1.3 DeriveKeyPair
+ */
+typedef struct {
+    uint16_t      kem_id; /* code point for key encipherment method */
+    const char    *keytype; /* string form of algtype "EC"/"X25519"/"X448" */
+    const char    *groupname; /* string form of EC group for NIST curves  */
+    const char    *mdname; /* hash alg name for the HKDF */
+    size_t        Nsecret; /* size of secrets */
+    size_t        Nenc; /* length of encapsulated key */
+    size_t        Npk; /* length of public key */
+    size_t        Nsk; /* length of raw private key */
+    uint8_t       bitmask;
+} OSSL_HPKE_KEM_INFO;
+
+/*
+ * @brief info about a KDF
+ */
+typedef struct {
+    uint16_t       kdf_id; /* code point for KDF */
+    const char     *mdname; /* hash alg name for the HKDF */
+    size_t         Nh; /* length of hash/extract output */
+} OSSL_HPKE_KDF_INFO;
+
+/*
+ * @brief info about an AEAD
+ */
+typedef struct {
+    uint16_t       aead_id; /* code point for aead alg */
+    const char     *name;   /* alg name */
+    size_t         taglen; /* aead tag len */
+    size_t         Nk; /* size of a key for this aead */
+    size_t         Nn; /* length of a nonce for this aead */
+} OSSL_HPKE_AEAD_INFO;
+
+const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_curve(const char *curve);
+const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_id(uint16_t kemid);
+const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_random(OSSL_LIB_CTX *ctx);
+const OSSL_HPKE_KDF_INFO *ossl_HPKE_KDF_INFO_find_id(uint16_t kdfid);
+const OSSL_HPKE_KDF_INFO *ossl_HPKE_KDF_INFO_find_random(OSSL_LIB_CTX *ctx);
+const OSSL_HPKE_AEAD_INFO *ossl_HPKE_AEAD_INFO_find_id(uint16_t aeadid);
+const OSSL_HPKE_AEAD_INFO *ossl_HPKE_AEAD_INFO_find_random(OSSL_LIB_CTX *ctx);
+
+int ossl_hpke_kdf_extract(EVP_KDF_CTX *kctx,
+                          unsigned char *prk, size_t prklen,
+                          const unsigned char *salt, size_t saltlen,
+                          const unsigned char *ikm, size_t ikmlen);
+
+int ossl_hpke_kdf_expand(EVP_KDF_CTX *kctx,
+                         unsigned char *okm, size_t okmlen,
+                         const unsigned char *prk, size_t prklen,
+                         const unsigned char *info, size_t infolen);
+
+int ossl_hpke_labeled_extract(EVP_KDF_CTX *kctx,
+                              unsigned char *prk, size_t prklen,
+                              const unsigned char *salt, size_t saltlen,
+                              const char *protocol_label,
+                              const unsigned char *suiteid, size_t suiteidlen,
+                              const char *label,
+                              const unsigned char *ikm, size_t ikmlen);
+int ossl_hpke_labeled_expand(EVP_KDF_CTX *kctx,
+                             unsigned char *okm, size_t okmlen,
+                             const unsigned char *prk, size_t prklen,
+                             const char *protocol_label,
+                             const unsigned char *suiteid, size_t suiteidlen,
+                             const char *label,
+                             const unsigned char *info, size_t infolen);
+
+EVP_KDF_CTX *ossl_kdf_ctx_create(const char *kdfname, const char *mdname,
+                                 OSSL_LIB_CTX *libctx, const char *propq);
+
+int ossl_hpke_str2suite(const char *suitestr, OSSL_HPKE_SUITE *suite);
+#endif

+ 144 - 0
include/openssl/hpke.h

@@ -0,0 +1,144 @@
+/*
+ * 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
+ */
+
+/* APIs and data structures for HPKE (RFC9180)  */
+#ifndef OSSL_HPKE_H
+# define OSSL_HPKE_H
+# pragma once
+
+# include <openssl/types.h>
+
+/* HPKE modes */
+# define OSSL_HPKE_MODE_BASE              0 /* Base mode  */
+# define OSSL_HPKE_MODE_PSK               1 /* Pre-shared key mode */
+# define OSSL_HPKE_MODE_AUTH              2 /* Authenticated mode */
+# define OSSL_HPKE_MODE_PSKAUTH           3 /* PSK+authenticated mode */
+
+/*
+ * Max for ikm, psk, pskid, info and exporter contexts.
+ * RFC9180, section 7.2.1 RECOMMENDS 64 octets but we have test vectors from
+ * Appendix A.6.1 with a 66 octet IKM so we'll allow that.
+ */
+# define OSSL_HPKE_MAX_PARMLEN        66
+# define OSSL_HPKE_MAX_INFOLEN        1024
+
+/*
+ * The (16bit) HPKE algorithm ID IANA codepoints
+ * If/when new IANA codepoints are added there are tables in
+ * crypto/hpke/hpke_util.c that must also be updated.
+ */
+# define OSSL_HPKE_KEM_ID_RESERVED         0x0000 /* not used */
+# define OSSL_HPKE_KEM_ID_P256             0x0010 /* NIST P-256 */
+# define OSSL_HPKE_KEM_ID_P384             0x0011 /* NIST P-384 */
+# define OSSL_HPKE_KEM_ID_P521             0x0012 /* NIST P-521 */
+# define OSSL_HPKE_KEM_ID_X25519           0x0020 /* Curve25519 */
+# define OSSL_HPKE_KEM_ID_X448             0x0021 /* Curve448 */
+
+# define OSSL_HPKE_KDF_ID_RESERVED         0x0000 /* not used */
+# define OSSL_HPKE_KDF_ID_HKDF_SHA256      0x0001 /* HKDF-SHA256 */
+# define OSSL_HPKE_KDF_ID_HKDF_SHA384      0x0002 /* HKDF-SHA384 */
+# define OSSL_HPKE_KDF_ID_HKDF_SHA512      0x0003 /* HKDF-SHA512 */
+
+# define OSSL_HPKE_AEAD_ID_RESERVED        0x0000 /* not used */
+# define OSSL_HPKE_AEAD_ID_AES_GCM_128     0x0001 /* AES-GCM-128 */
+# define OSSL_HPKE_AEAD_ID_AES_GCM_256     0x0002 /* AES-GCM-256 */
+# define OSSL_HPKE_AEAD_ID_CHACHA_POLY1305 0x0003 /* Chacha20-Poly1305 */
+# define OSSL_HPKE_AEAD_ID_EXPORTONLY      0xFFFF /* export-only fake ID */
+
+/* strings for suite components */
+# define OSSL_HPKE_KEMSTR_P256        "P-256"              /* KEM id 0x10 */
+# define OSSL_HPKE_KEMSTR_P384        "P-384"              /* KEM id 0x11 */
+# define OSSL_HPKE_KEMSTR_P521        "P-521"              /* KEM id 0x12 */
+# define OSSL_HPKE_KEMSTR_X25519      "X25519"             /* KEM id 0x20 */
+# define OSSL_HPKE_KEMSTR_X448        "X448"               /* KEM id 0x21 */
+# define OSSL_HPKE_KDFSTR_256         "hkdf-sha256"        /* KDF id 1 */
+# define OSSL_HPKE_KDFSTR_384         "hkdf-sha384"        /* KDF id 2 */
+# define OSSL_HPKE_KDFSTR_512         "hkdf-sha512"        /* KDF id 3 */
+# define OSSL_HPKE_AEADSTR_AES128GCM  "aes-128-gcm"        /* AEAD id 1 */
+# define OSSL_HPKE_AEADSTR_AES256GCM  "aes-256-gcm"        /* AEAD id 2 */
+# define OSSL_HPKE_AEADSTR_CP         "chacha20-poly1305"  /* AEAD id 3 */
+# define OSSL_HPKE_AEADSTR_EXP        "exporter"           /* AEAD id 0xff */
+
+typedef struct {
+    uint16_t    kem_id; /* Key Encapsulation Method id */
+    uint16_t    kdf_id; /* Key Derivation Function id */
+    uint16_t    aead_id; /* AEAD alg id */
+} OSSL_HPKE_SUITE;
+
+/**
+ * Suite constants, use this like:
+ *          OSSL_HPKE_SUITE myvar = OSSL_HPKE_SUITE_DEFAULT;
+ */
+# define OSSL_HPKE_SUITE_DEFAULT \
+    {\
+        OSSL_HPKE_KEM_ID_X25519, \
+        OSSL_HPKE_KDF_ID_HKDF_SHA256, \
+        OSSL_HPKE_AEAD_ID_AES_GCM_128 \
+    }
+
+typedef struct ossl_hpke_ctx_st OSSL_HPKE_CTX;
+
+OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite,
+                                 OSSL_LIB_CTX *libctx, const char *propq);
+void OSSL_HPKE_CTX_free(OSSL_HPKE_CTX *ctx);
+
+int OSSL_HPKE_encap(OSSL_HPKE_CTX *ctx,
+                    unsigned char *enc, size_t *enclen,
+                    const unsigned char *pub, size_t publen,
+                    const unsigned char *info, size_t infolen);
+int OSSL_HPKE_seal(OSSL_HPKE_CTX *ctx,
+                   unsigned char *ct, size_t *ctlen,
+                   const unsigned char *aad, size_t aadlen,
+                   const unsigned char *pt, size_t ptlen);
+
+int OSSL_HPKE_keygen(OSSL_HPKE_SUITE suite,
+                     unsigned char *pub, size_t *publen, EVP_PKEY **priv,
+                     const unsigned char *ikm, size_t ikmlen,
+                     OSSL_LIB_CTX *libctx, const char *propq);
+int OSSL_HPKE_decap(OSSL_HPKE_CTX *ctx,
+                    const unsigned char *enc, size_t enclen,
+                    EVP_PKEY *recippriv,
+                    const unsigned char *info, size_t infolen);
+int OSSL_HPKE_open(OSSL_HPKE_CTX *ctx,
+                   unsigned char *pt, size_t *ptlen,
+                   const unsigned char *aad, size_t aadlen,
+                   const unsigned char *ct, size_t ctlen);
+
+int OSSL_HPKE_export(OSSL_HPKE_CTX *ctx,
+                     unsigned char *secret,
+                     size_t secretlen,
+                     const unsigned char *label,
+                     size_t labellen);
+
+int OSSL_HPKE_CTX_set1_authpriv(OSSL_HPKE_CTX *ctx, EVP_PKEY *priv);
+int OSSL_HPKE_CTX_set1_authpub(OSSL_HPKE_CTX *ctx,
+                               const unsigned char *pub,
+                               size_t publen);
+int OSSL_HPKE_CTX_set1_psk(OSSL_HPKE_CTX *ctx,
+                           const char *pskid,
+                           const unsigned char *psk, size_t psklen);
+
+int OSSL_HPKE_CTX_set1_ikme(OSSL_HPKE_CTX *ctx,
+                            const unsigned char *ikme, size_t ikmelen);
+
+int OSSL_HPKE_CTX_set_seq(OSSL_HPKE_CTX *ctx, uint64_t seq);
+int OSSL_HPKE_CTX_get_seq(OSSL_HPKE_CTX *ctx, uint64_t *seq);
+
+int OSSL_HPKE_suite_check(OSSL_HPKE_SUITE suite);
+int OSSL_HPKE_get_grease_value(OSSL_LIB_CTX *libctx, const char *propq,
+                               const OSSL_HPKE_SUITE *suite_in,
+                               OSSL_HPKE_SUITE *suite,
+                               unsigned char *enc, size_t *enclen,
+                               unsigned char *ct, size_t ctlen);
+int OSSL_HPKE_str2suite(const char *str, OSSL_HPKE_SUITE *suite);
+size_t OSSL_HPKE_get_ciphertext_size(OSSL_HPKE_SUITE suite, size_t clearlen);
+size_t OSSL_HPKE_get_public_encap_size(OSSL_HPKE_SUITE suite);
+size_t OSSL_HPKE_get_recommended_ikmelen(OSSL_HPKE_SUITE suite);
+
+#endif

+ 3 - 1
include/openssl/proverr.h

@@ -1,6 +1,6 @@
 /*
  * Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-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
@@ -51,6 +51,7 @@
 # define PROV_R_INDICATOR_INTEGRITY_FAILURE               210
 # define PROV_R_INSUFFICIENT_DRBG_STRENGTH                181
 # define PROV_R_INVALID_AAD                               108
+# define PROV_R_INVALID_AEAD                              231
 # define PROV_R_INVALID_CONFIG_DATA                       211
 # define PROV_R_INVALID_CONSTANT_LENGTH                   157
 # define PROV_R_INVALID_CURVE                             176
@@ -62,6 +63,7 @@
 # define PROV_R_INVALID_INPUT_LENGTH                      230
 # define PROV_R_INVALID_ITERATION_COUNT                   123
 # define PROV_R_INVALID_IV_LENGTH                         109
+# define PROV_R_INVALID_KDF                               232
 # define PROV_R_INVALID_KEY                               158
 # define PROV_R_INVALID_KEY_LENGTH                        105
 # define PROV_R_INVALID_MAC                               151

+ 1 - 1
providers/common/include/prov/proverr.h

@@ -1,6 +1,6 @@
 /*
  * Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 2020-2021 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2020-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

+ 3 - 1
providers/common/provider_err.c

@@ -1,6 +1,6 @@
 /*
  * Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-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
@@ -67,6 +67,7 @@ static const ERR_STRING_DATA PROV_str_reasons[] = {
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INSUFFICIENT_DRBG_STRENGTH),
     "insufficient drbg strength"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_AAD), "invalid aad"},
+    {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_AEAD), "invalid aead"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_CONFIG_DATA),
     "invalid config data"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_CONSTANT_LENGTH),
@@ -85,6 +86,7 @@ static const ERR_STRING_DATA PROV_str_reasons[] = {
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_ITERATION_COUNT),
     "invalid iteration count"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_IV_LENGTH), "invalid iv length"},
+    {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_KDF), "invalid kdf"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_KEY), "invalid key"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_KEY_LENGTH),
     "invalid key length"},

+ 49 - 76
providers/implementations/kem/ec_kem.c

@@ -30,25 +30,12 @@
 #include "prov/securitycheck.h"
 #include "prov/providercommon.h"
 
-#include "crypto/hpke.h"
+#include <openssl/hpke.h>
+#include "internal/hpke_util.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;
@@ -59,7 +46,7 @@ typedef struct {
     unsigned char *ikm;
     size_t ikmlen;
     const char *kdfname;
-    const DHKEM_ALG *alg;
+    const OSSL_HPKE_KEM_INFO *info;
 } PROV_EC_CTX;
 
 static OSSL_FUNC_kem_newctx_fn eckem_newctx;
@@ -73,26 +60,8 @@ 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;
-}
+/* 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)
 {
@@ -151,8 +120,8 @@ static int recipient_key_set(PROV_EC_CTX *ctx, EC_KEY *ec)
 
         if (curve == NULL)
             return -2;
-        ctx->alg = dhkem_ec_find_alg(curve);
-        if (ctx->alg == NULL)
+        ctx->info = ossl_HPKE_KEM_INFO_find_curve(curve);
+        if (ctx->info == NULL)
             return -2;
         if (!EC_KEY_up_ref(ec))
             return 0;
@@ -372,7 +341,7 @@ static int dhkem_extract_and_expand(EVP_KDF_CTX *kctx,
                                     const unsigned char *kemctx,
                                     size_t kemctxlen)
 {
-    uint8_t suiteid[5];
+    uint8_t suiteid[2];
     uint8_t prk[EVP_MAX_MD_SIZE];
     size_t prklen = okmlen;
     int ret;
@@ -380,13 +349,14 @@ static int dhkem_extract_and_expand(EVP_KDF_CTX *kctx,
     if (prklen > sizeof(prk))
         return 0;
 
-    ossl_dhkem_getsuiteid(suiteid, kemid);
+    suiteid[0] = (kemid >> 8) & 0xff;
+    suiteid[1] = kemid & 0xff;
 
     ret = ossl_hpke_labeled_extract(kctx, prk, prklen,
-                                    NULL, 0, suiteid, sizeof(suiteid),
+                                    NULL, 0, LABEL_KEM, suiteid, sizeof(suiteid),
                                     OSSL_DHKEM_LABEL_EAE_PRK, dhkm, dhkmlen)
           && ossl_hpke_labeled_expand(kctx, okm, okmlen, prk, prklen,
-                                      suiteid, sizeof(suiteid),
+                                      LABEL_KEM, suiteid, sizeof(suiteid),
                                       OSSL_DHKEM_LABEL_SHARED_SECRET,
                                       kemctx, kemctxlen);
     OPENSSL_cleanse(prk, prklen);
@@ -413,52 +383,53 @@ int ossl_ec_dhkem_derive_private(EC_KEY *ec, BIGNUM *priv,
 {
     int ret = 0;
     EVP_KDF_CTX *kdfctx = NULL;
-    uint8_t suiteid[5];
+    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 DHKEM_ALG *alg;
     const char *curve = ec_curvename_get0(ec);
+    const OSSL_HPKE_KEM_INFO *info;
 
     if (curve == NULL)
         return -2;
 
-    alg = dhkem_ec_find_alg(curve);
-    if (alg == NULL)
+    info = ossl_HPKE_KEM_INFO_find_curve(curve);
+    if (info == NULL)
         return -2;
 
-    kdfctx = ossl_kdf_ctx_create("HKDF", alg->kdfdigestname,
+    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 < alg->encodedprivlen) {
+    if (ikmlen < info->Nsecret) {
         ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_INPUT_LENGTH,
                        "ikm length is :%zu, should be at least %zu",
-                       ikmlen, alg->encodedprivlen);
+                       ikmlen, info->Nsecret);
         goto err;
     }
 
-    ossl_dhkem_getsuiteid(suiteid, alg->kemid);
+    suiteid[0] = info->kem_id / 256;
+    suiteid[1] = info->kem_id % 256;
 
-    if (!ossl_hpke_labeled_extract(kdfctx, prk, alg->secretlen,
-                                   NULL, 0, suiteid, sizeof(suiteid),
+    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, alg->encodedprivlen,
-                                      prk, alg->secretlen,
-                                      suiteid, sizeof(suiteid),
+        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] &= alg->bitmask;
-        if (BN_bin2bn(privbuf, alg->encodedprivlen, priv) == NULL)
+        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);
@@ -499,7 +470,7 @@ static EC_KEY *derivekey(PROV_EC_CTX *ctx,
 
     /* Generate a random seed if there is no input ikm */
     if (seed == NULL || seedlen == 0) {
-        seedlen = ctx->alg->encodedprivlen;
+        seedlen = ctx->info->Nsk;
         if (seedlen > sizeof(tmpbuf))
             goto err;
         if (RAND_priv_bytes_ex(ctx->libctx, tmpbuf, seedlen, 0) <= 0)
@@ -599,8 +570,9 @@ static int derive_secret(PROV_EC_CTX *ctx, unsigned char *secret,
     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;
+    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))
@@ -630,17 +602,16 @@ static int derive_secret(PROV_EC_CTX *ctx, unsigned char *secret,
         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);
+    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, ctx->alg->kdfdigestname,
+    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, ctx->alg->secretlen,
-                                  ctx->alg->kemid, dhkm, dhkmlen,
+    if (!dhkem_extract_and_expand(kdfctx, secret, info->Nsecret,
+                                  info->kem_id, dhkm, dhkmlen,
                                   kemctx, kemctxlen))
         goto err;
     ret = 1;
@@ -677,22 +648,23 @@ static int dhkem_encap(PROV_EC_CTX *ctx,
     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 = ctx->alg->encodedpublen;
+            *enclen = info->Nenc;
         if (secretlen != NULL)
-            *secretlen = ctx->alg->secretlen;
+            *secretlen = info->Nsecret;
        return 1;
     }
 
-    if (*secretlen < ctx->alg->secretlen) {
+    if (*secretlen < info->Nsecret) {
         ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*secretlen too small");
         return 0;
     }
-    if (*enclen < ctx->alg->encodedpublen) {
+    if (*enclen < info->Nenc) {
         ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*enclen too small");
         return 0;
     }
@@ -707,7 +679,7 @@ static int dhkem_encap(PROV_EC_CTX *ctx,
                                 &recipient_publen, sizeof(recipient_pub)))
         goto err;
 
-    if (sender_publen != ctx->alg->encodedpublen
+    if (sender_publen != info->Npk
             || recipient_publen != sender_publen) {
         ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY, "Invalid public key");
         goto err;
@@ -722,7 +694,7 @@ static int dhkem_encap(PROV_EC_CTX *ctx,
     /* Return the senders ephemeral public key in encoded form */
     memcpy(enc, sender_pub, sender_publen);
     *enclen = sender_publen;
-    *secretlen = ctx->alg->secretlen;
+    *secretlen = info->Nsecret;
     ret = 1;
 err:
     EC_KEY_free(sender_ephemkey);
@@ -751,16 +723,17 @@ static int dhkem_decap(PROV_EC_CTX *ctx,
 {
     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 = ctx->alg->encodedpublen;
+    size_t encodedpublen = info->Npk;
 
     if (secret == NULL) {
-        *secretlen = ctx->alg->secretlen;
+        *secretlen = info->Nsecret;
         return 1;
     }
 
-    if (*secretlen < ctx->alg->secretlen) {
+    if (*secretlen < info->Nsecret) {
         ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*secretlen too small");
         return 0;
     }
@@ -785,7 +758,7 @@ static int dhkem_decap(PROV_EC_CTX *ctx,
                        ctx->recipient_key, ctx->sender_authkey,
                        enc, recipient_pub))
         goto err;
-    *secretlen = ctx->alg->secretlen;
+    *secretlen = info->Nsecret;
     ret = 1;
 err:
     EC_KEY_free(sender_ephempubkey);

+ 0 - 1
providers/implementations/kem/eckem.h

@@ -11,4 +11,3 @@
 #define KEM_MODE_DHKEM       1
 
 int ossl_eckem_modename2id(const char *name);
-void ossl_dhkem_getsuiteid(unsigned char suiteid[5], uint16_t kemid);

+ 56 - 58
providers/implementations/kem/ecx_kem.c

@@ -32,7 +32,8 @@
 #include "prov/providercommon.h"
 #include "prov/ecx.h"
 #include "crypto/ecx.h"
-#include "crypto/hpke.h"
+#include <openssl/hpke.h>
+#include "internal/hpke_util.h"
 #include "eckem.h"
 
 #define MAX_ECX_KEYLEN X448_KEYLEN
@@ -41,6 +42,9 @@
 #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;
@@ -48,13 +52,10 @@ typedef struct {
     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;
+    const OSSL_HPKE_KEM_INFO *info;
 } PROV_ECX_CTX;
 
 static OSSL_FUNC_kem_newctx_fn ecxkem_newctx;
@@ -72,21 +73,15 @@ static OSSL_FUNC_kem_auth_decapsulate_init_fn ecxkem_auth_decapsulate_init;
  * There is only one set of values for X25519 and X448.
  * Additional values could be set via set_params if required.
  */
-static void get_kem_values(ECX_KEY *ecx, uint16_t *kemid,
-                           const char **kdfdigestname, size_t *secretlen,
-                           size_t *keylen)
+static const OSSL_HPKE_KEM_INFO *get_kem_info(ECX_KEY *ecx)
 {
-    if (ecx->type == ECX_KEY_TYPE_X25519) {
-        *kemid = KEMID_X25519_HKDF_SHA256;
-        *kdfdigestname = "SHA256";
-        *secretlen = SHA256_DIGEST_LENGTH;
-    } else {
-        *kemid = KEMID_X448_HKDF_SHA512;
-        *kdfdigestname = "SHA512";
-        *secretlen = SHA512_DIGEST_LENGTH;
-    }
-    /* ECX keys have the same length for public and private keys */
-    *keylen = ecx->keylen;
+    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);
 }
 
 /*
@@ -98,8 +93,9 @@ 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) {
-        get_kem_values(ecx, &ctx->kemid, &ctx->kdfdigestname,
-                       &ctx->sharedsecretlen, &ctx->keylen);
+        ctx->info = get_kem_info(ecx);
+        if (ctx->info == NULL)
+            return -2;
         ctx->kdfname = "HKDF";
         if (!ossl_ecx_key_up_ref(ecx))
             return 0;
@@ -302,7 +298,7 @@ static int dhkem_extract_and_expand(EVP_KDF_CTX *kctx,
                                     const unsigned char *kemctx,
                                     size_t kemctxlen)
 {
-    uint8_t suiteid[5];
+    uint8_t suiteid[2];
     uint8_t prk[EVP_MAX_MD_SIZE];
     size_t prklen = okmlen; /* Nh */
     int ret;
@@ -310,13 +306,14 @@ static int dhkem_extract_and_expand(EVP_KDF_CTX *kctx,
     if (prklen > sizeof(prk))
         return 0;
 
-    ossl_dhkem_getsuiteid(suiteid, kemid);
+    suiteid[0] = (kemid >> 8) &0xff;
+    suiteid[1] = kemid & 0xff;
 
     ret = ossl_hpke_labeled_extract(kctx, prk, prklen,
-                                    NULL, 0, suiteid, sizeof(suiteid),
+                                    NULL, 0, LABEL_KEM, suiteid, sizeof(suiteid),
                                     OSSL_DHKEM_LABEL_EAE_PRK, dhkm, dhkmlen)
           && ossl_hpke_labeled_expand(kctx, okm, okmlen, prk, prklen,
-                                      suiteid, sizeof(suiteid),
+                                      LABEL_KEM, suiteid, sizeof(suiteid),
                                       OSSL_DHKEM_LABEL_SHARED_SECRET,
                                       kemctx, kemctxlen);
     OPENSSL_cleanse(prk, prklen);
@@ -344,35 +341,32 @@ int ossl_ecx_dhkem_derive_private(ECX_KEY *ecx, unsigned char *privout,
     int ret = 0;
     EVP_KDF_CTX *kdfctx = NULL;
     unsigned char prk[EVP_MAX_MD_SIZE];
-    uint16_t kemid;
-    const char *kdfdigestname;
-    uint8_t suiteid[5];
-    size_t prklen, keylen;
-
-    get_kem_values(ecx, &kemid, &kdfdigestname, &prklen, &keylen);
+    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 < keylen) {
+    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, keylen);
+                       ikmlen, info->Nsk);
         goto err;
     }
 
-    kdfctx = ossl_kdf_ctx_create("HKDF", kdfdigestname, ecx->libctx, ecx->propq);
+    kdfctx = ossl_kdf_ctx_create("HKDF", info->mdname, ecx->libctx, ecx->propq);
     if (kdfctx == NULL)
         return 0;
 
-    ossl_dhkem_getsuiteid(suiteid, kemid);
+    suiteid[0] = info->kem_id / 256;
+    suiteid[1] = info->kem_id % 256;
 
-    if (!ossl_hpke_labeled_extract(kdfctx, prk, prklen,
-                                   NULL, 0, suiteid, sizeof(suiteid),
+    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, keylen, prk, prklen,
-                                  suiteid, sizeof(suiteid), OSSL_DHKEM_LABEL_SK,
-                                  NULL, 0))
+    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:
@@ -398,6 +392,7 @@ static ECX_KEY *derivekey(PROV_ECX_CTX *ctx,
     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)
@@ -408,12 +403,12 @@ static ECX_KEY *derivekey(PROV_ECX_CTX *ctx,
 
     /* Generate a random seed if there is no input ikm */
     if (seed == NULL || seedlen == 0) {
-        if (ctx->keylen > sizeof(tmpbuf))
+        if (info->Nsk > sizeof(tmpbuf))
             goto err;
-        if (RAND_priv_bytes_ex(ctx->libctx, tmpbuf, ctx->keylen, 0) <= 0)
+        if (RAND_priv_bytes_ex(ctx->libctx, tmpbuf, info->Nsk, 0) <= 0)
             goto err;
         seed = tmpbuf;
-        seedlen = ctx->keylen;
+        seedlen = info->Nsk;
     }
     if (!ossl_ecx_dhkem_derive_private(key, privkey, seed, seedlen))
         goto err;
@@ -485,8 +480,9 @@ static int derive_secret(PROV_ECX_CTX *ctx, unsigned char *secret,
     unsigned char dhkm[MAX_ECX_KEYLEN * 2];
     unsigned char kemctx[MAX_ECX_KEYLEN * 3];
     size_t kemctxlen = 0, dhkmlen = 0;
-    size_t encodedkeylen = ctx->keylen;
+    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;
@@ -513,12 +509,12 @@ static int derive_secret(PROV_ECX_CTX *ctx, unsigned char *secret,
     memcpy(kemctx + encodedkeylen, recipient_pub, encodedkeylen);
     if (auth)
         memcpy(kemctx + 2 * encodedkeylen, sender_authpub, encodedkeylen);
-    kdfctx = ossl_kdf_ctx_create(ctx->kdfname, ctx->kdfdigestname,
+    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, ctx->sharedsecretlen,
-                                  ctx->kemid, dhkm, dhkmlen,
+    if (!dhkem_extract_and_expand(kdfctx, secret, info->Nsecret,
+                                  info->kem_id, dhkm, dhkmlen,
                                   kemctx, kemctxlen))
         goto err;
     ret = 1;
@@ -553,22 +549,23 @@ static int dhkem_encap(PROV_ECX_CTX *ctx,
     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 = ctx->keylen;
+            *enclen = info->Nenc;
         if (secretlen != NULL)
-            *secretlen = ctx->sharedsecretlen;
+            *secretlen = info->Nsecret;
        return 1;
     }
 
-    if (*secretlen < ctx->sharedsecretlen) {
+    if (*secretlen < info->Nsecret) {
         ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*secretlen too small");
         return 0;
     }
-    if (*enclen < ctx->keylen) {
+    if (*enclen < info->Nenc) {
         ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*enclen too small");
         return 0;
     }
@@ -588,9 +585,9 @@ static int dhkem_encap(PROV_ECX_CTX *ctx,
         goto err;
 
     /* Return the public part of the ephemeral key */
-    memcpy(enc, sender_ephempub, ctx->keylen);
-    *enclen = ctx->keylen;
-    *secretlen = ctx->sharedsecretlen;
+    memcpy(enc, sender_ephempub, info->Nenc);
+    *enclen = info->Nenc;
+    *secretlen = info->Nsecret;
     ret = 1;
 err:
     ossl_ecx_key_free(sender_ephemkey);
@@ -620,17 +617,18 @@ static int dhkem_decap(PROV_ECX_CTX *ctx,
     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 = ctx->sharedsecretlen;
+        *secretlen = info->Nsecret;
         return 1;
     }
-    if (*secretlen < ctx->sharedsecretlen) {
+    if (*secretlen < info->Nsecret) {
         ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*secretlen too small");
         return 0;
     }
-    if (enclen != ctx->keylen) {
+    if (enclen != info->Nenc) {
         ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY, "Invalid enc public key");
         return 0;
     }
@@ -650,7 +648,7 @@ static int dhkem_decap(PROV_ECX_CTX *ctx,
                        enc, recipient_pub))
         goto err;
 
-    *secretlen = ctx->sharedsecretlen;
+    *secretlen = info->Nsecret;
     ret = 1;
 err:
     ossl_ecx_key_free(sender_ephempubkey);

+ 0 - 8
providers/implementations/kem/kem_util.c

@@ -35,11 +35,3 @@ int ossl_eckem_modename2id(const char *name)
     }
     return KEM_MODE_UNDEFINED;
 }
-
-/* suiteid = concat("KEM", I2OSP(kem_id, 2)) */
-void ossl_dhkem_getsuiteid(unsigned char suiteid[5], uint16_t kemid)
-{
-    memcpy(suiteid, "KEM", 3);
-    suiteid[3] = kemid >> 8;
-    suiteid[4] = kemid & 0xFF;
-}

+ 5 - 1
test/build.info

@@ -64,7 +64,7 @@ IF[{- !$disabled{tests} -}]
           bio_readbuffer_test user_property_test pkcs7_test upcallstest \
           provfetchtest prov_config_test rand_test ca_internals_test \
           bio_tfo_test membio_test bio_dgram_test list_test fips_version_test \
-          x509_test
+          x509_test hpke_test
 
   IF[{- !$disabled{'deprecated-3.0'} -}]
     PROGRAMS{noinst}=enginetest
@@ -184,6 +184,10 @@ IF[{- !$disabled{tests} -}]
   INCLUDE[evp_extra_test]=../include ../apps/include
   DEPEND[evp_extra_test]=../libcrypto.a libtestutil.a
 
+  SOURCE[hpke_test]=hpke_test.c
+  INCLUDE[hpke_test]=../include ../apps/include
+  DEPEND[hpke_test]=../libcrypto.a libtestutil.a
+
   SOURCE[evp_extra_test2]=evp_extra_test2.c $INITSRC
   INCLUDE[evp_extra_test2]=../include ../apps/include
   DEPEND[evp_extra_test2]=../libcrypto libtestutil.a

+ 1921 - 0
test/hpke_test.c

@@ -0,0 +1,1921 @@
+/*
+ * 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 <openssl/evp.h>
+#include <openssl/core_names.h>
+#include <openssl/rand.h>
+#include <openssl/hpke.h>
+#include "testutil.h"
+
+/* a size to use for stack buffers */
+#define OSSL_HPKE_TSTSIZE 512
+
+static OSSL_LIB_CTX *testctx = NULL;
+static OSSL_PROVIDER *nullprov = NULL;
+static OSSL_PROVIDER *deflprov = NULL;
+static char *testpropq = "provider=default";
+static int verbose = 0;
+
+typedef struct {
+    int mode;
+    OSSL_HPKE_SUITE suite;
+    const unsigned char *ikmE;
+    size_t ikmElen;
+    const unsigned char *expected_pkEm;
+    size_t expected_pkEmlen;
+    const unsigned char *ikmR;
+    size_t ikmRlen;
+    const unsigned char *expected_pkRm;
+    size_t expected_pkRmlen;
+    const unsigned char *expected_skRm;
+    size_t expected_skRmlen;
+    const unsigned char *expected_secret;
+    size_t expected_secretlen;
+    const unsigned char *ksinfo;
+    size_t ksinfolen;
+    const unsigned char *ikmAuth;
+    size_t ikmAuthlen;
+    const unsigned char *psk;
+    size_t psklen;
+    const char *pskid; /* want terminating NUL here */
+} TEST_BASEDATA;
+
+typedef struct
+{
+    int seq;
+    const unsigned char *pt;
+    size_t ptlen;
+    const unsigned char *aad;
+    size_t aadlen;
+    const unsigned char *expected_ct;
+    size_t expected_ctlen;
+} TEST_AEADDATA;
+
+typedef struct
+{
+    const unsigned char *context;
+    size_t contextlen;
+    const unsigned char *expected_secret;
+    size_t expected_secretlen;
+} TEST_EXPORTDATA;
+
+/**
+ * @brief Test that an EVP_PKEY encoded public key matches the supplied buffer
+ * @param pkey is the EVP_PKEY we want to check
+ * @param pub is the expected public key buffer
+ * @param publen is the length of the above
+ * @return 1 for good, 0 for bad
+ */
+static int cmpkey(const EVP_PKEY *pkey,
+                  const unsigned char *pub, size_t publen)
+{
+    unsigned char pubbuf[256];
+    size_t pubbuflen = 0;
+    int erv = 0;
+
+    if (!TEST_true(publen <= sizeof(pubbuf)))
+        return 0;
+    erv = EVP_PKEY_get_octet_string_param(pkey,
+                                          OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY,
+                                          pubbuf, sizeof(pubbuf), &pubbuflen);
+    if (!TEST_true(erv))
+        return 0;
+    if (pub != NULL && !TEST_mem_eq(pubbuf, pubbuflen, pub, publen))
+        return 0;
+    return 1;
+}
+
+static int do_testhpke(const TEST_BASEDATA *base,
+                       const TEST_AEADDATA *aead, size_t aeadsz,
+                       const TEST_EXPORTDATA *export, size_t exportsz)
+{
+    OSSL_LIB_CTX *libctx = testctx;
+    const char *propq = testpropq;
+    OSSL_HPKE_CTX *sealctx = NULL, *openctx = NULL;
+    unsigned char ct[256];
+    unsigned char enc[256];
+    unsigned char ptout[256];
+    size_t ptoutlen = sizeof(ptout);
+    size_t enclen = sizeof(enc);
+    size_t ctlen = sizeof(ct);
+    unsigned char pub[OSSL_HPKE_TSTSIZE];
+    size_t publen = sizeof(pub);
+    EVP_PKEY *privE = NULL;
+    unsigned char authpub[OSSL_HPKE_TSTSIZE];
+    size_t authpublen = sizeof(authpub);
+    EVP_PKEY *authpriv = NULL;
+    unsigned char rpub[OSSL_HPKE_TSTSIZE];
+    size_t rpublen = sizeof(pub);
+    EVP_PKEY *privR = NULL;
+    int ret = 0;
+    size_t i;
+    uint64_t lastseq = 0;
+
+    if (!TEST_true(OSSL_HPKE_keygen(base->suite, pub, &publen, &privE,
+                                    base->ikmE, base->ikmElen, libctx, propq)))
+        goto end;
+    if (!TEST_true(cmpkey(privE, base->expected_pkEm, base->expected_pkEmlen)))
+        goto end;
+    if (!TEST_ptr(sealctx = OSSL_HPKE_CTX_new(base->mode, base->suite,
+                                              libctx, propq)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_CTX_set1_ikme(sealctx, base->ikmE, base->ikmElen)))
+        goto end;
+    if (base->mode == OSSL_HPKE_MODE_AUTH
+        || base->mode == OSSL_HPKE_MODE_PSKAUTH) {
+        if (!TEST_true(base->ikmAuth != NULL && base->ikmAuthlen > 0))
+            goto end;
+        if (!TEST_true(OSSL_HPKE_keygen(base->suite,
+                                        authpub, &authpublen, &authpriv,
+                                        base->ikmAuth, base->ikmAuthlen,
+                                        libctx, propq)))
+            goto end;
+        if (!TEST_true(OSSL_HPKE_CTX_set1_authpriv(sealctx, authpriv)))
+            goto end;
+    }
+    if (!TEST_true(OSSL_HPKE_keygen(base->suite, rpub, &rpublen, &privR,
+                                    base->ikmR, base->ikmRlen, libctx, propq)))
+        goto end;
+    if (!TEST_true(cmpkey(privR, base->expected_pkRm, base->expected_pkRmlen)))
+        goto end;
+    if (base->mode == OSSL_HPKE_MODE_PSK
+        || base->mode == OSSL_HPKE_MODE_PSKAUTH) {
+        if (!TEST_true(OSSL_HPKE_CTX_set1_psk(sealctx, base->pskid,
+                                              base->psk, base->psklen)))
+            goto end;
+    }
+    if (!TEST_true(OSSL_HPKE_encap(sealctx, enc, &enclen,
+                                   rpub, rpublen,
+                                   base->ksinfo, base->ksinfolen)))
+        goto end;
+    if (!TEST_true(cmpkey(privE, enc, enclen)))
+        goto end;
+    for (i = 0; i < aeadsz; ++i) {
+        ctlen = sizeof(ct);
+        memset(ct, 0, ctlen);
+        if (!TEST_true(OSSL_HPKE_seal(sealctx, ct, &ctlen,
+                                      aead[i].aad, aead[i].aadlen,
+                                      aead[i].pt, aead[i].ptlen)))
+            goto end;
+        if (!TEST_mem_eq(ct, ctlen, aead[i].expected_ct,
+                         aead[i].expected_ctlen))
+            goto end;
+        if (!TEST_true(OSSL_HPKE_CTX_get_seq(sealctx, &lastseq)))
+            goto end;
+        if (lastseq != (uint64_t)(i + 1))
+            goto end;
+    }
+    if (!TEST_ptr(openctx = OSSL_HPKE_CTX_new(base->mode, base->suite,
+                                              libctx, propq)))
+        goto end;
+    if (base->mode == OSSL_HPKE_MODE_PSK
+        || base->mode == OSSL_HPKE_MODE_PSKAUTH) {
+        if (!TEST_true(base->pskid != NULL && base->psk != NULL
+                       && base->psklen > 0))
+            goto end;
+        if (!TEST_true(OSSL_HPKE_CTX_set1_psk(openctx, base->pskid,
+                                              base->psk, base->psklen)))
+            goto end;
+    }
+    if (base->mode == OSSL_HPKE_MODE_AUTH
+        || base->mode == OSSL_HPKE_MODE_PSKAUTH) {
+        if (!TEST_true(OSSL_HPKE_CTX_set1_authpub(openctx,
+                                                  authpub, authpublen)))
+            goto end;
+    }
+    if (!TEST_true(OSSL_HPKE_decap(openctx, enc, enclen, privR,
+                                   base->ksinfo, base->ksinfolen)))
+        goto end;
+    for (i = 0; i < aeadsz; ++i) {
+        ptoutlen = sizeof(ptout);
+        memset(ptout, 0, ptoutlen);
+        if (!TEST_true(OSSL_HPKE_open(openctx, ptout, &ptoutlen,
+                                      aead[i].aad, aead[i].aadlen,
+                                      aead[i].expected_ct,
+                                      aead[i].expected_ctlen)))
+            goto end;
+        if (!TEST_mem_eq(aead[i].pt, aead[i].ptlen, ptout, ptoutlen))
+            goto end;
+        /* check the sequence is being incremented as expected */
+        if (!TEST_true(OSSL_HPKE_CTX_get_seq(openctx, &lastseq)))
+            goto end;
+        if (lastseq != (uint64_t)(i + 1))
+            goto end;
+    }
+    /* check exporters */
+    for (i = 0; i < exportsz; ++i) {
+        size_t len = export[i].expected_secretlen;
+        unsigned char eval[OSSL_HPKE_TSTSIZE];
+
+        if (len > sizeof(eval))
+            goto end;
+        /* export with too long label should fail */
+        if (!TEST_false(OSSL_HPKE_export(sealctx, eval, len,
+                                         export[i].context, -1)))
+            goto end;
+        /* good export call */
+        if (!TEST_true(OSSL_HPKE_export(sealctx, eval, len,
+                                        export[i].context,
+                                        export[i].contextlen)))
+            goto end;
+        if (!TEST_mem_eq(eval, len, export[i].expected_secret,
+                         export[i].expected_secretlen))
+            goto end;
+
+        /* check seal fails if export only mode */
+        if (aeadsz == 0) {
+
+            if (!TEST_false(OSSL_HPKE_seal(sealctx, ct, &ctlen,
+                                           NULL, 0, ptout, ptoutlen)))
+                goto end;
+        }
+    }
+    ret = 1;
+end:
+    OSSL_HPKE_CTX_free(sealctx);
+    OSSL_HPKE_CTX_free(openctx);
+    EVP_PKEY_free(privE);
+    EVP_PKEY_free(privR);
+    EVP_PKEY_free(authpriv);
+    return ret;
+}
+
+static const unsigned char pt[] = {
+    0x42, 0x65, 0x61, 0x75, 0x74, 0x79, 0x20, 0x69,
+    0x73, 0x20, 0x74, 0x72, 0x75, 0x74, 0x68, 0x2c,
+    0x20, 0x74, 0x72, 0x75, 0x74, 0x68, 0x20, 0x62,
+    0x65, 0x61, 0x75, 0x74, 0x79
+};
+static const unsigned char ksinfo[] = {
+    0x4f, 0x64, 0x65, 0x20, 0x6f, 0x6e, 0x20, 0x61,
+    0x20, 0x47, 0x72, 0x65, 0x63, 0x69, 0x61, 0x6e,
+    0x20, 0x55, 0x72, 0x6e
+};
+/*
+ * static const char *pskid = "Ennyn Durin aran Moria";
+ */
+static const unsigned char pskid[] = {
+    0x45, 0x6e, 0x6e, 0x79, 0x6e, 0x20, 0x44, 0x75,
+    0x72, 0x69, 0x6e, 0x20, 0x61, 0x72, 0x61, 0x6e,
+    0x20, 0x4d, 0x6f, 0x72, 0x69, 0x61, 0x00
+};
+static const unsigned char psk[] = {
+    0x02, 0x47, 0xfd, 0x33, 0xb9, 0x13, 0x76, 0x0f,
+    0xa1, 0xfa, 0x51, 0xe1, 0x89, 0x2d, 0x9f, 0x30,
+    0x7f, 0xbe, 0x65, 0xeb, 0x17, 0x1e, 0x81, 0x32,
+    0xc2, 0xaf, 0x18, 0x55, 0x5a, 0x73, 0x8b, 0x82
+};
+
+/* these need to be "outside" the function below to keep check-ansi CI happy */
+static const unsigned char first_ikme[] = {
+    0x78, 0x62, 0x8c, 0x35, 0x4e, 0x46, 0xf3, 0xe1,
+    0x69, 0xbd, 0x23, 0x1b, 0xe7, 0xb2, 0xff, 0x1c,
+    0x77, 0xaa, 0x30, 0x24, 0x60, 0xa2, 0x6d, 0xbf,
+    0xa1, 0x55, 0x15, 0x68, 0x4c, 0x00, 0x13, 0x0b
+};
+static const unsigned char first_ikmr[] = {
+    0xd4, 0xa0, 0x9d, 0x09, 0xf5, 0x75, 0xfe, 0xf4,
+    0x25, 0x90, 0x5d, 0x2a, 0xb3, 0x96, 0xc1, 0x44,
+    0x91, 0x41, 0x46, 0x3f, 0x69, 0x8f, 0x8e, 0xfd,
+    0xb7, 0xac, 0xcf, 0xaf, 0xf8, 0x99, 0x50, 0x98
+};
+static const unsigned char first_ikmepub[] = {
+    0x0a, 0xd0, 0x95, 0x0d, 0x9f, 0xb9, 0x58, 0x8e,
+    0x59, 0x69, 0x0b, 0x74, 0xf1, 0x23, 0x7e, 0xcd,
+    0xf1, 0xd7, 0x75, 0xcd, 0x60, 0xbe, 0x2e, 0xca,
+    0x57, 0xaf, 0x5a, 0x4b, 0x04, 0x71, 0xc9, 0x1b,
+};
+static const unsigned char first_ikmrpub[] = {
+    0x9f, 0xed, 0x7e, 0x8c, 0x17, 0x38, 0x75, 0x60,
+    0xe9, 0x2c, 0xc6, 0x46, 0x2a, 0x68, 0x04, 0x96,
+    0x57, 0x24, 0x6a, 0x09, 0xbf, 0xa8, 0xad, 0xe7,
+    0xae, 0xfe, 0x58, 0x96, 0x72, 0x01, 0x63, 0x66
+};
+static const unsigned char first_ikmrpriv[] = {
+    0xc5, 0xeb, 0x01, 0xeb, 0x45, 0x7f, 0xe6, 0xc6,
+    0xf5, 0x75, 0x77, 0xc5, 0x41, 0x3b, 0x93, 0x15,
+    0x50, 0xa1, 0x62, 0xc7, 0x1a, 0x03, 0xac, 0x8d,
+    0x19, 0x6b, 0xab, 0xbd, 0x4e, 0x5c, 0xe0, 0xfd
+};
+static const unsigned char first_expected_shared_secret[] = {
+    0x72, 0x76, 0x99, 0xf0, 0x09, 0xff, 0xe3, 0xc0,
+    0x76, 0x31, 0x50, 0x19, 0xc6, 0x96, 0x48, 0x36,
+    0x6b, 0x69, 0x17, 0x14, 0x39, 0xbd, 0x7d, 0xd0,
+    0x80, 0x77, 0x43, 0xbd, 0xe7, 0x69, 0x86, 0xcd
+};
+static const unsigned char first_aad0[] = {
+    0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2d, 0x30
+};
+static const unsigned char first_ct0[] = {
+    0xe5, 0x2c, 0x6f, 0xed, 0x7f, 0x75, 0x8d, 0x0c,
+    0xf7, 0x14, 0x56, 0x89, 0xf2, 0x1b, 0xc1, 0xbe,
+    0x6e, 0xc9, 0xea, 0x09, 0x7f, 0xef, 0x4e, 0x95,
+    0x94, 0x40, 0x01, 0x2f, 0x4f, 0xeb, 0x73, 0xfb,
+    0x61, 0x1b, 0x94, 0x61, 0x99, 0xe6, 0x81, 0xf4,
+    0xcf, 0xc3, 0x4d, 0xb8, 0xea
+};
+static const unsigned char first_aad1[] = {
+    0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2d, 0x31
+};
+static const unsigned char first_ct1[] = {
+    0x49, 0xf3, 0xb1, 0x9b, 0x28, 0xa9, 0xea, 0x9f,
+    0x43, 0xe8, 0xc7, 0x12, 0x04, 0xc0, 0x0d, 0x4a,
+    0x49, 0x0e, 0xe7, 0xf6, 0x13, 0x87, 0xb6, 0x71,
+    0x9d, 0xb7, 0x65, 0xe9, 0x48, 0x12, 0x3b, 0x45,
+    0xb6, 0x16, 0x33, 0xef, 0x05, 0x9b, 0xa2, 0x2c,
+    0xd6, 0x24, 0x37, 0xc8, 0xba
+};
+static const unsigned char first_aad2[] = {
+    0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2d, 0x32
+};
+static const unsigned char first_ct2[] = {
+    0x25, 0x7c, 0xa6, 0xa0, 0x84, 0x73, 0xdc, 0x85,
+    0x1f, 0xde, 0x45, 0xaf, 0xd5, 0x98, 0xcc, 0x83,
+    0xe3, 0x26, 0xdd, 0xd0, 0xab, 0xe1, 0xef, 0x23,
+    0xba, 0xa3, 0xba, 0xa4, 0xdd, 0x8c, 0xde, 0x99,
+    0xfc, 0xe2, 0xc1, 0xe8, 0xce, 0x68, 0x7b, 0x0b,
+    0x47, 0xea, 0xd1, 0xad, 0xc9
+};
+static const unsigned char first_export1[] = {
+    0xdf, 0xf1, 0x7a, 0xf3, 0x54, 0xc8, 0xb4, 0x16,
+    0x73, 0x56, 0x7d, 0xb6, 0x25, 0x9f, 0xd6, 0x02,
+    0x99, 0x67, 0xb4, 0xe1, 0xaa, 0xd1, 0x30, 0x23,
+    0xc2, 0xae, 0x5d, 0xf8, 0xf4, 0xf4, 0x3b, 0xf6
+};
+static const unsigned char first_context2[] = { 0x00 };
+static const unsigned char first_export2[] = {
+    0x6a, 0x84, 0x72, 0x61, 0xd8, 0x20, 0x7f, 0xe5,
+    0x96, 0xbe, 0xfb, 0x52, 0x92, 0x84, 0x63, 0x88,
+    0x1a, 0xb4, 0x93, 0xda, 0x34, 0x5b, 0x10, 0xe1,
+    0xdc, 0xc6, 0x45, 0xe3, 0xb9, 0x4e, 0x2d, 0x95
+};
+static const unsigned char first_context3[] = {
+    0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74,
+    0x65, 0x78, 0x74
+};
+static const unsigned char first_export3[] = {
+    0x8a, 0xff, 0x52, 0xb4, 0x5a, 0x1b, 0xe3, 0xa7,
+    0x34, 0xbc, 0x7a, 0x41, 0xe2, 0x0b, 0x4e, 0x05,
+    0x5a, 0xd4, 0xc4, 0xd2, 0x21, 0x04, 0xb0, 0xc2,
+    0x02, 0x85, 0xa7, 0xc4, 0x30, 0x24, 0x01, 0xcd
+};
+
+static int x25519kdfsha256_hkdfsha256_aes128gcm_psk_test(void)
+{
+    const TEST_BASEDATA pskdata = {
+        /* "X25519", NULL, "SHA256", "SHA256", "AES-128-GCM", */
+        OSSL_HPKE_MODE_PSK,
+        {
+            OSSL_HPKE_KEM_ID_X25519,
+            OSSL_HPKE_KDF_ID_HKDF_SHA256,
+            OSSL_HPKE_AEAD_ID_AES_GCM_128
+        },
+        first_ikme, sizeof(first_ikme),
+        first_ikmepub, sizeof(first_ikmepub),
+        first_ikmr, sizeof(first_ikmr),
+        first_ikmrpub, sizeof(first_ikmrpub),
+        first_ikmrpriv, sizeof(first_ikmrpriv),
+        first_expected_shared_secret, sizeof(first_expected_shared_secret),
+        ksinfo, sizeof(ksinfo),
+        NULL, 0,    /* No Auth */
+        psk, sizeof(psk), (char *) pskid
+    };
+    const TEST_AEADDATA aeaddata[] = {
+        {
+            0,
+            pt, sizeof(pt),
+            first_aad0, sizeof(first_aad0),
+            first_ct0, sizeof(first_ct0)
+        },
+        {
+            1,
+            pt, sizeof(pt),
+            first_aad1, sizeof(first_aad1),
+            first_ct1, sizeof(first_ct1)
+        },
+        {
+            2,
+            pt, sizeof(pt),
+            first_aad2, sizeof(first_aad2),
+            first_ct2, sizeof(first_ct2)
+        }
+    };
+    const TEST_EXPORTDATA exportdata[] = {
+        { NULL, 0, first_export1, sizeof(first_export1) },
+        { first_context2, sizeof(first_context2),
+          first_export2, sizeof(first_export2) },
+        { first_context3, sizeof(first_context3),
+          first_export3, sizeof(first_export3) },
+    };
+    return do_testhpke(&pskdata, aeaddata, OSSL_NELEM(aeaddata),
+                       exportdata, OSSL_NELEM(exportdata));
+}
+
+static const unsigned char second_ikme[] = {
+    0x72, 0x68, 0x60, 0x0d, 0x40, 0x3f, 0xce, 0x43,
+    0x15, 0x61, 0xae, 0xf5, 0x83, 0xee, 0x16, 0x13,
+    0x52, 0x7c, 0xff, 0x65, 0x5c, 0x13, 0x43, 0xf2,
+    0x98, 0x12, 0xe6, 0x67, 0x06, 0xdf, 0x32, 0x34
+};
+static const unsigned char second_ikmepub[] = {
+    0x37, 0xfd, 0xa3, 0x56, 0x7b, 0xdb, 0xd6, 0x28,
+    0xe8, 0x86, 0x68, 0xc3, 0xc8, 0xd7, 0xe9, 0x7d,
+    0x1d, 0x12, 0x53, 0xb6, 0xd4, 0xea, 0x6d, 0x44,
+    0xc1, 0x50, 0xf7, 0x41, 0xf1, 0xbf, 0x44, 0x31,
+};
+static const unsigned char second_ikmr[] = {
+    0x6d, 0xb9, 0xdf, 0x30, 0xaa, 0x07, 0xdd, 0x42,
+    0xee, 0x5e, 0x81, 0x81, 0xaf, 0xdb, 0x97, 0x7e,
+    0x53, 0x8f, 0x5e, 0x1f, 0xec, 0x8a, 0x06, 0x22,
+    0x3f, 0x33, 0xf7, 0x01, 0x3e, 0x52, 0x50, 0x37
+};
+static const unsigned char second_ikmrpub[] = {
+    0x39, 0x48, 0xcf, 0xe0, 0xad, 0x1d, 0xdb, 0x69,
+    0x5d, 0x78, 0x0e, 0x59, 0x07, 0x71, 0x95, 0xda,
+    0x6c, 0x56, 0x50, 0x6b, 0x02, 0x73, 0x29, 0x79,
+    0x4a, 0xb0, 0x2b, 0xca, 0x80, 0x81, 0x5c, 0x4d
+};
+static const unsigned char second_ikmrpriv[] = {
+    0x46, 0x12, 0xc5, 0x50, 0x26, 0x3f, 0xc8, 0xad,
+    0x58, 0x37, 0x5d, 0xf3, 0xf5, 0x57, 0xaa, 0xc5,
+    0x31, 0xd2, 0x68, 0x50, 0x90, 0x3e, 0x55, 0xa9,
+    0xf2, 0x3f, 0x21, 0xd8, 0x53, 0x4e, 0x8a, 0xc8
+};
+static const unsigned char second_expected_shared_secret[] = {
+    0xfe, 0x0e, 0x18, 0xc9, 0xf0, 0x24, 0xce, 0x43,
+    0x79, 0x9a, 0xe3, 0x93, 0xc7, 0xe8, 0xfe, 0x8f,
+    0xce, 0x9d, 0x21, 0x88, 0x75, 0xe8, 0x22, 0x7b,
+    0x01, 0x87, 0xc0, 0x4e, 0x7d, 0x2e, 0xa1, 0xfc
+};
+static const unsigned char second_aead0[] = {
+    0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2d, 0x30
+};
+static const unsigned char second_ct0[] = {
+    0xf9, 0x38, 0x55, 0x8b, 0x5d, 0x72, 0xf1, 0xa2,
+    0x38, 0x10, 0xb4, 0xbe, 0x2a, 0xb4, 0xf8, 0x43,
+    0x31, 0xac, 0xc0, 0x2f, 0xc9, 0x7b, 0xab, 0xc5,
+    0x3a, 0x52, 0xae, 0x82, 0x18, 0xa3, 0x55, 0xa9,
+    0x6d, 0x87, 0x70, 0xac, 0x83, 0xd0, 0x7b, 0xea,
+    0x87, 0xe1, 0x3c, 0x51, 0x2a
+};
+static const unsigned char second_aead1[] = {
+    0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2d, 0x31
+};
+static const unsigned char second_ct1[] = {
+    0xaf, 0x2d, 0x7e, 0x9a, 0xc9, 0xae, 0x7e, 0x27,
+    0x0f, 0x46, 0xba, 0x1f, 0x97, 0x5b, 0xe5, 0x3c,
+    0x09, 0xf8, 0xd8, 0x75, 0xbd, 0xc8, 0x53, 0x54,
+    0x58, 0xc2, 0x49, 0x4e, 0x8a, 0x6e, 0xab, 0x25,
+    0x1c, 0x03, 0xd0, 0xc2, 0x2a, 0x56, 0xb8, 0xca,
+    0x42, 0xc2, 0x06, 0x3b, 0x84
+};
+static const unsigned char second_export1[] = {
+    0x38, 0x53, 0xfe, 0x2b, 0x40, 0x35, 0x19, 0x5a,
+    0x57, 0x3f, 0xfc, 0x53, 0x85, 0x6e, 0x77, 0x05,
+    0x8e, 0x15, 0xd9, 0xea, 0x06, 0x4d, 0xe3, 0xe5,
+    0x9f, 0x49, 0x61, 0xd0, 0x09, 0x52, 0x50, 0xee
+};
+static const unsigned char second_context2[] = { 0x00 };
+static const unsigned char second_export2[] = {
+    0x2e, 0x8f, 0x0b, 0x54, 0x67, 0x3c, 0x70, 0x29,
+    0x64, 0x9d, 0x4e, 0xb9, 0xd5, 0xe3, 0x3b, 0xf1,
+    0x87, 0x2c, 0xf7, 0x6d, 0x62, 0x3f, 0xf1, 0x64,
+    0xac, 0x18, 0x5d, 0xa9, 0xe8, 0x8c, 0x21, 0xa5
+};
+static const unsigned char second_context3[] = {
+    0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74,
+    0x65, 0x78, 0x74
+};
+static const unsigned char second_export3[] = {
+    0xe9, 0xe4, 0x30, 0x65, 0x10, 0x2c, 0x38, 0x36,
+    0x40, 0x1b, 0xed, 0x8c, 0x3c, 0x3c, 0x75, 0xae,
+    0x46, 0xbe, 0x16, 0x39, 0x86, 0x93, 0x91, 0xd6,
+    0x2c, 0x61, 0xf1, 0xec, 0x7a, 0xf5, 0x49, 0x31
+};
+
+static int x25519kdfsha256_hkdfsha256_aes128gcm_base_test(void)
+{
+    const TEST_BASEDATA basedata = {
+        OSSL_HPKE_MODE_BASE,
+        {
+            OSSL_HPKE_KEM_ID_X25519,
+            OSSL_HPKE_KDF_ID_HKDF_SHA256,
+            OSSL_HPKE_AEAD_ID_AES_GCM_128
+        },
+        second_ikme, sizeof(second_ikme),
+        second_ikmepub, sizeof(second_ikmepub),
+        second_ikmr, sizeof(second_ikmr),
+        second_ikmrpub, sizeof(second_ikmrpub),
+        second_ikmrpriv, sizeof(second_ikmrpriv),
+        second_expected_shared_secret, sizeof(second_expected_shared_secret),
+        ksinfo, sizeof(ksinfo),
+        NULL, 0, /* no auth ikm */
+        NULL, 0, NULL /* no psk */
+    };
+    const TEST_AEADDATA aeaddata[] = {
+        {
+            0,
+            pt, sizeof(pt),
+            second_aead0, sizeof(second_aead0),
+            second_ct0, sizeof(second_ct0)
+        },
+        {
+            1,
+            pt, sizeof(pt),
+            second_aead1, sizeof(second_aead1),
+            second_ct1, sizeof(second_ct1)
+        }
+    };
+    const TEST_EXPORTDATA exportdata[] = {
+        { NULL, 0, second_export1, sizeof(second_export1) },
+        { second_context2, sizeof(second_context2),
+          second_export2, sizeof(second_export2) },
+        { second_context3, sizeof(second_context3),
+          second_export3, sizeof(second_export3) },
+    };
+    return do_testhpke(&basedata, aeaddata, OSSL_NELEM(aeaddata),
+                       exportdata, OSSL_NELEM(exportdata));
+}
+
+static const unsigned char third_ikme[] = {
+    0x42, 0x70, 0xe5, 0x4f, 0xfd, 0x08, 0xd7, 0x9d,
+    0x59, 0x28, 0x02, 0x0a, 0xf4, 0x68, 0x6d, 0x8f,
+    0x6b, 0x7d, 0x35, 0xdb, 0xe4, 0x70, 0x26, 0x5f,
+    0x1f, 0x5a, 0xa2, 0x28, 0x16, 0xce, 0x86, 0x0e
+};
+static const unsigned char third_ikmepub[] = {
+    0x04, 0xa9, 0x27, 0x19, 0xc6, 0x19, 0x5d, 0x50,
+    0x85, 0x10, 0x4f, 0x46, 0x9a, 0x8b, 0x98, 0x14,
+    0xd5, 0x83, 0x8f, 0xf7, 0x2b, 0x60, 0x50, 0x1e,
+    0x2c, 0x44, 0x66, 0xe5, 0xe6, 0x7b, 0x32, 0x5a,
+    0xc9, 0x85, 0x36, 0xd7, 0xb6, 0x1a, 0x1a, 0xf4,
+    0xb7, 0x8e, 0x5b, 0x7f, 0x95, 0x1c, 0x09, 0x00,
+    0xbe, 0x86, 0x3c, 0x40, 0x3c, 0xe6, 0x5c, 0x9b,
+    0xfc, 0xb9, 0x38, 0x26, 0x57, 0x22, 0x2d, 0x18,
+    0xc4,
+};
+static const unsigned char third_ikmr[] = {
+    0x66, 0x8b, 0x37, 0x17, 0x1f, 0x10, 0x72, 0xf3,
+    0xcf, 0x12, 0xea, 0x8a, 0x23, 0x6a, 0x45, 0xdf,
+    0x23, 0xfc, 0x13, 0xb8, 0x2a, 0xf3, 0x60, 0x9a,
+    0xd1, 0xe3, 0x54, 0xf6, 0xef, 0x81, 0x75, 0x50
+};
+static const unsigned char third_ikmrpub[] = {
+    0x04, 0xfe, 0x8c, 0x19, 0xce, 0x09, 0x05, 0x19,
+    0x1e, 0xbc, 0x29, 0x8a, 0x92, 0x45, 0x79, 0x25,
+    0x31, 0xf2, 0x6f, 0x0c, 0xec, 0xe2, 0x46, 0x06,
+    0x39, 0xe8, 0xbc, 0x39, 0xcb, 0x7f, 0x70, 0x6a,
+    0x82, 0x6a, 0x77, 0x9b, 0x4c, 0xf9, 0x69, 0xb8,
+    0xa0, 0xe5, 0x39, 0xc7, 0xf6, 0x2f, 0xb3, 0xd3,
+    0x0a, 0xd6, 0xaa, 0x8f, 0x80, 0xe3, 0x0f, 0x1d,
+    0x12, 0x8a, 0xaf, 0xd6, 0x8a, 0x2c, 0xe7, 0x2e,
+    0xa0
+};
+static const unsigned char third_ikmrpriv[] = {
+    0xf3, 0xce, 0x7f, 0xda, 0xe5, 0x7e, 0x1a, 0x31,
+    0x0d, 0x87, 0xf1, 0xeb, 0xbd, 0xe6, 0xf3, 0x28,
+    0xbe, 0x0a, 0x99, 0xcd, 0xbc, 0xad, 0xf4, 0xd6,
+    0x58, 0x9c, 0xf2, 0x9d, 0xe4, 0xb8, 0xff, 0xd2
+};
+static const unsigned char third_expected_shared_secret[] = {
+    0xc0, 0xd2, 0x6a, 0xea, 0xb5, 0x36, 0x60, 0x9a,
+    0x57, 0x2b, 0x07, 0x69, 0x5d, 0x93, 0x3b, 0x58,
+    0x9d, 0xcf, 0x36, 0x3f, 0xf9, 0xd9, 0x3c, 0x93,
+    0xad, 0xea, 0x53, 0x7a, 0xea, 0xbb, 0x8c, 0xb8
+};
+static const unsigned char third_aead0[] = {
+    0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2d, 0x30
+};
+static const unsigned char third_ct0[] = {
+    0x5a, 0xd5, 0x90, 0xbb, 0x8b, 0xaa, 0x57, 0x7f,
+    0x86, 0x19, 0xdb, 0x35, 0xa3, 0x63, 0x11, 0x22,
+    0x6a, 0x89, 0x6e, 0x73, 0x42, 0xa6, 0xd8, 0x36,
+    0xd8, 0xb7, 0xbc, 0xd2, 0xf2, 0x0b, 0x6c, 0x7f,
+    0x90, 0x76, 0xac, 0x23, 0x2e, 0x3a, 0xb2, 0x52,
+    0x3f, 0x39, 0x51, 0x34, 0x34
+};
+static const unsigned char third_aead1[] = {
+    0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2d, 0x31
+};
+static const unsigned char third_ct1[] = {
+    0xfa, 0x6f, 0x03, 0x7b, 0x47, 0xfc, 0x21, 0x82,
+    0x6b, 0x61, 0x01, 0x72, 0xca, 0x96, 0x37, 0xe8,
+    0x2d, 0x6e, 0x58, 0x01, 0xeb, 0x31, 0xcb, 0xd3,
+    0x74, 0x82, 0x71, 0xaf, 0xfd, 0x4e, 0xcb, 0x06,
+    0x64, 0x6e, 0x03, 0x29, 0xcb, 0xdf, 0x3c, 0x3c,
+    0xd6, 0x55, 0xb2, 0x8e, 0x82
+};
+static const unsigned char third_export1[] = {
+    0x5e, 0x9b, 0xc3, 0xd2, 0x36, 0xe1, 0x91, 0x1d,
+    0x95, 0xe6, 0x5b, 0x57, 0x6a, 0x8a, 0x86, 0xd4,
+    0x78, 0xfb, 0x82, 0x7e, 0x8b, 0xdf, 0xe7, 0x7b,
+    0x74, 0x1b, 0x28, 0x98, 0x90, 0x49, 0x0d, 0x4d
+};
+static const unsigned char third_context2[] = { 0x00 };
+static const unsigned char third_export2[] = {
+    0x6c, 0xff, 0x87, 0x65, 0x89, 0x31, 0xbd, 0xa8,
+    0x3d, 0xc8, 0x57, 0xe6, 0x35, 0x3e, 0xfe, 0x49,
+    0x87, 0xa2, 0x01, 0xb8, 0x49, 0x65, 0x8d, 0x9b,
+    0x04, 0x7a, 0xab, 0x4c, 0xf2, 0x16, 0xe7, 0x96
+};
+static const unsigned char third_context3[] = {
+    0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74,
+    0x65, 0x78, 0x74
+};
+static const unsigned char third_export3[] = {
+    0xd8, 0xf1, 0xea, 0x79, 0x42, 0xad, 0xbb, 0xa7,
+    0x41, 0x2c, 0x6d, 0x43, 0x1c, 0x62, 0xd0, 0x13,
+    0x71, 0xea, 0x47, 0x6b, 0x82, 0x3e, 0xb6, 0x97,
+    0xe1, 0xf6, 0xe6, 0xca, 0xe1, 0xda, 0xb8, 0x5a
+};
+
+static int P256kdfsha256_hkdfsha256_aes128gcm_base_test(void)
+{
+    const TEST_BASEDATA basedata = {
+        OSSL_HPKE_MODE_BASE,
+        {
+            OSSL_HPKE_KEM_ID_P256,
+            OSSL_HPKE_KDF_ID_HKDF_SHA256,
+            OSSL_HPKE_AEAD_ID_AES_GCM_128
+        },
+        third_ikme, sizeof(third_ikme),
+        third_ikmepub, sizeof(third_ikmepub),
+        third_ikmr, sizeof(third_ikmr),
+        third_ikmrpub, sizeof(third_ikmrpub),
+        third_ikmrpriv, sizeof(third_ikmrpriv),
+        third_expected_shared_secret, sizeof(third_expected_shared_secret),
+        ksinfo, sizeof(ksinfo),
+        NULL, 0, /* no auth */
+        NULL, 0, NULL /* PSK stuff */
+    };
+    const TEST_AEADDATA aeaddata[] = {
+        {
+            0,
+            pt, sizeof(pt),
+            third_aead0, sizeof(third_aead0),
+            third_ct0, sizeof(third_ct0)
+        },
+        {
+            1,
+            pt, sizeof(pt),
+            third_aead1, sizeof(third_aead1),
+            third_ct1, sizeof(third_ct1)
+        }
+    };
+    const TEST_EXPORTDATA exportdata[] = {
+        { NULL, 0, third_export1, sizeof(third_export1) },
+        { third_context2, sizeof(third_context2),
+          third_export2, sizeof(third_export2) },
+        { third_context3, sizeof(third_context3),
+          third_export3, sizeof(third_export3) },
+    };
+    return do_testhpke(&basedata, aeaddata, OSSL_NELEM(aeaddata),
+                       exportdata, OSSL_NELEM(exportdata));
+}
+
+static const unsigned char fourth_ikme[] = {
+    0x55, 0xbc, 0x24, 0x5e, 0xe4, 0xef, 0xda, 0x25,
+    0xd3, 0x8f, 0x2d, 0x54, 0xd5, 0xbb, 0x66, 0x65,
+    0x29, 0x1b, 0x99, 0xf8, 0x10, 0x8a, 0x8c, 0x4b,
+    0x68, 0x6c, 0x2b, 0x14, 0x89, 0x3e, 0xa5, 0xd9
+};
+static const unsigned char fourth_ikmepub[] = {
+    0xe5, 0xe8, 0xf9, 0xbf, 0xff, 0x6c, 0x2f, 0x29,
+    0x79, 0x1f, 0xc3, 0x51, 0xd2, 0xc2, 0x5c, 0xe1,
+    0x29, 0x9a, 0xa5, 0xea, 0xca, 0x78, 0xa7, 0x57,
+    0xc0, 0xb4, 0xfb, 0x4b, 0xcd, 0x83, 0x09, 0x18
+};
+static const unsigned char fourth_ikmr[] = {
+    0x68, 0x3a, 0xe0, 0xda, 0x1d, 0x22, 0x18, 0x1e,
+    0x74, 0xed, 0x2e, 0x50, 0x3e, 0xbf, 0x82, 0x84,
+    0x0d, 0xeb, 0x1d, 0x5e, 0x87, 0x2c, 0xad, 0xe2,
+    0x0f, 0x4b, 0x45, 0x8d, 0x99, 0x78, 0x3e, 0x31
+};
+static const unsigned char fourth_ikmrpub[] = {
+    0x19, 0x41, 0x41, 0xca, 0x6c, 0x3c, 0x3b, 0xeb,
+    0x47, 0x92, 0xcd, 0x97, 0xba, 0x0e, 0xa1, 0xfa,
+    0xff, 0x09, 0xd9, 0x84, 0x35, 0x01, 0x23, 0x45,
+    0x76, 0x6e, 0xe3, 0x3a, 0xae, 0x2d, 0x76, 0x64
+};
+static const unsigned char fourth_ikmrpriv[] = {
+    0x33, 0xd1, 0x96, 0xc8, 0x30, 0xa1, 0x2f, 0x9a,
+    0xc6, 0x5d, 0x6e, 0x56, 0x5a, 0x59, 0x0d, 0x80,
+    0xf0, 0x4e, 0xe9, 0xb1, 0x9c, 0x83, 0xc8, 0x7f,
+    0x2c, 0x17, 0x0d, 0x97, 0x2a, 0x81, 0x28, 0x48
+};
+static const unsigned char fourth_expected_shared_secret[] = {
+    0xe8, 0x17, 0x16, 0xce, 0x8f, 0x73, 0x14, 0x1d,
+    0x4f, 0x25, 0xee, 0x90, 0x98, 0xef, 0xc9, 0x68,
+    0xc9, 0x1e, 0x5b, 0x8c, 0xe5, 0x2f, 0xff, 0xf5,
+    0x9d, 0x64, 0x03, 0x9e, 0x82, 0x91, 0x8b, 0x66
+};
+static const unsigned char fourth_export1[] = {
+    0x7a, 0x36, 0x22, 0x1b, 0xd5, 0x6d, 0x50, 0xfb,
+    0x51, 0xee, 0x65, 0xed, 0xfd, 0x98, 0xd0, 0x6a,
+    0x23, 0xc4, 0xdc, 0x87, 0x08, 0x5a, 0xa5, 0x86,
+    0x6c, 0xb7, 0x08, 0x72, 0x44, 0xbd, 0x2a, 0x36
+};
+static const unsigned char fourth_context2[] = { 0x00 };
+static const unsigned char fourth_export2[] = {
+    0xd5, 0x53, 0x5b, 0x87, 0x09, 0x9c, 0x6c, 0x3c,
+    0xe8, 0x0d, 0xc1, 0x12, 0xa2, 0x67, 0x1c, 0x6e,
+    0xc8, 0xe8, 0x11, 0xa2, 0xf2, 0x84, 0xf9, 0x48,
+    0xce, 0xc6, 0xdd, 0x17, 0x08, 0xee, 0x33, 0xf0
+};
+static const unsigned char fourth_context3[] = {
+    0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74,
+    0x65, 0x78, 0x74
+};
+static const unsigned char fourth_export3[] = {
+    0xff, 0xaa, 0xbc, 0x85, 0xa7, 0x76, 0x13, 0x6c,
+    0xa0, 0xc3, 0x78, 0xe5, 0xd0, 0x84, 0xc9, 0x14,
+    0x0a, 0xb5, 0x52, 0xb7, 0x8f, 0x03, 0x9d, 0x2e,
+    0x87, 0x75, 0xf2, 0x6e, 0xff, 0xf4, 0xc7, 0x0e
+};
+
+static int export_only_test(void)
+{
+    /* based on RFC9180 A.7 */
+    const TEST_BASEDATA basedata = {
+        OSSL_HPKE_MODE_BASE,
+        {
+            OSSL_HPKE_KEM_ID_X25519,
+            OSSL_HPKE_KDF_ID_HKDF_SHA256,
+            OSSL_HPKE_AEAD_ID_EXPORTONLY
+        },
+        fourth_ikme, sizeof(fourth_ikme),
+        fourth_ikmepub, sizeof(fourth_ikmepub),
+        fourth_ikmr, sizeof(fourth_ikmr),
+        fourth_ikmrpub, sizeof(fourth_ikmrpub),
+        fourth_ikmrpriv, sizeof(fourth_ikmrpriv),
+        fourth_expected_shared_secret, sizeof(fourth_expected_shared_secret),
+        ksinfo, sizeof(ksinfo),
+        NULL, 0, /* no auth */
+        NULL, 0, NULL /* PSK stuff */
+    };
+    const TEST_EXPORTDATA exportdata[] = {
+        { NULL, 0, fourth_export1, sizeof(fourth_export1) },
+        { fourth_context2, sizeof(fourth_context2),
+          fourth_export2, sizeof(fourth_export2) },
+        { fourth_context3, sizeof(fourth_context3),
+          fourth_export3, sizeof(fourth_export3) },
+    };
+    return do_testhpke(&basedata, NULL, 0,
+                       exportdata, OSSL_NELEM(exportdata));
+}
+
+/*
+ * Randomly toss a coin
+ */
+#define COIN_IS_HEADS (test_random() % 2)
+
+/* tables of HPKE modes and suite values */
+static int hpke_mode_list[] = {
+    OSSL_HPKE_MODE_BASE,
+    OSSL_HPKE_MODE_PSK,
+    OSSL_HPKE_MODE_AUTH,
+    OSSL_HPKE_MODE_PSKAUTH
+};
+static uint16_t hpke_kem_list[] = {
+    OSSL_HPKE_KEM_ID_P256,
+    OSSL_HPKE_KEM_ID_P384,
+    OSSL_HPKE_KEM_ID_P521,
+    OSSL_HPKE_KEM_ID_X25519,
+    OSSL_HPKE_KEM_ID_X448
+};
+static uint16_t hpke_kdf_list[] = {
+    OSSL_HPKE_KDF_ID_HKDF_SHA256,
+    OSSL_HPKE_KDF_ID_HKDF_SHA384,
+    OSSL_HPKE_KDF_ID_HKDF_SHA512
+};
+static uint16_t hpke_aead_list[] = {
+    OSSL_HPKE_AEAD_ID_AES_GCM_128,
+    OSSL_HPKE_AEAD_ID_AES_GCM_256,
+    OSSL_HPKE_AEAD_ID_CHACHA_POLY1305
+};
+
+/*
+ * Strings that can be used with names or IANA codepoints.
+ * Note that the initial entries from these lists should
+ * match the lists above, i.e. kem_str_list[0] and
+ * hpke_kem_list[0] should refer to the same KEM. We use
+ * that for verbose output via TEST_note() below.
+ * Subsequent entries are only used for tests of
+ * OSSL_HPKE_str2suite()
+ */
+static const char *mode_str_list[] = {
+    "base", "psk", "auth", "pskauth"
+};
+static const char *kem_str_list[] = {
+    "P-256", "P-384", "P-521", "x25519", "x448",
+    "0x10", "0x11", "0x12", "0x20", "0x21",
+    "16", "17", "18", "32", "33"
+};
+static const char *kdf_str_list[] = {
+    "hkdf-sha256", "hkdf-sha384", "hkdf-sha512",
+    "0x1", "0x01", "0x2", "0x02", "0x3", "0x03",
+    "1", "2", "3"
+};
+static const char *aead_str_list[] = {
+    "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "exporter",
+    "0x1", "0x01", "0x2", "0x02", "0x3", "0x03",
+    "1", "2", "3",
+    "0xff", "255"
+};
+/* table of bogus strings that better not work */
+static const char *bogus_suite_strs[] = {
+    "3,33,3",
+    "bogus,bogus,bogus",
+    "bogus,33,3,1,bogus",
+    "bogus,33,3,1",
+    "bogus,bogus",
+    "bogus",
+    /* one bad token */
+    "0x10,0x01,bogus",
+    "0x10,bogus,0x01",
+    "bogus,0x02,0x01",
+    /* in reverse order */
+    "aes-256-gcm,hkdf-sha512,x25519",
+    /* surplus separators */
+    ",,0x10,0x01,0x02",
+    "0x10,,0x01,0x02",
+    "0x10,0x01,,0x02",
+    /* embedded NUL chars */
+    "0x10,\00x01,,0x02",
+    "0x10,\0""0x01,0x02",
+    "0x10\0,0x01,0x02",
+    "0x10,0x01\0,0x02",
+    "0x10,0x01,\0""0x02",
+    /* embedded whitespace */
+    " aes-256-gcm,hkdf-sha512,x25519",
+    "aes-256-gcm, hkdf-sha512,x25519",
+    "aes-256-gcm ,hkdf-sha512,x25519",
+    "aes-256-gcm,hkdf-sha512, x25519",
+    "aes-256-gcm,hkdf-sha512 ,x25519",
+    "aes-256-gcm,hkdf-sha512,x25519 ",
+    /* good value followed by extra stuff */
+    "0x10,0x01,0x02,",
+    "0x10,0x01,0x02,,,",
+    "0x10,0x01,0x01,0x02",
+    "0x10,0x01,0x01,blah",
+    "0x10,0x01,0x01 0x02",
+    /* too few but good tokens */
+    "0x10,0x01",
+    "0x10",
+    /* empty things */
+    NULL,
+    "",
+    ",",
+    ",,"
+};
+
+/**
+ * @brief round-trips, generating keys, encrypt and decrypt
+ *
+ * This iterates over all mode and ciphersuite options trying
+ * a key gen, encrypt and decrypt for each. The aad, info, and
+ * seq inputs are randomly set or omitted each time. EVP and
+ * non-EVP key generation are randomly selected.
+ *
+ * @return 1 for success, other otherwise
+ */
+static int test_hpke_modes_suites(void)
+{
+    int overallresult = 1;
+    size_t mind = 0; /* index into hpke_mode_list */
+    size_t kemind = 0; /* index into hpke_kem_list */
+    size_t kdfind = 0; /* index into hpke_kdf_list */
+    size_t aeadind = 0; /* index into hpke_aead_list */
+
+    /* iterate over the different modes */
+    for (mind = 0; mind < OSSL_NELEM(hpke_mode_list); mind++) {
+        int hpke_mode = hpke_mode_list[mind];
+        size_t aadlen = OSSL_HPKE_TSTSIZE;
+        unsigned char aad[OSSL_HPKE_TSTSIZE];
+        unsigned char *aadp = NULL;
+        size_t infolen = 32;
+        unsigned char info[32];
+        unsigned char *infop = NULL;
+        unsigned char lpsk[32];
+        unsigned char *pskp = NULL;
+        char lpskid[32];
+        size_t psklen = 32;
+        char *pskidp = NULL;
+        EVP_PKEY *privp = NULL;
+        OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
+        size_t plainlen = OSSL_HPKE_TSTSIZE;
+        unsigned char plain[OSSL_HPKE_TSTSIZE];
+        uint64_t startseq = 0;
+        OSSL_HPKE_CTX *rctx = NULL;
+        OSSL_HPKE_CTX *ctx = NULL;
+
+        memset(plain, 0x00, OSSL_HPKE_TSTSIZE);
+        strcpy((char *)plain, "a message not in a bottle");
+        plainlen = strlen((char *)plain);
+        /*
+         * Randomly try with/without info, aad, seq. Given mode and suite
+         * combos, and this being run even a few times, we'll exercise many
+         * code paths fairly quickly. We don't really care what the values
+         * are but it'll be easier to debug if they're known, so we set 'em.
+         */
+        if (COIN_IS_HEADS) {
+            aadp = aad;
+            memset(aad, 'a', aadlen);
+        } else {
+            aadlen = 0;
+        }
+        if (COIN_IS_HEADS) {
+            infop = info;
+            memset(info, 'i', infolen);
+        } else {
+            infolen = 0;
+        }
+        if (hpke_mode == OSSL_HPKE_MODE_PSK
+            || hpke_mode == OSSL_HPKE_MODE_PSKAUTH) {
+            pskp = lpsk;
+            memset(lpsk, 'P', psklen);
+            pskidp = lpskid;
+            memset(lpskid, 'I', psklen - 1);
+            lpskid[psklen - 1] = '\0';
+        } else {
+            psklen = 0;
+        }
+        for (kemind = 0; /* iterate over the kems, kdfs and aeads */
+             overallresult == 1 && kemind < OSSL_NELEM(hpke_kem_list);
+             kemind++) {
+            uint16_t kem_id = hpke_kem_list[kemind];
+            size_t authpublen = OSSL_HPKE_TSTSIZE;
+            unsigned char authpub[OSSL_HPKE_TSTSIZE];
+            unsigned char *authpubp = NULL;
+            EVP_PKEY *authpriv = NULL;
+
+            hpke_suite.kem_id = kem_id;
+            if (hpke_mode == OSSL_HPKE_MODE_AUTH
+                || hpke_mode == OSSL_HPKE_MODE_PSKAUTH) {
+                if (TEST_true(OSSL_HPKE_keygen(hpke_suite, authpub, &authpublen,
+                                               &authpriv, NULL, 0,
+                                               testctx, NULL)) != 1) {
+                    overallresult = 0;
+                }
+                authpubp = authpub;
+            } else {
+                authpublen = 0;
+            }
+            for (kdfind = 0;
+                 overallresult == 1 && kdfind < OSSL_NELEM(hpke_kdf_list);
+                 kdfind++) {
+                uint16_t kdf_id = hpke_kdf_list[kdfind];
+
+                hpke_suite.kdf_id = kdf_id;
+                for (aeadind = 0;
+                     overallresult == 1
+                     && aeadind < OSSL_NELEM(hpke_aead_list);
+                     aeadind++) {
+                    uint16_t aead_id = hpke_aead_list[aeadind];
+                    size_t publen = OSSL_HPKE_TSTSIZE;
+                    unsigned char pub[OSSL_HPKE_TSTSIZE];
+                    size_t senderpublen = OSSL_HPKE_TSTSIZE;
+                    unsigned char senderpub[OSSL_HPKE_TSTSIZE];
+                    size_t cipherlen = OSSL_HPKE_TSTSIZE;
+                    unsigned char cipher[OSSL_HPKE_TSTSIZE];
+                    size_t clearlen = OSSL_HPKE_TSTSIZE;
+                    unsigned char clear[OSSL_HPKE_TSTSIZE];
+
+                    hpke_suite.aead_id = aead_id;
+                    if (!TEST_true(OSSL_HPKE_keygen(hpke_suite,
+                                                    pub, &publen, &privp,
+                                                    NULL, 0, testctx, NULL)))
+                        overallresult = 0;
+                    if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+                                                          testctx, NULL)))
+                        overallresult = 0;
+                    if (hpke_mode == OSSL_HPKE_MODE_PSK
+                        || hpke_mode == OSSL_HPKE_MODE_PSKAUTH) {
+                        if (!TEST_true(OSSL_HPKE_CTX_set1_psk(ctx, pskidp,
+                                                              pskp, psklen)))
+                            overallresult = 0;
+                    }
+                    if (hpke_mode == OSSL_HPKE_MODE_AUTH
+                        || hpke_mode == OSSL_HPKE_MODE_PSKAUTH) {
+                        if (!TEST_true(OSSL_HPKE_CTX_set1_authpriv(ctx,
+                                                                   authpriv)))
+                            overallresult = 0;
+                    }
+                    if (COIN_IS_HEADS) {
+                        RAND_bytes_ex(testctx,
+                                      (unsigned char *) &startseq,
+                                      sizeof(startseq),
+                                      RAND_DRBG_STRENGTH);
+                        if (!TEST_true(OSSL_HPKE_CTX_set_seq(ctx, startseq)))
+                            overallresult = 0;
+                    } else {
+                        startseq = 0;
+                    }
+                    if (!TEST_true(OSSL_HPKE_encap(ctx, senderpub,
+                                                   &senderpublen,
+                                                   pub, publen,
+                                                   infop, infolen)))
+                        overallresult = 0;
+                    /* throw in a call with a too-short cipherlen */
+                    cipherlen = 15;
+                    if (!TEST_false(OSSL_HPKE_seal(ctx, cipher, &cipherlen,
+                                                   aadp, aadlen,
+                                                   plain, plainlen)))
+                        overallresult = 0;
+                    /* fix back real cipherlen */
+                    cipherlen = OSSL_HPKE_TSTSIZE;
+                    if (!TEST_true(OSSL_HPKE_seal(ctx, cipher, &cipherlen,
+                                                  aadp, aadlen,
+                                                  plain, plainlen)))
+                        overallresult = 0;
+                    OSSL_HPKE_CTX_free(ctx);
+                    memset(clear, 0, clearlen);
+                    if (!TEST_ptr(rctx = OSSL_HPKE_CTX_new(hpke_mode,
+                                                           hpke_suite,
+                                                           testctx, NULL)))
+                        overallresult = 0;
+                    if (hpke_mode == OSSL_HPKE_MODE_PSK
+                        || hpke_mode == OSSL_HPKE_MODE_PSKAUTH) {
+                        if (!TEST_true(OSSL_HPKE_CTX_set1_psk(rctx, pskidp,
+                                                              pskp, psklen)))
+                            overallresult = 0;
+                    }
+                    if (hpke_mode == OSSL_HPKE_MODE_AUTH
+                        || hpke_mode == OSSL_HPKE_MODE_PSKAUTH) {
+                        /* check a borked p256 key */
+                        if (hpke_suite.kem_id == OSSL_HPKE_KEM_ID_P256) {
+                            /* set to fail decode of authpub this time */
+                            if (!TEST_false(OSSL_HPKE_CTX_set1_authpub(rctx,
+                                                                       authpub,
+                                                                       10
+                                                                       )))
+                                overallresult = 0;
+                        }
+                        if (!TEST_true(OSSL_HPKE_CTX_set1_authpub(rctx,
+                                                                  authpubp,
+                                                                  authpublen)))
+                            overallresult = 0;
+                    }
+                    if (startseq != 0) {
+                        if (!TEST_true(OSSL_HPKE_CTX_set_seq(rctx, startseq)))
+                            overallresult = 0;
+                    }
+                    if (!TEST_true(OSSL_HPKE_decap(rctx, senderpub,
+                                                   senderpublen, privp,
+                                                   infop, infolen)))
+                        overallresult = 0;
+                    /* throw in a call with a too-short clearlen */
+                    clearlen = 15;
+                    if (!TEST_false(OSSL_HPKE_open(rctx, clear, &clearlen,
+                                                   aadp, aadlen, cipher,
+                                                   cipherlen)))
+                        overallresult = 0;
+                    /* fix up real clearlen again */
+                    clearlen = OSSL_HPKE_TSTSIZE;
+                    if (!TEST_true(OSSL_HPKE_open(rctx, clear, &clearlen,
+                                                  aadp, aadlen, cipher,
+                                                  cipherlen)))
+                        overallresult = 0;
+                    OSSL_HPKE_CTX_free(rctx);
+                    EVP_PKEY_free(privp);
+                    privp = NULL;
+                    /* check output */
+                    if (!TEST_mem_eq(clear, clearlen, plain, plainlen)) {
+                        overallresult = 0;
+                    }
+                    if (verbose || overallresult != 1) {
+                        const char *res = NULL;
+
+                        res = (overallresult == 1 ? "worked" : "failed");
+                        TEST_note("HPKE %s for mode: %s/0x%02x, "\
+                                  "kem: %s/0x%02x, kdf: %s/0x%02x, "\
+                                  "aead: %s/0x%02x", res,
+                                  mode_str_list[mind], (int) mind,
+                                  kem_str_list[kemind], kem_id,
+                                  kdf_str_list[kdfind], kdf_id,
+                                  aead_str_list[aeadind], aead_id);
+                    }
+                }
+            }
+            EVP_PKEY_free(authpriv);
+        }
+    }
+    return overallresult;
+}
+
+/**
+ * @brief check roundtrip for export
+ * @return 1 for success, other otherwise
+ */
+static int test_hpke_export(void)
+{
+    int erv = 0;
+    EVP_PKEY *privp = NULL;
+    unsigned char pub[OSSL_HPKE_TSTSIZE];
+    size_t publen = sizeof(pub);
+    int hpke_mode = OSSL_HPKE_MODE_BASE;
+    OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
+    OSSL_HPKE_CTX *ctx = NULL;
+    OSSL_HPKE_CTX *rctx = NULL;
+    unsigned char exp[32];
+    unsigned char exp2[32];
+    unsigned char rexp[32];
+    unsigned char rexp2[32];
+    unsigned char plain[] = "quick brown fox";
+    size_t plainlen = sizeof(plain);
+    unsigned char enc[OSSL_HPKE_TSTSIZE];
+    size_t enclen = sizeof(enc);
+    unsigned char cipher[OSSL_HPKE_TSTSIZE];
+    size_t cipherlen = sizeof(cipher);
+    unsigned char clear[OSSL_HPKE_TSTSIZE];
+    size_t clearlen = sizeof(clear);
+    char *estr = "foo";
+
+    if (!TEST_true(OSSL_HPKE_keygen(hpke_suite, pub, &publen, &privp,
+                                    NULL, 0, testctx, NULL)))
+        goto end;
+    if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+                                          testctx, NULL)))
+        goto end;
+    /* a few error cases 1st */
+    if (!TEST_false(OSSL_HPKE_export(NULL, exp, sizeof(exp),
+                                     (unsigned char *)estr, strlen(estr))))
+        goto end;
+    /* ctx before encap should fail too */
+    if (!TEST_false(OSSL_HPKE_export(ctx, exp, sizeof(exp),
+                                     (unsigned char *)estr, strlen(estr))))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_encap(ctx, enc, &enclen, pub, publen, NULL, 0)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_seal(ctx, cipher, &cipherlen, NULL, 0,
+                                  plain, plainlen)))
+        goto end;
+    /* now for real */
+    if (!TEST_true(OSSL_HPKE_export(ctx, exp, sizeof(exp),
+                                    (unsigned char *)estr, strlen(estr))))
+        goto end;
+    /* check a 2nd call with same input gives same output */
+    if (!TEST_true(OSSL_HPKE_export(ctx, exp2, sizeof(exp2),
+                                    (unsigned char *)estr, strlen(estr))))
+        goto end;
+    if (!TEST_mem_eq(exp, sizeof(exp), exp2, sizeof(exp2)))
+        goto end;
+    if (!TEST_ptr(rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+                                           testctx, NULL)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_decap(rctx, enc, enclen, privp, NULL, 0)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_open(rctx, clear, &clearlen, NULL, 0,
+                                  cipher, cipherlen)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_export(rctx, rexp, sizeof(rexp),
+                                    (unsigned char *)estr, strlen(estr))))
+        goto end;
+    /* check a 2nd call with same input gives same output */
+    if (!TEST_true(OSSL_HPKE_export(rctx, rexp2, sizeof(rexp2),
+                                    (unsigned char *)estr, strlen(estr))))
+        goto end;
+    if (!TEST_mem_eq(rexp, sizeof(rexp), rexp2, sizeof(rexp2)))
+        goto end;
+    if (!TEST_mem_eq(exp, sizeof(exp), rexp, sizeof(rexp)))
+        goto end;
+    erv = 1;
+end:
+    OSSL_HPKE_CTX_free(ctx);
+    OSSL_HPKE_CTX_free(rctx);
+    EVP_PKEY_free(privp);
+    return erv;
+}
+
+/**
+ * @brief Check mapping from strings to HPKE suites
+ * @return 1 for success, other otherwise
+ */
+static int test_hpke_suite_strs(void)
+{
+    int overallresult = 1;
+    int kemind = 0;
+    int kdfind = 0;
+    int aeadind = 0;
+    int sind = 0;
+    char sstr[128];
+    OSSL_HPKE_SUITE stirred;
+    char giant[2048];
+    size_t suitesize;
+    size_t ptr_suitesize;
+
+    for (kemind = 0; kemind != OSSL_NELEM(kem_str_list); kemind++) {
+        for (kdfind = 0; kdfind != OSSL_NELEM(kdf_str_list); kdfind++) {
+            for (aeadind = 0; aeadind != OSSL_NELEM(aead_str_list); aeadind++) {
+                snprintf(sstr, 128, "%s,%s,%s", kem_str_list[kemind],
+                         kdf_str_list[kdfind], aead_str_list[aeadind]);
+                if (TEST_true(OSSL_HPKE_str2suite(sstr, &stirred)) != 1) {
+                    if (verbose)
+                        TEST_note("Unexpected str2suite fail for :%s",
+                                  bogus_suite_strs[sind]);
+                    overallresult = 0;
+                }
+            }
+        }
+    }
+    for (sind = 0; sind != OSSL_NELEM(bogus_suite_strs); sind++) {
+        if (TEST_false(OSSL_HPKE_str2suite(bogus_suite_strs[sind],
+                                           &stirred)) != 1) {
+            if (verbose)
+                TEST_note("OSSL_HPKE_str2suite didn't fail for bogus[%d]:%s",
+                          sind, bogus_suite_strs[sind]);
+            overallresult = 0;
+        }
+    }
+    /* check a few errors */
+    if (!TEST_false(OSSL_HPKE_str2suite("", &stirred)))
+        overallresult = 0;
+    if (!TEST_false(OSSL_HPKE_str2suite(NULL, &stirred)))
+        overallresult = 0;
+    if (!TEST_false(OSSL_HPKE_str2suite("", NULL)))
+        overallresult = 0;
+    memset(giant, 'A', sizeof(giant) - 1);
+    giant[sizeof(giant) - 1] = '\0';
+    if (!TEST_false(OSSL_HPKE_str2suite(giant, &stirred)))
+        overallresult = 0;
+
+    /* we'll check the size of a suite just to see what we get */
+    suitesize = sizeof(stirred);
+    ptr_suitesize = sizeof(&stirred);
+    if (verbose) {
+        TEST_note("Size of OSSL_HPKE_SUITE is %d, size of ptr is %d",
+                  (int) suitesize, (int) ptr_suitesize);
+    }
+
+    return overallresult;
+}
+
+/**
+ * @brief try the various GREASEy APIs
+ * @return 1 for success, other otherwise
+ */
+static int test_hpke_grease(void)
+{
+    int overallresult = 1;
+    OSSL_HPKE_SUITE g_suite;
+    unsigned char g_pub[OSSL_HPKE_TSTSIZE];
+    size_t g_pub_len = OSSL_HPKE_TSTSIZE;
+    unsigned char g_cipher[OSSL_HPKE_TSTSIZE];
+    size_t g_cipher_len = 266;
+    size_t clearlen = 128;
+    size_t expanded = 0;
+    size_t enclen = 0;
+    size_t ikmelen = 0;
+
+    memset(&g_suite, 0, sizeof(OSSL_HPKE_SUITE));
+    /* GREASEing */
+    /* check too short for public value */
+    g_pub_len = 10;
+    if (TEST_false(OSSL_HPKE_get_grease_value(testctx, NULL, NULL, &g_suite,
+                                              g_pub, &g_pub_len,
+                                              g_cipher, g_cipher_len)) != 1) {
+        overallresult = 0;
+    }
+    /* reset to work */
+    g_pub_len = OSSL_HPKE_TSTSIZE;
+    if (TEST_true(OSSL_HPKE_get_grease_value(testctx, NULL, NULL, &g_suite,
+                                             g_pub, &g_pub_len,
+                                             g_cipher, g_cipher_len)) != 1) {
+        overallresult = 0;
+    }
+    /* expansion */
+    expanded = OSSL_HPKE_get_ciphertext_size(g_suite, clearlen);
+    if (!TEST_size_t_gt(expanded, clearlen)) {
+        overallresult = 0;
+    }
+    enclen = OSSL_HPKE_get_public_encap_size(g_suite);
+    if (!TEST_size_t_ne(enclen, 0))
+        overallresult = 0;
+    /* not really GREASE but we'll check ikmelen thing */
+    ikmelen = OSSL_HPKE_get_recommended_ikmelen(g_suite);
+    if (!TEST_size_t_ne(ikmelen, 0))
+        overallresult = 0;
+
+    return overallresult;
+}
+
+/*
+ * Make a set of calls with odd parameters
+ */
+static int test_hpke_oddcalls(void)
+{
+    int erv = 0;
+    EVP_PKEY *privp = NULL;
+    unsigned char pub[OSSL_HPKE_TSTSIZE];
+    size_t publen = sizeof(pub);
+    int hpke_mode = OSSL_HPKE_MODE_BASE;
+    int bad_mode = 0xbad;
+    OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
+    OSSL_HPKE_SUITE bad_suite = { 0xbad, 0xbad, 0xbad };
+    OSSL_HPKE_CTX *ctx = NULL;
+    OSSL_HPKE_CTX *rctx = NULL;
+    unsigned char plain[] = "quick brown fox";
+    size_t plainlen = sizeof(plain);
+    unsigned char enc[OSSL_HPKE_TSTSIZE];
+    size_t enclen = sizeof(enc);
+    unsigned char cipher[OSSL_HPKE_TSTSIZE];
+    size_t cipherlen = sizeof(cipher);
+    unsigned char clear[OSSL_HPKE_TSTSIZE];
+    size_t clearlen = sizeof(clear);
+    unsigned char fake_ikm[OSSL_HPKE_TSTSIZE];
+    char *badpropq = "yeah, this won't work";
+    uint64_t lseq = 0;
+    char giant_pskid[OSSL_HPKE_MAX_PARMLEN + 10];
+    unsigned char info[OSSL_HPKE_TSTSIZE];
+
+    /* many of the calls below are designed to get better test coverage */
+
+    /* NULL ctx calls */
+    OSSL_HPKE_CTX_free(NULL);
+    if (!TEST_false(OSSL_HPKE_CTX_set_seq(NULL, 1)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_CTX_get_seq(NULL, &lseq)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_CTX_set1_authpub(NULL, pub, publen)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_CTX_set1_authpriv(NULL, privp)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_CTX_set1_ikme(NULL, NULL, 0)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_CTX_set1_psk(NULL, NULL, NULL, 0)))
+        goto end;
+
+    /* make/break ctx */
+    if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+                                          testctx, "foo")))
+        goto end;
+    OSSL_HPKE_CTX_free(ctx);
+    ctx = NULL;
+
+    /* bad suite calls */
+    hpke_suite.aead_id = 0xbad;
+    if (!TEST_false(OSSL_HPKE_suite_check(hpke_suite)))
+        goto end;
+    hpke_suite.aead_id = OSSL_HPKE_AEAD_ID_AES_GCM_128;
+    if (!TEST_false(OSSL_HPKE_suite_check(bad_suite)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_get_recommended_ikmelen(bad_suite)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_get_public_encap_size(bad_suite)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_get_ciphertext_size(bad_suite, 0)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_keygen(bad_suite, pub, &publen, &privp,
+                                     NULL, 0, testctx, badpropq)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_keygen(bad_suite, pub, &publen, &privp,
+                                     NULL, 0, testctx, NULL)))
+        goto end;
+
+    /* dodgy keygen calls */
+    /* no pub */
+    if (!TEST_false(OSSL_HPKE_keygen(hpke_suite, NULL, &publen, &privp,
+                                     NULL, 0, testctx, NULL)))
+        goto end;
+    /* ikmlen but NULL ikm */
+    if (!TEST_false(OSSL_HPKE_keygen(hpke_suite, pub, &publen, &privp,
+                                     NULL, 80, testctx, NULL)))
+        goto end;
+    /* zero ikmlen but ikm */
+    if (!TEST_false(OSSL_HPKE_keygen(hpke_suite, pub, &publen, &privp,
+                                     fake_ikm, 0, testctx, NULL)))
+        goto end;
+    /* GIANT ikmlen */
+    if (!TEST_false(OSSL_HPKE_keygen(hpke_suite, pub, &publen, &privp,
+                                     fake_ikm, -1, testctx, NULL)))
+        goto end;
+    /* short publen */
+    publen = 10;
+    if (!TEST_false(OSSL_HPKE_keygen(hpke_suite, pub, &publen, &privp,
+                                     NULL, 0, testctx, NULL)))
+        goto end;
+    publen = sizeof(pub);
+
+    /* encap/decap with NULLs */
+    if (!TEST_false(OSSL_HPKE_encap(NULL, NULL, NULL, NULL, 0, NULL, 0)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_decap(NULL, NULL, 0, NULL, NULL, 0)))
+        goto end;
+
+    /*
+     * run through a sender/recipient set of calls but with
+     * failing calls interspersed whenever possible
+     */
+    /* good keygen */
+    if (!TEST_true(OSSL_HPKE_keygen(hpke_suite, pub, &publen, &privp,
+                                    NULL, 0, testctx, NULL)))
+        goto end;
+
+    /* a psk context with no psk => encap fail */
+    if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_PSK, hpke_suite,
+                                          testctx, NULL)))
+        goto end;
+    /* set bad length psk */
+    if (!TEST_false(OSSL_HPKE_CTX_set1_psk(ctx, "foo",
+                                           (unsigned char *)"bar", -1)))
+        goto end;
+    /* set bad length pskid */
+    memset(giant_pskid, 'A', sizeof(giant_pskid) - 1);
+    giant_pskid[sizeof(giant_pskid) - 1] = '\0';
+    if (!TEST_false(OSSL_HPKE_CTX_set1_psk(ctx, giant_pskid,
+                                           (unsigned char *)"bar", 3)))
+        goto end;
+    /* still no psk really set so encap fails */
+    if (!TEST_false(OSSL_HPKE_encap(ctx, enc, &enclen, pub, publen, NULL, 0)))
+        goto end;
+    OSSL_HPKE_CTX_free(ctx);
+
+    /* bad suite */
+    if (!TEST_ptr_null(ctx = OSSL_HPKE_CTX_new(hpke_mode, bad_suite,
+                                               testctx, NULL)))
+        goto end;
+    /* bad mode */
+    if (!TEST_ptr_null(ctx = OSSL_HPKE_CTX_new(bad_mode, hpke_suite,
+                                               testctx, NULL)))
+        goto end;
+    /* make good ctx */
+    if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+                                          testctx, NULL)))
+        goto end;
+    /* too long ikm */
+    if (!TEST_false(OSSL_HPKE_CTX_set1_ikme(ctx, fake_ikm, -1)))
+        goto end;
+    /* zero length ikm */
+    if (!TEST_false(OSSL_HPKE_CTX_set1_ikme(ctx, fake_ikm, 0)))
+        goto end;
+    /* NULL authpub */
+    if (!TEST_false(OSSL_HPKE_CTX_set1_authpub(ctx, NULL, 0)))
+        goto end;
+    /* NULL auth priv */
+    if (!TEST_false(OSSL_HPKE_CTX_set1_authpriv(ctx, NULL)))
+        goto end;
+    /* priv good, but mode is bad */
+    if (!TEST_false(OSSL_HPKE_CTX_set1_authpriv(ctx, privp)))
+        goto end;
+    /* bad mode for psk */
+    if (!TEST_false(OSSL_HPKE_CTX_set1_psk(ctx, "foo",
+                                           (unsigned char *)"bar", 3)))
+        goto end;
+    /* seal before encap */
+    if (!TEST_false(OSSL_HPKE_seal(ctx, cipher, &cipherlen, NULL, 0,
+                                   plain, plainlen)))
+        goto end;
+    /* encap with dodgy public */
+    if (!TEST_false(OSSL_HPKE_encap(ctx, enc, &enclen, pub, 1, NULL, 0)))
+        goto end;
+    /* encap with too big info */
+    if (!TEST_false(OSSL_HPKE_encap(ctx, enc, &enclen, pub, 1, info, -1)))
+        goto end;
+    /* good encap */
+    if (!TEST_true(OSSL_HPKE_encap(ctx, enc, &enclen, pub, publen, NULL, 0)))
+        goto end;
+    /* second encap fail */
+    if (!TEST_false(OSSL_HPKE_encap(ctx, enc, &enclen, pub, publen, NULL, 0)))
+        goto end;
+    plainlen = 0;
+    /* should fail for no plaintext */
+    if (!TEST_false(OSSL_HPKE_seal(ctx, cipher, &cipherlen, NULL, 0,
+                                   plain, plainlen)))
+        goto end;
+    /* the sequence ought not have been incremented, so good to start over */
+    plainlen = sizeof(plain);
+    /* seq wrap around test */
+    if (!TEST_true(OSSL_HPKE_CTX_set_seq(ctx, -1)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_seal(ctx, cipher, &cipherlen, NULL, 0,
+                                   plain, plainlen)))
+        goto end;
+    /* reset seq */
+    if (!TEST_true(OSSL_HPKE_CTX_set_seq(ctx, 0)))
+        goto end;
+    /* working seal */
+    if (!TEST_true(OSSL_HPKE_seal(ctx, cipher, &cipherlen, NULL, 0,
+                                  plain, plainlen)))
+        goto end;
+
+    /* receiver side */
+    /* decap fail with psk mode but no psk set */
+    if (!TEST_ptr(rctx = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_PSK, hpke_suite,
+                                           testctx, NULL)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_decap(rctx, enc, enclen, privp, NULL, 0)))
+        goto end;
+    /* done with PSK mode */
+    OSSL_HPKE_CTX_free(rctx);
+
+    /* back good calls for base mode  */
+    if (!TEST_ptr(rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+                                           testctx, NULL)))
+        goto end;
+    /* open before decap */
+    if (!TEST_false(OSSL_HPKE_open(rctx, clear, &clearlen, NULL, 0,
+                                   cipher, cipherlen)))
+        goto end;
+    /* decap with info too long */
+    if (!TEST_false(OSSL_HPKE_decap(rctx, enc, enclen, privp, info, -1)))
+        goto end;
+    /* good decap */
+    if (!TEST_true(OSSL_HPKE_decap(rctx, enc, enclen, privp, NULL, 0)))
+        goto end;
+    /* second decap fail */
+    if (!TEST_false(OSSL_HPKE_decap(rctx, enc, enclen, privp, NULL, 0)))
+        goto end;
+    /* no space for recovered clear */
+    clearlen = 0;
+    if (!TEST_false(OSSL_HPKE_open(rctx, clear, &clearlen, NULL, 0,
+                                   cipher, cipherlen)))
+        goto end;
+    clearlen = OSSL_HPKE_TSTSIZE;
+    /* seq wrap around test */
+    if (!TEST_true(OSSL_HPKE_CTX_set_seq(rctx, -1)))
+        goto end;
+    if (!TEST_false(OSSL_HPKE_open(rctx, clear, &clearlen, NULL, 0,
+                                   cipher, cipherlen)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_CTX_set_seq(rctx, 0)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_open(rctx, clear, &clearlen, NULL, 0,
+                                  cipher, cipherlen)))
+        goto end;
+    if (!TEST_mem_eq(plain, plainlen, clear, clearlen))
+        goto end;
+    erv = 1;
+end:
+    OSSL_HPKE_CTX_free(ctx);
+    OSSL_HPKE_CTX_free(rctx);
+    EVP_PKEY_free(privp);
+    return erv;
+}
+
+/* from RFC 9180 Appendix A.1.1 */
+static const unsigned char ikm25519[] = {
+    0x72, 0x68, 0x60, 0x0d, 0x40, 0x3f, 0xce, 0x43,
+    0x15, 0x61, 0xae, 0xf5, 0x83, 0xee, 0x16, 0x13,
+    0x52, 0x7c, 0xff, 0x65, 0x5c, 0x13, 0x43, 0xf2,
+    0x98, 0x12, 0xe6, 0x67, 0x06, 0xdf, 0x32, 0x34
+};
+static const unsigned char pub25519[] = {
+    0x37, 0xfd, 0xa3, 0x56, 0x7b, 0xdb, 0xd6, 0x28,
+    0xe8, 0x86, 0x68, 0xc3, 0xc8, 0xd7, 0xe9, 0x7d,
+    0x1d, 0x12, 0x53, 0xb6, 0xd4, 0xea, 0x6d, 0x44,
+    0xc1, 0x50, 0xf7, 0x41, 0xf1, 0xbf, 0x44, 0x31
+};
+
+/* from RFC9180 Appendix A.3.1 */
+static const unsigned char ikmp256[] = {
+    0x42, 0x70, 0xe5, 0x4f, 0xfd, 0x08, 0xd7, 0x9d,
+    0x59, 0x28, 0x02, 0x0a, 0xf4, 0x68, 0x6d, 0x8f,
+    0x6b, 0x7d, 0x35, 0xdb, 0xe4, 0x70, 0x26, 0x5f,
+    0x1f, 0x5a, 0xa2, 0x28, 0x16, 0xce, 0x86, 0x0e
+};
+static const unsigned char pubp256[] = {
+    0x04, 0xa9, 0x27, 0x19, 0xc6, 0x19, 0x5d, 0x50,
+    0x85, 0x10, 0x4f, 0x46, 0x9a, 0x8b, 0x98, 0x14,
+    0xd5, 0x83, 0x8f, 0xf7, 0x2b, 0x60, 0x50, 0x1e,
+    0x2c, 0x44, 0x66, 0xe5, 0xe6, 0x7b, 0x32, 0x5a,
+    0xc9, 0x85, 0x36, 0xd7, 0xb6, 0x1a, 0x1a, 0xf4,
+    0xb7, 0x8e, 0x5b, 0x7f, 0x95, 0x1c, 0x09, 0x00,
+    0xbe, 0x86, 0x3c, 0x40, 0x3c, 0xe6, 0x5c, 0x9b,
+    0xfc, 0xb9, 0x38, 0x26, 0x57, 0x22, 0x2d, 0x18,
+    0xc4
+};
+
+/*
+ * A test vector that exercises the counter iteration
+ * for p256. This was contributed by Ilari L. on the
+ * CFRG list, see the mail archive:
+ * https://mailarchive.ietf.org/arch/msg/cfrg/4zwl_y5YN6OU9oeWZOMHNOlOa2w/
+ */
+static const unsigned char ikmiter[] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x03, 0x01, 0x38, 0xb5, 0xec
+};
+static const unsigned char pubiter[] = {
+    0x04, 0x7d, 0x0c, 0x87, 0xff, 0xd5, 0xd1, 0x45,
+    0x54, 0xa7, 0x51, 0xdf, 0xa3, 0x99, 0x26, 0xa9,
+    0xe3, 0x0e, 0x7c, 0x3c, 0x65, 0x62, 0x4f, 0x4b,
+    0x5f, 0xb3, 0xad, 0x7a, 0xa4, 0xda, 0xc2, 0x4a,
+    0xd8, 0xf5, 0xbe, 0xd0, 0xe8, 0x6e, 0xb8, 0x84,
+    0x1c, 0xe4, 0x89, 0x2e, 0x0f, 0xc3, 0x87, 0xbb,
+    0xdb, 0xfe, 0x16, 0x0d, 0x58, 0x9c, 0x89, 0x2d,
+    0xd4, 0xb1, 0x46, 0x4a, 0xc3, 0x51, 0xc5, 0x6f,
+    0xb6
+};
+
+/* from RFC9180 Appendix A.6.1 */
+static const unsigned char ikmp521[] = {
+    0x7f, 0x06, 0xab, 0x82, 0x15, 0x10, 0x5f, 0xc4,
+    0x6a, 0xce, 0xeb, 0x2e, 0x3d, 0xc5, 0x02, 0x8b,
+    0x44, 0x36, 0x4f, 0x96, 0x04, 0x26, 0xeb, 0x0d,
+    0x8e, 0x40, 0x26, 0xc2, 0xf8, 0xb5, 0xd7, 0xe7,
+    0xa9, 0x86, 0x68, 0x8f, 0x15, 0x91, 0xab, 0xf5,
+    0xab, 0x75, 0x3c, 0x35, 0x7a, 0x5d, 0x6f, 0x04,
+    0x40, 0x41, 0x4b, 0x4e, 0xd4, 0xed, 0xe7, 0x13,
+    0x17, 0x77, 0x2a, 0xc9, 0x8d, 0x92, 0x39, 0xf7,
+    0x09, 0x04
+};
+static const unsigned char pubp521[] = {
+    0x04, 0x01, 0x38, 0xb3, 0x85, 0xca, 0x16, 0xbb,
+    0x0d, 0x5f, 0xa0, 0xc0, 0x66, 0x5f, 0xbb, 0xd7,
+    0xe6, 0x9e, 0x3e, 0xe2, 0x9f, 0x63, 0x99, 0x1d,
+    0x3e, 0x9b, 0x5f, 0xa7, 0x40, 0xaa, 0xb8, 0x90,
+    0x0a, 0xae, 0xed, 0x46, 0xed, 0x73, 0xa4, 0x90,
+    0x55, 0x75, 0x84, 0x25, 0xa0, 0xce, 0x36, 0x50,
+    0x7c, 0x54, 0xb2, 0x9c, 0xc5, 0xb8, 0x5a, 0x5c,
+    0xee, 0x6b, 0xae, 0x0c, 0xf1, 0xc2, 0x1f, 0x27,
+    0x31, 0xec, 0xe2, 0x01, 0x3d, 0xc3, 0xfb, 0x7c,
+    0x8d, 0x21, 0x65, 0x4b, 0xb1, 0x61, 0xb4, 0x63,
+    0x96, 0x2c, 0xa1, 0x9e, 0x8c, 0x65, 0x4f, 0xf2,
+    0x4c, 0x94, 0xdd, 0x28, 0x98, 0xde, 0x12, 0x05,
+    0x1f, 0x1e, 0xd0, 0x69, 0x22, 0x37, 0xfb, 0x02,
+    0xb2, 0xf8, 0xd1, 0xdc, 0x1c, 0x73, 0xe9, 0xb3,
+    0x66, 0xb5, 0x29, 0xeb, 0x43, 0x6e, 0x98, 0xa9,
+    0x96, 0xee, 0x52, 0x2a, 0xef, 0x86, 0x3d, 0xd5,
+    0x73, 0x9d, 0x2f, 0x29, 0xb0
+};
+
+static int test_hpke_random_suites(void)
+{
+    OSSL_HPKE_SUITE def_suite = OSSL_HPKE_SUITE_DEFAULT;
+    OSSL_HPKE_SUITE suite = OSSL_HPKE_SUITE_DEFAULT;
+    OSSL_HPKE_SUITE suite2 = { 0xff01, 0xff02, 0xff03 };
+    unsigned char enc[200];
+    size_t enclen = sizeof(enc);
+    unsigned char ct[500];
+    size_t ctlen = sizeof(ct);
+
+    /* test with NULL/0 inputs */
+    if (!TEST_false(OSSL_HPKE_get_grease_value(testctx, NULL, NULL, NULL,
+                                               NULL, NULL, NULL, 0)))
+        return 0;
+    enclen = 10;
+    if (!TEST_false(OSSL_HPKE_get_grease_value(testctx, NULL, &def_suite,
+                                               &suite2, enc, &enclen,
+                                               ct, ctlen)))
+        return 0;
+
+    enclen = sizeof(enc); /* reset, 'cause get_grease() will have set */
+    /* test with a should-be-good suite */
+    if (!TEST_true(OSSL_HPKE_get_grease_value(testctx, NULL, &def_suite,
+                                              &suite2, enc, &enclen,
+                                              ct, ctlen)))
+        return 0;
+    /* no suggested suite */
+    enclen = sizeof(enc); /* reset, 'cause get_grease() will have set */
+    if (!TEST_true(OSSL_HPKE_get_grease_value(testctx, NULL, NULL, &suite2,
+                                              enc, &enclen, ct, ctlen)))
+        return 0;
+    /* suggested suite with P-521, just to be sure we hit long values */
+    enclen = sizeof(enc); /* reset, 'cause get_grease() will have set */
+    suite.kem_id = OSSL_HPKE_KEM_ID_P521;
+    if (!TEST_true(OSSL_HPKE_get_grease_value(testctx, NULL, &suite, &suite2,
+                                              enc, &enclen, ct, ctlen)))
+        return 0;
+    enclen = sizeof(enc);
+    ctlen = 2; /* too-short cttext (can't fit an aead tag) */
+    if (!TEST_false(OSSL_HPKE_get_grease_value(testctx, NULL, NULL, &suite2,
+                                               enc, &enclen, ct, ctlen)))
+        return 0;
+
+    ctlen = sizeof(ct);
+    enclen = sizeof(enc);
+
+    suite.kem_id = OSSL_HPKE_KEM_ID_X25519; /* back to default */
+    suite.aead_id = 0x1234; /* bad aead */
+    if (!TEST_false(OSSL_HPKE_get_grease_value(testctx, NULL, &suite, &suite2,
+                                               enc, &enclen, ct, ctlen)))
+        return 0;
+    enclen = sizeof(enc);
+    suite.aead_id = def_suite.aead_id; /* good aead */
+    suite.kdf_id = 0x3451; /* bad kdf */
+    if (!TEST_false(OSSL_HPKE_get_grease_value(testctx, NULL, &suite, &suite2,
+                                               enc, &enclen, ct, ctlen)))
+        return 0;
+    enclen = sizeof(enc);
+    suite.kdf_id = def_suite.kdf_id; /* good kdf */
+    suite.kem_id = 0x4517; /* bad kem */
+    if (!TEST_false(OSSL_HPKE_get_grease_value(testctx, NULL, &suite, &suite2,
+                                               enc, &enclen, ct, ctlen)))
+        return 0;
+    return 1;
+}
+
+/*
+ * @brief generate a key pair from initial key material (ikm) and check public
+ * @param kem_id the KEM to use (RFC9180 code point)
+ * @ikm is the initial key material buffer
+ * @ikmlen is the length of ikm
+ * @pub is the public key buffer
+ * @publen is the length of the public key
+ * @return 1 for good, other otherwise
+ *
+ * This calls OSSL_HPKE_keygen specifying only the IKM, then
+ * compares the key pair values with the already-known values
+ * that were input.
+ */
+static int test_hpke_one_ikm_gen(uint16_t kem_id,
+                                 const unsigned char *ikm, size_t ikmlen,
+                                 const unsigned char *pub, size_t publen)
+{
+    OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
+    unsigned char lpub[OSSL_HPKE_TSTSIZE];
+    size_t lpublen = OSSL_HPKE_TSTSIZE;
+    EVP_PKEY *sk = NULL;
+
+    hpke_suite.kem_id = kem_id;
+    if (!TEST_true(OSSL_HPKE_keygen(hpke_suite, lpub, &lpublen, &sk,
+                                    ikm, ikmlen, testctx, NULL)))
+        return 0;
+    if (!TEST_ptr(sk))
+        return 0;
+    EVP_PKEY_free(sk);
+    if (!TEST_mem_eq(pub, publen, lpub, lpublen))
+        return 0;
+    return 1;
+}
+
+/*
+ * @brief test some uses of IKM produce the expected public keys
+ */
+static int test_hpke_ikms(void)
+{
+    int res = 1;
+
+    res = test_hpke_one_ikm_gen(OSSL_HPKE_KEM_ID_X25519,
+                                ikm25519, sizeof(ikm25519),
+                                pub25519, sizeof(pub25519));
+    if (res != 1)
+        return res;
+
+    res = test_hpke_one_ikm_gen(OSSL_HPKE_KEM_ID_P521,
+                                ikmp521, sizeof(ikmp521),
+                                pubp521, sizeof(pubp521));
+    if (res != 1)
+        return res;
+
+    res = test_hpke_one_ikm_gen(OSSL_HPKE_KEM_ID_P256,
+                                ikmp256, sizeof(ikmp256),
+                                pubp256, sizeof(pubp256));
+    if (res != 1)
+        return res;
+
+    res = test_hpke_one_ikm_gen(OSSL_HPKE_KEM_ID_P256,
+                                ikmiter, sizeof(ikmiter),
+                                pubiter, sizeof(pubiter));
+    if (res != 1)
+        return res;
+
+    return res;
+}
+
+/*
+ * Test that use of a compressed format auth public key works
+ * We'll do a typical round-trip for auth mode but provide the
+ * auth public key in compressed form. That should work.
+ */
+static int test_hpke_compressed(void)
+{
+    int erv = 0;
+    EVP_PKEY *privp = NULL;
+    unsigned char pub[OSSL_HPKE_TSTSIZE];
+    size_t publen = sizeof(pub);
+    EVP_PKEY *authpriv = NULL;
+    unsigned char authpub[OSSL_HPKE_TSTSIZE];
+    size_t authpublen = sizeof(authpub);
+    int hpke_mode = OSSL_HPKE_MODE_AUTH;
+    OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
+    OSSL_HPKE_CTX *ctx = NULL;
+    OSSL_HPKE_CTX *rctx = NULL;
+    unsigned char plain[] = "quick brown fox";
+    size_t plainlen = sizeof(plain);
+    unsigned char enc[OSSL_HPKE_TSTSIZE];
+    size_t enclen = sizeof(enc);
+    unsigned char cipher[OSSL_HPKE_TSTSIZE];
+    size_t cipherlen = sizeof(cipher);
+    unsigned char clear[OSSL_HPKE_TSTSIZE];
+    size_t clearlen = sizeof(clear);
+
+    hpke_suite.kem_id = OSSL_HPKE_KEM_ID_P256;
+
+    /* generate auth key pair */
+    if (!TEST_true(OSSL_HPKE_keygen(hpke_suite, authpub, &authpublen, &authpriv,
+                                    NULL, 0, testctx, NULL)))
+        goto end;
+    /* now get the compressed form public key */
+    if (!TEST_true(EVP_PKEY_set_utf8_string_param(authpriv,
+                      OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT,
+                      OSSL_PKEY_EC_POINT_CONVERSION_FORMAT_COMPRESSED)))
+        goto end;
+    if (!TEST_true(EVP_PKEY_get_octet_string_param(authpriv,
+                                                   OSSL_PKEY_PARAM_PUB_KEY,
+                                                   authpub,
+                                                   sizeof(authpub),
+                                                   &authpublen)))
+        goto end;
+
+    /* sender side as usual */
+    if (!TEST_true(OSSL_HPKE_keygen(hpke_suite, pub, &publen, &privp,
+                                    NULL, 0, testctx, NULL)))
+        goto end;
+    if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+                                          testctx, NULL)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_CTX_set1_authpriv(ctx, authpriv)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_encap(ctx, enc, &enclen, pub, publen, NULL, 0)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_seal(ctx, cipher, &cipherlen, NULL, 0,
+                                  plain, plainlen)))
+        goto end;
+
+    /* receiver side providing compressed form of auth public */
+    if (!TEST_ptr(rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+                                           testctx, NULL)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_CTX_set1_authpub(rctx, authpub, authpublen)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_decap(rctx, enc, enclen, privp, NULL, 0)))
+        goto end;
+    if (!TEST_true(OSSL_HPKE_open(rctx, clear, &clearlen, NULL, 0,
+                                  cipher, cipherlen)))
+        goto end;
+    erv = 1;
+
+end:
+    EVP_PKEY_free(privp);
+    EVP_PKEY_free(authpriv);
+    OSSL_HPKE_CTX_free(ctx);
+    OSSL_HPKE_CTX_free(rctx);
+    return erv;
+}
+
+typedef enum OPTION_choice {
+    OPT_ERR = -1,
+    OPT_EOF = 0,
+    OPT_VERBOSE,
+    OPT_TEST_ENUM
+} OPTION_CHOICE;
+
+const OPTIONS *test_get_options(void)
+{
+    static const OPTIONS test_options[] = {
+        OPT_TEST_OPTIONS_DEFAULT_USAGE,
+        { "v", OPT_VERBOSE, '-', "Enable verbose mode" },
+        { OPT_HELP_STR, 1, '-', "Run HPKE tests\n" },
+        { NULL }
+    };
+    return test_options;
+}
+
+int setup_tests(void)
+{
+    OPTION_CHOICE o;
+
+    while ((o = opt_next()) != OPT_EOF) {
+        switch (o) {
+        case OPT_VERBOSE:
+            verbose = 1; /* Print progress dots */
+            break;
+        case OPT_TEST_CASES:
+            break;
+        default:
+            return 0;
+        }
+    }
+
+    if (!test_get_libctx(&testctx, &nullprov, NULL, &deflprov, "default"))
+        return 0;
+    ADD_TEST(x25519kdfsha256_hkdfsha256_aes128gcm_base_test);
+    ADD_TEST(x25519kdfsha256_hkdfsha256_aes128gcm_psk_test);
+    ADD_TEST(P256kdfsha256_hkdfsha256_aes128gcm_base_test);
+    ADD_TEST(export_only_test);
+    ADD_TEST(test_hpke_export);
+    ADD_TEST(test_hpke_modes_suites);
+    ADD_TEST(test_hpke_suite_strs);
+    ADD_TEST(test_hpke_grease);
+    ADD_TEST(test_hpke_ikms);
+    ADD_TEST(test_hpke_random_suites);
+    ADD_TEST(test_hpke_oddcalls);
+    ADD_TEST(test_hpke_compressed);
+    return 1;
+}
+
+void cleanup_tests(void)
+{
+    OSSL_PROVIDER_unload(deflprov);
+    OSSL_PROVIDER_unload(nullprov);
+    OSSL_LIB_CTX_free(testctx);
+}

+ 20 - 0
test/recipes/30-test_hpke.t

@@ -0,0 +1,20 @@
+#! /usr/bin/env perl
+# Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+# Copyright (c) 2022, Oracle and/or its affiliates.  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
+
+use strict;
+use OpenSSL::Test;              # get 'plan'
+use OpenSSL::Test::Simple;
+use OpenSSL::Test::Utils;
+
+setup("test_hpke");
+
+plan skip_all => "This test is unsupported in a no-ec build"
+    if disabled("ec");
+
+simple_test("test_hpke", "hpke_test");

+ 20 - 0
util/libcrypto.num

@@ -5485,3 +5485,23 @@ BIO_f_zstd                              ?	3_2_0	EXIST::FUNCTION:COMP
 d2i_PUBKEY_ex_fp                        ?	3_2_0	EXIST::FUNCTION:STDIO
 d2i_PUBKEY_ex_bio                       ?	3_2_0	EXIST::FUNCTION:
 COMP_zlib_oneshot                       ?	3_2_0	EXIST::FUNCTION:COMP
+OSSL_HPKE_keygen                        ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_suite_check                   ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_get_grease_value              ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_str2suite                     ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_CTX_free                      ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_CTX_new                       ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_CTX_set1_authpriv             ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_CTX_set1_authpub              ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_CTX_set1_psk                  ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_CTX_set1_ikme                 ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_get_ciphertext_size           ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_get_public_encap_size         ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_export                        ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_encap                         ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_decap                         ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_seal                          ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_open                          ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_CTX_get_seq                   ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_CTX_set_seq                   ?	3_2_0	EXIST::FUNCTION:
+OSSL_HPKE_get_recommended_ikmelen       ?	3_2_0	EXIST::FUNCTION: