### 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.
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
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
LIBS=../../libcrypto
-$COMMON=hpke_util.c
+$COMMON=hpke_util.c hpke.c
SOURCE[../../libcrypto]=$COMMON
--- /dev/null
+/*
+ * 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;
+}
* 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,
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)) {
end:
WPACKET_cleanup(&pkt);
OPENSSL_cleanse(labeled_ikm, labeled_ikmlen);
+ OPENSSL_free(labeled_ikm);
return ret;
}
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)) {
prk, prklen, labeled_info, labeled_infolen);
end:
WPACKET_cleanup(&pkt);
+ OPENSSL_free(labeled_info);
return ret;
}
}
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;
+}
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
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 \
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 \
--- /dev/null
+=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
+++ /dev/null
-/*
- * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
/*
* 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
# 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
# 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
/*
* 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
/*
* 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
{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),
{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"},
#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;
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;
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)
{
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;
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;
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);
{
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);
/* 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)
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))
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;
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;
}
&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;
/* 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);
{
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;
}
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);
#define KEM_MODE_DHKEM 1
int ossl_eckem_modename2id(const char *name);
-void ossl_dhkem_getsuiteid(unsigned char suiteid[5], uint16_t kemid);
#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
#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;
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;
* 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);
}
/*
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;
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;
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);
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:
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)
/* 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;
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;
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;
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;
}
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);
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;
}
enc, recipient_pub))
goto err;
- *secretlen = ctx->sharedsecretlen;
+ *secretlen = info->Nsecret;
ret = 1;
err:
ossl_ecx_key_free(sender_ephempubkey);
}
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;
-}
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
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
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+#! /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");
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: