Chromium Code Reviews| Index: content/child/webcrypto/nss/aes_gcm_nss.cc |
| diff --git a/content/child/webcrypto/nss/aes_gcm_nss.cc b/content/child/webcrypto/nss/aes_gcm_nss.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..5cd3890d29f51f2d18678194b68dc99d97e81349 |
| --- /dev/null |
| +++ b/content/child/webcrypto/nss/aes_gcm_nss.cc |
| @@ -0,0 +1,194 @@ |
| +// 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 "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/util_nss.h" |
| +#include "content/child/webcrypto/status.h" |
| +#include "content/child/webcrypto/webcrypto_util.h" |
| +#include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h" |
| + |
| +// At the time of this writing: |
| +// * Windows and Mac builds ship with their own copy of NSS (3.15+) |
| +// * Linux builds use the system's libnss, which is 3.14 on Debian (but 3.15+ |
| +// on other distros). |
| +// |
| +// Since NSS provides AES-GCM support starting in version 3.15, it may be |
| +// unavailable for Linux Chrome users. |
| +// |
| +// * !defined(CKM_AES_GCM) |
| +// |
| +// This means that at build time, the NSS header pkcs11t.h is older than |
| +// 3.15. However at runtime support may be present. |
| +// |
| +// * !defined(USE_NSS) |
| +// |
| +// This means that Chrome is being built with an embedded copy of NSS, |
| +// which can be assumed to be >= 3.15. On the other hand if USE_NSS is |
| +// defined, it also implies running on Linux. |
| +// |
| +// TODO(eroman): Simplify this once 3.15+ is required by Linux builds. |
| +#if !defined(CKM_AES_GCM) |
| +#define CKM_AES_GCM 0x00001087 |
| + |
| +struct CK_GCM_PARAMS { |
| + CK_BYTE_PTR pIv; |
| + CK_ULONG ulIvLen; |
| + CK_BYTE_PTR pAAD; |
| + CK_ULONG ulAADLen; |
| + CK_ULONG ulTagBits; |
| +}; |
| +#endif // !defined(CKM_AES_GCM) |
| + |
| +namespace content { |
| + |
| +namespace webcrypto { |
| + |
| +namespace { |
| + |
| +Status NssSupportsAesGcm() { |
| + if (NssRuntimeSupport::Get()->IsAesGcmSupported()) |
| + return Status::Success(); |
| + return Status::ErrorUnsupported( |
| + "NSS version doesn't support AES-GCM. Try using version 3.15 or later"); |
| +} |
| + |
| +// Helper to either encrypt or decrypt for AES-GCM. The result of encryption is |
| +// the concatenation of the ciphertext and the authentication tag. Similarly, |
| +// this is the expectation for the input to decryption. |
| +Status AesGcmEncryptDecrypt(EncryptOrDecrypt mode, |
| + const blink::WebCryptoAlgorithm& algorithm, |
| + const blink::WebCryptoKey& key, |
| + const CryptoData& data, |
| + std::vector<uint8>* buffer) { |
| + Status status = NssSupportsAesGcm(); |
| + if (status.IsError()) |
| + return status; |
| + |
| + PK11SymKey* sym_key = SymKeyNss::Cast(key)->key(); |
| + const blink::WebCryptoAesGcmParams* params = algorithm.aesGcmParams(); |
| + if (!params) |
| + return Status::ErrorUnexpected(); |
| + |
| + unsigned int tag_length_bits; |
| + status = GetAesGcmTagLength(params, &tag_length_bits); |
| + if (status.IsError()) |
| + return status; |
| + unsigned int tag_length_bytes = tag_length_bits / 8; |
| + |
| + CryptoData iv(params->iv()); |
| + CryptoData additional_data(params->optionalAdditionalData()); |
| + |
| + CK_GCM_PARAMS gcm_params = {0}; |
| + gcm_params.pIv = const_cast<unsigned char*>(iv.bytes()); |
| + gcm_params.ulIvLen = iv.byte_length(); |
| + |
| + gcm_params.pAAD = const_cast<unsigned char*>(additional_data.bytes()); |
| + gcm_params.ulAADLen = additional_data.byte_length(); |
| + |
| + gcm_params.ulTagBits = tag_length_bits; |
| + |
| + SECItem param; |
| + param.type = siBuffer; |
| + param.data = reinterpret_cast<unsigned char*>(&gcm_params); |
| + param.len = sizeof(gcm_params); |
| + |
| + unsigned int buffer_size = 0; |
| + |
| + // Calculate the output buffer size. |
| + if (mode == ENCRYPT) { |
| + // TODO(eroman): This is ugly, abstract away the safe integer arithmetic. |
| + if (data.byte_length() > (UINT_MAX - tag_length_bytes)) |
| + return Status::ErrorDataTooLarge(); |
|
Ryan Sleevi
2014/07/17 00:06:54
base::CheckedNumeric<> for a follow-up if you want
eroman
2014/07/17 20:37:25
Will follow up through: https://code.google.com/p/
|
| + buffer_size = data.byte_length() + tag_length_bytes; |
| + } else { |
| + // TODO(eroman): In theory the buffer allocated for the plain text should be |
| + // sized as |data.byte_length() - tag_length_bytes|. |
| + // |
| + // However NSS has a bug whereby it will fail if the output buffer size is |
| + // not at least as large as the ciphertext: |
| + // |
| + // https://bugzilla.mozilla.org/show_bug.cgi?id=%20853674 |
| + // |
| + // From the analysis of that bug it looks like it might be safe to pass a |
| + // correctly sized buffer but lie about its size. Since resizing the |
| + // WebCryptoArrayBuffer is expensive that hack may be worth looking into. |
| + buffer_size = data.byte_length(); |
| + } |
| + |
| + buffer->resize(buffer_size); |
| + unsigned char* buffer_data = Uint8VectorStart(buffer); |
| + |
| + PK11_EncryptDecryptFunction func = |
|
Ryan Sleevi
2014/07/17 00:06:53
pedantry: encrypt_or_decrypt_function?
eroman
2014/07/17 20:37:25
Done.
|
| + (mode == ENCRYPT) ? NssRuntimeSupport::Get()->pk11_encrypt_func() |
| + : NssRuntimeSupport::Get()->pk11_decrypt_func(); |
| + |
| + unsigned int output_len = 0; |
| + SECStatus result = func(sym_key, |
| + CKM_AES_GCM, |
| + ¶m, |
| + buffer_data, |
| + &output_len, |
| + buffer->size(), |
| + data.bytes(), |
| + data.byte_length()); |
| + |
| + if (result != SECSuccess) |
| + return Status::OperationError(); |
| + |
| + // Unfortunately the buffer needs to be shrunk for decryption (see the NSS bug |
| + // above). |
| + buffer->resize(output_len); |
| + |
| + return Status::Success(); |
| +} |
| + |
| +class AesGcmImplementation : public AesAlgorithm { |
| + public: |
| + AesGcmImplementation() : AesAlgorithm(CKM_AES_GCM, "GCM") {} |
| + |
| + virtual Status VerifyKeyUsagesBeforeImportKey( |
| + blink::WebCryptoKeyFormat format, |
| + blink::WebCryptoKeyUsageMask usage_mask) const OVERRIDE { |
| + // Prevent importing AES-GCM keys if it is unavailable. |
| + Status status = NssSupportsAesGcm(); |
| + if (status.IsError()) |
| + return status; |
| + return AesAlgorithm::VerifyKeyUsagesBeforeImportKey(format, usage_mask); |
| + } |
| + |
| + virtual Status VerifyKeyUsagesBeforeGenerateKey( |
| + blink::WebCryptoKeyUsageMask usage_mask) const OVERRIDE { |
| + // Prevent generating AES-GCM keys if it is unavailable. |
| + Status status = NssSupportsAesGcm(); |
| + if (status.IsError()) |
| + return status; |
| + return AesAlgorithm::VerifyKeyUsagesBeforeGenerateKey(usage_mask); |
| + } |
| + |
| + virtual Status Encrypt(const blink::WebCryptoAlgorithm& algorithm, |
| + const blink::WebCryptoKey& key, |
| + const CryptoData& data, |
| + std::vector<uint8>* buffer) const OVERRIDE { |
| + return AesGcmEncryptDecrypt(ENCRYPT, algorithm, key, data, buffer); |
| + } |
| + |
| + virtual Status Decrypt(const blink::WebCryptoAlgorithm& algorithm, |
| + const blink::WebCryptoKey& key, |
| + const CryptoData& data, |
| + std::vector<uint8>* buffer) const OVERRIDE { |
| + return AesGcmEncryptDecrypt(DECRYPT, algorithm, key, data, buffer); |
| + } |
| +}; |
| + |
| +} // namespace |
| + |
| +AlgorithmImplementation* CreatePlatformAesGcmImplementation() { |
| + return new AesGcmImplementation; |
| +} |
| + |
| +} // namespace webcrypto |
| + |
| +} // namespace content |