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