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)
This commit is contained in:
Stephen Farrell 2022-11-22 02:42:04 +00:00 committed by Matt Caswell
parent 0dbd3a81e4
commit ad062480f7
21 changed files with 4678 additions and 211 deletions

View File

@ -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.

View File

@ -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

View File

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

1439
crypto/hpke/hpke.c Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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 \

View File

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

View File

@ -1,47 +0,0 @@
/*
* Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#ifndef OSSL_CRYPTO_HPKE_H
# define OSSL_CRYPTO_HPKE_H
# pragma once
/* Constants from RFC 9180 Section 7.1 and 7.3 */
#define OSSL_HPKE_MAX_SECRET 64
#define OSSL_HPKE_MAX_PUBLIC 133
#define OSSL_HPKE_MAX_PRIVATE 66
#define OSSL_HPKE_MAX_NONCE 12
#define OSSL_HPKE_MAX_KDF_INPUTLEN 64
int ossl_hpke_kdf_extract(EVP_KDF_CTX *kctx,
unsigned char *prk, size_t prklen,
const unsigned char *salt, size_t saltlen,
const unsigned char *ikm, size_t ikmlen);
int ossl_hpke_kdf_expand(EVP_KDF_CTX *kctx,
unsigned char *okm, size_t okmlen,
const unsigned char *prk, size_t prklen,
const unsigned char *info, size_t infolen);
int ossl_hpke_labeled_extract(EVP_KDF_CTX *kctx,
unsigned char *prk, size_t prklen,
const unsigned char *salt, size_t saltlen,
const unsigned char *suiteid, size_t suiteidlen,
const char *label,
const unsigned char *ikm, size_t ikmlen);
int ossl_hpke_labeled_expand(EVP_KDF_CTX *kctx,
unsigned char *okm, size_t okmlen,
const unsigned char *prk, size_t prklen,
const unsigned char *suiteid, size_t suiteidlen,
const char *label,
const unsigned char *info, size_t infolen);
EVP_KDF_CTX *ossl_kdf_ctx_create(const char *kdfname, const char *mdname,
OSSL_LIB_CTX *libctx, const char *propq);
#endif

View File

@ -0,0 +1,100 @@
/*
* Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#ifndef OSSL_INTERNAL_HPKE_UTIL_H
# define OSSL_INTERNAL_HPKE_UTIL_H
# pragma once
/* Constants from RFC 9180 Section 7.1 and 7.3 */
# define OSSL_HPKE_MAX_SECRET 64
# define OSSL_HPKE_MAX_PUBLIC 133
# define OSSL_HPKE_MAX_PRIVATE 66
# define OSSL_HPKE_MAX_KDF_INPUTLEN 64
/*
* max length of a base-nonce (the Nn field from OSSL_HPKE_AEAD_INFO), this
* is used for a local stack array size
*/
# define OSSL_HPKE_MAX_NONCELEN 12
/*
* @brief info about a KEM
* Used to store constants from Section 7.1 "Table 2 KEM IDs"
* and the bitmask for EC curves described in Section 7.1.3 DeriveKeyPair
*/
typedef struct {
uint16_t kem_id; /* code point for key encipherment method */
const char *keytype; /* string form of algtype "EC"/"X25519"/"X448" */
const char *groupname; /* string form of EC group for NIST curves */
const char *mdname; /* hash alg name for the HKDF */
size_t Nsecret; /* size of secrets */
size_t Nenc; /* length of encapsulated key */
size_t Npk; /* length of public key */
size_t Nsk; /* length of raw private key */
uint8_t bitmask;
} OSSL_HPKE_KEM_INFO;
/*
* @brief info about a KDF
*/
typedef struct {
uint16_t kdf_id; /* code point for KDF */
const char *mdname; /* hash alg name for the HKDF */
size_t Nh; /* length of hash/extract output */
} OSSL_HPKE_KDF_INFO;
/*
* @brief info about an AEAD
*/
typedef struct {
uint16_t aead_id; /* code point for aead alg */
const char *name; /* alg name */
size_t taglen; /* aead tag len */
size_t Nk; /* size of a key for this aead */
size_t Nn; /* length of a nonce for this aead */
} OSSL_HPKE_AEAD_INFO;
const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_curve(const char *curve);
const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_id(uint16_t kemid);
const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_random(OSSL_LIB_CTX *ctx);
const OSSL_HPKE_KDF_INFO *ossl_HPKE_KDF_INFO_find_id(uint16_t kdfid);
const OSSL_HPKE_KDF_INFO *ossl_HPKE_KDF_INFO_find_random(OSSL_LIB_CTX *ctx);
const OSSL_HPKE_AEAD_INFO *ossl_HPKE_AEAD_INFO_find_id(uint16_t aeadid);
const OSSL_HPKE_AEAD_INFO *ossl_HPKE_AEAD_INFO_find_random(OSSL_LIB_CTX *ctx);
int ossl_hpke_kdf_extract(EVP_KDF_CTX *kctx,
unsigned char *prk, size_t prklen,
const unsigned char *salt, size_t saltlen,
const unsigned char *ikm, size_t ikmlen);
int ossl_hpke_kdf_expand(EVP_KDF_CTX *kctx,
unsigned char *okm, size_t okmlen,
const unsigned char *prk, size_t prklen,
const unsigned char *info, size_t infolen);
int ossl_hpke_labeled_extract(EVP_KDF_CTX *kctx,
unsigned char *prk, size_t prklen,
const unsigned char *salt, size_t saltlen,
const char *protocol_label,
const unsigned char *suiteid, size_t suiteidlen,
const char *label,
const unsigned char *ikm, size_t ikmlen);
int ossl_hpke_labeled_expand(EVP_KDF_CTX *kctx,
unsigned char *okm, size_t okmlen,
const unsigned char *prk, size_t prklen,
const char *protocol_label,
const unsigned char *suiteid, size_t suiteidlen,
const char *label,
const unsigned char *info, size_t infolen);
EVP_KDF_CTX *ossl_kdf_ctx_create(const char *kdfname, const char *mdname,
OSSL_LIB_CTX *libctx, const char *propq);
int ossl_hpke_str2suite(const char *suitestr, OSSL_HPKE_SUITE *suite);
#endif

