Chromium Code Reviews| Index: content/child/webcrypto/nss/aes_kw_nss.cc |
| diff --git a/content/child/webcrypto/nss/aes_kw_nss.cc b/content/child/webcrypto/nss/aes_kw_nss.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b0de13930f299538933cd4d5f3eb00eb012bfa08 |
| --- /dev/null |
| +++ b/content/child/webcrypto/nss/aes_kw_nss.cc |
| @@ -0,0 +1,201 @@ |
| +// Copyright 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include <secerr.h> |
| + |
| +#include "content/child/webcrypto/crypto_data.h" |
| +#include "content/child/webcrypto/nss/aes_key_nss.h" |
| +#include "content/child/webcrypto/nss/key_nss.h" |
| +#include "content/child/webcrypto/nss/sym_key_nss.h" |
| +#include "content/child/webcrypto/nss/util_nss.h" |
| +#include "content/child/webcrypto/status.h" |
| +#include "content/child/webcrypto/webcrypto_util.h" |
| +#include "crypto/scoped_nss_types.h" |
| +#include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h" |
| +#include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h" |
| + |
| +namespace content { |
| + |
| +namespace webcrypto { |
| + |
| +namespace { |
| + |
| +// The Default IV for AES-KW. See http://www.ietf.org/rfc/rfc3394.txt |
| +// Section 2.2.3.1. |
| +const unsigned char kAesIv[] = {0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6}; |
| + |
| +Status DoUnwrapSymKeyAesKw(const CryptoData& wrapped_key_data, |
|
Ryan Sleevi
2014/07/17 00:06:54
Document why this exists as working on a SymKey (1
eroman
2014/07/17 20:37:25
Done. Documented as:
// The result of unwrapping
|
| + PK11SymKey* wrapping_key, |
| + CK_MECHANISM_TYPE mechanism, |
| + CK_FLAGS flags, |
| + crypto::ScopedPK11SymKey* unwrapped_key) { |
| + DCHECK_GE(wrapped_key_data.byte_length(), 24u); |
| + DCHECK_EQ(wrapped_key_data.byte_length() % 8, 0u); |
| + |
| + SECItem iv_item = MakeSECItemForBuffer(CryptoData(kAesIv, sizeof(kAesIv))); |
| + crypto::ScopedSECItem param_item( |
| + PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP, &iv_item)); |
| + if (!param_item) |
| + return Status::ErrorUnexpected(); |
| + |
| + SECItem cipher_text = MakeSECItemForBuffer(wrapped_key_data); |
| + |
| + // The plaintext length is always 64 bits less than the data size. |
| + const unsigned int plaintext_length = wrapped_key_data.byte_length() - 8; |
| + |
| +#if defined(USE_NSS) |
| + // Part of workaround for |
| + // https://bugzilla.mozilla.org/show_bug.cgi?id=981170. See the explanation |
| + // later in this function. |
| + PORT_SetError(0); |
| +#endif |
| + |
| + crypto::ScopedPK11SymKey new_key( |
| + PK11_UnwrapSymKeyWithFlags(wrapping_key, |
| + CKM_NSS_AES_KEY_WRAP, |
| + param_item.get(), |
| + &cipher_text, |
| + mechanism, |
| + CKA_FLAGS_ONLY, |
| + plaintext_length, |
| + flags)); |
| + |
| + // TODO(padolph): Use NSS PORT_GetError() and friends to report a more |
| + // accurate error, providing if doesn't leak any information to web pages |
| + // about other web crypto users, key details, etc. |
| + if (!new_key) |
| + return Status::OperationError(); |
| + |
| +#if defined(USE_NSS) |
| + // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=981170 |
| + // which was fixed in NSS 3.16.0. |
| + // If unwrap fails, NSS nevertheless returns a valid-looking PK11SymKey, |
| + // with a reasonable length but with key data pointing to uninitialized |
| + // memory. |
| + // To understand this workaround see the fix for 981170: |
| + // https://hg.mozilla.org/projects/nss/rev/753bb69e543c |
| + if (!NSS_VersionCheck("3.16") && PORT_GetError() == SEC_ERROR_BAD_DATA) |
| + return Status::OperationError(); |
| +#endif |
| + |
| + *unwrapped_key = new_key.Pass(); |
| + return Status::Success(); |
| +} |
| + |
| +Status WrapSymKeyAesKw(PK11SymKey* key, |
| + PK11SymKey* wrapping_key, |
| + std::vector<uint8>* buffer) { |
| + // The data size must be at least 16 bytes and a multiple of 8 bytes. |
| + // RFC 3394 does not specify a maximum allowed data length, but since only |
| + // keys are being wrapped in this application (which are small), a reasonable |
| + // max limit is whatever will fit into an unsigned. For the max size test, |
| + // note that AES Key Wrap always adds 8 bytes to the input data size. |
| + const unsigned int input_length = PK11_GetKeyLength(key); |
| + DCHECK_GE(input_length, 16u); |
| + DCHECK((input_length % 8) == 0); |
| + if (input_length > UINT_MAX - 8) |
| + return Status::ErrorDataTooLarge(); |
| + |
| + SECItem iv_item = MakeSECItemForBuffer(CryptoData(kAesIv, sizeof(kAesIv))); |
| + crypto::ScopedSECItem param_item( |
| + PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP, &iv_item)); |
| + if (!param_item) |
| + return Status::ErrorUnexpected(); |
| + |
| + const unsigned int output_length = input_length + 8; |
| + buffer->resize(output_length); |
| + SECItem wrapped_key_item = MakeSECItemForBuffer(CryptoData(*buffer)); |
| + |
| + if (SECSuccess != PK11_WrapSymKey(CKM_NSS_AES_KEY_WRAP, |
| + param_item.get(), |
| + wrapping_key, |
| + key, |
| + &wrapped_key_item)) { |
| + return Status::OperationError(); |
| + } |
| + if (output_length != wrapped_key_item.len) |
| + return Status::ErrorUnexpected(); |
| + |
| + return Status::Success(); |
| +} |
| + |
| +class AesKwCryptoAlgorithmNss : public AesAlgorithm { |
| + public: |
| + AesKwCryptoAlgorithmNss() |
| + : AesAlgorithm( |
| + CKM_NSS_AES_KEY_WRAP, |
| + CKF_WRAP | CKF_WRAP, |
| + blink::WebCryptoKeyUsageWrapKey | blink::WebCryptoKeyUsageUnwrapKey, |
| + "KW") {} |
| + |
| + virtual Status Encrypt(const blink::WebCryptoAlgorithm& algorithm, |
| + const blink::WebCryptoKey& wrapping_key, |
| + const CryptoData& data, |
| + std::vector<uint8>* buffer) const OVERRIDE { |
| + if (data.byte_length() < 16) |
| + return Status::ErrorDataTooSmall(); |
| + if (data.byte_length() % 8) |
| + return Status::ErrorInvalidAesKwDataLength(); |
| + |
| + // Due to limitations in the NSS API for the AES-KW algorithm, |data| must |
| + // be temporarily viewed as a symmetric key to be wrapped (encrypted). |
| + SECItem data_item = MakeSECItemForBuffer(data); |
| + crypto::ScopedPK11Slot slot(PK11_GetInternalSlot()); |
| + crypto::ScopedPK11SymKey data_as_sym_key( |
| + PK11_ImportSymKey(slot.get(), |
| + CKK_GENERIC_SECRET, |
| + PK11_OriginUnwrap, |
| + CKA_SIGN, |
| + &data_item, |
| + NULL)); |
| + if (!data_as_sym_key) |
| + return Status::OperationError(); |
| + |
| + return WrapSymKeyAesKw( |
| + data_as_sym_key.get(), SymKeyNss::Cast(wrapping_key)->key(), buffer); |
| + } |
| + |
| + virtual Status Decrypt(const blink::WebCryptoAlgorithm& algorithm, |
| + const blink::WebCryptoKey& wrapping_key, |
| + const CryptoData& data, |
| + std::vector<uint8>* buffer) const OVERRIDE { |
| + if (data.byte_length() < 24) |
| + return Status::ErrorDataTooSmall(); |
| + if (data.byte_length() % 8) |
| + return Status::ErrorInvalidAesKwDataLength(); |
| + |
| + // Due to limitations in the NSS API for the AES-KW algorithm, |data| must |
| + // be |
| + // temporarily viewed as a symmetric key to be unwrapped (decrypted). |
| + crypto::ScopedPK11SymKey decrypted; |
| + Status status = DoUnwrapSymKeyAesKw(data, |
| + SymKeyNss::Cast(wrapping_key)->key(), |
| + CKK_GENERIC_SECRET, |
| + 0, |
| + &decrypted); |
| + if (status.IsError()) |
| + return status; |
| + |
| + // Once the decrypt is complete, extract the resultant raw bytes from NSS |
| + // and return them to the caller. |
| + if (PK11_ExtractKeyValue(decrypted.get()) != SECSuccess) |
| + return Status::OperationError(); |
| + const SECItem* const key_data = PK11_GetKeyData(decrypted.get()); |
| + if (!key_data) |
| + return Status::OperationError(); |
| + buffer->assign(key_data->data, key_data->data + key_data->len); |
| + |
| + return Status::Success(); |
| + } |
| +}; |
| + |
| +} // namespace |
| + |
| +AlgorithmImplementation* CreatePlatformAesKwImplementation() { |
| + return new AesKwCryptoAlgorithmNss; |
| +} |
| + |
| +} // namespace webcrypto |
| + |
| +} // namespace content |