123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- // Copyright 2014 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include <stddef.h>
- #include <stdint.h>
- #include "base/containers/span.h"
- #include "components/webcrypto/algorithm_dispatch.h"
- #include "components/webcrypto/algorithms/test_helpers.h"
- #include "components/webcrypto/jwk.h"
- #include "components/webcrypto/status.h"
- #include "third_party/blink/public/platform/web_crypto_algorithm_params.h"
- #include "third_party/blink/public/platform/web_crypto_key_algorithm.h"
- namespace webcrypto {
- namespace {
- blink::WebCryptoAlgorithm CreateEcdsaKeyGenAlgorithm(
- blink::WebCryptoNamedCurve named_curve) {
- return blink::WebCryptoAlgorithm::AdoptParamsAndCreate(
- blink::kWebCryptoAlgorithmIdEcdsa,
- new blink::WebCryptoEcKeyGenParams(named_curve));
- }
- blink::WebCryptoAlgorithm CreateEcdsaImportAlgorithm(
- blink::WebCryptoNamedCurve named_curve) {
- return CreateEcImportAlgorithm(blink::kWebCryptoAlgorithmIdEcdsa,
- named_curve);
- }
- blink::WebCryptoAlgorithm CreateEcdsaAlgorithm(
- blink::WebCryptoAlgorithmId hash_id) {
- return blink::WebCryptoAlgorithm::AdoptParamsAndCreate(
- blink::kWebCryptoAlgorithmIdEcdsa,
- new blink::WebCryptoEcdsaParams(CreateAlgorithm(hash_id)));
- }
- class WebCryptoEcdsaTest : public WebCryptoTestBase {};
- // Generates some ECDSA key pairs. Validates basic properties on the keys, and
- // ensures the serialized key (as JWK) is unique. This test does nothing to
- // ensure that the keys are otherwise usable (by trying to sign/verify with
- // them).
- TEST_F(WebCryptoEcdsaTest, GenerateKeyIsRandom) {
- blink::WebCryptoNamedCurve named_curve = blink::kWebCryptoNamedCurveP256;
- std::vector<std::vector<uint8_t>> serialized_keys;
- // Generate a small sample of keys.
- for (int j = 0; j < 4; ++j) {
- blink::WebCryptoKey public_key;
- blink::WebCryptoKey private_key;
- ASSERT_EQ(Status::Success(),
- GenerateKeyPair(CreateEcdsaKeyGenAlgorithm(named_curve), true,
- blink::kWebCryptoKeyUsageSign, &public_key,
- &private_key));
- // Basic sanity checks on the generated key pair.
- EXPECT_EQ(blink::kWebCryptoKeyTypePublic, public_key.GetType());
- EXPECT_EQ(blink::kWebCryptoKeyTypePrivate, private_key.GetType());
- EXPECT_EQ(named_curve, public_key.Algorithm().EcParams()->NamedCurve());
- EXPECT_EQ(named_curve, private_key.Algorithm().EcParams()->NamedCurve());
- // Export the key pair to JWK.
- std::vector<uint8_t> key_bytes;
- ASSERT_EQ(Status::Success(),
- ExportKey(blink::kWebCryptoKeyFormatJwk, public_key, &key_bytes));
- serialized_keys.push_back(key_bytes);
- ASSERT_EQ(Status::Success(), ExportKey(blink::kWebCryptoKeyFormatJwk,
- private_key, &key_bytes));
- serialized_keys.push_back(key_bytes);
- }
- // Ensure all entries in the key sample set are unique. This is a simplistic
- // estimate of whether the generated keys appear random.
- EXPECT_FALSE(CopiesExist(serialized_keys));
- }
- TEST_F(WebCryptoEcdsaTest, GenerateKeyEmptyUsage) {
- blink::WebCryptoNamedCurve named_curve = blink::kWebCryptoNamedCurveP256;
- blink::WebCryptoKey public_key;
- blink::WebCryptoKey private_key;
- ASSERT_EQ(Status::ErrorCreateKeyEmptyUsages(),
- GenerateKeyPair(CreateEcdsaKeyGenAlgorithm(named_curve), true, 0,
- &public_key, &private_key));
- }
- // Verify that ECDSA signatures are probabilistic. Signing the same message two
- // times should yield different signatures. However both signatures should
- // verify correctly.
- TEST_F(WebCryptoEcdsaTest, SignatureIsRandom) {
- // Import a public and private keypair from "ec_private_keys.json". It doesn't
- // really matter which one is used since they are all valid. In this case
- // using the first one.
- base::Value::List private_keys =
- ReadJsonTestFileAsList("ec_private_keys.json");
- const base::Value& key_value = private_keys[0];
- ASSERT_TRUE(key_value.is_dict());
- const base::DictionaryValue* key_dict =
- &base::Value::AsDictionaryValue(key_value);
- blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(key_dict);
- const base::DictionaryValue* key_jwk;
- ASSERT_TRUE(key_dict->GetDictionary("jwk", &key_jwk));
- blink::WebCryptoKey private_key;
- ASSERT_EQ(
- Status::Success(),
- ImportKeyJwkFromDict(*key_jwk, CreateEcdsaImportAlgorithm(curve), true,
- blink::kWebCryptoKeyUsageSign, &private_key));
- // Erase the "d" member so the private key JWK can be used to import the
- // public key (WebCrypto doesn't provide a mechanism for importing a public
- // key given a private key).
- std::unique_ptr<base::DictionaryValue> key_jwk_copy =
- base::DictionaryValue::From(
- base::Value::ToUniquePtrValue(key_jwk->Clone()));
- key_jwk_copy->RemoveKey("d");
- blink::WebCryptoKey public_key;
- ASSERT_EQ(
- Status::Success(),
- ImportKeyJwkFromDict(*key_jwk_copy, CreateEcdsaImportAlgorithm(curve),
- true, blink::kWebCryptoKeyUsageVerify, &public_key));
- // Sign twice
- std::vector<uint8_t> message(10);
- blink::WebCryptoAlgorithm algorithm =
- CreateEcdsaAlgorithm(blink::kWebCryptoAlgorithmIdSha1);
- std::vector<uint8_t> signature1;
- std::vector<uint8_t> signature2;
- ASSERT_EQ(Status::Success(),
- Sign(algorithm, private_key, message, &signature1));
- ASSERT_EQ(Status::Success(),
- Sign(algorithm, private_key, message, &signature2));
- // The two signatures should be different.
- EXPECT_NE(signature1, signature2);
- // And both should be valid signatures which can be verified.
- bool signature_matches;
- ASSERT_EQ(Status::Success(), Verify(algorithm, public_key, signature1,
- message, &signature_matches));
- EXPECT_TRUE(signature_matches);
- ASSERT_EQ(Status::Success(), Verify(algorithm, public_key, signature2,
- message, &signature_matches));
- EXPECT_TRUE(signature_matches);
- }
- // Tests verify() for ECDSA using an assortment of keys, curves and hashes.
- // These tests also include expected failures for bad signatures and keys.
- TEST_F(WebCryptoEcdsaTest, VerifyKnownAnswer) {
- base::Value::List tests = ReadJsonTestFileAsList("ecdsa.json");
- for (const auto& test_value : tests) {
- SCOPED_TRACE(&test_value - &tests[0]);
- ASSERT_TRUE(test_value.is_dict());
- const base::DictionaryValue* test =
- &base::Value::AsDictionaryValue(test_value);
- blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(test);
- blink::WebCryptoKeyFormat key_format = GetKeyFormatFromJsonTestCase(test);
- std::vector<uint8_t> key_data =
- GetKeyDataFromJsonTestCase(test, key_format);
- // If the test didn't specify an error, that implies it expects success.
- std::string expected_error = "Success";
- test->GetString("error", &expected_error);
- // Import the public key.
- blink::WebCryptoKey key;
- Status status =
- ImportKey(key_format, key_data, CreateEcdsaImportAlgorithm(curve), true,
- blink::kWebCryptoKeyUsageVerify, &key);
- ASSERT_EQ(expected_error, StatusToString(status));
- if (status.IsError())
- continue;
- // Basic sanity checks on the imported public key.
- EXPECT_EQ(blink::kWebCryptoKeyTypePublic, key.GetType());
- EXPECT_EQ(blink::kWebCryptoKeyUsageVerify, key.Usages());
- EXPECT_EQ(curve, key.Algorithm().EcParams()->NamedCurve());
- // Now try to verify the given message and signature.
- std::vector<uint8_t> message = GetBytesFromHexString(test, "msg");
- std::vector<uint8_t> signature = GetBytesFromHexString(test, "sig");
- blink::WebCryptoAlgorithm hash = GetDigestAlgorithm(test, "hash");
- bool verify_result;
- status = Verify(CreateEcdsaAlgorithm(hash.Id()), key, signature, message,
- &verify_result);
- ASSERT_EQ(expected_error, StatusToString(status));
- if (status.IsError())
- continue;
- // If no error was expected, the verification's boolean must match
- // "verify_result" for the test.
- absl::optional<bool> expected_result = test->FindBoolKey("verify_result");
- ASSERT_TRUE(expected_result);
- EXPECT_EQ(expected_result.value(), verify_result);
- }
- }
- // The test file may include either public or private keys. In order to import
- // them successfully, the correct usages need to be specified. This function
- // determines what usages to use for the key.
- blink::WebCryptoKeyUsageMask GetExpectedUsagesForKeyImport(
- blink::WebCryptoKeyFormat key_format,
- const base::DictionaryValue* test) {
- blink::WebCryptoKeyUsageMask kPublicUsages = blink::kWebCryptoKeyUsageVerify;
- blink::WebCryptoKeyUsageMask kPrivateUsages = blink::kWebCryptoKeyUsageSign;
- switch (key_format) {
- case blink::kWebCryptoKeyFormatRaw:
- case blink::kWebCryptoKeyFormatSpki:
- return kPublicUsages;
- case blink::kWebCryptoKeyFormatPkcs8:
- return kPrivateUsages;
- case blink::kWebCryptoKeyFormatJwk: {
- const base::DictionaryValue* key = nullptr;
- if (!test->GetDictionary("key", &key))
- ADD_FAILURE() << "Missing key property";
- return key->FindKey("d") ? kPrivateUsages : kPublicUsages;
- }
- }
- // Appease compiler.
- return kPrivateUsages;
- }
- // Tests importing bad public/private keys in a variety of formats.
- TEST_F(WebCryptoEcdsaTest, ImportBadKeys) {
- base::Value::List tests = ReadJsonTestFileAsList("bad_ec_keys.json");
- for (const auto& test_value : tests) {
- SCOPED_TRACE(&test_value - &tests[0]);
- ASSERT_TRUE(test_value.is_dict());
- const base::DictionaryValue* test =
- &base::Value::AsDictionaryValue(test_value);
- blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(test);
- blink::WebCryptoKeyFormat key_format = GetKeyFormatFromJsonTestCase(test);
- std::vector<uint8_t> key_data =
- GetKeyDataFromJsonTestCase(test, key_format);
- std::string expected_error;
- ASSERT_TRUE(test->GetString("error", &expected_error));
- blink::WebCryptoKey key;
- Status status =
- ImportKey(key_format, key_data, CreateEcdsaImportAlgorithm(curve), true,
- GetExpectedUsagesForKeyImport(key_format, test), &key);
- ASSERT_EQ(expected_error, StatusToString(status));
- }
- }
- // Tests importing and exporting of EC private keys, using both JWK and PKCS8
- // formats.
- //
- // The test imports a key first using JWK, and then exporting it to JWK and
- // PKCS8. It does the same thing using PKCS8 as the original source of truth.
- TEST_F(WebCryptoEcdsaTest, ImportExportPrivateKey) {
- base::Value::List tests = ReadJsonTestFileAsList("ec_private_keys.json");
- for (const auto& test_value : tests) {
- SCOPED_TRACE(&test_value - &tests[0]);
- ASSERT_TRUE(test_value.is_dict());
- const base::DictionaryValue* test =
- &base::Value::AsDictionaryValue(test_value);
- blink::WebCryptoNamedCurve curve = GetCurveNameFromDictionary(test);
- const base::DictionaryValue* jwk_dict;
- EXPECT_TRUE(test->GetDictionary("jwk", &jwk_dict));
- std::vector<uint8_t> jwk_bytes = MakeJsonVector(*jwk_dict);
- std::vector<uint8_t> pkcs8_bytes = GetBytesFromHexString(
- test, test->FindKey("exported_pkcs8") ? "exported_pkcs8" : "pkcs8");
- // -------------------------------------------------
- // Test from JWK, and then export to {JWK, PKCS8}
- // -------------------------------------------------
- // Import the key using JWK
- blink::WebCryptoKey key;
- ASSERT_EQ(Status::Success(),
- ImportKey(blink::kWebCryptoKeyFormatJwk, jwk_bytes,
- CreateEcdsaImportAlgorithm(curve), true,
- blink::kWebCryptoKeyUsageSign, &key));
- // Export the key as JWK
- std::vector<uint8_t> exported_bytes;
- ASSERT_EQ(Status::Success(),
- ExportKey(blink::kWebCryptoKeyFormatJwk, key, &exported_bytes));
- // NOTE: The exported bytes can't be directly compared to jwk_bytes because
- // the exported JWK differs from the imported one. In particular it contains
- // extra properties for extractability and key_ops.
- //
- // Verification is instead done by using the first exported JWK bytes as the
- // expectation.
- jwk_bytes = exported_bytes;
- ASSERT_EQ(Status::Success(),
- ImportKey(blink::kWebCryptoKeyFormatJwk, jwk_bytes,
- CreateEcdsaImportAlgorithm(curve), true,
- blink::kWebCryptoKeyUsageSign, &key));
- // Export the key as JWK (again)
- ASSERT_EQ(Status::Success(),
- ExportKey(blink::kWebCryptoKeyFormatJwk, key, &exported_bytes));
- EXPECT_EQ(jwk_bytes, exported_bytes);
- // Export the key as PKCS8
- ASSERT_EQ(Status::Success(),
- ExportKey(blink::kWebCryptoKeyFormatPkcs8, key, &exported_bytes));
- EXPECT_EQ(pkcs8_bytes, exported_bytes);
- // -------------------------------------------------
- // Test from PKCS8, and then export to {JWK, PKCS8}
- // -------------------------------------------------
- // The imported PKCS8 bytes may differ from the exported bytes (in the case
- // where the publicKey was missing, it will be synthesized and written back
- // during export).
- std::vector<uint8_t> pkcs8_input_bytes = GetBytesFromHexString(
- test, test->FindKey("original_pkcs8") ? "original_pkcs8" : "pkcs8");
- base::span<const uint8_t> pkcs8_input_data(
- pkcs8_input_bytes.empty() ? pkcs8_bytes : pkcs8_input_bytes);
- // Import the key using PKCS8
- ASSERT_EQ(Status::Success(),
- ImportKey(blink::kWebCryptoKeyFormatPkcs8, pkcs8_input_data,
- CreateEcdsaImportAlgorithm(curve), true,
- blink::kWebCryptoKeyUsageSign, &key));
- // Export the key as PKCS8
- ASSERT_EQ(Status::Success(),
- ExportKey(blink::kWebCryptoKeyFormatPkcs8, key, &exported_bytes));
- EXPECT_EQ(pkcs8_bytes, exported_bytes);
- // Export the key as JWK
- ASSERT_EQ(Status::Success(),
- ExportKey(blink::kWebCryptoKeyFormatJwk, key, &exported_bytes));
- EXPECT_EQ(jwk_bytes, exported_bytes);
- }
- }
- } // namespace
- } // namespace webcrypto
|