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"
19 bool SupportsEcdsa() {
20 #if defined(USE_OPENSSL)
23 LOG(ERROR
) << "Skipping ECDSA test because unsupported";
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
53 TEST_F(WebCryptoEcdsaTest
, GenerateKeyIsRandom
) {
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
,
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
) {
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
108 TEST_F(WebCryptoEcdsaTest
, SignatureIsRandom
) {
109 if (!SupportsEcdsa())
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
;
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
));
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())
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())
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");
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())
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
;
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
;
250 return kPrivateUsages
;
253 // Tests importing bad public/private keys in a variety of formats.
254 TEST_F(WebCryptoEcdsaTest
, ImportBadKeys
) {
255 if (!SupportsEcdsa())
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
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())
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
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
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
));
377 } // namespace webcrypto