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