Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / child / webcrypto / jwk.cc
blob9c29dc2d396b85e6f807a06abf0f5933a38fa880
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.
5 #include "content/child/webcrypto/jwk.h"
7 #include <set>
9 #include "base/base64.h"
10 #include "base/json/json_reader.h"
11 #include "base/json/json_writer.h"
12 #include "base/stl_util.h"
13 #include "base/strings/string_piece.h"
14 #include "base/strings/stringprintf.h"
15 #include "content/child/webcrypto/crypto_data.h"
16 #include "content/child/webcrypto/status.h"
17 #include "content/child/webcrypto/webcrypto_util.h"
19 // TODO(eroman): The algorithm-specific logic in this file for AES and RSA
20 // should be moved into the corresponding AlgorithmImplementation file. It
21 // exists in this file to avoid duplication between OpenSSL and NSS
22 // implementations.
24 // JSON Web Key Format (JWK) is defined by:
25 // http://tools.ietf.org/html/draft-ietf-jose-json-web-key
27 // A JWK is a simple JSON dictionary with the following members:
28 // - "kty" (Key Type) Parameter, REQUIRED
29 // - <kty-specific parameters, see below>, REQUIRED
30 // - "use" (Key Use) OPTIONAL
31 // - "key_ops" (Key Operations) OPTIONAL
32 // - "alg" (Algorithm) OPTIONAL
33 // - "ext" (Key Exportability), OPTIONAL
34 // (all other entries are ignored)
36 // The <kty-specific parameters> are defined by the JWA spec:
37 // http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms
39 namespace content {
41 namespace webcrypto {
43 namespace {
45 // Web Crypto equivalent usage mask for JWK 'use' = 'enc'.
46 const blink::WebCryptoKeyUsageMask kJwkEncUsage =
47 blink::WebCryptoKeyUsageEncrypt | blink::WebCryptoKeyUsageDecrypt |
48 blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey;
49 // Web Crypto equivalent usage mask for JWK 'use' = 'sig'.
50 const blink::WebCryptoKeyUsageMask kJwkSigUsage =
51 blink::WebCryptoKeyUsageSign | blink::WebCryptoKeyUsageVerify;
53 // Checks that the "ext" member of the JWK is consistent with
54 // "expected_extractable".
55 Status VerifyExt(const JwkReader& jwk, bool expected_extractable) {
56 // JWK "ext" (optional) --> extractable parameter
57 bool jwk_ext_value = false;
58 bool has_jwk_ext;
59 Status status = jwk.GetOptionalBool("ext", &jwk_ext_value, &has_jwk_ext);
60 if (status.IsError())
61 return status;
62 if (has_jwk_ext && expected_extractable && !jwk_ext_value)
63 return Status::ErrorJwkExtInconsistent();
64 return Status::Success();
67 struct JwkToWebCryptoUsageMapping {
68 const char* const jwk_key_op;
69 const blink::WebCryptoKeyUsage webcrypto_usage;
72 // Keep this ordered the same as WebCrypto's "recognized key usage
73 // values". While this is not required for spec compliance,
74 // it makes the ordering of key_ops match that of WebCrypto's Key.usages.
75 const JwkToWebCryptoUsageMapping kJwkWebCryptoUsageMap[] = {
76 {"encrypt", blink::WebCryptoKeyUsageEncrypt},
77 {"decrypt", blink::WebCryptoKeyUsageDecrypt},
78 {"sign", blink::WebCryptoKeyUsageSign},
79 {"verify", blink::WebCryptoKeyUsageVerify},
80 {"deriveKey", blink::WebCryptoKeyUsageDeriveKey},
81 {"deriveBits", blink::WebCryptoKeyUsageDeriveBits},
82 {"wrapKey", blink::WebCryptoKeyUsageWrapKey},
83 {"unwrapKey", blink::WebCryptoKeyUsageUnwrapKey}};
85 bool JwkKeyOpToWebCryptoUsage(const std::string& key_op,
86 blink::WebCryptoKeyUsage* usage) {
87 for (size_t i = 0; i < arraysize(kJwkWebCryptoUsageMap); ++i) {
88 if (kJwkWebCryptoUsageMap[i].jwk_key_op == key_op) {
89 *usage = kJwkWebCryptoUsageMap[i].webcrypto_usage;
90 return true;
93 return false;
96 // Creates a JWK key_ops list from a Web Crypto usage mask.
97 scoped_ptr<base::ListValue> CreateJwkKeyOpsFromWebCryptoUsages(
98 blink::WebCryptoKeyUsageMask usages) {
99 scoped_ptr<base::ListValue> jwk_key_ops(new base::ListValue());
100 for (size_t i = 0; i < arraysize(kJwkWebCryptoUsageMap); ++i) {
101 if (usages & kJwkWebCryptoUsageMap[i].webcrypto_usage)
102 jwk_key_ops->AppendString(kJwkWebCryptoUsageMap[i].jwk_key_op);
104 return jwk_key_ops.Pass();
107 // Composes a Web Crypto usage mask from an array of JWK key_ops values.
108 Status GetWebCryptoUsagesFromJwkKeyOps(const base::ListValue* key_ops,
109 blink::WebCryptoKeyUsageMask* usages) {
110 // This set keeps track of all unrecognized key_ops values.
111 std::set<std::string> unrecognized_usages;
113 *usages = 0;
114 for (size_t i = 0; i < key_ops->GetSize(); ++i) {
115 std::string key_op;
116 if (!key_ops->GetString(i, &key_op)) {
117 return Status::ErrorJwkMemberWrongType(
118 base::StringPrintf("key_ops[%d]", static_cast<int>(i)), "string");
121 blink::WebCryptoKeyUsage usage;
122 if (JwkKeyOpToWebCryptoUsage(key_op, &usage)) {
123 // Ensure there are no duplicate usages.
124 if (*usages & usage)
125 return Status::ErrorJwkDuplicateKeyOps();
126 *usages |= usage;
129 // Reaching here means the usage was unrecognized. Such usages are skipped
130 // over, however they are kept track of in a set to ensure there were no
131 // duplicates.
132 if (!unrecognized_usages.insert(key_op).second)
133 return Status::ErrorJwkDuplicateKeyOps();
135 return Status::Success();
138 // Checks that the usages ("use" and "key_ops") of the JWK is consistent with
139 // "expected_usages".
140 Status VerifyUsages(const JwkReader& jwk,
141 blink::WebCryptoKeyUsageMask expected_usages) {
142 // JWK "key_ops" (optional) --> usages parameter
143 base::ListValue* jwk_key_ops_value = NULL;
144 bool has_jwk_key_ops;
145 Status status =
146 jwk.GetOptionalList("key_ops", &jwk_key_ops_value, &has_jwk_key_ops);
147 if (status.IsError())
148 return status;
149 blink::WebCryptoKeyUsageMask jwk_key_ops_mask = 0;
150 if (has_jwk_key_ops) {
151 status =
152 GetWebCryptoUsagesFromJwkKeyOps(jwk_key_ops_value, &jwk_key_ops_mask);
153 if (status.IsError())
154 return status;
155 // The input usages must be a subset of jwk_key_ops_mask.
156 if (!ContainsKeyUsages(jwk_key_ops_mask, expected_usages))
157 return Status::ErrorJwkKeyopsInconsistent();
160 // JWK "use" (optional) --> usages parameter
161 std::string jwk_use_value;
162 bool has_jwk_use;
163 status = jwk.GetOptionalString("use", &jwk_use_value, &has_jwk_use);
164 if (status.IsError())
165 return status;
166 blink::WebCryptoKeyUsageMask jwk_use_mask = 0;
167 if (has_jwk_use) {
168 if (jwk_use_value == "enc")
169 jwk_use_mask = kJwkEncUsage;
170 else if (jwk_use_value == "sig")
171 jwk_use_mask = kJwkSigUsage;
172 else
173 return Status::ErrorJwkUnrecognizedUse();
174 // The input usages must be a subset of jwk_use_mask.
175 if (!ContainsKeyUsages(jwk_use_mask, expected_usages))
176 return Status::ErrorJwkUseInconsistent();
179 // If both 'key_ops' and 'use' are present, ensure they are consistent.
180 if (has_jwk_key_ops && has_jwk_use &&
181 !ContainsKeyUsages(jwk_use_mask, jwk_key_ops_mask))
182 return Status::ErrorJwkUseAndKeyopsInconsistent();
184 return Status::Success();
187 } // namespace
189 JwkReader::JwkReader() {
192 JwkReader::~JwkReader() {
195 Status JwkReader::Init(const CryptoData& bytes,
196 bool expected_extractable,
197 blink::WebCryptoKeyUsageMask expected_usages,
198 const std::string& expected_kty,
199 const std::string& expected_alg) {
200 // Parse the incoming JWK JSON.
201 base::StringPiece json_string(reinterpret_cast<const char*>(bytes.bytes()),
202 bytes.byte_length());
204 scoped_ptr<base::Value> value(base::JSONReader::Read(json_string));
205 base::DictionaryValue* dict_value = NULL;
207 if (!value.get() || !value->GetAsDictionary(&dict_value) || !dict_value)
208 return Status::ErrorJwkNotDictionary();
210 // Release |value|, as ownership will be transferred to |dict| via
211 // |dict_value|, which points to the same object as |value|.
212 ignore_result(value.release());
213 dict_.reset(dict_value);
215 // JWK "kty". Exit early if this required JWK parameter is missing.
216 std::string kty;
217 Status status = GetString("kty", &kty);
218 if (status.IsError())
219 return status;
221 if (kty != expected_kty)
222 return Status::ErrorJwkUnexpectedKty(expected_kty);
224 status = VerifyExt(*this, expected_extractable);
225 if (status.IsError())
226 return status;
228 status = VerifyUsages(*this, expected_usages);
229 if (status.IsError())
230 return status;
232 // Verify the algorithm if an expectation was provided.
233 if (!expected_alg.empty()) {
234 status = VerifyAlg(expected_alg);
235 if (status.IsError())
236 return status;
239 return Status::Success();
242 bool JwkReader::HasMember(const std::string& member_name) const {
243 return dict_->HasKey(member_name);
246 Status JwkReader::GetString(const std::string& member_name,
247 std::string* result) const {
248 base::Value* value = NULL;
249 if (!dict_->Get(member_name, &value))
250 return Status::ErrorJwkMemberMissing(member_name);
251 if (!value->GetAsString(result))
252 return Status::ErrorJwkMemberWrongType(member_name, "string");
253 return Status::Success();
256 Status JwkReader::GetOptionalString(const std::string& member_name,
257 std::string* result,
258 bool* member_exists) const {
259 *member_exists = false;
260 base::Value* value = NULL;
261 if (!dict_->Get(member_name, &value))
262 return Status::Success();
264 if (!value->GetAsString(result))
265 return Status::ErrorJwkMemberWrongType(member_name, "string");
267 *member_exists = true;
268 return Status::Success();
271 Status JwkReader::GetOptionalList(const std::string& member_name,
272 base::ListValue** result,
273 bool* member_exists) const {
274 *member_exists = false;
275 base::Value* value = NULL;
276 if (!dict_->Get(member_name, &value))
277 return Status::Success();
279 if (!value->GetAsList(result))
280 return Status::ErrorJwkMemberWrongType(member_name, "list");
282 *member_exists = true;
283 return Status::Success();
286 Status JwkReader::GetBytes(const std::string& member_name,
287 std::string* result) const {
288 std::string base64_string;
289 Status status = GetString(member_name, &base64_string);
290 if (status.IsError())
291 return status;
293 if (!Base64DecodeUrlSafe(base64_string, result))
294 return Status::ErrorJwkBase64Decode(member_name);
296 return Status::Success();
299 Status JwkReader::GetBigInteger(const std::string& member_name,
300 std::string* result) const {
301 Status status = GetBytes(member_name, result);
302 if (status.IsError())
303 return status;
305 if (result->empty())
306 return Status::ErrorJwkEmptyBigInteger(member_name);
308 // The JWA spec says that "The octet sequence MUST utilize the minimum number
309 // of octets to represent the value." This means there shouldn't be any
310 // leading zeros.
311 if (result->size() > 1 && (*result)[0] == 0)
312 return Status::ErrorJwkBigIntegerHasLeadingZero(member_name);
314 return Status::Success();
317 Status JwkReader::GetOptionalBool(const std::string& member_name,
318 bool* result,
319 bool* member_exists) const {
320 *member_exists = false;
321 base::Value* value = NULL;
322 if (!dict_->Get(member_name, &value))
323 return Status::Success();
325 if (!value->GetAsBoolean(result))
326 return Status::ErrorJwkMemberWrongType(member_name, "boolean");
328 *member_exists = true;
329 return Status::Success();
332 Status JwkReader::GetAlg(std::string* alg, bool* has_alg) const {
333 return GetOptionalString("alg", alg, has_alg);
336 Status JwkReader::VerifyAlg(const std::string& expected_alg) const {
337 bool has_jwk_alg;
338 std::string jwk_alg_value;
339 Status status = GetAlg(&jwk_alg_value, &has_jwk_alg);
340 if (status.IsError())
341 return status;
343 if (has_jwk_alg && jwk_alg_value != expected_alg)
344 return Status::ErrorJwkAlgorithmInconsistent();
346 return Status::Success();
349 JwkWriter::JwkWriter(const std::string& algorithm,
350 bool extractable,
351 blink::WebCryptoKeyUsageMask usages,
352 const std::string& kty) {
353 if (!algorithm.empty())
354 dict_.SetString("alg", algorithm);
355 dict_.Set("key_ops", CreateJwkKeyOpsFromWebCryptoUsages(usages).release());
356 dict_.SetBoolean("ext", extractable);
357 dict_.SetString("kty", kty);
360 void JwkWriter::SetString(const std::string& member_name,
361 const std::string& value) {
362 dict_.SetString(member_name, value);
365 void JwkWriter::SetBytes(const std::string& member_name,
366 const CryptoData& value) {
367 dict_.SetString(member_name, Base64EncodeUrlSafe(base::StringPiece(
368 reinterpret_cast<const char*>(value.bytes()),
369 value.byte_length())));
372 void JwkWriter::ToJson(std::vector<uint8_t>* utf8_bytes) const {
373 std::string json;
374 base::JSONWriter::Write(&dict_, &json);
375 utf8_bytes->assign(json.begin(), json.end());
378 Status ReadSecretKeyNoExpectedAlg(const CryptoData& key_data,
379 bool expected_extractable,
380 blink::WebCryptoKeyUsageMask expected_usages,
381 std::vector<uint8_t>* raw_key_data,
382 JwkReader* jwk) {
383 Status status = jwk->Init(key_data, expected_extractable, expected_usages,
384 "oct", std::string());
385 if (status.IsError())
386 return status;
388 std::string jwk_k_value;
389 status = jwk->GetBytes("k", &jwk_k_value);
390 if (status.IsError())
391 return status;
392 raw_key_data->assign(jwk_k_value.begin(), jwk_k_value.end());
394 return Status::Success();
397 void WriteSecretKeyJwk(const CryptoData& raw_key_data,
398 const std::string& algorithm,
399 bool extractable,
400 blink::WebCryptoKeyUsageMask usages,
401 std::vector<uint8_t>* jwk_key_data) {
402 JwkWriter writer(algorithm, extractable, usages, "oct");
403 writer.SetBytes("k", raw_key_data);
404 writer.ToJson(jwk_key_data);
407 Status ReadSecretKeyJwk(const CryptoData& key_data,
408 const std::string& expected_alg,
409 bool expected_extractable,
410 blink::WebCryptoKeyUsageMask expected_usages,
411 std::vector<uint8_t>* raw_key_data) {
412 JwkReader jwk;
413 Status status = ReadSecretKeyNoExpectedAlg(
414 key_data, expected_extractable, expected_usages, raw_key_data, &jwk);
415 if (status.IsError())
416 return status;
417 return jwk.VerifyAlg(expected_alg);
420 std::string MakeJwkAesAlgorithmName(const std::string& suffix,
421 unsigned int keylen_bytes) {
422 if (keylen_bytes == 16)
423 return std::string("A128") + suffix;
424 if (keylen_bytes == 24)
425 return std::string("A192") + suffix;
426 if (keylen_bytes == 32)
427 return std::string("A256") + suffix;
428 return std::string();
431 Status ReadAesSecretKeyJwk(const CryptoData& key_data,
432 const std::string& algorithm_name_suffix,
433 bool expected_extractable,
434 blink::WebCryptoKeyUsageMask expected_usages,
435 std::vector<uint8_t>* raw_key_data) {
436 JwkReader jwk;
437 Status status = ReadSecretKeyNoExpectedAlg(
438 key_data, expected_extractable, expected_usages, raw_key_data, &jwk);
439 if (status.IsError())
440 return status;
442 bool has_jwk_alg;
443 std::string jwk_alg;
444 status = jwk.GetAlg(&jwk_alg, &has_jwk_alg);
445 if (status.IsError())
446 return status;
448 if (has_jwk_alg) {
449 std::string expected_algorithm_name =
450 MakeJwkAesAlgorithmName(algorithm_name_suffix, raw_key_data->size());
452 if (jwk_alg != expected_algorithm_name) {
453 // Give a different error message if the key length was wrong.
454 if (jwk_alg == MakeJwkAesAlgorithmName(algorithm_name_suffix, 16) ||
455 jwk_alg == MakeJwkAesAlgorithmName(algorithm_name_suffix, 24) ||
456 jwk_alg == MakeJwkAesAlgorithmName(algorithm_name_suffix, 32)) {
457 return Status::ErrorJwkIncorrectKeyLength();
459 return Status::ErrorJwkAlgorithmInconsistent();
463 return Status::Success();
466 // Writes an RSA public key to a JWK dictionary
467 void WriteRsaPublicKeyJwk(const CryptoData& n,
468 const CryptoData& e,
469 const std::string& algorithm,
470 bool extractable,
471 blink::WebCryptoKeyUsageMask usages,
472 std::vector<uint8_t>* jwk_key_data) {
473 JwkWriter writer(algorithm, extractable, usages, "RSA");
474 writer.SetBytes("n", n);
475 writer.SetBytes("e", e);
476 writer.ToJson(jwk_key_data);
479 // Writes an RSA private key to a JWK dictionary
480 void WriteRsaPrivateKeyJwk(const CryptoData& n,
481 const CryptoData& e,
482 const CryptoData& d,
483 const CryptoData& p,
484 const CryptoData& q,
485 const CryptoData& dp,
486 const CryptoData& dq,
487 const CryptoData& qi,
488 const std::string& algorithm,
489 bool extractable,
490 blink::WebCryptoKeyUsageMask usages,
491 std::vector<uint8_t>* jwk_key_data) {
492 JwkWriter writer(algorithm, extractable, usages, "RSA");
494 writer.SetBytes("n", n);
495 writer.SetBytes("e", e);
496 writer.SetBytes("d", d);
497 // Although these are "optional" in the JWA, WebCrypto spec requires them to
498 // be emitted.
499 writer.SetBytes("p", p);
500 writer.SetBytes("q", q);
501 writer.SetBytes("dp", dp);
502 writer.SetBytes("dq", dq);
503 writer.SetBytes("qi", qi);
504 writer.ToJson(jwk_key_data);
507 JwkRsaInfo::JwkRsaInfo() : is_private_key(false) {
510 JwkRsaInfo::~JwkRsaInfo() {
513 Status ReadRsaKeyJwk(const CryptoData& key_data,
514 const std::string& expected_alg,
515 bool expected_extractable,
516 blink::WebCryptoKeyUsageMask expected_usages,
517 JwkRsaInfo* result) {
518 JwkReader jwk;
519 Status status = jwk.Init(key_data, expected_extractable, expected_usages,
520 "RSA", expected_alg);
521 if (status.IsError())
522 return status;
524 // An RSA public key must have an "n" (modulus) and an "e" (exponent) entry
525 // in the JWK, while an RSA private key must have those, plus at least a "d"
526 // (private exponent) entry.
527 // See http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-18,
528 // section 6.3.
529 status = jwk.GetBigInteger("n", &result->n);
530 if (status.IsError())
531 return status;
532 status = jwk.GetBigInteger("e", &result->e);
533 if (status.IsError())
534 return status;
536 result->is_private_key = jwk.HasMember("d");
537 if (!result->is_private_key)
538 return Status::Success();
540 status = jwk.GetBigInteger("d", &result->d);
541 if (status.IsError())
542 return status;
544 // The "p", "q", "dp", "dq", and "qi" properties are optional in the JWA
545 // spec. However they are required by Chromium's WebCrypto implementation.
547 status = jwk.GetBigInteger("p", &result->p);
548 if (status.IsError())
549 return status;
551 status = jwk.GetBigInteger("q", &result->q);
552 if (status.IsError())
553 return status;
555 status = jwk.GetBigInteger("dp", &result->dp);
556 if (status.IsError())
557 return status;
559 status = jwk.GetBigInteger("dq", &result->dq);
560 if (status.IsError())
561 return status;
563 status = jwk.GetBigInteger("qi", &result->qi);
564 if (status.IsError())
565 return status;
567 return Status::Success();
570 const char* GetJwkHmacAlgorithmName(blink::WebCryptoAlgorithmId hash) {
571 switch (hash) {
572 case blink::WebCryptoAlgorithmIdSha1:
573 return "HS1";
574 case blink::WebCryptoAlgorithmIdSha256:
575 return "HS256";
576 case blink::WebCryptoAlgorithmIdSha384:
577 return "HS384";
578 case blink::WebCryptoAlgorithmIdSha512:
579 return "HS512";
580 default:
581 return NULL;
585 bool Base64DecodeUrlSafe(const std::string& input, std::string* output) {
586 // The JSON web signature spec specifically says that padding is omitted.
587 if (input.find_first_of("+/=") != std::string::npos)
588 return false;
590 std::string base64_encoded_text(input);
591 std::replace(base64_encoded_text.begin(), base64_encoded_text.end(), '-',
592 '+');
593 std::replace(base64_encoded_text.begin(), base64_encoded_text.end(), '_',
594 '/');
595 base64_encoded_text.append((4 - base64_encoded_text.size() % 4) % 4, '=');
596 return base::Base64Decode(base64_encoded_text, output);
599 std::string Base64EncodeUrlSafe(const base::StringPiece& input) {
600 std::string output;
601 base::Base64Encode(input, &output);
602 std::replace(output.begin(), output.end(), '+', '-');
603 std::replace(output.begin(), output.end(), '/', '_');
604 output.erase(std::remove(output.begin(), output.end(), '='), output.end());
605 return output;
608 std::string Base64EncodeUrlSafe(const std::vector<uint8_t>& input) {
609 const base::StringPiece string_piece(
610 reinterpret_cast<const char*>(vector_as_array(&input)), input.size());
611 return Base64EncodeUrlSafe(string_piece);
614 Status GetWebCryptoUsagesFromJwkKeyOpsForTest(
615 const base::ListValue* key_ops,
616 blink::WebCryptoKeyUsageMask* usages) {
617 return GetWebCryptoUsagesFromJwkKeyOps(key_ops, usages);
620 } // namespace webcrypto
622 } // namespace content