summaryrefslogtreecommitdiffstats
path: root/content/child/webcrypto
diff options
context:
space:
mode:
authorpadolph@netflix.com <padolph@netflix.com@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-14 21:51:45 +0000
committerpadolph@netflix.com <padolph@netflix.com@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-14 21:51:45 +0000
commit5d6e9f6b22c9dfcb86d0c07408d4a5311c36e256 (patch)
tree3d15d1bbc37a79649493244736563f0b893aa9bc /content/child/webcrypto
parent142b19f13984918a79842905ddfee6efed72e56d (diff)
downloadchromium_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.cc336
-rw-r--r--content/child/webcrypto/shared_crypto.h3
-rw-r--r--content/child/webcrypto/shared_crypto_unittest.cc442
-rw-r--r--content/child/webcrypto/webcrypto_util.cc95
-rw-r--r--content/child/webcrypto/webcrypto_util.h38
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);