diff options
author | padolph@netflix.com <padolph@netflix.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-14 21:51:45 +0000 |
---|---|---|
committer | padolph@netflix.com <padolph@netflix.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-14 21:51:45 +0000 |
commit | 5d6e9f6b22c9dfcb86d0c07408d4a5311c36e256 (patch) | |
tree | 3d15d1bbc37a79649493244736563f0b893aa9bc /content/child/webcrypto | |
parent | 142b19f13984918a79842905ddfee6efed72e56d (diff) | |
download | chromium_src-5d6e9f6b22c9dfcb86d0c07408d4a5311c36e256.zip chromium_src-5d6e9f6b22c9dfcb86d0c07408d4a5311c36e256.tar.gz chromium_src-5d6e9f6b22c9dfcb86d0c07408d4a5311c36e256.tar.bz2 |
[webcrypto] JWK: Updated import(ext, key_ops) and added export of symmetric keys
See https://www.w3.org/Bugs/Public/show_bug.cgi?id=23796
BUG=245025
TEST=content_unittests --gtest_filter="SharedCryptoTest*"
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=256399
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=257078
Review URL: https://codereview.chromium.org/184043021
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@257217 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/child/webcrypto')
-rw-r--r-- | content/child/webcrypto/jwk.cc | 336 | ||||
-rw-r--r-- | content/child/webcrypto/shared_crypto.h | 3 | ||||
-rw-r--r-- | content/child/webcrypto/shared_crypto_unittest.cc | 442 | ||||
-rw-r--r-- | content/child/webcrypto/webcrypto_util.cc | 95 | ||||
-rw-r--r-- | content/child/webcrypto/webcrypto_util.h | 38 |
5 files changed, 804 insertions, 110 deletions
diff --git a/content/child/webcrypto/jwk.cc b/content/child/webcrypto/jwk.cc index 5131930..38ae18e 100644 --- a/content/child/webcrypto/jwk.cc +++ b/content/child/webcrypto/jwk.cc @@ -6,15 +6,14 @@ #include <functional> #include <map> #include "base/json/json_reader.h" +#include "base/json/json_writer.h" #include "base/lazy_instance.h" -#include "base/logging.h" -#include "base/memory/scoped_ptr.h" -#include "base/strings/string_piece.h" -#include "base/values.h" +#include "base/strings/stringprintf.h" #include "content/child/webcrypto/crypto_data.h" #include "content/child/webcrypto/platform_crypto.h" #include "content/child/webcrypto/shared_crypto.h" #include "content/child/webcrypto/webcrypto_util.h" +#include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h" namespace content { @@ -22,6 +21,16 @@ namespace webcrypto { namespace { +// Web Crypto equivalent usage mask for JWK 'use' = 'enc'. +// TODO(padolph): Add 'deriveBits' once supported by Blink. +const blink::WebCryptoKeyUsageMask kJwkEncUsage = + blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt | + blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey | + blink::WebCryptoKeyUsageDeriveKey; +// Web Crypto equivalent usage mask for JWK 'use' = 'sig'. +const blink::WebCryptoKeyUsageMask kJwkSigUsage = + blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify; + typedef blink::WebCryptoAlgorithm (*AlgorithmCreationFunc)(); class JwkAlgorithmInfo { @@ -38,7 +47,7 @@ class JwkAlgorithmInfo { unsigned int required_key_length_bits) : creation_func_(algorithm_creation_func), required_key_length_bytes_(required_key_length_bits / 8) { - DCHECK((required_key_length_bits % 8) == 0); + DCHECK_EQ(0u, required_key_length_bits % 8); } bool CreateImportAlgorithm(blink::WebCryptoAlgorithm* algorithm) const { @@ -70,6 +79,9 @@ class JwkAlgorithmRegistry { // http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-20 // says HMAC with SHA-2 should have a key size at least as large as the // hash output. + alg_to_info_["HS1"] = + JwkAlgorithmInfo(&BindAlgorithmId<CreateHmacImportAlgorithm, + blink::WebCryptoAlgorithmIdSha1>); alg_to_info_["HS256"] = JwkAlgorithmInfo(&BindAlgorithmId<CreateHmacImportAlgorithm, blink::WebCryptoAlgorithmIdSha256>); @@ -94,15 +106,21 @@ class JwkAlgorithmRegistry { alg_to_info_["RSA-OAEP"] = JwkAlgorithmInfo(&BindAlgorithmId<CreateRsaOaepImportAlgorithm, blink::WebCryptoAlgorithmIdSha1>); - // TODO(padolph): The Web Crypto spec does not enumerate AES-KW 128 yet - alg_to_info_["A128KW"] = - JwkAlgorithmInfo(&blink::WebCryptoAlgorithm::createNull, 128); - // TODO(padolph): The Web Crypto spec does not enumerate AES-KW 256 yet - alg_to_info_["A256KW"] = - JwkAlgorithmInfo(&blink::WebCryptoAlgorithm::createNull, 256); + alg_to_info_["A128KW"] = JwkAlgorithmInfo( + &BindAlgorithmId<CreateAlgorithm, blink::WebCryptoAlgorithmIdAesKw>, + 128); + alg_to_info_["A192KW"] = JwkAlgorithmInfo( + &BindAlgorithmId<CreateAlgorithm, blink::WebCryptoAlgorithmIdAesKw>, + 192); + alg_to_info_["A256KW"] = JwkAlgorithmInfo( + &BindAlgorithmId<CreateAlgorithm, blink::WebCryptoAlgorithmIdAesKw>, + 256); alg_to_info_["A128GCM"] = JwkAlgorithmInfo( &BindAlgorithmId<CreateAlgorithm, blink::WebCryptoAlgorithmIdAesGcm>, 128); + alg_to_info_["A192GCM"] = JwkAlgorithmInfo( + &BindAlgorithmId<CreateAlgorithm, blink::WebCryptoAlgorithmIdAesGcm>, + 192); alg_to_info_["A256GCM"] = JwkAlgorithmInfo( &BindAlgorithmId<CreateAlgorithm, blink::WebCryptoAlgorithmIdAesGcm>, 256); @@ -197,9 +215,29 @@ Status GetOptionalJwkString(base::DictionaryValue* dict, return Status::Success(); } +// Extracts the optional array property with key |path| from |dict| and saves +// the result to |*result| if it was found. If the property exists and is not an +// array, returns an error. Otherwise returns success, and sets +// |*property_exists| if it was found. Note that |*result| is owned by |dict|. +Status GetOptionalJwkList(base::DictionaryValue* dict, + const std::string& path, + base::ListValue** result, + bool* property_exists) { + *property_exists = false; + base::Value* value = NULL; + if (!dict->Get(path, &value)) + return Status::Success(); + + if (!value->GetAsList(result)) + return Status::ErrorJwkPropertyWrongType(path, "list"); + + *property_exists = true; + return Status::Success(); +} + // Extracts the required string property with key |path| from |dict| and saves -// the base64-decoded bytes to |*result|. If the property does not exist or is -// not a string, or could not be base64-decoded, returns an error. +// the base64url-decoded bytes to |*result|. If the property does not exist or +// is not a string, or could not be base64url-decoded, returns an error. Status GetJwkBytes(base::DictionaryValue* dict, const std::string& path, std::string* result) { @@ -234,6 +272,115 @@ Status GetOptionalJwkBool(base::DictionaryValue* dict, return Status::Success(); } +// Returns true if the set bits in b make up a subset of the set bits in a. +bool ContainsKeyUsages(blink::WebCryptoKeyUsageMask a, + blink::WebCryptoKeyUsageMask b) { + return (a & b) == b; +} + +// Writes a secret/symmetric key to a JWK dictionary. +void WriteSecretKey(const blink::WebArrayBuffer& raw_key, + base::DictionaryValue* jwk_dict) { + DCHECK(jwk_dict); + jwk_dict->SetString("kty", "oct"); + // For a secret/symmetric key, the only extra JWK field is 'k', containing the + // base64url encoding of the raw key. + DCHECK(!raw_key.isNull()); + DCHECK(raw_key.data()); + DCHECK(raw_key.byteLength()); + unsigned int key_length_bytes = raw_key.byteLength(); + const base::StringPiece key_str(static_cast<const char*>(raw_key.data()), + key_length_bytes); + jwk_dict->SetString("k", Base64EncodeUrlSafe(key_str)); +} + +// Writes a Web Crypto usage mask to a JWK dictionary. +void WriteKeyOps(blink::WebCryptoKeyUsageMask key_usages, + base::DictionaryValue* jwk_dict) { + jwk_dict->Set("key_ops", CreateJwkKeyOpsFromWebCryptoUsages(key_usages)); +} + +// Writes a Web Crypto extractable value to a JWK dictionary. +void WriteExt(bool extractable, base::DictionaryValue* jwk_dict) { + jwk_dict->SetBoolean("ext", extractable); +} + +// Writes a Web Crypto algorithm to a JWK dictionary. +Status WriteAlg(const blink::WebCryptoKeyAlgorithm& algorithm, + unsigned int raw_key_length_bytes, + base::DictionaryValue* jwk_dict) { + switch (algorithm.paramsType()) { + case blink::WebCryptoKeyAlgorithmParamsTypeAes: { + const char* aes_prefix = ""; + switch (raw_key_length_bytes) { + case 16: + aes_prefix = "A128"; + break; + case 24: + aes_prefix = "A192"; + break; + case 32: + aes_prefix = "A256"; + break; + default: + NOTREACHED(); // bad key length means algorithm was built improperly + return Status::ErrorUnexpected(); + } + const char* aes_suffix = ""; + switch (algorithm.id()) { + case blink::WebCryptoAlgorithmIdAesCbc: + aes_suffix = "CBC"; + break; + case blink::WebCryptoAlgorithmIdAesCtr: + aes_suffix = "CTR"; + break; + case blink::WebCryptoAlgorithmIdAesGcm: + aes_suffix = "GCM"; + break; + case blink::WebCryptoAlgorithmIdAesKw: + aes_suffix = "KW"; + break; + default: + return Status::ErrorUnsupported(); + } + jwk_dict->SetString("alg", + base::StringPrintf("%s%s", aes_prefix, aes_suffix)); + break; + } + case blink::WebCryptoKeyAlgorithmParamsTypeHmac: { + DCHECK(algorithm.hmacParams()); + switch (algorithm.hmacParams()->hash().id()) { + case blink::WebCryptoAlgorithmIdSha1: + jwk_dict->SetString("alg", "HS1"); + break; + case blink::WebCryptoAlgorithmIdSha224: + jwk_dict->SetString("alg", "HS224"); + break; + case blink::WebCryptoAlgorithmIdSha256: + jwk_dict->SetString("alg", "HS256"); + break; + case blink::WebCryptoAlgorithmIdSha384: + jwk_dict->SetString("alg", "HS384"); + break; + case blink::WebCryptoAlgorithmIdSha512: + jwk_dict->SetString("alg", "HS512"); + break; + default: + NOTREACHED(); + return Status::ErrorUnexpected(); + } + break; + } + case blink::WebCryptoKeyAlgorithmParamsTypeRsa: + case blink::WebCryptoKeyAlgorithmParamsTypeRsaHashed: + // TODO(padolph): Handle RSA key + return Status::ErrorUnsupported(); + default: + return Status::ErrorUnsupported(); + } + return Status::Success(); +} + } // namespace Status ImportKeyJwk(const CryptoData& key_data, @@ -241,22 +388,23 @@ Status ImportKeyJwk(const CryptoData& key_data, bool extractable, blink::WebCryptoKeyUsageMask usage_mask, blink::WebCryptoKey* key) { + // TODO(padolph): Generalize this comment to include export, and move to top + // of file. // The goal of this method is to extract key material and meta data from the // incoming JWK, combine them with the input parameters, and ultimately import // a Web Crypto Key. // // JSON Web Key Format (JWK) - // http://tools.ietf.org/html/draft-ietf-jose-json-web-key-16 - // TODO(padolph): Not all possible values are handled by this code right now + // http://tools.ietf.org/html/draft-ietf-jose-json-web-key-21 // // A JWK is a simple JSON dictionary with the following entries // - "kty" (Key Type) Parameter, REQUIRED // - <kty-specific parameters, see below>, REQUIRED // - "use" (Key Use) Parameter, OPTIONAL + // - "key_ops" (Key Operations) Parameter, OPTIONAL // - "alg" (Algorithm) Parameter, OPTIONAL - // - "extractable" (Key Exportability), OPTIONAL [NOTE: not yet part of JOSE, - // see https://www.w3.org/Bugs/Public/show_bug.cgi?id=23796] + // - "ext" (Key Exportability), OPTIONAL // (all other entries are ignored) // // OPTIONAL here means that this code does not require the entry to be present @@ -271,13 +419,13 @@ Status ImportKeyJwk(const CryptoData& key_data, // values are parsed out and combined with the method input parameters to // build a Web Crypto Key: // Web Crypto Key type <-- (deduced) - // Web Crypto Key extractable <-- JWK extractable + input extractable + // Web Crypto Key extractable <-- JWK ext + input extractable // Web Crypto Key algorithm <-- JWK alg + input algorithm - // Web Crypto Key keyUsage <-- JWK use + input usage_mask + // Web Crypto Key keyUsage <-- JWK use, key_ops + input usage_mask // Web Crypto Key keying material <-- kty-specific parameters // // Values for each JWK entry are case-sensitive and defined in - // http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-16. + // http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-18. // Note that not all values specified by JOSE are handled by this code. Only // handled values are listed. // - kty (Key Type) @@ -285,22 +433,50 @@ Status ImportKeyJwk(const CryptoData& key_data, // | "RSA" | RSA [RFC3447] | // | "oct" | Octet sequence (used to represent symmetric keys) | // +-------+--------------------------------------------------------------+ + // + // - key_ops (Key Use Details) + // The key_ops field is an array that contains one or more strings from + // the table below, and describes the operations for which this key may be + // used. + // +-------+--------------------------------------------------------------+ + // | "encrypt" | encrypt operations | + // | "decrypt" | decrypt operations | + // | "sign" | sign (MAC) operations | + // | "verify" | verify (MAC) operations | + // | "wrapKey" | key wrap | + // | "unwrapKey" | key unwrap | + // | "deriveKey" | key derivation | + // | "deriveBits" | key derivation TODO(padolph): not currently supported | + // +-------+--------------------------------------------------------------+ + // // - use (Key Use) + // The use field contains a single entry from the table below. // +-------+--------------------------------------------------------------+ - // | "enc" | encrypt and decrypt operations | - // | "sig" | sign and verify (MAC) operations | - // | "wrap"| key wrap and unwrap [not yet part of JOSE] | + // | "sig" | equivalent to key_ops of [sign, verify] | + // | "enc" | equivalent to key_ops of [encrypt, decrypt, wrapKey, | + // | | unwrapKey, deriveKey, deriveBits] | // +-------+--------------------------------------------------------------+ - // - extractable (Key Exportability) + // + // NOTE: If both "use" and "key_ops" JWK members are present, the usages + // specified by them MUST be consistent. In particular, the "use" value + // "sig" corresponds to "sign" and/or "verify". The "use" value "enc" + // corresponds to all other values defined above. If "key_ops" values + // corresponding to both "sig" and "enc" "use" values are present, the "use" + // member SHOULD NOT be present, and if present, its value MUST NOT be + // either "sig" or "enc". + // + // - ext (Key Exportability) // +-------+--------------------------------------------------------------+ // | true | Key may be exported from the trusted environment | // | false | Key cannot exit the trusted environment | // +-------+--------------------------------------------------------------+ + // // - alg (Algorithm) - // See http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-16 + // See http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-18 // +--------------+-------------------------------------------------------+ // | Digital Signature or MAC Algorithm | // +--------------+-------------------------------------------------------+ + // | "HS1" | HMAC using SHA-1 hash algorithm | // | "HS256" | HMAC using SHA-256 hash algorithm | // | "HS384" | HMAC using SHA-384 hash algorithm | // | "HS512" | HMAC using SHA-512 hash algorithm | @@ -316,15 +492,16 @@ Status ImportKeyJwk(const CryptoData& key_data, // | | specified by RFC3447 in Section A.2.1 | // | "A128KW" | Advanced Encryption Standard (AES) Key Wrap Algorithm | // | | [RFC3394] using 128 bit keys | + // | "A192KW" | AES Key Wrap Algorithm using 192 bit keys | // | "A256KW" | AES Key Wrap Algorithm using 256 bit keys | // | "A128GCM" | AES in Galois/Counter Mode (GCM) [NIST.800-38D] using | // | | 128 bit keys | + // | "A192GCM" | AES GCM using 192 bit keys | // | "A256GCM" | AES GCM using 256 bit keys | // | "A128CBC" | AES in Cipher Block Chaining Mode (CBC) with PKCS #5 | - // | | padding [NIST.800-38A] [not yet part of JOSE, see | - // | | https://www.w3.org/Bugs/Public/show_bug.cgi?id=23796 | - // | "A192CBC" | AES CBC using 192 bit keys [not yet part of JOSE] | - // | "A256CBC" | AES CBC using 256 bit keys [not yet part of JOSE] | + // | | padding [NIST.800-38A] | + // | "A192CBC" | AES CBC using 192 bit keys | + // | "A256CBC" | AES CBC using 256 bit keys | // +--------------+-------------------------------------------------------+ // // kty-specific parameters @@ -366,8 +543,8 @@ Status ImportKeyJwk(const CryptoData& key_data, // algorithm. // // extractable - // If the JWK extractable is true but the input parameter is false, make the - // Web Crypto Key non-extractable. Conversely, if the JWK extractable is + // If the JWK ext field is true but the input parameter is false, make the + // Web Crypto Key non-extractable. Conversely, if the JWK ext field is // false but the input parameter is true, it is an inconsistency. If both // are true or both are false, use that value. // @@ -396,18 +573,16 @@ Status ImportKeyJwk(const CryptoData& key_data, if (status.IsError()) return status; - // JWK "extractable" (optional) --> extractable parameter + // JWK "ext" (optional) --> extractable parameter { - bool jwk_extractable_value = false; - bool has_jwk_extractable; - status = GetOptionalJwkBool(dict_value, - "extractable", - &jwk_extractable_value, - &has_jwk_extractable); + bool jwk_ext_value = false; + bool has_jwk_ext; + status = + GetOptionalJwkBool(dict_value, "ext", &jwk_ext_value, &has_jwk_ext); if (status.IsError()) return status; - if (has_jwk_extractable && !jwk_extractable_value && extractable) - return Status::ErrorJwkExtractableInconsistent(); + if (has_jwk_ext && !jwk_ext_value && extractable) + return Status::ErrorJwkExtInconsistent(); } // JWK "alg" (optional) --> algorithm parameter @@ -460,6 +635,24 @@ Status ImportKeyJwk(const CryptoData& key_data, } DCHECK(!algorithm.isNull()); + // JWK "key_ops" (optional) --> usage_mask parameter + base::ListValue* jwk_key_ops_value = NULL; + bool has_jwk_key_ops; + status = GetOptionalJwkList( + dict_value, "key_ops", &jwk_key_ops_value, &has_jwk_key_ops); + if (status.IsError()) + return status; + blink::WebCryptoKeyUsageMask jwk_key_ops_mask = 0; + if (has_jwk_key_ops) { + status = + GetWebCryptoUsagesFromJwkKeyOps(jwk_key_ops_value, &jwk_key_ops_mask); + if (status.IsError()) + return status; + // The input usage_mask must be a subset of jwk_key_ops_mask. + if (!ContainsKeyUsages(jwk_key_ops_mask, usage_mask)) + return Status::ErrorJwkKeyopsInconsistent(); + } + // JWK "use" (optional) --> usage_mask parameter std::string jwk_use_value; bool has_jwk_use; @@ -467,29 +660,26 @@ Status ImportKeyJwk(const CryptoData& key_data, GetOptionalJwkString(dict_value, "use", &jwk_use_value, &has_jwk_use); if (status.IsError()) return status; + blink::WebCryptoKeyUsageMask jwk_use_mask = 0; if (has_jwk_use) { - blink::WebCryptoKeyUsageMask jwk_usage_mask = 0; - if (jwk_use_value == "enc") { - jwk_usage_mask = - blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt; - } else if (jwk_use_value == "sig") { - jwk_usage_mask = - blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify; - } else if (jwk_use_value == "wrap") { - jwk_usage_mask = - blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey; - } else { - return Status::ErrorJwkUnrecognizedUsage(); - } - if ((jwk_usage_mask & usage_mask) != usage_mask) { - // A usage_mask must be a subset of jwk_usage_mask. - return Status::ErrorJwkUsageInconsistent(); - } + if (jwk_use_value == "enc") + jwk_use_mask = kJwkEncUsage; + else if (jwk_use_value == "sig") + jwk_use_mask = kJwkSigUsage; + else + return Status::ErrorJwkUnrecognizedUse(); + // The input usage_mask must be a subset of jwk_use_mask. + if (!ContainsKeyUsages(jwk_use_mask, usage_mask)) + return Status::ErrorJwkUseInconsistent(); } + // If both 'key_ops' and 'use' are present, ensure they are consistent. + if (has_jwk_key_ops && has_jwk_use && + !ContainsKeyUsages(jwk_use_mask, jwk_key_ops_mask)) + return Status::ErrorJwkUseAndKeyopsInconsistent(); + // JWK keying material --> ImportKeyInternal() if (jwk_kty_value == "oct") { - std::string jwk_k_value; status = GetJwkBytes(dict_value, "k", &jwk_k_value); if (status.IsError()) @@ -513,7 +703,6 @@ Status ImportKeyJwk(const CryptoData& key_data, usage_mask, key); } else if (jwk_kty_value == "RSA") { - // An RSA public key must have an "n" (modulus) and an "e" (exponent) entry // in the JWK, while an RSA private key must have those, plus at least a "d" // (private exponent) entry. @@ -549,6 +738,35 @@ Status ImportKeyJwk(const CryptoData& key_data, return Status::Success(); } +Status ExportKeyJwk(const blink::WebCryptoKey& key, + blink::WebArrayBuffer* buffer) { + base::DictionaryValue jwk_dict; + Status status = Status::Error(); + blink::WebArrayBuffer exported_key; + + if (key.type() == blink::WebCryptoKeyTypeSecret) { + status = ExportKey(blink::WebCryptoKeyFormatRaw, key, &exported_key); + if (status.IsError()) + return status; + WriteSecretKey(exported_key, &jwk_dict); + } else { + // TODO(padolph): Handle asymmetric keys, at least the public key. + return Status::ErrorUnsupported(); + } + + WriteKeyOps(key.usages(), &jwk_dict); + WriteExt(key.extractable(), &jwk_dict); + status = WriteAlg(key.algorithm(), exported_key.byteLength(), &jwk_dict); + if (status.IsError()) + return status; + + std::string json; + base::JSONWriter::Write(&jwk_dict, &json); + *buffer = CreateArrayBuffer(reinterpret_cast<const uint8*>(json.data()), + json.size()); + return Status::Success(); +} + } // namespace webcrypto } // namespace content diff --git a/content/child/webcrypto/shared_crypto.h b/content/child/webcrypto/shared_crypto.h index c8fe3be..f668a8a 100644 --- a/content/child/webcrypto/shared_crypto.h +++ b/content/child/webcrypto/shared_crypto.h @@ -132,6 +132,9 @@ CONTENT_EXPORT Status blink::WebCryptoKey* key); CONTENT_EXPORT Status + ExportKeyJwk(const blink::WebCryptoKey& key, blink::WebArrayBuffer* buffer); + +CONTENT_EXPORT Status WrapKey(blink::WebCryptoKeyFormat format, const blink::WebCryptoKey& wrapping_key, const blink::WebCryptoKey& key_to_wrap, diff --git a/content/child/webcrypto/shared_crypto_unittest.cc b/content/child/webcrypto/shared_crypto_unittest.cc index 48db1a6..a71b52f 100644 --- a/content/child/webcrypto/shared_crypto_unittest.cc +++ b/content/child/webcrypto/shared_crypto_unittest.cc @@ -16,7 +16,7 @@ #include "base/memory/ref_counted.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" -#include "base/values.h" +#include "base/strings/string_util.h" #include "content/child/webcrypto/crypto_data.h" #include "content/child/webcrypto/webcrypto_util.h" #include "content/public/common/content_paths.h" @@ -51,6 +51,25 @@ namespace webcrypto { 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> 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_STATUS(Status::ErrorUnsupported(), status); + return status.IsSuccess(); +} + blink::WebCryptoAlgorithm CreateRsaKeyGenAlgorithm( blink::WebCryptoAlgorithmId algorithm_id, unsigned int modulus_length, @@ -93,6 +112,7 @@ blink::WebCryptoAlgorithm CreateAesGcmAlgorithm( const std::vector<uint8>& iv, const std::vector<uint8>& additional_data, unsigned int tag_length_bits) { + EXPECT_TRUE(SupportsAesGcm()); return blink::WebCryptoAlgorithm::adoptParamsAndCreate( blink::WebCryptoAlgorithmIdAesGcm, new blink::WebCryptoAesGcmParams(Uint8VectorStart(iv), @@ -279,7 +299,7 @@ void RestoreJwkOctDictionary(base::DictionaryValue* dict) { dict->SetString("kty", "oct"); dict->SetString("alg", "A128CBC"); dict->SetString("use", "enc"); - dict->SetBoolean("extractable", false); + dict->SetBoolean("ext", false); dict->SetString("k", "GADWrMRHwQfoNaXU5fZvTg=="); } @@ -290,7 +310,7 @@ void RestoreJwkRsaDictionary(base::DictionaryValue* dict) { dict->SetString("kty", "RSA"); dict->SetString("alg", "RSA1_5"); dict->SetString("use", "enc"); - dict->SetBoolean("extractable", false); + dict->SetBoolean("ext", false); dict->SetString( "n", "qLOyhK-OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx-CwgtaTpef87Wdc9GaFEncsDLxk" @@ -343,6 +363,7 @@ blink::WebCryptoAlgorithm CreateAesCbcKeyGenAlgorithm( blink::WebCryptoAlgorithm CreateAesGcmKeyGenAlgorithm( unsigned short key_length_bits) { + EXPECT_TRUE(SupportsAesGcm()); return CreateAesKeyGenAlgorithm(blink::WebCryptoAlgorithmIdAesGcm, key_length_bits); } @@ -450,24 +471,6 @@ void ImportRsaKeyPair(const std::vector<uint8>& spki_der, EXPECT_EQ(usage_mask, private_key->usages()); } -// TODO(eroman): For Linux builds using system NSS, AES-GCM support is a -// runtime dependency. Test it by trying to import a key. -bool SupportsAesGcm() { - std::vector<uint8> 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(Status::ErrorUnsupported().ToString(), status.ToString()); - return status.IsSuccess(); -} - Status AesGcmEncrypt(const blink::WebCryptoKey& key, const std::vector<uint8>& iv, const std::vector<uint8>& additional_data, @@ -475,6 +478,7 @@ Status AesGcmEncrypt(const blink::WebCryptoKey& key, const std::vector<uint8>& plain_text, std::vector<uint8>* cipher_text, std::vector<uint8>* authentication_tag) { + EXPECT_TRUE(SupportsAesGcm()); blink::WebCryptoAlgorithm algorithm = CreateAesGcmAlgorithm(iv, additional_data, tag_length_bits); @@ -506,6 +510,7 @@ Status AesGcmDecrypt(const blink::WebCryptoKey& key, const std::vector<uint8>& cipher_text, const std::vector<uint8>& authentication_tag, blink::WebArrayBuffer* plain_text) { + EXPECT_TRUE(SupportsAesGcm()); blink::WebCryptoAlgorithm algorithm = CreateAesGcmAlgorithm(iv, additional_data, tag_length_bits); @@ -533,8 +538,93 @@ Status ImportKeyJwkFromDict(const base::DictionaryValue& dict, key); } +// Parses an ArrayBuffer of JSON into a dictionary. +scoped_ptr<base::DictionaryValue> GetJwkDictionary( + const blink::WebArrayBuffer& json) { + base::StringPiece json_string(reinterpret_cast<const char*>(json.data()), + json.byteLength()); + 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 that the JSON in the input ArrayBuffer contains the provided +// expected values. Exact matches are required on the fields examined. +::testing::AssertionResult VerifySymmetricJwk( + const blink::WebArrayBuffer& 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"; + + // ---- kty + std::string value_string; + if (!dict->GetString("kty", &value_string)) + return ::testing::AssertionFailure() << "Missing 'kty'"; + if (value_string != "oct") + return ::testing::AssertionFailure() + << "Expected 'kty' to be 'oct' 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; + + // ---- k + 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"; + } + // ---- 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(); +} + } // 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; + } +} + TEST_F(SharedCryptoTest, StatusToString) { EXPECT_EQ("Success", Status::Success().ToString()); EXPECT_EQ("", Status::Error().ToString()); @@ -572,7 +662,7 @@ TEST_F(SharedCryptoTest, DigestSampleSets) { 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; @@ -814,8 +904,9 @@ TEST_F(SharedCryptoTest, MAYBE(GenerateKeyAes)) { const unsigned short kKeyLength[] = {128, 192, 256}; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kKeyLength); ++i) { algorithm.push_back(CreateAesCbcKeyGenAlgorithm(kKeyLength[i])); - algorithm.push_back(CreateAesGcmKeyGenAlgorithm(kKeyLength[i])); algorithm.push_back(CreateAesKwKeyGenAlgorithm(kKeyLength[i])); + if (SupportsAesGcm()) + algorithm.push_back(CreateAesGcmKeyGenAlgorithm(kKeyLength[i])); } blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); std::vector<blink::WebArrayBuffer> keys; @@ -850,10 +941,13 @@ TEST_F(SharedCryptoTest, MAYBE(GenerateKeyAesBadLength)) { CreateAesCbcKeyGenAlgorithm(kKeyLen[i]), true, 0, &key)); EXPECT_STATUS(Status::ErrorGenerateKeyLength(), GenerateSecretKey( - CreateAesGcmKeyGenAlgorithm(kKeyLen[i]), true, 0, &key)); - EXPECT_STATUS(Status::ErrorGenerateKeyLength(), - GenerateSecretKey( CreateAesKwKeyGenAlgorithm(kKeyLen[i]), true, 0, &key)); + if (SupportsAesGcm()) { + EXPECT_STATUS( + Status::ErrorGenerateKeyLength(), + GenerateSecretKey( + CreateAesGcmKeyGenAlgorithm(kKeyLen[i]), true, 0, &key)); + } } } @@ -918,8 +1012,139 @@ TEST_F(SharedCryptoTest, MAYBE(ImportSecretKeyNoAlgorithm)) { &key)); } -TEST_F(SharedCryptoTest, ImportJwkFailures) { +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_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}, + {"deriveKey", "HS256", hmac_algorithm, + blink::WebCryptoKeyUsageDeriveKey}}; + 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_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_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_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_STATUS(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_STATUS(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_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_STATUS_SUCCESS(ImportKeyJwkFromDict( + dict, + aes_cbc_algorithm, + false, + blink::WebCryptoKeyUsageDecrypt | blink::WebCryptoKeyUsageEncrypt | + blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey | + blink::WebCryptoKeyUsageDeriveKey, + &key)); + EXPECT_EQ(blink::WebCryptoKeyUsageDecrypt | blink::WebCryptoKeyUsageEncrypt | + blink::WebCryptoKeyUsageWrapKey | + blink::WebCryptoKeyUsageUnwrapKey | + blink::WebCryptoKeyUsageDeriveKey, + key.usages()); +} +TEST_F(SharedCryptoTest, ImportJwkFailures) { blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); blink::WebCryptoAlgorithm algorithm = CreateAlgorithm(blink::WebCryptoAlgorithmIdAesCbc); @@ -987,7 +1212,7 @@ TEST_F(SharedCryptoTest, ImportJwkFailures) { // Fail on invalid use. dict.SetString("use", "foo"); - EXPECT_STATUS(Status::ErrorJwkUnrecognizedUsage(), + EXPECT_STATUS(Status::ErrorJwkUnrecognizedUse(), ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); RestoreJwkOctDictionary(&dict); @@ -998,14 +1223,28 @@ TEST_F(SharedCryptoTest, ImportJwkFailures) { RestoreJwkOctDictionary(&dict); // Fail on invalid extractable (wrong type). - dict.SetInteger("extractable", 0); - EXPECT_STATUS(Status::ErrorJwkPropertyWrongType("extractable", "boolean"), + dict.SetInteger("ext", 0); + EXPECT_STATUS(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_STATUS(Status::ErrorJwkPropertyWrongType("key_ops", "list"), + ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); + RestoreJwkOctDictionary(&dict); + + // Fail on invalid key_ops (wrong element value). + 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_STATUS(Status::ErrorJwkUnrecognizedKeyop(), ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); RestoreJwkOctDictionary(&dict); } TEST_F(SharedCryptoTest, ImportJwkOctFailures) { - base::DictionaryValue dict; RestoreJwkOctDictionary(&dict); blink::WebCryptoAlgorithm algorithm = @@ -1057,7 +1296,6 @@ TEST_F(SharedCryptoTest, ImportJwkOctFailures) { } TEST_F(SharedCryptoTest, MAYBE(ImportJwkRsaFailures)) { - base::DictionaryValue dict; RestoreJwkRsaDictionary(&dict); blink::WebCryptoAlgorithm algorithm = @@ -1084,7 +1322,6 @@ TEST_F(SharedCryptoTest, MAYBE(ImportJwkRsaFailures)) { // 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_STATUS_ERROR( @@ -1144,7 +1381,7 @@ TEST_F(SharedCryptoTest, MAYBE(ImportJwkInputConsistency)) { dict.SetString("kty", "oct"); dict.SetString("alg", "HS256"); dict.SetString("use", "sig"); - dict.SetBoolean("extractable", false); + dict.SetBoolean("ext", false); dict.SetString("k", "l3nZEgZCeX8XRwJdWyK3rGB8qwjhdY8vOkbIvh4lxTuMao9Y_--hdg"); json_vec = MakeJsonVector(dict); EXPECT_STATUS_SUCCESS(ImportKeyJwk( @@ -1156,19 +1393,19 @@ TEST_F(SharedCryptoTest, MAYBE(ImportJwkInputConsistency)) { // 2. input=T, JWK=T ==> pass, result extractable is T // 3. input=F, JWK=T ==> pass, result extractable is F EXPECT_STATUS( - Status::ErrorJwkExtractableInconsistent(), + Status::ErrorJwkExtInconsistent(), ImportKeyJwk(CryptoData(json_vec), algorithm, true, usage_mask, &key)); EXPECT_STATUS_SUCCESS( ImportKeyJwk(CryptoData(json_vec), algorithm, false, usage_mask, &key)); EXPECT_FALSE(key.extractable()); - dict.SetBoolean("extractable", true); + dict.SetBoolean("ext", true); EXPECT_STATUS_SUCCESS( ImportKeyJwkFromDict(dict, algorithm, true, usage_mask, &key)); EXPECT_TRUE(key.extractable()); EXPECT_STATUS_SUCCESS( ImportKeyJwkFromDict(dict, algorithm, false, usage_mask, &key)); EXPECT_FALSE(key.extractable()); - dict.SetBoolean("extractable", true); // restore previous value + dict.SetBoolean("ext", true); // restore previous value // Fail: Input algorithm (AES-CBC) is inconsistent with JWK value // (HMAC SHA256). @@ -1210,7 +1447,7 @@ TEST_F(SharedCryptoTest, MAYBE(ImportJwkInputConsistency)) { // Fail: Input usage_mask (encrypt) is not a subset of the JWK value // (sign|verify) - EXPECT_STATUS(Status::ErrorJwkUsageInconsistent(), + EXPECT_STATUS(Status::ErrorJwkUseInconsistent(), ImportKeyJwk(CryptoData(json_vec), algorithm, extractable, @@ -1222,7 +1459,7 @@ TEST_F(SharedCryptoTest, MAYBE(ImportJwkInputConsistency)) { usage_mask = blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify; EXPECT_STATUS( - Status::ErrorJwkUsageInconsistent(), + Status::ErrorJwkUseInconsistent(), ImportKeyJwk( CryptoData(json_vec), algorithm, extractable, usage_mask, &key)); @@ -1230,10 +1467,11 @@ TEST_F(SharedCryptoTest, MAYBE(ImportJwkInputConsistency)) { // 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, MAYBE(ImportJwkHappy)) { - // This test verifies the happy path of JWK import, including the application // of the imported key material. @@ -1250,7 +1488,7 @@ TEST_F(SharedCryptoTest, MAYBE(ImportJwkHappy)) { dict.SetString("kty", "oct"); dict.SetString("alg", "HS256"); dict.SetString("use", "sig"); - dict.SetBoolean("extractable", false); + dict.SetBoolean("ext", false); dict.SetString("k", "l3nZEgZCeX8XRwJdWyK3rGB8qwjhdY8vOkbIvh4lxTuMao9Y_--hdg"); ASSERT_STATUS_SUCCESS( @@ -1280,6 +1518,128 @@ TEST_F(SharedCryptoTest, MAYBE(ImportJwkHappy)) { // TODO(padolph): Import an RSA public key JWK and use it } +TEST_F(SharedCryptoTest, MAYBE(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_192 = + "ed91f916dc034eba68a0f9e7f34ddd48b98bd2848109e243"; + 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 192 + {key_hex_192, aes_cbc_alg, blink::WebCryptoKeyUsageEncrypt, "A192CBC"}, + // 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-CGM 192 + {key_hex_192, aes_gcm_alg, blink::WebCryptoKeyUsageEncrypt, "A192GCM"}, + // 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 192 + {key_hex_192, aes_kw_alg, + blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey, + "A192KW"}, + // 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(); + blink::WebArrayBuffer 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_STATUS_SUCCESS(ExportKeyJwk(key, &json)); + EXPECT_TRUE( + VerifySymmetricJwk(json, test.jwk_alg, test.key_hex, test.usage)); + + // Import the JWK-formatted key. + ASSERT_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. + blink::WebArrayBuffer key_raw_out; + ASSERT_STATUS_SUCCESS( + ExportKey(blink::WebCryptoKeyFormatRaw, key, &key_raw_out)); + ExpectArrayBufferMatchesHex(test.key_hex, key_raw_out); + } +} + TEST_F(SharedCryptoTest, MAYBE(ImportExportSpki)) { // Passing case: Import a valid RSA key in SPKI format. blink::WebCryptoKey key = blink::WebCryptoKey::createNull(); diff --git a/content/child/webcrypto/webcrypto_util.cc b/content/child/webcrypto/webcrypto_util.cc index 1fa4658..537e551 100644 --- a/content/child/webcrypto/webcrypto_util.cc +++ b/content/child/webcrypto/webcrypto_util.cc @@ -6,6 +6,7 @@ #include "base/base64.h" #include "base/logging.h" +#include "base/strings/stringprintf.h" #include "third_party/WebKit/public/platform/WebCryptoAlgorithm.h" #include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h" #include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h" @@ -47,10 +48,10 @@ Status Status::ErrorJwkBase64Decode(const std::string& property) { "\" could not be base64 decoded"); } -Status Status::ErrorJwkExtractableInconsistent() { +Status Status::ErrorJwkExtInconsistent() { return Status( - "The \"extractable\" property of the JWK dictionary is " - "inconsistent what that specified by the Web Crypto call"); + "The \"ext\" property of the JWK dictionary is inconsistent what that " + "specified by the Web Crypto call"); } Status Status::ErrorJwkUnrecognizedAlgorithm() { @@ -69,17 +70,34 @@ Status Status::ErrorJwkAlgorithmMissing() { "and one wasn't specified by the Web Crypto call"); } -Status Status::ErrorJwkUnrecognizedUsage() { +Status Status::ErrorJwkUnrecognizedUse() { return Status("The JWK \"use\" property could not be parsed"); } -Status Status::ErrorJwkUsageInconsistent() { +Status Status::ErrorJwkUnrecognizedKeyop() { + return Status("The JWK \"key_ops\" property could not be parsed"); +} + +Status Status::ErrorJwkUseInconsistent() { return Status( "The JWK \"use\" property was inconsistent with that specified " "by the Web Crypto call. The JWK usage must be a superset of " "those requested"); } +Status Status::ErrorJwkKeyopsInconsistent() { + return Status( + "The JWK \"key_ops\" property was inconsistent with that " + "specified by the Web Crypto call. The JWK usage must be a " + "superset of those requested"); +} + +Status Status::ErrorJwkUseAndKeyopsInconsistent() { + return Status( + "The JWK \"use\" and \"key_ops\" properties were both found " + "but are inconsistent with each other."); +} + Status Status::ErrorJwkRsaPrivateKeyUnsupported() { return Status( "JWK RSA key contained \"d\" property: Private key import is " @@ -219,6 +237,73 @@ bool Base64DecodeUrlSafe(const std::string& input, std::string* output) { return base::Base64Decode(base64EncodedText, output); } +// Returns an unpadded 'base64url' encoding of the input data, using the +// inverse of the process above. +std::string Base64EncodeUrlSafe(const base::StringPiece& input) { + std::string output; + base::Base64Encode(input, &output); + std::replace(output.begin(), output.end(), '+', '-'); + std::replace(output.begin(), output.end(), '/', '_'); + output.erase(std::remove(output.begin(), output.end(), '='), output.end()); + return output; +} + +struct JwkToWebCryptoUsage { + const char* const jwk_key_op; + const blink::WebCryptoKeyUsage webcrypto_usage; +}; + +const JwkToWebCryptoUsage kJwkWebCryptoUsageMap[] = { + {"encrypt", blink::WebCryptoKeyUsageEncrypt}, + {"decrypt", blink::WebCryptoKeyUsageDecrypt}, + {"deriveKey", blink::WebCryptoKeyUsageDeriveKey}, + // TODO(padolph): Add 'deriveBits' once supported by Blink. + {"sign", blink::WebCryptoKeyUsageSign}, + {"unwrapKey", blink::WebCryptoKeyUsageUnwrapKey}, + {"verify", blink::WebCryptoKeyUsageVerify}, + {"wrapKey", blink::WebCryptoKeyUsageWrapKey}}; + +// Modifies the input usage_mask by according to the key_op value. +bool JwkKeyOpToWebCryptoUsage(const std::string& key_op, + blink::WebCryptoKeyUsageMask* usage_mask) { + for (size_t i = 0; i < arraysize(kJwkWebCryptoUsageMap); ++i) { + if (kJwkWebCryptoUsageMap[i].jwk_key_op == key_op) { + *usage_mask |= kJwkWebCryptoUsageMap[i].webcrypto_usage; + return true; + } + } + return false; +} + +// Composes a Web Crypto usage mask from an array of JWK key_ops values. +Status GetWebCryptoUsagesFromJwkKeyOps( + const base::ListValue* jwk_key_ops_value, + blink::WebCryptoKeyUsageMask* usage_mask) { + *usage_mask = 0; + for (size_t i = 0; i < jwk_key_ops_value->GetSize(); ++i) { + std::string key_op; + if (!jwk_key_ops_value->GetString(i, &key_op)) { + return Status::ErrorJwkPropertyWrongType( + base::StringPrintf("key_ops[%d]", static_cast<int>(i)), "string"); + } + if (!JwkKeyOpToWebCryptoUsage(key_op, usage_mask)) + return Status::ErrorJwkUnrecognizedKeyop(); + } + return Status::Success(); +} + +// Composes a JWK key_ops List from a Web Crypto usage mask. +// Note: Caller must assume ownership of returned instance. +base::ListValue* CreateJwkKeyOpsFromWebCryptoUsages( + blink::WebCryptoKeyUsageMask usage_mask) { + base::ListValue* jwk_key_ops = new base::ListValue(); + for (size_t i = 0; i < arraysize(kJwkWebCryptoUsageMap); ++i) { + if (usage_mask & kJwkWebCryptoUsageMap[i].webcrypto_usage) + jwk_key_ops->AppendString(kJwkWebCryptoUsageMap[i].jwk_key_op); + } + return jwk_key_ops; +} + bool IsHashAlgorithm(blink::WebCryptoAlgorithmId alg_id) { return alg_id == blink::WebCryptoAlgorithmIdSha1 || alg_id == blink::WebCryptoAlgorithmIdSha256 || diff --git a/content/child/webcrypto/webcrypto_util.h b/content/child/webcrypto/webcrypto_util.h index 3c82da9..9d23c73 100644 --- a/content/child/webcrypto/webcrypto_util.h +++ b/content/child/webcrypto/webcrypto_util.h @@ -8,6 +8,8 @@ #include <string> #include <vector> #include "base/basictypes.h" +#include "base/strings/string_piece.h" +#include "base/values.h" #include "content/common/content_export.h" #include "third_party/WebKit/public/platform/WebArrayBuffer.h" #include "third_party/WebKit/public/platform/WebCrypto.h" // TODO(eroman): delete @@ -73,9 +75,9 @@ class CONTENT_EXPORT Status { // base64 decoded. static Status ErrorJwkBase64Decode(const std::string& property); - // The "extractable" parameter was specified but was + // The "ext" parameter was specified but was // incompatible with the value requested by the Web Crypto call. - static Status ErrorJwkExtractableInconsistent(); + static Status ErrorJwkExtInconsistent(); // The "alg" parameter could not be converted to an equivalent // WebCryptoAlgorithm. Either it was malformed or unrecognized. @@ -91,11 +93,23 @@ class CONTENT_EXPORT Status { // The "use" parameter was specified, however it couldn't be converted to an // equivalent Web Crypto usage. - static Status ErrorJwkUnrecognizedUsage(); + static Status ErrorJwkUnrecognizedUse(); + + // The "key_ops" parameter was specified, however one of the values in the + // array couldn't be converted to an equivalent Web Crypto usage. + static Status ErrorJwkUnrecognizedKeyop(); // The "use" parameter was specified, however it is incompatible with that // specified by the Web Crypto import operation. - static Status ErrorJwkUsageInconsistent(); + static Status ErrorJwkUseInconsistent(); + + // The "key_ops" parameter was specified, however it is incompatible with that + // specified by the Web Crypto import operation. + static Status ErrorJwkKeyopsInconsistent(); + + // Both the "key_ops" and the "use" parameters were specified, however they + // are incompatible with each other. + static Status ErrorJwkUseAndKeyopsInconsistent(); // TODO(eroman): Private key import through JWK is not yet supported. static Status ErrorJwkRsaPrivateKeyUnsupported(); @@ -218,7 +232,21 @@ blink::WebArrayBuffer CreateArrayBuffer(const uint8* data, // This function decodes unpadded 'base64url' encoded data, as described in // RFC4648 (http://www.ietf.org/rfc/rfc4648.txt) Section 5. // In Web Crypto, this type of encoding is only used inside JWK. -bool Base64DecodeUrlSafe(const std::string& input, std::string* output); +CONTENT_EXPORT bool Base64DecodeUrlSafe(const std::string& input, + std::string* output); + +// Returns an unpadded 'base64url' encoding of the input data, the opposite of +// Base64DecodeUrlSafe() above. +std::string Base64EncodeUrlSafe(const base::StringPiece& input); + +// Composes a Web Crypto usage mask from an array of JWK key_ops values. +CONTENT_EXPORT Status GetWebCryptoUsagesFromJwkKeyOps( + const base::ListValue* jwk_key_ops_value, + blink::WebCryptoKeyUsageMask* jwk_key_ops_mask); + +// Composes a JWK key_ops array from a Web Crypto usage mask. +base::ListValue* CreateJwkKeyOpsFromWebCryptoUsages( + blink::WebCryptoKeyUsageMask usage_mask); CONTENT_EXPORT bool IsHashAlgorithm(blink::WebCryptoAlgorithmId alg_id); |