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