• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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