pk11_curve25519_unittest.cc (9317B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include <algorithm> 6 #include <memory> 7 #include "nss.h" 8 #include "pk11pub.h" 9 #include "prerror.h" 10 #include "cpputil.h" 11 #include "nss_scoped_ptrs.h" 12 #include "json_reader.h" 13 14 #include "testvectors/curve25519-vectors.h" 15 #include "gtest/gtest.h" 16 17 namespace nss_test { 18 19 class Pkcs11Curve25519TestBase { 20 protected: 21 void Derive(const uint8_t* pkcs8, size_t pkcs8_len, const uint8_t* spki, 22 size_t spki_len, const uint8_t* secret, size_t secret_len, 23 bool expect_success) { 24 ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot()); 25 ASSERT_TRUE(slot); 26 27 SECItem pkcs8_item = {siBuffer, toUcharPtr(pkcs8), 28 static_cast<unsigned int>(pkcs8_len)}; 29 30 SECKEYPrivateKey* key = nullptr; 31 SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey( 32 slot.get(), &pkcs8_item, nullptr, nullptr, false, false, KU_ALL, &key, 33 nullptr); 34 EXPECT_EQ(SECSuccess, rv); 35 36 ScopedSECKEYPrivateKey priv_key_sess(key); 37 ASSERT_TRUE(priv_key_sess); 38 39 SECItem spki_item = {siBuffer, toUcharPtr(spki), 40 static_cast<unsigned int>(spki_len)}; 41 42 ScopedCERTSubjectPublicKeyInfo cert_spki( 43 SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item)); 44 if (!expect_success && !cert_spki) { 45 return; 46 } 47 ASSERT_TRUE(cert_spki); 48 49 ScopedSECKEYPublicKey pub_key_remote( 50 SECKEY_ExtractPublicKey(cert_spki.get())); 51 ASSERT_TRUE(pub_key_remote); 52 53 // sym_key_sess = ECDH(session_import(private_test), public_test) 54 ScopedPK11SymKey sym_key_sess(PK11_PubDeriveWithKDF( 55 priv_key_sess.get(), pub_key_remote.get(), false, nullptr, nullptr, 56 CKM_ECDH1_DERIVE, CKM_SHA512_HMAC, CKA_DERIVE, 0, CKD_NULL, nullptr, 57 nullptr)); 58 ASSERT_EQ(expect_success, !!sym_key_sess); 59 60 if (expect_success) { 61 rv = PK11_ExtractKeyValue(sym_key_sess.get()); 62 EXPECT_EQ(SECSuccess, rv); 63 64 SECItem* key_data = PK11_GetKeyData(sym_key_sess.get()); 65 EXPECT_EQ(secret_len, key_data->len); 66 EXPECT_EQ(memcmp(key_data->data, secret, secret_len), 0); 67 68 // Perform wrapped export on the imported private, import it as 69 // permanent, and verify we derive the same shared secret 70 static const uint8_t pw[] = "pw"; 71 SECItem pwItem = {siBuffer, toUcharPtr(pw), sizeof(pw)}; 72 ScopedSECKEYEncryptedPrivateKeyInfo epki(PK11_ExportEncryptedPrivKeyInfo( 73 slot.get(), SEC_OID_AES_256_CBC, &pwItem, priv_key_sess.get(), 1, 74 nullptr)); 75 ASSERT_NE(nullptr, epki) << "PK11_ExportEncryptedPrivKeyInfo failed: " 76 << PORT_ErrorToName(PORT_GetError()); 77 78 ScopedSECKEYPublicKey pub_key_local( 79 SECKEY_ConvertToPublicKey(priv_key_sess.get())); 80 81 SECKEYPrivateKey* priv_key_tok = nullptr; 82 rv = PK11_ImportEncryptedPrivateKeyInfoAndReturnKey( 83 slot.get(), epki.get(), &pwItem, nullptr, 84 &pub_key_local->u.ec.publicValue, PR_TRUE, PR_TRUE, ecKey, 0, 85 &priv_key_tok, nullptr); 86 ASSERT_EQ(SECSuccess, rv) << "PK11_ImportEncryptedPrivateKeyInfo failed " 87 << PORT_ErrorToName(PORT_GetError()); 88 ASSERT_TRUE(priv_key_tok); 89 90 // sym_key_tok = ECDH(token_import(export(private_test)), 91 // public_test) 92 ScopedPK11SymKey sym_key_tok(PK11_PubDeriveWithKDF( 93 priv_key_tok, pub_key_remote.get(), false, nullptr, nullptr, 94 CKM_ECDH1_DERIVE, CKM_SHA512_HMAC, CKA_DERIVE, 0, CKD_NULL, nullptr, 95 nullptr)); 96 EXPECT_TRUE(sym_key_tok); 97 98 if (sym_key_tok) { 99 rv = PK11_ExtractKeyValue(sym_key_tok.get()); 100 EXPECT_EQ(SECSuccess, rv); 101 102 key_data = PK11_GetKeyData(sym_key_tok.get()); 103 EXPECT_EQ(secret_len, key_data->len); 104 EXPECT_EQ(memcmp(key_data->data, secret, secret_len), 0); 105 } 106 rv = PK11_DeleteTokenPrivateKey(priv_key_tok, true); 107 EXPECT_EQ(SECSuccess, rv); 108 } 109 } 110 111 void Derive(const EcdhTestVector& testvector) { 112 std::cout << "Running test: " << testvector.id << std::endl; 113 114 Derive(testvector.private_key.data(), testvector.private_key.size(), 115 testvector.public_key.data(), testvector.public_key.size(), 116 testvector.secret.data(), testvector.secret.size(), 117 testvector.valid); 118 } 119 }; 120 121 class Pkcs11Curve25519Wycheproof : public Pkcs11Curve25519TestBase, 122 public ::testing::Test { 123 protected: 124 void RunGroup(JsonReader& r) { 125 std::vector<EcdhTestVector> tests; 126 while (r.NextItem()) { 127 std::string n = r.ReadLabel(); 128 if (n == "") { 129 break; 130 } 131 if (n == "curve") { 132 ASSERT_EQ("curve25519", r.ReadString()); 133 } else if (n == "type") { 134 ASSERT_EQ("XdhComp", r.ReadString()); 135 } else if (n == "tests") { 136 WycheproofReadTests(r, &tests, ReadTestAttr, true, 137 Pkcs11Curve25519Wycheproof::FilterInvalid); 138 } else { 139 FAIL() << "unknown group label: " << n; 140 } 141 } 142 143 for (auto& t : tests) { 144 Derive(t); 145 } 146 } 147 148 private: 149 static void FilterInvalid(EcdhTestVector& t, const std::string& result, 150 const std::vector<std::string>& flags) { 151 static const std::vector<uint8_t> kNonCanonPublic1 = { 152 0x30, 0x39, 0x30, 0x14, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 153 0x01, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01, 154 0x03, 0x21, 0x00, 0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 155 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 156 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 157 }; 158 static const std::vector<uint8_t> kNonCanonPublic2 = { 159 0x30, 0x39, 0x30, 0x14, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 160 0x01, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01, 161 0x03, 0x21, 0x00, 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 162 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 163 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 164 }; 165 166 if (result == "acceptable" && 167 (std::find_if(flags.begin(), flags.end(), 168 [](const std::string& flag) { 169 return flag == "SmallPublicKey" || 170 flag == "ZeroSharedSecret"; 171 }) != flags.end() || 172 t.public_key == kNonCanonPublic1 || 173 t.public_key == kNonCanonPublic2)) { 174 t.valid = false; 175 } 176 } 177 178 static void ReadTestAttr(EcdhTestVector& t, const std::string& n, 179 JsonReader& r) { 180 // Static PKCS#8 and SPKI wrappers for the raw keys from Wycheproof. 181 static const std::vector<uint8_t> kPrivatePrefix = { 182 0x30, 0x67, 0x02, 0x01, 0x00, 0x30, 0x14, 0x06, 0x07, 0x2a, 0x86, 0x48, 183 0xce, 0x3d, 0x02, 0x01, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 184 0x47, 0x0f, 0x01, 0x04, 0x4c, 0x30, 0x4a, 0x02, 0x01, 0x01, 0x04, 0x20}; 185 // The public key section of the PKCS#8 wrapper is filled up with 0's, which 186 // is not correct, but acceptable for the tests at this moment because 187 // validity of the public key is not checked. 188 // It's still necessary because of 189 // https://searchfox.org/nss/rev/7bc70a3317b800aac07bad83e74b6c79a9ec5bff/lib/pk11wrap/pk11pk12.c#171 190 static const std::vector<uint8_t> kPrivateSuffix = { 191 0xa1, 0x23, 0x03, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 192 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 193 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 194 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 195 static const std::vector<uint8_t> kPublicPrefix = { 196 0x30, 0x39, 0x30, 0x14, 0x06, 0x07, 0x2a, 0x86, 0x48, 197 0xce, 0x3d, 0x02, 0x01, 0x06, 0x09, 0x2b, 0x06, 0x01, 198 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01, 0x03, 0x21, 0x00}; 199 200 if (n == "public") { 201 t.public_key = kPublicPrefix; 202 std::vector<uint8_t> pub = r.ReadHex(); 203 t.public_key.insert(t.public_key.end(), pub.begin(), pub.end()); 204 } else if (n == "private") { 205 t.private_key = kPrivatePrefix; 206 std::vector<uint8_t> priv = r.ReadHex(); 207 t.private_key.insert(t.private_key.end(), priv.begin(), priv.end()); 208 t.private_key.insert(t.private_key.end(), kPrivateSuffix.begin(), 209 kPrivateSuffix.end()); 210 } else if (n == "shared") { 211 t.secret = r.ReadHex(); 212 } else { 213 FAIL() << "unsupported test case field: " << n; 214 } 215 } 216 }; 217 218 TEST_F(Pkcs11Curve25519Wycheproof, Run) { 219 WycheproofHeader("x25519", "XDH", "xdh_comp_schema.json", 220 [this](JsonReader& r) { RunGroup(r); }); 221 } 222 223 class Pkcs11Curve25519ParamTest 224 : public Pkcs11Curve25519TestBase, 225 public ::testing::TestWithParam<EcdhTestVector> {}; 226 227 TEST_P(Pkcs11Curve25519ParamTest, TestVectors) { Derive(GetParam()); } 228 229 INSTANTIATE_TEST_SUITE_P(NSSTestVector, Pkcs11Curve25519ParamTest, 230 ::testing::ValuesIn(kCurve25519Vectors)); 231 232 } // namespace nss_test