1 // Copyright 2014 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 <stddef.h>
6 #include <stdint.h>
7
8 #include "base/stl_util.h"
9 #include "components/webcrypto/algorithm_dispatch.h"
10 #include "components/webcrypto/algorithms/test_helpers.h"
11 #include "components/webcrypto/crypto_data.h"
12 #include "components/webcrypto/jwk.h"
13 #include "components/webcrypto/status.h"
14 #include "third_party/blink/public/platform/web_crypto_algorithm_params.h"
15 #include "third_party/blink/public/platform/web_crypto_key_algorithm.h"
16
17 namespace webcrypto {
18
19 namespace {
20
CreateEcdsaKeyGenAlgorithm(blink::WebCryptoNamedCurve named_curve)21 blink::WebCryptoAlgorithm CreateEcdsaKeyGenAlgorithm(
22 blink::WebCryptoNamedCurve named_curve) {
23 return blink::WebCryptoAlgorithm::AdoptParamsAndCreate(
24 blink::kWebCryptoAlgorithmIdEcdsa,
25 new blink::WebCryptoEcKeyGenParams(named_curve));
26 }
27
CreateEcdsaImportAlgorithm(blink::WebCryptoNamedCurve named_curve)28 blink::WebCryptoAlgorithm CreateEcdsaImportAlgorithm(
29 blink::WebCryptoNamedCurve named_curve) {
30 return CreateEcImportAlgorithm(blink::kWebCryptoAlgorithmIdEcdsa,
31 named_curve);
32 }
33
CreateEcdsaAlgorithm(blink::WebCryptoAlgorithmId hash_id)34 blink::WebCryptoAlgorithm CreateEcdsaAlgorithm(
35 blink::WebCryptoAlgorithmId hash_id) {
36 return blink::WebCryptoAlgorithm::AdoptParamsAndCreate(
37 blink::kWebCryptoAlgorithmIdEcdsa,
38 new blink::WebCryptoEcdsaParams(CreateAlgorithm(hash_id)));
39 }
40
41 class WebCryptoEcdsaTest : public WebCryptoTestBase {};
42
43 // Generates some ECDSA key pairs. Validates basic properties on the keys, and
44 // ensures the serialized key (as JWK) is unique. This test does nothing to
45 // ensure that the keys are otherwise usable (by trying to sign/verify with
46 // them).
TEST_F(WebCryptoEcdsaTest,GenerateKeyIsRandom)47 TEST_F(WebCryptoEcdsaTest, GenerateKeyIsRandom) {
48 blink::WebCryptoNamedCurve named_curve = blink::kWebCryptoNamedCurveP256;
49
50 std::vector<std::vector<uint8_t>> serialized_keys;
51
52 // Generate a small sample of keys.
53 for (int j = 0; j < 4; ++j) {
54 blink::WebCryptoKey public_key;
55 blink::WebCryptoKey private_key;
56
57 ASSERT_EQ(Status::Success(),
58 GenerateKeyPair(CreateEcdsaKeyGenAlgorithm(named_curve), true,
59 blink::kWebCryptoKeyUsageSign, &public_key,
60 &private_key));
61
62 // Basic sanity checks on the generated key pair.
63 EXPECT_EQ(blink::kWebCryptoKeyTypePublic, public_key.GetType());
64 EXPECT_EQ(blink::kWebCryptoKeyTypePrivate, private_key.GetType());
65 EXPECT_EQ(named_curve, public_key.Algorithm().EcParams()->NamedCurve());
66 EXPECT_EQ(named_curve, private_key.Algorithm().EcParams()->NamedCurve());
67
68 // Export the key pair to JWK.
69 std::vector<uint8_t> key_bytes;
70 ASSERT_EQ(Status::Success(),
71 ExportKey(blink::kWebCryptoKeyFormatJwk, public_key, &key_bytes));
72 serialized_keys.push_back(key_bytes);
73
74 ASSERT_EQ(Status::Success(), ExportKey(blink::kWebCryptoKeyFormatJwk,
75 private_key, &key_bytes));
76 serialized_keys.push_back(key_bytes);
77 }
78
79 // Ensure all entries in the key sample set are unique. This is a simplistic
80 // estimate of whether the generated keys appear random.
81 EXPECT_FALSE(CopiesExist(serialized_keys));
82 }
83
TEST_F(WebCryptoEcdsaTest,GenerateKeyEmptyUsage)84 TEST_F(WebCryptoEcdsaTest, GenerateKeyEmptyUsage) {
85 blink::WebCryptoNamedCurve named_curve = blink::kWebCryptoNamedCurveP256;
86 blink::WebCryptoKey public_key;
87 blink::WebCryptoKey private_key;
88 ASSERT_EQ(Status::ErrorCreateKeyEmptyUsages(),
89 GenerateKeyPair(CreateEcdsaKeyGenAlgorithm(named_curve), true, 0,
90 &public_key, &private_key));
91 }
92
93 // Verify that ECDSA signatures are probabilistic. Signing the same message two
94 // times should yield different signatures. However both signatures should
95 // verify correctly.
TEST_F(WebCryptoEcdsaTest,SignatureIsRandom)96 TEST_F(WebCryptoEcdsaTest, SignatureIsRandom) {
97 // Import a public and private keypair from "ec_private_keys.json". It doesn't
98 // really matter which one is used since they are all valid. In this case
99 // using the first one.
100 base::ListValue private_keys;
101 ASSERT_TRUE(ReadJsonTestFileToList("ec_private_keys.json", &private_keys));
102 const base::DictionaryValue* key_dict;
103 ASSERT_TRUE(private_keys.GetDictionary(0, &key_dict));
104 blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(key_dict);
105 const base::DictionaryValue* key_jwk;
106 ASSERT_TRUE(key_dict->GetDictionary("jwk", &key_jwk));
107
108 blink::WebCryptoKey private_key;
109 ASSERT_EQ(
110 Status::Success(),
111 ImportKeyJwkFromDict(*key_jwk, CreateEcdsaImportAlgorithm(curve), true,
112 blink::kWebCryptoKeyUsageSign, &private_key));
113
114 // Erase the "d" member so the private key JWK can be used to import the
115 // public key (WebCrypto doesn't provide a mechanism for importing a public
116 // key given a private key).
117 std::unique_ptr<base::DictionaryValue> key_jwk_copy(key_jwk->DeepCopy());
118 key_jwk_copy->Remove("d", nullptr);
119 blink::WebCryptoKey public_key;
120 ASSERT_EQ(
121 Status::Success(),
122 ImportKeyJwkFromDict(*key_jwk_copy, CreateEcdsaImportAlgorithm(curve),
123 true, blink::kWebCryptoKeyUsageVerify, &public_key));
124
125 // Sign twice
126 std::vector<uint8_t> message(10);
127 blink::WebCryptoAlgorithm algorithm =
128 CreateEcdsaAlgorithm(blink::kWebCryptoAlgorithmIdSha1);
129
130 std::vector<uint8_t> signature1;
131 std::vector<uint8_t> signature2;
132 ASSERT_EQ(Status::Success(),
133 Sign(algorithm, private_key, CryptoData(message), &signature1));
134 ASSERT_EQ(Status::Success(),
135 Sign(algorithm, private_key, CryptoData(message), &signature2));
136
137 // The two signatures should be different.
138 EXPECT_NE(CryptoData(signature1), CryptoData(signature2));
139
140 // And both should be valid signatures which can be verified.
141 bool signature_matches;
142 ASSERT_EQ(Status::Success(),
143 Verify(algorithm, public_key, CryptoData(signature1),
144 CryptoData(message), &signature_matches));
145 EXPECT_TRUE(signature_matches);
146 ASSERT_EQ(Status::Success(),
147 Verify(algorithm, public_key, CryptoData(signature2),
148 CryptoData(message), &signature_matches));
149 EXPECT_TRUE(signature_matches);
150 }
151
152 // Tests verify() for ECDSA using an assortment of keys, curves and hashes.
153 // These tests also include expected failures for bad signatures and keys.
TEST_F(WebCryptoEcdsaTest,VerifyKnownAnswer)154 TEST_F(WebCryptoEcdsaTest, VerifyKnownAnswer) {
155 base::ListValue tests;
156 ASSERT_TRUE(ReadJsonTestFileToList("ecdsa.json", &tests));
157
158 for (size_t test_index = 0; test_index < tests.GetSize(); ++test_index) {
159 SCOPED_TRACE(test_index);
160
161 const base::DictionaryValue* test;
162 ASSERT_TRUE(tests.GetDictionary(test_index, &test));
163
164 blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(test);
165 blink::WebCryptoKeyFormat key_format = GetKeyFormatFromJsonTestCase(test);
166 std::vector<uint8_t> key_data =
167 GetKeyDataFromJsonTestCase(test, key_format);
168
169 // If the test didn't specify an error, that implies it expects success.
170 std::string expected_error = "Success";
171 test->GetString("error", &expected_error);
172
173 // Import the public key.
174 blink::WebCryptoKey key;
175 Status status = ImportKey(key_format, CryptoData(key_data),
176 CreateEcdsaImportAlgorithm(curve), true,
177 blink::kWebCryptoKeyUsageVerify, &key);
178 ASSERT_EQ(expected_error, StatusToString(status));
179 if (status.IsError())
180 continue;
181
182 // Basic sanity checks on the imported public key.
183 EXPECT_EQ(blink::kWebCryptoKeyTypePublic, key.GetType());
184 EXPECT_EQ(blink::kWebCryptoKeyUsageVerify, key.Usages());
185 EXPECT_EQ(curve, key.Algorithm().EcParams()->NamedCurve());
186
187 // Now try to verify the given message and signature.
188 std::vector<uint8_t> message = GetBytesFromHexString(test, "msg");
189 std::vector<uint8_t> signature = GetBytesFromHexString(test, "sig");
190 blink::WebCryptoAlgorithm hash = GetDigestAlgorithm(test, "hash");
191
192 bool verify_result;
193 status = Verify(CreateEcdsaAlgorithm(hash.Id()), key, CryptoData(signature),
194 CryptoData(message), &verify_result);
195 ASSERT_EQ(expected_error, StatusToString(status));
196 if (status.IsError())
197 continue;
198
199 // If no error was expected, the verification's boolean must match
200 // "verify_result" for the test.
201 bool expected_result = false;
202 ASSERT_TRUE(test->GetBoolean("verify_result", &expected_result));
203 EXPECT_EQ(expected_result, verify_result);
204 }
205 }
206
207 // The test file may include either public or private keys. In order to import
208 // them successfully, the correct usages need to be specified. This function
209 // determines what usages to use for the key.
GetExpectedUsagesForKeyImport(blink::WebCryptoKeyFormat key_format,const base::DictionaryValue * test)210 blink::WebCryptoKeyUsageMask GetExpectedUsagesForKeyImport(
211 blink::WebCryptoKeyFormat key_format,
212 const base::DictionaryValue* test) {
213 blink::WebCryptoKeyUsageMask kPublicUsages = blink::kWebCryptoKeyUsageVerify;
214 blink::WebCryptoKeyUsageMask kPrivateUsages = blink::kWebCryptoKeyUsageSign;
215
216 switch (key_format) {
217 case blink::kWebCryptoKeyFormatRaw:
218 case blink::kWebCryptoKeyFormatSpki:
219 return kPublicUsages;
220 case blink::kWebCryptoKeyFormatPkcs8:
221 return kPrivateUsages;
222 case blink::kWebCryptoKeyFormatJwk: {
223 const base::DictionaryValue* key = nullptr;
224 if (!test->GetDictionary("key", &key))
225 ADD_FAILURE() << "Missing key property";
226 return key->HasKey("d") ? kPrivateUsages : kPublicUsages;
227 }
228 }
229
230 // Appease compiler.
231 return kPrivateUsages;
232 }
233
234 // Tests importing bad public/private keys in a variety of formats.
TEST_F(WebCryptoEcdsaTest,ImportBadKeys)235 TEST_F(WebCryptoEcdsaTest, ImportBadKeys) {
236 base::ListValue tests;
237 ASSERT_TRUE(ReadJsonTestFileToList("bad_ec_keys.json", &tests));
238
239 for (size_t test_index = 0; test_index < tests.GetSize(); ++test_index) {
240 SCOPED_TRACE(test_index);
241
242 const base::DictionaryValue* test;
243 ASSERT_TRUE(tests.GetDictionary(test_index, &test));
244
245 blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(test);
246 blink::WebCryptoKeyFormat key_format = GetKeyFormatFromJsonTestCase(test);
247 std::vector<uint8_t> key_data =
248 GetKeyDataFromJsonTestCase(test, key_format);
249 std::string expected_error;
250 ASSERT_TRUE(test->GetString("error", &expected_error));
251
252 blink::WebCryptoKey key;
253 Status status = ImportKey(
254 key_format, CryptoData(key_data), CreateEcdsaImportAlgorithm(curve),
255 true, GetExpectedUsagesForKeyImport(key_format, test), &key);
256 ASSERT_EQ(expected_error, StatusToString(status));
257 }
258 }
259
260 // Tests importing and exporting of EC private keys, using both JWK and PKCS8
261 // formats.
262 //
263 // The test imports a key first using JWK, and then exporting it to JWK and
264 // PKCS8. It does the same thing using PKCS8 as the original source of truth.
TEST_F(WebCryptoEcdsaTest,ImportExportPrivateKey)265 TEST_F(WebCryptoEcdsaTest, ImportExportPrivateKey) {
266 base::ListValue tests;
267 ASSERT_TRUE(ReadJsonTestFileToList("ec_private_keys.json", &tests));
268
269 for (size_t test_index = 0; test_index < tests.GetSize(); ++test_index) {
270 SCOPED_TRACE(test_index);
271
272 const base::DictionaryValue* test;
273 ASSERT_TRUE(tests.GetDictionary(test_index, &test));
274
275 blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(test);
276 const base::DictionaryValue* jwk_dict;
277 EXPECT_TRUE(test->GetDictionary("jwk", &jwk_dict));
278 std::vector<uint8_t> jwk_bytes = MakeJsonVector(*jwk_dict);
279 std::vector<uint8_t> pkcs8_bytes = GetBytesFromHexString(
280 test, test->HasKey("exported_pkcs8") ? "exported_pkcs8" : "pkcs8");
281
282 // -------------------------------------------------
283 // Test from JWK, and then export to {JWK, PKCS8}
284 // -------------------------------------------------
285
286 // Import the key using JWK
287 blink::WebCryptoKey key;
288 ASSERT_EQ(Status::Success(),
289 ImportKey(blink::kWebCryptoKeyFormatJwk, CryptoData(jwk_bytes),
290 CreateEcdsaImportAlgorithm(curve), true,
291 blink::kWebCryptoKeyUsageSign, &key));
292
293 // Export the key as JWK
294 std::vector<uint8_t> exported_bytes;
295 ASSERT_EQ(Status::Success(),
296 ExportKey(blink::kWebCryptoKeyFormatJwk, key, &exported_bytes));
297
298 // NOTE: The exported bytes can't be directly compared to jwk_bytes because
299 // the exported JWK differs from the imported one. In particular it contains
300 // extra properties for extractability and key_ops.
301 //
302 // Verification is instead done by using the first exported JWK bytes as the
303 // expectation.
304 jwk_bytes = exported_bytes;
305 ASSERT_EQ(Status::Success(),
306 ImportKey(blink::kWebCryptoKeyFormatJwk, CryptoData(jwk_bytes),
307 CreateEcdsaImportAlgorithm(curve), true,
308 blink::kWebCryptoKeyUsageSign, &key));
309
310 // Export the key as JWK (again)
311 ASSERT_EQ(Status::Success(),
312 ExportKey(blink::kWebCryptoKeyFormatJwk, key, &exported_bytes));
313 EXPECT_EQ(CryptoData(jwk_bytes), CryptoData(exported_bytes));
314
315 // Export the key as PKCS8
316 ASSERT_EQ(Status::Success(),
317 ExportKey(blink::kWebCryptoKeyFormatPkcs8, key, &exported_bytes));
318 EXPECT_EQ(CryptoData(pkcs8_bytes), CryptoData(exported_bytes));
319
320 // -------------------------------------------------
321 // Test from PKCS8, and then export to {JWK, PKCS8}
322 // -------------------------------------------------
323
324 // The imported PKCS8 bytes may differ from the exported bytes (in the case
325 // where the publicKey was missing, it will be synthesized and written back
326 // during export).
327 std::vector<uint8_t> pkcs8_input_bytes = GetBytesFromHexString(
328 test, test->HasKey("original_pkcs8") ? "original_pkcs8" : "pkcs8");
329 CryptoData pkcs8_input_data(pkcs8_input_bytes.empty() ? pkcs8_bytes
330 : pkcs8_input_bytes);
331
332 // Import the key using PKCS8
333 ASSERT_EQ(Status::Success(),
334 ImportKey(blink::kWebCryptoKeyFormatPkcs8, pkcs8_input_data,
335 CreateEcdsaImportAlgorithm(curve), true,
336 blink::kWebCryptoKeyUsageSign, &key));
337
338 // Export the key as PKCS8
339 ASSERT_EQ(Status::Success(),
340 ExportKey(blink::kWebCryptoKeyFormatPkcs8, key, &exported_bytes));
341 EXPECT_EQ(CryptoData(pkcs8_bytes), CryptoData(exported_bytes));
342
343 // Export the key as JWK
344 ASSERT_EQ(Status::Success(),
345 ExportKey(blink::kWebCryptoKeyFormatJwk, key, &exported_bytes));
346 EXPECT_EQ(CryptoData(jwk_bytes), CryptoData(exported_bytes));
347 }
348 }
349
350 } // namespace
351
352 } // namespace webcrypto
353