From ad062480f7490197b174edad8625ce40d74f6e68 Mon Sep 17 00:00:00 2001 From: Stephen Farrell Date: Tue, 22 Nov 2022 02:42:04 +0000 Subject: [PATCH] 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 Reviewed-by: Shane Lontis Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/17172) --- CHANGES.md | 9 + crypto/err/openssl.txt | 2 + crypto/hpke/build.info | 2 +- crypto/hpke/hpke.c | 1439 ++++++++++++++++ crypto/hpke/hpke_util.c | 377 ++++- doc/build.info | 6 + doc/man3/OSSL_HPKE_CTX_new.pod | 538 ++++++ include/crypto/hpke.h | 47 - include/internal/hpke_util.h | 100 ++ include/openssl/hpke.h | 144 ++ include/openssl/proverr.h | 4 +- providers/common/include/prov/proverr.h | 2 +- providers/common/provider_err.c | 4 +- providers/implementations/kem/ec_kem.c | 125 +- providers/implementations/kem/eckem.h | 1 - providers/implementations/kem/ecx_kem.c | 114 +- providers/implementations/kem/kem_util.c | 8 - test/build.info | 6 +- test/hpke_test.c | 1921 ++++++++++++++++++++++ test/recipes/30-test_hpke.t | 20 + util/libcrypto.num | 20 + 21 files changed, 4678 insertions(+), 211 deletions(-) create mode 100644 crypto/hpke/hpke.c create mode 100644 doc/man3/OSSL_HPKE_CTX_new.pod delete mode 100644 include/crypto/hpke.h create mode 100644 include/internal/hpke_util.h create mode 100644 include/openssl/hpke.h create mode 100644 test/hpke_test.c create mode 100644 test/recipes/30-test_hpke.t 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 +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/crypto/hpke/hpke_util.c b/crypto/hpke/hpke_util.c index 92f9892a41..e2d28bbb58 100644 --- a/crypto/hpke/hpke_util.c +++ b/crypto/hpke/hpke_util.c @@ -7,30 +7,239 @@ * https://www.openssl.org/source/license.html */ +#include #include #include #include #include #include -#include "crypto/hpke.h" +#include +#include +#include +#include "crypto/ecx.h" +#include "internal/hpke_util.h" #include "internal/packet.h" +#include "internal/nelem.h" /* - * The largest value happens inside dhkem_extract_and_expand - * Which consists of a max dkmlen of 2*privkeylen + suiteid + small label + * Delimiter used in OSSL_HPKE_str2suite */ -#define LABELED_EXTRACT_SIZE (10 + 12 + 2 * OSSL_HPKE_MAX_PRIVATE) +#define OSSL_HPKE_STR_DELIMCHAR ',' /* - * The largest value happens inside dhkem_extract_and_expand - * Which consists of a prklen of secretlen + contextlen of 3 encoded public keys - * + suiteid + small label + * table with identifier and synonym strings + * right now, there are 4 synonyms for each - a name, a hex string + * a hex string with a leading zero and a decimal string - more + * could be added but that seems like enough */ -#define LABELED_EXPAND_SIZE (LABELED_EXTRACT_SIZE + 3 * OSSL_HPKE_MAX_PUBLIC) +typedef struct { + uint16_t id; + char *synonyms[4]; +} synonymttab_t; +/* max length of string we'll try map to a suite */ +#define OSSL_HPKE_MAX_SUITESTR 38 + +/* Define HPKE labels from RFC9180 in hex for EBCDIC compatibility */ /* ASCII: "HPKE-v1", in hex for EBCDIC compatibility */ static const char LABEL_HPKEV1[] = "\x48\x50\x4B\x45\x2D\x76\x31"; +/* + * Note that if additions are made to the set of IANA codepoints + * and the tables below, corresponding additions should also be + * made to the synonymtab tables a little further down so that + * OSSL_HPKE_str2suite() continues to function correctly. + * + * The canonical place to check for IANA registered codepoints + * is: https://www.iana.org/assignments/hpke/hpke.xhtml + */ + +/* + * @brief table of KEMs + * See RFC9180 Section 7.1 "Table 2 KEM IDs" + */ +static const OSSL_HPKE_KEM_INFO hpke_kem_tab[] = { +#ifndef OPENSSL_NO_EC + { OSSL_HPKE_KEM_ID_P256, "EC", OSSL_HPKE_KEMSTR_P256, + LN_sha256, SHA256_DIGEST_LENGTH, 65, 65, 32, 0xFF }, + { OSSL_HPKE_KEM_ID_P384, "EC", OSSL_HPKE_KEMSTR_P384, + LN_sha384, SHA384_DIGEST_LENGTH, 97, 97, 48, 0xFF }, + { OSSL_HPKE_KEM_ID_P521, "EC", OSSL_HPKE_KEMSTR_P521, + LN_sha512, SHA512_DIGEST_LENGTH, 133, 133, 66, 0x01 }, + { OSSL_HPKE_KEM_ID_X25519, OSSL_HPKE_KEMSTR_X25519, NULL, + LN_sha256, SHA256_DIGEST_LENGTH, + X25519_KEYLEN, X25519_KEYLEN, X25519_KEYLEN, 0x00 }, + { OSSL_HPKE_KEM_ID_X448, OSSL_HPKE_KEMSTR_X448, NULL, + LN_sha512, SHA512_DIGEST_LENGTH, + X448_KEYLEN, X448_KEYLEN, X448_KEYLEN, 0x00 } +#else + { OSSL_HPKE_KEM_ID_RESERVED, NULL, NULL, NULL, 0, 0, 0, 0, 0x00 } +#endif +}; + +/* + * @brief table of AEADs + * See RFC9180 Section 7.2 "Table 3 KDF IDs" + */ +static const OSSL_HPKE_AEAD_INFO hpke_aead_tab[] = { + { OSSL_HPKE_AEAD_ID_AES_GCM_128, LN_aes_128_gcm, 16, 16, + OSSL_HPKE_MAX_NONCELEN }, + { OSSL_HPKE_AEAD_ID_AES_GCM_256, LN_aes_256_gcm, 16, 32, + OSSL_HPKE_MAX_NONCELEN }, +#ifndef OPENSSL_NO_CHACHA20 +# ifndef OPENSSL_NO_POLY1305 + { OSSL_HPKE_AEAD_ID_CHACHA_POLY1305, LN_chacha20_poly1305, 16, 32, + OSSL_HPKE_MAX_NONCELEN }, +# endif + { OSSL_HPKE_AEAD_ID_EXPORTONLY, NULL, 0, 0, 0 } +#endif +}; + +/* + * @brief table of KDFs + * See RFC9180 Section 7.3 "Table 5 AEAD IDs" + */ +static const OSSL_HPKE_KDF_INFO hpke_kdf_tab[] = { + { OSSL_HPKE_KDF_ID_HKDF_SHA256, LN_sha256, SHA256_DIGEST_LENGTH }, + { OSSL_HPKE_KDF_ID_HKDF_SHA384, LN_sha384, SHA384_DIGEST_LENGTH }, + { OSSL_HPKE_KDF_ID_HKDF_SHA512, LN_sha512, SHA512_DIGEST_LENGTH } +}; + +/** + * Synonym tables for KEMs, KDFs and AEADs: idea is to allow + * mapping strings to suites with a little flexibility in terms + * of allowing a name or a couple of forms of number (for + * the IANA codepoint). If new IANA codepoints are allocated + * then these tables should be updated at the same time as the + * others above. + * + * The function to use these is ossl_hpke_str2suite() further down + * this file and shouln't need modification so long as the table + * sizes (i.e. allow exactly 4 synonyms) don't change. + */ +static const synonymttab_t kemstrtab[] = { + {OSSL_HPKE_KEM_ID_P256, + {OSSL_HPKE_KEMSTR_P256, "0x10", "0x10", "16" }}, + {OSSL_HPKE_KEM_ID_P384, + {OSSL_HPKE_KEMSTR_P384, "0x11", "0x11", "17" }}, + {OSSL_HPKE_KEM_ID_P521, + {OSSL_HPKE_KEMSTR_P521, "0x12", "0x12", "18" }}, + {OSSL_HPKE_KEM_ID_X25519, + {OSSL_HPKE_KEMSTR_X25519, "0x20", "0x20", "32" }}, + {OSSL_HPKE_KEM_ID_X448, + {OSSL_HPKE_KEMSTR_X448, "0x21", "0x21", "33" }} +}; +static const synonymttab_t kdfstrtab[] = { + {OSSL_HPKE_KDF_ID_HKDF_SHA256, + {OSSL_HPKE_KDFSTR_256, "0x1", "0x01", "1"}}, + {OSSL_HPKE_KDF_ID_HKDF_SHA384, + {OSSL_HPKE_KDFSTR_384, "0x2", "0x02", "2"}}, + {OSSL_HPKE_KDF_ID_HKDF_SHA512, + {OSSL_HPKE_KDFSTR_512, "0x3", "0x03", "3"}} +}; +static const synonymttab_t aeadstrtab[] = { + {OSSL_HPKE_AEAD_ID_AES_GCM_128, + {OSSL_HPKE_AEADSTR_AES128GCM, "0x1", "0x01", "1"}}, + {OSSL_HPKE_AEAD_ID_AES_GCM_256, + {OSSL_HPKE_AEADSTR_AES256GCM, "0x2", "0x02", "2"}}, + {OSSL_HPKE_AEAD_ID_CHACHA_POLY1305, + {OSSL_HPKE_AEADSTR_CP, "0x3", "0x03", "3"}}, + {OSSL_HPKE_AEAD_ID_EXPORTONLY, + {OSSL_HPKE_AEADSTR_EXP, "ff", "0xff", "255"}} +}; + +/* Return an object containing KEM constants associated with a EC curve name */ +const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_curve(const char *curve) +{ + int i, sz = OSSL_NELEM(hpke_kem_tab); + + for (i = 0; i < sz; ++i) { + const char *group = hpke_kem_tab[i].groupname; + + if (group == NULL) + group = hpke_kem_tab[i].keytype; + if (OPENSSL_strcasecmp(curve, group) == 0) + return &hpke_kem_tab[i]; + } + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CURVE); + return NULL; +} + +const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_id(uint16_t kemid) +{ + int i, sz = OSSL_NELEM(hpke_kem_tab); + + /* + * this check can happen if we're in a no-ec build and there are no + * KEMS available + */ + if (kemid == OSSL_HPKE_KEM_ID_RESERVED) { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CURVE); + return NULL; + } + for (i = 0; i != sz; ++i) { + if (hpke_kem_tab[i].kem_id == kemid) + return &hpke_kem_tab[i]; + } + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CURVE); + return NULL; +} + +const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_random(OSSL_LIB_CTX *ctx) +{ + unsigned char rval = 0; + int sz = OSSL_NELEM(hpke_kem_tab); + + if (RAND_bytes_ex(ctx, &rval, sizeof(rval), 0) <= 0) + return NULL; + return &hpke_kem_tab[rval % sz]; +} + +const OSSL_HPKE_KDF_INFO *ossl_HPKE_KDF_INFO_find_id(uint16_t kdfid) +{ + int i, sz = OSSL_NELEM(hpke_kdf_tab); + + for (i = 0; i != sz; ++i) { + if (hpke_kdf_tab[i].kdf_id == kdfid) + return &hpke_kdf_tab[i]; + } + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KDF); + return NULL; +} + +const OSSL_HPKE_KDF_INFO *ossl_HPKE_KDF_INFO_find_random(OSSL_LIB_CTX *ctx) +{ + unsigned char rval = 0; + int sz = OSSL_NELEM(hpke_kdf_tab); + + if (RAND_bytes_ex(ctx, &rval, sizeof(rval), 0) <= 0) + return NULL; + return &hpke_kdf_tab[rval % sz]; +} + +const OSSL_HPKE_AEAD_INFO *ossl_HPKE_AEAD_INFO_find_id(uint16_t aeadid) +{ + int i, sz = OSSL_NELEM(hpke_aead_tab); + + for (i = 0; i != sz; ++i) { + if (hpke_aead_tab[i].aead_id == aeadid) + return &hpke_aead_tab[i]; + } + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_AEAD); + return NULL; +} + +const OSSL_HPKE_AEAD_INFO *ossl_HPKE_AEAD_INFO_find_random(OSSL_LIB_CTX *ctx) +{ + unsigned char rval = 0; + /* the minus 1 below is so we don't pick the EXPORTONLY codepoint */ + int sz = OSSL_NELEM(hpke_aead_tab) - 1; + + if (RAND_bytes_ex(ctx, &rval, sizeof(rval), 0) <= 0) + return NULL; + return &hpke_aead_tab[rval % sz]; +} + static int kdf_derive(EVP_KDF_CTX *kctx, unsigned char *out, size_t outlen, int mode, const unsigned char *salt, size_t saltlen, @@ -82,20 +291,34 @@ int ossl_hpke_kdf_expand(EVP_KDF_CTX *kctx, int ossl_hpke_labeled_extract(EVP_KDF_CTX *kctx, unsigned char *prk, size_t prklen, const unsigned char *salt, size_t saltlen, + const char *protocol_label, const unsigned char *suiteid, size_t suiteidlen, const char *label, const unsigned char *ikm, size_t ikmlen) { int ret = 0; + size_t label_hpkev1len = 0; + size_t protocol_labellen = 0; + size_t labellen = 0; size_t labeled_ikmlen = 0; - unsigned char labeled_ikm[LABELED_EXTRACT_SIZE]; + unsigned char *labeled_ikm = NULL; WPACKET pkt; + label_hpkev1len = strlen(LABEL_HPKEV1); + protocol_labellen = strlen(protocol_label); + labellen = strlen(label); + labeled_ikmlen = label_hpkev1len + protocol_labellen + + suiteidlen + labellen + ikmlen; + labeled_ikm = OPENSSL_malloc(labeled_ikmlen); + if (labeled_ikm == NULL) + return 0; + /* labeled_ikm = concat("HPKE-v1", suiteid, label, ikm) */ - if (!WPACKET_init_static_len(&pkt, labeled_ikm, sizeof(labeled_ikm), 0) - || !WPACKET_memcpy(&pkt, LABEL_HPKEV1, strlen(LABEL_HPKEV1)) + if (!WPACKET_init_static_len(&pkt, labeled_ikm, labeled_ikmlen, 0) + || !WPACKET_memcpy(&pkt, LABEL_HPKEV1, label_hpkev1len) + || !WPACKET_memcpy(&pkt, protocol_label, protocol_labellen) || !WPACKET_memcpy(&pkt, suiteid, suiteidlen) - || !WPACKET_memcpy(&pkt, label, strlen(label)) + || !WPACKET_memcpy(&pkt, label, labellen) || !WPACKET_memcpy(&pkt, ikm, ikmlen) || !WPACKET_get_total_written(&pkt, &labeled_ikmlen) || !WPACKET_finish(&pkt)) { @@ -108,6 +331,7 @@ int ossl_hpke_labeled_extract(EVP_KDF_CTX *kctx, end: WPACKET_cleanup(&pkt); OPENSSL_cleanse(labeled_ikm, labeled_ikmlen); + OPENSSL_free(labeled_ikm); return ret; } @@ -117,21 +341,35 @@ end: int ossl_hpke_labeled_expand(EVP_KDF_CTX *kctx, unsigned char *okm, size_t okmlen, const unsigned char *prk, size_t prklen, + const char *protocol_label, const unsigned char *suiteid, size_t suiteidlen, const char *label, const unsigned char *info, size_t infolen) { int ret = 0; + size_t label_hpkev1len = 0; + size_t protocol_labellen = 0; + size_t labellen = 0; size_t labeled_infolen = 0; - unsigned char labeled_info[LABELED_EXPAND_SIZE]; + unsigned char *labeled_info = NULL; WPACKET pkt; + label_hpkev1len = strlen(LABEL_HPKEV1); + protocol_labellen = strlen(protocol_label); + labellen = strlen(label); + labeled_infolen = 2 + okmlen + prklen + label_hpkev1len + + protocol_labellen + suiteidlen + labellen + infolen; + labeled_info = OPENSSL_malloc(labeled_infolen); + if (labeled_info == NULL) + return 0; + /* labeled_info = concat(okmlen, "HPKE-v1", suiteid, label, info) */ - if (!WPACKET_init_static_len(&pkt, labeled_info, sizeof(labeled_info), 0) + if (!WPACKET_init_static_len(&pkt, labeled_info, labeled_infolen, 0) || !WPACKET_put_bytes_u16(&pkt, okmlen) - || !WPACKET_memcpy(&pkt, LABEL_HPKEV1, strlen(LABEL_HPKEV1)) + || !WPACKET_memcpy(&pkt, LABEL_HPKEV1, label_hpkev1len) + || !WPACKET_memcpy(&pkt, protocol_label, protocol_labellen) || !WPACKET_memcpy(&pkt, suiteid, suiteidlen) - || !WPACKET_memcpy(&pkt, label, strlen(label)) + || !WPACKET_memcpy(&pkt, label, labellen) || !WPACKET_memcpy(&pkt, info, infolen) || !WPACKET_get_total_written(&pkt, &labeled_infolen) || !WPACKET_finish(&pkt)) { @@ -143,6 +381,7 @@ int ossl_hpke_labeled_expand(EVP_KDF_CTX *kctx, prk, prklen, labeled_info, labeled_infolen); end: WPACKET_cleanup(&pkt); + OPENSSL_free(labeled_info); return ret; } @@ -173,3 +412,109 @@ EVP_KDF_CTX *ossl_kdf_ctx_create(const char *kdfname, const char *mdname, } return kctx; } + +/* + * @brief look for a label into the synonym tables, and return its id + * @param st is the string value + * @param synp is the synonyms labels array + * @param arrsize is the previous array size + * @return 0 when not found, else the matching item id. + */ +static uint16_t synonyms_name2id(const char *st, const synonymttab_t *synp, + size_t arrsize) +{ + size_t i, j; + + for (i = 0; i < arrsize; ++i) { + for (j = 0; j < OSSL_NELEM(synp[i].synonyms); ++j) { + if (OPENSSL_strcasecmp(st, synp[i].synonyms[j]) == 0) + return synp[i].id; + } + } + return 0; +} + +/* + * @brief map a string to a HPKE suite based on synonym tables + * @param str is the string value + * @param suite is the resulting suite + * @return 1 for success, otherwise failure + */ +int ossl_hpke_str2suite(const char *suitestr, OSSL_HPKE_SUITE *suite) +{ + uint16_t kem = 0, kdf = 0, aead = 0; + char *st = NULL, *instrcp = NULL; + size_t inplen; + int labels = 0, result = 0; + int delim_count = 0; + + if (suitestr == NULL || suitestr[0] == 0x00 || suite == NULL) { + ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + inplen = OPENSSL_strnlen(suitestr, OSSL_HPKE_MAX_SUITESTR); + if (inplen >= OSSL_HPKE_MAX_SUITESTR) { + ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT); + return 0; + } + + /* + * we don't want a delimiter at the end of the string; + * strtok_r/s() doesn't care about that, so we should + */ + if (suitestr[inplen - 1] == OSSL_HPKE_STR_DELIMCHAR) + return 0; + /* We want exactly two delimiters in the input string */ + for (st = (char *)suitestr; *st != '\0'; st++) { + if (*st == OSSL_HPKE_STR_DELIMCHAR) + delim_count++; + } + if (delim_count != 2) + return 0; + + /* Duplicate `suitestr` to allow its parsing */ + instrcp = OPENSSL_memdup(suitestr, inplen + 1); + if (instrcp == NULL) + goto fail; + + /* See if it contains a mix of our strings and numbers */ + st = instrcp; + + while (st != NULL && labels < 3) { + char *cp = strchr(st, OSSL_HPKE_STR_DELIMCHAR); + + /* add a NUL like strtok would if we're not at the end */ + if (cp != NULL) + *cp = '\0'; + + /* check if string is known or number and if so handle appropriately */ + if (labels == 0 + && (kem = synonyms_name2id(st, kemstrtab, + OSSL_NELEM(kemstrtab))) == 0) + goto fail; + else if (labels == 1 + && (kdf = synonyms_name2id(st, kdfstrtab, + OSSL_NELEM(kdfstrtab))) == 0) + goto fail; + else if (labels == 2 + && (aead = synonyms_name2id(st, aeadstrtab, + OSSL_NELEM(aeadstrtab))) == 0) + goto fail; + + if (cp == NULL) + st = NULL; + else + st = cp + 1; + ++labels; + } + if (st != NULL || labels != 3) + goto fail; + suite->kem_id = kem; + suite->kdf_id = kdf; + suite->aead_id = aead; + result = 1; + +fail: + OPENSSL_free(instrcp); + return result; +} diff --git a/doc/build.info b/doc/build.info index bc81e8378e..c18438d7cf 100644 --- a/doc/build.info +++ b/doc/build.info @@ -1651,6 +1651,10 @@ DEPEND[html/man3/OSSL_ESS_check_signing_certs.html]=man3/OSSL_ESS_check_signing_ GENERATE[html/man3/OSSL_ESS_check_signing_certs.html]=man3/OSSL_ESS_check_signing_certs.pod DEPEND[man/man3/OSSL_ESS_check_signing_certs.3]=man3/OSSL_ESS_check_signing_certs.pod GENERATE[man/man3/OSSL_ESS_check_signing_certs.3]=man3/OSSL_ESS_check_signing_certs.pod +DEPEND[html/man3/OSSL_HPKE_CTX_new.html]=man3/OSSL_HPKE_CTX_new.pod +GENERATE[html/man3/OSSL_HPKE_CTX_new.html]=man3/OSSL_HPKE_CTX_new.pod +DEPEND[man/man3/OSSL_HPKE_CTX_new.3]=man3/OSSL_HPKE_CTX_new.pod +GENERATE[man/man3/OSSL_HPKE_CTX_new.3]=man3/OSSL_HPKE_CTX_new.pod DEPEND[html/man3/OSSL_HTTP_REQ_CTX.html]=man3/OSSL_HTTP_REQ_CTX.pod GENERATE[html/man3/OSSL_HTTP_REQ_CTX.html]=man3/OSSL_HTTP_REQ_CTX.pod DEPEND[man/man3/OSSL_HTTP_REQ_CTX.3]=man3/OSSL_HTTP_REQ_CTX.pod @@ -3188,6 +3192,7 @@ html/man3/OSSL_ENCODER_CTX.html \ html/man3/OSSL_ENCODER_CTX_new_for_pkey.html \ html/man3/OSSL_ENCODER_to_bio.html \ html/man3/OSSL_ESS_check_signing_certs.html \ +html/man3/OSSL_HPKE_CTX_new.html \ html/man3/OSSL_HTTP_REQ_CTX.html \ html/man3/OSSL_HTTP_parse_url.html \ html/man3/OSSL_HTTP_transfer.html \ @@ -3794,6 +3799,7 @@ man/man3/OSSL_ENCODER_CTX.3 \ man/man3/OSSL_ENCODER_CTX_new_for_pkey.3 \ man/man3/OSSL_ENCODER_to_bio.3 \ man/man3/OSSL_ESS_check_signing_certs.3 \ +man/man3/OSSL_HPKE_CTX_new.3 \ man/man3/OSSL_HTTP_REQ_CTX.3 \ man/man3/OSSL_HTTP_parse_url.3 \ man/man3/OSSL_HTTP_transfer.3 \ diff --git a/doc/man3/OSSL_HPKE_CTX_new.pod b/doc/man3/OSSL_HPKE_CTX_new.pod new file mode 100644 index 0000000000..1e0b118e84 --- /dev/null +++ b/doc/man3/OSSL_HPKE_CTX_new.pod @@ -0,0 +1,538 @@ +=pod + +=head1 NAME + +OSSL_HPKE_CTX_new, OSSL_HPKE_CTX_free, +OSSL_HPKE_encap, OSSL_HPKE_decap, +OSSL_HPKE_seal, OSSL_HPKE_open, OSSL_HPKE_export, +OSSL_HPKE_suite_check, OSSL_HPKE_str2suite, +OSSL_HPKE_keygen, OSSL_HPKE_get_grease_value, +OSSL_HPKE_get_ciphertext_size, OSSL_HPKE_get_public_encap_size, +OSSL_HPKE_get_recommended_ikmelen, +OSSL_HPKE_CTX_set1_psk, OSSL_HPKE_CTX_set1_ikme, +OSSL_HPKE_CTX_set1_authpriv, OSSL_HPKE_CTX_set1_authpub, +OSSL_HPKE_CTX_get_seq, OSSL_HPKE_CTX_set_seq +- Hybrid Public Key Encryption (HPKE) functions + +=head1 SYNOPSIS + + #include + + 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 is a structure that holds identifiers for the algorithms +used for KEM, KDF and AEAD operations. + +B is a context that maintains internal state as HPKE +operations are carried out. Separate B 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 are: + +The KEM identifier I is one of the following: + +=over 4 + +=item 0x10 B + +=item 0x11 B + +=item 0x12 B + +=item 0x20 B + +=item 0x21 B + +=back + +The KDF identifier I is one of the following: + +=over 4 + +=item 0x01 B + +=item 0x02 B + +=item 0x03 B + +=back + +The AEAD identifier I is one of the following: + +=over 4 + +=item 0x01 B + +=item 0x02 B + +=item 0x03 B + +=item 0xFFFF B + +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, 0x00 + +Authentication is not used. + +=item B, 0x01 + +Authenticates possession of a pre-shared key (PSK). + +=item B, 0x02 + +Authenticates possession of a KEM-based sender private key. + +=item B, 0x03 + +A combination of B and B. +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 +and L. + +=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, I, and +I parameters, and for the length of the string I for HPKE +functions below. The constant I 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 parameter, +that is not sufficient for TLS Encrypted ClientHello (ECH) processing, so we +enforce a limit of I with a value of 1024 as the limit +for the I parameter. + +=head2 Context Construct/Free + +OSSL_HPKE_CTX_new() creates a B context object used for subsequent +HPKE operations, given a I (See L) and +I (see L). The I and I +are used when fetching algorithms from providers and may be set to NULL. + +OSSL_HPKE_CTX_free() frees the I B 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) and to internally derive secrets. This produces the encapsulated public value +(I) 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. + +OSSL_HPKE_encap() uses the HPKE context I, the recipient public value +I of size I, and an optional I parameter of size I, +to produce the encapsulated public value I. +On input I should contain the maximum size of the I buffer, and returns +the output size. An error will occur if the input I is +smaller than the value returned from OSSL_HPKE_get_public_encap_size(). +I may be used to bind other protocol or application artefacts such as identifiers. +Generally, the encapsulated public value I 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_seal() takes the B context I, the plaintext +buffer I of size I and optional additional authenticated data buffer +I of size I, and returns the ciphertext I of size I. +On input I should contain the maximum size of the I buffer, and returns +the output size. An error will occur if the input I 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 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 for the private value. + +OSSL_HPKE_keygen() provides a way for recipients to generate a key pair based +on the HPKE I to be used. It returns a L pointer +for the private value I and a encoded public key I of size I. +On input I should contain the maximum size of the I buffer, and +returns the output size. An error will occur if the input I is too small. +The I and I 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 +parameter of size I. If either I is NULL or I is zero, +then a randomly generated key for the relevant I will be produced. +If required I 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) and the recipient's L +pointer (I), and then re-generates the internal secret derived by the +sender. As before, an optional I 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_open() is used by the recipient to decrypt the ciphertext I of +size I using the I and additional authenticated data I of +size I, to produce the plaintext I of size I. +On input I should contain the maximum size of the I buffer, and +returns the output size. A I buffer that is the same size as the +I buffer will suffice - generally the plaintext output will be +a little smaller than the ciphertext input. +An error will occur if the input I 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 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, and an application +supplied label I