diff options
Diffstat (limited to 'chromium/content/child/webcrypto/jwk.cc')
-rw-r--r-- | chromium/content/child/webcrypto/jwk.cc | 1018 |
1 files changed, 1018 insertions, 0 deletions
diff --git a/chromium/content/child/webcrypto/jwk.cc b/chromium/content/child/webcrypto/jwk.cc new file mode 100644 index 00000000000..a3d65da2051 --- /dev/null +++ b/chromium/content/child/webcrypto/jwk.cc @@ -0,0 +1,1018 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "jwk.h" + +#include <algorithm> +#include <functional> +#include <map> + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/lazy_instance.h" +#include "base/strings/string_piece.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/status.h" +#include "content/child/webcrypto/webcrypto_util.h" +#include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h" + +// JSON Web Key Format (JWK) +// 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 +// - "ext" (Key Exportability), OPTIONAL +// (all other entries are ignored) +// +// OPTIONAL here means that this code does not require the entry to be present +// in the incoming JWK, because the method input parameters contain similar +// information. If the optional JWK entry is present, it will be validated +// against the corresponding input parameter for consistency and combined with +// it according to rules defined below. +// +// Input 'key_data' contains the JWK. To build a Web Crypto Key, the JWK +// 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 ext + input extractable +// Web Crypto Key algorithm <-- JWK alg + input algorithm +// 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-18. +// Note that not all values specified by JOSE are handled by this code. Only +// handled values are listed. +// - kty (Key Type) +// +-------+--------------------------------------------------------------+ +// | "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 | +// +-------+--------------------------------------------------------------+ +// +// - use (Key Use) +// The use field contains a single entry from the table below. +// +-------+--------------------------------------------------------------+ +// | "sig" | equivalent to key_ops of [sign, verify] | +// | "enc" | equivalent to key_ops of [encrypt, decrypt, wrapKey, | +// | | unwrapKey, deriveKey, deriveBits] | +// +-------+--------------------------------------------------------------+ +// +// 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-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 | +// | "RS1" | RSASSA using SHA-1 hash algorithm +// | "RS256" | RSASSA using SHA-256 hash algorithm | +// | "RS384" | RSASSA using SHA-384 hash algorithm | +// | "RS512" | RSASSA using SHA-512 hash algorithm | +// +--------------+-------------------------------------------------------| +// | Key Management Algorithm | +// +--------------+-------------------------------------------------------+ +// | "RSA-OAEP" | RSAES using Optimal Asymmetric Encryption Padding | +// | | (OAEP) [RFC3447], with the default parameters | +// | | 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] | +// | "A192CBC" | AES CBC using 192 bit keys | +// | "A256CBC" | AES CBC using 256 bit keys | +// +--------------+-------------------------------------------------------+ +// +// kty-specific parameters +// The value of kty determines the type and content of the keying material +// carried in the JWK to be imported. +// // - kty == "oct" (symmetric or other raw key) +// +-------+--------------------------------------------------------------+ +// | "k" | Contains the value of the symmetric (or other single-valued) | +// | | key. It is represented as the base64url encoding of the | +// | | octet sequence containing the key value. | +// +-------+--------------------------------------------------------------+ +// - kty == "RSA" (RSA public key) +// +-------+--------------------------------------------------------------+ +// | "n" | Contains the modulus value for the RSA public key. It is | +// | | represented as the base64url encoding of the value's | +// | | unsigned big endian representation as an octet sequence. | +// +-------+--------------------------------------------------------------+ +// | "e" | Contains the exponent value for the RSA public key. It is | +// | | represented as the base64url encoding of the value's | +// | | unsigned big endian representation as an octet sequence. | +// +-------+--------------------------------------------------------------+ +// - If key == "RSA" and the "d" parameter is present then it is a private key. +// All the parameters above for public keys apply, as well as the following. +// (Note that except for "d", all of these are optional): +// +-------+--------------------------------------------------------------+ +// | "d" | Contains the private exponent value for the RSA private key. | +// | | It is represented as the base64url encoding of the value's | +// | | unsigned big endian representation as an octet sequence. | +// +-------+--------------------------------------------------------------+ +// | "p" | Contains the first prime factor value for the RSA private | +// | | key. It is represented as the base64url encoding of the | +// | | value's | +// | | unsigned big endian representation as an octet sequence. | +// +-------+--------------------------------------------------------------+ +// | "q" | Contains the second prime factor value for the RSA private | +// | | key. It is represented as the base64url encoding of the | +// | | value's unsigned big endian representation as an octet | +// | | sequence. | +// +-------+--------------------------------------------------------------+ +// | "dp" | Contains the first factor CRT exponent value for the RSA | +// | | private key. It is represented as the base64url encoding of | +// | | the value's unsigned big endian representation as an octet | +// | | sequence. | +// +-------+--------------------------------------------------------------+ +// | "dq" | Contains the second factor CRT exponent value for the RSA | +// | | private key. It is represented as the base64url encoding of | +// | | the value's unsigned big endian representation as an octet | +// | | sequence. | +// +-------+--------------------------------------------------------------+ +// | "dq" | Contains the first CRT coefficient value for the RSA private | +// | | key. It is represented as the base64url encoding of the | +// | | value's unsigned big endian representation as an octet | +// | | sequence. | +// +-------+--------------------------------------------------------------+ +// +// Consistency and conflict resolution +// The 'algorithm', 'extractable', and 'usage_mask' input parameters +// may be different than the corresponding values inside the JWK. The Web +// Crypto spec says that if a JWK value is present but is inconsistent with +// the input value, it is an error and the operation must fail. If no +// inconsistency is found then the input parameters are used. +// +// algorithm +// If the JWK algorithm is provided, it must match the web crypto input +// algorithm (both the algorithm ID and inner hash if applicable). +// +// extractable +// 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. +// +// usage_mask +// The input usage_mask must be a strict subset of the interpreted JWK use +// value, else it is judged inconsistent. In all cases the input usage_mask +// is used as the final usage_mask. +// + +namespace content { + +namespace webcrypto { + +namespace { + +// Creates an RSASSA-PKCS1-v1_5 algorithm. It is an error to call this with a +// hash_id that is not a SHA*. +blink::WebCryptoAlgorithm CreateRsaSsaImportAlgorithm( + blink::WebCryptoAlgorithmId hash_id) { + return CreateRsaHashedImportAlgorithm( + blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5, hash_id); +} + +// Creates an RSA-OAEP algorithm. It is an error to call this with a hash_id +// that is not a SHA*. +blink::WebCryptoAlgorithm CreateRsaOaepImportAlgorithm( + blink::WebCryptoAlgorithmId hash_id) { + return CreateRsaHashedImportAlgorithm(blink::WebCryptoAlgorithmIdRsaOaep, + hash_id); +} + +// Web Crypto equivalent usage mask for JWK 'use' = 'enc'. +const blink::WebCryptoKeyUsageMask kJwkEncUsage = + blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt | + blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey | + blink::WebCryptoKeyUsageDeriveKey | blink::WebCryptoKeyUsageDeriveBits; +// Web Crypto equivalent usage mask for JWK 'use' = 'sig'. +const blink::WebCryptoKeyUsageMask kJwkSigUsage = + blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify; + +typedef blink::WebCryptoAlgorithm (*AlgorithmCreationFunc)(); + +class JwkAlgorithmInfo { + public: + JwkAlgorithmInfo() + : creation_func_(NULL), + required_key_length_bytes_(NO_KEY_SIZE_REQUIREMENT) {} + + explicit JwkAlgorithmInfo(AlgorithmCreationFunc algorithm_creation_func) + : creation_func_(algorithm_creation_func), + required_key_length_bytes_(NO_KEY_SIZE_REQUIREMENT) {} + + JwkAlgorithmInfo(AlgorithmCreationFunc algorithm_creation_func, + unsigned int required_key_length_bits) + : creation_func_(algorithm_creation_func), + required_key_length_bytes_(required_key_length_bits / 8) { + DCHECK_EQ(0u, required_key_length_bits % 8); + } + + bool CreateImportAlgorithm(blink::WebCryptoAlgorithm* algorithm) const { + *algorithm = creation_func_(); + return !algorithm->isNull(); + } + + bool IsInvalidKeyByteLength(size_t byte_length) const { + if (required_key_length_bytes_ == NO_KEY_SIZE_REQUIREMENT) + return false; + return required_key_length_bytes_ != byte_length; + } + + private: + enum { NO_KEY_SIZE_REQUIREMENT = UINT_MAX }; + + AlgorithmCreationFunc creation_func_; + + // The expected key size for the algorithm or NO_KEY_SIZE_REQUIREMENT. + unsigned int required_key_length_bytes_; +}; + +typedef std::map<std::string, JwkAlgorithmInfo> JwkAlgorithmInfoMap; + +class JwkAlgorithmRegistry { + public: + JwkAlgorithmRegistry() { + // TODO(eroman): + // 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>); + alg_to_info_["HS384"] = + JwkAlgorithmInfo(&BindAlgorithmId<CreateHmacImportAlgorithm, + blink::WebCryptoAlgorithmIdSha384>); + alg_to_info_["HS512"] = + JwkAlgorithmInfo(&BindAlgorithmId<CreateHmacImportAlgorithm, + blink::WebCryptoAlgorithmIdSha512>); + alg_to_info_["RS1"] = + JwkAlgorithmInfo(&BindAlgorithmId<CreateRsaSsaImportAlgorithm, + blink::WebCryptoAlgorithmIdSha1>); + alg_to_info_["RS256"] = + JwkAlgorithmInfo(&BindAlgorithmId<CreateRsaSsaImportAlgorithm, + blink::WebCryptoAlgorithmIdSha256>); + alg_to_info_["RS384"] = + JwkAlgorithmInfo(&BindAlgorithmId<CreateRsaSsaImportAlgorithm, + blink::WebCryptoAlgorithmIdSha384>); + alg_to_info_["RS512"] = + JwkAlgorithmInfo(&BindAlgorithmId<CreateRsaSsaImportAlgorithm, + blink::WebCryptoAlgorithmIdSha512>); + alg_to_info_["RSA-OAEP"] = + JwkAlgorithmInfo(&BindAlgorithmId<CreateRsaOaepImportAlgorithm, + blink::WebCryptoAlgorithmIdSha1>); + alg_to_info_["RSA-OAEP-256"] = + JwkAlgorithmInfo(&BindAlgorithmId<CreateRsaOaepImportAlgorithm, + blink::WebCryptoAlgorithmIdSha256>); + alg_to_info_["RSA-OAEP-384"] = + JwkAlgorithmInfo(&BindAlgorithmId<CreateRsaOaepImportAlgorithm, + blink::WebCryptoAlgorithmIdSha384>); + alg_to_info_["RSA-OAEP-512"] = + JwkAlgorithmInfo(&BindAlgorithmId<CreateRsaOaepImportAlgorithm, + blink::WebCryptoAlgorithmIdSha512>); + 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); + alg_to_info_["A128CBC"] = JwkAlgorithmInfo( + &BindAlgorithmId<CreateAlgorithm, blink::WebCryptoAlgorithmIdAesCbc>, + 128); + alg_to_info_["A192CBC"] = JwkAlgorithmInfo( + &BindAlgorithmId<CreateAlgorithm, blink::WebCryptoAlgorithmIdAesCbc>, + 192); + alg_to_info_["A256CBC"] = JwkAlgorithmInfo( + &BindAlgorithmId<CreateAlgorithm, blink::WebCryptoAlgorithmIdAesCbc>, + 256); + } + + // Returns NULL if the algorithm name was not registered. + const JwkAlgorithmInfo* GetAlgorithmInfo(const std::string& jwk_alg) const { + const JwkAlgorithmInfoMap::const_iterator pos = alg_to_info_.find(jwk_alg); + if (pos == alg_to_info_.end()) + return NULL; + return &pos->second; + } + + private: + // Binds a WebCryptoAlgorithmId value to a compatible factory function. + typedef blink::WebCryptoAlgorithm (*FuncWithWebCryptoAlgIdArg)( + blink::WebCryptoAlgorithmId); + template <FuncWithWebCryptoAlgIdArg func, + blink::WebCryptoAlgorithmId algorithm_id> + static blink::WebCryptoAlgorithm BindAlgorithmId() { + return func(algorithm_id); + } + + JwkAlgorithmInfoMap alg_to_info_; +}; + +base::LazyInstance<JwkAlgorithmRegistry> jwk_alg_registry = + LAZY_INSTANCE_INITIALIZER; + +bool ImportAlgorithmsConsistent(const blink::WebCryptoAlgorithm& alg1, + const blink::WebCryptoAlgorithm& alg2) { + DCHECK(!alg1.isNull()); + DCHECK(!alg2.isNull()); + if (alg1.id() != alg2.id()) + return false; + if (alg1.paramsType() != alg2.paramsType()) + return false; + switch (alg1.paramsType()) { + case blink::WebCryptoAlgorithmParamsTypeNone: + return true; + case blink::WebCryptoAlgorithmParamsTypeRsaHashedImportParams: + return ImportAlgorithmsConsistent(alg1.rsaHashedImportParams()->hash(), + alg2.rsaHashedImportParams()->hash()); + case blink::WebCryptoAlgorithmParamsTypeHmacImportParams: + return ImportAlgorithmsConsistent(alg1.hmacImportParams()->hash(), + alg2.hmacImportParams()->hash()); + default: + return false; + } +} + +// Extracts the required string property with key |path| from |dict| and saves +// the result to |*result|. If the property does not exist or is not a string, +// returns an error. +Status GetJwkString(base::DictionaryValue* dict, + const std::string& path, + std::string* result) { + base::Value* value = NULL; + if (!dict->Get(path, &value)) + return Status::ErrorJwkPropertyMissing(path); + if (!value->GetAsString(result)) + return Status::ErrorJwkPropertyWrongType(path, "string"); + return Status::Success(); +} + +// Extracts the optional string property with key |path| from |dict| and saves +// the result to |*result| if it was found. If the property exists and is not a +// string, returns an error. Otherwise returns success, and sets +// |*property_exists| if it was found. +Status GetOptionalJwkString(base::DictionaryValue* dict, + const std::string& path, + std::string* result, + bool* property_exists) { + *property_exists = false; + base::Value* value = NULL; + if (!dict->Get(path, &value)) + return Status::Success(); + + if (!value->GetAsString(result)) + return Status::ErrorJwkPropertyWrongType(path, "string"); + + *property_exists = true; + 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 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) { + std::string base64_string; + Status status = GetJwkString(dict, path, &base64_string); + if (status.IsError()) + return status; + + if (!Base64DecodeUrlSafe(base64_string, result)) + return Status::ErrorJwkBase64Decode(path); + + return Status::Success(); +} + +// Extracts the optional string property with key |path| from |dict| and saves +// the base64url-decoded bytes to |*result|. If the property exist and is not a +// string, or could not be base64url-decoded, returns an error. In the case +// where the property does not exist, |result| is guaranteed to be empty. +Status GetOptionalJwkBytes(base::DictionaryValue* dict, + const std::string& path, + std::string* result, + bool* property_exists) { + std::string base64_string; + Status status = + GetOptionalJwkString(dict, path, &base64_string, property_exists); + if (status.IsError()) + return status; + + if (!*property_exists) { + result->clear(); + return Status::Success(); + } + + if (!Base64DecodeUrlSafe(base64_string, result)) + return Status::ErrorJwkBase64Decode(path); + + return Status::Success(); +} + +// Extracts the optional boolean property with key |path| from |dict| and saves +// the result to |*result| if it was found. If the property exists and is not a +// boolean, returns an error. Otherwise returns success, and sets +// |*property_exists| if it was found. +Status GetOptionalJwkBool(base::DictionaryValue* dict, + const std::string& path, + bool* result, + bool* property_exists) { + *property_exists = false; + base::Value* value = NULL; + if (!dict->Get(path, &value)) + return Status::Success(); + + if (!value->GetAsBoolean(result)) + return Status::ErrorJwkPropertyWrongType(path, "boolean"); + + *property_exists = true; + return Status::Success(); +} + +// Writes a secret/symmetric key to a JWK dictionary. +void WriteSecretKey(const std::vector<uint8>& 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. + const base::StringPiece key_str( + reinterpret_cast<const char*>(Uint8VectorStart(raw_key)), raw_key.size()); + jwk_dict->SetString("k", Base64EncodeUrlSafe(key_str)); +} + +// Writes an RSA public key to a JWK dictionary +void WriteRsaPublicKey(const std::vector<uint8>& modulus, + const std::vector<uint8>& public_exponent, + base::DictionaryValue* jwk_dict) { + DCHECK(jwk_dict); + DCHECK(modulus.size()); + DCHECK(public_exponent.size()); + jwk_dict->SetString("kty", "RSA"); + jwk_dict->SetString("n", Base64EncodeUrlSafe(modulus)); + jwk_dict->SetString("e", Base64EncodeUrlSafe(public_exponent)); +} + +// Writes an RSA private key to a JWK dictionary +Status ExportRsaPrivateKeyJwk(const blink::WebCryptoKey& key, + base::DictionaryValue* jwk_dict) { + platform::PrivateKey* private_key; + Status status = ToPlatformPrivateKey(key, &private_key); + if (status.IsError()) + return status; + + // TODO(eroman): Copying the key properties to temporary vectors is + // inefficient. Once there aren't two implementations of platform_crypto this + // and other code will be easier to streamline. + std::vector<uint8> modulus; + std::vector<uint8> public_exponent; + std::vector<uint8> private_exponent; + std::vector<uint8> prime1; + std::vector<uint8> prime2; + std::vector<uint8> exponent1; + std::vector<uint8> exponent2; + std::vector<uint8> coefficient; + + status = platform::ExportRsaPrivateKey(private_key, + &modulus, + &public_exponent, + &private_exponent, + &prime1, + &prime2, + &exponent1, + &exponent2, + &coefficient); + if (status.IsError()) + return status; + + jwk_dict->SetString("kty", "RSA"); + jwk_dict->SetString("n", Base64EncodeUrlSafe(modulus)); + jwk_dict->SetString("e", Base64EncodeUrlSafe(public_exponent)); + jwk_dict->SetString("d", Base64EncodeUrlSafe(private_exponent)); + // Although these are "optional" in the JWA, WebCrypto spec requires them to + // be emitted. + jwk_dict->SetString("p", Base64EncodeUrlSafe(prime1)); + jwk_dict->SetString("q", Base64EncodeUrlSafe(prime2)); + jwk_dict->SetString("dp", Base64EncodeUrlSafe(exponent1)); + jwk_dict->SetString("dq", Base64EncodeUrlSafe(exponent2)); + jwk_dict->SetString("qi", Base64EncodeUrlSafe(coefficient)); + + return Status::Success(); +} + +// 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, + base::DictionaryValue* jwk_dict) { + switch (algorithm.paramsType()) { + case blink::WebCryptoKeyAlgorithmParamsTypeAes: { + DCHECK(algorithm.aesParams()); + const char* aes_prefix = ""; + switch (algorithm.aesParams()->lengthBits()) { + case 128: + aes_prefix = "A128"; + break; + case 192: + aes_prefix = "A192"; + break; + case 256: + 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::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::WebCryptoKeyAlgorithmParamsTypeRsaHashed: + switch (algorithm.id()) { + case blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5: { + switch (algorithm.rsaHashedParams()->hash().id()) { + case blink::WebCryptoAlgorithmIdSha1: + jwk_dict->SetString("alg", "RS1"); + break; + case blink::WebCryptoAlgorithmIdSha256: + jwk_dict->SetString("alg", "RS256"); + break; + case blink::WebCryptoAlgorithmIdSha384: + jwk_dict->SetString("alg", "RS384"); + break; + case blink::WebCryptoAlgorithmIdSha512: + jwk_dict->SetString("alg", "RS512"); + break; + default: + NOTREACHED(); + return Status::ErrorUnexpected(); + } + break; + } + case blink::WebCryptoAlgorithmIdRsaOaep: { + switch (algorithm.rsaHashedParams()->hash().id()) { + case blink::WebCryptoAlgorithmIdSha1: + jwk_dict->SetString("alg", "RSA-OAEP"); + break; + case blink::WebCryptoAlgorithmIdSha256: + jwk_dict->SetString("alg", "RSA-OAEP-256"); + break; + case blink::WebCryptoAlgorithmIdSha384: + jwk_dict->SetString("alg", "RSA-OAEP-384"); + break; + case blink::WebCryptoAlgorithmIdSha512: + jwk_dict->SetString("alg", "RSA-OAEP-512"); + break; + default: + NOTREACHED(); + return Status::ErrorUnexpected(); + } + break; + } + default: + NOTREACHED(); + return Status::ErrorUnexpected(); + } + break; + default: + return Status::ErrorUnsupported(); + } + return Status::Success(); +} + +bool IsRsaKey(const blink::WebCryptoKey& key) { + return IsAlgorithmRsa(key.algorithm().id()); +} + +Status ImportRsaKey(base::DictionaryValue* dict, + const blink::WebCryptoAlgorithm& algorithm, + bool extractable, + blink::WebCryptoKeyUsageMask usage_mask, + blink::WebCryptoKey* key) { + // 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. + // See http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-18, + // section 6.3. + std::string jwk_n_value; + Status status = GetJwkBytes(dict, "n", &jwk_n_value); + if (status.IsError()) + return status; + std::string jwk_e_value; + status = GetJwkBytes(dict, "e", &jwk_e_value); + if (status.IsError()) + return status; + + bool is_public_key = !dict->HasKey("d"); + + // Now that the key type is known, do an additional check on the usages to + // make sure they are all applicable for this algorithm + key type. + status = CheckKeyUsages(algorithm.id(), + is_public_key ? blink::WebCryptoKeyTypePublic + : blink::WebCryptoKeyTypePrivate, + usage_mask); + + if (status.IsError()) + return status; + + if (is_public_key) { + return platform::ImportRsaPublicKey(algorithm, + extractable, + usage_mask, + CryptoData(jwk_n_value), + CryptoData(jwk_e_value), + key); + } + + std::string jwk_d_value; + status = GetJwkBytes(dict, "d", &jwk_d_value); + if (status.IsError()) + return status; + + // The "p", "q", "dp", "dq", and "qi" properties are optional. Treat these + // properties the same if they are unspecified, as if they were specified-but + // empty, since ImportRsaPrivateKey() doesn't do validation checks anyway. + + std::string jwk_p_value; + bool has_p; + status = GetOptionalJwkBytes(dict, "p", &jwk_p_value, &has_p); + if (status.IsError()) + return status; + + std::string jwk_q_value; + bool has_q; + status = GetOptionalJwkBytes(dict, "q", &jwk_q_value, &has_q); + if (status.IsError()) + return status; + + std::string jwk_dp_value; + bool has_dp; + status = GetOptionalJwkBytes(dict, "dp", &jwk_dp_value, &has_dp); + if (status.IsError()) + return status; + + std::string jwk_dq_value; + bool has_dq; + status = GetOptionalJwkBytes(dict, "dq", &jwk_dq_value, &has_dq); + if (status.IsError()) + return status; + + std::string jwk_qi_value; + bool has_qi; + status = GetOptionalJwkBytes(dict, "qi", &jwk_qi_value, &has_qi); + if (status.IsError()) + return status; + + int num_optional_properties = has_p + has_q + has_dp + has_dq + has_qi; + if (num_optional_properties != 0 && num_optional_properties != 5) + return Status::ErrorJwkIncompleteOptionalRsaPrivateKey(); + + return platform::ImportRsaPrivateKey( + algorithm, + extractable, + usage_mask, + CryptoData(jwk_n_value), // modulus + CryptoData(jwk_e_value), // public_exponent + CryptoData(jwk_d_value), // private_exponent + CryptoData(jwk_p_value), // prime1 + CryptoData(jwk_q_value), // prime2 + CryptoData(jwk_dp_value), // exponent1 + CryptoData(jwk_dq_value), // exponent2 + CryptoData(jwk_qi_value), // coefficient + key); +} + +} // namespace + +// TODO(eroman): Split this up into smaller functions. +Status ImportKeyJwk(const CryptoData& key_data, + const blink::WebCryptoAlgorithm& algorithm, + bool extractable, + blink::WebCryptoKeyUsageMask usage_mask, + blink::WebCryptoKey* key) { + if (!key_data.byte_length()) + return Status::ErrorImportEmptyKeyData(); + DCHECK(key); + + // Parse the incoming JWK JSON. + base::StringPiece json_string(reinterpret_cast<const char*>(key_data.bytes()), + key_data.byte_length()); + scoped_ptr<base::Value> value(base::JSONReader::Read(json_string)); + // Note, bare pointer dict_value is ok since it points into scoped value. + base::DictionaryValue* dict_value = NULL; + if (!value.get() || !value->GetAsDictionary(&dict_value) || !dict_value) + return Status::ErrorJwkNotDictionary(); + + // JWK "kty". Exit early if this required JWK parameter is missing. + std::string jwk_kty_value; + Status status = GetJwkString(dict_value, "kty", &jwk_kty_value); + if (status.IsError()) + return status; + + // JWK "ext" (optional) --> extractable parameter + { + 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_ext && !jwk_ext_value && extractable) + return Status::ErrorJwkExtInconsistent(); + } + + // JWK "alg" --> algorithm parameter + // 1. JWK alg present but unrecognized: error + // 2. JWK alg valid and inconsistent with input algorithm: error + // 3. JWK alg valid and consistent with input algorithm: use input value + // 4. JWK alg is missing: use input value + const JwkAlgorithmInfo* algorithm_info = NULL; + std::string jwk_alg_value; + bool has_jwk_alg; + status = + GetOptionalJwkString(dict_value, "alg", &jwk_alg_value, &has_jwk_alg); + if (status.IsError()) + return status; + + if (has_jwk_alg) { + // JWK alg present + + // TODO(padolph): Validate alg vs kty. For example kty="RSA" implies alg can + // only be from the RSA family. + + blink::WebCryptoAlgorithm jwk_algorithm = + blink::WebCryptoAlgorithm::createNull(); + algorithm_info = jwk_alg_registry.Get().GetAlgorithmInfo(jwk_alg_value); + if (!algorithm_info || + !algorithm_info->CreateImportAlgorithm(&jwk_algorithm)) + return Status::ErrorJwkUnrecognizedAlgorithm(); + + if (!ImportAlgorithmsConsistent(jwk_algorithm, algorithm)) + return Status::ErrorJwkAlgorithmInconsistent(); + } + 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; + status = + 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) { + 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()) + return status; + + // Some JWK alg ID's embed information about the key length in the alg ID + // string. For example "A128CBC" implies the JWK carries 128 bits + // of key material. For such keys validate that enough bytes were provided. + // If this validation is not done, then it would be possible to select a + // different algorithm by passing a different lengthed key, since that is + // how WebCrypto interprets things. + if (algorithm_info && + algorithm_info->IsInvalidKeyByteLength(jwk_k_value.size())) { + return Status::ErrorJwkIncorrectKeyLength(); + } + + return ImportKey(blink::WebCryptoKeyFormatRaw, + CryptoData(jwk_k_value), + algorithm, + extractable, + usage_mask, + key); + } + + if (jwk_kty_value == "RSA") + return ImportRsaKey(dict_value, algorithm, extractable, usage_mask, key); + + return Status::ErrorJwkUnrecognizedKty(); +} + +Status ExportKeyJwk(const blink::WebCryptoKey& key, + std::vector<uint8>* buffer) { + DCHECK(key.extractable()); + base::DictionaryValue jwk_dict; + Status status = Status::OperationError(); + + switch (key.type()) { + case blink::WebCryptoKeyTypeSecret: { + std::vector<uint8> exported_key; + status = ExportKey(blink::WebCryptoKeyFormatRaw, key, &exported_key); + if (status.IsError()) + return status; + WriteSecretKey(exported_key, &jwk_dict); + break; + } + case blink::WebCryptoKeyTypePublic: { + // TODO(eroman): Update when there are asymmetric keys other than RSA. + if (!IsRsaKey(key)) + return Status::ErrorUnsupported(); + platform::PublicKey* public_key; + status = ToPlatformPublicKey(key, &public_key); + if (status.IsError()) + return status; + std::vector<uint8> modulus; + std::vector<uint8> public_exponent; + status = + platform::ExportRsaPublicKey(public_key, &modulus, &public_exponent); + if (status.IsError()) + return status; + WriteRsaPublicKey(modulus, public_exponent, &jwk_dict); + break; + } + case blink::WebCryptoKeyTypePrivate: { + // TODO(eroman): Update when there are asymmetric keys other than RSA. + if (!IsRsaKey(key)) + return Status::ErrorUnsupported(); + + status = ExportRsaPrivateKeyJwk(key, &jwk_dict); + if (status.IsError()) + return status; + break; + } + + default: + return Status::ErrorUnsupported(); + } + + WriteKeyOps(key.usages(), &jwk_dict); + WriteExt(key.extractable(), &jwk_dict); + status = WriteAlg(key.algorithm(), &jwk_dict); + if (status.IsError()) + return status; + + std::string json; + base::JSONWriter::Write(&jwk_dict, &json); + buffer->assign(json.data(), json.data() + json.size()); + return Status::Success(); +} + +} // namespace webcrypto + +} // namespace content |