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

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

Issue 2865883002: Unit-test CAPI and CNG SSLPrivateKey implementations. (Closed)
Patch Set: rebase 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
« no previous file with comments | « 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 <string>
8 #include <vector>
9
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "crypto/scoped_capi_types.h"
13 #include "net/cert/x509_certificate.h"
14 #include "net/ssl/ssl_private_key.h"
15 #include "net/ssl/ssl_private_key_test_util.h"
16 #include "net/test/cert_test_util.h"
17 #include "net/test/test_data_directory.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "third_party/boringssl/src/include/openssl/bn.h"
20 #include "third_party/boringssl/src/include/openssl/bytestring.h"
21 #include "third_party/boringssl/src/include/openssl/ec.h"
22 #include "third_party/boringssl/src/include/openssl/ec_key.h"
23 #include "third_party/boringssl/src/include/openssl/evp.h"
24 #include "third_party/boringssl/src/include/openssl/mem.h"
25 #include "third_party/boringssl/src/include/openssl/rsa.h"
26
27 namespace net {
28
29 namespace {
30
31 struct TestKey {
32 const char* name;
33 const char* cert_file;
34 const char* key_file;
35 };
36
37 const TestKey kTestKeys[] = {
38 {"RSA", "client_1.pem", "client_1.pk8"},
39 {"ECDSA_P256", "client_4.pem", "client_4.pk8"},
40 {"ECDSA_P384", "client_5.pem", "client_5.pk8"},
41 {"ECDSA_P521", "client_6.pem", "client_6.pk8"},
42 };
43
44 std::string TestKeyToString(const testing::TestParamInfo<TestKey>& params) {
45 return params.param.name;
46 }
47
48 class ScopedNCRYPT_PROV_HANDLE {
49 public:
50 ScopedNCRYPT_PROV_HANDLE(NCRYPT_PROV_HANDLE prov) : prov_(prov) {}
51 ~ScopedNCRYPT_PROV_HANDLE() { NCryptFreeObject(prov_); }
52
53 private:
54 NCRYPT_PROV_HANDLE prov_;
55 };
56
57 // Appends |bn| to |cbb|, represented as |len| bytes in little-endian order,
58 // zero-padded as needed. Returns true on success and false if |len| is too
59 // small.
60 bool AddBIGNUMLittleEndian(CBB* cbb, const BIGNUM* bn, size_t len) {
61 uint8_t* ptr;
62 return CBB_add_space(cbb, &ptr, len) && BN_bn2le_padded(ptr, len, bn);
63 }
64
65 // Converts the PKCS#8 PrivateKeyInfo structure serialized in |pkcs8| to a
66 // private key BLOB, suitable for import with CAPI using Microsoft Base
67 // Cryptographic Provider.
68 bool PKCS8ToBLOBForCAPI(const std::string& pkcs8, std::vector<uint8_t>* blob) {
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 // Appends |bn| to |cbb|, represented as |len| bytes in big-endian order,
113 // zero-padded as needed. Returns true on success and false if |len| is too
114 // small.
115 bool AddBIGNUMBigEndian(CBB* cbb, const BIGNUM* bn, size_t len) {
116 uint8_t* ptr;
117 return CBB_add_space(cbb, &ptr, len) && BN_bn2bin_padded(ptr, len, bn);
118 }
119
120 // Converts the PKCS#8 PrivateKeyInfo structure serialized in |pkcs8| to a
121 // private key BLOB, suitable for import with CNG using the Microsoft Software
122 // KSP, and sets |*blob_type| to the type of the BLOB.
123 bool PKCS8ToBLOBForCNG(const std::string& pkcs8,
124 LPCWSTR* blob_type,
125 std::vector<uint8_t>* blob) {
126 CBS cbs;
127 CBS_init(&cbs, reinterpret_cast<const uint8_t*>(pkcs8.data()), pkcs8.size());
128 bssl::UniquePtr<EVP_PKEY> key(EVP_parse_private_key(&cbs));
129 if (!key || CBS_len(&cbs) != 0)
130 return false;
131
132 if (EVP_PKEY_id(key.get()) == EVP_PKEY_RSA) {
133 // See
134 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375531(v=vs.85 ).aspx.
135 const RSA* rsa = EVP_PKEY_get0_RSA(key.get());
136 BCRYPT_RSAKEY_BLOB header = {0};
137 header.Magic = BCRYPT_RSAFULLPRIVATE_MAGIC;
138 header.BitLength = BN_num_bits(rsa->n);
139 header.cbPublicExp = BN_num_bytes(rsa->e);
140 header.cbModulus = BN_num_bytes(rsa->n);
141 header.cbPrime1 = BN_num_bytes(rsa->p);
142 header.cbPrime2 = BN_num_bytes(rsa->q);
143
144 uint8_t* blob_data;
145 size_t blob_len;
146 bssl::ScopedCBB cbb;
147 if (!CBB_init(cbb.get(), sizeof(header) + pkcs8.size()) ||
148 !CBB_add_bytes(cbb.get(), reinterpret_cast<const uint8_t*>(&header),
149 sizeof(header)) ||
150 !AddBIGNUMBigEndian(cbb.get(), rsa->e, header.cbPublicExp) ||
151 !AddBIGNUMBigEndian(cbb.get(), rsa->n, header.cbModulus) ||
152 !AddBIGNUMBigEndian(cbb.get(), rsa->p, header.cbPrime1) ||
153 !AddBIGNUMBigEndian(cbb.get(), rsa->q, header.cbPrime2) ||
154 !AddBIGNUMBigEndian(cbb.get(), rsa->dmp1, header.cbPrime1) ||
155 !AddBIGNUMBigEndian(cbb.get(), rsa->dmq1, header.cbPrime2) ||
156 !AddBIGNUMBigEndian(cbb.get(), rsa->iqmp, header.cbPrime1) ||
157 !AddBIGNUMBigEndian(cbb.get(), rsa->d, header.cbModulus) ||
158 !CBB_finish(cbb.get(), &blob_data, &blob_len)) {
159 return false;
160 }
161
162 *blob_type = BCRYPT_RSAFULLPRIVATE_BLOB;
163 blob->assign(blob_data, blob_data + blob_len);
164 OPENSSL_free(blob_data);
165 return true;
166 }
167
168 if (EVP_PKEY_id(key.get()) == EVP_PKEY_EC) {
169 // See
170 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375520(v=vs.85 ).aspx.
171 const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(key.get());
172 const EC_GROUP* group = EC_KEY_get0_group(ec_key);
173 bssl::UniquePtr<BIGNUM> x(BN_new());
174 bssl::UniquePtr<BIGNUM> y(BN_new());
175 if (!EC_POINT_get_affine_coordinates_GFp(
176 group, EC_KEY_get0_public_key(ec_key), x.get(), y.get(), nullptr)) {
177 return false;
178 }
179
180 BCRYPT_ECCKEY_BLOB header = {0};
181 switch (EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key))) {
182 case NID_X9_62_prime256v1:
183 header.dwMagic = BCRYPT_ECDSA_PRIVATE_P256_MAGIC;
184 break;
185 case NID_secp384r1:
186 header.dwMagic = BCRYPT_ECDSA_PRIVATE_P384_MAGIC;
187 break;
188 case NID_secp521r1:
189 header.dwMagic = BCRYPT_ECDSA_PRIVATE_P521_MAGIC;
190 break;
191 default:
192 return false;
193 }
194 header.cbKey = BN_num_bytes(EC_GROUP_get0_order(group));
195
196 uint8_t* blob_data;
197 size_t blob_len;
198 bssl::ScopedCBB cbb;
199 if (!CBB_init(cbb.get(), sizeof(header) + header.cbKey * 3) ||
200 !CBB_add_bytes(cbb.get(), reinterpret_cast<const uint8_t*>(&header),
201 sizeof(header)) ||
202 !AddBIGNUMBigEndian(cbb.get(), x.get(), header.cbKey) ||
203 !AddBIGNUMBigEndian(cbb.get(), y.get(), header.cbKey) ||
204 !AddBIGNUMBigEndian(cbb.get(), EC_KEY_get0_private_key(ec_key),
205 header.cbKey) ||
206 !CBB_finish(cbb.get(), &blob_data, &blob_len)) {
207 return false;
208 }
209
210 *blob_type = BCRYPT_ECCPRIVATE_BLOB;
211 blob->assign(blob_data, blob_data + blob_len);
212 OPENSSL_free(blob_data);
213 return true;
214 }
215
216 return false;
217 }
218
219 } // namespace
220
221 class SSLPlatformKeyCNGTest : public testing::TestWithParam<TestKey> {};
222
223 TEST_P(SSLPlatformKeyCNGTest, KeyMatches) {
224 const TestKey& test_key = GetParam();
225
226 // Load test data.
227 scoped_refptr<X509Certificate> cert =
228 ImportCertFromFile(GetTestCertsDirectory(), test_key.cert_file);
229 ASSERT_TRUE(cert);
230
231 std::string pkcs8;
232 base::FilePath pkcs8_path =
233 GetTestCertsDirectory().AppendASCII(test_key.key_file);
234 ASSERT_TRUE(base::ReadFileToString(pkcs8_path, &pkcs8));
235
236 // Import the key into CNG. Per MSDN's documentation on NCryptImportKey, if a
237 // key name is not supplied (via the pParameterList parameter for the BLOB
238 // types we use), the Microsoft Software KSP will treat the key as ephemeral.
239 //
240 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa376276(v=vs.85). aspx
241 NCRYPT_PROV_HANDLE prov;
242 SECURITY_STATUS status =
243 NCryptOpenStorageProvider(&prov, MS_KEY_STORAGE_PROVIDER, 0);
244 ASSERT_FALSE(FAILED(status)) << status;
245 ScopedNCRYPT_PROV_HANDLE scoped_prov(prov);
246
247 LPCWSTR blob_type;
248 std::vector<uint8_t> blob;
249 ASSERT_TRUE(PKCS8ToBLOBForCNG(pkcs8, &blob_type, &blob));
250 NCRYPT_KEY_HANDLE ncrypt_key;
251 status = NCryptImportKey(prov, 0 /* hImportKey */, blob_type,
252 nullptr /* pParameterList */, &ncrypt_key,
253 blob.data(), blob.size(), NCRYPT_SILENT_FLAG);
254 ASSERT_FALSE(FAILED(status)) << status;
255
256 scoped_refptr<SSLPrivateKey> key = WrapCNGPrivateKey(cert.get(), ncrypt_key);
257 ASSERT_TRUE(key);
258
259 std::vector<SSLPrivateKey::Hash> expected_hashes = {
260 SSLPrivateKey::Hash::SHA512, SSLPrivateKey::Hash::SHA384,
261 SSLPrivateKey::Hash::SHA256, SSLPrivateKey::Hash::SHA1,
262 };
263 EXPECT_EQ(expected_hashes, key->GetDigestPreferences());
264
265 TestSSLPrivateKeyMatches(key.get(), pkcs8);
266 }
267
268 INSTANTIATE_TEST_CASE_P(,
269 SSLPlatformKeyCNGTest,
270 testing::ValuesIn(kTestKeys),
271 TestKeyToString);
272
273 TEST(SSLPlatformKeyCAPITest, KeyMatches) {
274 // Load test data.
275 scoped_refptr<X509Certificate> cert =
276 ImportCertFromFile(GetTestCertsDirectory(), "client_1.pem");
277 ASSERT_TRUE(cert);
278
279 std::string pkcs8;
280 base::FilePath pkcs8_path =
281 GetTestCertsDirectory().AppendASCII("client_1.pk8");
282 ASSERT_TRUE(base::ReadFileToString(pkcs8_path, &pkcs8));
283
284 // Import the key into CAPI. Use CRYPT_VERIFYCONTEXT for an ephemeral key.
285 crypto::ScopedHCRYPTPROV prov;
286 ASSERT_NE(FALSE,
287 CryptAcquireContext(prov.receive(), nullptr, nullptr, PROV_RSA_AES,
288 CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
289 << GetLastError();
290
291 std::vector<uint8_t> blob;
292 ASSERT_TRUE(PKCS8ToBLOBForCAPI(pkcs8, &blob));
293
294 crypto::ScopedHCRYPTKEY hcryptkey;
295 ASSERT_NE(FALSE, CryptImportKey(prov.get(), blob.data(), blob.size(),
296 0 /* hPubKey */, 0 /* dwFlags */,
297 hcryptkey.receive()))
298 << GetLastError();
299 // Release |hcryptkey| so it does not outlive |prov|.
300 hcryptkey.reset();
301
302 scoped_refptr<SSLPrivateKey> key =
303 WrapCAPIPrivateKey(cert.get(), prov.release(), AT_SIGNATURE);
304 ASSERT_TRUE(key);
305
306 std::vector<SSLPrivateKey::Hash> expected_hashes = {
307 SSLPrivateKey::Hash::SHA1, SSLPrivateKey::Hash::SHA512,
308 SSLPrivateKey::Hash::SHA384, SSLPrivateKey::Hash::SHA256,
309 };
310 EXPECT_EQ(expected_hashes, key->GetDigestPreferences());
311
312 TestSSLPrivateKeyMatches(key.get(), pkcs8);
313 }
314
315 } // namespace net
OLDNEW
« no previous file with comments | « 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