Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / webcrypto / algorithms / ecdsa_unittest.cc
blob6193c088fc2c2cf1393cdbac93cff6976f342c74
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.
5 #include "base/stl_util.h"
6 #include "components/webcrypto/algorithm_dispatch.h"
7 #include "components/webcrypto/algorithms/test_helpers.h"
8 #include "components/webcrypto/crypto_data.h"
9 #include "components/webcrypto/jwk.h"
10 #include "components/webcrypto/status.h"
11 #include "components/webcrypto/webcrypto_util.h"
12 #include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h"
13 #include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h"
15 namespace webcrypto {
17 namespace {
19 bool SupportsEcdsa() {
20 #if defined(USE_OPENSSL)
21 return true;
22 #else
23 LOG(ERROR) << "Skipping ECDSA test because unsupported";
24 return false;
25 #endif
28 blink::WebCryptoAlgorithm CreateEcdsaKeyGenAlgorithm(
29 blink::WebCryptoNamedCurve named_curve) {
30 return blink::WebCryptoAlgorithm::adoptParamsAndCreate(
31 blink::WebCryptoAlgorithmIdEcdsa,
32 new blink::WebCryptoEcKeyGenParams(named_curve));
35 blink::WebCryptoAlgorithm CreateEcdsaImportAlgorithm(
36 blink::WebCryptoNamedCurve named_curve) {
37 return CreateEcImportAlgorithm(blink::WebCryptoAlgorithmIdEcdsa, named_curve);
40 blink::WebCryptoAlgorithm CreateEcdsaAlgorithm(
41 blink::WebCryptoAlgorithmId hash_id) {
42 return blink::WebCryptoAlgorithm::adoptParamsAndCreate(
43 blink::WebCryptoAlgorithmIdEcdsa,
44 new blink::WebCryptoEcdsaParams(CreateAlgorithm(hash_id)));
47 class WebCryptoEcdsaTest : public WebCryptoTestBase {};
49 // Generates some ECDSA key pairs. Validates basic properties on the keys, and
50 // ensures the serialized key (as JWK) is unique. This test does nothing to
51 // ensure that the keys are otherwise usable (by trying to sign/verify with
52 // them).
53 TEST_F(WebCryptoEcdsaTest, GenerateKeyIsRandom) {
54 if (!SupportsEcdsa())
55 return;
57 blink::WebCryptoNamedCurve named_curve = blink::WebCryptoNamedCurveP256;
59 std::vector<std::vector<uint8_t>> serialized_keys;
61 // Generate a small sample of keys.
62 for (int j = 0; j < 4; ++j) {
63 blink::WebCryptoKey public_key;
64 blink::WebCryptoKey private_key;
66 ASSERT_EQ(Status::Success(),
67 GenerateKeyPair(CreateEcdsaKeyGenAlgorithm(named_curve), true,
68 blink::WebCryptoKeyUsageSign, &public_key,
69 &private_key));
71 // Basic sanity checks on the generated key pair.
72 EXPECT_EQ(blink::WebCryptoKeyTypePublic, public_key.type());
73 EXPECT_EQ(blink::WebCryptoKeyTypePrivate, private_key.type());
74 EXPECT_EQ(named_curve, public_key.algorithm().ecParams()->namedCurve());
75 EXPECT_EQ(named_curve, private_key.algorithm().ecParams()->namedCurve());
77 // Export the key pair to JWK.
78 std::vector<uint8_t> key_bytes;
79 ASSERT_EQ(Status::Success(),
80 ExportKey(blink::WebCryptoKeyFormatJwk, public_key, &key_bytes));
81 serialized_keys.push_back(key_bytes);
83 ASSERT_EQ(Status::Success(),
84 ExportKey(blink::WebCryptoKeyFormatJwk, private_key, &key_bytes));
85 serialized_keys.push_back(key_bytes);
88 // Ensure all entries in the key sample set are unique. This is a simplistic
89 // estimate of whether the generated keys appear random.
90 EXPECT_FALSE(CopiesExist(serialized_keys));
93 TEST_F(WebCryptoEcdsaTest, GenerateKeyEmptyUsage) {
94 if (!SupportsEcdsa())
95 return;
97 blink::WebCryptoNamedCurve named_curve = blink::WebCryptoNamedCurveP256;
98 blink::WebCryptoKey public_key;
99 blink::WebCryptoKey private_key;
100 ASSERT_EQ(Status::ErrorCreateKeyEmptyUsages(),
101 GenerateKeyPair(CreateEcdsaKeyGenAlgorithm(named_curve), true, 0,
102 &public_key, &private_key));
105 // Verify that ECDSA signatures are probabilistic. Signing the same message two
106 // times should yield different signatures. However both signatures should
107 // verify correctly.
108 TEST_F(WebCryptoEcdsaTest, SignatureIsRandom) {
109 if (!SupportsEcdsa())
110 return;
112 // Import a public and private keypair from "ec_private_keys.json". It doesn't
113 // really matter which one is used since they are all valid. In this case
114 // using the first one.
115 scoped_ptr<base::ListValue> private_keys;
116 ASSERT_TRUE(ReadJsonTestFileToList("ec_private_keys.json", &private_keys));
117 const base::DictionaryValue* key_dict;
118 ASSERT_TRUE(private_keys->GetDictionary(0, &key_dict));
119 blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(key_dict);
120 const base::DictionaryValue* key_jwk;
121 ASSERT_TRUE(key_dict->GetDictionary("jwk", &key_jwk));
123 blink::WebCryptoKey private_key;
124 ASSERT_EQ(
125 Status::Success(),
126 ImportKeyJwkFromDict(*key_jwk, CreateEcdsaImportAlgorithm(curve), true,
127 blink::WebCryptoKeyUsageSign, &private_key));
129 // Erase the "d" member so the private key JWK can be used to import the
130 // public key (WebCrypto doesn't provide a mechanism for importing a public
131 // key given a private key).
132 scoped_ptr<base::DictionaryValue> key_jwk_copy(key_jwk->DeepCopy());
133 key_jwk_copy->Remove("d", NULL);
134 blink::WebCryptoKey public_key;
135 ASSERT_EQ(Status::Success(),
136 ImportKeyJwkFromDict(*key_jwk_copy.get(),
137 CreateEcdsaImportAlgorithm(curve), true,
138 blink::WebCryptoKeyUsageVerify, &public_key));
140 // Sign twice
141 std::vector<uint8_t> message(10);
142 blink::WebCryptoAlgorithm algorithm =
143 CreateEcdsaAlgorithm(blink::WebCryptoAlgorithmIdSha1);
145 std::vector<uint8_t> signature1;
146 std::vector<uint8_t> signature2;
147 ASSERT_EQ(Status::Success(),
148 Sign(algorithm, private_key, CryptoData(message), &signature1));
149 ASSERT_EQ(Status::Success(),
150 Sign(algorithm, private_key, CryptoData(message), &signature2));
152 // The two signatures should be different.
153 EXPECT_NE(CryptoData(signature1), CryptoData(signature2));
155 // And both should be valid signatures which can be verified.
156 bool signature_matches;
157 ASSERT_EQ(Status::Success(),
158 Verify(algorithm, public_key, CryptoData(signature1),
159 CryptoData(message), &signature_matches));
160 EXPECT_TRUE(signature_matches);
161 ASSERT_EQ(Status::Success(),
162 Verify(algorithm, public_key, CryptoData(signature2),
163 CryptoData(message), &signature_matches));
164 EXPECT_TRUE(signature_matches);
167 // Tests verify() for ECDSA using an assortment of keys, curves and hashes.
168 // These tests also include expected failures for bad signatures and keys.
169 TEST_F(WebCryptoEcdsaTest, VerifyKnownAnswer) {
170 if (!SupportsEcdsa())
171 return;
173 scoped_ptr<base::ListValue> tests;
174 ASSERT_TRUE(ReadJsonTestFileToList("ecdsa.json", &tests));
176 for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) {
177 SCOPED_TRACE(test_index);
179 const base::DictionaryValue* test;
180 ASSERT_TRUE(tests->GetDictionary(test_index, &test));
182 blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(test);
183 blink::WebCryptoKeyFormat key_format = GetKeyFormatFromJsonTestCase(test);
184 std::vector<uint8_t> key_data =
185 GetKeyDataFromJsonTestCase(test, key_format);
187 // If the test didn't specify an error, that implies it expects success.
188 std::string expected_error = "Success";
189 test->GetString("error", &expected_error);
191 // Import the public key.
192 blink::WebCryptoKey key;
193 Status status = ImportKey(key_format, CryptoData(key_data),
194 CreateEcdsaImportAlgorithm(curve), true,
195 blink::WebCryptoKeyUsageVerify, &key);
196 ASSERT_EQ(expected_error, StatusToString(status));
197 if (status.IsError())
198 continue;
200 // Basic sanity checks on the imported public key.
201 EXPECT_EQ(blink::WebCryptoKeyTypePublic, key.type());
202 EXPECT_EQ(blink::WebCryptoKeyUsageVerify, key.usages());
203 EXPECT_EQ(curve, key.algorithm().ecParams()->namedCurve());
205 // Now try to verify the given message and signature.
206 std::vector<uint8_t> message = GetBytesFromHexString(test, "msg");
207 std::vector<uint8_t> signature = GetBytesFromHexString(test, "sig");
208 blink::WebCryptoAlgorithm hash = GetDigestAlgorithm(test, "hash");
210 bool verify_result;
211 status = Verify(CreateEcdsaAlgorithm(hash.id()), key, CryptoData(signature),
212 CryptoData(message), &verify_result);
213 ASSERT_EQ(expected_error, StatusToString(status));
214 if (status.IsError())
215 continue;
217 // If no error was expected, the verification's boolean must match
218 // "verify_result" for the test.
219 bool expected_result = false;
220 ASSERT_TRUE(test->GetBoolean("verify_result", &expected_result));
221 EXPECT_EQ(expected_result, verify_result);
225 // The test file may include either public or private keys. In order to import
226 // them successfully, the correct usages need to be specified. This function
227 // determines what usages to use for the key.
228 blink::WebCryptoKeyUsageMask GetExpectedUsagesForKeyImport(
229 blink::WebCryptoKeyFormat key_format,
230 const base::DictionaryValue* test) {
231 blink::WebCryptoKeyUsageMask kPublicUsages = blink::WebCryptoKeyUsageVerify;
232 blink::WebCryptoKeyUsageMask kPrivateUsages = blink::WebCryptoKeyUsageSign;
234 switch (key_format) {
235 case blink::WebCryptoKeyFormatRaw:
236 case blink::WebCryptoKeyFormatSpki:
237 return kPublicUsages;
238 case blink::WebCryptoKeyFormatPkcs8:
239 return kPrivateUsages;
240 break;
241 case blink::WebCryptoKeyFormatJwk: {
242 const base::DictionaryValue* key = NULL;
243 if (!test->GetDictionary("key", &key))
244 ADD_FAILURE() << "Missing key property";
245 return key->HasKey("d") ? kPrivateUsages : kPublicUsages;
249 // Appease compiler.
250 return kPrivateUsages;
253 // Tests importing bad public/private keys in a variety of formats.
254 TEST_F(WebCryptoEcdsaTest, ImportBadKeys) {
255 if (!SupportsEcdsa())
256 return;
258 scoped_ptr<base::ListValue> tests;
259 ASSERT_TRUE(ReadJsonTestFileToList("bad_ec_keys.json", &tests));
261 for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) {
262 SCOPED_TRACE(test_index);
264 const base::DictionaryValue* test;
265 ASSERT_TRUE(tests->GetDictionary(test_index, &test));
267 blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(test);
268 blink::WebCryptoKeyFormat key_format = GetKeyFormatFromJsonTestCase(test);
269 std::vector<uint8_t> key_data =
270 GetKeyDataFromJsonTestCase(test, key_format);
271 std::string expected_error;
272 ASSERT_TRUE(test->GetString("error", &expected_error));
274 blink::WebCryptoKey key;
275 Status status = ImportKey(
276 key_format, CryptoData(key_data), CreateEcdsaImportAlgorithm(curve),
277 true, GetExpectedUsagesForKeyImport(key_format, test), &key);
278 ASSERT_EQ(expected_error, StatusToString(status));
282 // Tests importing and exporting of EC private keys, using both JWK and PKCS8
283 // formats.
285 // The test imports a key first using JWK, and then exporting it to JWK and
286 // PKCS8. It does the same thing using PKCS8 as the original source of truth.
287 TEST_F(WebCryptoEcdsaTest, ImportExportPrivateKey) {
288 if (!SupportsEcdsa())
289 return;
291 scoped_ptr<base::ListValue> tests;
292 ASSERT_TRUE(ReadJsonTestFileToList("ec_private_keys.json", &tests));
294 for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) {
295 SCOPED_TRACE(test_index);
297 const base::DictionaryValue* test;
298 ASSERT_TRUE(tests->GetDictionary(test_index, &test));
300 blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(test);
301 const base::DictionaryValue* jwk_dict;
302 EXPECT_TRUE(test->GetDictionary("jwk", &jwk_dict));
303 std::vector<uint8_t> jwk_bytes = MakeJsonVector(*jwk_dict);
304 std::vector<uint8_t> pkcs8_bytes = GetBytesFromHexString(
305 test, test->HasKey("exported_pkcs8") ? "exported_pkcs8" : "pkcs8");
307 // -------------------------------------------------
308 // Test from JWK, and then export to {JWK, PKCS8}
309 // -------------------------------------------------
311 // Import the key using JWK
312 blink::WebCryptoKey key;
313 ASSERT_EQ(Status::Success(),
314 ImportKey(blink::WebCryptoKeyFormatJwk, CryptoData(jwk_bytes),
315 CreateEcdsaImportAlgorithm(curve), true,
316 blink::WebCryptoKeyUsageSign, &key));
318 // Export the key as JWK
319 std::vector<uint8_t> exported_bytes;
320 ASSERT_EQ(Status::Success(),
321 ExportKey(blink::WebCryptoKeyFormatJwk, key, &exported_bytes));
323 // NOTE: The exported bytes can't be directly compared to jwk_bytes because
324 // the exported JWK differs from the imported one. In particular it contains
325 // extra properties for extractability and key_ops.
327 // Verification is instead done by using the first exported JWK bytes as the
328 // expectation.
329 jwk_bytes = exported_bytes;
330 ASSERT_EQ(Status::Success(),
331 ImportKey(blink::WebCryptoKeyFormatJwk, CryptoData(jwk_bytes),
332 CreateEcdsaImportAlgorithm(curve), true,
333 blink::WebCryptoKeyUsageSign, &key));
335 // Export the key as JWK (again)
336 ASSERT_EQ(Status::Success(),
337 ExportKey(blink::WebCryptoKeyFormatJwk, key, &exported_bytes));
338 EXPECT_EQ(CryptoData(jwk_bytes), CryptoData(exported_bytes));
340 // Export the key as PKCS8
341 ASSERT_EQ(Status::Success(),
342 ExportKey(blink::WebCryptoKeyFormatPkcs8, key, &exported_bytes));
343 EXPECT_EQ(CryptoData(pkcs8_bytes), CryptoData(exported_bytes));
345 // -------------------------------------------------
346 // Test from PKCS8, and then export to {JWK, PKCS8}
347 // -------------------------------------------------
349 // The imported PKCS8 bytes may differ from the exported bytes (in the case
350 // where the publicKey was missing, it will be synthesized and written back
351 // during export).
352 std::vector<uint8_t> pkcs8_input_bytes = GetBytesFromHexString(
353 test, test->HasKey("original_pkcs8") ? "original_pkcs8" : "pkcs8");
354 CryptoData pkcs8_input_data(pkcs8_input_bytes.empty() ? pkcs8_bytes
355 : pkcs8_input_bytes);
357 // Import the key using PKCS8
358 ASSERT_EQ(Status::Success(),
359 ImportKey(blink::WebCryptoKeyFormatPkcs8, pkcs8_input_data,
360 CreateEcdsaImportAlgorithm(curve), true,
361 blink::WebCryptoKeyUsageSign, &key));
363 // Export the key as PKCS8
364 ASSERT_EQ(Status::Success(),
365 ExportKey(blink::WebCryptoKeyFormatPkcs8, key, &exported_bytes));
366 EXPECT_EQ(CryptoData(pkcs8_bytes), CryptoData(exported_bytes));
368 // Export the key as JWK
369 ASSERT_EQ(Status::Success(),
370 ExportKey(blink::WebCryptoKeyFormatJwk, key, &exported_bytes));
371 EXPECT_EQ(CryptoData(jwk_bytes), CryptoData(exported_bytes));
375 } // namespace
377 } // namespace webcrypto