| // 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 <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include "base/basictypes.h" |
| #include "base/file_util.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/path_service.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "content/child/webcrypto/algorithm_dispatch.h" |
| #include "content/child/webcrypto/crypto_data.h" |
| #include "content/child/webcrypto/status.h" |
| #include "content/child/webcrypto/webcrypto_util.h" |
| #include "content/public/common/content_paths.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/WebKit/public/platform/WebCryptoAlgorithm.h" |
| #include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h" |
| #include "third_party/WebKit/public/platform/WebCryptoKey.h" |
| #include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h" |
| #include "third_party/re2/re2/re2.h" |
| |
| #if !defined(USE_OPENSSL) |
| #include <nss.h> |
| #include <pk11pub.h> |
| |
| #include "crypto/nss_util.h" |
| #include "crypto/scoped_nss_types.h" |
| #endif |
| |
| #define EXPECT_BYTES_EQ(expected, actual) \ |
| EXPECT_EQ(CryptoData(expected), CryptoData(actual)) |
| |
| #define EXPECT_BYTES_EQ_HEX(expected_hex, actual_bytes) \ |
| EXPECT_BYTES_EQ(HexStringToBytes(expected_hex), actual_bytes) |
| |
| namespace content { |
| |
| namespace webcrypto { |
| |
| // These functions are used by GTEST to support EXPECT_EQ() for |
| // webcrypto::Status and webcrypto::CryptoData |
| |
| void PrintTo(const Status& status, ::std::ostream* os) { |
| if (status.IsSuccess()) |
| *os << "Success"; |
| else |
| *os << "Error type: " << status.error_type() |
| << " Error details: " << status.error_details(); |
| } |
| |
| bool operator==(const content::webcrypto::Status& a, |
| const content::webcrypto::Status& b) { |
| if (a.IsSuccess() != b.IsSuccess()) |
| return false; |
| if (a.IsSuccess()) |
| return true; |
| return a.error_type() == b.error_type() && |
| a.error_details() == b.error_details(); |
| } |
| |
| bool operator!=(const content::webcrypto::Status& a, |
| const content::webcrypto::Status& b) { |
| return !(a == b); |
| } |
| |
| void PrintTo(const CryptoData& data, ::std::ostream* os) { |
| *os << "[" << base::HexEncode(data.bytes(), data.byte_length()) << "]"; |
| } |
| |
| bool operator==(const content::webcrypto::CryptoData& a, |
| const content::webcrypto::CryptoData& b) { |
| return a.byte_length() == b.byte_length() && |
| memcmp(a.bytes(), b.bytes(), a.byte_length()) == 0; |
| } |
| |
| bool operator!=(const content::webcrypto::CryptoData& a, |
| const content::webcrypto::CryptoData& b) { |
| return !(a == b); |
| } |
| |
| namespace { |
| |
| // ----------------------------------------------------------------------------- |
| |
| // TODO(eroman): For Linux builds using system NSS, AES-GCM support is a |
| // runtime dependency. Test it by trying to import a key. |
| // TODO(padolph): Consider caching the result of the import key test. |
| bool SupportsAesGcm() { |
| std::vector<uint8_t> key_raw(16, 0); |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| Status status = ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(key_raw), |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesGcm), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &key); |
| |
| if (status.IsError()) |
| EXPECT_EQ(blink::WebCryptoErrorTypeNotSupported, status.error_type()); |
| return status.IsSuccess(); |
| } |
| |
| bool SupportsRsaOaep() { |
| #if defined(USE_OPENSSL) |
| return true; |
| #else |
| crypto::EnsureNSSInit(); |
| // TODO(eroman): Exclude version test for OS_CHROMEOS |
| #if defined(USE_NSS) |
| if (!NSS_VersionCheck("3.16.2")) |
| return false; |
| #endif |
| crypto::ScopedPK11Slot slot(PK11_GetInternalKeySlot()); |
| return !!PK11_DoesMechanism(slot.get(), CKM_RSA_PKCS_OAEP); |
| #endif |
| } |
| |
| bool SupportsRsaKeyImport() { |
| // TODO(eroman): Exclude version test for OS_CHROMEOS |
| #if defined(USE_NSS) |
| crypto::EnsureNSSInit(); |
| if (!NSS_VersionCheck("3.16.2")) { |
| LOG(WARNING) << "RSA key import is not supported by this version of NSS. " |
| "Skipping some tests"; |
| return false; |
| } |
| #endif |
| return true; |
| } |
| |
| blink::WebCryptoAlgorithm CreateRsaHashedKeyGenAlgorithm( |
| blink::WebCryptoAlgorithmId algorithm_id, |
| const blink::WebCryptoAlgorithmId hash_id, |
| unsigned int modulus_length, |
| const std::vector<uint8_t>& public_exponent) { |
| DCHECK(algorithm_id == blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5 || |
| algorithm_id == blink::WebCryptoAlgorithmIdRsaOaep); |
| DCHECK(blink::WebCryptoAlgorithm::isHash(hash_id)); |
| return blink::WebCryptoAlgorithm::adoptParamsAndCreate( |
| algorithm_id, |
| new blink::WebCryptoRsaHashedKeyGenParams( |
| CreateAlgorithm(hash_id), |
| modulus_length, |
| vector_as_array(&public_exponent), |
| public_exponent.size())); |
| } |
| |
| // Creates an RSA-OAEP algorithm |
| blink::WebCryptoAlgorithm CreateRsaOaepAlgorithm( |
| const std::vector<uint8_t>& label) { |
| return blink::WebCryptoAlgorithm::adoptParamsAndCreate( |
| blink::WebCryptoAlgorithmIdRsaOaep, |
| new blink::WebCryptoRsaOaepParams( |
| !label.empty(), vector_as_array(&label), label.size())); |
| } |
| |
| // Creates an AES-CBC algorithm. |
| blink::WebCryptoAlgorithm CreateAesCbcAlgorithm( |
| const std::vector<uint8_t>& iv) { |
| return blink::WebCryptoAlgorithm::adoptParamsAndCreate( |
| blink::WebCryptoAlgorithmIdAesCbc, |
| new blink::WebCryptoAesCbcParams(vector_as_array(&iv), iv.size())); |
| } |
| |
| // Creates an AES-GCM algorithm. |
| blink::WebCryptoAlgorithm CreateAesGcmAlgorithm( |
| const std::vector<uint8_t>& iv, |
| const std::vector<uint8_t>& additional_data, |
| unsigned int tag_length_bits) { |
| EXPECT_TRUE(SupportsAesGcm()); |
| return blink::WebCryptoAlgorithm::adoptParamsAndCreate( |
| blink::WebCryptoAlgorithmIdAesGcm, |
| new blink::WebCryptoAesGcmParams(vector_as_array(&iv), |
| iv.size(), |
| true, |
| vector_as_array(&additional_data), |
| additional_data.size(), |
| true, |
| tag_length_bits)); |
| } |
| |
| // Creates an HMAC algorithm whose parameters struct is compatible with key |
| // generation. It is an error to call this with a hash_id that is not a SHA*. |
| // The key_length_bits parameter is optional, with zero meaning unspecified. |
| blink::WebCryptoAlgorithm CreateHmacKeyGenAlgorithm( |
| blink::WebCryptoAlgorithmId hash_id, |
| unsigned int key_length_bits) { |
| DCHECK(blink::WebCryptoAlgorithm::isHash(hash_id)); |
| // key_length_bytes == 0 means unspecified |
| return blink::WebCryptoAlgorithm::adoptParamsAndCreate( |
| blink::WebCryptoAlgorithmIdHmac, |
| new blink::WebCryptoHmacKeyGenParams( |
| CreateAlgorithm(hash_id), (key_length_bits != 0), key_length_bits)); |
| } |
| |
| // Returns a slightly modified version of the input vector. |
| // |
| // - For non-empty inputs a single bit is inverted. |
| // - For empty inputs, a byte is added. |
| std::vector<uint8_t> Corrupted(const std::vector<uint8_t>& input) { |
| std::vector<uint8_t> corrupted_data(input); |
| if (corrupted_data.empty()) |
| corrupted_data.push_back(0); |
| corrupted_data[corrupted_data.size() / 2] ^= 0x01; |
| return corrupted_data; |
| } |
| |
| std::vector<uint8_t> HexStringToBytes(const std::string& hex) { |
| std::vector<uint8_t> bytes; |
| base::HexStringToBytes(hex, &bytes); |
| return bytes; |
| } |
| |
| std::vector<uint8_t> MakeJsonVector(const std::string& json_string) { |
| return std::vector<uint8_t>(json_string.begin(), json_string.end()); |
| } |
| |
| std::vector<uint8_t> MakeJsonVector(const base::DictionaryValue& dict) { |
| std::string json; |
| base::JSONWriter::Write(&dict, &json); |
| return MakeJsonVector(json); |
| } |
| |
| // ---------------------------------------------------------------- |
| // Helpers for working with JSON data files for test expectations. |
| // ---------------------------------------------------------------- |
| |
| // Reads a file in "src/content/test/data/webcrypto" to a base::Value. |
| // The file must be JSON, however it can also include C++ style comments. |
| ::testing::AssertionResult ReadJsonTestFile(const char* test_file_name, |
| scoped_ptr<base::Value>* value) { |
| base::FilePath test_data_dir; |
| if (!PathService::Get(DIR_TEST_DATA, &test_data_dir)) |
| return ::testing::AssertionFailure() << "Couldn't retrieve test dir"; |
| |
| base::FilePath file_path = |
| test_data_dir.AppendASCII("webcrypto").AppendASCII(test_file_name); |
| |
| std::string file_contents; |
| if (!base::ReadFileToString(file_path, &file_contents)) { |
| return ::testing::AssertionFailure() |
| << "Couldn't read test file: " << file_path.value(); |
| } |
| |
| // Strip C++ style comments out of the "json" file, otherwise it cannot be |
| // parsed. |
| re2::RE2::GlobalReplace(&file_contents, re2::RE2("\\s*//.*"), ""); |
| |
| // Parse the JSON to a dictionary. |
| value->reset(base::JSONReader::Read(file_contents)); |
| if (!value->get()) { |
| return ::testing::AssertionFailure() |
| << "Couldn't parse test file JSON: " << file_path.value(); |
| } |
| |
| return ::testing::AssertionSuccess(); |
| } |
| |
| // Same as ReadJsonTestFile(), but return the value as a List. |
| ::testing::AssertionResult ReadJsonTestFileToList( |
| const char* test_file_name, |
| scoped_ptr<base::ListValue>* list) { |
| // Read the JSON. |
| scoped_ptr<base::Value> json; |
| ::testing::AssertionResult result = ReadJsonTestFile(test_file_name, &json); |
| if (!result) |
| return result; |
| |
| // Cast to an ListValue. |
| base::ListValue* list_value = NULL; |
| if (!json->GetAsList(&list_value) || !list_value) |
| return ::testing::AssertionFailure() << "The JSON was not a list"; |
| |
| list->reset(list_value); |
| ignore_result(json.release()); |
| |
| return ::testing::AssertionSuccess(); |
| } |
| |
| // Read a string property from the dictionary with path |property_name| |
| // (which can include periods for nested dictionaries). Interprets the |
| // string as a hex encoded string and converts it to a bytes list. |
| // |
| // Returns empty vector on failure. |
| std::vector<uint8_t> GetBytesFromHexString(base::DictionaryValue* dict, |
| const char* property_name) { |
| std::string hex_string; |
| if (!dict->GetString(property_name, &hex_string)) { |
| EXPECT_TRUE(false) << "Couldn't get string property: " << property_name; |
| return std::vector<uint8_t>(); |
| } |
| |
| return HexStringToBytes(hex_string); |
| } |
| |
| // Reads a string property with path "property_name" and converts it to a |
| // WebCryptoAlgorith. Returns null algorithm on failure. |
| blink::WebCryptoAlgorithm GetDigestAlgorithm(base::DictionaryValue* dict, |
| const char* property_name) { |
| std::string algorithm_name; |
| if (!dict->GetString(property_name, &algorithm_name)) { |
| EXPECT_TRUE(false) << "Couldn't get string property: " << property_name; |
| return blink::WebCryptoAlgorithm::createNull(); |
| } |
| |
| struct { |
| const char* name; |
| blink::WebCryptoAlgorithmId id; |
| } kDigestNameToId[] = { |
| {"sha-1", blink::WebCryptoAlgorithmIdSha1}, |
| {"sha-256", blink::WebCryptoAlgorithmIdSha256}, |
| {"sha-384", blink::WebCryptoAlgorithmIdSha384}, |
| {"sha-512", blink::WebCryptoAlgorithmIdSha512}, |
| }; |
| |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kDigestNameToId); ++i) { |
| if (kDigestNameToId[i].name == algorithm_name) |
| return CreateAlgorithm(kDigestNameToId[i].id); |
| } |
| |
| return blink::WebCryptoAlgorithm::createNull(); |
| } |
| |
| // Helper for ImportJwkFailures and ImportJwkOctFailures. Restores the JWK JSON |
| // dictionary to a good state |
| void RestoreJwkOctDictionary(base::DictionaryValue* dict) { |
| dict->Clear(); |
| dict->SetString("kty", "oct"); |
| dict->SetString("alg", "A128CBC"); |
| dict->SetString("use", "enc"); |
| dict->SetBoolean("ext", false); |
| dict->SetString("k", "GADWrMRHwQfoNaXU5fZvTg=="); |
| } |
| |
| // Helper for ImportJwkRsaFailures. Restores the JWK JSON |
| // dictionary to a good state |
| void RestoreJwkRsaDictionary(base::DictionaryValue* dict) { |
| dict->Clear(); |
| dict->SetString("kty", "RSA"); |
| dict->SetString("alg", "RS256"); |
| dict->SetString("use", "sig"); |
| dict->SetBoolean("ext", false); |
| dict->SetString( |
| "n", |
| "qLOyhK-OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx-CwgtaTpef87Wdc9GaFEncsDLxk" |
| "p0LGxjD1M8jMcvYq6DPEC_JYQumEu3i9v5fAEH1VvbZi9cTg-rmEXLUUjvc5LdOq_5OuHmtm" |
| "e7PUJHYW1PW6ENTP0ibeiNOfFvs"); |
| dict->SetString("e", "AQAB"); |
| } |
| |
| // Returns true if any of the vectors in the input list have identical content. |
| // Dumb O(n^2) implementation but should be fast enough for the input sizes that |
| // are used. |
| bool CopiesExist(const std::vector<std::vector<uint8_t> >& bufs) { |
| for (size_t i = 0; i < bufs.size(); ++i) { |
| for (size_t j = i + 1; j < bufs.size(); ++j) { |
| if (CryptoData(bufs[i]) == CryptoData(bufs[j])) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| blink::WebCryptoAlgorithm CreateAesKeyGenAlgorithm( |
| blink::WebCryptoAlgorithmId aes_alg_id, |
| unsigned short length) { |
| return blink::WebCryptoAlgorithm::adoptParamsAndCreate( |
| aes_alg_id, new blink::WebCryptoAesKeyGenParams(length)); |
| } |
| |
| blink::WebCryptoAlgorithm CreateAesCbcKeyGenAlgorithm( |
| unsigned short key_length_bits) { |
| return CreateAesKeyGenAlgorithm(blink::WebCryptoAlgorithmIdAesCbc, |
| key_length_bits); |
| } |
| |
| blink::WebCryptoAlgorithm CreateAesGcmKeyGenAlgorithm( |
| unsigned short key_length_bits) { |
| EXPECT_TRUE(SupportsAesGcm()); |
| return CreateAesKeyGenAlgorithm(blink::WebCryptoAlgorithmIdAesGcm, |
| key_length_bits); |
| } |
| |
| blink::WebCryptoAlgorithm CreateAesKwKeyGenAlgorithm( |
| unsigned short key_length_bits) { |
| return CreateAesKeyGenAlgorithm(blink::WebCryptoAlgorithmIdAesKw, |
| key_length_bits); |
| } |
| |
| // The following key pair is comprised of the SPKI (public key) and PKCS#8 |
| // (private key) representations of the key pair provided in Example 1 of the |
| // NIST test vectors at |
| // ftp://ftp.rsa.com/pub/rsalabs/tmp/pkcs1v15sign-vectors.txt |
| const unsigned int kModulusLengthBits = 1024; |
| const char* const kPublicKeySpkiDerHex = |
| "30819f300d06092a864886f70d010101050003818d0030818902818100a5" |
| "6e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad9" |
| "91d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfc" |
| "e0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e" |
| "6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cf" |
| "fb2249bd9a21370203010001"; |
| const char* const kPrivateKeyPkcs8DerHex = |
| "30820275020100300d06092a864886f70d01010105000482025f3082025b" |
| "02010002818100a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52" |
| "a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab" |
| "7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921c" |
| "b23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef" |
| "22e1e1f20d0ce8cffb2249bd9a2137020301000102818033a5042a90b27d" |
| "4f5451ca9bbbd0b44771a101af884340aef9885f2a4bbe92e894a724ac3c" |
| "568c8f97853ad07c0266c8c6a3ca0929f1e8f11231884429fc4d9ae55fee" |
| "896a10ce707c3ed7e734e44727a39574501a532683109c2abacaba283c31" |
| "b4bd2f53c3ee37e352cee34f9e503bd80c0622ad79c6dcee883547c6a3b3" |
| "25024100e7e8942720a877517273a356053ea2a1bc0c94aa72d55c6e8629" |
| "6b2dfc967948c0a72cbccca7eacb35706e09a1df55a1535bd9b3cc34160b" |
| "3b6dcd3eda8e6443024100b69dca1cf7d4d7ec81e75b90fcca874abcde12" |
| "3fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542cd20dc72" |
| "3e6963364a1f9425452b269a6799fd024028fa13938655be1f8a159cbaca" |
| "5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8d" |
| "d3ede2448328f385d81b30e8e43b2fffa02786197902401a8b38f398fa71" |
| "2049898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd" |
| "48be455eaeb6e1678255827580a8e4e8e14151d1510a82a3f2e729024027" |
| "156aba4126d24a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319" |
| "584b8e22fdde1e5a2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24" |
| "a79f4d"; |
| // The modulus and exponent (in hex) of kPublicKeySpkiDerHex |
| const char* const kPublicKeyModulusHex = |
| "A56E4A0E701017589A5187DC7EA841D156F2EC0E36AD52A44DFEB1E61F7AD991D8C51056" |
| "FFEDB162B4C0F283A12A88A394DFF526AB7291CBB307CEABFCE0B1DFD5CD9508096D5B2B" |
| "8B6DF5D671EF6377C0921CB23C270A70E2598E6FF89D19F105ACC2D3F0CB35F29280E138" |
| "6B6F64C4EF22E1E1F20D0CE8CFFB2249BD9A2137"; |
| const char* const kPublicKeyExponentHex = "010001"; |
| |
| // TODO(eroman): Remove unnecessary test fixture. |
| class SharedCryptoTest : public testing::Test { |
| }; |
| |
| blink::WebCryptoKey ImportSecretKeyFromRaw( |
| const std::vector<uint8_t>& key_raw, |
| const blink::WebCryptoAlgorithm& algorithm, |
| blink::WebCryptoKeyUsageMask usage) { |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| bool extractable = true; |
| EXPECT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(key_raw), |
| algorithm, |
| extractable, |
| usage, |
| &key)); |
| |
| EXPECT_FALSE(key.isNull()); |
| EXPECT_TRUE(key.handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypeSecret, key.type()); |
| EXPECT_EQ(algorithm.id(), key.algorithm().id()); |
| EXPECT_EQ(extractable, key.extractable()); |
| EXPECT_EQ(usage, key.usages()); |
| return key; |
| } |
| |
| void ImportRsaKeyPair(const std::vector<uint8_t>& spki_der, |
| const std::vector<uint8_t>& pkcs8_der, |
| const blink::WebCryptoAlgorithm& algorithm, |
| bool extractable, |
| blink::WebCryptoKeyUsageMask public_key_usage_mask, |
| blink::WebCryptoKeyUsageMask private_key_usage_mask, |
| blink::WebCryptoKey* public_key, |
| blink::WebCryptoKey* private_key) { |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(spki_der), |
| algorithm, |
| true, |
| public_key_usage_mask, |
| public_key)); |
| EXPECT_FALSE(public_key->isNull()); |
| EXPECT_TRUE(public_key->handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePublic, public_key->type()); |
| EXPECT_EQ(algorithm.id(), public_key->algorithm().id()); |
| EXPECT_TRUE(public_key->extractable()); |
| EXPECT_EQ(public_key_usage_mask, public_key->usages()); |
| |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatPkcs8, |
| CryptoData(pkcs8_der), |
| algorithm, |
| extractable, |
| private_key_usage_mask, |
| private_key)); |
| EXPECT_FALSE(private_key->isNull()); |
| EXPECT_TRUE(private_key->handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePrivate, private_key->type()); |
| EXPECT_EQ(algorithm.id(), private_key->algorithm().id()); |
| EXPECT_EQ(extractable, private_key->extractable()); |
| EXPECT_EQ(private_key_usage_mask, private_key->usages()); |
| } |
| |
| Status AesGcmEncrypt(const blink::WebCryptoKey& key, |
| const std::vector<uint8_t>& iv, |
| const std::vector<uint8_t>& additional_data, |
| unsigned int tag_length_bits, |
| const std::vector<uint8_t>& plain_text, |
| std::vector<uint8_t>* cipher_text, |
| std::vector<uint8_t>* authentication_tag) { |
| EXPECT_TRUE(SupportsAesGcm()); |
| blink::WebCryptoAlgorithm algorithm = |
| CreateAesGcmAlgorithm(iv, additional_data, tag_length_bits); |
| |
| std::vector<uint8_t> output; |
| Status status = Encrypt(algorithm, key, CryptoData(plain_text), &output); |
| if (status.IsError()) |
| return status; |
| |
| if ((tag_length_bits % 8) != 0) { |
| EXPECT_TRUE(false) << "Encrypt should have failed."; |
| return Status::OperationError(); |
| } |
| |
| size_t tag_length_bytes = tag_length_bits / 8; |
| |
| if (tag_length_bytes > output.size()) { |
| EXPECT_TRUE(false) << "tag length is larger than output"; |
| return Status::OperationError(); |
| } |
| |
| // The encryption result is cipher text with authentication tag appended. |
| cipher_text->assign(output.begin(), |
| output.begin() + (output.size() - tag_length_bytes)); |
| authentication_tag->assign(output.begin() + cipher_text->size(), |
| output.end()); |
| |
| return Status::Success(); |
| } |
| |
| Status AesGcmDecrypt(const blink::WebCryptoKey& key, |
| const std::vector<uint8_t>& iv, |
| const std::vector<uint8_t>& additional_data, |
| unsigned int tag_length_bits, |
| const std::vector<uint8_t>& cipher_text, |
| const std::vector<uint8_t>& authentication_tag, |
| std::vector<uint8_t>* plain_text) { |
| EXPECT_TRUE(SupportsAesGcm()); |
| blink::WebCryptoAlgorithm algorithm = |
| CreateAesGcmAlgorithm(iv, additional_data, tag_length_bits); |
| |
| // Join cipher text and authentication tag. |
| std::vector<uint8_t> cipher_text_with_tag; |
| cipher_text_with_tag.reserve(cipher_text.size() + authentication_tag.size()); |
| cipher_text_with_tag.insert( |
| cipher_text_with_tag.end(), cipher_text.begin(), cipher_text.end()); |
| cipher_text_with_tag.insert(cipher_text_with_tag.end(), |
| authentication_tag.begin(), |
| authentication_tag.end()); |
| |
| return Decrypt(algorithm, key, CryptoData(cipher_text_with_tag), plain_text); |
| } |
| |
| Status ImportKeyJwk(const CryptoData& key_data, |
| const blink::WebCryptoAlgorithm& algorithm, |
| bool extractable, |
| blink::WebCryptoKeyUsageMask usage_mask, |
| blink::WebCryptoKey* key) { |
| return ImportKey(blink::WebCryptoKeyFormatJwk, |
| key_data, |
| algorithm, |
| extractable, |
| usage_mask, |
| key); |
| } |
| |
| Status ImportKeyJwkFromDict(const base::DictionaryValue& dict, |
| const blink::WebCryptoAlgorithm& algorithm, |
| bool extractable, |
| blink::WebCryptoKeyUsageMask usage_mask, |
| blink::WebCryptoKey* key) { |
| return ImportKeyJwk(CryptoData(MakeJsonVector(dict)), |
| algorithm, |
| extractable, |
| usage_mask, |
| key); |
| } |
| |
| // Parses a vector of JSON into a dictionary. |
| scoped_ptr<base::DictionaryValue> GetJwkDictionary( |
| const std::vector<uint8_t>& json) { |
| base::StringPiece json_string( |
| reinterpret_cast<const char*>(vector_as_array(&json)), json.size()); |
| base::Value* value = base::JSONReader::Read(json_string); |
| EXPECT_TRUE(value); |
| base::DictionaryValue* dict_value = NULL; |
| value->GetAsDictionary(&dict_value); |
| return scoped_ptr<base::DictionaryValue>(dict_value); |
| } |
| |
| // Verifies the input dictionary contains the expected values. Exact matches are |
| // required on the fields examined. |
| ::testing::AssertionResult VerifyJwk( |
| const scoped_ptr<base::DictionaryValue>& dict, |
| const std::string& kty_expected, |
| const std::string& alg_expected, |
| blink::WebCryptoKeyUsageMask use_mask_expected) { |
| // ---- kty |
| std::string value_string; |
| if (!dict->GetString("kty", &value_string)) |
| return ::testing::AssertionFailure() << "Missing 'kty'"; |
| if (value_string != kty_expected) |
| return ::testing::AssertionFailure() << "Expected 'kty' to be " |
| << kty_expected << "but found " |
| << value_string; |
| |
| // ---- alg |
| if (!dict->GetString("alg", &value_string)) |
| return ::testing::AssertionFailure() << "Missing 'alg'"; |
| if (value_string != alg_expected) |
| return ::testing::AssertionFailure() << "Expected 'alg' to be " |
| << alg_expected << " but found " |
| << value_string; |
| |
| // ---- ext |
| // always expect ext == true in this case |
| bool ext_value; |
| if (!dict->GetBoolean("ext", &ext_value)) |
| return ::testing::AssertionFailure() << "Missing 'ext'"; |
| if (!ext_value) |
| return ::testing::AssertionFailure() |
| << "Expected 'ext' to be true but found false"; |
| |
| // ---- key_ops |
| base::ListValue* key_ops; |
| if (!dict->GetList("key_ops", &key_ops)) |
| return ::testing::AssertionFailure() << "Missing 'key_ops'"; |
| blink::WebCryptoKeyUsageMask key_ops_mask = 0; |
| Status status = GetWebCryptoUsagesFromJwkKeyOps(key_ops, &key_ops_mask); |
| if (status.IsError()) |
| return ::testing::AssertionFailure() << "Failure extracting 'key_ops'"; |
| if (key_ops_mask != use_mask_expected) |
| return ::testing::AssertionFailure() |
| << "Expected 'key_ops' mask to be " << use_mask_expected |
| << " but found " << key_ops_mask << " (" << value_string << ")"; |
| |
| return ::testing::AssertionSuccess(); |
| } |
| |
| // Verifies that the JSON in the input vector contains the provided |
| // expected values. Exact matches are required on the fields examined. |
| ::testing::AssertionResult VerifySecretJwk( |
| const std::vector<uint8_t>& json, |
| const std::string& alg_expected, |
| const std::string& k_expected_hex, |
| blink::WebCryptoKeyUsageMask use_mask_expected) { |
| scoped_ptr<base::DictionaryValue> dict = GetJwkDictionary(json); |
| if (!dict.get() || dict->empty()) |
| return ::testing::AssertionFailure() << "JSON parsing failed"; |
| |
| // ---- k |
| std::string value_string; |
| if (!dict->GetString("k", &value_string)) |
| return ::testing::AssertionFailure() << "Missing 'k'"; |
| std::string k_value; |
| if (!webcrypto::Base64DecodeUrlSafe(value_string, &k_value)) |
| return ::testing::AssertionFailure() << "Base64DecodeUrlSafe(k) failed"; |
| if (!LowerCaseEqualsASCII(base::HexEncode(k_value.data(), k_value.size()), |
| k_expected_hex.c_str())) { |
| return ::testing::AssertionFailure() << "Expected 'k' to be " |
| << k_expected_hex |
| << " but found something different"; |
| } |
| |
| return VerifyJwk(dict, "oct", alg_expected, use_mask_expected); |
| } |
| |
| // Verifies that the JSON in the input vector contains the provided |
| // expected values. Exact matches are required on the fields examined. |
| ::testing::AssertionResult VerifyPublicJwk( |
| const std::vector<uint8_t>& json, |
| const std::string& alg_expected, |
| const std::string& n_expected_hex, |
| const std::string& e_expected_hex, |
| blink::WebCryptoKeyUsageMask use_mask_expected) { |
| scoped_ptr<base::DictionaryValue> dict = GetJwkDictionary(json); |
| if (!dict.get() || dict->empty()) |
| return ::testing::AssertionFailure() << "JSON parsing failed"; |
| |
| // ---- n |
| std::string value_string; |
| if (!dict->GetString("n", &value_string)) |
| return ::testing::AssertionFailure() << "Missing 'n'"; |
| std::string n_value; |
| if (!webcrypto::Base64DecodeUrlSafe(value_string, &n_value)) |
| return ::testing::AssertionFailure() << "Base64DecodeUrlSafe(n) failed"; |
| if (base::HexEncode(n_value.data(), n_value.size()) != n_expected_hex) { |
| return ::testing::AssertionFailure() << "'n' does not match the expected " |
| "value"; |
| } |
| // TODO(padolph): LowerCaseEqualsASCII() does not work for above! |
| |
| // ---- e |
| if (!dict->GetString("e", &value_string)) |
| return ::testing::AssertionFailure() << "Missing 'e'"; |
| std::string e_value; |
| if (!webcrypto::Base64DecodeUrlSafe(value_string, &e_value)) |
| return ::testing::AssertionFailure() << "Base64DecodeUrlSafe(e) failed"; |
| if (!LowerCaseEqualsASCII(base::HexEncode(e_value.data(), e_value.size()), |
| e_expected_hex.c_str())) { |
| return ::testing::AssertionFailure() << "Expected 'e' to be " |
| << e_expected_hex |
| << " but found something different"; |
| } |
| |
| return VerifyJwk(dict, "RSA", alg_expected, use_mask_expected); |
| } |
| |
| } // namespace |
| |
| TEST_F(SharedCryptoTest, CheckAesGcm) { |
| if (!SupportsAesGcm()) { |
| LOG(WARNING) << "AES GCM not supported on this platform, so some tests " |
| "will be skipped. Consider upgrading local NSS libraries"; |
| return; |
| } |
| } |
| |
| // Tests several Status objects against their expected hard coded values, as |
| // well as ensuring that comparison of Status objects works. |
| // Comparison should take into account both the error details, as well as the |
| // error type. |
| TEST_F(SharedCryptoTest, Status) { |
| // Even though the error message is the same, these should not be considered |
| // the same by the tests because the error type is different. |
| EXPECT_NE(Status::DataError(), Status::OperationError()); |
| EXPECT_NE(Status::Success(), Status::OperationError()); |
| |
| EXPECT_EQ(Status::Success(), Status::Success()); |
| EXPECT_EQ(Status::ErrorJwkPropertyWrongType("kty", "string"), |
| Status::ErrorJwkPropertyWrongType("kty", "string")); |
| |
| Status status = Status::Success(); |
| |
| EXPECT_FALSE(status.IsError()); |
| EXPECT_EQ("", status.error_details()); |
| |
| status = Status::OperationError(); |
| EXPECT_TRUE(status.IsError()); |
| EXPECT_EQ("", status.error_details()); |
| EXPECT_EQ(blink::WebCryptoErrorTypeOperation, status.error_type()); |
| |
| status = Status::DataError(); |
| EXPECT_TRUE(status.IsError()); |
| EXPECT_EQ("", status.error_details()); |
| EXPECT_EQ(blink::WebCryptoErrorTypeData, status.error_type()); |
| |
| status = Status::ErrorUnsupported(); |
| EXPECT_TRUE(status.IsError()); |
| EXPECT_EQ("The requested operation is unsupported", status.error_details()); |
| EXPECT_EQ(blink::WebCryptoErrorTypeNotSupported, status.error_type()); |
| |
| status = Status::ErrorJwkPropertyMissing("kty"); |
| EXPECT_TRUE(status.IsError()); |
| EXPECT_EQ("The required JWK property \"kty\" was missing", |
| status.error_details()); |
| EXPECT_EQ(blink::WebCryptoErrorTypeData, status.error_type()); |
| |
| status = Status::ErrorJwkPropertyWrongType("kty", "string"); |
| EXPECT_TRUE(status.IsError()); |
| EXPECT_EQ("The JWK property \"kty\" must be a string", |
| status.error_details()); |
| EXPECT_EQ(blink::WebCryptoErrorTypeData, status.error_type()); |
| |
| status = Status::ErrorJwkBase64Decode("n"); |
| EXPECT_TRUE(status.IsError()); |
| EXPECT_EQ("The JWK property \"n\" could not be base64 decoded", |
| status.error_details()); |
| EXPECT_EQ(blink::WebCryptoErrorTypeData, status.error_type()); |
| } |
| |
| TEST_F(SharedCryptoTest, DigestSampleSets) { |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("digest.json", &tests)); |
| |
| for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) { |
| SCOPED_TRACE(test_index); |
| base::DictionaryValue* test; |
| ASSERT_TRUE(tests->GetDictionary(test_index, &test)); |
| |
| blink::WebCryptoAlgorithm test_algorithm = |
| GetDigestAlgorithm(test, "algorithm"); |
| std::vector<uint8_t> test_input = GetBytesFromHexString(test, "input"); |
| std::vector<uint8_t> test_output = GetBytesFromHexString(test, "output"); |
| |
| std::vector<uint8_t> output; |
| ASSERT_EQ(Status::Success(), |
| Digest(test_algorithm, CryptoData(test_input), &output)); |
| EXPECT_BYTES_EQ(test_output, output); |
| } |
| } |
| |
| TEST_F(SharedCryptoTest, DigestSampleSetsInChunks) { |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("digest.json", &tests)); |
| |
| for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) { |
| SCOPED_TRACE(test_index); |
| base::DictionaryValue* test; |
| ASSERT_TRUE(tests->GetDictionary(test_index, &test)); |
| |
| blink::WebCryptoAlgorithm test_algorithm = |
| GetDigestAlgorithm(test, "algorithm"); |
| std::vector<uint8_t> test_input = GetBytesFromHexString(test, "input"); |
| std::vector<uint8_t> test_output = GetBytesFromHexString(test, "output"); |
| |
| // Test the chunk version of the digest functions. Test with 129 byte chunks |
| // because the SHA-512 chunk size is 128 bytes. |
| unsigned char* output; |
| unsigned int output_length; |
| static const size_t kChunkSizeBytes = 129; |
| size_t length = test_input.size(); |
| scoped_ptr<blink::WebCryptoDigestor> digestor( |
| CreateDigestor(test_algorithm.id())); |
| std::vector<uint8_t>::iterator begin = test_input.begin(); |
| size_t chunk_index = 0; |
| while (begin != test_input.end()) { |
| size_t chunk_length = std::min(kChunkSizeBytes, length - chunk_index); |
| std::vector<uint8_t> chunk(begin, begin + chunk_length); |
| ASSERT_TRUE(chunk.size() > 0); |
| EXPECT_TRUE(digestor->consume(&chunk.front(), chunk.size())); |
| chunk_index = chunk_index + chunk_length; |
| begin = begin + chunk_length; |
| } |
| EXPECT_TRUE(digestor->finish(output, output_length)); |
| EXPECT_BYTES_EQ(test_output, CryptoData(output, output_length)); |
| } |
| } |
| |
| TEST_F(SharedCryptoTest, HMACSampleSets) { |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("hmac.json", &tests)); |
| // TODO(padolph): Missing known answer tests for HMAC SHA384, and SHA512. |
| for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) { |
| SCOPED_TRACE(test_index); |
| base::DictionaryValue* test; |
| ASSERT_TRUE(tests->GetDictionary(test_index, &test)); |
| |
| blink::WebCryptoAlgorithm test_hash = GetDigestAlgorithm(test, "hash"); |
| const std::vector<uint8_t> test_key = GetBytesFromHexString(test, "key"); |
| const std::vector<uint8_t> test_message = |
| GetBytesFromHexString(test, "message"); |
| const std::vector<uint8_t> test_mac = GetBytesFromHexString(test, "mac"); |
| |
| blink::WebCryptoAlgorithm algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdHmac); |
| |
| blink::WebCryptoAlgorithm import_algorithm = |
| CreateHmacImportAlgorithm(test_hash.id()); |
| |
| blink::WebCryptoKey key = ImportSecretKeyFromRaw( |
| test_key, |
| import_algorithm, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify); |
| |
| EXPECT_EQ(test_hash.id(), key.algorithm().hmacParams()->hash().id()); |
| EXPECT_EQ(test_key.size() * 8, key.algorithm().hmacParams()->lengthBits()); |
| |
| // Verify exported raw key is identical to the imported data |
| std::vector<uint8_t> raw_key; |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &raw_key)); |
| EXPECT_BYTES_EQ(test_key, raw_key); |
| |
| std::vector<uint8_t> output; |
| |
| ASSERT_EQ(Status::Success(), |
| Sign(algorithm, key, CryptoData(test_message), &output)); |
| |
| EXPECT_BYTES_EQ(test_mac, output); |
| |
| bool signature_match = false; |
| EXPECT_EQ(Status::Success(), |
| Verify(algorithm, |
| key, |
| CryptoData(output), |
| CryptoData(test_message), |
| &signature_match)); |
| EXPECT_TRUE(signature_match); |
| |
| // Ensure truncated signature does not verify by passing one less byte. |
| EXPECT_EQ(Status::Success(), |
| Verify(algorithm, |
| key, |
| CryptoData(vector_as_array(&output), output.size() - 1), |
| CryptoData(test_message), |
| &signature_match)); |
| EXPECT_FALSE(signature_match); |
| |
| // Ensure truncated signature does not verify by passing no bytes. |
| EXPECT_EQ(Status::Success(), |
| Verify(algorithm, |
| key, |
| CryptoData(), |
| CryptoData(test_message), |
| &signature_match)); |
| EXPECT_FALSE(signature_match); |
| |
| // Ensure extra long signature does not cause issues and fails. |
| const unsigned char kLongSignature[1024] = {0}; |
| EXPECT_EQ(Status::Success(), |
| Verify(algorithm, |
| key, |
| CryptoData(kLongSignature, sizeof(kLongSignature)), |
| CryptoData(test_message), |
| &signature_match)); |
| EXPECT_FALSE(signature_match); |
| } |
| } |
| |
| TEST_F(SharedCryptoTest, AesCbcFailures) { |
| const std::string key_hex = "2b7e151628aed2a6abf7158809cf4f3c"; |
| blink::WebCryptoKey key = ImportSecretKeyFromRaw( |
| HexStringToBytes(key_hex), |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt); |
| |
| // Verify exported raw key is identical to the imported data |
| std::vector<uint8_t> raw_key; |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &raw_key)); |
| EXPECT_BYTES_EQ_HEX(key_hex, raw_key); |
| |
| std::vector<uint8_t> output; |
| |
| // Use an invalid |iv| (fewer than 16 bytes) |
| { |
| std::vector<uint8_t> input(32); |
| std::vector<uint8_t> iv; |
| EXPECT_EQ(Status::ErrorIncorrectSizeAesCbcIv(), |
| Encrypt(webcrypto::CreateAesCbcAlgorithm(iv), |
| key, |
| CryptoData(input), |
| &output)); |
| EXPECT_EQ(Status::ErrorIncorrectSizeAesCbcIv(), |
| Decrypt(webcrypto::CreateAesCbcAlgorithm(iv), |
| key, |
| CryptoData(input), |
| &output)); |
| } |
| |
| // Use an invalid |iv| (more than 16 bytes) |
| { |
| std::vector<uint8_t> input(32); |
| std::vector<uint8_t> iv(17); |
| EXPECT_EQ(Status::ErrorIncorrectSizeAesCbcIv(), |
| Encrypt(webcrypto::CreateAesCbcAlgorithm(iv), |
| key, |
| CryptoData(input), |
| &output)); |
| EXPECT_EQ(Status::ErrorIncorrectSizeAesCbcIv(), |
| Decrypt(webcrypto::CreateAesCbcAlgorithm(iv), |
| key, |
| CryptoData(input), |
| &output)); |
| } |
| |
| // Give an input that is too large (would cause integer overflow when |
| // narrowing to an int). |
| { |
| std::vector<uint8_t> iv(16); |
| |
| // Pretend the input is large. Don't pass data pointer as NULL in case that |
| // is special cased; the implementation shouldn't actually dereference the |
| // data. |
| CryptoData input(&iv[0], INT_MAX - 3); |
| |
| EXPECT_EQ(Status::ErrorDataTooLarge(), |
| Encrypt(CreateAesCbcAlgorithm(iv), key, input, &output)); |
| EXPECT_EQ(Status::ErrorDataTooLarge(), |
| Decrypt(CreateAesCbcAlgorithm(iv), key, input, &output)); |
| } |
| |
| // Fail importing the key (too few bytes specified) |
| { |
| std::vector<uint8_t> key_raw(1); |
| std::vector<uint8_t> iv(16); |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| EXPECT_EQ(Status::ErrorImportAesKeyLength(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(key_raw), |
| CreateAesCbcAlgorithm(iv), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &key)); |
| } |
| |
| // Fail exporting the key in SPKI and PKCS#8 formats (not allowed for secret |
| // keys). |
| EXPECT_EQ(Status::ErrorUnsupportedExportKeyFormat(), |
| ExportKey(blink::WebCryptoKeyFormatSpki, key, &output)); |
| EXPECT_EQ(Status::ErrorUnsupportedExportKeyFormat(), |
| ExportKey(blink::WebCryptoKeyFormatPkcs8, key, &output)); |
| } |
| |
| TEST_F(SharedCryptoTest, ImportAesCbcSpkiFailure) { |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::ErrorUnsupportedImportKeyFormat(), |
| ImportKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(HexStringToBytes(kPublicKeySpkiDerHex)), |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &key)); |
| } |
| |
| TEST_F(SharedCryptoTest, AesCbcSampleSets) { |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("aes_cbc.json", &tests)); |
| |
| for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) { |
| SCOPED_TRACE(test_index); |
| base::DictionaryValue* test; |
| ASSERT_TRUE(tests->GetDictionary(test_index, &test)); |
| |
| std::vector<uint8_t> test_key = GetBytesFromHexString(test, "key"); |
| std::vector<uint8_t> test_iv = GetBytesFromHexString(test, "iv"); |
| std::vector<uint8_t> test_plain_text = |
| GetBytesFromHexString(test, "plain_text"); |
| std::vector<uint8_t> test_cipher_text = |
| GetBytesFromHexString(test, "cipher_text"); |
| |
| blink::WebCryptoKey key = ImportSecretKeyFromRaw( |
| test_key, |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt); |
| |
| EXPECT_EQ(test_key.size() * 8, key.algorithm().aesParams()->lengthBits()); |
| |
| // Verify exported raw key is identical to the imported data |
| std::vector<uint8_t> raw_key; |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &raw_key)); |
| EXPECT_BYTES_EQ(test_key, raw_key); |
| |
| std::vector<uint8_t> output; |
| |
| // Test encryption. |
| EXPECT_EQ(Status::Success(), |
| Encrypt(webcrypto::CreateAesCbcAlgorithm(test_iv), |
| key, |
| CryptoData(test_plain_text), |
| &output)); |
| EXPECT_BYTES_EQ(test_cipher_text, output); |
| |
| // Test decryption. |
| EXPECT_EQ(Status::Success(), |
| Decrypt(webcrypto::CreateAesCbcAlgorithm(test_iv), |
| key, |
| CryptoData(test_cipher_text), |
| &output)); |
| EXPECT_BYTES_EQ(test_plain_text, output); |
| |
| const unsigned int kAesCbcBlockSize = 16; |
| |
| // Decrypt with a padding error by stripping the last block. This also ends |
| // up testing decryption over empty cipher text. |
| if (test_cipher_text.size() >= kAesCbcBlockSize) { |
| EXPECT_EQ(Status::OperationError(), |
| Decrypt(CreateAesCbcAlgorithm(test_iv), |
| key, |
| CryptoData(&test_cipher_text[0], |
| test_cipher_text.size() - kAesCbcBlockSize), |
| &output)); |
| } |
| |
| // Decrypt cipher text which is not a multiple of block size by stripping |
| // a few bytes off the cipher text. |
| if (test_cipher_text.size() > 3) { |
| EXPECT_EQ( |
| Status::OperationError(), |
| Decrypt(CreateAesCbcAlgorithm(test_iv), |
| key, |
| CryptoData(&test_cipher_text[0], test_cipher_text.size() - 3), |
| &output)); |
| } |
| } |
| } |
| |
| TEST_F(SharedCryptoTest, GenerateKeyAes) { |
| // Check key generation for each of AES-CBC, AES-GCM, and AES-KW, and for each |
| // allowed key length. |
| std::vector<blink::WebCryptoAlgorithm> algorithm; |
| const unsigned short kKeyLength[] = {128, 256}; |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kKeyLength); ++i) { |
| algorithm.push_back(CreateAesCbcKeyGenAlgorithm(kKeyLength[i])); |
| algorithm.push_back(CreateAesKwKeyGenAlgorithm(kKeyLength[i])); |
| if (SupportsAesGcm()) |
| algorithm.push_back(CreateAesGcmKeyGenAlgorithm(kKeyLength[i])); |
| } |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| std::vector<std::vector<uint8_t> > keys; |
| std::vector<uint8_t> key_bytes; |
| for (size_t i = 0; i < algorithm.size(); ++i) { |
| SCOPED_TRACE(i); |
| // Generate a small sample of keys. |
| keys.clear(); |
| for (int j = 0; j < 16; ++j) { |
| ASSERT_EQ(Status::Success(), |
| GenerateSecretKey(algorithm[i], true, 0, &key)); |
| EXPECT_TRUE(key.handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypeSecret, key.type()); |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &key_bytes)); |
| EXPECT_EQ(key_bytes.size() * 8, |
| key.algorithm().aesParams()->lengthBits()); |
| 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(keys)); |
| } |
| } |
| |
| TEST_F(SharedCryptoTest, GenerateKeyAesBadLength) { |
| const unsigned short kKeyLen[] = {0, 127, 257}; |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kKeyLen); ++i) { |
| SCOPED_TRACE(i); |
| EXPECT_EQ(Status::ErrorGenerateKeyLength(), |
| GenerateSecretKey( |
| CreateAesCbcKeyGenAlgorithm(kKeyLen[i]), true, 0, &key)); |
| EXPECT_EQ(Status::ErrorGenerateKeyLength(), |
| GenerateSecretKey( |
| CreateAesKwKeyGenAlgorithm(kKeyLen[i]), true, 0, &key)); |
| if (SupportsAesGcm()) { |
| EXPECT_EQ(Status::ErrorGenerateKeyLength(), |
| GenerateSecretKey( |
| CreateAesGcmKeyGenAlgorithm(kKeyLen[i]), true, 0, &key)); |
| } |
| } |
| } |
| |
| TEST_F(SharedCryptoTest, GenerateKeyHmac) { |
| // Generate a small sample of HMAC keys. |
| std::vector<std::vector<uint8_t> > keys; |
| for (int i = 0; i < 16; ++i) { |
| std::vector<uint8_t> key_bytes; |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoAlgorithm algorithm = |
| CreateHmacKeyGenAlgorithm(blink::WebCryptoAlgorithmIdSha1, 512); |
| ASSERT_EQ(Status::Success(), GenerateSecretKey(algorithm, true, 0, &key)); |
| EXPECT_FALSE(key.isNull()); |
| EXPECT_TRUE(key.handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypeSecret, key.type()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdHmac, key.algorithm().id()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha1, |
| key.algorithm().hmacParams()->hash().id()); |
| EXPECT_EQ(512u, key.algorithm().hmacParams()->lengthBits()); |
| |
| std::vector<uint8_t> raw_key; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &raw_key)); |
| EXPECT_EQ(64U, raw_key.size()); |
| keys.push_back(raw_key); |
| } |
| // 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(keys)); |
| } |
| |
| // If the key length is not provided, then the block size is used. |
| TEST_F(SharedCryptoTest, GenerateKeyHmacNoLength) { |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoAlgorithm algorithm = |
| CreateHmacKeyGenAlgorithm(blink::WebCryptoAlgorithmIdSha1, 0); |
| ASSERT_EQ(Status::Success(), GenerateSecretKey(algorithm, true, 0, &key)); |
| EXPECT_TRUE(key.handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypeSecret, key.type()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdHmac, key.algorithm().id()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha1, |
| key.algorithm().hmacParams()->hash().id()); |
| EXPECT_EQ(512u, key.algorithm().hmacParams()->lengthBits()); |
| std::vector<uint8_t> raw_key; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &raw_key)); |
| EXPECT_EQ(64U, raw_key.size()); |
| |
| // The block size for HMAC SHA-512 is larger. |
| algorithm = CreateHmacKeyGenAlgorithm(blink::WebCryptoAlgorithmIdSha512, 0); |
| ASSERT_EQ(Status::Success(), GenerateSecretKey(algorithm, true, 0, &key)); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdHmac, key.algorithm().id()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha512, |
| key.algorithm().hmacParams()->hash().id()); |
| EXPECT_EQ(1024u, key.algorithm().hmacParams()->lengthBits()); |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &raw_key)); |
| EXPECT_EQ(128U, raw_key.size()); |
| } |
| |
| TEST_F(SharedCryptoTest, ImportJwkKeyUsage) { |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| base::DictionaryValue dict; |
| dict.SetString("kty", "oct"); |
| dict.SetBoolean("ext", false); |
| dict.SetString("k", "GADWrMRHwQfoNaXU5fZvTg=="); |
| const blink::WebCryptoAlgorithm aes_cbc_algorithm = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc); |
| const blink::WebCryptoAlgorithm hmac_algorithm = |
| webcrypto::CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha256); |
| const blink::WebCryptoAlgorithm aes_kw_algorithm = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw); |
| |
| // Test null usage. |
| base::ListValue* key_ops = new base::ListValue; |
| // Note: the following call makes dict assume ownership of key_ops. |
| dict.Set("key_ops", key_ops); |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(dict, aes_cbc_algorithm, false, 0, &key)); |
| EXPECT_EQ(0, key.usages()); |
| |
| // Test each key_ops value translates to the correct Web Crypto value. |
| struct TestCase { |
| const char* jwk_key_op; |
| const char* jwk_alg; |
| const blink::WebCryptoAlgorithm algorithm; |
| const blink::WebCryptoKeyUsage usage; |
| }; |
| // TODO(padolph): Add 'deriveBits' key_ops value once it is supported. |
| const TestCase test_case[] = { |
| {"encrypt", "A128CBC", aes_cbc_algorithm, |
| blink::WebCryptoKeyUsageEncrypt}, |
| {"decrypt", "A128CBC", aes_cbc_algorithm, |
| blink::WebCryptoKeyUsageDecrypt}, |
| {"sign", "HS256", hmac_algorithm, blink::WebCryptoKeyUsageSign}, |
| {"verify", "HS256", hmac_algorithm, blink::WebCryptoKeyUsageVerify}, |
| {"wrapKey", "A128KW", aes_kw_algorithm, blink::WebCryptoKeyUsageWrapKey}, |
| {"unwrapKey", "A128KW", aes_kw_algorithm, |
| blink::WebCryptoKeyUsageUnwrapKey}}; |
| for (size_t test_index = 0; test_index < ARRAYSIZE_UNSAFE(test_case); |
| ++test_index) { |
| SCOPED_TRACE(test_index); |
| dict.SetString("alg", test_case[test_index].jwk_alg); |
| key_ops->Clear(); |
| key_ops->AppendString(test_case[test_index].jwk_key_op); |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(dict, |
| test_case[test_index].algorithm, |
| false, |
| test_case[test_index].usage, |
| &key)); |
| EXPECT_EQ(test_case[test_index].usage, key.usages()); |
| } |
| |
| // Test discrete multiple usages. |
| dict.SetString("alg", "A128CBC"); |
| key_ops->Clear(); |
| key_ops->AppendString("encrypt"); |
| key_ops->AppendString("decrypt"); |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(dict, |
| aes_cbc_algorithm, |
| false, |
| blink::WebCryptoKeyUsageDecrypt | |
| blink::WebCryptoKeyUsageEncrypt, |
| &key)); |
| EXPECT_EQ(blink::WebCryptoKeyUsageDecrypt | blink::WebCryptoKeyUsageEncrypt, |
| key.usages()); |
| |
| // Test constrained key usage (input usage is a subset of JWK usage). |
| key_ops->Clear(); |
| key_ops->AppendString("encrypt"); |
| key_ops->AppendString("decrypt"); |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(dict, |
| aes_cbc_algorithm, |
| false, |
| blink::WebCryptoKeyUsageDecrypt, |
| &key)); |
| EXPECT_EQ(blink::WebCryptoKeyUsageDecrypt, key.usages()); |
| |
| // Test failure if input usage is NOT a strict subset of the JWK usage. |
| key_ops->Clear(); |
| key_ops->AppendString("encrypt"); |
| EXPECT_EQ(Status::ErrorJwkKeyopsInconsistent(), |
| ImportKeyJwkFromDict(dict, |
| aes_cbc_algorithm, |
| false, |
| blink::WebCryptoKeyUsageEncrypt | |
| blink::WebCryptoKeyUsageDecrypt, |
| &key)); |
| |
| // Test 'use' inconsistent with 'key_ops'. |
| dict.SetString("alg", "HS256"); |
| dict.SetString("use", "sig"); |
| key_ops->AppendString("sign"); |
| key_ops->AppendString("verify"); |
| key_ops->AppendString("encrypt"); |
| EXPECT_EQ(Status::ErrorJwkUseAndKeyopsInconsistent(), |
| ImportKeyJwkFromDict( |
| dict, |
| hmac_algorithm, |
| false, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify, |
| &key)); |
| |
| // Test JWK composite 'sig' use |
| dict.Remove("key_ops", NULL); |
| dict.SetString("use", "sig"); |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict( |
| dict, |
| hmac_algorithm, |
| false, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify, |
| &key)); |
| EXPECT_EQ(blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify, |
| key.usages()); |
| |
| // Test JWK composite use 'enc' usage |
| dict.SetString("alg", "A128CBC"); |
| dict.SetString("use", "enc"); |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(dict, |
| aes_cbc_algorithm, |
| false, |
| blink::WebCryptoKeyUsageDecrypt | |
| blink::WebCryptoKeyUsageEncrypt | |
| blink::WebCryptoKeyUsageWrapKey | |
| blink::WebCryptoKeyUsageUnwrapKey, |
| &key)); |
| EXPECT_EQ(blink::WebCryptoKeyUsageDecrypt | blink::WebCryptoKeyUsageEncrypt | |
| blink::WebCryptoKeyUsageWrapKey | |
| blink::WebCryptoKeyUsageUnwrapKey, |
| key.usages()); |
| } |
| |
| TEST_F(SharedCryptoTest, ImportJwkFailures) { |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoAlgorithm algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc); |
| blink::WebCryptoKeyUsageMask usage_mask = blink::WebCryptoKeyUsageEncrypt; |
| |
| // Baseline pass: each test below breaks a single item, so we start with a |
| // passing case to make sure each failure is caused by the isolated break. |
| // Each breaking subtest below resets the dictionary to this passing case when |
| // complete. |
| base::DictionaryValue dict; |
| RestoreJwkOctDictionary(&dict); |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| |
| // Fail on empty JSON. |
| EXPECT_EQ( |
| Status::ErrorImportEmptyKeyData(), |
| ImportKeyJwk( |
| CryptoData(MakeJsonVector("")), algorithm, false, usage_mask, &key)); |
| |
| // Fail on invalid JSON. |
| const std::vector<uint8_t> bad_json_vec = MakeJsonVector( |
| "{" |
| "\"kty\" : \"oct\"," |
| "\"alg\" : \"HS256\"," |
| "\"use\" : "); |
| EXPECT_EQ(Status::ErrorJwkNotDictionary(), |
| ImportKeyJwk( |
| CryptoData(bad_json_vec), algorithm, false, usage_mask, &key)); |
| |
| // Fail on JWK alg present but incorrect (expecting A128CBC). |
| dict.SetString("alg", "A127CBC"); |
| EXPECT_EQ(Status::ErrorJwkAlgorithmInconsistent(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on invalid kty. |
| dict.SetString("kty", "foo"); |
| EXPECT_EQ(Status::ErrorJwkUnexpectedKty("oct"), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on missing kty. |
| dict.Remove("kty", NULL); |
| EXPECT_EQ(Status::ErrorJwkPropertyMissing("kty"), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on kty wrong type. |
| dict.SetDouble("kty", 0.1); |
| EXPECT_EQ(Status::ErrorJwkPropertyWrongType("kty", "string"), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on invalid use. |
| dict.SetString("use", "foo"); |
| EXPECT_EQ(Status::ErrorJwkUnrecognizedUse(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on invalid use (wrong type). |
| dict.SetBoolean("use", true); |
| EXPECT_EQ(Status::ErrorJwkPropertyWrongType("use", "string"), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on invalid extractable (wrong type). |
| dict.SetInteger("ext", 0); |
| EXPECT_EQ(Status::ErrorJwkPropertyWrongType("ext", "boolean"), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on invalid key_ops (wrong type). |
| dict.SetBoolean("key_ops", true); |
| EXPECT_EQ(Status::ErrorJwkPropertyWrongType("key_ops", "list"), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on inconsistent key_ops - asking for "encrypt" however JWK contains |
| // only "foo". |
| base::ListValue* key_ops = new base::ListValue; |
| // Note: the following call makes dict assume ownership of key_ops. |
| dict.Set("key_ops", key_ops); |
| key_ops->AppendString("foo"); |
| EXPECT_EQ(Status::ErrorJwkKeyopsInconsistent(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| } |
| |
| // Import a JWK with unrecognized values for "key_ops". |
| TEST_F(SharedCryptoTest, ImportJwkUnrecognizedKeyOps) { |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoAlgorithm algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc); |
| blink::WebCryptoKeyUsageMask usage_mask = blink::WebCryptoKeyUsageEncrypt; |
| |
| base::DictionaryValue dict; |
| RestoreJwkOctDictionary(&dict); |
| |
| base::ListValue* key_ops = new base::ListValue; |
| dict.Set("key_ops", key_ops); |
| key_ops->AppendString("foo"); |
| key_ops->AppendString("bar"); |
| key_ops->AppendString("baz"); |
| key_ops->AppendString("encrypt"); |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| } |
| |
| // Import a JWK with a value in key_ops array that is not a string. |
| TEST_F(SharedCryptoTest, ImportJwkNonStringKeyOp) { |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoAlgorithm algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc); |
| blink::WebCryptoKeyUsageMask usage_mask = blink::WebCryptoKeyUsageEncrypt; |
| |
| base::DictionaryValue dict; |
| RestoreJwkOctDictionary(&dict); |
| |
| base::ListValue* key_ops = new base::ListValue; |
| dict.Set("key_ops", key_ops); |
| key_ops->AppendString("encrypt"); |
| key_ops->AppendInteger(3); |
| EXPECT_EQ(Status::ErrorJwkPropertyWrongType("key_ops[1]", "string"), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| } |
| |
| TEST_F(SharedCryptoTest, ImportJwkOctFailures) { |
| base::DictionaryValue dict; |
| RestoreJwkOctDictionary(&dict); |
| blink::WebCryptoAlgorithm algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc); |
| blink::WebCryptoKeyUsageMask usage_mask = blink::WebCryptoKeyUsageEncrypt; |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| |
| // Baseline pass. |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| EXPECT_EQ(algorithm.id(), key.algorithm().id()); |
| EXPECT_FALSE(key.extractable()); |
| EXPECT_EQ(blink::WebCryptoKeyUsageEncrypt, key.usages()); |
| EXPECT_EQ(blink::WebCryptoKeyTypeSecret, key.type()); |
| |
| // The following are specific failure cases for when kty = "oct". |
| |
| // Fail on missing k. |
| dict.Remove("k", NULL); |
| EXPECT_EQ(Status::ErrorJwkPropertyMissing("k"), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on bad b64 encoding for k. |
| dict.SetString("k", "Qk3f0DsytU8lfza2au #$% Htaw2xpop9GYyTuH0p5GghxTI="); |
| EXPECT_EQ(Status::ErrorJwkBase64Decode("k"), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on empty k. |
| dict.SetString("k", ""); |
| EXPECT_EQ(Status::ErrorJwkIncorrectKeyLength(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on k actual length (120 bits) inconsistent with the embedded JWK alg |
| // value (128) for an AES key. |
| dict.SetString("k", "AVj42h0Y5aqGtE3yluKL"); |
| EXPECT_EQ(Status::ErrorJwkIncorrectKeyLength(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| |
| // Fail on k actual length (192 bits) inconsistent with the embedded JWK alg |
| // value (128) for an AES key. |
| dict.SetString("k", "dGhpcyAgaXMgIDI0ICBieXRlcyBsb25n"); |
| EXPECT_EQ(Status::ErrorJwkIncorrectKeyLength(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkOctDictionary(&dict); |
| } |
| |
| TEST_F(SharedCryptoTest, ImportExportJwkRsaPublicKey) { |
| if (!SupportsRsaKeyImport()) |
| return; |
| |
| const bool supports_rsa_oaep = SupportsRsaOaep(); |
| if (!supports_rsa_oaep) { |
| LOG(WARNING) << "RSA-OAEP not supported on this platform. Skipping some" |
| << "tests."; |
| } |
| |
| struct TestCase { |
| const blink::WebCryptoAlgorithm algorithm; |
| const blink::WebCryptoKeyUsageMask usage; |
| const char* const jwk_alg; |
| }; |
| const TestCase kTests[] = { |
| // RSASSA-PKCS1-v1_5 SHA-1 |
| {CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha1), |
| blink::WebCryptoKeyUsageVerify, "RS1"}, |
| // RSASSA-PKCS1-v1_5 SHA-256 |
| {CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256), |
| blink::WebCryptoKeyUsageVerify, "RS256"}, |
| // RSASSA-PKCS1-v1_5 SHA-384 |
| {CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha384), |
| blink::WebCryptoKeyUsageVerify, "RS384"}, |
| // RSASSA-PKCS1-v1_5 SHA-512 |
| {CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha512), |
| blink::WebCryptoKeyUsageVerify, "RS512"}, |
| // RSA-OAEP with SHA-1 and MGF-1 / SHA-1 |
| {CreateRsaHashedImportAlgorithm(blink::WebCryptoAlgorithmIdRsaOaep, |
| blink::WebCryptoAlgorithmIdSha1), |
| blink::WebCryptoKeyUsageEncrypt, "RSA-OAEP"}, |
| // RSA-OAEP with SHA-256 and MGF-1 / SHA-256 |
| {CreateRsaHashedImportAlgorithm(blink::WebCryptoAlgorithmIdRsaOaep, |
| blink::WebCryptoAlgorithmIdSha256), |
| blink::WebCryptoKeyUsageEncrypt, "RSA-OAEP-256"}, |
| // RSA-OAEP with SHA-384 and MGF-1 / SHA-384 |
| {CreateRsaHashedImportAlgorithm(blink::WebCryptoAlgorithmIdRsaOaep, |
| blink::WebCryptoAlgorithmIdSha384), |
| blink::WebCryptoKeyUsageEncrypt, "RSA-OAEP-384"}, |
| // RSA-OAEP with SHA-512 and MGF-1 / SHA-512 |
| {CreateRsaHashedImportAlgorithm(blink::WebCryptoAlgorithmIdRsaOaep, |
| blink::WebCryptoAlgorithmIdSha512), |
| blink::WebCryptoKeyUsageEncrypt, "RSA-OAEP-512"}}; |
| |
| for (size_t test_index = 0; test_index < ARRAYSIZE_UNSAFE(kTests); |
| ++test_index) { |
| SCOPED_TRACE(test_index); |
| const TestCase& test = kTests[test_index]; |
| if (!supports_rsa_oaep && |
| test.algorithm.id() == blink::WebCryptoAlgorithmIdRsaOaep) { |
| continue; |
| } |
| |
| // Import the spki to create a public key |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(HexStringToBytes(kPublicKeySpkiDerHex)), |
| test.algorithm, |
| true, |
| test.usage, |
| &public_key)); |
| |
| // Export the public key as JWK and verify its contents |
| std::vector<uint8_t> jwk; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatJwk, public_key, &jwk)); |
| EXPECT_TRUE(VerifyPublicJwk(jwk, |
| test.jwk_alg, |
| kPublicKeyModulusHex, |
| kPublicKeyExponentHex, |
| test.usage)); |
| |
| // Import the JWK back in to create a new key |
| blink::WebCryptoKey public_key2 = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ( |
| Status::Success(), |
| ImportKeyJwk( |
| CryptoData(jwk), test.algorithm, true, test.usage, &public_key2)); |
| ASSERT_TRUE(public_key2.handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePublic, public_key2.type()); |
| EXPECT_TRUE(public_key2.extractable()); |
| EXPECT_EQ(test.algorithm.id(), public_key2.algorithm().id()); |
| |
| // Only perform SPKI consistency test for RSA-SSA as its |
| // export format is the same as kPublicKeySpkiDerHex |
| if (test.algorithm.id() == blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5) { |
| // Export the new key as spki and compare to the original. |
| std::vector<uint8_t> spki; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatSpki, public_key2, &spki)); |
| EXPECT_BYTES_EQ_HEX(kPublicKeySpkiDerHex, CryptoData(spki)); |
| } |
| } |
| } |
| |
| TEST_F(SharedCryptoTest, ImportJwkRsaFailures) { |
| base::DictionaryValue dict; |
| RestoreJwkRsaDictionary(&dict); |
| blink::WebCryptoAlgorithm algorithm = |
| CreateRsaHashedImportAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256); |
| blink::WebCryptoKeyUsageMask usage_mask = blink::WebCryptoKeyUsageVerify; |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| |
| // An RSA public key JWK _must_ have an "n" (modulus) and an "e" (exponent) |
| // entry, while an RSA private key must have those plus at least a "d" |
| // (private exponent) entry. |
| // See http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-18, |
| // section 6.3. |
| |
| // Baseline pass. |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| EXPECT_EQ(algorithm.id(), key.algorithm().id()); |
| EXPECT_FALSE(key.extractable()); |
| EXPECT_EQ(blink::WebCryptoKeyUsageVerify, key.usages()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePublic, key.type()); |
| |
| // The following are specific failure cases for when kty = "RSA". |
| |
| // Fail if either "n" or "e" is not present or malformed. |
| const std::string kKtyParmName[] = {"n", "e"}; |
| for (size_t idx = 0; idx < ARRAYSIZE_UNSAFE(kKtyParmName); ++idx) { |
| // Fail on missing parameter. |
| dict.Remove(kKtyParmName[idx], NULL); |
| EXPECT_NE(Status::Success(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkRsaDictionary(&dict); |
| |
| // Fail on bad b64 parameter encoding. |
| dict.SetString(kKtyParmName[idx], "Qk3f0DsytU8lfza2au #$% Htaw2xpop9yTuH0"); |
| EXPECT_NE(Status::Success(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkRsaDictionary(&dict); |
| |
| // Fail on empty parameter. |
| dict.SetString(kKtyParmName[idx], ""); |
| EXPECT_EQ(Status::ErrorJwkEmptyBigInteger(kKtyParmName[idx]), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| RestoreJwkRsaDictionary(&dict); |
| } |
| } |
| |
| TEST_F(SharedCryptoTest, ImportJwkInputConsistency) { |
| // The Web Crypto spec says that if a JWK value is present, but is |
| // inconsistent with the input value, the operation must fail. |
| |
| // Consistency rules when JWK value is not present: Inputs should be used. |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| bool extractable = false; |
| blink::WebCryptoAlgorithm algorithm = |
| CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha256); |
| blink::WebCryptoKeyUsageMask usage_mask = blink::WebCryptoKeyUsageVerify; |
| base::DictionaryValue dict; |
| dict.SetString("kty", "oct"); |
| dict.SetString("k", "l3nZEgZCeX8XRwJdWyK3rGB8qwjhdY8vOkbIvh4lxTuMao9Y_--hdg"); |
| std::vector<uint8_t> json_vec = MakeJsonVector(dict); |
| EXPECT_EQ( |
| Status::Success(), |
| ImportKeyJwk( |
| CryptoData(json_vec), algorithm, extractable, usage_mask, &key)); |
| EXPECT_TRUE(key.handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypeSecret, key.type()); |
| EXPECT_EQ(extractable, key.extractable()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdHmac, key.algorithm().id()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha256, |
| key.algorithm().hmacParams()->hash().id()); |
| EXPECT_EQ(320u, key.algorithm().hmacParams()->lengthBits()); |
| EXPECT_EQ(blink::WebCryptoKeyUsageVerify, key.usages()); |
| key = blink::WebCryptoKey::createNull(); |
| |
| // Consistency rules when JWK value exists: Fail if inconsistency is found. |
| |
| // Pass: All input values are consistent with the JWK values. |
| dict.Clear(); |
| dict.SetString("kty", "oct"); |
| dict.SetString("alg", "HS256"); |
| dict.SetString("use", "sig"); |
| dict.SetBoolean("ext", false); |
| dict.SetString("k", "l3nZEgZCeX8XRwJdWyK3rGB8qwjhdY8vOkbIvh4lxTuMao9Y_--hdg"); |
| json_vec = MakeJsonVector(dict); |
| EXPECT_EQ( |
| Status::Success(), |
| ImportKeyJwk( |
| CryptoData(json_vec), algorithm, extractable, usage_mask, &key)); |
| |
| // Extractable cases: |
| // 1. input=T, JWK=F ==> fail (inconsistent) |
| // 4. input=F, JWK=F ==> pass, result extractable is F |
| // 2. input=T, JWK=T ==> pass, result extractable is T |
| // 3. input=F, JWK=T ==> pass, result extractable is F |
| EXPECT_EQ( |
| Status::ErrorJwkExtInconsistent(), |
| ImportKeyJwk(CryptoData(json_vec), algorithm, true, usage_mask, &key)); |
| EXPECT_EQ( |
| Status::Success(), |
| ImportKeyJwk(CryptoData(json_vec), algorithm, false, usage_mask, &key)); |
| EXPECT_FALSE(key.extractable()); |
| dict.SetBoolean("ext", true); |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(dict, algorithm, true, usage_mask, &key)); |
| EXPECT_TRUE(key.extractable()); |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); |
| EXPECT_FALSE(key.extractable()); |
| dict.SetBoolean("ext", true); // restore previous value |
| |
| // Fail: Input algorithm (AES-CBC) is inconsistent with JWK value |
| // (HMAC SHA256). |
| dict.Clear(); |
| dict.SetString("kty", "oct"); |
| dict.SetString("alg", "HS256"); |
| dict.SetString("k", "l3nZEgZCeX8XRwJdWyK3rGB8qwjhdY8vOkbIvh4lxTuMao9Y_--hdg"); |
| EXPECT_EQ( |
| Status::ErrorJwkAlgorithmInconsistent(), |
| ImportKeyJwkFromDict(dict, |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| extractable, |
| blink::WebCryptoKeyUsageEncrypt, |
| &key)); |
| // Fail: Input usage (encrypt) is inconsistent with JWK value (use=sig). |
| EXPECT_EQ(Status::ErrorJwkUseInconsistent(), |
| ImportKeyJwk(CryptoData(json_vec), |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| extractable, |
| blink::WebCryptoKeyUsageEncrypt, |
| &key)); |
| |
| // Fail: Input algorithm (HMAC SHA1) is inconsistent with JWK value |
| // (HMAC SHA256). |
| EXPECT_EQ( |
| Status::ErrorJwkAlgorithmInconsistent(), |
| ImportKeyJwk(CryptoData(json_vec), |
| CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha1), |
| extractable, |
| usage_mask, |
| &key)); |
| |
| // Pass: JWK alg missing but input algorithm specified: use input value |
| dict.Remove("alg", NULL); |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict( |
| dict, |
| CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha256), |
| extractable, |
| usage_mask, |
| &key)); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdHmac, algorithm.id()); |
| dict.SetString("alg", "HS256"); |
| |
| // Fail: Input usage_mask (encrypt) is not a subset of the JWK value |
| // (sign|verify). Moreover "encrypt" is not a valid usage for HMAC. |
| EXPECT_EQ(Status::ErrorCreateKeyBadUsages(), |
| ImportKeyJwk(CryptoData(json_vec), |
| algorithm, |
| extractable, |
| blink::WebCryptoKeyUsageEncrypt, |
| &key)); |
| |
| // Fail: Input usage_mask (encrypt|sign|verify) is not a subset of the JWK |
| // value (sign|verify). Moreover "encrypt" is not a valid usage for HMAC. |
| usage_mask = blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageSign | |
| blink::WebCryptoKeyUsageVerify; |
| EXPECT_EQ( |
| Status::ErrorCreateKeyBadUsages(), |
| ImportKeyJwk( |
| CryptoData(json_vec), algorithm, extractable, usage_mask, &key)); |
| |
| // TODO(padolph): kty vs alg consistency tests: Depending on the kty value, |
| // only certain alg values are permitted. For example, when kty = "RSA" alg |
| // must be of the RSA family, or when kty = "oct" alg must be symmetric |
| // algorithm. |
| |
| // TODO(padolph): key_ops consistency tests |
| } |
| |
| TEST_F(SharedCryptoTest, ImportJwkHappy) { |
| // This test verifies the happy path of JWK import, including the application |
| // of the imported key material. |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| bool extractable = false; |
| blink::WebCryptoAlgorithm algorithm = |
| CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha256); |
| blink::WebCryptoKeyUsageMask usage_mask = blink::WebCryptoKeyUsageSign; |
| |
| // Import a symmetric key JWK and HMAC-SHA256 sign() |
| // Uses the first SHA256 test vector from the HMAC sample set above. |
| |
| base::DictionaryValue dict; |
| dict.SetString("kty", "oct"); |
| dict.SetString("alg", "HS256"); |
| dict.SetString("use", "sig"); |
| dict.SetBoolean("ext", false); |
| dict.SetString("k", "l3nZEgZCeX8XRwJdWyK3rGB8qwjhdY8vOkbIvh4lxTuMao9Y_--hdg"); |
| |
| ASSERT_EQ( |
| Status::Success(), |
| ImportKeyJwkFromDict(dict, algorithm, extractable, usage_mask, &key)); |
| |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha256, |
| key.algorithm().hmacParams()->hash().id()); |
| |
| const std::vector<uint8_t> message_raw = HexStringToBytes( |
| "b1689c2591eaf3c9e66070f8a77954ffb81749f1b00346f9dfe0b2ee905dcc288baf4a" |
| "92de3f4001dd9f44c468c3d07d6c6ee82faceafc97c2fc0fc0601719d2dcd0aa2aec92" |
| "d1b0ae933c65eb06a03c9c935c2bad0459810241347ab87e9f11adb30415424c6c7f5f" |
| "22a003b8ab8de54f6ded0e3ab9245fa79568451dfa258e"); |
| |
| std::vector<uint8_t> output; |
| |
| ASSERT_EQ(Status::Success(), |
| Sign(CreateAlgorithm(blink::WebCryptoAlgorithmIdHmac), |
| key, |
| CryptoData(message_raw), |
| &output)); |
| |
| const std::string mac_raw = |
| "769f00d3e6a6cc1fb426a14a4f76c6462e6149726e0dee0ec0cf97a16605ac8b"; |
| |
| EXPECT_BYTES_EQ_HEX(mac_raw, output); |
| |
| // TODO(padolph): Import an RSA public key JWK and use it |
| } |
| |
| TEST_F(SharedCryptoTest, ImportExportJwkSymmetricKey) { |
| // Raw keys are generated by openssl: |
| // % openssl rand -hex <key length bytes> |
| const char* const key_hex_128 = "3f1e7cd4f6f8543f6b1e16002e688623"; |
| const char* const key_hex_256 = |
| "bd08286b81a74783fd1ccf46b7e05af84ee25ae021210074159e0c4d9d907692"; |
| const char* const key_hex_384 = |
| "a22c5441c8b185602283d64c7221de1d0951e706bfc09539435ec0e0ed614e1d406623f2" |
| "b31d31819fec30993380dd82"; |
| const char* const key_hex_512 = |
| "5834f639000d4cf82de124fbfd26fb88d463e99f839a76ba41ac88967c80a3f61e1239a4" |
| "52e573dba0750e988152988576efd75b8d0229b7aca2ada2afd392ee"; |
| const blink::WebCryptoAlgorithm aes_cbc_alg = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc); |
| const blink::WebCryptoAlgorithm aes_gcm_alg = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesGcm); |
| const blink::WebCryptoAlgorithm aes_kw_alg = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw); |
| const blink::WebCryptoAlgorithm hmac_sha_1_alg = |
| webcrypto::CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha1); |
| const blink::WebCryptoAlgorithm hmac_sha_256_alg = |
| webcrypto::CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha256); |
| const blink::WebCryptoAlgorithm hmac_sha_384_alg = |
| webcrypto::CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha384); |
| const blink::WebCryptoAlgorithm hmac_sha_512_alg = |
| webcrypto::CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha512); |
| |
| struct TestCase { |
| const char* const key_hex; |
| const blink::WebCryptoAlgorithm algorithm; |
| const blink::WebCryptoKeyUsageMask usage; |
| const char* const jwk_alg; |
| }; |
| |
| // TODO(padolph): Test AES-CTR JWK export, once AES-CTR import works. |
| const TestCase kTests[] = { |
| // AES-CBC 128 |
| {key_hex_128, aes_cbc_alg, |
| blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt, |
| "A128CBC"}, |
| // AES-CBC 256 |
| {key_hex_256, aes_cbc_alg, blink::WebCryptoKeyUsageDecrypt, "A256CBC"}, |
| // AES-GCM 128 |
| {key_hex_128, aes_gcm_alg, |
| blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt, |
| "A128GCM"}, |
| // AES-GCM 256 |
| {key_hex_256, aes_gcm_alg, blink::WebCryptoKeyUsageDecrypt, "A256GCM"}, |
| // AES-KW 128 |
| {key_hex_128, aes_kw_alg, |
| blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey, |
| "A128KW"}, |
| // AES-KW 256 |
| {key_hex_256, aes_kw_alg, |
| blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey, |
| "A256KW"}, |
| // HMAC SHA-1 |
| {key_hex_256, hmac_sha_1_alg, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify, "HS1"}, |
| // HMAC SHA-384 |
| {key_hex_384, hmac_sha_384_alg, blink::WebCryptoKeyUsageSign, "HS384"}, |
| // HMAC SHA-512 |
| {key_hex_512, hmac_sha_512_alg, blink::WebCryptoKeyUsageVerify, "HS512"}, |
| // Large usage value |
| {key_hex_256, aes_cbc_alg, |
| blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt | |
| blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey, |
| "A256CBC"}, |
| // Zero usage value |
| {key_hex_512, hmac_sha_512_alg, 0, "HS512"}, |
| }; |
| |
| // Round-trip import/export each key. |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| std::vector<uint8_t> json; |
| for (size_t test_index = 0; test_index < ARRAYSIZE_UNSAFE(kTests); |
| ++test_index) { |
| SCOPED_TRACE(test_index); |
| const TestCase& test = kTests[test_index]; |
| |
| // Skip AES-GCM tests where not supported. |
| if (test.algorithm.id() == blink::WebCryptoAlgorithmIdAesGcm && |
| !SupportsAesGcm()) { |
| continue; |
| } |
| |
| // Import a raw key. |
| key = ImportSecretKeyFromRaw( |
| HexStringToBytes(test.key_hex), test.algorithm, test.usage); |
| |
| // Export the key in JWK format and validate. |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatJwk, key, &json)); |
| EXPECT_TRUE(VerifySecretJwk(json, test.jwk_alg, test.key_hex, test.usage)); |
| |
| // Import the JWK-formatted key. |
| ASSERT_EQ( |
| Status::Success(), |
| ImportKeyJwk(CryptoData(json), test.algorithm, true, test.usage, &key)); |
| EXPECT_TRUE(key.handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypeSecret, key.type()); |
| EXPECT_EQ(test.algorithm.id(), key.algorithm().id()); |
| EXPECT_EQ(true, key.extractable()); |
| EXPECT_EQ(test.usage, key.usages()); |
| |
| // Export the key in raw format and compare to the original. |
| std::vector<uint8_t> key_raw_out; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &key_raw_out)); |
| EXPECT_BYTES_EQ_HEX(test.key_hex, key_raw_out); |
| } |
| } |
| |
| TEST_F(SharedCryptoTest, ExportJwkEmptySymmetricKey) { |
| const blink::WebCryptoAlgorithm import_algorithm = |
| webcrypto::CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha1); |
| |
| blink::WebCryptoKeyUsageMask usages = blink::WebCryptoKeyUsageSign; |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| |
| // Import a zero-byte HMAC key. |
| const char key_data_hex[] = ""; |
| key = ImportSecretKeyFromRaw( |
| HexStringToBytes(key_data_hex), import_algorithm, usages); |
| EXPECT_EQ(0u, key.algorithm().hmacParams()->lengthBits()); |
| |
| // Export the key in JWK format and validate. |
| std::vector<uint8_t> json; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatJwk, key, &json)); |
| EXPECT_TRUE(VerifySecretJwk(json, "HS1", key_data_hex, usages)); |
| |
| // Now try re-importing the JWK key. |
| key = blink::WebCryptoKey::createNull(); |
| EXPECT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatJwk, |
| CryptoData(json), |
| import_algorithm, |
| true, |
| usages, |
| &key)); |
| |
| EXPECT_EQ(blink::WebCryptoKeyTypeSecret, key.type()); |
| EXPECT_EQ(0u, key.algorithm().hmacParams()->lengthBits()); |
| |
| std::vector<uint8_t> exported_key_data; |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &exported_key_data)); |
| |
| EXPECT_EQ(0u, exported_key_data.size()); |
| } |
| |
| TEST_F(SharedCryptoTest, ImportExportSpki) { |
| if (!SupportsRsaKeyImport()) |
| return; |
| |
| // Passing case: Import a valid RSA key in SPKI format. |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(HexStringToBytes(kPublicKeySpkiDerHex)), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256), |
| true, |
| blink::WebCryptoKeyUsageVerify, |
| &key)); |
| EXPECT_TRUE(key.handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePublic, key.type()); |
| EXPECT_TRUE(key.extractable()); |
| EXPECT_EQ(blink::WebCryptoKeyUsageVerify, key.usages()); |
| EXPECT_EQ(kModulusLengthBits, |
| key.algorithm().rsaHashedParams()->modulusLengthBits()); |
| EXPECT_BYTES_EQ_HEX( |
| "010001", |
| CryptoData(key.algorithm().rsaHashedParams()->publicExponent())); |
| |
| // Failing case: Empty SPKI data |
| EXPECT_EQ( |
| Status::ErrorImportEmptyKeyData(), |
| ImportKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(std::vector<uint8_t>()), |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5), |
| true, |
| blink::WebCryptoKeyUsageVerify, |
| &key)); |
| |
| // Failing case: Bad DER encoding. |
| EXPECT_EQ( |
| Status::DataError(), |
| ImportKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(HexStringToBytes("618333c4cb")), |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5), |
| true, |
| blink::WebCryptoKeyUsageVerify, |
| &key)); |
| |
| // Failing case: Import RSA key but provide an inconsistent input algorithm. |
| EXPECT_EQ(Status::ErrorUnsupportedImportKeyFormat(), |
| ImportKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(HexStringToBytes(kPublicKeySpkiDerHex)), |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &key)); |
| |
| // Passing case: Export a previously imported RSA public key in SPKI format |
| // and compare to original data. |
| std::vector<uint8_t> output; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatSpki, key, &output)); |
| EXPECT_BYTES_EQ_HEX(kPublicKeySpkiDerHex, output); |
| |
| // Failing case: Try to export a previously imported RSA public key in raw |
| // format (not allowed for a public key). |
| EXPECT_EQ(Status::ErrorUnsupportedExportKeyFormat(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &output)); |
| |
| // Failing case: Try to export a non-extractable key |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(HexStringToBytes(kPublicKeySpkiDerHex)), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256), |
| false, |
| blink::WebCryptoKeyUsageVerify, |
| &key)); |
| EXPECT_TRUE(key.handle()); |
| EXPECT_FALSE(key.extractable()); |
| EXPECT_EQ(Status::ErrorKeyNotExtractable(), |
| ExportKey(blink::WebCryptoKeyFormatSpki, key, &output)); |
| |
| // TODO(eroman): Failing test: Import a SPKI with an unrecognized hash OID |
| // TODO(eroman): Failing test: Import a SPKI with invalid algorithm params |
| // TODO(eroman): Failing test: Import a SPKI with inconsistent parameters |
| // (e.g. SHA-1 in OID, SHA-256 in params) |
| // TODO(eroman): Failing test: Import a SPKI for RSA-SSA, but with params |
| // as OAEP/PSS |
| } |
| |
| TEST_F(SharedCryptoTest, ImportExportPkcs8) { |
| if (!SupportsRsaKeyImport()) |
| return; |
| |
| // Passing case: Import a valid RSA key in PKCS#8 format. |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatPkcs8, |
| CryptoData(HexStringToBytes(kPrivateKeyPkcs8DerHex)), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &key)); |
| EXPECT_TRUE(key.handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePrivate, key.type()); |
| EXPECT_TRUE(key.extractable()); |
| EXPECT_EQ(blink::WebCryptoKeyUsageSign, key.usages()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha1, |
| key.algorithm().rsaHashedParams()->hash().id()); |
| EXPECT_EQ(kModulusLengthBits, |
| key.algorithm().rsaHashedParams()->modulusLengthBits()); |
| EXPECT_BYTES_EQ_HEX( |
| "010001", |
| CryptoData(key.algorithm().rsaHashedParams()->publicExponent())); |
| |
| std::vector<uint8_t> exported_key; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatPkcs8, key, &exported_key)); |
| EXPECT_BYTES_EQ_HEX(kPrivateKeyPkcs8DerHex, exported_key); |
| |
| // Failing case: Empty PKCS#8 data |
| EXPECT_EQ(Status::ErrorImportEmptyKeyData(), |
| ImportKey(blink::WebCryptoKeyFormatPkcs8, |
| CryptoData(std::vector<uint8_t>()), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &key)); |
| |
| // Failing case: Bad DER encoding. |
| EXPECT_EQ( |
| Status::DataError(), |
| ImportKey(blink::WebCryptoKeyFormatPkcs8, |
| CryptoData(HexStringToBytes("618333c4cb")), |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &key)); |
| |
| // Failing case: Import RSA key but provide an inconsistent input algorithm |
| // and usage. Several issues here: |
| // * AES-CBC doesn't support PKCS8 key format |
| // * AES-CBC doesn't support "sign" usage |
| EXPECT_EQ(Status::ErrorUnsupportedImportKeyFormat(), |
| ImportKey(blink::WebCryptoKeyFormatPkcs8, |
| CryptoData(HexStringToBytes(kPrivateKeyPkcs8DerHex)), |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &key)); |
| } |
| |
| // Tests importing of PKCS8 data that does not define a valid RSA key. |
| TEST_F(SharedCryptoTest, ImportInvalidPkcs8) { |
| if (!SupportsRsaKeyImport()) |
| return; |
| |
| // kPrivateKeyPkcs8DerHex defines an RSA private key in PKCS8 format, whose |
| // parameters appear at the following offsets: |
| // |
| // n: (offset=36, len=129) |
| // e: (offset=167, len=3) |
| // d: (offset=173, len=128) |
| // p: (offset=303, len=65) |
| // q: (offset=370, len=65) |
| // dp: (offset=437, len=64) |
| // dq; (offset=503, len=64) |
| // qi: (offset=569, len=64) |
| |
| // Do several tests, each of which invert a single byte within the input. |
| const unsigned int kOffsetsToCorrupt[] = { |
| 50, // inside n |
| 168, // inside e |
| 175, // inside d |
| 333, // inside p |
| 373, // inside q |
| 450, // inside dp |
| 550, // inside dq |
| 600, // inside qi |
| }; |
| |
| for (size_t test_index = 0; test_index < arraysize(kOffsetsToCorrupt); |
| ++test_index) { |
| SCOPED_TRACE(test_index); |
| |
| unsigned int i = kOffsetsToCorrupt[test_index]; |
| std::vector<uint8_t> corrupted_data = |
| HexStringToBytes(kPrivateKeyPkcs8DerHex); |
| corrupted_data[i] = ~corrupted_data[i]; |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| EXPECT_EQ(Status::DataError(), |
| ImportKey(blink::WebCryptoKeyFormatPkcs8, |
| CryptoData(corrupted_data), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &key)); |
| } |
| } |
| |
| // Tests JWK import and export by doing a roundtrip key conversion and ensuring |
| // it was lossless: |
| // |
| // PKCS8 --> JWK --> PKCS8 |
| TEST_F(SharedCryptoTest, ImportRsaPrivateKeyJwkToPkcs8RoundTrip) { |
| if (!SupportsRsaKeyImport()) |
| return; |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatPkcs8, |
| CryptoData(HexStringToBytes(kPrivateKeyPkcs8DerHex)), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &key)); |
| |
| std::vector<uint8_t> exported_key_jwk; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatJwk, key, &exported_key_jwk)); |
| |
| // All of the optional parameters (p, q, dp, dq, qi) should be present in the |
| // output. |
| const char* expected_jwk = |
| "{\"alg\":\"RS1\",\"d\":\"M6UEKpCyfU9UUcqbu9C0R3GhAa-IQ0Cu-YhfKku-" |
| "kuiUpySsPFaMj5eFOtB8AmbIxqPKCSnx6PESMYhEKfxNmuVf7olqEM5wfD7X5zTkRyejlXRQ" |
| "GlMmgxCcKrrKuig8MbS9L1PD7jfjUs7jT55QO9gMBiKtecbc7og1R8ajsyU\",\"dp\":" |
| "\"KPoTk4ZVvh-" |
| "KFZy6ylpy6hkMMAieGc0nSlVvNsT24Z9VSzTAd3kEJ7vdjdPt4kSDKPOF2Bsw6OQ7L_-" |
| "gJ4YZeQ\",\"dq\":\"Gos485j6cSBJiY1_t57gp3ZoeRKZzfoJ78DlB6yyHtdDAe9b_Ui-" |
| "RV6utuFnglWCdYCo5OjhQVHRUQqCo_LnKQ\",\"e\":\"AQAB\",\"ext\":true,\"key_" |
| "ops\":[\"sign\"],\"kty\":\"RSA\",\"n\":" |
| "\"pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhKoijlN_" |
| "1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm_4nRnxBazC0_" |
| "DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc\",\"p\":\"5-" |
| "iUJyCod1Fyc6NWBT6iobwMlKpy1VxuhilrLfyWeUjApyy8zKfqyzVwbgmh31WhU1vZs8w0Fg" |
| "s7bc0-2o5kQw\",\"q\":\"tp3KHPfU1-yB51uQ_MqHSrzeEj_" |
| "ScAGAqpBHm25I3o1n7ST58Z2FuidYdPVCzSDccj5pYzZKH5QlRSsmmmeZ_Q\",\"qi\":" |
| "\"JxVqukEm0kqB86Uoy_sn9WiG-" |
| "ECp9uhuF6RLlP6TGVhLjiL93h5aLjvYqluo2FhBlOshkKz4MrhH8To9JKefTQ\"}"; |
| |
| ASSERT_EQ(CryptoData(std::string(expected_jwk)), |
| CryptoData(exported_key_jwk)); |
| |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatJwk, |
| CryptoData(exported_key_jwk), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &key)); |
| |
| std::vector<uint8_t> exported_key_pkcs8; |
| ASSERT_EQ( |
| Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatPkcs8, key, &exported_key_pkcs8)); |
| |
| ASSERT_EQ(CryptoData(HexStringToBytes(kPrivateKeyPkcs8DerHex)), |
| CryptoData(exported_key_pkcs8)); |
| } |
| |
| // Tests importing multiple RSA private keys from JWK, and then exporting to |
| // PKCS8. |
| // |
| // This is a regression test for http://crbug.com/378315, for which importing |
| // a sequence of keys from JWK could yield the wrong key. The first key would |
| // be imported correctly, however every key after that would actually import |
| // the first key. |
| TEST_F(SharedCryptoTest, ImportMultipleRSAPrivateKeysJwk) { |
| if (!SupportsRsaKeyImport()) |
| return; |
| |
| scoped_ptr<base::ListValue> key_list; |
| ASSERT_TRUE(ReadJsonTestFileToList("rsa_private_keys.json", &key_list)); |
| |
| // For this test to be meaningful the keys MUST be kept alive before importing |
| // new keys. |
| std::vector<blink::WebCryptoKey> live_keys; |
| |
| for (size_t key_index = 0; key_index < key_list->GetSize(); ++key_index) { |
| SCOPED_TRACE(key_index); |
| |
| base::DictionaryValue* key_values; |
| ASSERT_TRUE(key_list->GetDictionary(key_index, &key_values)); |
| |
| // Get the JWK representation of the key. |
| base::DictionaryValue* key_jwk; |
| ASSERT_TRUE(key_values->GetDictionary("jwk", &key_jwk)); |
| |
| // Get the PKCS8 representation of the key. |
| std::string pkcs8_hex_string; |
| ASSERT_TRUE(key_values->GetString("pkcs8", &pkcs8_hex_string)); |
| std::vector<uint8_t> pkcs8_bytes = HexStringToBytes(pkcs8_hex_string); |
| |
| // Get the modulus length for the key. |
| int modulus_length_bits = 0; |
| ASSERT_TRUE(key_values->GetInteger("modulusLength", &modulus_length_bits)); |
| |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| |
| // Import the key from JWK. |
| ASSERT_EQ( |
| Status::Success(), |
| ImportKeyJwkFromDict(*key_jwk, |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &private_key)); |
| |
| live_keys.push_back(private_key); |
| |
| EXPECT_EQ( |
| modulus_length_bits, |
| static_cast<int>( |
| private_key.algorithm().rsaHashedParams()->modulusLengthBits())); |
| |
| // Export to PKCS8 and verify that it matches expectation. |
| std::vector<uint8_t> exported_key_pkcs8; |
| ASSERT_EQ( |
| Status::Success(), |
| ExportKey( |
| blink::WebCryptoKeyFormatPkcs8, private_key, &exported_key_pkcs8)); |
| |
| EXPECT_BYTES_EQ(pkcs8_bytes, exported_key_pkcs8); |
| } |
| } |
| |
| // Import an RSA private key using JWK. Next import a JWK containing the same |
| // modulus, but mismatched parameters for the rest. It should NOT be possible |
| // that the second import retrieves the first key. See http://crbug.com/378315 |
| // for how that could happen. |
| TEST_F(SharedCryptoTest, ImportJwkExistingModulusAndInvalid) { |
| #if defined(USE_NSS) |
| if (!NSS_VersionCheck("3.16.2")) { |
| LOG(WARNING) << "Skipping test because lacks NSS support"; |
| return; |
| } |
| #endif |
| |
| scoped_ptr<base::ListValue> key_list; |
| ASSERT_TRUE(ReadJsonTestFileToList("rsa_private_keys.json", &key_list)); |
| |
| // Import a 1024-bit private key. |
| base::DictionaryValue* key1_props; |
| ASSERT_TRUE(key_list->GetDictionary(1, &key1_props)); |
| base::DictionaryValue* key1_jwk; |
| ASSERT_TRUE(key1_props->GetDictionary("jwk", &key1_jwk)); |
| |
| blink::WebCryptoKey key1 = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(*key1_jwk, |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &key1)); |
| |
| ASSERT_EQ(1024u, key1.algorithm().rsaHashedParams()->modulusLengthBits()); |
| |
| // Construct a JWK using the modulus of key1, but all the other fields from |
| // another key (also a 1024-bit private key). |
| base::DictionaryValue* key2_props; |
| ASSERT_TRUE(key_list->GetDictionary(5, &key2_props)); |
| base::DictionaryValue* key2_jwk; |
| ASSERT_TRUE(key2_props->GetDictionary("jwk", &key2_jwk)); |
| std::string modulus; |
| key1_jwk->GetString("n", &modulus); |
| key2_jwk->SetString("n", modulus); |
| |
| // This should fail, as the n,e,d parameters are not consistent. It MUST NOT |
| // somehow return the key created earlier. |
| blink::WebCryptoKey key2 = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::OperationError(), |
| ImportKeyJwkFromDict(*key2_jwk, |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &key2)); |
| } |
| |
| // Import a JWK RSA private key with some optional parameters missing (q, dp, |
| // dq, qi). |
| // |
| // The only optional parameter included is "p". |
| // |
| // This fails because JWA says that producers must include either ALL optional |
| // parameters or NONE. |
| TEST_F(SharedCryptoTest, ImportRsaPrivateKeyJwkMissingOptionalParams) { |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| |
| base::DictionaryValue dict; |
| dict.SetString("kty", "RSA"); |
| dict.SetString("alg", "RS1"); |
| |
| dict.SetString( |
| "n", |
| "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhKoijlN_" |
| "1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm_4nRnxBazC0_" |
| "DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc"); |
| dict.SetString("e", "AQAB"); |
| dict.SetString( |
| "d", |
| "M6UEKpCyfU9UUcqbu9C0R3GhAa-IQ0Cu-YhfKku-" |
| "kuiUpySsPFaMj5eFOtB8AmbIxqPKCSnx6PESMYhEKfxNmuVf7olqEM5wfD7X5zTkRyejlXRQ" |
| "GlMmgxCcKrrKuig8MbS9L1PD7jfjUs7jT55QO9gMBiKtecbc7og1R8ajsyU"); |
| |
| dict.SetString("p", |
| "5-" |
| "iUJyCod1Fyc6NWBT6iobwMlKpy1VxuhilrLfyWeUjApyy8zKfqyzVwbgmh31W" |
| "hU1vZs8w0Fgs7bc0-2o5kQw"); |
| |
| ASSERT_EQ(Status::ErrorJwkPropertyMissing("q"), |
| ImportKeyJwkFromDict(dict, |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &key)); |
| } |
| |
| // Import a JWK RSA private key, without any of the optional parameters. |
| // |
| // According to JWA, such keys are valid, but applications SHOULD |
| // include all the parameters when sending, and recipients MAY |
| // accept them, but are not required to. Chromium's WebCrypto does |
| // not allow such degenerate keys. |
| TEST_F(SharedCryptoTest, ImportRsaPrivateKeyJwkIncorrectOptionalEmpty) { |
| if (!SupportsRsaKeyImport()) |
| return; |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| |
| base::DictionaryValue dict; |
| dict.SetString("kty", "RSA"); |
| dict.SetString("alg", "RS1"); |
| |
| dict.SetString( |
| "n", |
| "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhKoijlN_" |
| "1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm_4nRnxBazC0_" |
| "DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc"); |
| dict.SetString("e", "AQAB"); |
| dict.SetString( |
| "d", |
| "M6UEKpCyfU9UUcqbu9C0R3GhAa-IQ0Cu-YhfKku-" |
| "kuiUpySsPFaMj5eFOtB8AmbIxqPKCSnx6PESMYhEKfxNmuVf7olqEM5wfD7X5zTkRyejlXRQ" |
| "GlMmgxCcKrrKuig8MbS9L1PD7jfjUs7jT55QO9gMBiKtecbc7og1R8ajsyU"); |
| |
| ASSERT_EQ(Status::ErrorJwkPropertyMissing("p"), |
| ImportKeyJwkFromDict(dict, |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &key)); |
| } |
| |
| // Tries importing a public RSA key whose exponent contains leading zeros. |
| TEST_F(SharedCryptoTest, ImportJwkRsaNonMinimalExponent) { |
| base::DictionaryValue dict; |
| |
| dict.SetString("kty", "RSA"); |
| dict.SetString("e", "AAEAAQ"); // 00 01 00 01 |
| dict.SetString( |
| "n", |
| "qLOyhK-OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx-CwgtaTpef87Wdc9GaFEncsDLxk" |
| "p0LGxjD1M8jMcvYq6DPEC_JYQumEu3i9v5fAEH1VvbZi9cTg-rmEXLUUjvc5LdOq_5OuHmtm" |
| "e7PUJHYW1PW6ENTP0ibeiNOfFvs"); |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| |
| EXPECT_EQ(Status::ErrorJwkBigIntegerHasLeadingZero("e"), |
| ImportKeyJwkFromDict(dict, |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256), |
| false, |
| blink::WebCryptoKeyUsageVerify, |
| &key)); |
| } |
| |
| TEST_F(SharedCryptoTest, GenerateKeyPairRsa) { |
| // Note: using unrealistic short key lengths here to avoid bogging down tests. |
| |
| // Successful WebCryptoAlgorithmIdRsaSsaPkcs1v1_5 key generation (sha256) |
| const unsigned int modulus_length = 256; |
| const std::vector<uint8_t> public_exponent = HexStringToBytes("010001"); |
| blink::WebCryptoAlgorithm algorithm = |
| CreateRsaHashedKeyGenAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| modulus_length, |
| public_exponent); |
| bool extractable = true; |
| const blink::WebCryptoKeyUsageMask usage_mask = 0; |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| |
| EXPECT_EQ(Status::Success(), |
| GenerateKeyPair( |
| algorithm, extractable, usage_mask, &public_key, &private_key)); |
| EXPECT_FALSE(public_key.isNull()); |
| EXPECT_FALSE(private_key.isNull()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePublic, public_key.type()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePrivate, private_key.type()); |
| EXPECT_EQ(modulus_length, |
| public_key.algorithm().rsaHashedParams()->modulusLengthBits()); |
| EXPECT_EQ(modulus_length, |
| private_key.algorithm().rsaHashedParams()->modulusLengthBits()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha256, |
| public_key.algorithm().rsaHashedParams()->hash().id()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha256, |
| private_key.algorithm().rsaHashedParams()->hash().id()); |
| EXPECT_TRUE(public_key.extractable()); |
| EXPECT_EQ(extractable, private_key.extractable()); |
| EXPECT_EQ(usage_mask, public_key.usages()); |
| EXPECT_EQ(usage_mask, private_key.usages()); |
| |
| // Try exporting the generated key pair, and then re-importing to verify that |
| // the exported data was valid. |
| std::vector<uint8_t> public_key_spki; |
| EXPECT_EQ( |
| Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatSpki, public_key, &public_key_spki)); |
| |
| if (SupportsRsaKeyImport()) { |
| public_key = blink::WebCryptoKey::createNull(); |
| EXPECT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(public_key_spki), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256), |
| true, |
| usage_mask, |
| &public_key)); |
| EXPECT_EQ(modulus_length, |
| public_key.algorithm().rsaHashedParams()->modulusLengthBits()); |
| |
| std::vector<uint8_t> private_key_pkcs8; |
| EXPECT_EQ( |
| Status::Success(), |
| ExportKey( |
| blink::WebCryptoKeyFormatPkcs8, private_key, &private_key_pkcs8)); |
| private_key = blink::WebCryptoKey::createNull(); |
| EXPECT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatPkcs8, |
| CryptoData(private_key_pkcs8), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256), |
| true, |
| usage_mask, |
| &private_key)); |
| EXPECT_EQ(modulus_length, |
| private_key.algorithm().rsaHashedParams()->modulusLengthBits()); |
| } |
| |
| // Fail with bad modulus. |
| algorithm = |
| CreateRsaHashedKeyGenAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| 0, |
| public_exponent); |
| EXPECT_EQ(Status::ErrorGenerateRsaUnsupportedModulus(), |
| GenerateKeyPair( |
| algorithm, extractable, usage_mask, &public_key, &private_key)); |
| |
| // Fail with bad exponent: larger than unsigned long. |
| unsigned int exponent_length = sizeof(unsigned long) + 1; // NOLINT |
| const std::vector<uint8_t> long_exponent(exponent_length, 0x01); |
| algorithm = |
| CreateRsaHashedKeyGenAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| modulus_length, |
| long_exponent); |
| EXPECT_EQ(Status::ErrorGenerateKeyPublicExponent(), |
| GenerateKeyPair( |
| algorithm, extractable, usage_mask, &public_key, &private_key)); |
| |
| // Fail with bad exponent: empty. |
| const std::vector<uint8_t> empty_exponent; |
| algorithm = |
| CreateRsaHashedKeyGenAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| modulus_length, |
| empty_exponent); |
| EXPECT_EQ(Status::ErrorGenerateKeyPublicExponent(), |
| GenerateKeyPair( |
| algorithm, extractable, usage_mask, &public_key, &private_key)); |
| |
| // Fail with bad exponent: all zeros. |
| std::vector<uint8_t> exponent_with_leading_zeros(15, 0x00); |
| algorithm = |
| CreateRsaHashedKeyGenAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| modulus_length, |
| exponent_with_leading_zeros); |
| EXPECT_EQ(Status::ErrorGenerateKeyPublicExponent(), |
| GenerateKeyPair( |
| algorithm, extractable, usage_mask, &public_key, &private_key)); |
| |
| // Key generation success using exponent with leading zeros. |
| exponent_with_leading_zeros.insert(exponent_with_leading_zeros.end(), |
| public_exponent.begin(), |
| public_exponent.end()); |
| algorithm = |
| CreateRsaHashedKeyGenAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| modulus_length, |
| exponent_with_leading_zeros); |
| EXPECT_EQ(Status::Success(), |
| GenerateKeyPair( |
| algorithm, extractable, usage_mask, &public_key, &private_key)); |
| EXPECT_FALSE(public_key.isNull()); |
| EXPECT_FALSE(private_key.isNull()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePublic, public_key.type()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePrivate, private_key.type()); |
| EXPECT_TRUE(public_key.extractable()); |
| EXPECT_EQ(extractable, private_key.extractable()); |
| EXPECT_EQ(usage_mask, public_key.usages()); |
| EXPECT_EQ(usage_mask, private_key.usages()); |
| |
| // Successful WebCryptoAlgorithmIdRsaSsaPkcs1v1_5 key generation (sha1) |
| algorithm = |
| CreateRsaHashedKeyGenAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha1, |
| modulus_length, |
| public_exponent); |
| EXPECT_EQ( |
| Status::Success(), |
| GenerateKeyPair(algorithm, false, usage_mask, &public_key, &private_key)); |
| EXPECT_FALSE(public_key.isNull()); |
| EXPECT_FALSE(private_key.isNull()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePublic, public_key.type()); |
| EXPECT_EQ(blink::WebCryptoKeyTypePrivate, private_key.type()); |
| EXPECT_EQ(modulus_length, |
| public_key.algorithm().rsaHashedParams()->modulusLengthBits()); |
| EXPECT_EQ(modulus_length, |
| private_key.algorithm().rsaHashedParams()->modulusLengthBits()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha1, |
| public_key.algorithm().rsaHashedParams()->hash().id()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha1, |
| private_key.algorithm().rsaHashedParams()->hash().id()); |
| // Even though "extractable" was set to false, the public key remains |
| // extractable. |
| EXPECT_TRUE(public_key.extractable()); |
| EXPECT_FALSE(private_key.extractable()); |
| EXPECT_EQ(usage_mask, public_key.usages()); |
| EXPECT_EQ(usage_mask, private_key.usages()); |
| |
| // Exporting a private key as SPKI format doesn't make sense. However this |
| // will first fail because the key is not extractable. |
| std::vector<uint8_t> output; |
| EXPECT_EQ(Status::ErrorKeyNotExtractable(), |
| ExportKey(blink::WebCryptoKeyFormatSpki, private_key, &output)); |
| |
| // Re-generate an extractable private_key and try to export it as SPKI format. |
| // This should fail since spki is for public keys. |
| EXPECT_EQ( |
| Status::Success(), |
| GenerateKeyPair(algorithm, true, usage_mask, &public_key, &private_key)); |
| EXPECT_EQ(Status::ErrorUnexpectedKeyType(), |
| ExportKey(blink::WebCryptoKeyFormatSpki, private_key, &output)); |
| } |
| |
| TEST_F(SharedCryptoTest, GenerateKeyPairRsaBadModulusLength) { |
| const unsigned int kBadModulusBits[] = { |
| 0, |
| 248, // Too small. |
| 257, // Not a multiple of 8. |
| 1023, // Not a multiple of 8. |
| 0xFFFFFFFF, // Too big. |
| 16384 + 8, // 16384 is the maxmimum length that NSS succeeds for. |
| }; |
| |
| const std::vector<uint8_t> public_exponent = HexStringToBytes("010001"); |
| |
| for (size_t i = 0; i < arraysize(kBadModulusBits); ++i) { |
| const unsigned int modulus_length_bits = kBadModulusBits[i]; |
| blink::WebCryptoAlgorithm algorithm = CreateRsaHashedKeyGenAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| modulus_length_bits, |
| public_exponent); |
| bool extractable = true; |
| const blink::WebCryptoKeyUsageMask usage_mask = 0; |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| |
| EXPECT_EQ( |
| Status::ErrorGenerateRsaUnsupportedModulus(), |
| GenerateKeyPair( |
| algorithm, extractable, usage_mask, &public_key, &private_key)); |
| } |
| } |
| |
| // Try generating RSA key pairs using unsupported public exponents. Only |
| // exponents of 3 and 65537 are supported. While both OpenSSL and NSS can |
| // support other values, OpenSSL hangs when given invalid exponents, so use a |
| // whitelist to validate the parameters. |
| TEST_F(SharedCryptoTest, GenerateKeyPairRsaBadExponent) { |
| const unsigned int modulus_length = 1024; |
| |
| const char* const kPublicExponents[] = { |
| "11", // 17 - This is a valid public exponent, but currently disallowed. |
| "00", |
| "01", |
| "02", |
| "010000", // 65536 |
| }; |
| |
| for (size_t i = 0; i < arraysize(kPublicExponents); ++i) { |
| SCOPED_TRACE(i); |
| blink::WebCryptoAlgorithm algorithm = CreateRsaHashedKeyGenAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| modulus_length, |
| HexStringToBytes(kPublicExponents[i])); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| |
| EXPECT_EQ(Status::ErrorGenerateKeyPublicExponent(), |
| GenerateKeyPair(algorithm, true, 0, &public_key, &private_key)); |
| } |
| } |
| |
| TEST_F(SharedCryptoTest, RsaSsaSignVerifyFailures) { |
| if (!SupportsRsaKeyImport()) |
| return; |
| |
| // Import a key pair. |
| blink::WebCryptoAlgorithm import_algorithm = |
| CreateRsaHashedImportAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha1); |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| ASSERT_NO_FATAL_FAILURE( |
| ImportRsaKeyPair(HexStringToBytes(kPublicKeySpkiDerHex), |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), |
| import_algorithm, |
| false, |
| blink::WebCryptoKeyUsageVerify, |
| blink::WebCryptoKeyUsageSign, |
| &public_key, |
| &private_key)); |
| |
| blink::WebCryptoAlgorithm algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5); |
| |
| std::vector<uint8_t> signature; |
| bool signature_match; |
| |
| // Compute a signature. |
| const std::vector<uint8_t> data = HexStringToBytes("010203040506070809"); |
| ASSERT_EQ(Status::Success(), |
| Sign(algorithm, private_key, CryptoData(data), &signature)); |
| |
| // Ensure truncated signature does not verify by passing one less byte. |
| EXPECT_EQ( |
| Status::Success(), |
| Verify(algorithm, |
| public_key, |
| CryptoData(vector_as_array(&signature), signature.size() - 1), |
| CryptoData(data), |
| &signature_match)); |
| EXPECT_FALSE(signature_match); |
| |
| // Ensure truncated signature does not verify by passing no bytes. |
| EXPECT_EQ(Status::Success(), |
| Verify(algorithm, |
| public_key, |
| CryptoData(), |
| CryptoData(data), |
| &signature_match)); |
| EXPECT_FALSE(signature_match); |
| |
| // Ensure corrupted signature does not verify. |
| std::vector<uint8_t> corrupt_sig = signature; |
| corrupt_sig[corrupt_sig.size() / 2] ^= 0x1; |
| EXPECT_EQ(Status::Success(), |
| Verify(algorithm, |
| public_key, |
| CryptoData(corrupt_sig), |
| CryptoData(data), |
| &signature_match)); |
| EXPECT_FALSE(signature_match); |
| |
| // Ensure signatures that are greater than the modulus size fail. |
| const unsigned int long_message_size_bytes = 1024; |
| DCHECK_GT(long_message_size_bytes, kModulusLengthBits / 8); |
| const unsigned char kLongSignature[long_message_size_bytes] = {0}; |
| EXPECT_EQ(Status::Success(), |
| Verify(algorithm, |
| public_key, |
| CryptoData(kLongSignature, sizeof(kLongSignature)), |
| CryptoData(data), |
| &signature_match)); |
| EXPECT_FALSE(signature_match); |
| |
| // Ensure that signing and verifying with an incompatible algorithm fails. |
| algorithm = CreateAlgorithm(blink::WebCryptoAlgorithmIdRsaOaep); |
| |
| EXPECT_EQ(Status::ErrorUnexpected(), |
| Sign(algorithm, private_key, CryptoData(data), &signature)); |
| EXPECT_EQ(Status::ErrorUnexpected(), |
| Verify(algorithm, |
| public_key, |
| CryptoData(signature), |
| CryptoData(data), |
| &signature_match)); |
| |
| // Some crypto libraries (NSS) can automatically select the RSA SSA inner hash |
| // based solely on the contents of the input signature data. In the Web Crypto |
| // implementation, the inner hash should be specified uniquely by the key |
| // algorithm parameter. To validate this behavior, call Verify with a computed |
| // signature that used one hash type (SHA-1), but pass in a key with a |
| // different inner hash type (SHA-256). If the hash type is determined by the |
| // signature itself (undesired), the verify will pass, while if the hash type |
| // is specified by the key algorithm (desired), the verify will fail. |
| |
| // Compute a signature using SHA-1 as the inner hash. |
| EXPECT_EQ(Status::Success(), |
| Sign(CreateAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5), |
| private_key, |
| CryptoData(data), |
| &signature)); |
| |
| blink::WebCryptoKey public_key_256 = blink::WebCryptoKey::createNull(); |
| EXPECT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(HexStringToBytes(kPublicKeySpkiDerHex)), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256), |
| true, |
| blink::WebCryptoKeyUsageVerify, |
| &public_key_256)); |
| |
| // Now verify using an algorithm whose inner hash is SHA-256, not SHA-1. The |
| // signature should not verify. |
| // NOTE: public_key was produced by generateKey, and so its associated |
| // algorithm has WebCryptoRsaKeyGenParams and not WebCryptoRsaSsaParams. Thus |
| // it has no inner hash to conflict with the input algorithm. |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha1, |
| private_key.algorithm().rsaHashedParams()->hash().id()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha256, |
| public_key_256.algorithm().rsaHashedParams()->hash().id()); |
| |
| bool is_match; |
| EXPECT_EQ(Status::Success(), |
| Verify(CreateAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5), |
| public_key_256, |
| CryptoData(signature), |
| CryptoData(data), |
| &is_match)); |
| EXPECT_FALSE(is_match); |
| } |
| |
| TEST_F(SharedCryptoTest, RsaSignVerifyKnownAnswer) { |
| if (!SupportsRsaKeyImport()) |
| return; |
| |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("pkcs1v15_sign.json", &tests)); |
| |
| // Import the key pair. |
| blink::WebCryptoAlgorithm import_algorithm = |
| CreateRsaHashedImportAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha1); |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| ASSERT_NO_FATAL_FAILURE( |
| ImportRsaKeyPair(HexStringToBytes(kPublicKeySpkiDerHex), |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), |
| import_algorithm, |
| false, |
| blink::WebCryptoKeyUsageVerify, |
| blink::WebCryptoKeyUsageSign, |
| &public_key, |
| &private_key)); |
| |
| blink::WebCryptoAlgorithm algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5); |
| |
| // Validate the signatures are computed and verified as expected. |
| std::vector<uint8_t> signature; |
| for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) { |
| SCOPED_TRACE(test_index); |
| |
| base::DictionaryValue* test; |
| ASSERT_TRUE(tests->GetDictionary(test_index, &test)); |
| |
| std::vector<uint8_t> test_message = |
| GetBytesFromHexString(test, "message_hex"); |
| std::vector<uint8_t> test_signature = |
| GetBytesFromHexString(test, "signature_hex"); |
| |
| signature.clear(); |
| ASSERT_EQ( |
| Status::Success(), |
| Sign(algorithm, private_key, CryptoData(test_message), &signature)); |
| EXPECT_BYTES_EQ(test_signature, signature); |
| |
| bool is_match = false; |
| ASSERT_EQ(Status::Success(), |
| Verify(algorithm, |
| public_key, |
| CryptoData(test_signature), |
| CryptoData(test_message), |
| &is_match)); |
| EXPECT_TRUE(is_match); |
| } |
| } |
| |
| TEST_F(SharedCryptoTest, AesKwKeyImport) { |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoAlgorithm algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw); |
| |
| // Import a 128-bit Key Encryption Key (KEK) |
| std::string key_raw_hex_in = "025a8cf3f08b4f6c5f33bbc76a471939"; |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(HexStringToBytes(key_raw_hex_in)), |
| algorithm, |
| true, |
| blink::WebCryptoKeyUsageWrapKey, |
| &key)); |
| std::vector<uint8_t> key_raw_out; |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &key_raw_out)); |
| EXPECT_BYTES_EQ_HEX(key_raw_hex_in, key_raw_out); |
| |
| // Import a 192-bit KEK |
| key_raw_hex_in = "c0192c6466b2370decbb62b2cfef4384544ffeb4d2fbc103"; |
| ASSERT_EQ(Status::ErrorAes192BitUnsupported(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(HexStringToBytes(key_raw_hex_in)), |
| algorithm, |
| true, |
| blink::WebCryptoKeyUsageWrapKey, |
| &key)); |
| |
| // Import a 256-bit Key Encryption Key (KEK) |
| key_raw_hex_in = |
| "e11fe66380d90fa9ebefb74e0478e78f95664d0c67ca20ce4a0b5842863ac46f"; |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(HexStringToBytes(key_raw_hex_in)), |
| algorithm, |
| true, |
| blink::WebCryptoKeyUsageWrapKey, |
| &key)); |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &key_raw_out)); |
| EXPECT_BYTES_EQ_HEX(key_raw_hex_in, key_raw_out); |
| |
| // Fail import of 0 length key |
| EXPECT_EQ(Status::ErrorImportAesKeyLength(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(HexStringToBytes("")), |
| algorithm, |
| true, |
| blink::WebCryptoKeyUsageWrapKey, |
| &key)); |
| |
| // Fail import of 124-bit KEK |
| key_raw_hex_in = "3e4566a2bdaa10cb68134fa66c15ddb"; |
| EXPECT_EQ(Status::ErrorImportAesKeyLength(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(HexStringToBytes(key_raw_hex_in)), |
| algorithm, |
| true, |
| blink::WebCryptoKeyUsageWrapKey, |
| &key)); |
| |
| // Fail import of 200-bit KEK |
| key_raw_hex_in = "0a1d88608a5ad9fec64f1ada269ebab4baa2feeb8d95638c0e"; |
| EXPECT_EQ(Status::ErrorImportAesKeyLength(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(HexStringToBytes(key_raw_hex_in)), |
| algorithm, |
| true, |
| blink::WebCryptoKeyUsageWrapKey, |
| &key)); |
| |
| // Fail import of 260-bit KEK |
| key_raw_hex_in = |
| "72d4e475ff34215416c9ad9c8281247a4d730c5f275ac23f376e73e3bce8d7d5a"; |
| EXPECT_EQ(Status::ErrorImportAesKeyLength(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(HexStringToBytes(key_raw_hex_in)), |
| algorithm, |
| true, |
| blink::WebCryptoKeyUsageWrapKey, |
| &key)); |
| } |
| |
| TEST_F(SharedCryptoTest, UnwrapFailures) { |
| // This test exercises the code path common to all unwrap operations. |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("aes_kw.json", &tests)); |
| base::DictionaryValue* test; |
| ASSERT_TRUE(tests->GetDictionary(0, &test)); |
| const std::vector<uint8_t> test_kek = GetBytesFromHexString(test, "kek"); |
| const std::vector<uint8_t> test_ciphertext = |
| GetBytesFromHexString(test, "ciphertext"); |
| |
| blink::WebCryptoKey unwrapped_key = blink::WebCryptoKey::createNull(); |
| |
| // Using a wrapping algorithm that does not match the wrapping key algorithm |
| // should fail. |
| blink::WebCryptoKey wrapping_key = ImportSecretKeyFromRaw( |
| test_kek, |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw), |
| blink::WebCryptoKeyUsageUnwrapKey); |
| EXPECT_EQ( |
| Status::ErrorUnexpected(), |
| UnwrapKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(test_ciphertext), |
| wrapping_key, |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &unwrapped_key)); |
| } |
| |
| TEST_F(SharedCryptoTest, AesKwRawSymkeyWrapUnwrapKnownAnswer) { |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("aes_kw.json", &tests)); |
| |
| for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) { |
| SCOPED_TRACE(test_index); |
| base::DictionaryValue* test; |
| ASSERT_TRUE(tests->GetDictionary(test_index, &test)); |
| const std::vector<uint8_t> test_kek = GetBytesFromHexString(test, "kek"); |
| const std::vector<uint8_t> test_key = GetBytesFromHexString(test, "key"); |
| const std::vector<uint8_t> test_ciphertext = |
| GetBytesFromHexString(test, "ciphertext"); |
| const blink::WebCryptoAlgorithm wrapping_algorithm = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw); |
| |
| // Import the wrapping key. |
| blink::WebCryptoKey wrapping_key = ImportSecretKeyFromRaw( |
| test_kek, |
| wrapping_algorithm, |
| blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey); |
| |
| // Import the key to be wrapped. |
| blink::WebCryptoKey key = ImportSecretKeyFromRaw( |
| test_key, |
| CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha1), |
| blink::WebCryptoKeyUsageSign); |
| |
| // Wrap the key and verify the ciphertext result against the known answer. |
| std::vector<uint8_t> wrapped_key; |
| ASSERT_EQ(Status::Success(), |
| WrapKey(blink::WebCryptoKeyFormatRaw, |
| key, |
| wrapping_key, |
| wrapping_algorithm, |
| &wrapped_key)); |
| EXPECT_BYTES_EQ(test_ciphertext, wrapped_key); |
| |
| // Unwrap the known ciphertext to get a new test_key. |
| blink::WebCryptoKey unwrapped_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ( |
| Status::Success(), |
| UnwrapKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(test_ciphertext), |
| wrapping_key, |
| wrapping_algorithm, |
| CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &unwrapped_key)); |
| EXPECT_FALSE(key.isNull()); |
| EXPECT_TRUE(key.handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypeSecret, key.type()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdHmac, key.algorithm().id()); |
| EXPECT_EQ(true, key.extractable()); |
| EXPECT_EQ(blink::WebCryptoKeyUsageSign, key.usages()); |
| |
| // Export the new key and compare its raw bytes with the original known key. |
| std::vector<uint8_t> raw_key; |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, unwrapped_key, &raw_key)); |
| EXPECT_BYTES_EQ(test_key, raw_key); |
| } |
| } |
| |
| // Unwrap a HMAC key using AES-KW, and then try doing a sign/verify with the |
| // unwrapped key |
| TEST_F(SharedCryptoTest, AesKwRawSymkeyUnwrapSignVerifyHmac) { |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("aes_kw.json", &tests)); |
| |
| base::DictionaryValue* test; |
| ASSERT_TRUE(tests->GetDictionary(0, &test)); |
| const std::vector<uint8_t> test_kek = GetBytesFromHexString(test, "kek"); |
| const std::vector<uint8_t> test_ciphertext = |
| GetBytesFromHexString(test, "ciphertext"); |
| const blink::WebCryptoAlgorithm wrapping_algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw); |
| |
| // Import the wrapping key. |
| blink::WebCryptoKey wrapping_key = ImportSecretKeyFromRaw( |
| test_kek, wrapping_algorithm, blink::WebCryptoKeyUsageUnwrapKey); |
| |
| // Unwrap the known ciphertext. |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ( |
| Status::Success(), |
| UnwrapKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(test_ciphertext), |
| wrapping_key, |
| wrapping_algorithm, |
| CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha1), |
| false, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify, |
| &key)); |
| |
| EXPECT_EQ(blink::WebCryptoKeyTypeSecret, key.type()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdHmac, key.algorithm().id()); |
| EXPECT_FALSE(key.extractable()); |
| EXPECT_EQ(blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify, |
| key.usages()); |
| |
| // Sign an empty message and ensure it is verified. |
| std::vector<uint8_t> test_message; |
| std::vector<uint8_t> signature; |
| |
| ASSERT_EQ(Status::Success(), |
| Sign(CreateAlgorithm(blink::WebCryptoAlgorithmIdHmac), |
| key, |
| CryptoData(test_message), |
| &signature)); |
| |
| EXPECT_GT(signature.size(), 0u); |
| |
| bool verify_result; |
| ASSERT_EQ(Status::Success(), |
| Verify(CreateAlgorithm(blink::WebCryptoAlgorithmIdHmac), |
| key, |
| CryptoData(signature), |
| CryptoData(test_message), |
| &verify_result)); |
| } |
| |
| TEST_F(SharedCryptoTest, AesKwRawSymkeyWrapUnwrapErrors) { |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("aes_kw.json", &tests)); |
| base::DictionaryValue* test; |
| // Use 256 bits of data with a 256-bit KEK |
| ASSERT_TRUE(tests->GetDictionary(3, &test)); |
| const std::vector<uint8_t> test_kek = GetBytesFromHexString(test, "kek"); |
| const std::vector<uint8_t> test_key = GetBytesFromHexString(test, "key"); |
| const std::vector<uint8_t> test_ciphertext = |
| GetBytesFromHexString(test, "ciphertext"); |
| const blink::WebCryptoAlgorithm wrapping_algorithm = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw); |
| const blink::WebCryptoAlgorithm key_algorithm = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc); |
| // Import the wrapping key. |
| blink::WebCryptoKey wrapping_key = ImportSecretKeyFromRaw( |
| test_kek, |
| wrapping_algorithm, |
| blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey); |
| // Import the key to be wrapped. |
| blink::WebCryptoKey key = ImportSecretKeyFromRaw( |
| test_key, |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| blink::WebCryptoKeyUsageEncrypt); |
| |
| // Unwrap with wrapped data too small must fail. |
| const std::vector<uint8_t> small_data(test_ciphertext.begin(), |
| test_ciphertext.begin() + 23); |
| blink::WebCryptoKey unwrapped_key = blink::WebCryptoKey::createNull(); |
| EXPECT_EQ(Status::ErrorDataTooSmall(), |
| UnwrapKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(small_data), |
| wrapping_key, |
| wrapping_algorithm, |
| key_algorithm, |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &unwrapped_key)); |
| |
| // Unwrap with wrapped data size not a multiple of 8 bytes must fail. |
| const std::vector<uint8_t> unaligned_data(test_ciphertext.begin(), |
| test_ciphertext.end() - 2); |
| EXPECT_EQ(Status::ErrorInvalidAesKwDataLength(), |
| UnwrapKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(unaligned_data), |
| wrapping_key, |
| wrapping_algorithm, |
| key_algorithm, |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &unwrapped_key)); |
| } |
| |
| TEST_F(SharedCryptoTest, AesKwRawSymkeyUnwrapCorruptData) { |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("aes_kw.json", &tests)); |
| base::DictionaryValue* test; |
| // Use 256 bits of data with a 256-bit KEK |
| ASSERT_TRUE(tests->GetDictionary(3, &test)); |
| const std::vector<uint8_t> test_kek = GetBytesFromHexString(test, "kek"); |
| const std::vector<uint8_t> test_key = GetBytesFromHexString(test, "key"); |
| const std::vector<uint8_t> test_ciphertext = |
| GetBytesFromHexString(test, "ciphertext"); |
| const blink::WebCryptoAlgorithm wrapping_algorithm = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw); |
| |
| // Import the wrapping key. |
| blink::WebCryptoKey wrapping_key = ImportSecretKeyFromRaw( |
| test_kek, |
| wrapping_algorithm, |
| blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey); |
| |
| // Unwrap of a corrupted version of the known ciphertext should fail, due to |
| // AES-KW's built-in integrity check. |
| blink::WebCryptoKey unwrapped_key = blink::WebCryptoKey::createNull(); |
| EXPECT_EQ( |
| Status::OperationError(), |
| UnwrapKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(Corrupted(test_ciphertext)), |
| wrapping_key, |
| wrapping_algorithm, |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &unwrapped_key)); |
| } |
| |
| TEST_F(SharedCryptoTest, AesKwJwkSymkeyUnwrapKnownData) { |
| // The following data lists a known HMAC SHA-256 key, then a JWK |
| // representation of this key which was encrypted ("wrapped") using AES-KW and |
| // the following wrapping key. |
| // For reference, the intermediate clear JWK is |
| // {"alg":"HS256","ext":true,"k":<b64urlKey>,"key_ops":["verify"],"kty":"oct"} |
| // (Not shown is space padding to ensure the cleartext meets the size |
| // requirements of the AES-KW algorithm.) |
| const std::vector<uint8_t> key_data = HexStringToBytes( |
| "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"); |
| const std::vector<uint8_t> wrapped_key_data = HexStringToBytes( |
| "14E6380B35FDC5B72E1994764B6CB7BFDD64E7832894356AAEE6C3768FC3D0F115E6B0" |
| "6729756225F999AA99FDF81FD6A359F1576D3D23DE6CB69C3937054EB497AC1E8C38D5" |
| "5E01B9783A20C8D930020932CF25926103002213D0FC37279888154FEBCEDF31832158" |
| "97938C5CFE5B10B4254D0C399F39D0"); |
| const std::vector<uint8_t> wrapping_key_data = |
| HexStringToBytes("000102030405060708090A0B0C0D0E0F"); |
| const blink::WebCryptoAlgorithm wrapping_algorithm = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw); |
| |
| // Import the wrapping key. |
| blink::WebCryptoKey wrapping_key = ImportSecretKeyFromRaw( |
| wrapping_key_data, wrapping_algorithm, blink::WebCryptoKeyUsageUnwrapKey); |
| |
| // Unwrap the known wrapped key data to produce a new key |
| blink::WebCryptoKey unwrapped_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ( |
| Status::Success(), |
| UnwrapKey(blink::WebCryptoKeyFormatJwk, |
| CryptoData(wrapped_key_data), |
| wrapping_key, |
| wrapping_algorithm, |
| CreateHmacImportAlgorithm(blink::WebCryptoAlgorithmIdSha256), |
| true, |
| blink::WebCryptoKeyUsageVerify, |
| &unwrapped_key)); |
| |
| // Validate the new key's attributes. |
| EXPECT_FALSE(unwrapped_key.isNull()); |
| EXPECT_TRUE(unwrapped_key.handle()); |
| EXPECT_EQ(blink::WebCryptoKeyTypeSecret, unwrapped_key.type()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdHmac, unwrapped_key.algorithm().id()); |
| EXPECT_EQ(blink::WebCryptoAlgorithmIdSha256, |
| unwrapped_key.algorithm().hmacParams()->hash().id()); |
| EXPECT_EQ(256u, unwrapped_key.algorithm().hmacParams()->lengthBits()); |
| EXPECT_EQ(true, unwrapped_key.extractable()); |
| EXPECT_EQ(blink::WebCryptoKeyUsageVerify, unwrapped_key.usages()); |
| |
| // Export the new key's raw data and compare to the known original. |
| std::vector<uint8_t> raw_key; |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, unwrapped_key, &raw_key)); |
| EXPECT_BYTES_EQ(key_data, raw_key); |
| } |
| |
| // TODO(eroman): |
| // * Test decryption when the tag length exceeds input size |
| // * Test decryption with empty input |
| // * Test decryption with tag length of 0. |
| TEST_F(SharedCryptoTest, AesGcmSampleSets) { |
| // Some Linux test runners may not have a new enough version of NSS. |
| if (!SupportsAesGcm()) { |
| LOG(WARNING) << "AES GCM not supported, skipping tests"; |
| return; |
| } |
| |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("aes_gcm.json", &tests)); |
| |
| // Note that WebCrypto appends the authentication tag to the ciphertext. |
| for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) { |
| SCOPED_TRACE(test_index); |
| base::DictionaryValue* test; |
| ASSERT_TRUE(tests->GetDictionary(test_index, &test)); |
| |
| const std::vector<uint8_t> test_key = GetBytesFromHexString(test, "key"); |
| const std::vector<uint8_t> test_iv = GetBytesFromHexString(test, "iv"); |
| const std::vector<uint8_t> test_additional_data = |
| GetBytesFromHexString(test, "additional_data"); |
| const std::vector<uint8_t> test_plain_text = |
| GetBytesFromHexString(test, "plain_text"); |
| const std::vector<uint8_t> test_authentication_tag = |
| GetBytesFromHexString(test, "authentication_tag"); |
| const unsigned int test_tag_size_bits = test_authentication_tag.size() * 8; |
| const std::vector<uint8_t> test_cipher_text = |
| GetBytesFromHexString(test, "cipher_text"); |
| |
| blink::WebCryptoKey key = ImportSecretKeyFromRaw( |
| test_key, |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesGcm), |
| blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt); |
| |
| // Verify exported raw key is identical to the imported data |
| std::vector<uint8_t> raw_key; |
| EXPECT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, key, &raw_key)); |
| |
| EXPECT_BYTES_EQ(test_key, raw_key); |
| |
| // Test encryption. |
| std::vector<uint8_t> cipher_text; |
| std::vector<uint8_t> authentication_tag; |
| EXPECT_EQ(Status::Success(), |
| AesGcmEncrypt(key, |
| test_iv, |
| test_additional_data, |
| test_tag_size_bits, |
| test_plain_text, |
| &cipher_text, |
| &authentication_tag)); |
| |
| EXPECT_BYTES_EQ(test_cipher_text, cipher_text); |
| EXPECT_BYTES_EQ(test_authentication_tag, authentication_tag); |
| |
| // Test decryption. |
| std::vector<uint8_t> plain_text; |
| EXPECT_EQ(Status::Success(), |
| AesGcmDecrypt(key, |
| test_iv, |
| test_additional_data, |
| test_tag_size_bits, |
| test_cipher_text, |
| test_authentication_tag, |
| &plain_text)); |
| EXPECT_BYTES_EQ(test_plain_text, plain_text); |
| |
| // Decryption should fail if any of the inputs are tampered with. |
| EXPECT_EQ(Status::OperationError(), |
| AesGcmDecrypt(key, |
| Corrupted(test_iv), |
| test_additional_data, |
| test_tag_size_bits, |
| test_cipher_text, |
| test_authentication_tag, |
| &plain_text)); |
| EXPECT_EQ(Status::OperationError(), |
| AesGcmDecrypt(key, |
| test_iv, |
| Corrupted(test_additional_data), |
| test_tag_size_bits, |
| test_cipher_text, |
| test_authentication_tag, |
| &plain_text)); |
| EXPECT_EQ(Status::OperationError(), |
| AesGcmDecrypt(key, |
| test_iv, |
| test_additional_data, |
| test_tag_size_bits, |
| Corrupted(test_cipher_text), |
| test_authentication_tag, |
| &plain_text)); |
| EXPECT_EQ(Status::OperationError(), |
| AesGcmDecrypt(key, |
| test_iv, |
| test_additional_data, |
| test_tag_size_bits, |
| test_cipher_text, |
| Corrupted(test_authentication_tag), |
| &plain_text)); |
| |
| // Try different incorrect tag lengths |
| uint8_t kAlternateTagLengths[] = {0, 8, 96, 120, 128, 160, 255}; |
| for (size_t tag_i = 0; tag_i < arraysize(kAlternateTagLengths); ++tag_i) { |
| unsigned int wrong_tag_size_bits = kAlternateTagLengths[tag_i]; |
| if (test_tag_size_bits == wrong_tag_size_bits) |
| continue; |
| EXPECT_NE(Status::Success(), |
| AesGcmDecrypt(key, |
| test_iv, |
| test_additional_data, |
| wrong_tag_size_bits, |
| test_cipher_text, |
| test_authentication_tag, |
| &plain_text)); |
| } |
| } |
| } |
| |
| // AES 192-bit is not allowed: http://crbug.com/381829 |
| TEST_F(SharedCryptoTest, ImportAesCbc192Raw) { |
| std::vector<uint8_t> key_raw(24, 0); |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| Status status = ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(key_raw), |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &key); |
| ASSERT_EQ(Status::ErrorAes192BitUnsupported(), status); |
| } |
| |
| // AES 192-bit is not allowed: http://crbug.com/381829 |
| TEST_F(SharedCryptoTest, ImportAesCbc192Jwk) { |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| |
| base::DictionaryValue dict; |
| dict.SetString("kty", "oct"); |
| dict.SetString("alg", "A192CBC"); |
| dict.SetString("k", "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh"); |
| |
| EXPECT_EQ( |
| Status::ErrorAes192BitUnsupported(), |
| ImportKeyJwkFromDict(dict, |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| false, |
| blink::WebCryptoKeyUsageEncrypt, |
| &key)); |
| } |
| |
| // AES 192-bit is not allowed: http://crbug.com/381829 |
| TEST_F(SharedCryptoTest, GenerateAesCbc192) { |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| Status status = GenerateSecretKey(CreateAesCbcKeyGenAlgorithm(192), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &key); |
| ASSERT_EQ(Status::ErrorAes192BitUnsupported(), status); |
| } |
| |
| // AES 192-bit is not allowed: http://crbug.com/381829 |
| TEST_F(SharedCryptoTest, UnwrapAesCbc192) { |
| std::vector<uint8_t> wrapping_key_data(16, 0); |
| std::vector<uint8_t> wrapped_key = HexStringToBytes( |
| "1A07ACAB6C906E50883173C29441DB1DE91D34F45C435B5F99C822867FB3956F"); |
| |
| blink::WebCryptoKey wrapping_key = |
| ImportSecretKeyFromRaw(wrapping_key_data, |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw), |
| blink::WebCryptoKeyUsageUnwrapKey); |
| |
| blink::WebCryptoKey unwrapped_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::ErrorAes192BitUnsupported(), |
| UnwrapKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(wrapped_key), |
| wrapping_key, |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw), |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &unwrapped_key)); |
| } |
| |
| class SharedCryptoRsaOaepTest : public ::testing::Test { |
| public: |
| scoped_ptr<base::DictionaryValue> CreatePublicKeyJwkDict() { |
| scoped_ptr<base::DictionaryValue> jwk(new base::DictionaryValue()); |
| jwk->SetString("kty", "RSA"); |
| jwk->SetString("n", |
| Base64EncodeUrlSafe(HexStringToBytes(kPublicKeyModulusHex))); |
| jwk->SetString( |
| "e", Base64EncodeUrlSafe(HexStringToBytes(kPublicKeyExponentHex))); |
| return jwk.Pass(); |
| } |
| }; |
| |
| // Import a PKCS#8 private key that uses RSAPrivateKey with the |
| // id-rsaEncryption OID. |
| TEST_F(SharedCryptoRsaOaepTest, ImportPkcs8WithRsaEncryption) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatPkcs8, |
| CryptoData(HexStringToBytes(kPrivateKeyPkcs8DerHex)), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageDecrypt, |
| &private_key)); |
| } |
| |
| TEST_F(SharedCryptoRsaOaepTest, ImportPublicJwkWithNoAlg) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| scoped_ptr<base::DictionaryValue> jwk(CreatePublicKeyJwkDict()); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(*jwk.get(), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &public_key)); |
| } |
| |
| TEST_F(SharedCryptoRsaOaepTest, ImportPublicJwkWithMatchingAlg) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| scoped_ptr<base::DictionaryValue> jwk(CreatePublicKeyJwkDict()); |
| jwk->SetString("alg", "RSA-OAEP"); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(*jwk.get(), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &public_key)); |
| } |
| |
| TEST_F(SharedCryptoRsaOaepTest, ImportPublicJwkWithMismatchedAlgFails) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| scoped_ptr<base::DictionaryValue> jwk(CreatePublicKeyJwkDict()); |
| jwk->SetString("alg", "RSA-OAEP-512"); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::ErrorJwkAlgorithmInconsistent(), |
| ImportKeyJwkFromDict(*jwk.get(), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &public_key)); |
| } |
| |
| TEST_F(SharedCryptoRsaOaepTest, ImportPublicJwkWithMismatchedTypeFails) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| scoped_ptr<base::DictionaryValue> jwk(CreatePublicKeyJwkDict()); |
| jwk->SetString("kty", "oct"); |
| jwk->SetString("alg", "RSA-OAEP"); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::ErrorJwkUnexpectedKty("RSA"), |
| ImportKeyJwkFromDict(*jwk.get(), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &public_key)); |
| } |
| |
| TEST_F(SharedCryptoRsaOaepTest, ExportPublicJwk) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| struct TestData { |
| blink::WebCryptoAlgorithmId hash_alg; |
| const char* expected_jwk_alg; |
| } kTestData[] = {{blink::WebCryptoAlgorithmIdSha1, "RSA-OAEP"}, |
| {blink::WebCryptoAlgorithmIdSha256, "RSA-OAEP-256"}, |
| {blink::WebCryptoAlgorithmIdSha384, "RSA-OAEP-384"}, |
| {blink::WebCryptoAlgorithmIdSha512, "RSA-OAEP-512"}}; |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestData); ++i) { |
| const TestData& test_data = kTestData[i]; |
| SCOPED_TRACE(test_data.expected_jwk_alg); |
| |
| scoped_ptr<base::DictionaryValue> jwk(CreatePublicKeyJwkDict()); |
| jwk->SetString("alg", test_data.expected_jwk_alg); |
| |
| // Import the key in a known-good format |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKeyJwkFromDict( |
| *jwk.get(), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, test_data.hash_alg), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &public_key)); |
| |
| // Now export the key as JWK and verify its contents |
| std::vector<uint8_t> jwk_data; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatJwk, public_key, &jwk_data)); |
| EXPECT_TRUE(VerifyPublicJwk(jwk_data, |
| test_data.expected_jwk_alg, |
| kPublicKeyModulusHex, |
| kPublicKeyExponentHex, |
| blink::WebCryptoKeyUsageEncrypt)); |
| } |
| } |
| |
| TEST_F(SharedCryptoRsaOaepTest, EncryptDecryptKnownAnswerTest) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| scoped_ptr<base::ListValue> tests; |
| ASSERT_TRUE(ReadJsonTestFileToList("rsa_oaep.json", &tests)); |
| |
| for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) { |
| SCOPED_TRACE(test_index); |
| |
| base::DictionaryValue* test = NULL; |
| ASSERT_TRUE(tests->GetDictionary(test_index, &test)); |
| |
| blink::WebCryptoAlgorithm digest_algorithm = |
| GetDigestAlgorithm(test, "hash"); |
| ASSERT_FALSE(digest_algorithm.isNull()); |
| std::vector<uint8_t> public_key_der = |
| GetBytesFromHexString(test, "public_key"); |
| std::vector<uint8_t> private_key_der = |
| GetBytesFromHexString(test, "private_key"); |
| std::vector<uint8_t> ciphertext = GetBytesFromHexString(test, "ciphertext"); |
| std::vector<uint8_t> plaintext = GetBytesFromHexString(test, "plaintext"); |
| std::vector<uint8_t> label = GetBytesFromHexString(test, "label"); |
| |
| blink::WebCryptoAlgorithm import_algorithm = CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, digest_algorithm.id()); |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| |
| ASSERT_NO_FATAL_FAILURE(ImportRsaKeyPair(public_key_der, |
| private_key_der, |
| import_algorithm, |
| false, |
| blink::WebCryptoKeyUsageEncrypt, |
| blink::WebCryptoKeyUsageDecrypt, |
| &public_key, |
| &private_key)); |
| |
| blink::WebCryptoAlgorithm op_algorithm = CreateRsaOaepAlgorithm(label); |
| std::vector<uint8_t> decrypted_data; |
| ASSERT_EQ(Status::Success(), |
| Decrypt(op_algorithm, |
| private_key, |
| CryptoData(ciphertext), |
| &decrypted_data)); |
| EXPECT_BYTES_EQ(plaintext, decrypted_data); |
| std::vector<uint8_t> encrypted_data; |
| ASSERT_EQ( |
| Status::Success(), |
| Encrypt( |
| op_algorithm, public_key, CryptoData(plaintext), &encrypted_data)); |
| std::vector<uint8_t> redecrypted_data; |
| ASSERT_EQ(Status::Success(), |
| Decrypt(op_algorithm, |
| private_key, |
| CryptoData(encrypted_data), |
| &redecrypted_data)); |
| EXPECT_BYTES_EQ(plaintext, redecrypted_data); |
| } |
| } |
| |
| TEST_F(SharedCryptoRsaOaepTest, EncryptWithLargeMessageFails) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| const blink::WebCryptoAlgorithmId kHash = blink::WebCryptoAlgorithmIdSha1; |
| const size_t kHashSize = 20; |
| |
| scoped_ptr<base::DictionaryValue> jwk(CreatePublicKeyJwkDict()); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(*jwk.get(), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, kHash), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &public_key)); |
| |
| // The maximum size of an encrypted message is: |
| // modulus length |
| // - 1 (leading octet) |
| // - hash size (maskedSeed) |
| // - hash size (lHash portion of maskedDB) |
| // - 1 (at least one octet for the padding string) |
| size_t kMaxMessageSize = (kModulusLengthBits / 8) - 2 - (2 * kHashSize); |
| |
| // The label has no influence on the maximum message size. For simplicity, |
| // use the empty string. |
| std::vector<uint8_t> label; |
| blink::WebCryptoAlgorithm op_algorithm = CreateRsaOaepAlgorithm(label); |
| |
| // Test that a message just before the boundary succeeds. |
| std::string large_message; |
| large_message.resize(kMaxMessageSize - 1, 'A'); |
| |
| std::vector<uint8_t> ciphertext; |
| ASSERT_EQ( |
| Status::Success(), |
| Encrypt( |
| op_algorithm, public_key, CryptoData(large_message), &ciphertext)); |
| |
| // Test that a message at the boundary succeeds. |
| large_message.resize(kMaxMessageSize, 'A'); |
| ciphertext.clear(); |
| |
| ASSERT_EQ( |
| Status::Success(), |
| Encrypt( |
| op_algorithm, public_key, CryptoData(large_message), &ciphertext)); |
| |
| // Test that a message greater than the largest size fails. |
| large_message.resize(kMaxMessageSize + 1, 'A'); |
| ciphertext.clear(); |
| |
| ASSERT_EQ( |
| Status::OperationError(), |
| Encrypt( |
| op_algorithm, public_key, CryptoData(large_message), &ciphertext)); |
| } |
| |
| // Ensures that if the selected hash algorithm for the RSA-OAEP message is too |
| // large, then it is rejected, independent of the actual message to be |
| // encrypted. |
| // For example, a 1024-bit RSA key is too small to accomodate a message that |
| // uses OAEP with SHA-512, since it requires 1040 bits to encode |
| // (2 * hash size + 2 padding bytes). |
| TEST_F(SharedCryptoRsaOaepTest, EncryptWithLargeDigestFails) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| const blink::WebCryptoAlgorithmId kHash = blink::WebCryptoAlgorithmIdSha512; |
| |
| scoped_ptr<base::DictionaryValue> jwk(CreatePublicKeyJwkDict()); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(*jwk.get(), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, kHash), |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &public_key)); |
| |
| // The label has no influence on the maximum message size. For simplicity, |
| // use the empty string. |
| std::vector<uint8_t> label; |
| blink::WebCryptoAlgorithm op_algorithm = CreateRsaOaepAlgorithm(label); |
| |
| std::string small_message("A"); |
| std::vector<uint8_t> ciphertext; |
| // This is an operation error, as the internal consistency checking of the |
| // algorithm parameters is up to the implementation. |
| ASSERT_EQ( |
| Status::OperationError(), |
| Encrypt( |
| op_algorithm, public_key, CryptoData(small_message), &ciphertext)); |
| } |
| |
| TEST_F(SharedCryptoRsaOaepTest, DecryptWithLargeMessageFails) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatPkcs8, |
| CryptoData(HexStringToBytes(kPrivateKeyPkcs8DerHex)), |
| CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, |
| blink::WebCryptoAlgorithmIdSha1), |
| true, |
| blink::WebCryptoKeyUsageDecrypt, |
| &private_key)); |
| |
| // The label has no influence on the maximum message size. For simplicity, |
| // use the empty string. |
| std::vector<uint8_t> label; |
| blink::WebCryptoAlgorithm op_algorithm = CreateRsaOaepAlgorithm(label); |
| |
| std::string large_dummy_message(kModulusLengthBits / 8, 'A'); |
| std::vector<uint8_t> plaintext; |
| |
| ASSERT_EQ(Status::OperationError(), |
| Decrypt(op_algorithm, |
| private_key, |
| CryptoData(large_dummy_message), |
| &plaintext)); |
| } |
| |
| TEST_F(SharedCryptoRsaOaepTest, WrapUnwrapRawKey) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| blink::WebCryptoAlgorithm import_algorithm = CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, blink::WebCryptoAlgorithmIdSha1); |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| |
| ASSERT_NO_FATAL_FAILURE(ImportRsaKeyPair( |
| HexStringToBytes(kPublicKeySpkiDerHex), |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), |
| import_algorithm, |
| false, |
| blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageWrapKey, |
| blink::WebCryptoKeyUsageDecrypt | blink::WebCryptoKeyUsageUnwrapKey, |
| &public_key, |
| &private_key)); |
| |
| std::vector<uint8_t> label; |
| blink::WebCryptoAlgorithm wrapping_algorithm = CreateRsaOaepAlgorithm(label); |
| |
| const std::string key_hex = "000102030405060708090A0B0C0D0E0F"; |
| const blink::WebCryptoAlgorithm key_algorithm = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc); |
| |
| blink::WebCryptoKey key = |
| ImportSecretKeyFromRaw(HexStringToBytes(key_hex), |
| key_algorithm, |
| blink::WebCryptoKeyUsageEncrypt); |
| ASSERT_FALSE(key.isNull()); |
| |
| std::vector<uint8_t> wrapped_key; |
| ASSERT_EQ(Status::Success(), |
| WrapKey(blink::WebCryptoKeyFormatRaw, |
| key, |
| public_key, |
| wrapping_algorithm, |
| &wrapped_key)); |
| |
| // Verify that |wrapped_key| can be decrypted and yields the key data. |
| // Because |private_key| supports both decrypt and unwrap, this is valid. |
| std::vector<uint8_t> decrypted_key; |
| ASSERT_EQ(Status::Success(), |
| Decrypt(wrapping_algorithm, |
| private_key, |
| CryptoData(wrapped_key), |
| &decrypted_key)); |
| EXPECT_BYTES_EQ_HEX(key_hex, decrypted_key); |
| |
| // Now attempt to unwrap the key, which should also decrypt the data. |
| blink::WebCryptoKey unwrapped_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| UnwrapKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(wrapped_key), |
| private_key, |
| wrapping_algorithm, |
| key_algorithm, |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &unwrapped_key)); |
| ASSERT_FALSE(unwrapped_key.isNull()); |
| |
| std::vector<uint8_t> raw_key; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, unwrapped_key, &raw_key)); |
| EXPECT_BYTES_EQ_HEX(key_hex, raw_key); |
| } |
| |
| TEST_F(SharedCryptoRsaOaepTest, WrapUnwrapJwkSymKey) { |
| if (!SupportsRsaOaep()) { |
| LOG(WARNING) << "RSA-OAEP support not present; skipping."; |
| return; |
| } |
| |
| // The public and private portions of a 2048-bit RSA key with the |
| // id-rsaEncryption OID |
| const char kPublicKey2048SpkiDerHex[] = |
| "30820122300d06092a864886f70d01010105000382010f003082010a0282010100c5d8ce" |
| "137a38168c8ab70229cfa5accc640567159750a312ce2e7d54b6e2fdd59b300c6a6c9764" |
| "f8de6f00519cdb90111453d273a967462786480621f9e7cee5b73d63358448e7183a3a68" |
| "e991186359f26aa88fbca5f53e673e502e4c5a2ba5068aeba60c9d0c44d872458d1b1e2f" |
| "7f339f986076d516e93dc750f0b7680b6f5f02bc0d5590495be04c4ae59d34ba17bc5d08" |
| "a93c75cfda2828f4a55b153af912038438276cb4a14f8116ca94db0ea9893652d02fc606" |
| "36f19975e3d79a4d8ea8bfed6f8e0a24b63d243b08ea70a086ad56dd6341d733711c89ca" |
| "749d4a80b3e6ecd2f8e53731eadeac2ea77788ee55d7b4b47c0f2523fbd61b557c16615d" |
| "5d0203010001"; |
| const char kPrivateKey2048Pkcs8DerHex[] = |
| "308204bd020100300d06092a864886f70d0101010500048204a7308204a3020100028201" |
| "0100c5d8ce137a38168c8ab70229cfa5accc640567159750a312ce2e7d54b6e2fdd59b30" |
| "0c6a6c9764f8de6f00519cdb90111453d273a967462786480621f9e7cee5b73d63358448" |
| "e7183a3a68e991186359f26aa88fbca5f53e673e502e4c5a2ba5068aeba60c9d0c44d872" |
| "458d1b1e2f7f339f986076d516e93dc750f0b7680b6f5f02bc0d5590495be04c4ae59d34" |
| "ba17bc5d08a93c75cfda2828f4a55b153af912038438276cb4a14f8116ca94db0ea98936" |
| "52d02fc60636f19975e3d79a4d8ea8bfed6f8e0a24b63d243b08ea70a086ad56dd6341d7" |
| "33711c89ca749d4a80b3e6ecd2f8e53731eadeac2ea77788ee55d7b4b47c0f2523fbd61b" |
| "557c16615d5d02030100010282010074b70feb41a0b0fcbc207670400556c9450042ede3" |
| "d4383fb1ce8f3558a6d4641d26dd4c333fa4db842d2b9cf9d2354d3e16ad027a9f682d8c" |
| "f4145a1ad97b9edcd8a41c402bd9d8db10f62f43df854cdccbbb2100834f083f53ed6d42" |
| "b1b729a59072b004a4e945fc027db15e9c121d1251464d320d4774d5732df6b3dbf751f4" |
| "9b19c9db201e19989c883bbaad5333db47f64f6f7a95b8d4936b10d945aa3f794cfaab62" |
| "e7d47686129358914f3b8085f03698a650ab5b8c7e45813f2b0515ec05b6e5195b6a7c2a" |
| "0d36969745f431ded4fd059f6aa361a4649541016d356297362b778e90f077d48815b339" |
| "ec6f43aba345df93e67fcb6c2cb5b4544e9be902818100e9c90abe5f9f32468c5b6d630c" |
| "54a4d7d75e29a72cf792f21e242aac78fd7995c42dfd4ae871d2619ff7096cb05baa78e3" |
| "23ecab338401a8059adf7a0d8be3b21edc9a9c82c5605634a2ec81ec053271721351868a" |
| "4c2e50c689d7cef94e31ff23658af5843366e2b289c5bf81d72756a7b93487dd8770d69c" |
| "1f4e089d6d89f302818100d8a58a727c4e209132afd9933b98c89aca862a01cc0be74133" |
| "bee517909e5c379e526895ac4af11780c1fe91194c777c9670b6423f0f5a32fd7691a622" |
| "113eef4bed2ef863363a335fd55b0e75088c582437237d7f3ed3f0a643950237bc6e6277" |
| "ccd0d0a1b4170aa1047aa7ffa7c8c54be10e8c7327ae2e0885663963817f6f02818100e5" |
| "aed9ba4d71b7502e6748a1ce247ecb7bd10c352d6d9256031cdf3c11a65e44b0b7ca2945" |
| "134671195af84c6b3bb3d10ebf65ae916f38bd5dbc59a0ad1c69b8beaf57cb3a8335f19b" |
| "c7117b576987b48331cd9fd3d1a293436b7bb5e1a35c6560de4b5688ea834367cb0997eb" |
| "b578f59ed4cb724c47dba94d3b484c1876dcd70281807f15bc7d2406007cac2b138a96af" |
| "2d1e00276b84da593132c253fcb73212732dfd25824c2a615bc3d9b7f2c8d2fa542d3562" |
| "b0c7738e61eeff580a6056239fb367ea9e5efe73d4f846033602e90c36a78db6fa8ea792" |
| "0769675ec58e237bd994d189c8045a96f5dd3a4f12547257ce224e3c9af830a4da3c0eab" |
| "9227a0035ae9028180067caea877e0b23090fc689322b71fbcce63d6596e66ab5fcdbaa0" |
| "0d49e93aba8effb4518c2da637f209028401a68f344865b4956b032c69acde51d29177ca" |
| "3db99fdbf5e74848ed4fa7bdfc2ebb60e2aaa5354770a763e1399ab7a2099762d525fea0" |
| "37f3e1972c45a477e66db95c9609bb27f862700ef93379930786cf751b"; |
| blink::WebCryptoAlgorithm import_algorithm = CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaOaep, blink::WebCryptoAlgorithmIdSha1); |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| |
| ASSERT_NO_FATAL_FAILURE(ImportRsaKeyPair( |
| HexStringToBytes(kPublicKey2048SpkiDerHex), |
| HexStringToBytes(kPrivateKey2048Pkcs8DerHex), |
| import_algorithm, |
| false, |
| blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageWrapKey, |
| blink::WebCryptoKeyUsageDecrypt | blink::WebCryptoKeyUsageUnwrapKey, |
| &public_key, |
| &private_key)); |
| |
| std::vector<uint8_t> label; |
| blink::WebCryptoAlgorithm wrapping_algorithm = CreateRsaOaepAlgorithm(label); |
| |
| const std::string key_hex = "000102030405060708090a0b0c0d0e0f"; |
| const blink::WebCryptoAlgorithm key_algorithm = |
| webcrypto::CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc); |
| |
| blink::WebCryptoKey key = |
| ImportSecretKeyFromRaw(HexStringToBytes(key_hex), |
| key_algorithm, |
| blink::WebCryptoKeyUsageEncrypt); |
| ASSERT_FALSE(key.isNull()); |
| |
| std::vector<uint8_t> wrapped_key; |
| ASSERT_EQ(Status::Success(), |
| WrapKey(blink::WebCryptoKeyFormatJwk, |
| key, |
| public_key, |
| wrapping_algorithm, |
| &wrapped_key)); |
| |
| // Verify that |wrapped_key| can be decrypted and yields a valid JWK object. |
| // Because |private_key| supports both decrypt and unwrap, this is valid. |
| std::vector<uint8_t> decrypted_jwk; |
| ASSERT_EQ(Status::Success(), |
| Decrypt(wrapping_algorithm, |
| private_key, |
| CryptoData(wrapped_key), |
| &decrypted_jwk)); |
| EXPECT_TRUE(VerifySecretJwk( |
| decrypted_jwk, "A128CBC", key_hex, blink::WebCryptoKeyUsageEncrypt)); |
| |
| // Now attempt to unwrap the key, which should also decrypt the data. |
| blink::WebCryptoKey unwrapped_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| UnwrapKey(blink::WebCryptoKeyFormatJwk, |
| CryptoData(wrapped_key), |
| private_key, |
| wrapping_algorithm, |
| key_algorithm, |
| true, |
| blink::WebCryptoKeyUsageEncrypt, |
| &unwrapped_key)); |
| ASSERT_FALSE(unwrapped_key.isNull()); |
| |
| std::vector<uint8_t> raw_key; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatRaw, unwrapped_key, &raw_key)); |
| EXPECT_BYTES_EQ_HEX(key_hex, raw_key); |
| } |
| |
| // Try importing an RSA-SSA public key with unsupported key usages using SPKI |
| // format. RSA-SSA public keys only support the 'verify' usage. |
| TEST_F(SharedCryptoTest, ImportRsaSsaPublicKeyBadUsage_SPKI) { |
| const blink::WebCryptoAlgorithm algorithm = |
| CreateRsaHashedImportAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256); |
| |
| blink::WebCryptoKeyUsageMask bad_usages[] = { |
| blink::WebCryptoKeyUsageSign, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify, |
| blink::WebCryptoKeyUsageEncrypt, |
| blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt, |
| }; |
| |
| for (size_t i = 0; i < arraysize(bad_usages); ++i) { |
| SCOPED_TRACE(i); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::ErrorCreateKeyBadUsages(), |
| ImportKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(HexStringToBytes(kPublicKeySpkiDerHex)), |
| algorithm, |
| false, |
| bad_usages[i], |
| &public_key)); |
| } |
| } |
| |
| // Try importing an RSA-SSA public key with unsupported key usages using JWK |
| // format. RSA-SSA public keys only support the 'verify' usage. |
| TEST_F(SharedCryptoTest, ImportRsaSsaPublicKeyBadUsage_JWK) { |
| const blink::WebCryptoAlgorithm algorithm = |
| CreateRsaHashedImportAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256); |
| |
| blink::WebCryptoKeyUsageMask bad_usages[] = { |
| blink::WebCryptoKeyUsageSign, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify, |
| blink::WebCryptoKeyUsageEncrypt, |
| blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt, |
| }; |
| |
| base::DictionaryValue dict; |
| RestoreJwkRsaDictionary(&dict); |
| dict.Remove("use", NULL); |
| dict.SetString("alg", "RS256"); |
| |
| for (size_t i = 0; i < arraysize(bad_usages); ++i) { |
| SCOPED_TRACE(i); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::ErrorCreateKeyBadUsages(), |
| ImportKeyJwkFromDict( |
| dict, algorithm, false, bad_usages[i], &public_key)); |
| } |
| } |
| |
| // Try importing an AES-CBC key with unsupported key usages using raw |
| // format. AES-CBC keys support the following usages: |
| // 'encrypt', 'decrypt', 'wrapKey', 'unwrapKey' |
| TEST_F(SharedCryptoTest, ImportAesCbcKeyBadUsage_Raw) { |
| const blink::WebCryptoAlgorithm algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc); |
| |
| blink::WebCryptoKeyUsageMask bad_usages[] = { |
| blink::WebCryptoKeyUsageSign, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageDecrypt, |
| blink::WebCryptoKeyUsageDeriveBits, |
| blink::WebCryptoKeyUsageUnwrapKey | blink::WebCryptoKeyUsageVerify, |
| }; |
| |
| std::vector<uint8_t> key_bytes(16); |
| |
| for (size_t i = 0; i < arraysize(bad_usages); ++i) { |
| SCOPED_TRACE(i); |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::ErrorCreateKeyBadUsages(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(key_bytes), |
| algorithm, |
| true, |
| bad_usages[i], |
| &key)); |
| } |
| } |
| |
| // Try importing an AES-KW key with unsupported key usages using raw |
| // format. AES-KW keys support the following usages: |
| // 'wrapKey', 'unwrapKey' |
| TEST_F(SharedCryptoTest, ImportAesKwKeyBadUsage_Raw) { |
| const blink::WebCryptoAlgorithm algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw); |
| |
| blink::WebCryptoKeyUsageMask bad_usages[] = { |
| blink::WebCryptoKeyUsageEncrypt, |
| blink::WebCryptoKeyUsageDecrypt, |
| blink::WebCryptoKeyUsageSign, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageUnwrapKey, |
| blink::WebCryptoKeyUsageDeriveBits, |
| blink::WebCryptoKeyUsageUnwrapKey | blink::WebCryptoKeyUsageVerify, |
| }; |
| |
| std::vector<uint8_t> key_bytes(16); |
| |
| for (size_t i = 0; i < arraysize(bad_usages); ++i) { |
| SCOPED_TRACE(i); |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::ErrorCreateKeyBadUsages(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(key_bytes), |
| algorithm, |
| true, |
| bad_usages[i], |
| &key)); |
| } |
| } |
| |
| // Try unwrapping an HMAC key with unsupported usages using JWK format and |
| // AES-KW. HMAC keys support the following usages: |
| // 'sign', 'verify' |
| TEST_F(SharedCryptoTest, UnwrapHmacKeyBadUsage_JWK) { |
| const blink::WebCryptoAlgorithm unwrap_algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw); |
| |
| blink::WebCryptoKeyUsageMask bad_usages[] = { |
| blink::WebCryptoKeyUsageEncrypt, |
| blink::WebCryptoKeyUsageDecrypt, |
| blink::WebCryptoKeyUsageWrapKey, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageWrapKey, |
| blink::WebCryptoKeyUsageVerify | blink::WebCryptoKeyUsageDeriveKey, |
| }; |
| |
| // Import the wrapping key. |
| blink::WebCryptoKey wrapping_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(std::vector<uint8_t>(16)), |
| unwrap_algorithm, |
| true, |
| blink::WebCryptoKeyUsageUnwrapKey, |
| &wrapping_key)); |
| |
| // The JWK plain text is: |
| // { "kty": "oct","alg": "HS256","k": "GADWrMRHwQfoNaXU5fZvTg=="} |
| const char* kWrappedJwk = |
| "0AA245F17064FFB2A7A094436A39BEBFC962C627303D1327EA750CE9F917688C2782A943" |
| "7AE7586547AC490E8AE7D5B02D63868D5C3BB57D36C4C8C5BF3962ACEC6F42E767E5706" |
| "4"; |
| |
| for (size_t i = 0; i < arraysize(bad_usages); ++i) { |
| SCOPED_TRACE(i); |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| |
| ASSERT_EQ(Status::ErrorCreateKeyBadUsages(), |
| UnwrapKey(blink::WebCryptoKeyFormatJwk, |
| CryptoData(HexStringToBytes(kWrappedJwk)), |
| wrapping_key, |
| unwrap_algorithm, |
| webcrypto::CreateHmacImportAlgorithm( |
| blink::WebCryptoAlgorithmIdSha256), |
| true, |
| bad_usages[i], |
| &key)); |
| } |
| } |
| |
| // Try unwrapping an RSA-SSA public key with unsupported usages using JWK format |
| // and AES-KW. RSA-SSA public keys support the following usages: |
| // 'verify' |
| TEST_F(SharedCryptoTest, UnwrapRsaSsaPublicKeyBadUsage_JWK) { |
| const blink::WebCryptoAlgorithm unwrap_algorithm = |
| CreateAlgorithm(blink::WebCryptoAlgorithmIdAesKw); |
| |
| blink::WebCryptoKeyUsageMask bad_usages[] = { |
| blink::WebCryptoKeyUsageEncrypt, |
| blink::WebCryptoKeyUsageSign, |
| blink::WebCryptoKeyUsageDecrypt, |
| blink::WebCryptoKeyUsageWrapKey, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageWrapKey, |
| }; |
| |
| // Import the wrapping key. |
| blink::WebCryptoKey wrapping_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::WebCryptoKeyFormatRaw, |
| CryptoData(std::vector<uint8_t>(16)), |
| unwrap_algorithm, |
| true, |
| blink::WebCryptoKeyUsageUnwrapKey, |
| &wrapping_key)); |
| |
| // The JWK plaintext is: |
| // { "kty": "RSA","alg": "RS256","n": "...","e": "AQAB"} |
| |
| const char* kWrappedJwk = |
| "CE8DAEF99E977EE58958B8C4494755C846E883B2ECA575C5366622839AF71AB30875F152" |
| "E8E33E15A7817A3A2874EB53EFE05C774D98BC936BA9BA29BEB8BB3F3C3CE2323CB3359D" |
| "E3F426605CF95CCF0E01E870ABD7E35F62E030B5FB6E520A5885514D1D850FB64B57806D" |
| "1ADA57C6E27DF345D8292D80F6B074F1BE51C4CF3D76ECC8886218551308681B44FAC60B" |
| "8CF6EA439BC63239103D0AE81ADB96F908680586C6169284E32EB7DD09D31103EBDAC0C2" |
| "40C72DCF0AEA454113CC47457B13305B25507CBEAB9BDC8D8E0F867F9167F9DCEF0D9F9B" |
| "30F2EE83CEDFD51136852C8A5939B768"; |
| |
| for (size_t i = 0; i < arraysize(bad_usages); ++i) { |
| SCOPED_TRACE(i); |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| |
| ASSERT_EQ(Status::ErrorCreateKeyBadUsages(), |
| UnwrapKey(blink::WebCryptoKeyFormatJwk, |
| CryptoData(HexStringToBytes(kWrappedJwk)), |
| wrapping_key, |
| unwrap_algorithm, |
| webcrypto::CreateRsaHashedImportAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256), |
| true, |
| bad_usages[i], |
| &key)); |
| } |
| } |
| |
| // Generate an AES-CBC key with invalid usages. AES-CBC supports: |
| // 'encrypt', 'decrypt', 'wrapKey', 'unwrapKey' |
| TEST_F(SharedCryptoTest, GenerateAesKeyBadUsages) { |
| blink::WebCryptoKeyUsageMask bad_usages[] = { |
| blink::WebCryptoKeyUsageSign, blink::WebCryptoKeyUsageVerify, |
| blink::WebCryptoKeyUsageDecrypt | blink::WebCryptoKeyUsageVerify, |
| }; |
| |
| for (size_t i = 0; i < arraysize(bad_usages); ++i) { |
| SCOPED_TRACE(i); |
| |
| blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); |
| |
| ASSERT_EQ(Status::ErrorCreateKeyBadUsages(), |
| GenerateSecretKey( |
| CreateAesCbcKeyGenAlgorithm(128), true, bad_usages[i], &key)); |
| } |
| } |
| |
| // Generate an RSA-SSA key pair with invalid usages. RSA-SSA supports: |
| // 'sign', 'verify' |
| TEST_F(SharedCryptoTest, GenerateRsaSsaBadUsages) { |
| blink::WebCryptoKeyUsageMask bad_usages[] = { |
| blink::WebCryptoKeyUsageDecrypt, |
| blink::WebCryptoKeyUsageVerify | blink::WebCryptoKeyUsageDecrypt, |
| blink::WebCryptoKeyUsageWrapKey, |
| }; |
| |
| const unsigned int modulus_length = 256; |
| const std::vector<uint8_t> public_exponent = HexStringToBytes("010001"); |
| |
| for (size_t i = 0; i < arraysize(bad_usages); ++i) { |
| SCOPED_TRACE(i); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| |
| ASSERT_EQ(Status::ErrorCreateKeyBadUsages(), |
| GenerateKeyPair(CreateRsaHashedKeyGenAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| modulus_length, |
| public_exponent), |
| true, |
| bad_usages[i], |
| &public_key, |
| &private_key)); |
| } |
| } |
| |
| // Generate an RSA-SSA key pair. The public and private keys should select the |
| // key usages which are applicable, and not have the exact same usages as was |
| // specified to GenerateKey |
| TEST_F(SharedCryptoTest, GenerateRsaSsaKeyPairIntersectUsages) { |
| const unsigned int modulus_length = 256; |
| const std::vector<uint8_t> public_exponent = HexStringToBytes("010001"); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| |
| ASSERT_EQ(Status::Success(), |
| GenerateKeyPair( |
| CreateRsaHashedKeyGenAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| modulus_length, |
| public_exponent), |
| true, |
| blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify, |
| &public_key, |
| &private_key)); |
| |
| EXPECT_EQ(blink::WebCryptoKeyUsageVerify, public_key.usages()); |
| EXPECT_EQ(blink::WebCryptoKeyUsageSign, private_key.usages()); |
| |
| // Try again but this time without the Verify usages. |
| ASSERT_EQ(Status::Success(), |
| GenerateKeyPair(CreateRsaHashedKeyGenAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| modulus_length, |
| public_exponent), |
| true, |
| blink::WebCryptoKeyUsageSign, |
| &public_key, |
| &private_key)); |
| |
| EXPECT_EQ(0, public_key.usages()); |
| EXPECT_EQ(blink::WebCryptoKeyUsageSign, private_key.usages()); |
| } |
| |
| // Generate an AES-CBC key and an RSA key pair. Use the AES-CBC key to wrap the |
| // key pair (using SPKI format for public key, PKCS8 format for private key). |
| // Then unwrap the wrapped key pair and verify that the key data is the same. |
| TEST_F(SharedCryptoTest, WrapUnwrapRoundtripSpkiPkcs8UsingAesCbc) { |
| if (!SupportsRsaKeyImport()) |
| return; |
| |
| // Generate the wrapping key. |
| blink::WebCryptoKey wrapping_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| GenerateSecretKey(CreateAesCbcKeyGenAlgorithm(128), |
| true, |
| blink::WebCryptoKeyUsageWrapKey | |
| blink::WebCryptoKeyUsageUnwrapKey, |
| &wrapping_key)); |
| |
| // Generate an RSA key pair to be wrapped. |
| const unsigned int modulus_length = 256; |
| const std::vector<uint8_t> public_exponent = HexStringToBytes("010001"); |
| |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::createNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::createNull(); |
| ASSERT_EQ(Status::Success(), |
| GenerateKeyPair(CreateRsaHashedKeyGenAlgorithm( |
| blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256, |
| modulus_length, |
| public_exponent), |
| true, |
| 0, |
| &public_key, |
| &private_key)); |
| |
| // Export key pair as SPKI + PKCS8 |
| std::vector<uint8_t> public_key_spki; |
| ASSERT_EQ( |
| Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatSpki, public_key, &public_key_spki)); |
| |
| std::vector<uint8_t> private_key_pkcs8; |
| ASSERT_EQ( |
| Status::Success(), |
| ExportKey( |
| blink::WebCryptoKeyFormatPkcs8, private_key, &private_key_pkcs8)); |
| |
| // Wrap the key pair. |
| blink::WebCryptoAlgorithm wrap_algorithm = |
| CreateAesCbcAlgorithm(std::vector<uint8_t>(16, 0)); |
| |
| std::vector<uint8_t> wrapped_public_key; |
| ASSERT_EQ(Status::Success(), |
| WrapKey(blink::WebCryptoKeyFormatSpki, |
| public_key, |
| wrapping_key, |
| wrap_algorithm, |
| &wrapped_public_key)); |
| |
| std::vector<uint8_t> wrapped_private_key; |
| ASSERT_EQ(Status::Success(), |
| WrapKey(blink::WebCryptoKeyFormatPkcs8, |
| private_key, |
| wrapping_key, |
| wrap_algorithm, |
| &wrapped_private_key)); |
| |
| // Unwrap the key pair. |
| blink::WebCryptoAlgorithm rsa_import_algorithm = |
| CreateRsaHashedImportAlgorithm(blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::WebCryptoAlgorithmIdSha256); |
| |
| blink::WebCryptoKey unwrapped_public_key = blink::WebCryptoKey::createNull(); |
| |
| ASSERT_EQ(Status::Success(), |
| UnwrapKey(blink::WebCryptoKeyFormatSpki, |
| CryptoData(wrapped_public_key), |
| wrapping_key, |
| wrap_algorithm, |
| rsa_import_algorithm, |
| true, |
| 0, |
| &unwrapped_public_key)); |
| |
| blink::WebCryptoKey unwrapped_private_key = blink::WebCryptoKey::createNull(); |
| |
| ASSERT_EQ(Status::Success(), |
| UnwrapKey(blink::WebCryptoKeyFormatPkcs8, |
| CryptoData(wrapped_private_key), |
| wrapping_key, |
| wrap_algorithm, |
| rsa_import_algorithm, |
| true, |
| 0, |
| &unwrapped_private_key)); |
| |
| // Export unwrapped key pair as SPKI + PKCS8 |
| std::vector<uint8_t> unwrapped_public_key_spki; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatSpki, |
| unwrapped_public_key, |
| &unwrapped_public_key_spki)); |
| |
| std::vector<uint8_t> unwrapped_private_key_pkcs8; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::WebCryptoKeyFormatPkcs8, |
| unwrapped_private_key, |
| &unwrapped_private_key_pkcs8)); |
| |
| EXPECT_EQ(public_key_spki, unwrapped_public_key_spki); |
| EXPECT_EQ(private_key_pkcs8, unwrapped_private_key_pkcs8); |
| |
| EXPECT_NE(public_key_spki, wrapped_public_key); |
| EXPECT_NE(private_key_pkcs8, wrapped_private_key); |
| } |
| |
| } // namespace webcrypto |
| |
| } // namespace content |