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 // JSON Web Key Format (JWK) is defined by:
20 // http://tools.ietf.org/html/draft-ietf-jose-json-web-key
22 // A JWK is a simple JSON dictionary with the following members:
23 // - "kty" (Key Type) Parameter, REQUIRED
24 // - <kty-specific parameters, see below>, REQUIRED
25 // - "use" (Key Use) OPTIONAL
26 // - "key_ops" (Key Operations) OPTIONAL
27 // - "alg" (Algorithm) OPTIONAL
28 // - "ext" (Key Exportability), OPTIONAL
29 // (all other entries are ignored)
31 // The <kty-specific parameters> are defined by the JWA spec:
32 // http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms
38 // Web Crypto equivalent usage mask for JWK 'use' = 'enc'.
39 const blink::WebCryptoKeyUsageMask kJwkEncUsage
=
40 blink::WebCryptoKeyUsageEncrypt
| blink::WebCryptoKeyUsageDecrypt
|
41 blink::WebCryptoKeyUsageWrapKey
| blink::WebCryptoKeyUsageUnwrapKey
;
42 // Web Crypto equivalent usage mask for JWK 'use' = 'sig'.
43 const blink::WebCryptoKeyUsageMask kJwkSigUsage
=
44 blink::WebCryptoKeyUsageSign
| blink::WebCryptoKeyUsageVerify
;
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;
52 Status status
= jwk
.GetOptionalBool("ext", &jwk_ext_value
, &has_jwk_ext
);
55 if (has_jwk_ext
&& expected_extractable
&& !jwk_ext_value
)
56 return Status::ErrorJwkExtInconsistent();
57 return Status::Success();
60 struct JwkToWebCryptoUsageMapping
{
61 const char* const jwk_key_op
;
62 const blink::WebCryptoKeyUsage webcrypto_usage
;
65 // Keep this ordered the same as WebCrypto's "recognized key usage
66 // values". While this is not required for spec compliance,
67 // it makes the ordering of key_ops match that of WebCrypto's Key.usages.
68 const JwkToWebCryptoUsageMapping kJwkWebCryptoUsageMap
[] = {
69 {"encrypt", blink::WebCryptoKeyUsageEncrypt
},
70 {"decrypt", blink::WebCryptoKeyUsageDecrypt
},
71 {"sign", blink::WebCryptoKeyUsageSign
},
72 {"verify", blink::WebCryptoKeyUsageVerify
},
73 {"deriveKey", blink::WebCryptoKeyUsageDeriveKey
},
74 {"deriveBits", blink::WebCryptoKeyUsageDeriveBits
},
75 {"wrapKey", blink::WebCryptoKeyUsageWrapKey
},
76 {"unwrapKey", blink::WebCryptoKeyUsageUnwrapKey
}};
78 bool JwkKeyOpToWebCryptoUsage(const std::string
& key_op
,
79 blink::WebCryptoKeyUsage
* usage
) {
80 for (size_t i
= 0; i
< arraysize(kJwkWebCryptoUsageMap
); ++i
) {
81 if (kJwkWebCryptoUsageMap
[i
].jwk_key_op
== key_op
) {
82 *usage
= kJwkWebCryptoUsageMap
[i
].webcrypto_usage
;
89 // Creates a JWK key_ops list from a Web Crypto usage mask.
90 scoped_ptr
<base::ListValue
> CreateJwkKeyOpsFromWebCryptoUsages(
91 blink::WebCryptoKeyUsageMask usages
) {
92 scoped_ptr
<base::ListValue
> jwk_key_ops(new base::ListValue());
93 for (size_t i
= 0; i
< arraysize(kJwkWebCryptoUsageMap
); ++i
) {
94 if (usages
& kJwkWebCryptoUsageMap
[i
].webcrypto_usage
)
95 jwk_key_ops
->AppendString(kJwkWebCryptoUsageMap
[i
].jwk_key_op
);
97 return jwk_key_ops
.Pass();
100 // Composes a Web Crypto usage mask from an array of JWK key_ops values.
101 Status
GetWebCryptoUsagesFromJwkKeyOps(const base::ListValue
* key_ops
,
102 blink::WebCryptoKeyUsageMask
* usages
) {
103 // This set keeps track of all unrecognized key_ops values.
104 std::set
<std::string
> unrecognized_usages
;
107 for (size_t i
= 0; i
< key_ops
->GetSize(); ++i
) {
109 if (!key_ops
->GetString(i
, &key_op
)) {
110 return Status::ErrorJwkMemberWrongType(
111 base::StringPrintf("key_ops[%d]", static_cast<int>(i
)), "string");
114 blink::WebCryptoKeyUsage usage
;
115 if (JwkKeyOpToWebCryptoUsage(key_op
, &usage
)) {
116 // Ensure there are no duplicate usages.
118 return Status::ErrorJwkDuplicateKeyOps();
122 // Reaching here means the usage was unrecognized. Such usages are skipped
123 // over, however they are kept track of in a set to ensure there were no
125 if (!unrecognized_usages
.insert(key_op
).second
)
126 return Status::ErrorJwkDuplicateKeyOps();
128 return Status::Success();
131 // Checks that the usages ("use" and "key_ops") of the JWK is consistent with
132 // "expected_usages".
133 Status
VerifyUsages(const JwkReader
& jwk
,
134 blink::WebCryptoKeyUsageMask expected_usages
) {
135 // JWK "key_ops" (optional) --> usages parameter
136 base::ListValue
* jwk_key_ops_value
= NULL
;
137 bool has_jwk_key_ops
;
139 jwk
.GetOptionalList("key_ops", &jwk_key_ops_value
, &has_jwk_key_ops
);
140 if (status
.IsError())
142 blink::WebCryptoKeyUsageMask jwk_key_ops_mask
= 0;
143 if (has_jwk_key_ops
) {
145 GetWebCryptoUsagesFromJwkKeyOps(jwk_key_ops_value
, &jwk_key_ops_mask
);
146 if (status
.IsError())
148 // The input usages must be a subset of jwk_key_ops_mask.
149 if (!ContainsKeyUsages(jwk_key_ops_mask
, expected_usages
))
150 return Status::ErrorJwkKeyopsInconsistent();
153 // JWK "use" (optional) --> usages parameter
154 std::string jwk_use_value
;
156 status
= jwk
.GetOptionalString("use", &jwk_use_value
, &has_jwk_use
);
157 if (status
.IsError())
159 blink::WebCryptoKeyUsageMask jwk_use_mask
= 0;
161 if (jwk_use_value
== "enc")
162 jwk_use_mask
= kJwkEncUsage
;
163 else if (jwk_use_value
== "sig")
164 jwk_use_mask
= kJwkSigUsage
;
166 return Status::ErrorJwkUnrecognizedUse();
167 // The input usages must be a subset of jwk_use_mask.
168 if (!ContainsKeyUsages(jwk_use_mask
, expected_usages
))
169 return Status::ErrorJwkUseInconsistent();
172 // If both 'key_ops' and 'use' are present, ensure they are consistent.
173 if (has_jwk_key_ops
&& has_jwk_use
&&
174 !ContainsKeyUsages(jwk_use_mask
, jwk_key_ops_mask
))
175 return Status::ErrorJwkUseAndKeyopsInconsistent();
177 return Status::Success();
182 JwkReader::JwkReader() {
185 JwkReader::~JwkReader() {
188 Status
JwkReader::Init(const CryptoData
& bytes
,
189 bool expected_extractable
,
190 blink::WebCryptoKeyUsageMask expected_usages
,
191 const std::string
& expected_kty
,
192 const std::string
& expected_alg
) {
193 // Parse the incoming JWK JSON.
194 base::StringPiece
json_string(reinterpret_cast<const char*>(bytes
.bytes()),
195 bytes
.byte_length());
197 scoped_ptr
<base::Value
> value
= base::JSONReader::Read(json_string
);
198 base::DictionaryValue
* dict_value
= NULL
;
200 if (!value
.get() || !value
->GetAsDictionary(&dict_value
) || !dict_value
)
201 return Status::ErrorJwkNotDictionary();
203 // Release |value|, as ownership will be transferred to |dict| via
204 // |dict_value|, which points to the same object as |value|.
205 ignore_result(value
.release());
206 dict_
.reset(dict_value
);
208 // JWK "kty". Exit early if this required JWK parameter is missing.
210 Status status
= GetString("kty", &kty
);
211 if (status
.IsError())
214 if (kty
!= expected_kty
)
215 return Status::ErrorJwkUnexpectedKty(expected_kty
);
217 status
= VerifyExt(*this, expected_extractable
);
218 if (status
.IsError())
221 status
= VerifyUsages(*this, expected_usages
);
222 if (status
.IsError())
225 // Verify the algorithm if an expectation was provided.
226 if (!expected_alg
.empty()) {
227 status
= VerifyAlg(expected_alg
);
228 if (status
.IsError())
232 return Status::Success();
235 bool JwkReader::HasMember(const std::string
& member_name
) const {
236 return dict_
->HasKey(member_name
);
239 Status
JwkReader::GetString(const std::string
& member_name
,
240 std::string
* result
) const {
241 base::Value
* value
= NULL
;
242 if (!dict_
->Get(member_name
, &value
))
243 return Status::ErrorJwkMemberMissing(member_name
);
244 if (!value
->GetAsString(result
))
245 return Status::ErrorJwkMemberWrongType(member_name
, "string");
246 return Status::Success();
249 Status
JwkReader::GetOptionalString(const std::string
& member_name
,
251 bool* member_exists
) const {
252 *member_exists
= false;
253 base::Value
* value
= NULL
;
254 if (!dict_
->Get(member_name
, &value
))
255 return Status::Success();
257 if (!value
->GetAsString(result
))
258 return Status::ErrorJwkMemberWrongType(member_name
, "string");
260 *member_exists
= true;
261 return Status::Success();
264 Status
JwkReader::GetOptionalList(const std::string
& member_name
,
265 base::ListValue
** result
,
266 bool* member_exists
) const {
267 *member_exists
= false;
268 base::Value
* value
= NULL
;
269 if (!dict_
->Get(member_name
, &value
))
270 return Status::Success();
272 if (!value
->GetAsList(result
))
273 return Status::ErrorJwkMemberWrongType(member_name
, "list");
275 *member_exists
= true;
276 return Status::Success();
279 Status
JwkReader::GetBytes(const std::string
& member_name
,
280 std::string
* result
) const {
281 std::string base64_string
;
282 Status status
= GetString(member_name
, &base64_string
);
283 if (status
.IsError())
286 if (!Base64DecodeUrlSafe(base64_string
, result
))
287 return Status::ErrorJwkBase64Decode(member_name
);
289 return Status::Success();
292 Status
JwkReader::GetBigInteger(const std::string
& member_name
,
293 std::string
* result
) const {
294 Status status
= GetBytes(member_name
, result
);
295 if (status
.IsError())
299 return Status::ErrorJwkEmptyBigInteger(member_name
);
301 // The JWA spec says that "The octet sequence MUST utilize the minimum number
302 // of octets to represent the value." This means there shouldn't be any
304 if (result
->size() > 1 && (*result
)[0] == 0)
305 return Status::ErrorJwkBigIntegerHasLeadingZero(member_name
);
307 return Status::Success();
310 Status
JwkReader::GetOptionalBool(const std::string
& member_name
,
312 bool* member_exists
) const {
313 *member_exists
= false;
314 base::Value
* value
= NULL
;
315 if (!dict_
->Get(member_name
, &value
))
316 return Status::Success();
318 if (!value
->GetAsBoolean(result
))
319 return Status::ErrorJwkMemberWrongType(member_name
, "boolean");
321 *member_exists
= true;
322 return Status::Success();
325 Status
JwkReader::GetAlg(std::string
* alg
, bool* has_alg
) const {
326 return GetOptionalString("alg", alg
, has_alg
);
329 Status
JwkReader::VerifyAlg(const std::string
& expected_alg
) const {
331 std::string jwk_alg_value
;
332 Status status
= GetAlg(&jwk_alg_value
, &has_jwk_alg
);
333 if (status
.IsError())
336 if (has_jwk_alg
&& jwk_alg_value
!= expected_alg
)
337 return Status::ErrorJwkAlgorithmInconsistent();
339 return Status::Success();
342 JwkWriter::JwkWriter(const std::string
& algorithm
,
344 blink::WebCryptoKeyUsageMask usages
,
345 const std::string
& kty
) {
346 if (!algorithm
.empty())
347 dict_
.SetString("alg", algorithm
);
348 dict_
.Set("key_ops", CreateJwkKeyOpsFromWebCryptoUsages(usages
).release());
349 dict_
.SetBoolean("ext", extractable
);
350 dict_
.SetString("kty", kty
);
353 void JwkWriter::SetString(const std::string
& member_name
,
354 const std::string
& value
) {
355 dict_
.SetString(member_name
, value
);
358 void JwkWriter::SetBytes(const std::string
& member_name
,
359 const CryptoData
& value
) {
360 dict_
.SetString(member_name
, Base64EncodeUrlSafe(base::StringPiece(
361 reinterpret_cast<const char*>(value
.bytes()),
362 value
.byte_length())));
365 void JwkWriter::ToJson(std::vector
<uint8_t>* utf8_bytes
) const {
367 base::JSONWriter::Write(dict_
, &json
);
368 utf8_bytes
->assign(json
.begin(), json
.end());
371 bool Base64DecodeUrlSafe(const std::string
& input
, std::string
* output
) {
372 // The JSON web signature spec specifically says that padding is omitted.
373 if (input
.find_first_of("+/=") != std::string::npos
)
376 std::string
base64_encoded_text(input
);
377 std::replace(base64_encoded_text
.begin(), base64_encoded_text
.end(), '-',
379 std::replace(base64_encoded_text
.begin(), base64_encoded_text
.end(), '_',
381 base64_encoded_text
.append((4 - base64_encoded_text
.size() % 4) % 4, '=');
382 return base::Base64Decode(base64_encoded_text
, output
);
385 std::string
Base64EncodeUrlSafe(const base::StringPiece
& input
) {
387 base::Base64Encode(input
, &output
);
388 std::replace(output
.begin(), output
.end(), '+', '-');
389 std::replace(output
.begin(), output
.end(), '/', '_');
390 output
.erase(std::remove(output
.begin(), output
.end(), '='), output
.end());
394 std::string
Base64EncodeUrlSafe(const std::vector
<uint8_t>& input
) {
395 const base::StringPiece
string_piece(
396 reinterpret_cast<const char*>(vector_as_array(&input
)), input
.size());
397 return Base64EncodeUrlSafe(string_piece
);
400 Status
GetWebCryptoUsagesFromJwkKeyOpsForTest(
401 const base::ListValue
* key_ops
,
402 blink::WebCryptoKeyUsageMask
* usages
) {
403 return GetWebCryptoUsagesFromJwkKeyOps(key_ops
, usages
);
406 } // namespace webcrypto