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 "components/webcrypto/jwk.h"
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 "components/webcrypto/crypto_data.h"
16 #include "components/webcrypto/status.h"
17 #include "components/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
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
43 // Web Crypto equivalent usage mask for JWK 'use' = 'enc'.
44 const blink::WebCryptoKeyUsageMask kJwkEncUsage
=
45 blink::WebCryptoKeyUsageEncrypt
| blink::WebCryptoKeyUsageDecrypt
|
46 blink::WebCryptoKeyUsageWrapKey
| blink::WebCryptoKeyUsageUnwrapKey
;
47 // Web Crypto equivalent usage mask for JWK 'use' = 'sig'.
48 const blink::WebCryptoKeyUsageMask kJwkSigUsage
=
49 blink::WebCryptoKeyUsageSign
| blink::WebCryptoKeyUsageVerify
;
51 // Checks that the "ext" member of the JWK is consistent with
52 // "expected_extractable".
53 Status
VerifyExt(const JwkReader
& jwk
, bool expected_extractable
) {
54 // JWK "ext" (optional) --> extractable parameter
55 bool jwk_ext_value
= false;
57 Status status
= jwk
.GetOptionalBool("ext", &jwk_ext_value
, &has_jwk_ext
);
60 if (has_jwk_ext
&& expected_extractable
&& !jwk_ext_value
)
61 return Status::ErrorJwkExtInconsistent();
62 return Status::Success();
65 struct JwkToWebCryptoUsageMapping
{
66 const char* const jwk_key_op
;
67 const blink::WebCryptoKeyUsage webcrypto_usage
;
70 // Keep this ordered the same as WebCrypto's "recognized key usage
71 // values". While this is not required for spec compliance,
72 // it makes the ordering of key_ops match that of WebCrypto's Key.usages.
73 const JwkToWebCryptoUsageMapping kJwkWebCryptoUsageMap
[] = {
74 {"encrypt", blink::WebCryptoKeyUsageEncrypt
},
75 {"decrypt", blink::WebCryptoKeyUsageDecrypt
},
76 {"sign", blink::WebCryptoKeyUsageSign
},
77 {"verify", blink::WebCryptoKeyUsageVerify
},
78 {"deriveKey", blink::WebCryptoKeyUsageDeriveKey
},
79 {"deriveBits", blink::WebCryptoKeyUsageDeriveBits
},
80 {"wrapKey", blink::WebCryptoKeyUsageWrapKey
},
81 {"unwrapKey", blink::WebCryptoKeyUsageUnwrapKey
}};
83 bool JwkKeyOpToWebCryptoUsage(const std::string
& key_op
,
84 blink::WebCryptoKeyUsage
* usage
) {
85 for (size_t i
= 0; i
< arraysize(kJwkWebCryptoUsageMap
); ++i
) {
86 if (kJwkWebCryptoUsageMap
[i
].jwk_key_op
== key_op
) {
87 *usage
= kJwkWebCryptoUsageMap
[i
].webcrypto_usage
;
94 // Creates a JWK key_ops list from a Web Crypto usage mask.
95 scoped_ptr
<base::ListValue
> CreateJwkKeyOpsFromWebCryptoUsages(
96 blink::WebCryptoKeyUsageMask usages
) {
97 scoped_ptr
<base::ListValue
> jwk_key_ops(new base::ListValue());
98 for (size_t i
= 0; i
< arraysize(kJwkWebCryptoUsageMap
); ++i
) {
99 if (usages
& kJwkWebCryptoUsageMap
[i
].webcrypto_usage
)
100 jwk_key_ops
->AppendString(kJwkWebCryptoUsageMap
[i
].jwk_key_op
);
102 return jwk_key_ops
.Pass();
105 // Composes a Web Crypto usage mask from an array of JWK key_ops values.
106 Status
GetWebCryptoUsagesFromJwkKeyOps(const base::ListValue
* key_ops
,
107 blink::WebCryptoKeyUsageMask
* usages
) {
108 // This set keeps track of all unrecognized key_ops values.
109 std::set
<std::string
> unrecognized_usages
;
112 for (size_t i
= 0; i
< key_ops
->GetSize(); ++i
) {
114 if (!key_ops
->GetString(i
, &key_op
)) {
115 return Status::ErrorJwkMemberWrongType(
116 base::StringPrintf("key_ops[%d]", static_cast<int>(i
)), "string");
119 blink::WebCryptoKeyUsage usage
;
120 if (JwkKeyOpToWebCryptoUsage(key_op
, &usage
)) {
121 // Ensure there are no duplicate usages.
123 return Status::ErrorJwkDuplicateKeyOps();
127 // Reaching here means the usage was unrecognized. Such usages are skipped
128 // over, however they are kept track of in a set to ensure there were no
130 if (!unrecognized_usages
.insert(key_op
).second
)
131 return Status::ErrorJwkDuplicateKeyOps();
133 return Status::Success();
136 // Checks that the usages ("use" and "key_ops") of the JWK is consistent with
137 // "expected_usages".
138 Status
VerifyUsages(const JwkReader
& jwk
,
139 blink::WebCryptoKeyUsageMask expected_usages
) {
140 // JWK "key_ops" (optional) --> usages parameter
141 base::ListValue
* jwk_key_ops_value
= NULL
;
142 bool has_jwk_key_ops
;
144 jwk
.GetOptionalList("key_ops", &jwk_key_ops_value
, &has_jwk_key_ops
);
145 if (status
.IsError())
147 blink::WebCryptoKeyUsageMask jwk_key_ops_mask
= 0;
148 if (has_jwk_key_ops
) {
150 GetWebCryptoUsagesFromJwkKeyOps(jwk_key_ops_value
, &jwk_key_ops_mask
);
151 if (status
.IsError())
153 // The input usages must be a subset of jwk_key_ops_mask.
154 if (!ContainsKeyUsages(jwk_key_ops_mask
, expected_usages
))
155 return Status::ErrorJwkKeyopsInconsistent();
158 // JWK "use" (optional) --> usages parameter
159 std::string jwk_use_value
;
161 status
= jwk
.GetOptionalString("use", &jwk_use_value
, &has_jwk_use
);
162 if (status
.IsError())
164 blink::WebCryptoKeyUsageMask jwk_use_mask
= 0;
166 if (jwk_use_value
== "enc")
167 jwk_use_mask
= kJwkEncUsage
;
168 else if (jwk_use_value
== "sig")
169 jwk_use_mask
= kJwkSigUsage
;
171 return Status::ErrorJwkUnrecognizedUse();
172 // The input usages must be a subset of jwk_use_mask.
173 if (!ContainsKeyUsages(jwk_use_mask
, expected_usages
))
174 return Status::ErrorJwkUseInconsistent();
177 // If both 'key_ops' and 'use' are present, ensure they are consistent.
178 if (has_jwk_key_ops
&& has_jwk_use
&&
179 !ContainsKeyUsages(jwk_use_mask
, jwk_key_ops_mask
))
180 return Status::ErrorJwkUseAndKeyopsInconsistent();
182 return Status::Success();
187 JwkReader::JwkReader() {
190 JwkReader::~JwkReader() {
193 Status
JwkReader::Init(const CryptoData
& bytes
,
194 bool expected_extractable
,
195 blink::WebCryptoKeyUsageMask expected_usages
,
196 const std::string
& expected_kty
,
197 const std::string
& expected_alg
) {
198 // Parse the incoming JWK JSON.
199 base::StringPiece
json_string(reinterpret_cast<const char*>(bytes
.bytes()),
200 bytes
.byte_length());
202 scoped_ptr
<base::Value
> value
= base::JSONReader::Read(json_string
);
203 base::DictionaryValue
* dict_value
= NULL
;
205 if (!value
.get() || !value
->GetAsDictionary(&dict_value
) || !dict_value
)
206 return Status::ErrorJwkNotDictionary();
208 // Release |value|, as ownership will be transferred to |dict| via
209 // |dict_value|, which points to the same object as |value|.
210 ignore_result(value
.release());
211 dict_
.reset(dict_value
);
213 // JWK "kty". Exit early if this required JWK parameter is missing.
215 Status status
= GetString("kty", &kty
);
216 if (status
.IsError())
219 if (kty
!= expected_kty
)
220 return Status::ErrorJwkUnexpectedKty(expected_kty
);
222 status
= VerifyExt(*this, expected_extractable
);
223 if (status
.IsError())
226 status
= VerifyUsages(*this, expected_usages
);
227 if (status
.IsError())
230 // Verify the algorithm if an expectation was provided.
231 if (!expected_alg
.empty()) {
232 status
= VerifyAlg(expected_alg
);
233 if (status
.IsError())
237 return Status::Success();
240 bool JwkReader::HasMember(const std::string
& member_name
) const {
241 return dict_
->HasKey(member_name
);
244 Status
JwkReader::GetString(const std::string
& member_name
,
245 std::string
* result
) const {
246 base::Value
* value
= NULL
;
247 if (!dict_
->Get(member_name
, &value
))
248 return Status::ErrorJwkMemberMissing(member_name
);
249 if (!value
->GetAsString(result
))
250 return Status::ErrorJwkMemberWrongType(member_name
, "string");
251 return Status::Success();
254 Status
JwkReader::GetOptionalString(const std::string
& member_name
,
256 bool* member_exists
) const {
257 *member_exists
= false;
258 base::Value
* value
= NULL
;
259 if (!dict_
->Get(member_name
, &value
))
260 return Status::Success();
262 if (!value
->GetAsString(result
))
263 return Status::ErrorJwkMemberWrongType(member_name
, "string");
265 *member_exists
= true;
266 return Status::Success();
269 Status
JwkReader::GetOptionalList(const std::string
& member_name
,
270 base::ListValue
** result
,
271 bool* member_exists
) const {
272 *member_exists
= false;
273 base::Value
* value
= NULL
;
274 if (!dict_
->Get(member_name
, &value
))
275 return Status::Success();
277 if (!value
->GetAsList(result
))
278 return Status::ErrorJwkMemberWrongType(member_name
, "list");
280 *member_exists
= true;
281 return Status::Success();
284 Status
JwkReader::GetBytes(const std::string
& member_name
,
285 std::string
* result
) const {
286 std::string base64_string
;
287 Status status
= GetString(member_name
, &base64_string
);
288 if (status
.IsError())
291 if (!Base64DecodeUrlSafe(base64_string
, result
))
292 return Status::ErrorJwkBase64Decode(member_name
);
294 return Status::Success();
297 Status
JwkReader::GetBigInteger(const std::string
& member_name
,
298 std::string
* result
) const {
299 Status status
= GetBytes(member_name
, result
);
300 if (status
.IsError())
304 return Status::ErrorJwkEmptyBigInteger(member_name
);
306 // The JWA spec says that "The octet sequence MUST utilize the minimum number
307 // of octets to represent the value." This means there shouldn't be any
309 if (result
->size() > 1 && (*result
)[0] == 0)
310 return Status::ErrorJwkBigIntegerHasLeadingZero(member_name
);
312 return Status::Success();
315 Status
JwkReader::GetOptionalBool(const std::string
& member_name
,
317 bool* member_exists
) const {
318 *member_exists
= false;
319 base::Value
* value
= NULL
;
320 if (!dict_
->Get(member_name
, &value
))
321 return Status::Success();
323 if (!value
->GetAsBoolean(result
))
324 return Status::ErrorJwkMemberWrongType(member_name
, "boolean");
326 *member_exists
= true;
327 return Status::Success();
330 Status
JwkReader::GetAlg(std::string
* alg
, bool* has_alg
) const {
331 return GetOptionalString("alg", alg
, has_alg
);
334 Status
JwkReader::VerifyAlg(const std::string
& expected_alg
) const {
336 std::string jwk_alg_value
;
337 Status status
= GetAlg(&jwk_alg_value
, &has_jwk_alg
);
338 if (status
.IsError())
341 if (has_jwk_alg
&& jwk_alg_value
!= expected_alg
)
342 return Status::ErrorJwkAlgorithmInconsistent();
344 return Status::Success();
347 JwkWriter::JwkWriter(const std::string
& algorithm
,
349 blink::WebCryptoKeyUsageMask usages
,
350 const std::string
& kty
) {
351 if (!algorithm
.empty())
352 dict_
.SetString("alg", algorithm
);
353 dict_
.Set("key_ops", CreateJwkKeyOpsFromWebCryptoUsages(usages
).release());
354 dict_
.SetBoolean("ext", extractable
);
355 dict_
.SetString("kty", kty
);
358 void JwkWriter::SetString(const std::string
& member_name
,
359 const std::string
& value
) {
360 dict_
.SetString(member_name
, value
);
363 void JwkWriter::SetBytes(const std::string
& member_name
,
364 const CryptoData
& value
) {
365 dict_
.SetString(member_name
, Base64EncodeUrlSafe(base::StringPiece(
366 reinterpret_cast<const char*>(value
.bytes()),
367 value
.byte_length())));
370 void JwkWriter::ToJson(std::vector
<uint8_t>* utf8_bytes
) const {
372 base::JSONWriter::Write(dict_
, &json
);
373 utf8_bytes
->assign(json
.begin(), json
.end());
376 Status
ReadSecretKeyNoExpectedAlg(const CryptoData
& key_data
,
377 bool expected_extractable
,
378 blink::WebCryptoKeyUsageMask expected_usages
,
379 std::vector
<uint8_t>* raw_key_data
,
381 Status status
= jwk
->Init(key_data
, expected_extractable
, expected_usages
,
382 "oct", std::string());
383 if (status
.IsError())
386 std::string jwk_k_value
;
387 status
= jwk
->GetBytes("k", &jwk_k_value
);
388 if (status
.IsError())
390 raw_key_data
->assign(jwk_k_value
.begin(), jwk_k_value
.end());
392 return Status::Success();
395 void WriteSecretKeyJwk(const CryptoData
& raw_key_data
,
396 const std::string
& algorithm
,
398 blink::WebCryptoKeyUsageMask usages
,
399 std::vector
<uint8_t>* jwk_key_data
) {
400 JwkWriter
writer(algorithm
, extractable
, usages
, "oct");
401 writer
.SetBytes("k", raw_key_data
);
402 writer
.ToJson(jwk_key_data
);
405 Status
ReadSecretKeyJwk(const CryptoData
& key_data
,
406 const std::string
& expected_alg
,
407 bool expected_extractable
,
408 blink::WebCryptoKeyUsageMask expected_usages
,
409 std::vector
<uint8_t>* raw_key_data
) {
411 Status status
= ReadSecretKeyNoExpectedAlg(
412 key_data
, expected_extractable
, expected_usages
, raw_key_data
, &jwk
);
413 if (status
.IsError())
415 return jwk
.VerifyAlg(expected_alg
);
418 std::string
MakeJwkAesAlgorithmName(const std::string
& suffix
,
419 size_t keylen_bytes
) {
420 if (keylen_bytes
== 16)
421 return std::string("A128") + suffix
;
422 if (keylen_bytes
== 24)
423 return std::string("A192") + suffix
;
424 if (keylen_bytes
== 32)
425 return std::string("A256") + suffix
;
426 return std::string();
429 Status
ReadAesSecretKeyJwk(const CryptoData
& key_data
,
430 const std::string
& algorithm_name_suffix
,
431 bool expected_extractable
,
432 blink::WebCryptoKeyUsageMask expected_usages
,
433 std::vector
<uint8_t>* raw_key_data
) {
435 Status status
= ReadSecretKeyNoExpectedAlg(
436 key_data
, expected_extractable
, expected_usages
, raw_key_data
, &jwk
);
437 if (status
.IsError())
442 status
= jwk
.GetAlg(&jwk_alg
, &has_jwk_alg
);
443 if (status
.IsError())
447 std::string expected_algorithm_name
=
448 MakeJwkAesAlgorithmName(algorithm_name_suffix
, raw_key_data
->size());
450 if (jwk_alg
!= expected_algorithm_name
) {
451 // Give a different error message if the key length was wrong.
452 if (jwk_alg
== MakeJwkAesAlgorithmName(algorithm_name_suffix
, 16) ||
453 jwk_alg
== MakeJwkAesAlgorithmName(algorithm_name_suffix
, 24) ||
454 jwk_alg
== MakeJwkAesAlgorithmName(algorithm_name_suffix
, 32)) {
455 return Status::ErrorJwkIncorrectKeyLength();
457 return Status::ErrorJwkAlgorithmInconsistent();
461 return Status::Success();
464 // Writes an RSA public key to a JWK dictionary
465 void WriteRsaPublicKeyJwk(const CryptoData
& n
,
467 const std::string
& algorithm
,
469 blink::WebCryptoKeyUsageMask usages
,
470 std::vector
<uint8_t>* jwk_key_data
) {
471 JwkWriter
writer(algorithm
, extractable
, usages
, "RSA");
472 writer
.SetBytes("n", n
);
473 writer
.SetBytes("e", e
);
474 writer
.ToJson(jwk_key_data
);
477 // Writes an RSA private key to a JWK dictionary
478 void WriteRsaPrivateKeyJwk(const CryptoData
& n
,
483 const CryptoData
& dp
,
484 const CryptoData
& dq
,
485 const CryptoData
& qi
,
486 const std::string
& algorithm
,
488 blink::WebCryptoKeyUsageMask usages
,
489 std::vector
<uint8_t>* jwk_key_data
) {
490 JwkWriter
writer(algorithm
, extractable
, usages
, "RSA");
492 writer
.SetBytes("n", n
);
493 writer
.SetBytes("e", e
);
494 writer
.SetBytes("d", d
);
495 // Although these are "optional" in the JWA, WebCrypto spec requires them to
497 writer
.SetBytes("p", p
);
498 writer
.SetBytes("q", q
);
499 writer
.SetBytes("dp", dp
);
500 writer
.SetBytes("dq", dq
);
501 writer
.SetBytes("qi", qi
);
502 writer
.ToJson(jwk_key_data
);
505 JwkRsaInfo::JwkRsaInfo() : is_private_key(false) {
508 JwkRsaInfo::~JwkRsaInfo() {
511 Status
ReadRsaKeyJwk(const CryptoData
& key_data
,
512 const std::string
& expected_alg
,
513 bool expected_extractable
,
514 blink::WebCryptoKeyUsageMask expected_usages
,
515 JwkRsaInfo
* result
) {
517 Status status
= jwk
.Init(key_data
, expected_extractable
, expected_usages
,
518 "RSA", expected_alg
);
519 if (status
.IsError())
522 // An RSA public key must have an "n" (modulus) and an "e" (exponent) entry
523 // in the JWK, while an RSA private key must have those, plus at least a "d"
524 // (private exponent) entry.
525 // See http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-18,
527 status
= jwk
.GetBigInteger("n", &result
->n
);
528 if (status
.IsError())
530 status
= jwk
.GetBigInteger("e", &result
->e
);
531 if (status
.IsError())
534 result
->is_private_key
= jwk
.HasMember("d");
535 if (!result
->is_private_key
)
536 return Status::Success();
538 status
= jwk
.GetBigInteger("d", &result
->d
);
539 if (status
.IsError())
542 // The "p", "q", "dp", "dq", and "qi" properties are optional in the JWA
543 // spec. However they are required by Chromium's WebCrypto implementation.
545 status
= jwk
.GetBigInteger("p", &result
->p
);
546 if (status
.IsError())
549 status
= jwk
.GetBigInteger("q", &result
->q
);
550 if (status
.IsError())
553 status
= jwk
.GetBigInteger("dp", &result
->dp
);
554 if (status
.IsError())
557 status
= jwk
.GetBigInteger("dq", &result
->dq
);
558 if (status
.IsError())
561 status
= jwk
.GetBigInteger("qi", &result
->qi
);
562 if (status
.IsError())
565 return Status::Success();
568 const char* GetJwkHmacAlgorithmName(blink::WebCryptoAlgorithmId hash
) {
570 case blink::WebCryptoAlgorithmIdSha1
:
572 case blink::WebCryptoAlgorithmIdSha256
:
574 case blink::WebCryptoAlgorithmIdSha384
:
576 case blink::WebCryptoAlgorithmIdSha512
:
583 bool Base64DecodeUrlSafe(const std::string
& input
, std::string
* output
) {
584 // The JSON web signature spec specifically says that padding is omitted.
585 if (input
.find_first_of("+/=") != std::string::npos
)
588 std::string
base64_encoded_text(input
);
589 std::replace(base64_encoded_text
.begin(), base64_encoded_text
.end(), '-',
591 std::replace(base64_encoded_text
.begin(), base64_encoded_text
.end(), '_',
593 base64_encoded_text
.append((4 - base64_encoded_text
.size() % 4) % 4, '=');
594 return base::Base64Decode(base64_encoded_text
, output
);
597 std::string
Base64EncodeUrlSafe(const base::StringPiece
& input
) {
599 base::Base64Encode(input
, &output
);
600 std::replace(output
.begin(), output
.end(), '+', '-');
601 std::replace(output
.begin(), output
.end(), '/', '_');
602 output
.erase(std::remove(output
.begin(), output
.end(), '='), output
.end());
606 std::string
Base64EncodeUrlSafe(const std::vector
<uint8_t>& input
) {
607 const base::StringPiece
string_piece(
608 reinterpret_cast<const char*>(vector_as_array(&input
)), input
.size());
609 return Base64EncodeUrlSafe(string_piece
);
612 Status
GetWebCryptoUsagesFromJwkKeyOpsForTest(
613 const base::ListValue
* key_ops
,
614 blink::WebCryptoKeyUsageMask
* usages
) {
615 return GetWebCryptoUsagesFromJwkKeyOps(key_ops
, usages
);
618 } // namespace webcrypto