1 // Copyright 2013 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/renderer/webcrypto/webcrypto_impl.h"
13 #include "base/logging.h"
14 #include "crypto/nss_util.h"
15 #include "crypto/scoped_nss_types.h"
16 #include "crypto/secure_util.h"
17 #include "third_party/WebKit/public/platform/WebArrayBuffer.h"
18 #include "third_party/WebKit/public/platform/WebCryptoAlgorithm.h"
19 #include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h"
25 class SymKeyHandle : public WebKit::WebCryptoKeyHandle {
27 explicit SymKeyHandle(crypto::ScopedPK11SymKey key) : key_(key.Pass()) {}
29 PK11SymKey* key() { return key_.get(); }
32 crypto::ScopedPK11SymKey key_;
34 DISALLOW_COPY_AND_ASSIGN(SymKeyHandle);
37 class PublicKeyHandle : public WebKit::WebCryptoKeyHandle {
39 explicit PublicKeyHandle(crypto::ScopedSECKEYPublicKey key)
42 SECKEYPublicKey* key() { return key_.get(); }
45 crypto::ScopedSECKEYPublicKey key_;
47 DISALLOW_COPY_AND_ASSIGN(PublicKeyHandle);
50 class PrivateKeyHandle : public WebKit::WebCryptoKeyHandle {
52 explicit PrivateKeyHandle(crypto::ScopedSECKEYPrivateKey key)
55 SECKEYPrivateKey* key() { return key_.get(); }
58 crypto::ScopedSECKEYPrivateKey key_;
60 DISALLOW_COPY_AND_ASSIGN(PrivateKeyHandle);
63 HASH_HashType WebCryptoAlgorithmToNSSHashType(
64 const WebKit::WebCryptoAlgorithm& algorithm) {
65 switch (algorithm.id()) {
66 case WebKit::WebCryptoAlgorithmIdSha1:
68 case WebKit::WebCryptoAlgorithmIdSha224:
69 return HASH_AlgSHA224;
70 case WebKit::WebCryptoAlgorithmIdSha256:
71 return HASH_AlgSHA256;
72 case WebKit::WebCryptoAlgorithmIdSha384:
73 return HASH_AlgSHA384;
74 case WebKit::WebCryptoAlgorithmIdSha512:
75 return HASH_AlgSHA512;
77 // Not a digest algorithm.
82 CK_MECHANISM_TYPE WebCryptoAlgorithmToHMACMechanism(
83 const WebKit::WebCryptoAlgorithm& algorithm) {
84 switch (algorithm.id()) {
85 case WebKit::WebCryptoAlgorithmIdSha1:
86 return CKM_SHA_1_HMAC;
87 case WebKit::WebCryptoAlgorithmIdSha256:
88 return CKM_SHA256_HMAC;
90 // Not a supported algorithm.
91 return CKM_INVALID_MECHANISM;
95 bool AesCbcEncryptDecrypt(
96 CK_ATTRIBUTE_TYPE operation,
97 const WebKit::WebCryptoAlgorithm& algorithm,
98 const WebKit::WebCryptoKey& key,
99 const unsigned char* data,
101 WebKit::WebArrayBuffer* buffer) {
102 DCHECK_EQ(WebKit::WebCryptoAlgorithmIdAesCbc, algorithm.id());
103 DCHECK_EQ(algorithm.id(), key.algorithm().id());
104 DCHECK_EQ(WebKit::WebCryptoKeyTypeSecret, key.type());
105 DCHECK(operation == CKA_ENCRYPT || operation == CKA_DECRYPT);
107 SymKeyHandle* sym_key = reinterpret_cast<SymKeyHandle*>(key.handle());
109 const WebKit::WebCryptoAesCbcParams* params = algorithm.aesCbcParams();
110 if (params->iv().size() != AES_BLOCK_SIZE)
114 iv_item.type = siBuffer;
115 iv_item.data = const_cast<unsigned char*>(params->iv().data());
116 iv_item.len = params->iv().size();
118 crypto::ScopedSECItem param(PK11_ParamFromIV(CKM_AES_CBC_PAD, &iv_item));
122 crypto::ScopedPK11Context context(PK11_CreateContextBySymKey(
123 CKM_AES_CBC_PAD, operation, sym_key->key(), param.get()));
128 // Oddly PK11_CipherOp takes input and output lengths as "int" rather than
129 // "unsigned". Do some checks now to avoid integer overflowing.
130 if (data_size >= INT_MAX - AES_BLOCK_SIZE) {
131 // TODO(eroman): Handle this by chunking the input fed into NSS. Right now
132 // it doesn't make much difference since the one-shot API would end up
133 // blowing out the memory and crashing anyway. However a newer version of
134 // the spec allows for a sequence<CryptoData> so this will be relevant.
138 // PK11_CipherOp does an invalid memory access when given empty decryption
139 // input, or input which is not a multiple of the block size. See also
140 // https://bugzilla.mozilla.com/show_bug.cgi?id=921687.
141 if (operation == CKA_DECRYPT &&
142 (data_size == 0 || (data_size % AES_BLOCK_SIZE != 0))) {
146 // TODO(eroman): Refine the output buffer size. It can be computed exactly for
147 // encryption, and can be smaller for decryption.
148 unsigned output_max_len = data_size + AES_BLOCK_SIZE;
149 CHECK_GT(output_max_len, data_size);
151 *buffer = WebKit::WebArrayBuffer::create(output_max_len, 1);
153 unsigned char* buffer_data = reinterpret_cast<unsigned char*>(buffer->data());
156 if (SECSuccess != PK11_CipherOp(context.get(),
159 buffer->byteLength(),
165 unsigned int final_output_chunk_len;
166 if (SECSuccess != PK11_DigestFinal(context.get(),
167 buffer_data + output_len,
168 &final_output_chunk_len,
169 output_max_len - output_len)) {
173 WebCryptoImpl::ShrinkBuffer(buffer, final_output_chunk_len + output_len);
177 CK_MECHANISM_TYPE HmacAlgorithmToGenMechanism(
178 const WebKit::WebCryptoAlgorithm& algorithm) {
179 DCHECK_EQ(algorithm.id(), WebKit::WebCryptoAlgorithmIdHmac);
180 const WebKit::WebCryptoHmacKeyParams* params = algorithm.hmacKeyParams();
182 switch (params->hash().id()) {
183 case WebKit::WebCryptoAlgorithmIdSha1:
184 return CKM_SHA_1_HMAC;
185 case WebKit::WebCryptoAlgorithmIdSha256:
186 return CKM_SHA256_HMAC;
188 return CKM_INVALID_MECHANISM;
192 CK_MECHANISM_TYPE WebCryptoAlgorithmToGenMechanism(
193 const WebKit::WebCryptoAlgorithm& algorithm) {
194 switch (algorithm.id()) {
195 case WebKit::WebCryptoAlgorithmIdAesCbc:
196 return CKM_AES_KEY_GEN;
197 case WebKit::WebCryptoAlgorithmIdHmac:
198 return HmacAlgorithmToGenMechanism(algorithm);
200 return CKM_INVALID_MECHANISM;
204 unsigned int WebCryptoHmacAlgorithmToBlockSize(
205 const WebKit::WebCryptoAlgorithm& algorithm) {
206 DCHECK_EQ(algorithm.id(), WebKit::WebCryptoAlgorithmIdHmac);
207 const WebKit::WebCryptoHmacKeyParams* params = algorithm.hmacKeyParams();
209 switch (params->hash().id()) {
210 case WebKit::WebCryptoAlgorithmIdSha1:
212 case WebKit::WebCryptoAlgorithmIdSha256:
219 // Converts a (big-endian) WebCrypto BigInteger, with or without leading zeros,
221 bool BigIntegerToLong(const uint8* data,
223 unsigned long* result) {
224 // TODO(padolph): Is it correct to say that empty data is an error, or does it
225 // mean value 0? See https://www.w3.org/Bugs/Public/show_bug.cgi?id=23655
230 for (size_t i = 0; i < data_size; ++i) {
231 size_t reverse_i = data_size - i - 1;
233 if (reverse_i >= sizeof(unsigned long) && data[i])
234 return false; // Too large for a long.
236 *result |= data[i] << 8 * reverse_i;
243 void WebCryptoImpl::Init() {
244 crypto::EnsureNSSInit();
247 bool WebCryptoImpl::EncryptInternal(
248 const WebKit::WebCryptoAlgorithm& algorithm,
249 const WebKit::WebCryptoKey& key,
250 const unsigned char* data,
252 WebKit::WebArrayBuffer* buffer) {
253 if (algorithm.id() == WebKit::WebCryptoAlgorithmIdAesCbc) {
254 return AesCbcEncryptDecrypt(
255 CKA_ENCRYPT, algorithm, key, data, data_size, buffer);
261 bool WebCryptoImpl::DecryptInternal(
262 const WebKit::WebCryptoAlgorithm& algorithm,
263 const WebKit::WebCryptoKey& key,
264 const unsigned char* data,
266 WebKit::WebArrayBuffer* buffer) {
267 if (algorithm.id() == WebKit::WebCryptoAlgorithmIdAesCbc) {
268 return AesCbcEncryptDecrypt(
269 CKA_DECRYPT, algorithm, key, data, data_size, buffer);
275 bool WebCryptoImpl::DigestInternal(
276 const WebKit::WebCryptoAlgorithm& algorithm,
277 const unsigned char* data,
279 WebKit::WebArrayBuffer* buffer) {
280 HASH_HashType hash_type = WebCryptoAlgorithmToNSSHashType(algorithm);
281 if (hash_type == HASH_AlgNULL) {
285 HASHContext* context = HASH_Create(hash_type);
292 HASH_Update(context, data, data_size);
294 unsigned hash_result_length = HASH_ResultLenContext(context);
295 DCHECK_LE(hash_result_length, static_cast<size_t>(HASH_LENGTH_MAX));
297 *buffer = WebKit::WebArrayBuffer::create(hash_result_length, 1);
299 unsigned char* digest = reinterpret_cast<unsigned char*>(buffer->data());
301 unsigned result_length = 0;
302 HASH_End(context, digest, &result_length, hash_result_length);
304 HASH_Destroy(context);
306 return result_length == hash_result_length;
309 bool WebCryptoImpl::GenerateKeyInternal(
310 const WebKit::WebCryptoAlgorithm& algorithm,
312 WebKit::WebCryptoKeyUsageMask usage_mask,
313 WebKit::WebCryptoKey* key) {
315 CK_MECHANISM_TYPE mech = WebCryptoAlgorithmToGenMechanism(algorithm);
316 unsigned int keylen_bytes = 0;
317 WebKit::WebCryptoKeyType key_type = WebKit::WebCryptoKeyTypeSecret;
319 if (mech == CKM_INVALID_MECHANISM) {
323 switch (algorithm.id()) {
324 case WebKit::WebCryptoAlgorithmIdAesCbc: {
325 const WebKit::WebCryptoAesKeyGenParams* params =
326 algorithm.aesKeyGenParams();
328 keylen_bytes = params->length() / 8;
329 if (params->length() % 8)
331 key_type = WebKit::WebCryptoKeyTypeSecret;
334 case WebKit::WebCryptoAlgorithmIdHmac: {
335 const WebKit::WebCryptoHmacKeyParams* params = algorithm.hmacKeyParams();
337 if (!params->getLength(keylen_bytes)) {
338 keylen_bytes = WebCryptoHmacAlgorithmToBlockSize(algorithm) / 8;
341 key_type = WebKit::WebCryptoKeyTypeSecret;
350 if (keylen_bytes == 0) {
354 crypto::ScopedPK11Slot slot(PK11_GetInternalKeySlot());
359 crypto::ScopedPK11SymKey pk11_key(
360 PK11_KeyGen(slot.get(), mech, NULL, keylen_bytes, NULL));
366 *key = WebKit::WebCryptoKey::create(
367 new SymKeyHandle(pk11_key.Pass()),
368 key_type, extractable, algorithm, usage_mask);
372 bool WebCryptoImpl::GenerateKeyPairInternal(
373 const WebKit::WebCryptoAlgorithm& algorithm,
375 WebKit::WebCryptoKeyUsageMask usage_mask,
376 WebKit::WebCryptoKey* public_key,
377 WebKit::WebCryptoKey* private_key) {
379 // TODO(padolph): Handle other asymmetric algorithm key generation.
380 switch (algorithm.id()) {
381 case WebKit::WebCryptoAlgorithmIdRsaEsPkcs1v1_5:
382 case WebKit::WebCryptoAlgorithmIdRsaOaep:
383 case WebKit::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5: {
384 const WebKit::WebCryptoRsaKeyGenParams* const params =
385 algorithm.rsaKeyGenParams();
388 crypto::ScopedPK11Slot slot(PK11_GetInternalKeySlot());
389 unsigned long public_exponent;
390 if (!slot || !params->modulusLength() ||
391 !BigIntegerToLong(params->publicExponent().data(),
392 params->publicExponent().size(),
398 PK11RSAGenParams rsa_gen_params;
399 rsa_gen_params.keySizeInBits = params->modulusLength();
400 rsa_gen_params.pe = public_exponent;
402 // Flags are verified at the Blink layer; here the flags are set to all
403 // possible operations for the given key type.
404 CK_FLAGS operation_flags;
405 switch (algorithm.id()) {
406 case WebKit::WebCryptoAlgorithmIdRsaEsPkcs1v1_5:
407 case WebKit::WebCryptoAlgorithmIdRsaOaep:
408 operation_flags = CKF_ENCRYPT | CKF_DECRYPT | CKF_WRAP | CKF_UNWRAP;
410 case WebKit::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5:
411 operation_flags = CKF_SIGN | CKF_VERIFY;
417 const CK_FLAGS operation_flags_mask = CKF_ENCRYPT | CKF_DECRYPT |
418 CKF_SIGN | CKF_VERIFY | CKF_WRAP |
420 const PK11AttrFlags attribute_flags = 0; // Default all PK11_ATTR_ flags.
422 // Note: NSS does not generate an sec_public_key if the call below fails,
423 // so there is no danger of a leaked sec_public_key.
424 SECKEYPublicKey* sec_public_key;
425 crypto::ScopedSECKEYPrivateKey scoped_sec_private_key(
426 PK11_GenerateKeyPairWithOpFlags(slot.get(),
427 CKM_RSA_PKCS_KEY_PAIR_GEN,
432 operation_flags_mask,
438 // One extractable input parameter is provided, and the Web Crypto API
439 // spec at this time says it applies to both members of the key pair.
440 // This is probably not correct: it makes more operational sense to have
441 // extractable apply only to the private key and make the public key
442 // always extractable. For now implement what the spec says and track the
443 // spec bug here: https://www.w3.org/Bugs/Public/show_bug.cgi?id=23695
444 *public_key = WebKit::WebCryptoKey::create(
445 new PublicKeyHandle(crypto::ScopedSECKEYPublicKey(sec_public_key)),
446 WebKit::WebCryptoKeyTypePublic,
447 extractable, // probably should be 'true' always
450 *private_key = WebKit::WebCryptoKey::create(
451 new PrivateKeyHandle(scoped_sec_private_key.Pass()),
452 WebKit::WebCryptoKeyTypePrivate,
464 bool WebCryptoImpl::ImportKeyInternal(
465 WebKit::WebCryptoKeyFormat format,
466 const unsigned char* key_data,
467 unsigned key_data_size,
468 const WebKit::WebCryptoAlgorithm& algorithm_or_null,
470 WebKit::WebCryptoKeyUsageMask usage_mask,
471 WebKit::WebCryptoKey* key) {
472 // TODO(eroman): Currently expects algorithm to always be specified, as it is
473 // required for raw format.
474 if (algorithm_or_null.isNull())
476 const WebKit::WebCryptoAlgorithm& algorithm = algorithm_or_null;
478 WebKit::WebCryptoKeyType type;
479 switch (algorithm.id()) {
480 case WebKit::WebCryptoAlgorithmIdHmac:
481 case WebKit::WebCryptoAlgorithmIdAesCbc:
482 type = WebKit::WebCryptoKeyTypeSecret;
484 // TODO(bryaneyler): Support more key types.
489 // TODO(bryaneyler): Need to split handling for symmetric and asymmetric keys.
490 // Currently only supporting symmetric.
491 CK_MECHANISM_TYPE mechanism = CKM_INVALID_MECHANISM;
492 // Flags are verified at the Blink layer; here the flags are set to all
493 // possible operations for this key type.
496 switch(algorithm.id()) {
497 case WebKit::WebCryptoAlgorithmIdHmac: {
498 const WebKit::WebCryptoHmacParams* params = algorithm.hmacParams();
503 mechanism = WebCryptoAlgorithmToHMACMechanism(params->hash());
504 if (mechanism == CKM_INVALID_MECHANISM) {
508 flags |= CKF_SIGN | CKF_VERIFY;
512 case WebKit::WebCryptoAlgorithmIdAesCbc: {
513 mechanism = CKM_AES_CBC;
514 flags |= CKF_ENCRYPT | CKF_DECRYPT;
521 DCHECK_NE(CKM_INVALID_MECHANISM, mechanism);
522 DCHECK_NE(0ul, flags);
524 SECItem key_item = { siBuffer, NULL, 0 };
527 case WebKit::WebCryptoKeyFormatRaw:
528 key_item.data = const_cast<unsigned char*>(key_data);
529 key_item.len = key_data_size;
531 // TODO(bryaneyler): Handle additional formats.
536 crypto::ScopedPK11SymKey pk11_sym_key(
537 PK11_ImportSymKeyWithFlags(PK11_GetInternalSlot(),
545 if (!pk11_sym_key.get()) {
549 *key = WebKit::WebCryptoKey::create(new SymKeyHandle(pk11_sym_key.Pass()),
550 type, extractable, algorithm, usage_mask);
554 bool WebCryptoImpl::SignInternal(
555 const WebKit::WebCryptoAlgorithm& algorithm,
556 const WebKit::WebCryptoKey& key,
557 const unsigned char* data,
559 WebKit::WebArrayBuffer* buffer) {
560 WebKit::WebArrayBuffer result;
562 switch (algorithm.id()) {
563 case WebKit::WebCryptoAlgorithmIdHmac: {
564 const WebKit::WebCryptoHmacParams* params = algorithm.hmacParams();
569 SymKeyHandle* sym_key = reinterpret_cast<SymKeyHandle*>(key.handle());
571 DCHECK_EQ(PK11_GetMechanism(sym_key->key()),
572 WebCryptoAlgorithmToHMACMechanism(params->hash()));
573 DCHECK_NE(0, key.usages() & WebKit::WebCryptoKeyUsageSign);
575 SECItem param_item = { siBuffer, NULL, 0 };
576 SECItem data_item = {
578 const_cast<unsigned char*>(data),
581 // First call is to figure out the length.
582 SECItem signature_item = { siBuffer, NULL, 0 };
584 if (PK11_SignWithSymKey(sym_key->key(),
585 PK11_GetMechanism(sym_key->key()),
588 &data_item) != SECSuccess) {
593 DCHECK_NE(0u, signature_item.len);
595 result = WebKit::WebArrayBuffer::create(signature_item.len, 1);
596 signature_item.data = reinterpret_cast<unsigned char*>(result.data());
598 if (PK11_SignWithSymKey(sym_key->key(),
599 PK11_GetMechanism(sym_key->key()),
602 &data_item) != SECSuccess) {
607 DCHECK_EQ(result.byteLength(), signature_item.len);
619 bool WebCryptoImpl::VerifySignatureInternal(
620 const WebKit::WebCryptoAlgorithm& algorithm,
621 const WebKit::WebCryptoKey& key,
622 const unsigned char* signature,
623 unsigned signature_size,
624 const unsigned char* data,
626 bool* signature_match) {
627 switch (algorithm.id()) {
628 case WebKit::WebCryptoAlgorithmIdHmac: {
629 WebKit::WebArrayBuffer result;
630 if (!SignInternal(algorithm, key, data, data_size, &result)) {
634 // Handling of truncated signatures is underspecified in the WebCrypto
635 // spec, so here we fail verification if a truncated signature is being
637 // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=23097
639 result.byteLength() == signature_size &&
640 crypto::SecureMemEqual(result.data(), signature, signature_size);
651 } // namespace content