| /* -*- 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 |