123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 |
- // 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 "components/webcrypto/jwk.h"
- #include <stddef.h>
- #include <set>
- #include "base/base64url.h"
- #include "base/cxx17_backports.h"
- #include "base/json/json_reader.h"
- #include "base/json/json_writer.h"
- #include "base/strings/string_piece.h"
- #include "base/strings/stringprintf.h"
- #include "components/webcrypto/algorithms/util.h"
- #include "components/webcrypto/status.h"
- // JSON Web Key Format (JWK) is defined by:
- // http://tools.ietf.org/html/draft-ietf-jose-json-web-key
- //
- // A JWK is a simple JSON dictionary with the following members:
- // - "kty" (Key Type) Parameter, REQUIRED
- // - <kty-specific parameters, see below>, REQUIRED
- // - "use" (Key Use) OPTIONAL
- // - "key_ops" (Key Operations) OPTIONAL
- // - "alg" (Algorithm) OPTIONAL
- // - "ext" (Key Exportability), OPTIONAL
- // (all other entries are ignored)
- //
- // The <kty-specific parameters> are defined by the JWA spec:
- // http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms
- namespace webcrypto {
- namespace {
- // |kJwkEncUsage| and |kJwkSigUsage| are a superset of the possible meanings of
- // JWK's {"use":"enc"}, and {"use":"sig"} respectively.
- //
- // TODO(https://crbug.com/1136147): Remove these masks,
- // as they are not consistent with the Web Crypto
- // processing model for JWK. In particular,
- // intersecting the usages after processing the JWK
- // means Chrome can fail with a Syntax error in cases
- // where the spec describes a Data error.
- const blink::WebCryptoKeyUsageMask kJwkEncUsage =
- blink::kWebCryptoKeyUsageEncrypt | blink::kWebCryptoKeyUsageDecrypt |
- blink::kWebCryptoKeyUsageWrapKey | blink::kWebCryptoKeyUsageUnwrapKey |
- blink::kWebCryptoKeyUsageDeriveKey | blink::kWebCryptoKeyUsageDeriveBits;
- const blink::WebCryptoKeyUsageMask kJwkSigUsage =
- blink::kWebCryptoKeyUsageSign | blink::kWebCryptoKeyUsageVerify;
- // Checks that the "ext" member of the JWK is consistent with
- // "expected_extractable".
- Status VerifyExt(const JwkReader& jwk, bool expected_extractable) {
- // JWK "ext" (optional) --> extractable parameter
- bool jwk_ext_value = false;
- bool has_jwk_ext;
- Status status = jwk.GetOptionalBool("ext", &jwk_ext_value, &has_jwk_ext);
- if (status.IsError())
- return status;
- if (has_jwk_ext && expected_extractable && !jwk_ext_value)
- return Status::ErrorJwkExtInconsistent();
- return Status::Success();
- }
- struct JwkToWebCryptoUsageMapping {
- const char* const jwk_key_op;
- const blink::WebCryptoKeyUsage webcrypto_usage;
- };
- // Keep this ordered the same as WebCrypto's "recognized key usage
- // values". While this is not required for spec compliance,
- // it makes the ordering of key_ops match that of WebCrypto's Key.usages.
- const JwkToWebCryptoUsageMapping kJwkWebCryptoUsageMap[] = {
- {"encrypt", blink::kWebCryptoKeyUsageEncrypt},
- {"decrypt", blink::kWebCryptoKeyUsageDecrypt},
- {"sign", blink::kWebCryptoKeyUsageSign},
- {"verify", blink::kWebCryptoKeyUsageVerify},
- {"deriveKey", blink::kWebCryptoKeyUsageDeriveKey},
- {"deriveBits", blink::kWebCryptoKeyUsageDeriveBits},
- {"wrapKey", blink::kWebCryptoKeyUsageWrapKey},
- {"unwrapKey", blink::kWebCryptoKeyUsageUnwrapKey}};
- bool JwkKeyOpToWebCryptoUsage(const std::string& key_op,
- blink::WebCryptoKeyUsage* usage) {
- for (const auto& crypto_usage_entry : kJwkWebCryptoUsageMap) {
- if (crypto_usage_entry.jwk_key_op == key_op) {
- *usage = crypto_usage_entry.webcrypto_usage;
- return true;
- }
- }
- return false;
- }
- // Creates a JWK key_ops list from a Web Crypto usage mask.
- base::Value CreateJwkKeyOpsFromWebCryptoUsages(
- blink::WebCryptoKeyUsageMask usages) {
- base::Value jwk_key_ops(base::Value::Type::LIST);
- for (const auto& crypto_usage_entry : kJwkWebCryptoUsageMap) {
- if (usages & crypto_usage_entry.webcrypto_usage)
- jwk_key_ops.Append(crypto_usage_entry.jwk_key_op);
- }
- return jwk_key_ops;
- }
- // Composes a Web Crypto usage mask from an array of JWK key_ops values.
- Status GetWebCryptoUsagesFromJwkKeyOps(const base::Value::List& key_ops,
- blink::WebCryptoKeyUsageMask* usages) {
- // This set keeps track of all unrecognized key_ops values.
- std::set<std::string> unrecognized_usages;
- *usages = 0;
- for (size_t i = 0; i < key_ops.size(); ++i) {
- const base::Value& key_op_value = key_ops[i];
- if (!key_op_value.is_string()) {
- return Status::ErrorJwkMemberWrongType(
- base::StringPrintf("key_ops[%d]", static_cast<int>(i)), "string");
- }
- std::string key_op = key_op_value.GetString();
- blink::WebCryptoKeyUsage usage;
- if (JwkKeyOpToWebCryptoUsage(key_op, &usage)) {
- // Ensure there are no duplicate usages.
- if (*usages & usage)
- return Status::ErrorJwkDuplicateKeyOps();
- *usages |= usage;
- }
- // Reaching here means the usage was unrecognized. Such usages are skipped
- // over, however they are kept track of in a set to ensure there were no
- // duplicates.
- if (!unrecognized_usages.insert(key_op).second)
- return Status::ErrorJwkDuplicateKeyOps();
- }
- return Status::Success();
- }
- // Checks that the usages ("use" and "key_ops") of the JWK is consistent with
- // "expected_usages".
- Status VerifyUsages(const JwkReader& jwk,
- blink::WebCryptoKeyUsageMask expected_usages) {
- // JWK "key_ops" (optional) --> usages parameter
- const base::Value::List* jwk_key_ops_value = nullptr;
- bool has_jwk_key_ops;
- Status status =
- jwk.GetOptionalList("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 usages must be a subset of jwk_key_ops_mask.
- if (!ContainsKeyUsages(jwk_key_ops_mask, expected_usages))
- return Status::ErrorJwkKeyopsInconsistent();
- }
- // JWK "use" (optional) --> usages parameter
- std::string jwk_use_value;
- bool has_jwk_use;
- status = jwk.GetOptionalString("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 usages must be a subset of jwk_use_mask.
- if (!ContainsKeyUsages(jwk_use_mask, expected_usages))
- 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();
- return Status::Success();
- }
- } // namespace
- JwkReader::JwkReader() {
- }
- JwkReader::~JwkReader() {
- }
- Status JwkReader::Init(base::span<const uint8_t> bytes,
- bool expected_extractable,
- blink::WebCryptoKeyUsageMask expected_usages,
- const std::string& expected_kty,
- const std::string& expected_alg) {
- // Parse the incoming JWK JSON.
- base::StringPiece json_string(reinterpret_cast<const char*>(bytes.data()),
- bytes.size());
- {
- // Limit the visibility for |value| as it is moved to |dict_| (via
- // |dict_value|) once it has been loaded successfully.
- absl::optional<base::Value> dict = base::JSONReader::Read(json_string);
- if (!dict.has_value() || !dict->is_dict())
- return Status::ErrorJwkNotDictionary();
- dict_ = std::move(dict.value());
- }
- // JWK "kty". Exit early if this required JWK parameter is missing.
- std::string kty;
- Status status = GetString("kty", &kty);
- if (status.IsError())
- return status;
- if (kty != expected_kty)
- return Status::ErrorJwkUnexpectedKty(expected_kty);
- status = VerifyExt(*this, expected_extractable);
- if (status.IsError())
- return status;
- status = VerifyUsages(*this, expected_usages);
- if (status.IsError())
- return status;
- // Verify the algorithm if an expectation was provided.
- if (!expected_alg.empty()) {
- status = VerifyAlg(expected_alg);
- if (status.IsError())
- return status;
- }
- return Status::Success();
- }
- bool JwkReader::HasMember(const std::string& member_name) const {
- return !!dict_.FindKey(member_name);
- }
- Status JwkReader::GetString(const std::string& member_name,
- std::string* result) const {
- const base::Value* value = dict_.FindKey(member_name);
- if (!value)
- return Status::ErrorJwkMemberMissing(member_name);
- if (!value->is_string())
- return Status::ErrorJwkMemberWrongType(member_name, "string");
- *result = value->GetString();
- return Status::Success();
- }
- Status JwkReader::GetOptionalString(const std::string& member_name,
- std::string* result,
- bool* member_exists) const {
- *member_exists = false;
- const base::Value* value = dict_.FindKey(member_name);
- if (!value)
- return Status::Success();
- if (!value->is_string())
- return Status::ErrorJwkMemberWrongType(member_name, "string");
- *result = value->GetString();
- *member_exists = true;
- return Status::Success();
- }
- Status JwkReader::GetOptionalList(const std::string& member_name,
- const base::Value::List** result,
- bool* member_exists) const {
- *member_exists = false;
- const base::Value* value = dict_.FindKey(member_name);
- if (!value)
- return Status::Success();
- if (!value->is_list())
- return Status::ErrorJwkMemberWrongType(member_name, "list");
- *result = &value->GetList();
- *member_exists = true;
- return Status::Success();
- }
- Status JwkReader::GetBytes(const std::string& member_name,
- std::vector<uint8_t>* result) const {
- std::string base64_string;
- Status status = GetString(member_name, &base64_string);
- if (status.IsError())
- return status;
- // The JSON web signature spec says that padding is omitted.
- // https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-36#section-2
- std::string result_str;
- if (!base::Base64UrlDecode(base64_string,
- base::Base64UrlDecodePolicy::DISALLOW_PADDING,
- &result_str)) {
- return Status::ErrorJwkBase64Decode(member_name);
- }
- result->assign(result_str.begin(), result_str.end());
- return Status::Success();
- }
- Status JwkReader::GetBigInteger(const std::string& member_name,
- std::vector<uint8_t>* result) const {
- Status status = GetBytes(member_name, result);
- if (status.IsError())
- return status;
- if (result->empty())
- return Status::ErrorJwkEmptyBigInteger(member_name);
- // The JWA spec says that "The octet sequence MUST utilize the minimum number
- // of octets to represent the value." This means there shouldn't be any
- // leading zeros.
- if (result->size() > 1 && (*result)[0] == 0)
- return Status::ErrorJwkBigIntegerHasLeadingZero(member_name);
- return Status::Success();
- }
- Status JwkReader::GetOptionalBool(const std::string& member_name,
- bool* result,
- bool* member_exists) const {
- *member_exists = false;
- const base::Value* value = dict_.FindKey(member_name);
- if (!value)
- return Status::Success();
- if (!value->is_bool())
- return Status::ErrorJwkMemberWrongType(member_name, "boolean");
- *result = value->GetBool();
- *member_exists = true;
- return Status::Success();
- }
- Status JwkReader::GetAlg(std::string* alg, bool* has_alg) const {
- return GetOptionalString("alg", alg, has_alg);
- }
- Status JwkReader::VerifyAlg(const std::string& expected_alg) const {
- bool has_jwk_alg;
- std::string jwk_alg_value;
- Status status = GetAlg(&jwk_alg_value, &has_jwk_alg);
- if (status.IsError())
- return status;
- if (has_jwk_alg && jwk_alg_value != expected_alg)
- return Status::ErrorJwkAlgorithmInconsistent();
- return Status::Success();
- }
- JwkWriter::JwkWriter(const std::string& algorithm,
- bool extractable,
- blink::WebCryptoKeyUsageMask usages,
- const std::string& kty)
- : dict_(base::Value::Type::DICTIONARY) {
- if (!algorithm.empty())
- dict_.SetStringKey("alg", algorithm);
- dict_.SetKey("key_ops", CreateJwkKeyOpsFromWebCryptoUsages(usages));
- dict_.SetBoolKey("ext", extractable);
- dict_.SetStringKey("kty", kty);
- }
- void JwkWriter::SetString(const std::string& member_name,
- const std::string& value) {
- dict_.SetStringKey(member_name, value);
- }
- void JwkWriter::SetBytes(const std::string& member_name,
- base::span<const uint8_t> value) {
- // The JSON web signature spec says that padding is omitted.
- // https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-36#section-2
- std::string base64url_encoded;
- base::Base64UrlEncode(
- base::StringPiece(reinterpret_cast<const char*>(value.data()),
- value.size()),
- base::Base64UrlEncodePolicy::OMIT_PADDING, &base64url_encoded);
- dict_.SetStringKey(member_name, base64url_encoded);
- }
- void JwkWriter::ToJson(std::vector<uint8_t>* utf8_bytes) const {
- std::string json;
- base::JSONWriter::Write(dict_, &json);
- utf8_bytes->assign(json.begin(), json.end());
- }
- Status GetWebCryptoUsagesFromJwkKeyOpsForTest(
- const base::Value::List& key_ops,
- blink::WebCryptoKeyUsageMask* usages) {
- return GetWebCryptoUsagesFromJwkKeyOps(key_ops, usages);
- }
- } // namespace webcrypto
|