blob: b08a6a5cd8aefb7e42f58b526b00a890a410ea36 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <memory>
#include "blapi.h"
#include "gtest/gtest.h"
#include "nss.h"
#include "nss_scoped_ptrs.h"
#include "pk11hpke.h"
#include "pk11pub.h"
#include "secerr.h"
#include "sechash.h"
#include "util.h"
extern std::string g_source_dir;
namespace nss_test {
/* See note in pk11pub.h. */
#include "cpputil.h"
class HpkeTest {
protected:
void CheckEquality(const std::vector<uint8_t> &expected, SECItem *actual) {
if (!actual) {
EXPECT_TRUE(expected.empty());
return;
}
std::vector<uint8_t> vact(actual->data, actual->data + actual->len);
EXPECT_EQ(expected, vact);
}
void CheckEquality(SECItem *expected, SECItem *actual) {
EXPECT_EQ(!!expected, !!actual);
if (expected && actual) {
EXPECT_EQ(expected->len, actual->len);
if (expected->len == actual->len) {
EXPECT_EQ(0, memcmp(expected->data, actual->data, actual->len));
}
}
}
void CheckEquality(const std::vector<uint8_t> &expected, PK11SymKey *actual) {
if (!actual) {
EXPECT_TRUE(expected.empty());
return;
}
SECStatus rv = PK11_ExtractKeyValue(actual);
EXPECT_EQ(SECSuccess, rv);
if (rv != SECSuccess) {
return;
}
SECItem *rawkey = PK11_GetKeyData(actual);
CheckEquality(expected, rawkey);
}
void CheckEquality(PK11SymKey *expected, PK11SymKey *actual) {
if (!actual || !expected) {
EXPECT_EQ(!!expected, !!actual);
return;
}
SECStatus rv = PK11_ExtractKeyValue(expected);
EXPECT_EQ(SECSuccess, rv);
if (rv != SECSuccess) {
return;
}
SECItem *raw = PK11_GetKeyData(expected);
ASSERT_NE(nullptr, raw);
ASSERT_NE(nullptr, raw->data);
std::vector<uint8_t> expected_vec(raw->data, raw->data + raw->len);
CheckEquality(expected_vec, actual);
}
void Seal(const ScopedHpkeContext &cx, const std::vector<uint8_t> &aad_vec,
const std::vector<uint8_t> &pt_vec,
std::vector<uint8_t> *out_sealed) {
SECItem aad_item = {siBuffer, toUcharPtr(aad_vec.data()),
static_cast<unsigned int>(aad_vec.size())};
SECItem pt_item = {siBuffer, toUcharPtr(pt_vec.data()),
static_cast<unsigned int>(pt_vec.size())};
SECItem *sealed_item = nullptr;
EXPECT_EQ(SECSuccess,
PK11_HPKE_Seal(cx.get(), &aad_item, &pt_item, &sealed_item));
ASSERT_NE(nullptr, sealed_item);
ScopedSECItem sealed(sealed_item);
out_sealed->assign(sealed->data, sealed->data + sealed->len);
}
void Open(const ScopedHpkeContext &cx, const std::vector<uint8_t> &aad_vec,
const std::vector<uint8_t> &ct_vec,
std::vector<uint8_t> *out_opened) {
SECItem aad_item = {siBuffer, toUcharPtr(aad_vec.data()),
static_cast<unsigned int>(aad_vec.size())};
SECItem ct_item = {siBuffer, toUcharPtr(ct_vec.data()),
static_cast<unsigned int>(ct_vec.size())};
SECItem *opened_item = nullptr;
EXPECT_EQ(SECSuccess,
PK11_HPKE_Open(cx.get(), &aad_item, &ct_item, &opened_item));
ASSERT_NE(nullptr, opened_item);
ScopedSECItem opened(opened_item);
out_opened->assign(opened->data, opened->data + opened->len);
}
void SealOpen(const ScopedHpkeContext &sender,
const ScopedHpkeContext &receiver,
const std::vector<uint8_t> &msg,
const std::vector<uint8_t> &aad,
const std::vector<uint8_t> *expect) {
std::vector<uint8_t> sealed;
std::vector<uint8_t> opened;
Seal(sender, aad, msg, &sealed);
if (expect) {
EXPECT_EQ(*expect, sealed);
}
Open(receiver, aad, sealed, &opened);
EXPECT_EQ(msg, opened);
}
void ExportSecret(const ScopedHpkeContext &receiver,
ScopedPK11SymKey &exported) {
std::vector<uint8_t> context = {'c', 't', 'x', 't'};
SECItem context_item = {siBuffer, context.data(),
static_cast<unsigned int>(context.size())};
PK11SymKey *tmp_exported = nullptr;
ASSERT_EQ(SECSuccess, PK11_HPKE_ExportSecret(receiver.get(), &context_item,
64, &tmp_exported));
exported.reset(tmp_exported);
}
void ExportImportRecvContext(ScopedHpkeContext &scoped_cx,
PK11SymKey *wrapping_key) {
SECItem *tmp_exported = nullptr;
EXPECT_EQ(SECSuccess, PK11_HPKE_ExportContext(scoped_cx.get(), wrapping_key,
&tmp_exported));
EXPECT_NE(nullptr, tmp_exported);
ScopedSECItem context(tmp_exported);
scoped_cx.reset();
HpkeContext *tmp_imported =
PK11_HPKE_ImportContext(context.get(), wrapping_key);
EXPECT_NE(nullptr, tmp_imported);
scoped_cx.reset(tmp_imported);
}
bool GenerateKeyPair(ScopedSECKEYPublicKey &pub_key,
ScopedSECKEYPrivateKey &priv_key) {
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
if (!slot) {
ADD_FAILURE() << "Couldn't get slot";
return false;
}
unsigned char param_buf[65];
SECItem ecdsa_params = {siBuffer, param_buf, sizeof(param_buf)};
SECOidData *oid_data = SECOID_FindOIDByTag(SEC_OID_CURVE25519);
if (!oid_data) {
ADD_FAILURE() << "Couldn't get oid_data";
return false;
}
ecdsa_params.data[0] = SEC_ASN1_OBJECT_ID;
ecdsa_params.data[1] = oid_data->oid.len;
memcpy(ecdsa_params.data + 2, oid_data->oid.data, oid_data->oid.len);
ecdsa_params.len = oid_data->oid.len + 2;
SECKEYPublicKey *pub_tmp;
SECKEYPrivateKey *priv_tmp;
priv_tmp =
PK11_GenerateKeyPair(slot.get(), CKM_EC_KEY_PAIR_GEN, &ecdsa_params,
&pub_tmp, PR_FALSE, PR_TRUE, nullptr);
if (!pub_tmp || !priv_tmp) {
ADD_FAILURE() << "PK11_GenerateKeyPair failed";
return false;
}
pub_key.reset(pub_tmp);
priv_key.reset(priv_tmp);
return true;
}
void SetUpEphemeralContexts(ScopedHpkeContext &sender,
ScopedHpkeContext &receiver,
HpkeModeId mode = HpkeModeBase,
HpkeKemId kem = HpkeDhKemX25519Sha256,
HpkeKdfId kdf = HpkeKdfHkdfSha256,
HpkeAeadId aead = HpkeAeadAes128Gcm) {
// Generate a PSK, if the mode calls for it.
PRUint8 psk_id_buf[] = {'p', 's', 'k', '-', 'i', 'd'};
SECItem psk_id = {siBuffer, psk_id_buf, sizeof(psk_id_buf)};
SECItem *psk_id_item = (mode == HpkeModePsk) ? &psk_id : nullptr;
ScopedPK11SymKey psk;
if (mode == HpkeModePsk) {
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
ASSERT_TRUE(slot);
PK11SymKey *tmp_psk =
PK11_KeyGen(slot.get(), CKM_HKDF_DERIVE, nullptr, 16, nullptr);
ASSERT_NE(nullptr, tmp_psk);
psk.reset(tmp_psk);
}
std::vector<uint8_t> info = {'t', 'e', 's', 't', '-', 'i', 'n', 'f', 'o'};
SECItem info_item = {siBuffer, info.data(),
static_cast<unsigned int>(info.size())};
sender.reset(PK11_HPKE_NewContext(kem, kdf, aead, psk.get(), psk_id_item));
receiver.reset(
PK11_HPKE_NewContext(kem, kdf, aead, psk.get(), psk_id_item));
ASSERT_TRUE(sender);
ASSERT_TRUE(receiver);
ScopedSECKEYPublicKey pub_key_r;
ScopedSECKEYPrivateKey priv_key_r;
ASSERT_TRUE(GenerateKeyPair(pub_key_r, priv_key_r));
EXPECT_EQ(SECSuccess, PK11_HPKE_SetupS(sender.get(), nullptr, nullptr,
pub_key_r.get(), &info_item));
const SECItem *enc = PK11_HPKE_GetEncapPubKey(sender.get());
EXPECT_NE(nullptr, enc);
EXPECT_EQ(SECSuccess, PK11_HPKE_SetupR(
receiver.get(), pub_key_r.get(), priv_key_r.get(),
const_cast<SECItem *>(enc), &info_item));
}
};
// If we make a few assumptions about the file, parsing JSON can be easy.
// This is not a full parser, it only works on a narrow set of inputs.
class JsonReader {
public:
JsonReader(const std::string &n) : buf_(), available_(0), i_(0) {
f_.reset(PR_Open(n.c_str(), PR_RDONLY, 00600));
EXPECT_TRUE(f_) << "error opening vectors from: " << n;
buf_[0] = 0;
}
void next() { i_++; }
uint8_t peek() {
TopUp();
return buf_[i_];
}
uint8_t take() {
uint8_t v = peek();
next();
return v;
}
// No input checking, overflow protection, or any safety.
// Returns 0 if there isn't a number here rather than aborting.
uint64_t ReadInt() {
SkipWhitespace();
uint8_t c = peek();
uint64_t v = 0;
while (c >= '0' && c <= '9') {
v = v * 10 + c - '0';
next();
c = peek();
}
return v;
}
// No input checking, no unicode, no escaping (not even \"), just read ASCII.
std::string ReadLabel() {
SkipWhitespace();
if (peek() != '"') {
return "";
}
next();
std::string s;
uint8_t c = take();
while (c != '"') {
s.push_back(c);
c = take();
}
SkipWhitespace();
EXPECT_EQ(take(), ':');
return s;
}
std::vector<uint8_t> ReadHex() {
SkipWhitespace();
uint8_t c = take();
EXPECT_EQ(c, '"');
std::vector<uint8_t> v;
c = take();
while (c != '"') {
v.push_back(JsonReader::Hex(c) << 4 | JsonReader::Hex(take()));
c = take();
}
return v;
}
bool NextItem(uint8_t h = '{', uint8_t t = '}') {
SkipWhitespace();
switch (uint8_t c = take()) {
case ',':
return true;
case '{':
case '[':
EXPECT_EQ(c, h);
SkipWhitespace();
if (peek() == t) {
next();
return false;
}
return true;
case '}':
case ']':
EXPECT_EQ(c, t);
return false;
default:
ADD_FAILURE() << "Unexpected '" << c << "'";
}
return false;
}
void SkipValue() {
uint8_t c = take();
if (c == '"') {
do {
c = take();
} while (c != '"');
} else if (c >= '0' && c <= '9') {
c = peek();
while (c >= '0' && c <= '9') {
next();
c = peek();
}
} else {
ADD_FAILURE() << "No idea how to skip'" << c << "'";
}
}
private:
void TopUp() {
if (available_ > i_) {
return;
}
i_ = 0;
if (!f_) {
return;
}
PRInt32 res = PR_Read(f_.get(), buf_, sizeof(buf_));
if (res > 0) {
available_ = static_cast<size_t>(res);
} else {
available_ = 1;
f_.reset(nullptr);
buf_[0] = 0;
}
}
void SkipWhitespace() {
uint8_t c = peek();
while (c && (c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
next();
c = peek();
}
}
// This only handles lowercase.
uint8_t Hex(uint8_t c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
EXPECT_TRUE(c >= 'a' && c <= 'f');
return c - 'a' + 10;
}
ScopedPRFileDesc f_;
uint8_t buf_[4096];
size_t available_;
size_t i_;
};
struct HpkeEncryptVector {
std::vector<uint8_t> pt;
std::vector<uint8_t> aad;
std::vector<uint8_t> ct;
static std::vector<HpkeEncryptVector> ReadVec(JsonReader &r) {
std::vector<HpkeEncryptVector> all;
while (r.NextItem('[', ']')) {
HpkeEncryptVector enc;
while (r.NextItem()) {
std::string n = r.ReadLabel();
if (n == "") {
break;
}
if (n == "plaintext") {
enc.pt = r.ReadHex();
} else if (n == "aad") {
enc.aad = r.ReadHex();
} else if (n == "ciphertext") {
enc.ct = r.ReadHex();
} else {
r.SkipValue();
}
}
all.push_back(enc);
}
return all;
}
};
struct HpkeExportVector {
std::vector<uint8_t> ctxt;
size_t len;
std::vector<uint8_t> exported;
static std::vector<HpkeExportVector> ReadVec(JsonReader &r) {
std::vector<HpkeExportVector> all;
while (r.NextItem('[', ']')) {
HpkeExportVector exp;
while (r.NextItem()) {
std::string n = r.ReadLabel();
if (n == "") {
break;
}
if (n == "exporter_context") {
exp.ctxt = r.ReadHex();
} else if (n == "L") {
exp.len = r.ReadInt();
} else if (n == "exported_value") {
exp.exported = r.ReadHex();
} else {
r.SkipValue();
}
}
all.push_back(exp);
}
return all;
}
};
struct HpkeVector {
uint32_t test_id;
HpkeModeId mode;
HpkeKemId kem_id;
HpkeKdfId kdf_id;
HpkeAeadId aead_id;
std::vector<uint8_t> info;
std::vector<uint8_t> pkcs8_e;
std::vector<uint8_t> pkcs8_r;
std::vector<uint8_t> psk;
std::vector<uint8_t> psk_id;
std::vector<uint8_t> enc;
std::vector<uint8_t> key;
std::vector<uint8_t> nonce;
std::vector<HpkeEncryptVector> encryptions;
std::vector<HpkeExportVector> exports;
static std::vector<uint8_t> Pkcs8(const std::vector<uint8_t> &sk,
const std::vector<uint8_t> &pk) {
// Only X25519 format.
std::vector<uint8_t> v(105);
v.assign({
0x30, 0x67, 0x02, 0x01, 0x00, 0x30, 0x14, 0x06, 0x07, 0x2a, 0x86, 0x48,
0xce, 0x3d, 0x02, 0x01, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xda,
0x47, 0x0f, 0x01, 0x04, 0x4c, 0x30, 0x4a, 0x02, 0x01, 0x01, 0x04, 0x20,
});
v.insert(v.end(), sk.begin(), sk.end());
v.insert(v.end(), {
0xa1, 0x23, 0x03, 0x21, 0x00,
});
v.insert(v.end(), pk.begin(), pk.end());
return v;
}
static std::vector<HpkeVector> Read(JsonReader &r) {
std::vector<HpkeVector> all_tests;
uint32_t test_id = 0;
while (r.NextItem('[', ']')) {
HpkeVector vec = {0};
uint32_t fields = 0;
enum class RequiredFields {
mode,
kem,
kdf,
aead,
skEm,
skRm,
pkEm,
pkRm,
all
};
std::vector<uint8_t> sk_e, pk_e, sk_r, pk_r;
test_id++;
while (r.NextItem()) {
std::string n = r.ReadLabel();
if (n == "") {
break;
}
if (n == "mode") {
vec.mode = static_cast<HpkeModeId>(r.ReadInt());
fields |= 1 << static_cast<uint32_t>(RequiredFields::mode);
} else if (n == "kem_id") {
vec.kem_id = static_cast<HpkeKemId>(r.ReadInt());
fields |= 1 << static_cast<uint32_t>(RequiredFields::kem);
} else if (n == "kdf_id") {
vec.kdf_id = static_cast<HpkeKdfId>(r.ReadInt());
fields |= 1 << static_cast<uint32_t>(RequiredFields::kdf);
} else if (n == "aead_id") {
vec.aead_id = static_cast<HpkeAeadId>(r.ReadInt());
fields |= 1 << static_cast<uint32_t>(RequiredFields::aead);
} else if (n == "info") {
vec.info = r.ReadHex();
} else if (n == "skEm") {
sk_e = r.ReadHex();
fields |= 1 << static_cast<uint32_t>(RequiredFields::skEm);
} else if (n == "pkEm") {
pk_e = r.ReadHex();
fields |= 1 << static_cast<uint32_t>(RequiredFields::pkEm);
} else if (n == "skRm") {
sk_r = r.ReadHex();
fields |= 1 << static_cast<uint32_t>(RequiredFields::skRm);
} else if (n == "pkRm") {
pk_r = r.ReadHex();
fields |= 1 << static_cast<uint32_t>(RequiredFields::pkRm);
} else if (n == "psk") {
vec.psk = r.ReadHex();
} else if (n == "psk_id") {
vec.psk_id = r.ReadHex();
} else if (n == "enc") {
vec.enc = r.ReadHex();
} else if (n == "key") {
vec.key = r.ReadHex();
} else if (n == "base_nonce") {
vec.nonce = r.ReadHex();
} else if (n == "encryptions") {
vec.encryptions = HpkeEncryptVector::ReadVec(r);
} else if (n == "exports") {
vec.exports = HpkeExportVector::ReadVec(r);
} else {
r.SkipValue();
}
}
if (fields != (1 << static_cast<uint32_t>(RequiredFields::all)) - 1) {
std::cerr << "Skipping entry " << test_id << " for missing fields"
<< std::endl;
continue;
}
// Skip modes and configurations we don't support.
if (vec.mode != HpkeModeBase && vec.mode != HpkeModePsk) {
continue;
}
SECStatus rv =
PK11_HPKE_ValidateParameters(vec.kem_id, vec.kdf_id, vec.aead_id);
if (rv != SECSuccess) {
continue;
}
vec.test_id = test_id;
vec.pkcs8_e = HpkeVector::Pkcs8(sk_e, pk_e);
vec.pkcs8_r = HpkeVector::Pkcs8(sk_r, pk_r);
all_tests.push_back(vec);
}
return all_tests;
}
};
class TestVectors : public HpkeTest, public ::testing::Test {
struct Endpoint {
bool init(const HpkeVector &vec, const std::vector<uint8_t> &sk_data) {
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
if (!slot) {
ADD_FAILURE() << "No slot";
return false;
}
cx_ = Endpoint::MakeContext(slot, vec);
SECItem item = {siBuffer, toUcharPtr(sk_data.data()),
static_cast<unsigned int>(sk_data.size())};
SECKEYPrivateKey *sk = nullptr;
SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey(
slot.get(), &item, nullptr, nullptr, false, false, KU_ALL, &sk,
nullptr);
if (rv != SECSuccess) {
ADD_FAILURE() << "Failed to import secret";
return false;
}
sk_.reset(sk);
SECKEYPublicKey *pk = SECKEY_ConvertToPublicKey(sk_.get());
pk_.reset(pk);
return cx_ && sk_ && pk_;
}
static ScopedHpkeContext MakeContext(const ScopedPK11SlotInfo &slot,
const HpkeVector &vec) {
ScopedPK11SymKey psk = Endpoint::ReadPsk(slot, vec);
SECItem psk_id_item = {siBuffer, toUcharPtr(vec.psk_id.data()),
static_cast<unsigned int>(vec.psk_id.size())};
SECItem *psk_id = psk ? &psk_id_item : nullptr;
return ScopedHpkeContext(PK11_HPKE_NewContext(
vec.kem_id, vec.kdf_id, vec.aead_id, psk.get(), psk_id));
}
static ScopedPK11SymKey ReadPsk(const ScopedPK11SlotInfo &slot,
const HpkeVector &vec) {
ScopedPK11SymKey psk;
if (!vec.psk.empty()) {
SECItem psk_item = {siBuffer, toUcharPtr(vec.psk.data()),
static_cast<unsigned int>(vec.psk.size())};
PK11SymKey *psk_key =
PK11_ImportSymKey(slot.get(), CKM_HKDF_KEY_GEN, PK11_OriginUnwrap,
CKA_WRAP, &psk_item, nullptr);
EXPECT_NE(nullptr, psk_key);
psk.reset(psk_key);
}
return psk;
}
ScopedHpkeContext cx_;
ScopedSECKEYPublicKey pk_;
ScopedSECKEYPrivateKey sk_;
};
protected:
void TestExports(const HpkeVector &vec, const Endpoint &sender,
const Endpoint &receiver) {
for (auto &exp : vec.exports) {
SECItem context_item = {siBuffer, toUcharPtr(exp.ctxt.data()),
static_cast<unsigned int>(exp.ctxt.size())};
PK11SymKey *actual_r = nullptr;
PK11SymKey *actual_s = nullptr;
ASSERT_EQ(SECSuccess,
PK11_HPKE_ExportSecret(sender.cx_.get(), &context_item, exp.len,
&actual_s));
ASSERT_EQ(SECSuccess,
PK11_HPKE_ExportSecret(receiver.cx_.get(), &context_item,
exp.len, &actual_r));
ScopedPK11SymKey scoped_act_s(actual_s);
ScopedPK11SymKey scoped_act_r(actual_r);
CheckEquality(exp.exported, scoped_act_s.get());
CheckEquality(exp.exported, scoped_act_r.get());
}
}
void TestEncryptions(const HpkeVector &vec, const Endpoint &sender,
const Endpoint &receiver) {
for (auto &enc : vec.encryptions) {
SealOpen(sender.cx_, receiver.cx_, enc.pt, enc.aad, &enc.ct);
}
}
void SetupS(const ScopedHpkeContext &cx, const ScopedSECKEYPublicKey &pkE,
const ScopedSECKEYPrivateKey &skE,
const ScopedSECKEYPublicKey &pkR,
const std::vector<uint8_t> &info) {
SECItem info_item = {siBuffer, toUcharPtr(info.data()),
static_cast<unsigned int>(info.size())};
EXPECT_EQ(SECSuccess, PK11_HPKE_SetupS(cx.get(), pkE.get(), skE.get(),
pkR.get(), &info_item));
}
void SetupR(const ScopedHpkeContext &cx, const ScopedSECKEYPublicKey &pkR,
const ScopedSECKEYPrivateKey &skR,
const std::vector<uint8_t> &enc,
const std::vector<uint8_t> &info) {
SECItem enc_item = {siBuffer, toUcharPtr(enc.data()),
static_cast<unsigned int>(enc.size())};
SECItem info_item = {siBuffer, toUcharPtr(info.data()),
static_cast<unsigned int>(info.size())};
EXPECT_EQ(SECSuccess, PK11_HPKE_SetupR(cx.get(), pkR.get(), skR.get(),
&enc_item, &info_item));
}
void SetupSenderReceiver(const HpkeVector &vec, const Endpoint &sender,
const Endpoint &receiver) {
SetupS(sender.cx_, sender.pk_, sender.sk_, receiver.pk_, vec.info);
uint8_t buf[32]; // Curve25519 only, fixed size.
SECItem encap_item = {siBuffer, const_cast<uint8_t *>(buf), sizeof(buf)};
ASSERT_EQ(SECSuccess, PK11_HPKE_Serialize(sender.pk_.get(), encap_item.data,
&encap_item.len, encap_item.len));
CheckEquality(vec.enc, &encap_item);
SetupR(receiver.cx_, receiver.pk_, receiver.sk_, vec.enc, vec.info);
}
void RunTestVector(const HpkeVector &vec) {
Endpoint sender;
ASSERT_TRUE(sender.init(vec, vec.pkcs8_e));
Endpoint receiver;
ASSERT_TRUE(receiver.init(vec, vec.pkcs8_r));
SetupSenderReceiver(vec, sender, receiver);
TestEncryptions(vec, sender, receiver);
TestExports(vec, sender, receiver);
}
};
TEST_F(TestVectors, HpkeVectors) {
JsonReader r(::g_source_dir + "/hpke-vectors.json");
auto all_tests = HpkeVector::Read(r);
for (auto &vec : all_tests) {
std::cout << "HPKE vector " << vec.test_id << std::endl;
RunTestVector(vec);
}
}
class ModeParameterizedTest
: public HpkeTest,
public ::testing::TestWithParam<
std::tuple<HpkeModeId, HpkeKemId, HpkeKdfId, HpkeAeadId>> {};
static const HpkeModeId kHpkeModesAll[] = {HpkeModeBase, HpkeModePsk};
static const HpkeKemId kHpkeKemIdsAll[] = {HpkeDhKemX25519Sha256};
static const HpkeKdfId kHpkeKdfIdsAll[] = {HpkeKdfHkdfSha256, HpkeKdfHkdfSha384,
HpkeKdfHkdfSha512};
static const HpkeAeadId kHpkeAeadIdsAll[] = {HpkeAeadAes128Gcm,
HpkeAeadChaCha20Poly1305};
INSTANTIATE_TEST_SUITE_P(
Pk11Hpke, ModeParameterizedTest,
::testing::Combine(::testing::ValuesIn(kHpkeModesAll),
::testing::ValuesIn(kHpkeKemIdsAll),
::testing::ValuesIn(kHpkeKdfIdsAll),
::testing::ValuesIn(kHpkeAeadIdsAll)));
TEST_F(ModeParameterizedTest, BadEncapsulatedPubKey) {
ScopedHpkeContext sender(
PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256,
HpkeAeadAes128Gcm, nullptr, nullptr));
ScopedHpkeContext receiver(
PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256,
HpkeAeadAes128Gcm, nullptr, nullptr));
SECItem empty = {siBuffer, nullptr, 0};
uint8_t buf[100];
SECItem short_encap = {siBuffer, buf, 1};
SECItem long_encap = {siBuffer, buf, sizeof(buf)};
SECKEYPublicKey *tmp_pub_key;
ScopedSECKEYPublicKey pub_key;
ScopedSECKEYPrivateKey priv_key;
ASSERT_TRUE(GenerateKeyPair(pub_key, priv_key));
// Decapsulating an empty buffer should fail.
EXPECT_EQ(SECFailure, PK11_HPKE_Deserialize(sender.get(), empty.data,
empty.len, &tmp_pub_key));
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
// Decapsulating anything short will succeed, but the setup will fail.
EXPECT_EQ(SECSuccess, PK11_HPKE_Deserialize(sender.get(), short_encap.data,
short_encap.len, &tmp_pub_key));
ScopedSECKEYPublicKey bad_pub_key(tmp_pub_key);
EXPECT_EQ(SECFailure,
PK11_HPKE_SetupS(receiver.get(), pub_key.get(), priv_key.get(),
bad_pub_key.get(), &empty));
EXPECT_EQ(SEC_ERROR_INVALID_KEY, PORT_GetError());
// Test the same for a receiver.
EXPECT_EQ(SECFailure, PK11_HPKE_SetupR(sender.get(), pub_key.get(),
priv_key.get(), &empty, &empty));
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
EXPECT_EQ(SECFailure, PK11_HPKE_SetupR(sender.get(), pub_key.get(),
priv_key.get(), &short_encap, &empty));
EXPECT_EQ(SEC_ERROR_INVALID_KEY, PORT_GetError());
// Encapsulated key too long
EXPECT_EQ(SECSuccess, PK11_HPKE_Deserialize(sender.get(), long_encap.data,
long_encap.len, &tmp_pub_key));
bad_pub_key.reset(tmp_pub_key);
EXPECT_EQ(SECFailure,
PK11_HPKE_SetupS(receiver.get(), pub_key.get(), priv_key.get(),
bad_pub_key.get(), &empty));
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
EXPECT_EQ(SECFailure, PK11_HPKE_SetupR(sender.get(), pub_key.get(),
priv_key.get(), &long_encap, &empty));
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
}
TEST_P(ModeParameterizedTest, ContextExportImportEncrypt) {
std::vector<uint8_t> msg = {'s', 'e', 'c', 'r', 'e', 't'};
std::vector<uint8_t> aad = {'a', 'a', 'd'};
ScopedHpkeContext sender;
ScopedHpkeContext receiver;
SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()),
std::get<1>(GetParam()), std::get<2>(GetParam()),
std::get<3>(GetParam()));
SealOpen(sender, receiver, msg, aad, nullptr);
ExportImportRecvContext(receiver, nullptr);
SealOpen(sender, receiver, msg, aad, nullptr);
}
TEST_P(ModeParameterizedTest, ContextExportImportExport) {
ScopedHpkeContext sender;
ScopedHpkeContext receiver;
ScopedPK11SymKey sender_export;
ScopedPK11SymKey receiver_export;
ScopedPK11SymKey receiver_reexport;
SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()),
std::get<1>(GetParam()), std::get<2>(GetParam()),
std::get<3>(GetParam()));
ExportSecret(sender, sender_export);
ExportSecret(receiver, receiver_export);
CheckEquality(sender_export.get(), receiver_export.get());
ExportImportRecvContext(receiver, nullptr);
ExportSecret(receiver, receiver_reexport);
CheckEquality(receiver_export.get(), receiver_reexport.get());
}
TEST_P(ModeParameterizedTest, ContextExportImportWithWrap) {
std::vector<uint8_t> msg = {'s', 'e', 'c', 'r', 'e', 't'};
std::vector<uint8_t> aad = {'a', 'a', 'd'};
// Generate a wrapping key, then use it for export.
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
ASSERT_TRUE(slot);
ScopedPK11SymKey kek(
PK11_KeyGen(slot.get(), CKM_AES_CBC, nullptr, 16, nullptr));
ASSERT_NE(nullptr, kek);
ScopedHpkeContext sender;
ScopedHpkeContext receiver;
SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()),
std::get<1>(GetParam()), std::get<2>(GetParam()),
std::get<3>(GetParam()));
SealOpen(sender, receiver, msg, aad, nullptr);
ExportImportRecvContext(receiver, kek.get());
SealOpen(sender, receiver, msg, aad, nullptr);
}
TEST_P(ModeParameterizedTest, ExportSenderContext) {
std::vector<uint8_t> msg = {'s', 'e', 'c', 'r', 'e', 't'};
std::vector<uint8_t> aad = {'a', 'a', 'd'};
ScopedHpkeContext sender;
ScopedHpkeContext receiver;
SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()),
std::get<1>(GetParam()), std::get<2>(GetParam()),
std::get<3>(GetParam()));
SECItem *tmp_exported = nullptr;
EXPECT_EQ(SECFailure,
PK11_HPKE_ExportContext(sender.get(), nullptr, &tmp_exported));
EXPECT_EQ(nullptr, tmp_exported);
EXPECT_EQ(SEC_ERROR_NOT_A_RECIPIENT, PORT_GetError());
}
TEST_P(ModeParameterizedTest, ContextUnwrapBadKey) {
std::vector<uint8_t> msg = {'s', 'e', 'c', 'r', 'e', 't'};
std::vector<uint8_t> aad = {'a', 'a', 'd'};
// Generate a wrapping key, then use it for export.
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
ASSERT_TRUE(slot);
ScopedPK11SymKey kek(
PK11_KeyGen(slot.get(), CKM_AES_CBC, nullptr, 16, nullptr));
ASSERT_NE(nullptr, kek);
ScopedPK11SymKey not_kek(
PK11_KeyGen(slot.get(), CKM_AES_CBC, nullptr, 16, nullptr));
ASSERT_NE(nullptr, not_kek);
ScopedHpkeContext sender;
ScopedHpkeContext receiver;
SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()),
std::get<1>(GetParam()), std::get<2>(GetParam()),
std::get<3>(GetParam()));
SECItem *tmp_exported = nullptr;
EXPECT_EQ(SECSuccess,
PK11_HPKE_ExportContext(receiver.get(), kek.get(), &tmp_exported));
EXPECT_NE(nullptr, tmp_exported);
ScopedSECItem context(tmp_exported);
EXPECT_EQ(nullptr, PK11_HPKE_ImportContext(context.get(), not_kek.get()));
EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError());
}
TEST_P(ModeParameterizedTest, EphemeralKeys) {
std::vector<uint8_t> msg = {'s', 'e', 'c', 'r', 'e', 't'};
std::vector<uint8_t> aad = {'a', 'a', 'd'};
SECItem msg_item = {siBuffer, msg.data(),
static_cast<unsigned int>(msg.size())};
SECItem aad_item = {siBuffer, aad.data(),
static_cast<unsigned int>(aad.size())};
ScopedHpkeContext sender;
ScopedHpkeContext receiver;
SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()),
std::get<1>(GetParam()), std::get<2>(GetParam()),
std::get<3>(GetParam()));
SealOpen(sender, receiver, msg, aad, nullptr);
// Seal for negative tests
SECItem *tmp_sealed = nullptr;
SECItem *tmp_unsealed = nullptr;
EXPECT_EQ(SECSuccess,
PK11_HPKE_Seal(sender.get(), &aad_item, &msg_item, &tmp_sealed));
ASSERT_NE(nullptr, tmp_sealed);
ScopedSECItem sealed(tmp_sealed);
// Drop AAD
EXPECT_EQ(SECFailure, PK11_HPKE_Open(receiver.get(), nullptr, sealed.get(),
&tmp_unsealed));
EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError());
EXPECT_EQ(nullptr, tmp_unsealed);
// Modify AAD
aad_item.data[0] ^= 0xff;
EXPECT_EQ(SECFailure, PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(),
&tmp_unsealed));
EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError());
EXPECT_EQ(nullptr, tmp_unsealed);
aad_item.data[0] ^= 0xff;
// Modify ciphertext
sealed->data[0] ^= 0xff;
EXPECT_EQ(SECFailure, PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(),
&tmp_unsealed));
EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError());
EXPECT_EQ(nullptr, tmp_unsealed);
sealed->data[0] ^= 0xff;
EXPECT_EQ(SECSuccess, PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(),
&tmp_unsealed));
EXPECT_NE(nullptr, tmp_unsealed);
ScopedSECItem unsealed(tmp_unsealed);
CheckEquality(&msg_item, unsealed.get());
}
TEST_F(ModeParameterizedTest, InvalidContextParams) {
HpkeContext *cx =
PK11_HPKE_NewContext(static_cast<HpkeKemId>(0xff), HpkeKdfHkdfSha256,
HpkeAeadChaCha20Poly1305, nullptr, nullptr);
EXPECT_EQ(nullptr, cx);
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
cx = PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, static_cast<HpkeKdfId>(0xff),
HpkeAeadChaCha20Poly1305, nullptr, nullptr);
EXPECT_EQ(nullptr, cx);
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
cx = PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256,
static_cast<HpkeAeadId>(0xff), nullptr, nullptr);
EXPECT_EQ(nullptr, cx);
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
}
TEST_F(ModeParameterizedTest, InvalidReceiverKeyType) {
ScopedHpkeContext sender(
PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256,
HpkeAeadChaCha20Poly1305, nullptr, nullptr));
ASSERT_TRUE(!!sender);
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
if (!slot) {
ADD_FAILURE() << "No slot";
return;
}
// Give the client an RSA key
PK11RSAGenParams rsa_param;
rsa_param.keySizeInBits = 1024;
rsa_param.pe = 65537L;
SECKEYPublicKey *pub_tmp;
ScopedSECKEYPublicKey pub_key;
ScopedSECKEYPrivateKey priv_key(
PK11_GenerateKeyPair(slot.get(), CKM_RSA_PKCS_KEY_PAIR_GEN, &rsa_param,
&pub_tmp, PR_FALSE, PR_FALSE, nullptr));
ASSERT_NE(nullptr, priv_key);
ASSERT_NE(nullptr, pub_tmp);
pub_key.reset(pub_tmp);
SECItem info_item = {siBuffer, nullptr, 0};
EXPECT_EQ(SECFailure, PK11_HPKE_SetupS(sender.get(), nullptr, nullptr,
pub_key.get(), &info_item));
EXPECT_EQ(SEC_ERROR_BAD_KEY, PORT_GetError());
// Try with an unexpected curve
StackSECItem ecParams;
SECOidData *oidData = SECOID_FindOIDByTag(SEC_OID_ANSIX962_EC_PRIME256V1);
ASSERT_NE(oidData, nullptr);
if (!SECITEM_AllocItem(nullptr, &ecParams, (2 + oidData->oid.len))) {
FAIL() << "Couldn't allocate memory for OID.";
}
ecParams.data[0] = SEC_ASN1_OBJECT_ID;
ecParams.data[1] = oidData->oid.len;
memcpy(ecParams.data + 2, oidData->oid.data, oidData->oid.len);
priv_key.reset(PK11_GenerateKeyPair(slot.get(), CKM_EC_KEY_PAIR_GEN,
&ecParams, &pub_tmp, PR_FALSE, PR_FALSE,
nullptr));
ASSERT_NE(nullptr, priv_key);
ASSERT_NE(nullptr, pub_tmp);
pub_key.reset(pub_tmp);
EXPECT_EQ(SECFailure, PK11_HPKE_SetupS(sender.get(), nullptr, nullptr,
pub_key.get(), &info_item));
EXPECT_EQ(SEC_ERROR_BAD_KEY, PORT_GetError());
}
} // namespace nss_test