1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "net/ssl/ssl_platform_key_win.h"
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/test/scoped_feature_list.h"
18 #include "base/test/task_environment.h"
19 #include "crypto/scoped_capi_types.h"
20 #include "crypto/scoped_cng_types.h"
21 #include "crypto/unexportable_key.h"
22 #include "net/base/features.h"
23 #include "net/cert/x509_certificate.h"
24 #include "net/ssl/ssl_private_key.h"
25 #include "net/ssl/ssl_private_key_test_util.h"
26 #include "net/test/cert_test_util.h"
27 #include "net/test/test_data_directory.h"
28 #include "net/test/test_with_task_environment.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #include "third_party/boringssl/src/include/openssl/bn.h"
31 #include "third_party/boringssl/src/include/openssl/bytestring.h"
32 #include "third_party/boringssl/src/include/openssl/ec.h"
33 #include "third_party/boringssl/src/include/openssl/ec_key.h"
34 #include "third_party/boringssl/src/include/openssl/evp.h"
35 #include "third_party/boringssl/src/include/openssl/mem.h"
36 #include "third_party/boringssl/src/include/openssl/rsa.h"
37 #include "third_party/boringssl/src/include/openssl/ssl.h"
38
39 namespace net {
40
41 namespace {
42
43 struct TestKey {
44 const char* name;
45 const char* cert_file;
46 const char* key_file;
47 int type;
48 };
49
50 const TestKey kTestKeys[] = {
51 {.name = "RSA",
52 .cert_file = "client_1.pem",
53 .key_file = "client_1.pk8",
54 .type = EVP_PKEY_RSA},
55 {.name = "P256",
56 .cert_file = "client_4.pem",
57 .key_file = "client_4.pk8",
58 .type = EVP_PKEY_EC},
59 {.name = "P384",
60 .cert_file = "client_5.pem",
61 .key_file = "client_5.pk8",
62 .type = EVP_PKEY_EC},
63 {.name = "P521",
64 .cert_file = "client_6.pem",
65 .key_file = "client_6.pk8",
66 .type = EVP_PKEY_EC},
67 {.name = "RSA1024",
68 .cert_file = "client_7.pem",
69 .key_file = "client_7.pk8",
70 .type = EVP_PKEY_RSA},
71 };
72
TestParamsToString(const testing::TestParamInfo<TestKey> & params)73 std::string TestParamsToString(const testing::TestParamInfo<TestKey>& params) {
74 return params.param.name;
75 }
76
77 // Appends |bn| to |cbb|, represented as |len| bytes in little-endian order,
78 // zero-padded as needed. Returns true on success and false if |len| is too
79 // small.
AddBIGNUMLittleEndian(CBB * cbb,const BIGNUM * bn,size_t len)80 bool AddBIGNUMLittleEndian(CBB* cbb, const BIGNUM* bn, size_t len) {
81 uint8_t* ptr;
82 return CBB_add_space(cbb, &ptr, len) && BN_bn2le_padded(ptr, len, bn);
83 }
84
85 // Converts the PKCS#8 PrivateKeyInfo structure serialized in |pkcs8| to a
86 // private key BLOB, suitable for import with CAPI using Microsoft Base
87 // Cryptographic Provider.
PKCS8ToBLOBForCAPI(const std::string & pkcs8,std::vector<uint8_t> * blob)88 bool PKCS8ToBLOBForCAPI(const std::string& pkcs8, std::vector<uint8_t>* blob) {
89 CBS cbs;
90 CBS_init(&cbs, reinterpret_cast<const uint8_t*>(pkcs8.data()), pkcs8.size());
91 bssl::UniquePtr<EVP_PKEY> key(EVP_parse_private_key(&cbs));
92 if (!key || CBS_len(&cbs) != 0 || EVP_PKEY_id(key.get()) != EVP_PKEY_RSA)
93 return false;
94 const RSA* rsa = EVP_PKEY_get0_RSA(key.get());
95
96 // See
97 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375601(v=vs.85).aspx
98 PUBLICKEYSTRUC header = {0};
99 header.bType = PRIVATEKEYBLOB;
100 header.bVersion = 2;
101 header.aiKeyAlg = CALG_RSA_SIGN;
102
103 RSAPUBKEY rsapubkey = {0};
104 rsapubkey.magic = 0x32415352;
105 rsapubkey.bitlen = RSA_bits(rsa);
106 rsapubkey.pubexp = BN_get_word(RSA_get0_e(rsa));
107
108 uint8_t* blob_data;
109 size_t blob_len;
110 bssl::ScopedCBB cbb;
111 if (!CBB_init(cbb.get(), sizeof(header) + sizeof(rsapubkey) + pkcs8.size()) ||
112 !CBB_add_bytes(cbb.get(), reinterpret_cast<const uint8_t*>(&header),
113 sizeof(header)) ||
114 !CBB_add_bytes(cbb.get(), reinterpret_cast<const uint8_t*>(&rsapubkey),
115 sizeof(rsapubkey)) ||
116 !AddBIGNUMLittleEndian(cbb.get(), RSA_get0_n(rsa),
117 rsapubkey.bitlen / 8) ||
118 !AddBIGNUMLittleEndian(cbb.get(), RSA_get0_p(rsa),
119 rsapubkey.bitlen / 16) ||
120 !AddBIGNUMLittleEndian(cbb.get(), RSA_get0_q(rsa),
121 rsapubkey.bitlen / 16) ||
122 !AddBIGNUMLittleEndian(cbb.get(), RSA_get0_dmp1(rsa),
123 rsapubkey.bitlen / 16) ||
124 !AddBIGNUMLittleEndian(cbb.get(), RSA_get0_dmq1(rsa),
125 rsapubkey.bitlen / 16) ||
126 !AddBIGNUMLittleEndian(cbb.get(), RSA_get0_iqmp(rsa),
127 rsapubkey.bitlen / 16) ||
128 !AddBIGNUMLittleEndian(cbb.get(), RSA_get0_d(rsa),
129 rsapubkey.bitlen / 8) ||
130 !CBB_finish(cbb.get(), &blob_data, &blob_len)) {
131 return false;
132 }
133
134 blob->assign(blob_data, blob_data + blob_len);
135 OPENSSL_free(blob_data);
136 return true;
137 }
138
139 // Appends |bn| to |cbb|, represented as |len| bytes in big-endian order,
140 // zero-padded as needed. Returns true on success and false if |len| is too
141 // small.
AddBIGNUMBigEndian(CBB * cbb,const BIGNUM * bn,size_t len)142 bool AddBIGNUMBigEndian(CBB* cbb, const BIGNUM* bn, size_t len) {
143 uint8_t* ptr;
144 return CBB_add_space(cbb, &ptr, len) && BN_bn2bin_padded(ptr, len, bn);
145 }
146
147 // Converts the PKCS#8 PrivateKeyInfo structure serialized in |pkcs8| to a
148 // private key BLOB, suitable for import with CNG using the Microsoft Software
149 // KSP, and sets |*blob_type| to the type of the BLOB.
PKCS8ToBLOBForCNG(const std::string & pkcs8,LPCWSTR * blob_type,std::vector<uint8_t> * blob)150 bool PKCS8ToBLOBForCNG(const std::string& pkcs8,
151 LPCWSTR* blob_type,
152 std::vector<uint8_t>* blob) {
153 CBS cbs;
154 CBS_init(&cbs, reinterpret_cast<const uint8_t*>(pkcs8.data()), pkcs8.size());
155 bssl::UniquePtr<EVP_PKEY> key(EVP_parse_private_key(&cbs));
156 if (!key || CBS_len(&cbs) != 0)
157 return false;
158
159 if (EVP_PKEY_id(key.get()) == EVP_PKEY_RSA) {
160 // See
161 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375531(v=vs.85).aspx.
162 const RSA* rsa = EVP_PKEY_get0_RSA(key.get());
163 BCRYPT_RSAKEY_BLOB header = {0};
164 header.Magic = BCRYPT_RSAFULLPRIVATE_MAGIC;
165 header.BitLength = RSA_bits(rsa);
166 header.cbPublicExp = BN_num_bytes(RSA_get0_e(rsa));
167 header.cbModulus = BN_num_bytes(RSA_get0_n(rsa));
168 header.cbPrime1 = BN_num_bytes(RSA_get0_p(rsa));
169 header.cbPrime2 = BN_num_bytes(RSA_get0_q(rsa));
170
171 uint8_t* blob_data;
172 size_t blob_len;
173 bssl::ScopedCBB cbb;
174 if (!CBB_init(cbb.get(), sizeof(header) + pkcs8.size()) ||
175 !CBB_add_bytes(cbb.get(), reinterpret_cast<const uint8_t*>(&header),
176 sizeof(header)) ||
177 !AddBIGNUMBigEndian(cbb.get(), RSA_get0_e(rsa), header.cbPublicExp) ||
178 !AddBIGNUMBigEndian(cbb.get(), RSA_get0_n(rsa), header.cbModulus) ||
179 !AddBIGNUMBigEndian(cbb.get(), RSA_get0_p(rsa), header.cbPrime1) ||
180 !AddBIGNUMBigEndian(cbb.get(), RSA_get0_q(rsa), header.cbPrime2) ||
181 !AddBIGNUMBigEndian(cbb.get(), RSA_get0_dmp1(rsa), header.cbPrime1) ||
182 !AddBIGNUMBigEndian(cbb.get(), RSA_get0_dmq1(rsa), header.cbPrime2) ||
183 !AddBIGNUMBigEndian(cbb.get(), RSA_get0_iqmp(rsa), header.cbPrime1) ||
184 !AddBIGNUMBigEndian(cbb.get(), RSA_get0_d(rsa), header.cbModulus) ||
185 !CBB_finish(cbb.get(), &blob_data, &blob_len)) {
186 return false;
187 }
188
189 *blob_type = BCRYPT_RSAFULLPRIVATE_BLOB;
190 blob->assign(blob_data, blob_data + blob_len);
191 OPENSSL_free(blob_data);
192 return true;
193 }
194
195 if (EVP_PKEY_id(key.get()) == EVP_PKEY_EC) {
196 // See
197 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375520(v=vs.85).aspx.
198 const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(key.get());
199 const EC_GROUP* group = EC_KEY_get0_group(ec_key);
200 bssl::UniquePtr<BIGNUM> x(BN_new());
201 bssl::UniquePtr<BIGNUM> y(BN_new());
202 if (!EC_POINT_get_affine_coordinates_GFp(
203 group, EC_KEY_get0_public_key(ec_key), x.get(), y.get(), nullptr)) {
204 return false;
205 }
206
207 BCRYPT_ECCKEY_BLOB header = {0};
208 switch (EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key))) {
209 case NID_X9_62_prime256v1:
210 header.dwMagic = BCRYPT_ECDSA_PRIVATE_P256_MAGIC;
211 break;
212 case NID_secp384r1:
213 header.dwMagic = BCRYPT_ECDSA_PRIVATE_P384_MAGIC;
214 break;
215 case NID_secp521r1:
216 header.dwMagic = BCRYPT_ECDSA_PRIVATE_P521_MAGIC;
217 break;
218 default:
219 return false;
220 }
221 header.cbKey = BN_num_bytes(EC_GROUP_get0_order(group));
222
223 uint8_t* blob_data;
224 size_t blob_len;
225 bssl::ScopedCBB cbb;
226 if (!CBB_init(cbb.get(), sizeof(header) + header.cbKey * 3) ||
227 !CBB_add_bytes(cbb.get(), reinterpret_cast<const uint8_t*>(&header),
228 sizeof(header)) ||
229 !AddBIGNUMBigEndian(cbb.get(), x.get(), header.cbKey) ||
230 !AddBIGNUMBigEndian(cbb.get(), y.get(), header.cbKey) ||
231 !AddBIGNUMBigEndian(cbb.get(), EC_KEY_get0_private_key(ec_key),
232 header.cbKey) ||
233 !CBB_finish(cbb.get(), &blob_data, &blob_len)) {
234 return false;
235 }
236
237 *blob_type = BCRYPT_ECCPRIVATE_BLOB;
238 blob->assign(blob_data, blob_data + blob_len);
239 OPENSSL_free(blob_data);
240 return true;
241 }
242
243 return false;
244 }
245
246 } // namespace
247
248 class SSLPlatformKeyWinTest
249 : public testing::TestWithParam<TestKey>,
250 public WithTaskEnvironment {
251 public:
GetTestKey() const252 const TestKey& GetTestKey() const { return GetParam(); }
253 };
254
TEST_P(SSLPlatformKeyWinTest,KeyMatchesCNG)255 TEST_P(SSLPlatformKeyWinTest, KeyMatchesCNG) {
256 const TestKey& test_key = GetTestKey();
257
258 // Load test data.
259 scoped_refptr<X509Certificate> cert =
260 ImportCertFromFile(GetTestCertsDirectory(), test_key.cert_file);
261 ASSERT_TRUE(cert);
262
263 std::string pkcs8;
264 base::FilePath pkcs8_path =
265 GetTestCertsDirectory().AppendASCII(test_key.key_file);
266 ASSERT_TRUE(base::ReadFileToString(pkcs8_path, &pkcs8));
267
268 // Import the key into CNG. Per MSDN's documentation on NCryptImportKey, if a
269 // key name is not supplied (via the pParameterList parameter for the BLOB
270 // types we use), the Microsoft Software KSP will treat the key as ephemeral.
271 //
272 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa376276(v=vs.85).aspx
273 crypto::ScopedNCryptProvider prov;
274 SECURITY_STATUS status = NCryptOpenStorageProvider(
275 crypto::ScopedNCryptProvider::Receiver(prov).get(),
276 MS_KEY_STORAGE_PROVIDER, 0);
277 ASSERT_FALSE(FAILED(status)) << status;
278
279 LPCWSTR blob_type;
280 std::vector<uint8_t> blob;
281 ASSERT_TRUE(PKCS8ToBLOBForCNG(pkcs8, &blob_type, &blob));
282 crypto::ScopedNCryptKey ncrypt_key;
283 status = NCryptImportKey(prov.get(), /*hImportKey=*/0, blob_type,
284 /*pParameterList=*/nullptr,
285 crypto::ScopedNCryptKey::Receiver(ncrypt_key).get(),
286 blob.data(), blob.size(), NCRYPT_SILENT_FLAG);
287 ASSERT_FALSE(FAILED(status)) << status;
288
289 scoped_refptr<SSLPrivateKey> key =
290 WrapCNGPrivateKey(cert.get(), std::move(ncrypt_key));
291 ASSERT_TRUE(key);
292
293 EXPECT_EQ(SSLPrivateKey::DefaultAlgorithmPreferences(test_key.type,
294 /*supports_pss=*/true),
295 key->GetAlgorithmPreferences());
296 TestSSLPrivateKeyMatches(key.get(), pkcs8);
297 }
298
TEST_P(SSLPlatformKeyWinTest,KeyMatchesCAPI)299 TEST_P(SSLPlatformKeyWinTest, KeyMatchesCAPI) {
300 const TestKey& test_key = GetTestKey();
301 if (test_key.type != EVP_PKEY_RSA) {
302 GTEST_SKIP() << "CAPI only supports RSA keys";
303 }
304
305 // Load test data.
306 scoped_refptr<X509Certificate> cert =
307 ImportCertFromFile(GetTestCertsDirectory(), test_key.cert_file);
308 ASSERT_TRUE(cert);
309
310 std::string pkcs8;
311 base::FilePath pkcs8_path =
312 GetTestCertsDirectory().AppendASCII(test_key.key_file);
313 ASSERT_TRUE(base::ReadFileToString(pkcs8_path, &pkcs8));
314
315 // Import the key into CAPI. Use CRYPT_VERIFYCONTEXT for an ephemeral key.
316 crypto::ScopedHCRYPTPROV prov;
317 ASSERT_NE(FALSE,
318 CryptAcquireContext(crypto::ScopedHCRYPTPROV::Receiver(prov).get(),
319 nullptr, nullptr, PROV_RSA_AES,
320 CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
321 << GetLastError();
322
323 std::vector<uint8_t> blob;
324 ASSERT_TRUE(PKCS8ToBLOBForCAPI(pkcs8, &blob));
325
326 crypto::ScopedHCRYPTKEY hcryptkey;
327 ASSERT_NE(FALSE,
328 CryptImportKey(prov.get(), blob.data(), blob.size(),
329 /*hPubKey=*/0, /*dwFlags=*/0,
330 crypto::ScopedHCRYPTKEY::Receiver(hcryptkey).get()))
331 << GetLastError();
332 // Release |hcryptkey| so it does not outlive |prov|.
333 hcryptkey.reset();
334
335 scoped_refptr<SSLPrivateKey> key =
336 WrapCAPIPrivateKey(cert.get(), std::move(prov), AT_SIGNATURE);
337 ASSERT_TRUE(key);
338
339 std::vector<uint16_t> expected = {
340 SSL_SIGN_RSA_PKCS1_SHA256,
341 SSL_SIGN_RSA_PKCS1_SHA384,
342 SSL_SIGN_RSA_PKCS1_SHA512,
343 SSL_SIGN_RSA_PKCS1_SHA1,
344 };
345 EXPECT_EQ(expected, key->GetAlgorithmPreferences());
346 TestSSLPrivateKeyMatches(key.get(), pkcs8);
347 }
348
349 INSTANTIATE_TEST_SUITE_P(All,
350 SSLPlatformKeyWinTest,
351 testing::ValuesIn(kTestKeys),
352 TestParamsToString);
353
TEST(UnexportableSSLPlatformKeyWinTest,WrapUnexportableKeySlowly)354 TEST(UnexportableSSLPlatformKeyWinTest, WrapUnexportableKeySlowly) {
355 auto provider = crypto::GetUnexportableKeyProvider({});
356 if (!provider) {
357 GTEST_SKIP() << "Hardware-backed keys are not supported.";
358 }
359
360 const crypto::SignatureVerifier::SignatureAlgorithm algorithms[] = {
361 crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256,
362 crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256};
363 auto key = provider->GenerateSigningKeySlowly(algorithms);
364 if (!key) {
365 // Could be hitting crbug.com/41494935. Fine to skip the test as the
366 // UnexportableKeyProvider logic is covered in another test suite.
367 GTEST_SKIP()
368 << "Workaround for https://issues.chromium.org/issues/41494935";
369 }
370
371 auto ssl_private_key = WrapUnexportableKeySlowly(*key);
372 ASSERT_TRUE(ssl_private_key);
373 }
374
375 } // namespace net
376