Cleanup: Pass std::string as const reference from pdf/
[chromium-blink-merge.git] / components / webcrypto / jwk.cc
blob1a3245aceeae52c21e9c426f1b9de773960c7e0e
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"
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 "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
34 namespace webcrypto {
36 namespace {
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;
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();
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;
83 return true;
86 return false;
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;
106 *usages = 0;
107 for (size_t i = 0; i < key_ops->GetSize(); ++i) {
108 std::string key_op;
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.
117 if (*usages & usage)
118 return Status::ErrorJwkDuplicateKeyOps();
119 *usages |= usage;
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
124 // duplicates.
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;
138 Status status =
139 jwk.GetOptionalList("key_ops", &jwk_key_ops_value, &has_jwk_key_ops);
140 if (status.IsError())
141 return status;
142 blink::WebCryptoKeyUsageMask jwk_key_ops_mask = 0;
143 if (has_jwk_key_ops) {
144 status =
145 GetWebCryptoUsagesFromJwkKeyOps(jwk_key_ops_value, &jwk_key_ops_mask);
146 if (status.IsError())
147 return status;
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;
155 bool has_jwk_use;
156 status = jwk.GetOptionalString("use", &jwk_use_value, &has_jwk_use);
157 if (status.IsError())
158 return status;
159 blink::WebCryptoKeyUsageMask jwk_use_mask = 0;
160 if (has_jwk_use) {
161 if (jwk_use_value == "enc")
162 jwk_use_mask = kJwkEncUsage;
163 else if (jwk_use_value == "sig")
164 jwk_use_mask = kJwkSigUsage;
165 else
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();
180 } // namespace
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.
209 std::string kty;
210 Status status = GetString("kty", &kty);
211 if (status.IsError())
212 return status;
214 if (kty != expected_kty)
215 return Status::ErrorJwkUnexpectedKty(expected_kty);
217 status = VerifyExt(*this, expected_extractable);
218 if (status.IsError())
219 return status;
221 status = VerifyUsages(*this, expected_usages);
222 if (status.IsError())
223 return status;
225 // Verify the algorithm if an expectation was provided.
226 if (!expected_alg.empty()) {
227 status = VerifyAlg(expected_alg);
228 if (status.IsError())
229 return status;
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,
250 std::string* result,
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())
284 return status;
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())
296 return status;
298 if (result->empty())
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
303 // leading zeros.
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,
311 bool* result,
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 {
330 bool has_jwk_alg;
331 std::string jwk_alg_value;
332 Status status = GetAlg(&jwk_alg_value, &has_jwk_alg);
333 if (status.IsError())
334 return status;
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,
343 bool extractable,
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 {
366 std::string json;
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)
374 return false;
376 std::string base64_encoded_text(input);
377 std::replace(base64_encoded_text.begin(), base64_encoded_text.end(), '-',
378 '+');
379 std::replace(base64_encoded_text.begin(), base64_encoded_text.end(), '_',
380 '/');
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) {
386 std::string output;
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());
391 return output;
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