jwk.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/webcrypto/jwk.h"
  5. #include <stddef.h>
  6. #include <set>
  7. #include "base/base64url.h"
  8. #include "base/cxx17_backports.h"
  9. #include "base/json/json_reader.h"
  10. #include "base/json/json_writer.h"
  11. #include "base/strings/string_piece.h"
  12. #include "base/strings/stringprintf.h"
  13. #include "components/webcrypto/algorithms/util.h"
  14. #include "components/webcrypto/status.h"
  15. // JSON Web Key Format (JWK) is defined by:
  16. // http://tools.ietf.org/html/draft-ietf-jose-json-web-key
  17. //
  18. // A JWK is a simple JSON dictionary with the following members:
  19. // - "kty" (Key Type) Parameter, REQUIRED
  20. // - <kty-specific parameters, see below>, REQUIRED
  21. // - "use" (Key Use) OPTIONAL
  22. // - "key_ops" (Key Operations) OPTIONAL
  23. // - "alg" (Algorithm) OPTIONAL
  24. // - "ext" (Key Exportability), OPTIONAL
  25. // (all other entries are ignored)
  26. //
  27. // The <kty-specific parameters> are defined by the JWA spec:
  28. // http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms
  29. namespace webcrypto {
  30. namespace {
  31. // |kJwkEncUsage| and |kJwkSigUsage| are a superset of the possible meanings of
  32. // JWK's {"use":"enc"}, and {"use":"sig"} respectively.
  33. //
  34. // TODO(https://crbug.com/1136147): Remove these masks,
  35. // as they are not consistent with the Web Crypto
  36. // processing model for JWK. In particular,
  37. // intersecting the usages after processing the JWK
  38. // means Chrome can fail with a Syntax error in cases
  39. // where the spec describes a Data error.
  40. const blink::WebCryptoKeyUsageMask kJwkEncUsage =
  41. blink::kWebCryptoKeyUsageEncrypt | blink::kWebCryptoKeyUsageDecrypt |
  42. blink::kWebCryptoKeyUsageWrapKey | blink::kWebCryptoKeyUsageUnwrapKey |
  43. blink::kWebCryptoKeyUsageDeriveKey | blink::kWebCryptoKeyUsageDeriveBits;
  44. const blink::WebCryptoKeyUsageMask kJwkSigUsage =
  45. blink::kWebCryptoKeyUsageSign | blink::kWebCryptoKeyUsageVerify;
  46. // Checks that the "ext" member of the JWK is consistent with
  47. // "expected_extractable".
  48. Status VerifyExt(const JwkReader& jwk, bool expected_extractable) {
  49. // JWK "ext" (optional) --> extractable parameter
  50. bool jwk_ext_value = false;
  51. bool has_jwk_ext;
  52. Status status = jwk.GetOptionalBool("ext", &jwk_ext_value, &has_jwk_ext);
  53. if (status.IsError())
  54. return status;
  55. if (has_jwk_ext && expected_extractable && !jwk_ext_value)
  56. return Status::ErrorJwkExtInconsistent();
  57. return Status::Success();
  58. }
  59. struct JwkToWebCryptoUsageMapping {
  60. const char* const jwk_key_op;
  61. const blink::WebCryptoKeyUsage webcrypto_usage;
  62. };
  63. // Keep this ordered the same as WebCrypto's "recognized key usage
  64. // values". While this is not required for spec compliance,
  65. // it makes the ordering of key_ops match that of WebCrypto's Key.usages.
  66. const JwkToWebCryptoUsageMapping kJwkWebCryptoUsageMap[] = {
  67. {"encrypt", blink::kWebCryptoKeyUsageEncrypt},
  68. {"decrypt", blink::kWebCryptoKeyUsageDecrypt},
  69. {"sign", blink::kWebCryptoKeyUsageSign},
  70. {"verify", blink::kWebCryptoKeyUsageVerify},
  71. {"deriveKey", blink::kWebCryptoKeyUsageDeriveKey},
  72. {"deriveBits", blink::kWebCryptoKeyUsageDeriveBits},
  73. {"wrapKey", blink::kWebCryptoKeyUsageWrapKey},
  74. {"unwrapKey", blink::kWebCryptoKeyUsageUnwrapKey}};
  75. bool JwkKeyOpToWebCryptoUsage(const std::string& key_op,
  76. blink::WebCryptoKeyUsage* usage) {
  77. for (const auto& crypto_usage_entry : kJwkWebCryptoUsageMap) {
  78. if (crypto_usage_entry.jwk_key_op == key_op) {
  79. *usage = crypto_usage_entry.webcrypto_usage;
  80. return true;
  81. }
  82. }
  83. return false;
  84. }
  85. // Creates a JWK key_ops list from a Web Crypto usage mask.
  86. base::Value CreateJwkKeyOpsFromWebCryptoUsages(
  87. blink::WebCryptoKeyUsageMask usages) {
  88. base::Value jwk_key_ops(base::Value::Type::LIST);
  89. for (const auto& crypto_usage_entry : kJwkWebCryptoUsageMap) {
  90. if (usages & crypto_usage_entry.webcrypto_usage)
  91. jwk_key_ops.Append(crypto_usage_entry.jwk_key_op);
  92. }
  93. return jwk_key_ops;
  94. }
  95. // Composes a Web Crypto usage mask from an array of JWK key_ops values.
  96. Status GetWebCryptoUsagesFromJwkKeyOps(const base::Value::List& key_ops,
  97. blink::WebCryptoKeyUsageMask* usages) {
  98. // This set keeps track of all unrecognized key_ops values.
  99. std::set<std::string> unrecognized_usages;
  100. *usages = 0;
  101. for (size_t i = 0; i < key_ops.size(); ++i) {
  102. const base::Value& key_op_value = key_ops[i];
  103. if (!key_op_value.is_string()) {
  104. return Status::ErrorJwkMemberWrongType(
  105. base::StringPrintf("key_ops[%d]", static_cast<int>(i)), "string");
  106. }
  107. std::string key_op = key_op_value.GetString();
  108. blink::WebCryptoKeyUsage usage;
  109. if (JwkKeyOpToWebCryptoUsage(key_op, &usage)) {
  110. // Ensure there are no duplicate usages.
  111. if (*usages & usage)
  112. return Status::ErrorJwkDuplicateKeyOps();
  113. *usages |= usage;
  114. }
  115. // Reaching here means the usage was unrecognized. Such usages are skipped
  116. // over, however they are kept track of in a set to ensure there were no
  117. // duplicates.
  118. if (!unrecognized_usages.insert(key_op).second)
  119. return Status::ErrorJwkDuplicateKeyOps();
  120. }
  121. return Status::Success();
  122. }
  123. // Checks that the usages ("use" and "key_ops") of the JWK is consistent with
  124. // "expected_usages".
  125. Status VerifyUsages(const JwkReader& jwk,
  126. blink::WebCryptoKeyUsageMask expected_usages) {
  127. // JWK "key_ops" (optional) --> usages parameter
  128. const base::Value::List* jwk_key_ops_value = nullptr;
  129. bool has_jwk_key_ops;
  130. Status status =
  131. jwk.GetOptionalList("key_ops", &jwk_key_ops_value, &has_jwk_key_ops);
  132. if (status.IsError())
  133. return status;
  134. blink::WebCryptoKeyUsageMask jwk_key_ops_mask = 0;
  135. if (has_jwk_key_ops) {
  136. status =
  137. GetWebCryptoUsagesFromJwkKeyOps(*jwk_key_ops_value, &jwk_key_ops_mask);
  138. if (status.IsError())
  139. return status;
  140. // The input usages must be a subset of jwk_key_ops_mask.
  141. if (!ContainsKeyUsages(jwk_key_ops_mask, expected_usages))
  142. return Status::ErrorJwkKeyopsInconsistent();
  143. }
  144. // JWK "use" (optional) --> usages parameter
  145. std::string jwk_use_value;
  146. bool has_jwk_use;
  147. status = jwk.GetOptionalString("use", &jwk_use_value, &has_jwk_use);
  148. if (status.IsError())
  149. return status;
  150. blink::WebCryptoKeyUsageMask jwk_use_mask = 0;
  151. if (has_jwk_use) {
  152. if (jwk_use_value == "enc")
  153. jwk_use_mask = kJwkEncUsage;
  154. else if (jwk_use_value == "sig")
  155. jwk_use_mask = kJwkSigUsage;
  156. else
  157. return Status::ErrorJwkUnrecognizedUse();
  158. // The input usages must be a subset of jwk_use_mask.
  159. if (!ContainsKeyUsages(jwk_use_mask, expected_usages))
  160. return Status::ErrorJwkUseInconsistent();
  161. }
  162. // If both 'key_ops' and 'use' are present, ensure they are consistent.
  163. if (has_jwk_key_ops && has_jwk_use &&
  164. !ContainsKeyUsages(jwk_use_mask, jwk_key_ops_mask))
  165. return Status::ErrorJwkUseAndKeyopsInconsistent();
  166. return Status::Success();
  167. }
  168. } // namespace
  169. JwkReader::JwkReader() {
  170. }
  171. JwkReader::~JwkReader() {
  172. }
  173. Status JwkReader::Init(base::span<const uint8_t> bytes,
  174. bool expected_extractable,
  175. blink::WebCryptoKeyUsageMask expected_usages,
  176. const std::string& expected_kty,
  177. const std::string& expected_alg) {
  178. // Parse the incoming JWK JSON.
  179. base::StringPiece json_string(reinterpret_cast<const char*>(bytes.data()),
  180. bytes.size());
  181. {
  182. // Limit the visibility for |value| as it is moved to |dict_| (via
  183. // |dict_value|) once it has been loaded successfully.
  184. absl::optional<base::Value> dict = base::JSONReader::Read(json_string);
  185. if (!dict.has_value() || !dict->is_dict())
  186. return Status::ErrorJwkNotDictionary();
  187. dict_ = std::move(dict.value());
  188. }
  189. // JWK "kty". Exit early if this required JWK parameter is missing.
  190. std::string kty;
  191. Status status = GetString("kty", &kty);
  192. if (status.IsError())
  193. return status;
  194. if (kty != expected_kty)
  195. return Status::ErrorJwkUnexpectedKty(expected_kty);
  196. status = VerifyExt(*this, expected_extractable);
  197. if (status.IsError())
  198. return status;
  199. status = VerifyUsages(*this, expected_usages);
  200. if (status.IsError())
  201. return status;
  202. // Verify the algorithm if an expectation was provided.
  203. if (!expected_alg.empty()) {
  204. status = VerifyAlg(expected_alg);
  205. if (status.IsError())
  206. return status;
  207. }
  208. return Status::Success();
  209. }
  210. bool JwkReader::HasMember(const std::string& member_name) const {
  211. return !!dict_.FindKey(member_name);
  212. }
  213. Status JwkReader::GetString(const std::string& member_name,
  214. std::string* result) const {
  215. const base::Value* value = dict_.FindKey(member_name);
  216. if (!value)
  217. return Status::ErrorJwkMemberMissing(member_name);
  218. if (!value->is_string())
  219. return Status::ErrorJwkMemberWrongType(member_name, "string");
  220. *result = value->GetString();
  221. return Status::Success();
  222. }
  223. Status JwkReader::GetOptionalString(const std::string& member_name,
  224. std::string* result,
  225. bool* member_exists) const {
  226. *member_exists = false;
  227. const base::Value* value = dict_.FindKey(member_name);
  228. if (!value)
  229. return Status::Success();
  230. if (!value->is_string())
  231. return Status::ErrorJwkMemberWrongType(member_name, "string");
  232. *result = value->GetString();
  233. *member_exists = true;
  234. return Status::Success();
  235. }
  236. Status JwkReader::GetOptionalList(const std::string& member_name,
  237. const base::Value::List** result,
  238. bool* member_exists) const {
  239. *member_exists = false;
  240. const base::Value* value = dict_.FindKey(member_name);
  241. if (!value)
  242. return Status::Success();
  243. if (!value->is_list())
  244. return Status::ErrorJwkMemberWrongType(member_name, "list");
  245. *result = &value->GetList();
  246. *member_exists = true;
  247. return Status::Success();
  248. }
  249. Status JwkReader::GetBytes(const std::string& member_name,
  250. std::vector<uint8_t>* result) const {
  251. std::string base64_string;
  252. Status status = GetString(member_name, &base64_string);
  253. if (status.IsError())
  254. return status;
  255. // The JSON web signature spec says that padding is omitted.
  256. // https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-36#section-2
  257. std::string result_str;
  258. if (!base::Base64UrlDecode(base64_string,
  259. base::Base64UrlDecodePolicy::DISALLOW_PADDING,
  260. &result_str)) {
  261. return Status::ErrorJwkBase64Decode(member_name);
  262. }
  263. result->assign(result_str.begin(), result_str.end());
  264. return Status::Success();
  265. }
  266. Status JwkReader::GetBigInteger(const std::string& member_name,
  267. std::vector<uint8_t>* result) const {
  268. Status status = GetBytes(member_name, result);
  269. if (status.IsError())
  270. return status;
  271. if (result->empty())
  272. return Status::ErrorJwkEmptyBigInteger(member_name);
  273. // The JWA spec says that "The octet sequence MUST utilize the minimum number
  274. // of octets to represent the value." This means there shouldn't be any
  275. // leading zeros.
  276. if (result->size() > 1 && (*result)[0] == 0)
  277. return Status::ErrorJwkBigIntegerHasLeadingZero(member_name);
  278. return Status::Success();
  279. }
  280. Status JwkReader::GetOptionalBool(const std::string& member_name,
  281. bool* result,
  282. bool* member_exists) const {
  283. *member_exists = false;
  284. const base::Value* value = dict_.FindKey(member_name);
  285. if (!value)
  286. return Status::Success();
  287. if (!value->is_bool())
  288. return Status::ErrorJwkMemberWrongType(member_name, "boolean");
  289. *result = value->GetBool();
  290. *member_exists = true;
  291. return Status::Success();
  292. }
  293. Status JwkReader::GetAlg(std::string* alg, bool* has_alg) const {
  294. return GetOptionalString("alg", alg, has_alg);
  295. }
  296. Status JwkReader::VerifyAlg(const std::string& expected_alg) const {
  297. bool has_jwk_alg;
  298. std::string jwk_alg_value;
  299. Status status = GetAlg(&jwk_alg_value, &has_jwk_alg);
  300. if (status.IsError())
  301. return status;
  302. if (has_jwk_alg && jwk_alg_value != expected_alg)
  303. return Status::ErrorJwkAlgorithmInconsistent();
  304. return Status::Success();
  305. }
  306. JwkWriter::JwkWriter(const std::string& algorithm,
  307. bool extractable,
  308. blink::WebCryptoKeyUsageMask usages,
  309. const std::string& kty)
  310. : dict_(base::Value::Type::DICTIONARY) {
  311. if (!algorithm.empty())
  312. dict_.SetStringKey("alg", algorithm);
  313. dict_.SetKey("key_ops", CreateJwkKeyOpsFromWebCryptoUsages(usages));
  314. dict_.SetBoolKey("ext", extractable);
  315. dict_.SetStringKey("kty", kty);
  316. }
  317. void JwkWriter::SetString(const std::string& member_name,
  318. const std::string& value) {
  319. dict_.SetStringKey(member_name, value);
  320. }
  321. void JwkWriter::SetBytes(const std::string& member_name,
  322. base::span<const uint8_t> value) {
  323. // The JSON web signature spec says that padding is omitted.
  324. // https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-36#section-2
  325. std::string base64url_encoded;
  326. base::Base64UrlEncode(
  327. base::StringPiece(reinterpret_cast<const char*>(value.data()),
  328. value.size()),
  329. base::Base64UrlEncodePolicy::OMIT_PADDING, &base64url_encoded);
  330. dict_.SetStringKey(member_name, base64url_encoded);
  331. }
  332. void JwkWriter::ToJson(std::vector<uint8_t>* utf8_bytes) const {
  333. std::string json;
  334. base::JSONWriter::Write(dict_, &json);
  335. utf8_bytes->assign(json.begin(), json.end());
  336. }
  337. Status GetWebCryptoUsagesFromJwkKeyOpsForTest(
  338. const base::Value::List& key_ops,
  339. blink::WebCryptoKeyUsageMask* usages) {
  340. return GetWebCryptoUsagesFromJwkKeyOps(key_ops, usages);
  341. }
  342. } // namespace webcrypto