144
include/openssl/hpke.h Normal file
View File

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

View File

@ -1,6 +1,6 @@
/*
* Generated by util/mkerr.pl DO NOT EDIT
* Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
* Copyright 1995-2022 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
@ -51,6 +51,7 @@
# define PROV_R_INDICATOR_INTEGRITY_FAILURE 210
# define PROV_R_INSUFFICIENT_DRBG_STRENGTH 181
# define PROV_R_INVALID_AAD 108
# define PROV_R_INVALID_AEAD 231
# define PROV_R_INVALID_CONFIG_DATA 211
# define PROV_R_INVALID_CONSTANT_LENGTH 157
# define PROV_R_INVALID_CURVE 176
@ -62,6 +63,7 @@
# define PROV_R_INVALID_INPUT_LENGTH 230
# define PROV_R_INVALID_ITERATION_COUNT 123
# define PROV_R_INVALID_IV_LENGTH 109
# define PROV_R_INVALID_KDF 232
# define PROV_R_INVALID_KEY 158
# define PROV_R_INVALID_KEY_LENGTH 105
# define PROV_R_INVALID_MAC 151

View File

@ -1,6 +1,6 @@
/*
* Generated by util/mkerr.pl DO NOT EDIT
* Copyright 2020-2021 The OpenSSL Project Authors. All Rights Reserved.
* Copyright 2020-2022 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy

View File

@ -1,6 +1,6 @@
/*
* Generated by util/mkerr.pl DO NOT EDIT
* Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
* Copyright 1995-2022 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
@ -67,6 +67,7 @@ static const ERR_STRING_DATA PROV_str_reasons[] = {
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INSUFFICIENT_DRBG_STRENGTH),
"insufficient drbg strength"},
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_AAD), "invalid aad"},
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_AEAD), "invalid aead"},
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_CONFIG_DATA),
"invalid config data"},
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_CONSTANT_LENGTH),
@ -85,6 +86,7 @@ static const ERR_STRING_DATA PROV_str_reasons[] = {
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_ITERATION_COUNT),
"invalid iteration count"},
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_IV_LENGTH), "invalid iv length"},
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_KDF), "invalid kdf"},
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_KEY), "invalid key"},
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_KEY_LENGTH),
"invalid key length"},

View File

@ -30,25 +30,12 @@
#include "prov/securitycheck.h"
#include "prov/providercommon.h"
#include "crypto/hpke.h"
#include <openssl/hpke.h>
#include "internal/hpke_util.h"
#include "crypto/ec.h"
#include "prov/ecx.h"
#include "eckem.h"
/*
* Used to store constants from Section 7.1 "Table 2 KEM IDs"
* and the bitmask for curves described in Section 7.1.3 DeriveKeyPair
*/
typedef struct {
const char *curve;
const char *kdfdigestname;
uint16_t kemid;
size_t secretlen; /* Nsecret = Nh */
size_t encodedpublen;
size_t encodedprivlen;
uint8_t bitmask;
} DHKEM_ALG;
typedef struct {
EC_KEY *recipient_key;
EC_KEY *sender_authkey;
@ -59,7 +46,7 @@ typedef struct {
unsigned char *ikm;
size_t ikmlen;
const char *kdfname;
const DHKEM_ALG *alg;
const OSSL_HPKE_KEM_INFO *info;
} PROV_EC_CTX;
static OSSL_FUNC_kem_newctx_fn eckem_newctx;
@ -73,26 +60,8 @@ static OSSL_FUNC_kem_freectx_fn eckem_freectx;
static OSSL_FUNC_kem_set_ctx_params_fn eckem_set_ctx_params;
static OSSL_FUNC_kem_settable_ctx_params_fn eckem_settable_ctx_params;
/* See Section 7.1 "Table 2 KEM IDs" */
static const DHKEM_ALG dhkem_alg[] = {
{ "P-256", "SHA256", 0x0010, 32, 65, 32, 0xFF },
{ "P-384", "SHA384", 0x0011, 48, 97, 48, 0xFF },
{ "P-521", "SHA512", 0x0012, 64, 133, 66, 0x01 },
{ NULL }
};
/* Return an object containing KEM constants associated with a EC curve name */
static const DHKEM_ALG *dhkem_ec_find_alg(const char *curve)
{
int i;
for (i = 0; dhkem_alg[i].curve != NULL; ++i) {
if (OPENSSL_strcasecmp(curve, dhkem_alg[i].curve) == 0)
return &dhkem_alg[i];
}
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CURVE);
return NULL;
}
/* ASCII: "KEM", in hex for EBCDIC compatibility */
static const char LABEL_KEM[] = "\x4b\x45\x4d";
static int eckey_check(const EC_KEY *ec, int requires_privatekey)
{
@ -151,8 +120,8 @@ static int recipient_key_set(PROV_EC_CTX *ctx, EC_KEY *ec)
if (curve == NULL)
return -2;
ctx->alg = dhkem_ec_find_alg(curve);
if (ctx->alg == NULL)
ctx->info = ossl_HPKE_KEM_INFO_find_curve(curve);
if (ctx->info == NULL)
return -2;
if (!EC_KEY_up_ref(ec))
return 0;
@ -372,7 +341,7 @@ static int dhkem_extract_and_expand(EVP_KDF_CTX *kctx,
const unsigned char *kemctx,
size_t kemctxlen)
{
uint8_t suiteid[5];
uint8_t suiteid[2];
uint8_t prk[EVP_MAX_MD_SIZE];
size_t prklen = okmlen;
int ret;
@ -380,13 +349,14 @@ static int dhkem_extract_and_expand(EVP_KDF_CTX *kctx,
if (prklen > sizeof(prk))
return 0;
ossl_dhkem_getsuiteid(suiteid, kemid);
suiteid[0] = (kemid >> 8) & 0xff;
suiteid[1] = kemid & 0xff;
ret = ossl_hpke_labeled_extract(kctx, prk, prklen,
NULL, 0, suiteid, sizeof(suiteid),
NULL, 0, LABEL_KEM, suiteid, sizeof(suiteid),
OSSL_DHKEM_LABEL_EAE_PRK, dhkm, dhkmlen)
&& ossl_hpke_labeled_expand(kctx, okm, okmlen, prk, prklen,
suiteid, sizeof(suiteid),
LABEL_KEM, suiteid, sizeof(suiteid),
OSSL_DHKEM_LABEL_SHARED_SECRET,
kemctx, kemctxlen);
OPENSSL_cleanse(prk, prklen);
@ -413,52 +383,53 @@ int ossl_ec_dhkem_derive_private(EC_KEY *ec, BIGNUM *priv,
{
int ret = 0;
EVP_KDF_CTX *kdfctx = NULL;
uint8_t suiteid[5];
uint8_t suiteid[2];
unsigned char prk[OSSL_HPKE_MAX_SECRET];
unsigned char privbuf[OSSL_HPKE_MAX_PRIVATE];
const BIGNUM *order;
unsigned char counter = 0;
const DHKEM_ALG *alg;
const char *curve = ec_curvename_get0(ec);
const OSSL_HPKE_KEM_INFO *info;
if (curve == NULL)
return -2;
alg = dhkem_ec_find_alg(curve);
if (alg == NULL)
info = ossl_HPKE_KEM_INFO_find_curve(curve);
if (info == NULL)
return -2;
kdfctx = ossl_kdf_ctx_create("HKDF", alg->kdfdigestname,
kdfctx = ossl_kdf_ctx_create("HKDF", info->mdname,
ossl_ec_key_get_libctx(ec),
ossl_ec_key_get0_propq(ec));
if (kdfctx == NULL)
return 0;
/* ikmlen should have a length of at least Nsk */
if (ikmlen < alg->encodedprivlen) {
if (ikmlen < info->Nsecret) {
ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_INPUT_LENGTH,
"ikm length is :%zu, should be at least %zu",
ikmlen, alg->encodedprivlen);
ikmlen, info->Nsecret);
goto err;
}
ossl_dhkem_getsuiteid(suiteid, alg->kemid);
suiteid[0] = info->kem_id / 256;
suiteid[1] = info->kem_id % 256;
if (!ossl_hpke_labeled_extract(kdfctx, prk, alg->secretlen,
NULL, 0, suiteid, sizeof(suiteid),
if (!ossl_hpke_labeled_extract(kdfctx, prk, info->Nsecret,
NULL, 0, LABEL_KEM, suiteid, sizeof(suiteid),
OSSL_DHKEM_LABEL_DKP_PRK, ikm, ikmlen))
goto err;
order = EC_GROUP_get0_order(EC_KEY_get0_group(ec));
do {
if (!ossl_hpke_labeled_expand(kdfctx, privbuf, alg->encodedprivlen,
prk, alg->secretlen,
suiteid, sizeof(suiteid),
if (!ossl_hpke_labeled_expand(kdfctx, privbuf, info->Nsk,
prk, info->Nsecret,
LABEL_KEM, suiteid, sizeof(suiteid),
OSSL_DHKEM_LABEL_CANDIDATE,
&counter, 1))
goto err;
privbuf[0] &= alg->bitmask;
if (BN_bin2bn(privbuf, alg->encodedprivlen, priv) == NULL)
privbuf[0] &= info->bitmask;
if (BN_bin2bn(privbuf, info->Nsk, priv) == NULL)
goto err;
if (counter == 0xFF) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GENERATE_KEY);
@ -499,7 +470,7 @@ static EC_KEY *derivekey(PROV_EC_CTX *ctx,
/* Generate a random seed if there is no input ikm */
if (seed == NULL || seedlen == 0) {
seedlen = ctx->alg->encodedprivlen;
seedlen = ctx->info->Nsk;
if (seedlen > sizeof(tmpbuf))
goto err;
if (RAND_priv_bytes_ex(ctx->libctx, tmpbuf, seedlen, 0) <= 0)
@ -599,8 +570,9 @@ static int derive_secret(PROV_EC_CTX *ctx, unsigned char *secret,
unsigned char kemctx[OSSL_HPKE_MAX_PUBLIC * 3];
size_t sender_authpublen;
size_t kemctxlen = 0, dhkmlen = 0;
size_t encodedpublen = ctx->alg->encodedpublen;
size_t encodedprivlen = ctx->alg->encodedprivlen;
const OSSL_HPKE_KEM_INFO *info = ctx->info;
size_t encodedpublen = info->Npk;
size_t encodedprivlen = info->Nsk;
int auth = ctx->sender_authkey != NULL;
if (!generate_ecdhkm(privkey1, peerkey1, dhkm, sizeof(dhkm), encodedprivlen))
@ -630,17 +602,16 @@ static int derive_secret(PROV_EC_CTX *ctx, unsigned char *secret,
goto err;
/* kemctx is the concat of both sides encoded public key */
memcpy(kemctx, sender_pub, ctx->alg->encodedpublen);
memcpy(kemctx + ctx->alg->encodedpublen, recipient_pub,
ctx->alg->encodedpublen);
memcpy(kemctx, sender_pub, info->Npk);
memcpy(kemctx + info->Npk, recipient_pub, info->Npk);
if (auth)
memcpy(kemctx + 2 * encodedpublen, sender_authpub, encodedpublen);
kdfctx = ossl_kdf_ctx_create(ctx->kdfname, ctx->alg->kdfdigestname,
kdfctx = ossl_kdf_ctx_create(ctx->kdfname, info->mdname,
ctx->libctx, ctx->propq);
if (kdfctx == NULL)
goto err;
if (!dhkem_extract_and_expand(kdfctx, secret, ctx->alg->secretlen,
ctx->alg->kemid, dhkm, dhkmlen,
if (!dhkem_extract_and_expand(kdfctx, secret, info->Nsecret,
info->kem_id, dhkm, dhkmlen,
kemctx, kemctxlen))
goto err;
ret = 1;
@ -677,22 +648,23 @@ static int dhkem_encap(PROV_EC_CTX *ctx,
unsigned char sender_pub[OSSL_HPKE_MAX_PUBLIC];
unsigned char recipient_pub[OSSL_HPKE_MAX_PUBLIC];
size_t sender_publen, recipient_publen;
const OSSL_HPKE_KEM_INFO *info = ctx->info;
if (enc == NULL) {
if (enclen == NULL && secretlen == NULL)
return 0;
if (enclen != NULL)
*enclen = ctx->alg->encodedpublen;
*enclen = info->Nenc;
if (secretlen != NULL)
*secretlen = ctx->alg->secretlen;
*secretlen = info->Nsecret;
return 1;
}
if (*secretlen < ctx->alg->secretlen) {
if (*secretlen < info->Nsecret) {
ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*secretlen too small");
return 0;
}
if (*enclen < ctx->alg->encodedpublen) {
if (*enclen < info->Nenc) {
ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*enclen too small");
return 0;
}
@ -707,7 +679,7 @@ static int dhkem_encap(PROV_EC_CTX *ctx,
&recipient_publen, sizeof(recipient_pub)))
goto err;
if (sender_publen != ctx->alg->encodedpublen
if (sender_publen != info->Npk
|| recipient_publen != sender_publen) {
ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY, "Invalid public key");
goto err;
@ -722,7 +694,7 @@ static int dhkem_encap(PROV_EC_CTX *ctx,
/* Return the senders ephemeral public key in encoded form */
memcpy(enc, sender_pub, sender_publen);
*enclen = sender_publen;
*secretlen = ctx->alg->secretlen;
*secretlen = info->Nsecret;
ret = 1;
err:
EC_KEY_free(sender_ephemkey);
@ -751,16 +723,17 @@ static int dhkem_decap(PROV_EC_CTX *ctx,
{
int ret = 0;
EC_KEY *sender_ephempubkey = NULL;
const OSSL_HPKE_KEM_INFO *info = ctx->info;
unsigned char recipient_pub[OSSL_HPKE_MAX_PUBLIC];
size_t recipient_publen;
size_t encodedpublen = ctx->alg->encodedpublen;
size_t encodedpublen = info->Npk;
if (secret == NULL) {
*secretlen = ctx->alg->secretlen;
*secretlen = info->Nsecret;
return 1;
}
if (*secretlen < ctx->alg->secretlen) {
if (*secretlen < info->Nsecret) {
ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_LENGTH, "*secretlen too small");
return 0;
}
@ -785,7 +758,7 @@ static int dhkem_decap(PROV_EC_CTX *ctx,
ctx->recipient_key, ctx->sender_authkey,
enc, recipient_pub))
goto err;
*secretlen = ctx->alg->secretlen;
*secretlen = info->Nsecret;
ret = 1;
err:
EC_KEY_free(sender_ephempubkey);

View File

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

View File

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

View File

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

View File

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

1921
test/hpke_test.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
#! /usr/bin/env perl
# Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
use strict;
use OpenSSL::Test; # get 'plan'
use OpenSSL::Test::Simple;
use OpenSSL::Test::Utils;
setup("test_hpke");
plan skip_all => "This test is unsupported in a no-ec build"
if disabled("ec");
simple_test("test_hpke", "hpke_test");

View File

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