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