Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(174)

Side by Side Diff: net/ssl/ssl_platform_key_win_unittest.cc

Issue 2865883002: Unit-test CAPI and CNG SSLPrivateKey implementations. (Closed)
Patch Set: . Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« net/BUILD.gn ('K') | « net/ssl/ssl_platform_key_win.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« net/BUILD.gn ('K') | « net/ssl/ssl_platform_key_win.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698