| // 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/stl_util.h" |
| #include "components/webcrypto/algorithm_dispatch.h" |
| #include "components/webcrypto/algorithms/ec.h" |
| #include "components/webcrypto/algorithms/test_helpers.h" |
| #include "components/webcrypto/crypto_data.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 { |
| |
| // TODO(eroman): Test passing an RSA public key instead of ECDH key. |
| // TODO(eroman): Test passing an ECDSA public key |
| |
| blink::WebCryptoAlgorithm CreateEcdhImportAlgorithm( |
| blink::WebCryptoNamedCurve named_curve) { |
| return CreateEcImportAlgorithm(blink::kWebCryptoAlgorithmIdEcdh, named_curve); |
| } |
| |
| blink::WebCryptoAlgorithm CreateEcdhDeriveParams( |
| const blink::WebCryptoKey& public_key) { |
| return blink::WebCryptoAlgorithm::AdoptParamsAndCreate( |
| blink::kWebCryptoAlgorithmIdEcdh, |
| new blink::WebCryptoEcdhKeyDeriveParams(public_key)); |
| } |
| |
| blink::WebCryptoAlgorithm CreateAesGcmDerivedKeyParams(uint16_t length_bits) { |
| return blink::WebCryptoAlgorithm::AdoptParamsAndCreate( |
| blink::kWebCryptoAlgorithmIdAesGcm, |
| new blink::WebCryptoAesDerivedKeyParams(length_bits)); |
| } |
| |
| // Helper that loads a "public_key" and "private_key" from the test data. |
| bool ImportKeysFromTest(const base::DictionaryValue* test, |
| blink::WebCryptoKey* public_key, |
| blink::WebCryptoKey* private_key) { |
| // Import the public key. |
| const base::DictionaryValue* public_key_json = nullptr; |
| EXPECT_TRUE(test->GetDictionary("public_key", &public_key_json)); |
| blink::WebCryptoNamedCurve curve = |
| GetCurveNameFromDictionary(public_key_json); |
| EXPECT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatJwk, |
| CryptoData(MakeJsonVector(*public_key_json)), |
| CreateEcdhImportAlgorithm(curve), true, 0, public_key)); |
| |
| // If the test didn't specify an error for private key import, that implies |
| // it expects success. |
| std::string expected_private_key_error = "Success"; |
| test->GetString("private_key_error", &expected_private_key_error); |
| |
| // Import the private key. |
| const base::DictionaryValue* private_key_json = nullptr; |
| EXPECT_TRUE(test->GetDictionary("private_key", &private_key_json)); |
| curve = GetCurveNameFromDictionary(private_key_json); |
| Status status = ImportKey( |
| blink::kWebCryptoKeyFormatJwk, |
| CryptoData(MakeJsonVector(*private_key_json)), |
| CreateEcdhImportAlgorithm(curve), true, |
| blink::kWebCryptoKeyUsageDeriveBits | blink::kWebCryptoKeyUsageDeriveKey, |
| private_key); |
| EXPECT_EQ(expected_private_key_error, StatusToString(status)); |
| return status.IsSuccess(); |
| } |
| |
| class WebCryptoEcdhTest : public WebCryptoTestBase {}; |
| |
| TEST_F(WebCryptoEcdhTest, DeriveBitsKnownAnswer) { |
| base::ListValue tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("ecdh.json", &tests)); |
| |
| for (size_t test_index = 0; test_index < tests.GetSize(); ++test_index) { |
| SCOPED_TRACE(test_index); |
| |
| const base::DictionaryValue* test; |
| ASSERT_TRUE(tests.GetDictionary(test_index, &test)); |
| |
| // Import the keys. |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey private_key; |
| if (!ImportKeysFromTest(test, &public_key, &private_key)) |
| continue; |
| |
| // Now try to derive bytes. |
| std::vector<uint8_t> derived_bytes; |
| int length_bits = 0; |
| ASSERT_TRUE(test->GetInteger("length_bits", &length_bits)); |
| |
| // If the test didn't specify an error, that implies it expects success. |
| std::string expected_error = "Success"; |
| test->GetString("error", &expected_error); |
| |
| Status status = DeriveBits(CreateEcdhDeriveParams(public_key), private_key, |
| length_bits, &derived_bytes); |
| ASSERT_EQ(expected_error, StatusToString(status)); |
| if (status.IsError()) |
| continue; |
| |
| std::vector<uint8_t> expected_bytes = |
| GetBytesFromHexString(test, "derived_bytes"); |
| |
| EXPECT_EQ(CryptoData(expected_bytes), CryptoData(derived_bytes)); |
| } |
| } |
| |
| // Loads up a test ECDH public and private key for P-521. The keys |
| // come from different key pairs, and can be used for key derivation of up to |
| // 528 bits. |
| ::testing::AssertionResult LoadTestKeys(blink::WebCryptoKey* public_key, |
| blink::WebCryptoKey* private_key) { |
| base::ListValue tests; |
| if (!ReadJsonTestFileToList("ecdh.json", &tests)) |
| return ::testing::AssertionFailure() << "Failed loading ecdh.json"; |
| |
| const base::DictionaryValue* test = nullptr; |
| bool valid_p521_keys = false; |
| for (size_t test_index = 0; test_index < tests.GetSize(); ++test_index) { |
| SCOPED_TRACE(test_index); |
| EXPECT_TRUE(tests.GetDictionary(test_index, &test)); |
| test->GetBoolean("valid_p521_keys", &valid_p521_keys); |
| if (valid_p521_keys) |
| break; |
| } |
| if (!valid_p521_keys) { |
| return ::testing::AssertionFailure() |
| << "The P-521 test are missing in ecdh.json"; |
| } |
| |
| ImportKeysFromTest(test, public_key, private_key); |
| |
| EXPECT_EQ(blink::kWebCryptoNamedCurveP521, |
| public_key->Algorithm().EcParams()->NamedCurve()); |
| |
| return ::testing::AssertionSuccess(); |
| } |
| |
| // Try deriving an AES key of length 129 bits. |
| TEST_F(WebCryptoEcdhTest, DeriveKeyBadAesLength) { |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey base_key; |
| ASSERT_TRUE(LoadTestKeys(&public_key, &base_key)); |
| |
| blink::WebCryptoKey derived_key; |
| |
| ASSERT_EQ(Status::ErrorGetAesKeyLength(), |
| DeriveKey(CreateEcdhDeriveParams(public_key), base_key, |
| CreateAlgorithm(blink::kWebCryptoAlgorithmIdAesGcm), |
| CreateAesGcmDerivedKeyParams(129), true, |
| blink::kWebCryptoKeyUsageEncrypt, &derived_key)); |
| } |
| |
| // Try deriving an AES key of length 192 bits. |
| TEST_F(WebCryptoEcdhTest, DeriveKeyUnsupportedAesLength) { |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey base_key; |
| ASSERT_TRUE(LoadTestKeys(&public_key, &base_key)); |
| |
| blink::WebCryptoKey derived_key; |
| |
| ASSERT_EQ(Status::ErrorAes192BitUnsupported(), |
| DeriveKey(CreateEcdhDeriveParams(public_key), base_key, |
| CreateAlgorithm(blink::kWebCryptoAlgorithmIdAesGcm), |
| CreateAesGcmDerivedKeyParams(192), true, |
| blink::kWebCryptoKeyUsageEncrypt, &derived_key)); |
| } |
| |
| // Try deriving an HMAC key of length 0 bits. |
| TEST_F(WebCryptoEcdhTest, DeriveKeyZeroLengthHmac) { |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey base_key; |
| ASSERT_TRUE(LoadTestKeys(&public_key, &base_key)); |
| |
| blink::WebCryptoKey derived_key; |
| |
| const blink::WebCryptoAlgorithm import_algorithm = |
| CreateHmacImportAlgorithm(blink::kWebCryptoAlgorithmIdSha1, 0); |
| |
| ASSERT_EQ(Status::ErrorGetHmacKeyLengthZero(), |
| DeriveKey(CreateEcdhDeriveParams(public_key), base_key, |
| import_algorithm, import_algorithm, true, |
| blink::kWebCryptoKeyUsageSign, &derived_key)); |
| } |
| |
| // Derive an HMAC key of length 19 bits. |
| TEST_F(WebCryptoEcdhTest, DeriveKeyHmac19Bits) { |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey base_key; |
| ASSERT_TRUE(LoadTestKeys(&public_key, &base_key)); |
| |
| blink::WebCryptoKey derived_key; |
| |
| const blink::WebCryptoAlgorithm import_algorithm = |
| CreateHmacImportAlgorithm(blink::kWebCryptoAlgorithmIdSha1, 19); |
| |
| ASSERT_EQ(Status::Success(), |
| DeriveKey(CreateEcdhDeriveParams(public_key), base_key, |
| import_algorithm, import_algorithm, true, |
| blink::kWebCryptoKeyUsageSign, &derived_key)); |
| |
| ASSERT_EQ(blink::kWebCryptoAlgorithmIdHmac, derived_key.Algorithm().Id()); |
| ASSERT_EQ(blink::kWebCryptoAlgorithmIdSha1, |
| derived_key.Algorithm().HmacParams()->GetHash().Id()); |
| ASSERT_EQ(19u, derived_key.Algorithm().HmacParams()->LengthBits()); |
| |
| // Export the key and verify its contents. |
| std::vector<uint8_t> raw_key; |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::kWebCryptoKeyFormatRaw, derived_key, &raw_key)); |
| EXPECT_EQ(3u, raw_key.size()); |
| // The last 7 bits of the key should be zero. |
| EXPECT_EQ(0, raw_key.back() & 0x1f); |
| } |
| |
| // Derive an HMAC key with no specified length (just the hash of SHA-256). |
| TEST_F(WebCryptoEcdhTest, DeriveKeyHmacSha256NoLength) { |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey base_key; |
| ASSERT_TRUE(LoadTestKeys(&public_key, &base_key)); |
| |
| blink::WebCryptoKey derived_key; |
| |
| const blink::WebCryptoAlgorithm import_algorithm = |
| CreateHmacImportAlgorithmNoLength(blink::kWebCryptoAlgorithmIdSha256); |
| |
| ASSERT_EQ(Status::Success(), |
| DeriveKey(CreateEcdhDeriveParams(public_key), base_key, |
| import_algorithm, import_algorithm, true, |
| blink::kWebCryptoKeyUsageSign, &derived_key)); |
| |
| ASSERT_EQ(blink::kWebCryptoAlgorithmIdHmac, derived_key.Algorithm().Id()); |
| ASSERT_EQ(blink::kWebCryptoAlgorithmIdSha256, |
| derived_key.Algorithm().HmacParams()->GetHash().Id()); |
| ASSERT_EQ(512u, derived_key.Algorithm().HmacParams()->LengthBits()); |
| |
| // Export the key and verify its contents. |
| std::vector<uint8_t> raw_key; |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::kWebCryptoKeyFormatRaw, derived_key, &raw_key)); |
| EXPECT_EQ(64u, raw_key.size()); |
| } |
| |
| // Derive an HMAC key with no specified length (just the hash of SHA-512). |
| // |
| // This fails, because ECDH using P-521 can only generate 528 bits, however HMAC |
| // SHA-512 requires 1024 bits. |
| // |
| // In practice, authors won't be directly generating keys from key agreement |
| // schemes, as that is frequently insecure, and instead be using KDFs to expand |
| // and generate keys. For simplicity of testing, however, test using an HMAC |
| // key. |
| TEST_F(WebCryptoEcdhTest, DeriveKeyHmacSha512NoLength) { |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey base_key; |
| ASSERT_TRUE(LoadTestKeys(&public_key, &base_key)); |
| |
| blink::WebCryptoKey derived_key; |
| |
| const blink::WebCryptoAlgorithm import_algorithm = |
| CreateHmacImportAlgorithmNoLength(blink::kWebCryptoAlgorithmIdSha512); |
| |
| ASSERT_EQ(Status::ErrorEcdhLengthTooBig(528), |
| DeriveKey(CreateEcdhDeriveParams(public_key), base_key, |
| import_algorithm, import_algorithm, true, |
| blink::kWebCryptoKeyUsageSign, &derived_key)); |
| } |
| |
| // Try deriving an AES key of length 128 bits. |
| TEST_F(WebCryptoEcdhTest, DeriveKeyAes128) { |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey base_key; |
| ASSERT_TRUE(LoadTestKeys(&public_key, &base_key)); |
| |
| blink::WebCryptoKey derived_key; |
| |
| ASSERT_EQ(Status::Success(), |
| DeriveKey(CreateEcdhDeriveParams(public_key), base_key, |
| CreateAlgorithm(blink::kWebCryptoAlgorithmIdAesGcm), |
| CreateAesGcmDerivedKeyParams(128), true, |
| blink::kWebCryptoKeyUsageEncrypt, &derived_key)); |
| |
| ASSERT_EQ(blink::kWebCryptoAlgorithmIdAesGcm, derived_key.Algorithm().Id()); |
| ASSERT_EQ(128, derived_key.Algorithm().AesParams()->LengthBits()); |
| |
| // Export the key and verify its contents. |
| std::vector<uint8_t> raw_key; |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::kWebCryptoKeyFormatRaw, derived_key, &raw_key)); |
| EXPECT_EQ(16u, raw_key.size()); |
| } |
| |
| TEST_F(WebCryptoEcdhTest, ImportKeyEmptyUsage) { |
| blink::WebCryptoKey key; |
| |
| base::ListValue tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("ecdh.json", &tests)); |
| |
| const base::DictionaryValue* test; |
| ASSERT_TRUE(tests.GetDictionary(0, &test)); |
| |
| // Import the public key. |
| const base::DictionaryValue* public_key_json = nullptr; |
| EXPECT_TRUE(test->GetDictionary("public_key", &public_key_json)); |
| blink::WebCryptoNamedCurve curve = |
| GetCurveNameFromDictionary(public_key_json); |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatJwk, |
| CryptoData(MakeJsonVector(*public_key_json)), |
| CreateEcdhImportAlgorithm(curve), true, 0, &key)); |
| EXPECT_EQ(0, key.Usages()); |
| |
| // Import the private key. |
| const base::DictionaryValue* private_key_json = nullptr; |
| EXPECT_TRUE(test->GetDictionary("private_key", &private_key_json)); |
| curve = GetCurveNameFromDictionary(private_key_json); |
| ASSERT_EQ(Status::ErrorCreateKeyEmptyUsages(), |
| ImportKey(blink::kWebCryptoKeyFormatJwk, |
| CryptoData(MakeJsonVector(*private_key_json)), |
| CreateEcdhImportAlgorithm(curve), true, 0, &key)); |
| } |
| |
| } // namespace |
| |
| } // namespace webcrypto |