diff options
Diffstat (limited to 'content/child/webcrypto/nss/aes_gcm_nss.cc')
-rw-r--r-- | content/child/webcrypto/nss/aes_gcm_nss.cc | 188 |
1 files changed, 188 insertions, 0 deletions
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 0000000..a745556 --- /dev/null +++ b/content/child/webcrypto/nss/aes_gcm_nss.cc @@ -0,0 +1,188 @@ +// 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. +// +// 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 = GetAesGcmTagLengthInBits(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(); + 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 encrypt_or_decrypt_func = + (mode == ENCRYPT) ? NssRuntimeSupport::Get()->pk11_encrypt_func() + : NssRuntimeSupport::Get()->pk11_decrypt_func(); + + unsigned int output_len = 0; + SECStatus result = encrypt_or_decrypt_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 |