chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,789 @@
// Copyright (c) 2020, Google Inc.
// SPDX-License-Identifier: ISC
#include <openssl/hpke.h>
#include <assert.h>
#include <string.h>
#include <openssl/aead.h>
#include <openssl/bytestring.h>
#include <openssl/curve25519.h>
#include <openssl/digest.h>
#include <openssl/err.h>
#include <openssl/evp_errors.h>
#include <openssl/hkdf.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include "../internal.h"
// This file implements RFC 9180.
#define MAX_SEED_LEN X25519_PRIVATE_KEY_LEN
#define MAX_SHARED_SECRET_LEN SHA256_DIGEST_LENGTH
struct evp_hpke_kem_st {
uint16_t id;
size_t public_key_len;
size_t private_key_len;
size_t seed_len;
size_t enc_len;
int (*init_key)(EVP_HPKE_KEY *key, const uint8_t *priv_key,
size_t priv_key_len);
int (*generate_key)(EVP_HPKE_KEY *key);
int (*encap_with_seed)(const EVP_HPKE_KEM *kem, uint8_t *out_shared_secret,
size_t *out_shared_secret_len, uint8_t *out_enc,
size_t *out_enc_len, size_t max_enc,
const uint8_t *peer_public_key,
size_t peer_public_key_len, const uint8_t *seed,
size_t seed_len);
int (*decap)(const EVP_HPKE_KEY *key, uint8_t *out_shared_secret,
size_t *out_shared_secret_len, const uint8_t *enc,
size_t enc_len);
int (*auth_encap_with_seed)(const EVP_HPKE_KEY *key,
uint8_t *out_shared_secret,
size_t *out_shared_secret_len, uint8_t *out_enc,
size_t *out_enc_len, size_t max_enc,
const uint8_t *peer_public_key,
size_t peer_public_key_len, const uint8_t *seed,
size_t seed_len);
int (*auth_decap)(const EVP_HPKE_KEY *key, uint8_t *out_shared_secret,
size_t *out_shared_secret_len, const uint8_t *enc,
size_t enc_len, const uint8_t *peer_public_key,
size_t peer_public_key_len);
};
struct evp_hpke_kdf_st {
uint16_t id;
// We only support HKDF-based KDFs.
const EVP_MD *(*hkdf_md_func)(void);
};
struct evp_hpke_aead_st {
uint16_t id;
const EVP_AEAD *(*aead_func)(void);
};
// Low-level labeled KDF functions.
static const char kHpkeVersionId[] = "HPKE-v1";
static int add_label_string(CBB *cbb, const char *label) {
return CBB_add_bytes(cbb, (const uint8_t *)label, strlen(label));
}
static int hpke_labeled_extract(const EVP_MD *hkdf_md, uint8_t *out_key,
size_t *out_len, const uint8_t *salt,
size_t salt_len, const uint8_t *suite_id,
size_t suite_id_len, const char *label,
const uint8_t *ikm, size_t ikm_len) {
// labeledIKM = concat("HPKE-v1", suite_id, label, IKM)
CBB labeled_ikm;
int ok = CBB_init(&labeled_ikm, 0) &&
add_label_string(&labeled_ikm, kHpkeVersionId) &&
CBB_add_bytes(&labeled_ikm, suite_id, suite_id_len) &&
add_label_string(&labeled_ikm, label) &&
CBB_add_bytes(&labeled_ikm, ikm, ikm_len) &&
HKDF_extract(out_key, out_len, hkdf_md, CBB_data(&labeled_ikm),
CBB_len(&labeled_ikm), salt, salt_len);
CBB_cleanup(&labeled_ikm);
return ok;
}
static int hpke_labeled_expand(const EVP_MD *hkdf_md, uint8_t *out_key,
size_t out_len, const uint8_t *prk,
size_t prk_len, const uint8_t *suite_id,
size_t suite_id_len, const char *label,
const uint8_t *info, size_t info_len) {
// labeledInfo = concat(I2OSP(L, 2), "HPKE-v1", suite_id, label, info)
CBB labeled_info;
int ok = CBB_init(&labeled_info, 0) &&
CBB_add_u16(&labeled_info, out_len) &&
add_label_string(&labeled_info, kHpkeVersionId) &&
CBB_add_bytes(&labeled_info, suite_id, suite_id_len) &&
add_label_string(&labeled_info, label) &&
CBB_add_bytes(&labeled_info, info, info_len) &&
HKDF_expand(out_key, out_len, hkdf_md, prk, prk_len,
CBB_data(&labeled_info), CBB_len(&labeled_info));
CBB_cleanup(&labeled_info);
return ok;
}
// KEM implementations.
// dhkem_extract_and_expand implements the ExtractAndExpand operation in the
// DHKEM construction. See section 4.1 of RFC 9180.
static int dhkem_extract_and_expand(uint16_t kem_id, const EVP_MD *hkdf_md,
uint8_t *out_key, size_t out_len,
const uint8_t *dh, size_t dh_len,
const uint8_t *kem_context,
size_t kem_context_len) {
// concat("KEM", I2OSP(kem_id, 2))
uint8_t suite_id[5] = {'K', 'E', 'M', kem_id >> 8, kem_id & 0xff};
uint8_t prk[EVP_MAX_MD_SIZE];
size_t prk_len;
return hpke_labeled_extract(hkdf_md, prk, &prk_len, NULL, 0, suite_id,
sizeof(suite_id), "eae_prk", dh, dh_len) &&
hpke_labeled_expand(hkdf_md, out_key, out_len, prk, prk_len, suite_id,
sizeof(suite_id), "shared_secret", kem_context,
kem_context_len);
}
static int x25519_init_key(EVP_HPKE_KEY *key, const uint8_t *priv_key,
size_t priv_key_len) {
if (priv_key_len != X25519_PRIVATE_KEY_LEN) {
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
return 0;
}
OPENSSL_memcpy(key->private_key, priv_key, priv_key_len);
X25519_public_from_private(key->public_key, priv_key);
return 1;
}
static int x25519_generate_key(EVP_HPKE_KEY *key) {
X25519_keypair(key->public_key, key->private_key);
return 1;
}
static int x25519_encap_with_seed(
const EVP_HPKE_KEM *kem, uint8_t *out_shared_secret,
size_t *out_shared_secret_len, uint8_t *out_enc, size_t *out_enc_len,
size_t max_enc, const uint8_t *peer_public_key, size_t peer_public_key_len,
const uint8_t *seed, size_t seed_len) {
if (max_enc < X25519_PUBLIC_VALUE_LEN) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
return 0;
}
if (seed_len != X25519_PRIVATE_KEY_LEN) {
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
return 0;
}
X25519_public_from_private(out_enc, seed);
uint8_t dh[X25519_SHARED_KEY_LEN];
if (peer_public_key_len != X25519_PUBLIC_VALUE_LEN ||
!X25519(dh, seed, peer_public_key)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_PEER_KEY);
return 0;
}
uint8_t kem_context[2 * X25519_PUBLIC_VALUE_LEN];
OPENSSL_memcpy(kem_context, out_enc, X25519_PUBLIC_VALUE_LEN);
OPENSSL_memcpy(kem_context + X25519_PUBLIC_VALUE_LEN, peer_public_key,
X25519_PUBLIC_VALUE_LEN);
if (!dhkem_extract_and_expand(kem->id, EVP_sha256(), out_shared_secret,
SHA256_DIGEST_LENGTH, dh, sizeof(dh),
kem_context, sizeof(kem_context))) {
return 0;
}
*out_enc_len = X25519_PUBLIC_VALUE_LEN;
*out_shared_secret_len = SHA256_DIGEST_LENGTH;
return 1;
}
static int x25519_decap(const EVP_HPKE_KEY *key, uint8_t *out_shared_secret,
size_t *out_shared_secret_len, const uint8_t *enc,
size_t enc_len) {
uint8_t dh[X25519_SHARED_KEY_LEN];
if (enc_len != X25519_PUBLIC_VALUE_LEN ||
!X25519(dh, key->private_key, enc)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_PEER_KEY);
return 0;
}
uint8_t kem_context[2 * X25519_PUBLIC_VALUE_LEN];
OPENSSL_memcpy(kem_context, enc, X25519_PUBLIC_VALUE_LEN);
OPENSSL_memcpy(kem_context + X25519_PUBLIC_VALUE_LEN, key->public_key,
X25519_PUBLIC_VALUE_LEN);
if (!dhkem_extract_and_expand(key->kem->id, EVP_sha256(), out_shared_secret,
SHA256_DIGEST_LENGTH, dh, sizeof(dh),
kem_context, sizeof(kem_context))) {
return 0;
}
*out_shared_secret_len = SHA256_DIGEST_LENGTH;
return 1;
}
static int x25519_auth_encap_with_seed(
const EVP_HPKE_KEY *key, uint8_t *out_shared_secret,
size_t *out_shared_secret_len, uint8_t *out_enc, size_t *out_enc_len,
size_t max_enc, const uint8_t *peer_public_key, size_t peer_public_key_len,
const uint8_t *seed, size_t seed_len) {
if (max_enc < X25519_PUBLIC_VALUE_LEN) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
return 0;
}
if (seed_len != X25519_PRIVATE_KEY_LEN) {
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
return 0;
}
X25519_public_from_private(out_enc, seed);
uint8_t dh[2 * X25519_SHARED_KEY_LEN];
if (peer_public_key_len != X25519_PUBLIC_VALUE_LEN ||
!X25519(dh, seed, peer_public_key) ||
!X25519(dh + X25519_SHARED_KEY_LEN, key->private_key, peer_public_key)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_PEER_KEY);
return 0;
}
uint8_t kem_context[3 * X25519_PUBLIC_VALUE_LEN];
OPENSSL_memcpy(kem_context, out_enc, X25519_PUBLIC_VALUE_LEN);
OPENSSL_memcpy(kem_context + X25519_PUBLIC_VALUE_LEN, peer_public_key,
X25519_PUBLIC_VALUE_LEN);
OPENSSL_memcpy(kem_context + 2 * X25519_PUBLIC_VALUE_LEN, key->public_key,
X25519_PUBLIC_VALUE_LEN);
if (!dhkem_extract_and_expand(key->kem->id, EVP_sha256(), out_shared_secret,
SHA256_DIGEST_LENGTH, dh, sizeof(dh),
kem_context, sizeof(kem_context))) {
return 0;
}
*out_enc_len = X25519_PUBLIC_VALUE_LEN;
*out_shared_secret_len = SHA256_DIGEST_LENGTH;
return 1;
}
static int x25519_auth_decap(const EVP_HPKE_KEY *key,
uint8_t *out_shared_secret,
size_t *out_shared_secret_len, const uint8_t *enc,
size_t enc_len, const uint8_t *peer_public_key,
size_t peer_public_key_len) {
uint8_t dh[2 * X25519_SHARED_KEY_LEN];
if (enc_len != X25519_PUBLIC_VALUE_LEN ||
peer_public_key_len != X25519_PUBLIC_VALUE_LEN ||
!X25519(dh, key->private_key, enc) ||
!X25519(dh + X25519_SHARED_KEY_LEN, key->private_key, peer_public_key)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_PEER_KEY);
return 0;
}
uint8_t kem_context[3 * X25519_PUBLIC_VALUE_LEN];
OPENSSL_memcpy(kem_context, enc, X25519_PUBLIC_VALUE_LEN);
OPENSSL_memcpy(kem_context + X25519_PUBLIC_VALUE_LEN, key->public_key,
X25519_PUBLIC_VALUE_LEN);
OPENSSL_memcpy(kem_context + 2 * X25519_PUBLIC_VALUE_LEN, peer_public_key,
X25519_PUBLIC_VALUE_LEN);
if (!dhkem_extract_and_expand(key->kem->id, EVP_sha256(), out_shared_secret,
SHA256_DIGEST_LENGTH, dh, sizeof(dh),
kem_context, sizeof(kem_context))) {
return 0;
}
*out_shared_secret_len = SHA256_DIGEST_LENGTH;
return 1;
}
const EVP_HPKE_KEM *EVP_hpke_x25519_hkdf_sha256(void) {
static const EVP_HPKE_KEM kKEM = {
/*id=*/EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
/*public_key_len=*/X25519_PUBLIC_VALUE_LEN,
/*private_key_len=*/X25519_PRIVATE_KEY_LEN,
/*seed_len=*/X25519_PRIVATE_KEY_LEN,
/*enc_len=*/X25519_PUBLIC_VALUE_LEN,
x25519_init_key,
x25519_generate_key,
x25519_encap_with_seed,
x25519_decap,
x25519_auth_encap_with_seed,
x25519_auth_decap,
};
return &kKEM;
}
uint16_t EVP_HPKE_KEM_id(const EVP_HPKE_KEM *kem) { return kem->id; }
size_t EVP_HPKE_KEM_public_key_len(const EVP_HPKE_KEM *kem) {
return kem->public_key_len;
}
size_t EVP_HPKE_KEM_private_key_len(const EVP_HPKE_KEM *kem) {
return kem->private_key_len;
}
size_t EVP_HPKE_KEM_enc_len(const EVP_HPKE_KEM *kem) { return kem->enc_len; }
void EVP_HPKE_KEY_zero(EVP_HPKE_KEY *key) {
OPENSSL_memset(key, 0, sizeof(EVP_HPKE_KEY));
}
void EVP_HPKE_KEY_cleanup(EVP_HPKE_KEY *key) {
// Nothing to clean up for now, but we may introduce a cleanup process in the
// future.
}
EVP_HPKE_KEY *EVP_HPKE_KEY_new(void) {
EVP_HPKE_KEY *key = OPENSSL_malloc(sizeof(EVP_HPKE_KEY));
if (key == NULL) {
return NULL;
}
EVP_HPKE_KEY_zero(key);
return key;
}
void EVP_HPKE_KEY_free(EVP_HPKE_KEY *key) {
if (key != NULL) {
EVP_HPKE_KEY_cleanup(key);
OPENSSL_free(key);
}
}
int EVP_HPKE_KEY_copy(EVP_HPKE_KEY *dst, const EVP_HPKE_KEY *src) {
// For now, |EVP_HPKE_KEY| is trivially copyable.
OPENSSL_memcpy(dst, src, sizeof(EVP_HPKE_KEY));
return 1;
}
void EVP_HPKE_KEY_move(EVP_HPKE_KEY *out, EVP_HPKE_KEY *in) {
EVP_HPKE_KEY_cleanup(out);
// For now, |EVP_HPKE_KEY| is trivially movable.
OPENSSL_memcpy(out, in, sizeof(EVP_HPKE_KEY));
EVP_HPKE_KEY_zero(in);
}
int EVP_HPKE_KEY_init(EVP_HPKE_KEY *key, const EVP_HPKE_KEM *kem,
const uint8_t *priv_key, size_t priv_key_len) {
EVP_HPKE_KEY_zero(key);
key->kem = kem;
if (!kem->init_key(key, priv_key, priv_key_len)) {
key->kem = NULL;
return 0;
}
return 1;
}
int EVP_HPKE_KEY_generate(EVP_HPKE_KEY *key, const EVP_HPKE_KEM *kem) {
EVP_HPKE_KEY_zero(key);
key->kem = kem;
if (!kem->generate_key(key)) {
key->kem = NULL;
return 0;
}
return 1;
}
const EVP_HPKE_KEM *EVP_HPKE_KEY_kem(const EVP_HPKE_KEY *key) {
return key->kem;
}
int EVP_HPKE_KEY_public_key(const EVP_HPKE_KEY *key, uint8_t *out,
size_t *out_len, size_t max_out) {
if (max_out < key->kem->public_key_len) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
return 0;
}
OPENSSL_memcpy(out, key->public_key, key->kem->public_key_len);
*out_len = key->kem->public_key_len;
return 1;
}
int EVP_HPKE_KEY_private_key(const EVP_HPKE_KEY *key, uint8_t *out,
size_t *out_len, size_t max_out) {
if (max_out < key->kem->private_key_len) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
return 0;
}
OPENSSL_memcpy(out, key->private_key, key->kem->private_key_len);
*out_len = key->kem->private_key_len;
return 1;
}
// Supported KDFs and AEADs.
const EVP_HPKE_KDF *EVP_hpke_hkdf_sha256(void) {
static const EVP_HPKE_KDF kKDF = {EVP_HPKE_HKDF_SHA256, &EVP_sha256};
return &kKDF;
}
uint16_t EVP_HPKE_KDF_id(const EVP_HPKE_KDF *kdf) { return kdf->id; }
const EVP_MD *EVP_HPKE_KDF_hkdf_md(const EVP_HPKE_KDF *kdf) {
return kdf->hkdf_md_func();
}
const EVP_HPKE_AEAD *EVP_hpke_aes_128_gcm(void) {
static const EVP_HPKE_AEAD kAEAD = {EVP_HPKE_AES_128_GCM,
&EVP_aead_aes_128_gcm};
return &kAEAD;
}
const EVP_HPKE_AEAD *EVP_hpke_aes_256_gcm(void) {
static const EVP_HPKE_AEAD kAEAD = {EVP_HPKE_AES_256_GCM,
&EVP_aead_aes_256_gcm};
return &kAEAD;
}
const EVP_HPKE_AEAD *EVP_hpke_chacha20_poly1305(void) {
static const EVP_HPKE_AEAD kAEAD = {EVP_HPKE_CHACHA20_POLY1305,
&EVP_aead_chacha20_poly1305};
return &kAEAD;
}
uint16_t EVP_HPKE_AEAD_id(const EVP_HPKE_AEAD *aead) { return aead->id; }
const EVP_AEAD *EVP_HPKE_AEAD_aead(const EVP_HPKE_AEAD *aead) {
return aead->aead_func();
}
// HPKE implementation.
// This is strlen("HPKE") + 3 * sizeof(uint16_t).
#define HPKE_SUITE_ID_LEN 10
// The suite_id for non-KEM pieces of HPKE is defined as concat("HPKE",
// I2OSP(kem_id, 2), I2OSP(kdf_id, 2), I2OSP(aead_id, 2)).
static int hpke_build_suite_id(const EVP_HPKE_CTX *ctx,
uint8_t out[HPKE_SUITE_ID_LEN]) {
CBB cbb;
CBB_init_fixed(&cbb, out, HPKE_SUITE_ID_LEN);
return add_label_string(&cbb, "HPKE") && //
CBB_add_u16(&cbb, ctx->kem->id) && //
CBB_add_u16(&cbb, ctx->kdf->id) && //
CBB_add_u16(&cbb, ctx->aead->id);
}
#define HPKE_MODE_BASE 0
#define HPKE_MODE_AUTH 2
static int hpke_key_schedule(EVP_HPKE_CTX *ctx, uint8_t mode,
const uint8_t *shared_secret,
size_t shared_secret_len, const uint8_t *info,
size_t info_len) {
uint8_t suite_id[HPKE_SUITE_ID_LEN];
if (!hpke_build_suite_id(ctx, suite_id)) {
return 0;
}
// psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
// TODO(davidben): Precompute this value and store it with the EVP_HPKE_KDF.
const EVP_MD *hkdf_md = ctx->kdf->hkdf_md_func();
uint8_t psk_id_hash[EVP_MAX_MD_SIZE];
size_t psk_id_hash_len;
if (!hpke_labeled_extract(hkdf_md, psk_id_hash, &psk_id_hash_len, NULL, 0,
suite_id, sizeof(suite_id), "psk_id_hash", NULL,
0)) {
return 0;
}
// info_hash = LabeledExtract("", "info_hash", info)
uint8_t info_hash[EVP_MAX_MD_SIZE];
size_t info_hash_len;
if (!hpke_labeled_extract(hkdf_md, info_hash, &info_hash_len, NULL, 0,
suite_id, sizeof(suite_id), "info_hash", info,
info_len)) {
return 0;
}
// key_schedule_context = concat(mode, psk_id_hash, info_hash)
uint8_t context[sizeof(uint8_t) + 2 * EVP_MAX_MD_SIZE];
size_t context_len;
CBB context_cbb;
CBB_init_fixed(&context_cbb, context, sizeof(context));
if (!CBB_add_u8(&context_cbb, mode) ||
!CBB_add_bytes(&context_cbb, psk_id_hash, psk_id_hash_len) ||
!CBB_add_bytes(&context_cbb, info_hash, info_hash_len) ||
!CBB_finish(&context_cbb, NULL, &context_len)) {
return 0;
}
// secret = LabeledExtract(shared_secret, "secret", psk)
uint8_t secret[EVP_MAX_MD_SIZE];
size_t secret_len;
if (!hpke_labeled_extract(hkdf_md, secret, &secret_len, shared_secret,
shared_secret_len, suite_id, sizeof(suite_id),
"secret", NULL, 0)) {
return 0;
}
// key = LabeledExpand(secret, "key", key_schedule_context, Nk)
const EVP_AEAD *aead = EVP_HPKE_AEAD_aead(ctx->aead);
uint8_t key[EVP_AEAD_MAX_KEY_LENGTH];
const size_t kKeyLen = EVP_AEAD_key_length(aead);
if (!hpke_labeled_expand(hkdf_md, key, kKeyLen, secret, secret_len, suite_id,
sizeof(suite_id), "key", context, context_len) ||
!EVP_AEAD_CTX_init(&ctx->aead_ctx, aead, key, kKeyLen,
EVP_AEAD_DEFAULT_TAG_LENGTH, NULL)) {
return 0;
}
// base_nonce = LabeledExpand(secret, "base_nonce", key_schedule_context, Nn)
if (!hpke_labeled_expand(hkdf_md, ctx->base_nonce,
EVP_AEAD_nonce_length(aead), secret, secret_len,
suite_id, sizeof(suite_id), "base_nonce", context,
context_len)) {
return 0;
}
// exporter_secret = LabeledExpand(secret, "exp", key_schedule_context, Nh)
if (!hpke_labeled_expand(hkdf_md, ctx->exporter_secret, EVP_MD_size(hkdf_md),
secret, secret_len, suite_id, sizeof(suite_id),
"exp", context, context_len)) {
return 0;
}
return 1;
}
void EVP_HPKE_CTX_zero(EVP_HPKE_CTX *ctx) {
OPENSSL_memset(ctx, 0, sizeof(EVP_HPKE_CTX));
EVP_AEAD_CTX_zero(&ctx->aead_ctx);
}
void EVP_HPKE_CTX_cleanup(EVP_HPKE_CTX *ctx) {
EVP_AEAD_CTX_cleanup(&ctx->aead_ctx);
}
EVP_HPKE_CTX *EVP_HPKE_CTX_new(void) {
EVP_HPKE_CTX *ctx = OPENSSL_zalloc(sizeof(EVP_HPKE_CTX));
if (ctx == NULL) {
return NULL;
}
// NO-OP: struct already zeroed
//EVP_HPKE_CTX_zero(ctx);
return ctx;
}
void EVP_HPKE_CTX_free(EVP_HPKE_CTX *ctx) {
if (ctx != NULL) {
EVP_HPKE_CTX_cleanup(ctx);
OPENSSL_free(ctx);
}
}
int EVP_HPKE_CTX_setup_sender(EVP_HPKE_CTX *ctx, uint8_t *out_enc,
size_t *out_enc_len, size_t max_enc,
const EVP_HPKE_KEM *kem, const EVP_HPKE_KDF *kdf,
const EVP_HPKE_AEAD *aead,
const uint8_t *peer_public_key,
size_t peer_public_key_len, const uint8_t *info,
size_t info_len) {
uint8_t seed[MAX_SEED_LEN];
AWSLC_ABORT_IF_NOT_ONE(RAND_bytes(seed, kem->seed_len));
return EVP_HPKE_CTX_setup_sender_with_seed_for_testing(
ctx, out_enc, out_enc_len, max_enc, kem, kdf, aead, peer_public_key,
peer_public_key_len, info, info_len, seed, kem->seed_len);
}
int EVP_HPKE_CTX_setup_sender_with_seed_for_testing(
EVP_HPKE_CTX *ctx, uint8_t *out_enc, size_t *out_enc_len, size_t max_enc,
const EVP_HPKE_KEM *kem, const EVP_HPKE_KDF *kdf, const EVP_HPKE_AEAD *aead,
const uint8_t *peer_public_key, size_t peer_public_key_len,
const uint8_t *info, size_t info_len, const uint8_t *seed,
size_t seed_len) {
EVP_HPKE_CTX_zero(ctx);
ctx->is_sender = 1;
ctx->kem = kem;
ctx->kdf = kdf;
ctx->aead = aead;
uint8_t shared_secret[MAX_SHARED_SECRET_LEN];
size_t shared_secret_len;
if (!kem->encap_with_seed(kem, shared_secret, &shared_secret_len, out_enc,
out_enc_len, max_enc, peer_public_key,
peer_public_key_len, seed, seed_len) ||
!hpke_key_schedule(ctx, HPKE_MODE_BASE, shared_secret, shared_secret_len,
info, info_len)) {
EVP_HPKE_CTX_cleanup(ctx);
return 0;
}
return 1;
}
int EVP_HPKE_CTX_setup_recipient(EVP_HPKE_CTX *ctx, const EVP_HPKE_KEY *key,
const EVP_HPKE_KDF *kdf,
const EVP_HPKE_AEAD *aead, const uint8_t *enc,
size_t enc_len, const uint8_t *info,
size_t info_len) {
EVP_HPKE_CTX_zero(ctx);
ctx->is_sender = 0;
ctx->kem = key->kem;
ctx->kdf = kdf;
ctx->aead = aead;
uint8_t shared_secret[MAX_SHARED_SECRET_LEN];
size_t shared_secret_len;
if (!key->kem->decap(key, shared_secret, &shared_secret_len, enc, enc_len) ||
!hpke_key_schedule(ctx, HPKE_MODE_BASE, shared_secret, shared_secret_len,
info, info_len)) {
EVP_HPKE_CTX_cleanup(ctx);
return 0;
}
return 1;
}
int EVP_HPKE_CTX_setup_auth_sender(
EVP_HPKE_CTX *ctx, uint8_t *out_enc, size_t *out_enc_len, size_t max_enc,
const EVP_HPKE_KEY *key, const EVP_HPKE_KDF *kdf, const EVP_HPKE_AEAD *aead,
const uint8_t *peer_public_key, size_t peer_public_key_len,
const uint8_t *info, size_t info_len) {
uint8_t seed[MAX_SEED_LEN];
AWSLC_ABORT_IF_NOT_ONE(RAND_bytes(seed, key->kem->seed_len));
return EVP_HPKE_CTX_setup_auth_sender_with_seed_for_testing(
ctx, out_enc, out_enc_len, max_enc, key, kdf, aead, peer_public_key,
peer_public_key_len, info, info_len, seed, key->kem->seed_len);
}
int EVP_HPKE_CTX_setup_auth_sender_with_seed_for_testing(
EVP_HPKE_CTX *ctx, uint8_t *out_enc, size_t *out_enc_len, size_t max_enc,
const EVP_HPKE_KEY *key, const EVP_HPKE_KDF *kdf, const EVP_HPKE_AEAD *aead,
const uint8_t *peer_public_key, size_t peer_public_key_len,
const uint8_t *info, size_t info_len, const uint8_t *seed,
size_t seed_len) {
if (key->kem->auth_encap_with_seed == NULL) {
// Not all HPKE KEMs support AuthEncap.
OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
return 0;
}
EVP_HPKE_CTX_zero(ctx);
ctx->is_sender = 1;
ctx->kem = key->kem;
ctx->kdf = kdf;
ctx->aead = aead;
uint8_t shared_secret[MAX_SHARED_SECRET_LEN];
size_t shared_secret_len;
if (!key->kem->auth_encap_with_seed(
key, shared_secret, &shared_secret_len, out_enc, out_enc_len, max_enc,
peer_public_key, peer_public_key_len, seed, seed_len) ||
!hpke_key_schedule(ctx, HPKE_MODE_AUTH, shared_secret, shared_secret_len,
info, info_len)) {
EVP_HPKE_CTX_cleanup(ctx);
return 0;
}
return 1;
}
int EVP_HPKE_CTX_setup_auth_recipient(
EVP_HPKE_CTX *ctx, const EVP_HPKE_KEY *key, const EVP_HPKE_KDF *kdf,
const EVP_HPKE_AEAD *aead, const uint8_t *enc, size_t enc_len,
const uint8_t *info, size_t info_len, const uint8_t *peer_public_key,
size_t peer_public_key_len) {
if (key->kem->auth_decap == NULL) {
// Not all HPKE KEMs support AuthDecap.
OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
return 0;
}
EVP_HPKE_CTX_zero(ctx);
ctx->is_sender = 0;
ctx->kem = key->kem;
ctx->kdf = kdf;
ctx->aead = aead;
uint8_t shared_secret[MAX_SHARED_SECRET_LEN];
size_t shared_secret_len;
if (!key->kem->auth_decap(key, shared_secret, &shared_secret_len, enc,
enc_len, peer_public_key, peer_public_key_len) ||
!hpke_key_schedule(ctx, HPKE_MODE_AUTH, shared_secret, shared_secret_len,
info, info_len)) {
EVP_HPKE_CTX_cleanup(ctx);
return 0;
}
return 1;
}
static void hpke_nonce(const EVP_HPKE_CTX *ctx, uint8_t *out_nonce,
size_t nonce_len) {
assert(nonce_len >= 8);
// Write padded big-endian bytes of |ctx->seq| to |out_nonce|.
OPENSSL_memset(out_nonce, 0, nonce_len);
uint64_t seq_copy = ctx->seq;
for (size_t i = 0; i < 8; i++) {
out_nonce[nonce_len - i - 1] = seq_copy & 0xff;
seq_copy >>= 8;
}
// XOR the encoded sequence with the |ctx->base_nonce|.
for (size_t i = 0; i < nonce_len; i++) {
out_nonce[i] ^= ctx->base_nonce[i];
}
}
int EVP_HPKE_CTX_open(EVP_HPKE_CTX *ctx, uint8_t *out, size_t *out_len,
size_t max_out_len, const uint8_t *in, size_t in_len,
const uint8_t *ad, size_t ad_len) {
if (ctx->is_sender) {
OPENSSL_PUT_ERROR(EVP, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
}
if (ctx->seq == UINT64_MAX) {
OPENSSL_PUT_ERROR(EVP, ERR_R_OVERFLOW);
return 0;
}
uint8_t nonce[EVP_AEAD_MAX_NONCE_LENGTH];
const size_t nonce_len = EVP_AEAD_nonce_length(ctx->aead_ctx.aead);
hpke_nonce(ctx, nonce, nonce_len);
if (!EVP_AEAD_CTX_open(&ctx->aead_ctx, out, out_len, max_out_len, nonce,
nonce_len, in, in_len, ad, ad_len)) {
return 0;
}
ctx->seq++;
return 1;
}
int EVP_HPKE_CTX_seal(EVP_HPKE_CTX *ctx, uint8_t *out, size_t *out_len,
size_t max_out_len, const uint8_t *in, size_t in_len,
const uint8_t *ad, size_t ad_len) {
if (!ctx->is_sender) {
OPENSSL_PUT_ERROR(EVP, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
}
if (ctx->seq == UINT64_MAX) {
OPENSSL_PUT_ERROR(EVP, ERR_R_OVERFLOW);
return 0;
}
uint8_t nonce[EVP_AEAD_MAX_NONCE_LENGTH];
const size_t nonce_len = EVP_AEAD_nonce_length(ctx->aead_ctx.aead);
hpke_nonce(ctx, nonce, nonce_len);
if (!EVP_AEAD_CTX_seal(&ctx->aead_ctx, out, out_len, max_out_len, nonce,
nonce_len, in, in_len, ad, ad_len)) {
return 0;
}
ctx->seq++;
return 1;
}
int EVP_HPKE_CTX_export(const EVP_HPKE_CTX *ctx, uint8_t *out,
size_t secret_len, const uint8_t *context,
size_t context_len) {
uint8_t suite_id[HPKE_SUITE_ID_LEN];
if (!hpke_build_suite_id(ctx, suite_id)) {
return 0;
}
const EVP_MD *hkdf_md = ctx->kdf->hkdf_md_func();
if (!hpke_labeled_expand(hkdf_md, out, secret_len, ctx->exporter_secret,
EVP_MD_size(hkdf_md), suite_id, sizeof(suite_id),
"sec", context, context_len)) {
return 0;
}
return 1;
}
size_t EVP_HPKE_CTX_max_overhead(const EVP_HPKE_CTX *ctx) {
assert(ctx->is_sender);
return EVP_AEAD_max_overhead(EVP_AEAD_CTX_aead(&ctx->aead_ctx));
}
const EVP_HPKE_KEM *EVP_HPKE_CTX_kem(const EVP_HPKE_CTX *ctx) {
return ctx->kem;
}
const EVP_HPKE_AEAD *EVP_HPKE_CTX_aead(const EVP_HPKE_CTX *ctx) {
return ctx->aead;
}
const EVP_HPKE_KDF *EVP_HPKE_CTX_kdf(const EVP_HPKE_CTX *ctx) {
return ctx->kdf;
}

View File

@@ -0,0 +1,628 @@
// Copyright (c) 2020, Google Inc.
// SPDX-License-Identifier: ISC
#include <openssl/hpke.h>
#include <cstdint>
#include <limits>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <openssl/base.h>
#include <openssl/curve25519.h>
#include <openssl/digest.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <openssl/span.h>
#include "../test/file_test.h"
#include "../test/test_util.h"
namespace bssl {
namespace {
const decltype(&EVP_hpke_aes_128_gcm) kAllAEADs[] = {
&EVP_hpke_aes_128_gcm,
&EVP_hpke_aes_256_gcm,
&EVP_hpke_chacha20_poly1305,
};
const decltype(&EVP_hpke_hkdf_sha256) kAllKDFs[] = {
&EVP_hpke_hkdf_sha256,
};
// HPKETestVector corresponds to one array member in the published
// test-vectors.json.
class HPKETestVector {
public:
explicit HPKETestVector() = default;
~HPKETestVector() = default;
bool ReadFromFileTest(FileTest *t);
void Verify() const {
const EVP_HPKE_KEM *kem = EVP_hpke_x25519_hkdf_sha256();
const EVP_HPKE_AEAD *aead = GetAEAD();
ASSERT_TRUE(aead);
const EVP_HPKE_KDF *kdf = GetKDF();
ASSERT_TRUE(kdf);
// Test the sender.
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[EVP_HPKE_MAX_ENC_LENGTH];
size_t enc_len = 0;
switch (mode_) {
case Mode::kBase:
ASSERT_TRUE(EVP_HPKE_CTX_setup_sender_with_seed_for_testing(
sender_ctx.get(), enc, &enc_len, sizeof(enc), kem, kdf, aead,
public_key_r_.data(), public_key_r_.size(), info_.data(),
info_.size(), secret_key_e_.data(), secret_key_e_.size()));
break;
case Mode::kAuth: {
ScopedEVP_HPKE_KEY sender_key;
ASSERT_TRUE(EVP_HPKE_KEY_init(
sender_key.get(), kem, secret_key_s_.data(), secret_key_s_.size()));
ASSERT_TRUE(EVP_HPKE_CTX_setup_auth_sender_with_seed_for_testing(
sender_ctx.get(), enc, &enc_len, sizeof(enc), sender_key.get(), kdf,
aead, public_key_r_.data(), public_key_r_.size(), info_.data(),
info_.size(), secret_key_e_.data(), secret_key_e_.size()));
break;
}
}
EXPECT_EQ(Bytes(enc, enc_len), Bytes(public_key_e_));
VerifySender(sender_ctx.get());
// Test the recipient.
ScopedEVP_HPKE_KEY base_key;
ASSERT_TRUE(EVP_HPKE_KEY_init(base_key.get(), kem, secret_key_r_.data(),
secret_key_r_.size()));
enum class CopyMode { kOriginal, kCopy, kMove };
for (CopyMode copy :
{CopyMode::kOriginal, CopyMode::kCopy, CopyMode::kMove}) {
SCOPED_TRACE(static_cast<int>(copy));
const EVP_HPKE_KEY *key = base_key.get();
ScopedEVP_HPKE_KEY key_copy;
switch (copy) {
case CopyMode::kOriginal:
break;
case CopyMode::kCopy:
ASSERT_TRUE(EVP_HPKE_KEY_copy(key_copy.get(), base_key.get()));
key = key_copy.get();
break;
case CopyMode::kMove:
EVP_HPKE_KEY_move(key_copy.get(), base_key.get());
key = key_copy.get();
break;
}
uint8_t public_key[EVP_HPKE_MAX_PUBLIC_KEY_LENGTH];
size_t public_key_len;
ASSERT_TRUE(EVP_HPKE_KEY_public_key(key, public_key, &public_key_len,
sizeof(public_key)));
EXPECT_EQ(Bytes(public_key, public_key_len), Bytes(public_key_r_));
uint8_t private_key[EVP_HPKE_MAX_PRIVATE_KEY_LENGTH];
size_t private_key_len;
ASSERT_TRUE(EVP_HPKE_KEY_private_key(key, private_key, &private_key_len,
sizeof(private_key)));
EXPECT_EQ(Bytes(private_key, private_key_len), Bytes(secret_key_r_));
// Set up the recipient.
ScopedEVP_HPKE_CTX recipient_ctx;
switch (mode_) {
case Mode::kBase:
ASSERT_TRUE(EVP_HPKE_CTX_setup_recipient(recipient_ctx.get(), key,
kdf, aead, enc, enc_len,
info_.data(), info_.size()));
break;
case Mode::kAuth:
ASSERT_TRUE(EVP_HPKE_CTX_setup_auth_recipient(
recipient_ctx.get(), key, kdf, aead, enc, enc_len, info_.data(),
info_.size(), public_key_s_.data(), public_key_s_.size()));
break;
}
VerifyRecipient(recipient_ctx.get());
}
}
private:
const EVP_HPKE_AEAD *GetAEAD() const {
for (const auto aead : kAllAEADs) {
if (EVP_HPKE_AEAD_id(aead()) == aead_id_) {
return aead();
}
}
return nullptr;
}
const EVP_HPKE_KDF *GetKDF() const {
for (const auto kdf : kAllKDFs) {
if (EVP_HPKE_KDF_id(kdf()) == kdf_id_) {
return kdf();
}
}
return nullptr;
}
void VerifySender(EVP_HPKE_CTX *ctx) const {
for (const Encryption &task : encryptions_) {
std::vector<uint8_t> encrypted(task.plaintext.size() +
EVP_HPKE_CTX_max_overhead(ctx));
size_t encrypted_len;
ASSERT_TRUE(EVP_HPKE_CTX_seal(ctx, encrypted.data(), &encrypted_len,
encrypted.size(), task.plaintext.data(),
task.plaintext.size(), task.aad.data(),
task.aad.size()));
ASSERT_EQ(Bytes(encrypted.data(), encrypted_len), Bytes(task.ciphertext));
}
VerifyExports(ctx);
}
void VerifyRecipient(EVP_HPKE_CTX *ctx) const {
for (const Encryption &task : encryptions_) {
std::vector<uint8_t> decrypted(task.ciphertext.size());
size_t decrypted_len;
ASSERT_TRUE(EVP_HPKE_CTX_open(ctx, decrypted.data(), &decrypted_len,
decrypted.size(), task.ciphertext.data(),
task.ciphertext.size(), task.aad.data(),
task.aad.size()));
ASSERT_EQ(Bytes(decrypted.data(), decrypted_len), Bytes(task.plaintext));
}
VerifyExports(ctx);
}
void VerifyExports(EVP_HPKE_CTX *ctx) const {
for (const Export &task : exports_) {
std::vector<uint8_t> exported_secret(task.export_length);
ASSERT_TRUE(EVP_HPKE_CTX_export(
ctx, exported_secret.data(), exported_secret.size(),
task.exporter_context.data(), task.exporter_context.size()));
ASSERT_EQ(Bytes(exported_secret), Bytes(task.exported_value));
}
}
enum class Mode {
kBase = 0,
kAuth = 2,
};
struct Encryption {
std::vector<uint8_t> aad;
std::vector<uint8_t> ciphertext;
std::vector<uint8_t> plaintext;
};
struct Export {
std::vector<uint8_t> exporter_context;
size_t export_length;
std::vector<uint8_t> exported_value;
};
Mode mode_;
uint16_t kdf_id_;
uint16_t aead_id_;
std::vector<uint8_t> context_;
std::vector<uint8_t> info_;
std::vector<uint8_t> public_key_e_;
std::vector<uint8_t> secret_key_e_;
std::vector<uint8_t> public_key_r_;
std::vector<uint8_t> secret_key_r_;
std::vector<uint8_t> public_key_s_;
std::vector<uint8_t> secret_key_s_;
std::vector<Encryption> encryptions_;
std::vector<Export> exports_;
};
// Match FileTest's naming scheme for duplicated attribute names.
std::string BuildAttrName(const std::string &name, int iter) {
return iter == 1 ? name : name + "/" + std::to_string(iter);
}
// Parses |s| as an unsigned integer of type T and writes the value to |out|.
// Returns true on success. If the integer value exceeds the maximum T value,
// returns false.
template <typename T>
bool ParseIntSafe(T *out, const std::string &s) {
T value = 0;
for (char c : s) {
if (c < '0' || c > '9') {
return false;
}
if (value > (std::numeric_limits<T>::max() - (c - '0')) / 10) {
return false;
}
value = 10 * value + (c - '0');
}
*out = value;
return true;
}
// Read the |key| attribute from |file_test| and convert it to an integer.
template <typename T>
bool FileTestReadInt(FileTest *file_test, T *out, const std::string &key) {
std::string s;
return file_test->GetAttribute(&s, key) && ParseIntSafe(out, s);
}
bool HPKETestVector::ReadFromFileTest(FileTest *t) {
uint8_t mode = 0;
if (!FileTestReadInt(t, &mode, "mode") ||
!FileTestReadInt(t, &kdf_id_, "kdf_id") ||
!FileTestReadInt(t, &aead_id_, "aead_id") ||
!t->GetBytes(&info_, "info") ||
!t->GetBytes(&secret_key_r_, "skRm") ||
!t->GetBytes(&public_key_r_, "pkRm") ||
!t->GetBytes(&secret_key_e_, "skEm") ||
!t->GetBytes(&public_key_e_, "pkEm")) {
return false;
}
switch (mode) {
case static_cast<int>(Mode::kBase):
mode_ = Mode::kBase;
break;
case static_cast<int>(Mode::kAuth):
mode_ = Mode::kAuth;
if (!t->GetBytes(&secret_key_s_, "skSm") ||
!t->GetBytes(&public_key_s_, "pkSm")) {
return false;
}
break;
default:
return false;
}
for (int i = 1; t->HasAttribute(BuildAttrName("aad", i)); i++) {
Encryption encryption;
if (!t->GetBytes(&encryption.aad, BuildAttrName("aad", i)) ||
!t->GetBytes(&encryption.ciphertext, BuildAttrName("ct", i)) ||
!t->GetBytes(&encryption.plaintext, BuildAttrName("pt", i))) {
return false;
}
encryptions_.push_back(std::move(encryption));
}
for (int i = 1; t->HasAttribute(BuildAttrName("exporter_context", i)); i++) {
Export exp;
if (!t->GetBytes(&exp.exporter_context,
BuildAttrName("exporter_context", i)) ||
!FileTestReadInt(t, &exp.export_length, BuildAttrName("L", i)) ||
!t->GetBytes(&exp.exported_value, BuildAttrName("exported_value", i))) {
return false;
}
exports_.push_back(std::move(exp));
}
return true;
}
} // namespace
TEST(HPKETest, VerifyTestVectors) {
FileTestGTest("crypto/hpke/hpke_test_vectors.txt", [](FileTest *t) {
HPKETestVector test_vec;
EXPECT_TRUE(test_vec.ReadFromFileTest(t));
test_vec.Verify();
});
}
// The test vectors used fixed sender ephemeral keys, while HPKE itself
// generates new keys for each context. Test this codepath by checking we can
// decrypt our own messages.
TEST(HPKETest, RoundTrip) {
const uint8_t info_a[] = {1, 1, 2, 3, 5, 8};
const uint8_t info_b[] = {42, 42, 42};
const uint8_t ad_a[] = {1, 2, 4, 8, 16};
const uint8_t ad_b[] = {7};
Span<const uint8_t> info_values[] = {{nullptr, 0}, info_a, info_b};
Span<const uint8_t> ad_values[] = {{nullptr, 0}, ad_a, ad_b};
const EVP_HPKE_KEM *kem = EVP_hpke_x25519_hkdf_sha256();
// Generate the recipient's keypair.
ScopedEVP_HPKE_KEY key;
ASSERT_TRUE(EVP_HPKE_KEY_generate(key.get(), kem));
uint8_t public_key_r[X25519_PUBLIC_VALUE_LEN];
size_t public_key_r_len;
ASSERT_TRUE(EVP_HPKE_KEY_public_key(key.get(), public_key_r,
&public_key_r_len, sizeof(public_key_r)));
// Generate the sender's keypair, for auth modes.
ScopedEVP_HPKE_KEY sender_key;
ASSERT_TRUE(
EVP_HPKE_KEY_generate(sender_key.get(), kem));
uint8_t public_key_s[X25519_PUBLIC_VALUE_LEN];
size_t public_key_s_len;
ASSERT_TRUE(EVP_HPKE_KEY_public_key(sender_key.get(), public_key_s,
&public_key_s_len, sizeof(public_key_r)));
for (const auto kdf : kAllKDFs) {
SCOPED_TRACE(EVP_HPKE_KDF_id(kdf()));
for (const auto aead : kAllAEADs) {
SCOPED_TRACE(EVP_HPKE_AEAD_id(aead()));
for (const Span<const uint8_t> &info : info_values) {
SCOPED_TRACE(Bytes(info));
for (const Span<const uint8_t> &ad : ad_values) {
SCOPED_TRACE(Bytes(ad));
auto check_messages = [&](EVP_HPKE_CTX *sender_ctx,
EVP_HPKE_CTX *recipient_ctx) {
const char kCleartextPayload[] = "foobar";
// Have sender encrypt message for the recipient.
std::vector<uint8_t> ciphertext(
sizeof(kCleartextPayload) +
EVP_HPKE_CTX_max_overhead(sender_ctx));
size_t ciphertext_len;
ASSERT_TRUE(EVP_HPKE_CTX_seal(
sender_ctx, ciphertext.data(), &ciphertext_len,
ciphertext.size(),
reinterpret_cast<const uint8_t *>(kCleartextPayload),
sizeof(kCleartextPayload), ad.data(), ad.size()));
// Have recipient decrypt the message.
std::vector<uint8_t> cleartext(ciphertext.size());
size_t cleartext_len;
ASSERT_TRUE(EVP_HPKE_CTX_open(recipient_ctx, cleartext.data(),
&cleartext_len, cleartext.size(),
ciphertext.data(), ciphertext_len,
ad.data(), ad.size()));
// Verify that decrypted message matches the original.
ASSERT_EQ(Bytes(cleartext.data(), cleartext_len),
Bytes(kCleartextPayload, sizeof(kCleartextPayload)));
};
// Test the base mode.
{
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN];
size_t enc_len;
ASSERT_TRUE(EVP_HPKE_CTX_setup_sender(
sender_ctx.get(), enc, &enc_len, sizeof(enc), kem, kdf(),
aead(), public_key_r, public_key_r_len, info.data(),
info.size()));
ScopedEVP_HPKE_CTX recipient_ctx;
ASSERT_TRUE(EVP_HPKE_CTX_setup_recipient(
recipient_ctx.get(), key.get(), kdf(), aead(), enc, enc_len,
info.data(), info.size()));
check_messages(sender_ctx.get(), recipient_ctx.get());
}
// Test the auth mode.
{
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN];
size_t enc_len;
ASSERT_TRUE(EVP_HPKE_CTX_setup_auth_sender(
sender_ctx.get(), enc, &enc_len, sizeof(enc), sender_key.get(),
kdf(), aead(), public_key_r, public_key_r_len, info.data(),
info.size()));
ScopedEVP_HPKE_CTX recipient_ctx;
ASSERT_TRUE(EVP_HPKE_CTX_setup_auth_recipient(
recipient_ctx.get(), key.get(), kdf(), aead(), enc, enc_len,
info.data(), info.size(), public_key_s, public_key_s_len));
check_messages(sender_ctx.get(), recipient_ctx.get());
}
}
}
}
}
}
// Verify that the DH operations inside Encap() and Decap() both fail when the
// public key is on a small-order point in the curve.
TEST(HPKETest, X25519EncapSmallOrderPoint) {
// Borrowed from X25519Test.SmallOrder.
static const uint8_t kSmallOrderPoint[32] = {
0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3,
0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32,
0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8,
};
static const uint8_t kValidPoint[32] = {
0xe6, 0xdb, 0x68, 0x67, 0x58, 0x30, 0x30, 0xdb, 0x35, 0x94, 0xc1,
0xa4, 0x24, 0xb1, 0x5f, 0x7c, 0x72, 0x66, 0x24, 0xec, 0x26, 0xb3,
0x35, 0x3b, 0x10, 0xa9, 0x03, 0xa6, 0xd0, 0xab, 0x1c, 0x4c,
};
ScopedEVP_HPKE_KEY key;
ASSERT_TRUE(EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256()));
for (const auto kdf : kAllKDFs) {
SCOPED_TRACE(EVP_HPKE_KDF_id(kdf()));
for (const auto aead : kAllAEADs) {
SCOPED_TRACE(EVP_HPKE_AEAD_id(aead()));
// Set up the sender, passing in kSmallOrderPoint as |peer_public_key|.
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN];
size_t enc_len;
EXPECT_FALSE(EVP_HPKE_CTX_setup_sender(
sender_ctx.get(), enc, &enc_len, sizeof(enc),
EVP_hpke_x25519_hkdf_sha256(), kdf(), aead(), kSmallOrderPoint,
sizeof(kSmallOrderPoint), nullptr, 0));
// Likewise with auth.
EXPECT_FALSE(EVP_HPKE_CTX_setup_auth_sender(
sender_ctx.get(), enc, &enc_len, sizeof(enc), key.get(), kdf(),
aead(), kSmallOrderPoint, sizeof(kSmallOrderPoint), nullptr, 0));
// Set up the recipient, passing in kSmallOrderPoint as |enc|.
ScopedEVP_HPKE_CTX recipient_ctx;
EXPECT_FALSE(EVP_HPKE_CTX_setup_recipient(
recipient_ctx.get(), key.get(), kdf(), aead(), kSmallOrderPoint,
sizeof(kSmallOrderPoint), nullptr, 0));
// Likewise with auth. With auth, a small-order point could appear as
// either |enc| or the peer public key.
EXPECT_FALSE(EVP_HPKE_CTX_setup_auth_recipient(
recipient_ctx.get(), key.get(), kdf(), aead(), kSmallOrderPoint,
sizeof(kSmallOrderPoint), nullptr, 0, kValidPoint,
sizeof(kValidPoint)));
EXPECT_FALSE(EVP_HPKE_CTX_setup_auth_recipient(
recipient_ctx.get(), key.get(), kdf(), aead(), kValidPoint,
sizeof(kValidPoint), nullptr, 0, kSmallOrderPoint,
sizeof(kSmallOrderPoint)));
}
}
}
// Test that Seal() fails when the context has been initialized as a recipient.
TEST(HPKETest, RecipientInvalidSeal) {
const uint8_t kMockEnc[X25519_PUBLIC_VALUE_LEN] = {0xff};
const char kCleartextPayload[] = "foobar";
ScopedEVP_HPKE_KEY key;
ASSERT_TRUE(EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256()));
// Set up the recipient.
ScopedEVP_HPKE_CTX recipient_ctx;
ASSERT_TRUE(EVP_HPKE_CTX_setup_recipient(
recipient_ctx.get(), key.get(), EVP_hpke_hkdf_sha256(),
EVP_hpke_aes_128_gcm(), kMockEnc, sizeof(kMockEnc), nullptr, 0));
// Call Seal() on the recipient.
size_t ciphertext_len;
uint8_t ciphertext[100];
ASSERT_FALSE(EVP_HPKE_CTX_seal(
recipient_ctx.get(), ciphertext, &ciphertext_len, sizeof(ciphertext),
reinterpret_cast<const uint8_t *>(kCleartextPayload),
sizeof(kCleartextPayload), nullptr, 0));
}
// Test that Open() fails when the context has been initialized as a sender.
TEST(HPKETest, SenderInvalidOpen) {
const uint8_t kMockCiphertext[100] = {0xff};
const size_t kMockCiphertextLen = 80;
// Generate the recipient's keypair.
uint8_t secret_key_r[X25519_PRIVATE_KEY_LEN];
uint8_t public_key_r[X25519_PUBLIC_VALUE_LEN];
X25519_keypair(public_key_r, secret_key_r);
// Set up the sender.
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN];
size_t enc_len;
ASSERT_TRUE(EVP_HPKE_CTX_setup_sender(
sender_ctx.get(), enc, &enc_len, sizeof(enc),
EVP_hpke_x25519_hkdf_sha256(), EVP_hpke_hkdf_sha256(),
EVP_hpke_aes_128_gcm(), public_key_r, sizeof(public_key_r), nullptr, 0));
// Call Open() on the sender.
uint8_t cleartext[128];
size_t cleartext_len;
ASSERT_FALSE(EVP_HPKE_CTX_open(sender_ctx.get(), cleartext, &cleartext_len,
sizeof(cleartext), kMockCiphertext,
kMockCiphertextLen, nullptr, 0));
}
TEST(HPKETest, SetupSenderBufferTooSmall) {
uint8_t secret_key_r[X25519_PRIVATE_KEY_LEN];
uint8_t public_key_r[X25519_PUBLIC_VALUE_LEN];
X25519_keypair(public_key_r, secret_key_r);
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN - 1];
size_t enc_len;
ASSERT_FALSE(EVP_HPKE_CTX_setup_sender(
sender_ctx.get(), enc, &enc_len, sizeof(enc),
EVP_hpke_x25519_hkdf_sha256(), EVP_hpke_hkdf_sha256(),
EVP_hpke_aes_128_gcm(), public_key_r, sizeof(public_key_r), nullptr, 0));
EXPECT_TRUE(
ErrorEquals(ERR_get_error(), ERR_LIB_EVP, EVP_R_INVALID_BUFFER_SIZE));
ERR_clear_error();
}
TEST(HPKETest, SetupSenderBufferTooLarge) {
uint8_t secret_key_r[X25519_PRIVATE_KEY_LEN];
uint8_t public_key_r[X25519_PUBLIC_VALUE_LEN];
X25519_keypair(public_key_r, secret_key_r);
// Too large of an output buffer is fine because the function reports the
// actual length.
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN + 1];
size_t enc_len;
EXPECT_TRUE(EVP_HPKE_CTX_setup_sender(
sender_ctx.get(), enc, &enc_len, sizeof(enc),
EVP_hpke_x25519_hkdf_sha256(), EVP_hpke_hkdf_sha256(),
EVP_hpke_aes_128_gcm(), public_key_r, sizeof(public_key_r), nullptr, 0));
EXPECT_EQ(size_t{X25519_PUBLIC_VALUE_LEN}, enc_len);
}
TEST(HPKETest, SetupRecipientWrongLengthEnc) {
ScopedEVP_HPKE_KEY key;
ASSERT_TRUE(EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256()));
const uint8_t bogus_enc[X25519_PUBLIC_VALUE_LEN + 5] = {0xff};
ScopedEVP_HPKE_CTX recipient_ctx;
ASSERT_FALSE(EVP_HPKE_CTX_setup_recipient(
recipient_ctx.get(), key.get(), EVP_hpke_hkdf_sha256(),
EVP_hpke_aes_128_gcm(), bogus_enc, sizeof(bogus_enc), nullptr, 0));
EXPECT_TRUE(
ErrorEquals(ERR_get_error(), ERR_LIB_EVP, EVP_R_INVALID_PEER_KEY));
ERR_clear_error();
}
TEST(HPKETest, SetupSenderWrongLengthPeerPublicValue) {
const uint8_t bogus_public_key_r[X25519_PRIVATE_KEY_LEN + 5] = {0xff};
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN];
size_t enc_len;
ASSERT_FALSE(EVP_HPKE_CTX_setup_sender(
sender_ctx.get(), enc, &enc_len, sizeof(enc),
EVP_hpke_x25519_hkdf_sha256(), EVP_hpke_hkdf_sha256(),
EVP_hpke_aes_128_gcm(), bogus_public_key_r, sizeof(bogus_public_key_r),
nullptr, 0));
EXPECT_TRUE(
ErrorEquals(ERR_get_error(), ERR_LIB_EVP, EVP_R_INVALID_PEER_KEY));
ERR_clear_error();
}
TEST(HPKETest, InvalidRecipientKey) {
const uint8_t private_key[X25519_PUBLIC_VALUE_LEN + 5] = {0xff};
ScopedEVP_HPKE_KEY key;
EXPECT_FALSE(EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(),
private_key, sizeof(private_key)));
}
TEST(HPKETest, InternalParseIntSafe) {
uint8_t u8 = 0xff;
ASSERT_FALSE(ParseIntSafe(&u8, "-1"));
ASSERT_TRUE(ParseIntSafe(&u8, "0"));
ASSERT_EQ(u8, 0);
ASSERT_TRUE(ParseIntSafe(&u8, "255"));
ASSERT_EQ(u8, 255);
ASSERT_FALSE(ParseIntSafe(&u8, "256"));
uint16_t u16 = 0xffff;
ASSERT_TRUE(ParseIntSafe(&u16, "257"));
ASSERT_EQ(u16, 257);
ASSERT_TRUE(ParseIntSafe(&u16, "65535"));
ASSERT_EQ(u16, 65535);
ASSERT_FALSE(ParseIntSafe(&u16, "65536"));
}
} // namespace bssl