From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../nss/gtests/pk11_gtest/pk11_hpke_unittest.cc | 852 +++++++++++++++++++++ 1 file changed, 852 insertions(+) create mode 100644 security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc (limited to 'security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc') diff --git a/security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc b/security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc new file mode 100644 index 0000000000..65aeb9414f --- /dev/null +++ b/security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc @@ -0,0 +1,852 @@ +/* -*- 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 +#include "blapi.h" +#include "gtest/gtest.h" +#include "json_reader.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 &expected, SECItem *actual) { + if (!actual) { + EXPECT_TRUE(expected.empty()); + return; + } + std::vector 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 &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 expected_vec(raw->data, raw->data + raw->len); + CheckEquality(expected_vec, actual); + } + + void Seal(const ScopedHpkeContext &cx, const std::vector &aad_vec, + const std::vector &pt_vec, + std::vector *out_sealed) { + SECItem aad_item = {siBuffer, toUcharPtr(aad_vec.data()), + static_cast(aad_vec.size())}; + SECItem pt_item = {siBuffer, toUcharPtr(pt_vec.data()), + static_cast(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 &aad_vec, + const std::vector &ct_vec, + std::vector *out_opened) { + SECItem aad_item = {siBuffer, toUcharPtr(aad_vec.data()), + static_cast(aad_vec.size())}; + SECItem ct_item = {siBuffer, toUcharPtr(ct_vec.data()), + static_cast(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 &msg, + const std::vector &aad, + const std::vector *expect) { + std::vector sealed; + std::vector 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 context = {'c', 't', 'x', 't'}; + SECItem context_item = {siBuffer, context.data(), + static_cast(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 info = {'t', 'e', 's', 't', '-', 'i', 'n', 'f', 'o'}; + SECItem info_item = {siBuffer, info.data(), + static_cast(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(enc), &info_item)); + } +}; + +struct HpkeEncryptVector { + std::vector pt; + std::vector aad; + std::vector ct; + + static std::vector ReadVec(JsonReader &r) { + std::vector all; + + while (r.NextItemArray()) { + 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 ctxt; + size_t len; + std::vector exported; + + static std::vector ReadVec(JsonReader &r) { + std::vector all; + + while (r.NextItemArray()) { + 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 info; + std::vector pkcs8_e; + std::vector pkcs8_r; + std::vector psk; + std::vector psk_id; + std::vector enc; + std::vector key; + std::vector nonce; + std::vector encryptions; + std::vector exports; + + static std::vector Pkcs8(const std::vector &sk, + const std::vector &pk) { + // Only X25519 format. + std::vector 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 Read(JsonReader &r) { + std::vector all_tests; + uint32_t test_id = 0; + + while (r.NextItemArray()) { + HpkeVector vec = {0}; + uint32_t fields = 0; + enum class RequiredFields { + mode, + kem, + kdf, + aead, + skEm, + skRm, + pkEm, + pkRm, + all + }; + std::vector 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(r.ReadInt()); + fields |= 1 << static_cast(RequiredFields::mode); + } else if (n == "kem_id") { + vec.kem_id = static_cast(r.ReadInt()); + fields |= 1 << static_cast(RequiredFields::kem); + } else if (n == "kdf_id") { + vec.kdf_id = static_cast(r.ReadInt()); + fields |= 1 << static_cast(RequiredFields::kdf); + } else if (n == "aead_id") { + vec.aead_id = static_cast(r.ReadInt()); + fields |= 1 << static_cast(RequiredFields::aead); + } else if (n == "info") { + vec.info = r.ReadHex(); + } else if (n == "skEm") { + sk_e = r.ReadHex(); + fields |= 1 << static_cast(RequiredFields::skEm); + } else if (n == "pkEm") { + pk_e = r.ReadHex(); + fields |= 1 << static_cast(RequiredFields::pkEm); + } else if (n == "skRm") { + sk_r = r.ReadHex(); + fields |= 1 << static_cast(RequiredFields::skRm); + } else if (n == "pkRm") { + pk_r = r.ReadHex(); + fields |= 1 << static_cast(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(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 &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(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(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(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(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 &info) { + SECItem info_item = {siBuffer, toUcharPtr(info.data()), + static_cast(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 &enc, + const std::vector &info) { + SECItem enc_item = {siBuffer, toUcharPtr(enc.data()), + static_cast(enc.size())}; + SECItem info_item = {siBuffer, toUcharPtr(info.data()), + static_cast(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(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> {}; + +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 msg = {'s', 'e', 'c', 'r', 'e', 't'}; + std::vector 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 msg = {'s', 'e', 'c', 'r', 'e', 't'}; + std::vector 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 msg = {'s', 'e', 'c', 'r', 'e', 't'}; + std::vector 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 msg = {'s', 'e', 'c', 'r', 'e', 't'}; + std::vector 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 msg = {'s', 'e', 'c', 'r', 'e', 't'}; + std::vector aad = {'a', 'a', 'd'}; + SECItem msg_item = {siBuffer, msg.data(), + static_cast(msg.size())}; + SECItem aad_item = {siBuffer, aad.data(), + static_cast(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(0xff), HpkeKdfHkdfSha256, + HpkeAeadChaCha20Poly1305, nullptr, nullptr); + EXPECT_EQ(nullptr, cx); + EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); + + cx = PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, static_cast(0xff), + HpkeAeadChaCha20Poly1305, nullptr, nullptr); + EXPECT_EQ(nullptr, cx); + EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); + cx = PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256, + static_cast(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 -- cgit v1.2.3