Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "net/ssl/ssl_platform_key_win.h" | |
| 6 | |
| 7 #include <windows.h> | |
| 8 | |
| 9 // Must be after windows.h. | |
| 10 #include <NCrypt.h> | |
|
Ryan Sleevi
2017/05/08 21:01:41
These are in _win.h, but you removed them from _wi
davidben
2017/05/08 21:19:06
Removed.
| |
| 11 | |
| 12 #include <string> | |
| 13 #include <vector> | |
| 14 | |
| 15 #include "base/files/file_path.h" | |
| 16 #include "base/files/file_util.h" | |
| 17 #include "base/memory/ref_counted.h" | |
| 18 #include "crypto/scoped_capi_types.h" | |
| 19 #include "net/cert/x509_certificate.h" | |
| 20 #include "net/ssl/ssl_private_key.h" | |
| 21 #include "net/ssl/ssl_private_key_test_util.h" | |
| 22 #include "net/test/cert_test_util.h" | |
| 23 #include "net/test/test_data_directory.h" | |
| 24 #include "testing/gtest/include/gtest/gtest.h" | |
| 25 #include "third_party/boringssl/src/include/openssl/bn.h" | |
| 26 #include "third_party/boringssl/src/include/openssl/bytestring.h" | |
| 27 #include "third_party/boringssl/src/include/openssl/ec.h" | |
| 28 #include "third_party/boringssl/src/include/openssl/ec_key.h" | |
| 29 #include "third_party/boringssl/src/include/openssl/evp.h" | |
| 30 #include "third_party/boringssl/src/include/openssl/mem.h" | |
| 31 #include "third_party/boringssl/src/include/openssl/rsa.h" | |
| 32 | |
| 33 namespace net { | |
| 34 | |
| 35 namespace { | |
| 36 | |
| 37 struct TestKey { | |
| 38 const char* name; | |
| 39 const char* cert_file; | |
| 40 const char* key_file; | |
| 41 }; | |
| 42 | |
| 43 const TestKey kTestKeys[] = { | |
| 44 {"RSA", "client_1.pem", "client_1.pk8"}, | |
| 45 {"ECDSA_P256", "client_4.pem", "client_4.pk8"}, | |
| 46 {"ECDSA_P384", "client_5.pem", "client_5.pk8"}, | |
| 47 {"ECDSA_P521", "client_6.pem", "client_6.pk8"}, | |
| 48 }; | |
| 49 | |
| 50 std::string TestKeyToString(const testing::TestParamInfo<TestKey>& params) { | |
| 51 return params.param.name; | |
| 52 } | |
| 53 | |
| 54 class ScopedNCRYPT_PROV_HANDLE { | |
| 55 public: | |
| 56 ScopedNCRYPT_PROV_HANDLE(NCRYPT_PROV_HANDLE prov) : prov_(prov) {} | |
| 57 ~ScopedNCRYPT_PROV_HANDLE() { NCryptFreeObject(prov_); } | |
| 58 | |
| 59 private: | |
| 60 NCRYPT_PROV_HANDLE prov_; | |
| 61 }; | |
| 62 | |
| 63 bool AddBIGNUMLittleEndian(CBB* cbb, const BIGNUM* bn, size_t len) { | |
|
Ryan Sleevi
2017/05/08 21:01:41
Document?
davidben
2017/05/08 21:19:06
Done.
| |
| 64 uint8_t* ptr; | |
| 65 return CBB_add_space(cbb, &ptr, len) && BN_bn2le_padded(ptr, len, bn); | |
| 66 } | |
| 67 | |
| 68 bool PKCS8ToBLOBForCAPI(const std::string& pkcs8, std::vector<uint8_t>* blob) { | |
|
Ryan Sleevi
2017/05/08 21:01:41
Document?
davidben
2017/05/08 21:19:06
Done.
| |
| 69 CBS cbs; | |
| 70 CBS_init(&cbs, reinterpret_cast<const uint8_t*>(pkcs8.data()), pkcs8.size()); | |
| 71 bssl::UniquePtr<EVP_PKEY> key(EVP_parse_private_key(&cbs)); | |
| 72 if (!key || CBS_len(&cbs) != 0 || EVP_PKEY_id(key.get()) != EVP_PKEY_RSA) | |
| 73 return false; | |
| 74 const RSA* rsa = EVP_PKEY_get0_RSA(key.get()); | |
| 75 | |
| 76 // See | |
| 77 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375601(v=vs.85). aspx | |
| 78 PUBLICKEYSTRUC header = {0}; | |
| 79 header.bType = PRIVATEKEYBLOB; | |
| 80 header.bVersion = 2; | |
| 81 header.aiKeyAlg = CALG_RSA_SIGN; | |
| 82 | |
| 83 RSAPUBKEY rsapubkey = {0}; | |
| 84 rsapubkey.magic = 0x32415352; | |
| 85 rsapubkey.bitlen = BN_num_bits(rsa->n); | |
| 86 rsapubkey.pubexp = BN_get_word(rsa->e); | |
| 87 | |
| 88 uint8_t* blob_data; | |
| 89 size_t blob_len; | |
| 90 bssl::ScopedCBB cbb; | |
| 91 if (!CBB_init(cbb.get(), sizeof(header) + sizeof(rsapubkey) + pkcs8.size()) || | |
| 92 !CBB_add_bytes(cbb.get(), reinterpret_cast<const uint8_t*>(&header), | |
| 93 sizeof(header)) || | |
| 94 !CBB_add_bytes(cbb.get(), reinterpret_cast<const uint8_t*>(&rsapubkey), | |
| 95 sizeof(rsapubkey)) || | |
| 96 !AddBIGNUMLittleEndian(cbb.get(), rsa->n, rsapubkey.bitlen / 8) || | |
| 97 !AddBIGNUMLittleEndian(cbb.get(), rsa->p, rsapubkey.bitlen / 16) || | |
| 98 !AddBIGNUMLittleEndian(cbb.get(), rsa->q, rsapubkey.bitlen / 16) || | |
| 99 !AddBIGNUMLittleEndian(cbb.get(), rsa->dmp1, rsapubkey.bitlen / 16) || | |
| 100 !AddBIGNUMLittleEndian(cbb.get(), rsa->dmq1, rsapubkey.bitlen / 16) || | |
| 101 !AddBIGNUMLittleEndian(cbb.get(), rsa->iqmp, rsapubkey.bitlen / 16) || | |
| 102 !AddBIGNUMLittleEndian(cbb.get(), rsa->d, rsapubkey.bitlen / 8) || | |
| 103 !CBB_finish(cbb.get(), &blob_data, &blob_len)) { | |
| 104 return false; | |
| 105 } | |
| 106 | |
| 107 blob->assign(blob_data, blob_data + blob_len); | |
| 108 OPENSSL_free(blob_data); | |
| 109 return true; | |
| 110 } | |
| 111 | |
| 112 bool AddBIGNUMBigEndian(CBB* cbb, const BIGNUM* bn, size_t len) { | |
| 113 uint8_t* ptr; | |
| 114 return CBB_add_space(cbb, &ptr, len) && BN_bn2bin_padded(ptr, len, bn); | |
| 115 } | |
| 116 | |
| 117 bool PKCS8ToBLOBForCNG(const std::string& pkcs8, | |
| 118 LPCWSTR* blob_type, | |
| 119 std::vector<uint8_t>* blob) { | |
| 120 CBS cbs; | |
| 121 CBS_init(&cbs, reinterpret_cast<const uint8_t*>(pkcs8.data()), pkcs8.size()); | |
| 122 bssl::UniquePtr<EVP_PKEY> key(EVP_parse_private_key(&cbs)); | |
| 123 if (!key || CBS_len(&cbs) != 0) | |
| 124 return false; | |
| 125 | |
| 126 if (EVP_PKEY_id(key.get()) == EVP_PKEY_RSA) { | |
| 127 // See | |
| 128 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375531(v=vs.85 ).aspx. | |
| 129 const RSA* rsa = EVP_PKEY_get0_RSA(key.get()); | |
| 130 BCRYPT_RSAKEY_BLOB header = {0}; | |
| 131 header.Magic = BCRYPT_RSAFULLPRIVATE_MAGIC; | |
| 132 header.BitLength = BN_num_bits(rsa->n); | |
| 133 header.cbPublicExp = BN_num_bytes(rsa->e); | |
| 134 header.cbModulus = BN_num_bytes(rsa->n); | |
| 135 header.cbPrime1 = BN_num_bytes(rsa->p); | |
| 136 header.cbPrime2 = BN_num_bytes(rsa->q); | |
| 137 | |
| 138 uint8_t* blob_data; | |
| 139 size_t blob_len; | |
| 140 bssl::ScopedCBB cbb; | |
| 141 if (!CBB_init(cbb.get(), sizeof(header) + pkcs8.size()) || | |
| 142 !CBB_add_bytes(cbb.get(), reinterpret_cast<const uint8_t*>(&header), | |
| 143 sizeof(header)) || | |
| 144 !AddBIGNUMBigEndian(cbb.get(), rsa->e, header.cbPublicExp) || | |
| 145 !AddBIGNUMBigEndian(cbb.get(), rsa->n, header.cbModulus) || | |
| 146 !AddBIGNUMBigEndian(cbb.get(), rsa->p, header.cbPrime1) || | |
| 147 !AddBIGNUMBigEndian(cbb.get(), rsa->q, header.cbPrime2) || | |
| 148 !AddBIGNUMBigEndian(cbb.get(), rsa->dmp1, header.cbPrime1) || | |
| 149 !AddBIGNUMBigEndian(cbb.get(), rsa->dmq1, header.cbPrime2) || | |
| 150 !AddBIGNUMBigEndian(cbb.get(), rsa->iqmp, header.cbPrime1) || | |
| 151 !AddBIGNUMBigEndian(cbb.get(), rsa->d, header.cbModulus) || | |
| 152 !CBB_finish(cbb.get(), &blob_data, &blob_len)) { | |
| 153 return false; | |
| 154 } | |
| 155 | |
| 156 *blob_type = BCRYPT_RSAFULLPRIVATE_BLOB; | |
| 157 blob->assign(blob_data, blob_data + blob_len); | |
| 158 OPENSSL_free(blob_data); | |
| 159 return true; | |
| 160 } | |
| 161 | |
| 162 if (EVP_PKEY_id(key.get()) == EVP_PKEY_EC) { | |
| 163 // See | |
| 164 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375520(v=vs.85 ).aspx. | |
| 165 const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(key.get()); | |
| 166 const EC_GROUP* group = EC_KEY_get0_group(ec_key); | |
| 167 bssl::UniquePtr<BIGNUM> x(BN_new()); | |
| 168 bssl::UniquePtr<BIGNUM> y(BN_new()); | |
| 169 if (!EC_POINT_get_affine_coordinates_GFp( | |
| 170 group, EC_KEY_get0_public_key(ec_key), x.get(), y.get(), nullptr)) { | |
| 171 return false; | |
| 172 } | |
| 173 | |
| 174 BCRYPT_ECCKEY_BLOB header = {0}; | |
| 175 switch (EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key))) { | |
| 176 case NID_X9_62_prime256v1: | |
| 177 header.dwMagic = BCRYPT_ECDSA_PRIVATE_P256_MAGIC; | |
| 178 break; | |
| 179 case NID_secp384r1: | |
| 180 header.dwMagic = BCRYPT_ECDSA_PRIVATE_P384_MAGIC; | |
| 181 break; | |
| 182 case NID_secp521r1: | |
| 183 header.dwMagic = BCRYPT_ECDSA_PRIVATE_P521_MAGIC; | |
|
davidben
2017/05/06 22:27:34
The MSDN docs call this field 'Magic', but it seem
| |
| 184 break; | |
| 185 default: | |
| 186 return false; | |
| 187 } | |
| 188 header.cbKey = BN_num_bytes(EC_GROUP_get0_order(group)); | |
| 189 | |
| 190 uint8_t* blob_data; | |
| 191 size_t blob_len; | |
| 192 bssl::ScopedCBB cbb; | |
| 193 if (!CBB_init(cbb.get(), sizeof(header) + header.cbKey * 3) || | |
| 194 !CBB_add_bytes(cbb.get(), reinterpret_cast<const uint8_t*>(&header), | |
| 195 sizeof(header)) || | |
| 196 !AddBIGNUMBigEndian(cbb.get(), x.get(), header.cbKey) || | |
| 197 !AddBIGNUMBigEndian(cbb.get(), y.get(), header.cbKey) || | |
| 198 !AddBIGNUMBigEndian(cbb.get(), EC_KEY_get0_private_key(ec_key), | |
| 199 header.cbKey) || | |
| 200 !CBB_finish(cbb.get(), &blob_data, &blob_len)) { | |
| 201 return false; | |
| 202 } | |
| 203 | |
| 204 *blob_type = BCRYPT_ECCPRIVATE_BLOB; | |
| 205 blob->assign(blob_data, blob_data + blob_len); | |
| 206 OPENSSL_free(blob_data); | |
| 207 return true; | |
| 208 } | |
| 209 | |
| 210 return false; | |
| 211 } | |
| 212 | |
| 213 } // namespace | |
| 214 | |
| 215 class SSLPlatformKeyCNGTest : public testing::TestWithParam<TestKey> {}; | |
| 216 | |
| 217 TEST_P(SSLPlatformKeyCNGTest, KeyMatches) { | |
| 218 const TestKey& test_key = GetParam(); | |
| 219 | |
| 220 // Load test data. | |
| 221 scoped_refptr<X509Certificate> cert = | |
| 222 ImportCertFromFile(GetTestCertsDirectory(), test_key.cert_file); | |
| 223 ASSERT_TRUE(cert); | |
| 224 | |
| 225 std::string pkcs8; | |
| 226 base::FilePath pkcs8_path = | |
| 227 GetTestCertsDirectory().AppendASCII(test_key.key_file); | |
| 228 ASSERT_TRUE(base::ReadFileToString(pkcs8_path, &pkcs8)); | |
| 229 | |
| 230 // Import the key into CNG. Per MSDN's documentation on NCryptImportKey, if a | |
| 231 // key name is not supplied (via the pParameterList parameter for the BLOB | |
| 232 // types we use), the Microsoft Software KSP will treat the key as ephemeral. | |
| 233 // | |
| 234 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa376276(v=vs.85). aspx | |
| 235 NCRYPT_PROV_HANDLE prov; | |
| 236 SECURITY_STATUS status = | |
| 237 NCryptOpenStorageProvider(&prov, MS_KEY_STORAGE_PROVIDER, 0); | |
| 238 ASSERT_FALSE(FAILED(status)) << status; | |
| 239 ScopedNCRYPT_PROV_HANDLE scoped_prov(prov); | |
| 240 | |
| 241 LPCWSTR blob_type; | |
| 242 std::vector<uint8_t> blob; | |
| 243 ASSERT_TRUE(PKCS8ToBLOBForCNG(pkcs8, &blob_type, &blob)); | |
| 244 NCRYPT_KEY_HANDLE ncrypt_key; | |
| 245 status = NCryptImportKey(prov, 0 /* hImportKey */, blob_type, | |
| 246 nullptr /* pParameterList */, &ncrypt_key, | |
| 247 blob.data(), blob.size(), NCRYPT_SILENT_FLAG); | |
| 248 ASSERT_FALSE(FAILED(status)) << status; | |
| 249 | |
| 250 scoped_refptr<SSLPrivateKey> key = WrapCNGPrivateKey(cert.get(), ncrypt_key); | |
| 251 ASSERT_TRUE(key); | |
| 252 | |
| 253 std::vector<SSLPrivateKey::Hash> expected_hashes = { | |
| 254 SSLPrivateKey::Hash::SHA512, SSLPrivateKey::Hash::SHA384, | |
| 255 SSLPrivateKey::Hash::SHA256, SSLPrivateKey::Hash::SHA1, | |
| 256 }; | |
| 257 EXPECT_EQ(expected_hashes, key->GetDigestPreferences()); | |
| 258 | |
| 259 TestSSLPrivateKeyMatches(key.get(), pkcs8); | |
| 260 } | |
| 261 | |
| 262 INSTANTIATE_TEST_CASE_P(, | |
| 263 SSLPlatformKeyCNGTest, | |
| 264 testing::ValuesIn(kTestKeys), | |
| 265 TestKeyToString); | |
| 266 | |
| 267 TEST(SSLPlatformKeyCAPITest, KeyMatches) { | |
| 268 // Load test data. | |
| 269 scoped_refptr<X509Certificate> cert = | |
| 270 ImportCertFromFile(GetTestCertsDirectory(), "client_1.pem"); | |
| 271 ASSERT_TRUE(cert); | |
| 272 | |
| 273 std::string pkcs8; | |
| 274 base::FilePath pkcs8_path = | |
| 275 GetTestCertsDirectory().AppendASCII("client_1.pk8"); | |
| 276 ASSERT_TRUE(base::ReadFileToString(pkcs8_path, &pkcs8)); | |
| 277 | |
| 278 // Import the key into CAPI. Use CRYPT_VERIFYCONTEXT for an ephemeral key. | |
| 279 crypto::ScopedHCRYPTPROV prov; | |
| 280 ASSERT_NE(FALSE, | |
| 281 CryptAcquireContext(prov.receive(), nullptr, nullptr, PROV_RSA_AES, | |
| 282 CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) | |
| 283 << GetLastError(); | |
| 284 | |
| 285 std::vector<uint8_t> blob; | |
| 286 ASSERT_TRUE(PKCS8ToBLOBForCAPI(pkcs8, &blob)); | |
| 287 | |
| 288 crypto::ScopedHCRYPTKEY hcryptkey; | |
| 289 ASSERT_NE(FALSE, CryptImportKey(prov.get(), blob.data(), blob.size(), | |
| 290 0 /* hPubKey */, 0 /* dwFlags */, | |
| 291 hcryptkey.receive())) | |
| 292 << GetLastError(); | |
| 293 // Release |hcryptkey| so it does not outlive |prov|. | |
| 294 hcryptkey.reset(); | |
| 295 | |
| 296 scoped_refptr<SSLPrivateKey> key = | |
| 297 WrapCAPIPrivateKey(cert.get(), prov.release(), AT_SIGNATURE); | |
| 298 ASSERT_TRUE(key); | |
| 299 | |
| 300 std::vector<SSLPrivateKey::Hash> expected_hashes = { | |
| 301 SSLPrivateKey::Hash::SHA1, SSLPrivateKey::Hash::SHA512, | |
| 302 SSLPrivateKey::Hash::SHA384, SSLPrivateKey::Hash::SHA256, | |
| 303 }; | |
| 304 EXPECT_EQ(expected_hashes, key->GetDigestPreferences()); | |
| 305 | |
| 306 TestSSLPrivateKeyMatches(key.get(), pkcs8); | |
| 307 } | |
| 308 | |
| 309 } // namespace net | |
| OLD | NEW |