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"
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
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
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;
59 Status status
= jwk
.GetOptionalBool("ext", &jwk_ext_value
, &has_jwk_ext
);
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
;
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
;
114 for (size_t i
= 0; i
< key_ops
->GetSize(); ++i
) {
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.
125 return Status::ErrorJwkDuplicateKeyOps();
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
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
;
146 jwk
.GetOptionalList("key_ops", &jwk_key_ops_value
, &has_jwk_key_ops
);
147 if (status
.IsError())
149 blink::WebCryptoKeyUsageMask jwk_key_ops_mask
= 0;
150 if (has_jwk_key_ops
) {
152 GetWebCryptoUsagesFromJwkKeyOps(jwk_key_ops_value
, &jwk_key_ops_mask
);
153 if (status
.IsError())
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
;
163 status
= jwk
.GetOptionalString("use", &jwk_use_value
, &has_jwk_use
);
164 if (status
.IsError())
166 blink::WebCryptoKeyUsageMask jwk_use_mask
= 0;
168 if (jwk_use_value
== "enc")
169 jwk_use_mask
= kJwkEncUsage
;
170 else if (jwk_use_value
== "sig")
171 jwk_use_mask
= kJwkSigUsage
;
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();
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.
217 Status status
= GetString("kty", &kty
);
218 if (status
.IsError())
221 if (kty
!= expected_kty
)
222 return Status::ErrorJwkUnexpectedKty(expected_kty
);
224 status
= VerifyExt(*this, expected_extractable
);
225 if (status
.IsError())
228 status
= VerifyUsages(*this, expected_usages
);
229 if (status
.IsError())
232 // Verify the algorithm if an expectation was provided.
233 if (!expected_alg
.empty()) {
234 status
= VerifyAlg(expected_alg
);
235 if (status
.IsError())
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
,
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())
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())
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
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
,
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 {
338 std::string jwk_alg_value
;
339 Status status
= GetAlg(&jwk_alg_value
, &has_jwk_alg
);
340 if (status
.IsError())
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
,
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 {
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
,
383 Status status
= jwk
->Init(key_data
, expected_extractable
, expected_usages
,
384 "oct", std::string());
385 if (status
.IsError())
388 std::string jwk_k_value
;
389 status
= jwk
->GetBytes("k", &jwk_k_value
);
390 if (status
.IsError())
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
,
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
) {
413 Status status
= ReadSecretKeyNoExpectedAlg(
414 key_data
, expected_extractable
, expected_usages
, raw_key_data
, &jwk
);
415 if (status
.IsError())
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
) {
437 Status status
= ReadSecretKeyNoExpectedAlg(
438 key_data
, expected_extractable
, expected_usages
, raw_key_data
, &jwk
);
439 if (status
.IsError())
444 status
= jwk
.GetAlg(&jwk_alg
, &has_jwk_alg
);
445 if (status
.IsError())
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
,
469 const std::string
& algorithm
,
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
,
485 const CryptoData
& dp
,
486 const CryptoData
& dq
,
487 const CryptoData
& qi
,
488 const std::string
& algorithm
,
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
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
) {
519 Status status
= jwk
.Init(key_data
, expected_extractable
, expected_usages
,
520 "RSA", expected_alg
);
521 if (status
.IsError())
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,
529 status
= jwk
.GetBigInteger("n", &result
->n
);
530 if (status
.IsError())
532 status
= jwk
.GetBigInteger("e", &result
->e
);
533 if (status
.IsError())
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())
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())
551 status
= jwk
.GetBigInteger("q", &result
->q
);
552 if (status
.IsError())
555 status
= jwk
.GetBigInteger("dp", &result
->dp
);
556 if (status
.IsError())
559 status
= jwk
.GetBigInteger("dq", &result
->dq
);
560 if (status
.IsError())
563 status
= jwk
.GetBigInteger("qi", &result
->qi
);
564 if (status
.IsError())
567 return Status::Success();
570 const char* GetJwkHmacAlgorithmName(blink::WebCryptoAlgorithmId hash
) {
572 case blink::WebCryptoAlgorithmIdSha1
:
574 case blink::WebCryptoAlgorithmIdSha256
:
576 case blink::WebCryptoAlgorithmIdSha384
:
578 case blink::WebCryptoAlgorithmIdSha512
:
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
)
590 std::string
base64_encoded_text(input
);
591 std::replace(base64_encoded_text
.begin(), base64_encoded_text
.end(), '-',
593 std::replace(base64_encoded_text
.begin(), base64_encoded_text
.end(), '_',
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
) {
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());
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