mirror of
https://github.com/openssl/openssl.git
synced 2024-11-21 01:15:20 +08:00
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:
parent
0dbd3a81e4
commit
ad062480f7
@ -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.
|
||||
|
||||
|
@ -1020,6 +1020,7 @@ PROV_R_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE:165:\
|
||||
PROV_R_INDICATOR_INTEGRITY_FAILURE:210:indicator integrity failure
|
||||
PROV_R_INSUFFICIENT_DRBG_STRENGTH:181:insufficient drbg strength
|
||||
PROV_R_INVALID_AAD:108:invalid aad
|
||||
PROV_R_INVALID_AEAD:231:invalid aead
|
||||
PROV_R_INVALID_CONFIG_DATA:211:invalid config data
|
||||
PROV_R_INVALID_CONSTANT_LENGTH:157:invalid constant length
|
||||
PROV_R_INVALID_CURVE:176:invalid curve
|
||||
@ -1031,6 +1032,7 @@ PROV_R_INVALID_DIGEST_SIZE:218:invalid digest size
|
||||
PROV_R_INVALID_INPUT_LENGTH:230:invalid input length
|
||||
PROV_R_INVALID_ITERATION_COUNT:123:invalid iteration count
|
||||
PROV_R_INVALID_IV_LENGTH:109:invalid iv length
|
||||
PROV_R_INVALID_KDF:232:invalid kdf
|
||||
PROV_R_INVALID_KEY:158:invalid key
|
||||
PROV_R_INVALID_KEY_LENGTH:105:invalid key length
|
||||
PROV_R_INVALID_MAC:151:invalid mac
|
||||
|
@ -1,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
1439
crypto/hpke/hpke.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
|
@ -1651,6 +1651,10 @@ DEPEND[html/man3/OSSL_ESS_check_signing_certs.html]=man3/OSSL_ESS_check_signing_
|
||||
GENERATE[html/man3/OSSL_ESS_check_signing_certs.html]=man3/OSSL_ESS_check_signing_certs.pod
|
||||
DEPEND[man/man3/OSSL_ESS_check_signing_certs.3]=man3/OSSL_ESS_check_signing_certs.pod
|
||||
GENERATE[man/man3/OSSL_ESS_check_signing_certs.3]=man3/OSSL_ESS_check_signing_certs.pod
|
||||
DEPEND[html/man3/OSSL_HPKE_CTX_new.html]=man3/OSSL_HPKE_CTX_new.pod
|
||||
GENERATE[html/man3/OSSL_HPKE_CTX_new.html]=man3/OSSL_HPKE_CTX_new.pod
|
||||
DEPEND[man/man3/OSSL_HPKE_CTX_new.3]=man3/OSSL_HPKE_CTX_new.pod
|
||||
GENERATE[man/man3/OSSL_HPKE_CTX_new.3]=man3/OSSL_HPKE_CTX_new.pod
|
||||
DEPEND[html/man3/OSSL_HTTP_REQ_CTX.html]=man3/OSSL_HTTP_REQ_CTX.pod
|
||||
GENERATE[html/man3/OSSL_HTTP_REQ_CTX.html]=man3/OSSL_HTTP_REQ_CTX.pod
|
||||
DEPEND[man/man3/OSSL_HTTP_REQ_CTX.3]=man3/OSSL_HTTP_REQ_CTX.pod
|
||||
@ -3188,6 +3192,7 @@ html/man3/OSSL_ENCODER_CTX.html \
|
||||
html/man3/OSSL_ENCODER_CTX_new_for_pkey.html \
|
||||
html/man3/OSSL_ENCODER_to_bio.html \
|
||||
html/man3/OSSL_ESS_check_signing_certs.html \
|
||||
html/man3/OSSL_HPKE_CTX_new.html \
|
||||
html/man3/OSSL_HTTP_REQ_CTX.html \
|
||||
html/man3/OSSL_HTTP_parse_url.html \
|
||||
html/man3/OSSL_HTTP_transfer.html \
|
||||
@ -3794,6 +3799,7 @@ man/man3/OSSL_ENCODER_CTX.3 \
|
||||
man/man3/OSSL_ENCODER_CTX_new_for_pkey.3 \
|
||||
man/man3/OSSL_ENCODER_to_bio.3 \
|
||||
man/man3/OSSL_ESS_check_signing_certs.3 \
|
||||
man/man3/OSSL_HPKE_CTX_new.3 \
|
||||
man/man3/OSSL_HTTP_REQ_CTX.3 \
|
||||
man/man3/OSSL_HTTP_parse_url.3 \
|
||||
man/man3/OSSL_HTTP_transfer.3 \
|
||||
|
538
doc/man3/OSSL_HPKE_CTX_new.pod
Normal file
538
doc/man3/OSSL_HPKE_CTX_new.pod
Normal 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
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#ifndef OSSL_CRYPTO_HPKE_H
|
||||
# define OSSL_CRYPTO_HPKE_H
|
||||
# pragma once
|
||||
|
||||
/* Constants from RFC 9180 Section 7.1 and 7.3 */
|
||||
#define OSSL_HPKE_MAX_SECRET 64
|
||||
#define OSSL_HPKE_MAX_PUBLIC 133
|
||||
#define OSSL_HPKE_MAX_PRIVATE 66
|
||||
#define OSSL_HPKE_MAX_NONCE 12
|
||||
#define OSSL_HPKE_MAX_KDF_INPUTLEN 64
|
||||
|
||||
int ossl_hpke_kdf_extract(EVP_KDF_CTX *kctx,
|
||||
unsigned char *prk, size_t prklen,
|
||||
const unsigned char *salt, size_t saltlen,
|
||||
const unsigned char *ikm, size_t ikmlen);
|
||||
|
||||
int ossl_hpke_kdf_expand(EVP_KDF_CTX *kctx,
|
||||
unsigned char *okm, size_t okmlen,
|
||||
const unsigned char *prk, size_t prklen,
|
||||
const unsigned char *info, size_t infolen);
|
||||
|
||||
int ossl_hpke_labeled_extract(EVP_KDF_CTX *kctx,
|
||||
unsigned char *prk, size_t prklen,
|
||||
const unsigned char *salt, size_t saltlen,
|
||||
const unsigned char *suiteid, size_t suiteidlen,
|
||||
const char *label,
|
||||
const unsigned char *ikm, size_t ikmlen);
|
||||
int ossl_hpke_labeled_expand(EVP_KDF_CTX *kctx,
|
||||
unsigned char *okm, size_t okmlen,
|
||||
const unsigned char *prk, size_t prklen,
|
||||
const unsigned char *suiteid, size_t suiteidlen,
|
||||
const char *label,
|
||||
const unsigned char *info, size_t infolen);
|
||||
|
||||
EVP_KDF_CTX *ossl_kdf_ctx_create(const char *kdfname, const char *mdname,
|
||||
OSSL_LIB_CTX *libctx, const char *propq);
|
||||
|
||||
#endif
|
100
include/internal/hpke_util.h
Normal file
100
include/internal/hpke_util.h
Normal 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
144
include/openssl/hpke.h
Normal 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
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2022 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@ -51,6 +51,7 @@
|
||||
# define PROV_R_INDICATOR_INTEGRITY_FAILURE 210
|
||||
# define PROV_R_INSUFFICIENT_DRBG_STRENGTH 181
|
||||
# define PROV_R_INVALID_AAD 108
|
||||
# define PROV_R_INVALID_AEAD 231
|
||||
# define PROV_R_INVALID_CONFIG_DATA 211
|
||||
# define PROV_R_INVALID_CONSTANT_LENGTH 157
|
||||
# define PROV_R_INVALID_CURVE 176
|
||||
@ -62,6 +63,7 @@
|
||||
# define PROV_R_INVALID_INPUT_LENGTH 230
|
||||
# define PROV_R_INVALID_ITERATION_COUNT 123
|
||||
# define PROV_R_INVALID_IV_LENGTH 109
|
||||
# define PROV_R_INVALID_KDF 232
|
||||
# define PROV_R_INVALID_KEY 158
|
||||
# define PROV_R_INVALID_KEY_LENGTH 105
|
||||
# define PROV_R_INVALID_MAC 151
|
||||
|
@ -1,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
|
||||
|
@ -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"},
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
1921
test/hpke_test.c
Normal file
File diff suppressed because it is too large
Load Diff
20
test/recipes/30-test_hpke.t
Normal file
20
test/recipes/30-test_hpke.t
Normal 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");
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user