diff options
author | peter <peter@chromium.org> | 2015-07-03 04:20:18 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-07-03 11:21:07 +0000 |
commit | 9addb016ed7f97ee95afa1352ec17ef0bf9d2f35 (patch) | |
tree | 2941f0c5292f2c1c9b4f1c99798ef1b5a5bea9af | |
parent | 1165ce6280675b42827b9d2a69af6f2ad6367363 (diff) | |
download | chromium_src-9addb016ed7f97ee95afa1352ec17ef0bf9d2f35.zip chromium_src-9addb016ed7f97ee95afa1352ec17ef0bf9d2f35.tar.gz chromium_src-9addb016ed7f97ee95afa1352ec17ef0bf9d2f35.tar.bz2 |
Generalize the NSS PK11_Decrypt/Encrypt methods in //crypto/.
The Quic implementation provides a decrypter and encrypter for
AES128_GCM(-12) with implementations for both BoringSSL and NSS,
where the NSS one has extensive logic for manually computing the
Galois when the system NSS library does not provide the required
functionality.
Since another user of AES128_GCM(-16) is in progress of being
implemented, I'm generalizign the logic to two helper methods in
//crypto/ in order to avoid needing to duplicate all of it.
BUG=486040
Review URL: https://codereview.chromium.org/1199033006
Cr-Commit-Position: refs/heads/master@{#337301}
-rw-r--r-- | crypto/BUILD.gn | 9 | ||||
-rw-r--r-- | crypto/aes_128_gcm_helpers_nss.cc | 374 | ||||
-rw-r--r-- | crypto/aes_128_gcm_helpers_nss.h | 48 | ||||
-rw-r--r-- | crypto/aes_128_gcm_helpers_nss_unittest.cc | 580 | ||||
-rw-r--r-- | crypto/crypto.gyp | 6 | ||||
-rw-r--r-- | crypto/crypto.gypi | 2 | ||||
-rw-r--r-- | net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc | 186 | ||||
-rw-r--r-- | net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc | 181 |
8 files changed, 1026 insertions, 360 deletions
diff --git a/crypto/BUILD.gn b/crypto/BUILD.gn index f84c4d4..9483916 100644 --- a/crypto/BUILD.gn +++ b/crypto/BUILD.gn @@ -10,6 +10,8 @@ component("crypto") { sources = [ "aead_openssl.cc", "aead_openssl.h", + "aes_128_gcm_helpers_nss.cc", + "aes_128_gcm_helpers_nss.h", "apple_keychain.h", "apple_keychain_ios.mm", "apple_keychain_mac.mm", @@ -136,6 +138,8 @@ component("crypto") { if (use_openssl) { # Remove NSS files when using OpenSSL sources -= [ + "aes_128_gcm_helpers_nss.cc", + "aes_128_gcm_helpers_nss.h", "ec_private_key_nss.cc", "ec_signature_creator_nss.cc", "encryptor_nss.cc", @@ -223,6 +227,7 @@ if (false && is_win) { test("crypto_unittests") { sources = [ # Tests. + "aes_128_gcm_helpers_nss_unittest.cc", "curve25519_unittest.cc", "ec_private_key_unittest.cc", "ec_signature_creator_unittest.cc", @@ -253,7 +258,9 @@ test("crypto_unittests") { ] } - if (!use_openssl) { + if (use_openssl) { + sources -= [ "aes_128_gcm_helpers_nss_unittest.cc" ] + } else { sources -= [ "openssl_bio_string_unittest.cc" ] } diff --git a/crypto/aes_128_gcm_helpers_nss.cc b/crypto/aes_128_gcm_helpers_nss.cc new file mode 100644 index 0000000..621f28b --- /dev/null +++ b/crypto/aes_128_gcm_helpers_nss.cc @@ -0,0 +1,374 @@ +// Copyright 2015 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 "crypto/aes_128_gcm_helpers_nss.h" + +#include <pkcs11t.h> +#include <seccomon.h> + +#include "base/lazy_instance.h" +#include "base/macros.h" +#include "crypto/ghash.h" +#include "crypto/scoped_nss_types.h" + +#if defined(USE_NSS_CERTS) +#include <dlfcn.h> +#endif + +namespace crypto { +namespace { + +// Declaration of the prototype both PK11_Decrypt and PK11_Encrypt follow. +using PK11_TransformFunction = SECStatus(PK11SymKey* symKey, + CK_MECHANISM_TYPE mechanism, + SECItem* param, + unsigned char* out, + unsigned int* outLen, + unsigned int maxLen, + const unsigned char* data, + unsigned int dataLen); + +// On Linux, dynamically link against the system version of libnss3.so. In +// order to continue working on systems without up-to-date versions of NSS, +// lookup PK11_Decrypt and PK11_Encrypt with dlsym. +// +// GcmSupportChecker is a singleton which caches the results of runtime symbol +// resolution of these symbols. +class GcmSupportChecker { + public: + PK11_TransformFunction* pk11_decrypt_func() { return pk11_decrypt_func_; } + + PK11_TransformFunction* pk11_encrypt_func() { return pk11_encrypt_func_; } + + private: + friend struct base::DefaultLazyInstanceTraits<GcmSupportChecker>; + + GcmSupportChecker() { +#if !defined(USE_NSS_CERTS) + // Using a bundled version of NSS that is guaranteed to have these symbols. + pk11_decrypt_func_ = PK11_Decrypt; + pk11_encrypt_func_ = PK11_Encrypt; +#else + // Using system NSS libraries and PCKS #11 modules, which may not have the + // necessary functions (PK11_Decrypt and PK11_Encrypt) or mechanism support + // (CKM_AES_GCM). + + // If PK11_Decrypt() and PK11_Encrypt() were successfully resolved, then NSS + // will support AES-GCM directly. This was introduced in NSS 3.15. + pk11_decrypt_func_ = reinterpret_cast<PK11_TransformFunction*>( + dlsym(RTLD_DEFAULT, "PK11_Decrypt")); + pk11_encrypt_func_ = reinterpret_cast<PK11_TransformFunction*>( + dlsym(RTLD_DEFAULT, "PK11_Encrypt")); +#endif + } + + ~GcmSupportChecker() {} + + // |pk11_decrypt_func_| stores the runtime symbol resolution of PK11_Decrypt. + PK11_TransformFunction* pk11_decrypt_func_; + + // |pk11_encrypt_func_| stores the runtime symbol resolution of PK11_Encrypt. + PK11_TransformFunction* pk11_encrypt_func_; + + DISALLOW_COPY_AND_ASSIGN(GcmSupportChecker); +}; + +base::LazyInstance<GcmSupportChecker>::Leaky g_gcm_support_checker = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// Calls PK11_Decrypt if it's available. Otherwise, emulates CKM_AES_GCM using +// CKM_AES_CTR and the GaloisHash class. +SECStatus PK11DecryptHelper(PK11SymKey* key, + CK_MECHANISM_TYPE mechanism, + SECItem* param, + unsigned char* out, + unsigned int* out_len, + unsigned int max_len, + const unsigned char* data, + unsigned int data_len) { + // If PK11_Decrypt() was successfully resolved or if bundled version of NSS is + // being used, then NSS will support AES-GCM directly. + PK11_TransformFunction* pk11_decrypt_func = + g_gcm_support_checker.Get().pk11_decrypt_func(); + + if (pk11_decrypt_func != nullptr) { + return pk11_decrypt_func(key, mechanism, param, out, out_len, max_len, data, + data_len); + } + + // Otherwise, the user has an older version of NSS. Regrettably, NSS 3.14.x + // has a bug in the AES GCM code + // (https://bugzilla.mozilla.org/show_bug.cgi?id=853285), as well as missing + // the PK11_Decrypt function + // (https://bugzilla.mozilla.org/show_bug.cgi?id=854063), both of which are + // resolved in NSS 3.15. + + CHECK_EQ(mechanism, static_cast<CK_MECHANISM_TYPE>(CKM_AES_GCM)); + CHECK_EQ(param->len, sizeof(CK_GCM_PARAMS)); + + const CK_GCM_PARAMS* gcm_params = + reinterpret_cast<CK_GCM_PARAMS*>(param->data); + + const CK_ULONG auth_tag_size = gcm_params->ulTagBits / 8; + + if (gcm_params->ulIvLen != 12u) { + DVLOG(1) << "ulIvLen is not equal to 12"; + PORT_SetError(SEC_ERROR_INPUT_LEN); + return SECFailure; + } + + SECItem my_param = {siBuffer, nullptr, 0}; + + // Step 2. Let H = CIPH_K(128 '0' bits). + unsigned char ghash_key[16] = {0}; + crypto::ScopedPK11Context ctx( + PK11_CreateContextBySymKey(CKM_AES_ECB, CKA_ENCRYPT, key, &my_param)); + if (!ctx) { + DVLOG(1) << "PK11_CreateContextBySymKey failed"; + return SECFailure; + } + int output_len; + if (PK11_CipherOp(ctx.get(), ghash_key, &output_len, sizeof(ghash_key), + ghash_key, sizeof(ghash_key)) != SECSuccess) { + DVLOG(1) << "PK11_CipherOp failed"; + return SECFailure; + } + + if (PK11_Finalize(ctx.get()) != SECSuccess) { + DVLOG(1) << "PK11_Finalize failed"; + return SECFailure; + } + + if (output_len != sizeof(ghash_key)) { + DVLOG(1) << "Wrong output length"; + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + // Step 3. If len(IV)=96, then let J0 = IV || 31 '0' bits || 1. + CK_AES_CTR_PARAMS ctr_params = {0}; + ctr_params.ulCounterBits = 32; + memcpy(ctr_params.cb, gcm_params->pIv, gcm_params->ulIvLen); + ctr_params.cb[12] = 0; + ctr_params.cb[13] = 0; + ctr_params.cb[14] = 0; + ctr_params.cb[15] = 1; + + my_param.type = siBuffer; + my_param.data = reinterpret_cast<unsigned char*>(&ctr_params); + my_param.len = sizeof(ctr_params); + + ctx.reset( + PK11_CreateContextBySymKey(CKM_AES_CTR, CKA_ENCRYPT, key, &my_param)); + if (!ctx) { + DVLOG(1) << "PK11_CreateContextBySymKey failed"; + return SECFailure; + } + + // Step 6. Calculate the encryption mask of GCTR_K(J0, ...). + unsigned char tag_mask[16] = {0}; + if (PK11_CipherOp(ctx.get(), tag_mask, &output_len, sizeof(tag_mask), + tag_mask, sizeof(tag_mask)) != SECSuccess) { + DVLOG(1) << "PK11_CipherOp failed"; + return SECFailure; + } + if (output_len != sizeof(tag_mask)) { + DVLOG(1) << "Wrong output length"; + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + if (data_len < auth_tag_size) { + PORT_SetError(SEC_ERROR_INPUT_LEN); + return SECFailure; + } + + // The const_cast for |data| can be removed if system NSS libraries are + // NSS 3.14.1 or later (NSS bug + // https://bugzilla.mozilla.org/show_bug.cgi?id=808218). + if (PK11_CipherOp(ctx.get(), out, &output_len, max_len, + const_cast<unsigned char*>(data), + data_len - auth_tag_size) != SECSuccess) { + DVLOG(1) << "PK11_CipherOp failed"; + return SECFailure; + } + + if (PK11_Finalize(ctx.get()) != SECSuccess) { + DVLOG(1) << "PK11_Finalize failed"; + return SECFailure; + } + + if (static_cast<unsigned int>(output_len) != data_len - auth_tag_size) { + DVLOG(1) << "Wrong output length"; + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + crypto::GaloisHash ghash(ghash_key); + ghash.UpdateAdditional(gcm_params->pAAD, gcm_params->ulAADLen); + ghash.UpdateCiphertext(data, output_len); + unsigned char auth_tag[auth_tag_size]; + ghash.Finish(auth_tag, auth_tag_size); + for (unsigned int i = 0; i < auth_tag_size; i++) { + auth_tag[i] ^= tag_mask[i]; + } + + if (NSS_SecureMemcmp(auth_tag, data + output_len, auth_tag_size) != 0) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + + *out_len = output_len; + return SECSuccess; +} + +// Calls PK11_Encrypt if it's available. Otherwise, emulates CKM_AES_GCM using +// CKM_AES_CTR and the GaloisHash class. +SECStatus PK11EncryptHelper(PK11SymKey* key, + CK_MECHANISM_TYPE mechanism, + SECItem* param, + unsigned char* out, + unsigned int* out_len, + unsigned int max_len, + const unsigned char* data, + unsigned int data_len) { + // If PK11_Encrypt() was successfully resolved or if bundled version of NSS is + // being used, then NSS will support AES-GCM directly. + PK11_TransformFunction* pk11_encrypt_func = + g_gcm_support_checker.Get().pk11_encrypt_func(); + + if (pk11_encrypt_func != nullptr) { + return pk11_encrypt_func(key, mechanism, param, out, out_len, max_len, data, + data_len); + } + + // Otherwise, the user has an older version of NSS. Regrettably, NSS 3.14.x + // has a bug in the AES GCM code + // (https://bugzilla.mozilla.org/show_bug.cgi?id=853285), as well as missing + // the PK11_Encrypt function + // (https://bugzilla.mozilla.org/show_bug.cgi?id=854063), both of which are + // resolved in NSS 3.15. + + CHECK_EQ(mechanism, static_cast<CK_MECHANISM_TYPE>(CKM_AES_GCM)); + CHECK_EQ(param->len, sizeof(CK_GCM_PARAMS)); + + const CK_GCM_PARAMS* gcm_params = + reinterpret_cast<CK_GCM_PARAMS*>(param->data); + + const CK_ULONG auth_tag_size = gcm_params->ulTagBits / 8; + + if (max_len < auth_tag_size) { + DVLOG(1) << "max_len is less than kAuthTagSize"; + PORT_SetError(SEC_ERROR_OUTPUT_LEN); + return SECFailure; + } + + if (gcm_params->ulIvLen != 12u) { + DVLOG(1) << "ulIvLen is not equal to 12"; + PORT_SetError(SEC_ERROR_INPUT_LEN); + return SECFailure; + } + + SECItem my_param = {siBuffer, nullptr, 0}; + + // Step 1. Let H = CIPH_K(128 '0' bits). + unsigned char ghash_key[16] = {0}; + crypto::ScopedPK11Context ctx( + PK11_CreateContextBySymKey(CKM_AES_ECB, CKA_ENCRYPT, key, &my_param)); + if (!ctx) { + DVLOG(1) << "PK11_CreateContextBySymKey failed"; + return SECFailure; + } + int output_len; + if (PK11_CipherOp(ctx.get(), ghash_key, &output_len, sizeof(ghash_key), + ghash_key, sizeof(ghash_key)) != SECSuccess) { + DVLOG(1) << "PK11_CipherOp failed"; + return SECFailure; + } + + if (PK11_Finalize(ctx.get()) != SECSuccess) { + DVLOG(1) << "PK11_Finalize failed"; + return SECFailure; + } + + if (output_len != sizeof(ghash_key)) { + DVLOG(1) << "Wrong output length"; + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + // Step 2. If len(IV)=96, then let J0 = IV || 31 '0' bits || 1. + CK_AES_CTR_PARAMS ctr_params = {0}; + ctr_params.ulCounterBits = 32; + memcpy(ctr_params.cb, gcm_params->pIv, gcm_params->ulIvLen); + ctr_params.cb[12] = 0; + ctr_params.cb[13] = 0; + ctr_params.cb[14] = 0; + ctr_params.cb[15] = 1; + + my_param.type = siBuffer; + my_param.data = reinterpret_cast<unsigned char*>(&ctr_params); + my_param.len = sizeof(ctr_params); + + ctx.reset( + PK11_CreateContextBySymKey(CKM_AES_CTR, CKA_ENCRYPT, key, &my_param)); + if (!ctx) { + DVLOG(1) << "PK11_CreateContextBySymKey failed"; + return SECFailure; + } + + // Step 6. Calculate the encryption mask of GCTR_K(J0, ...). + unsigned char tag_mask[16] = {0}; + if (PK11_CipherOp(ctx.get(), tag_mask, &output_len, sizeof(tag_mask), + tag_mask, sizeof(tag_mask)) != SECSuccess) { + DVLOG(1) << "PK11_CipherOp failed"; + return SECFailure; + } + if (output_len != sizeof(tag_mask)) { + DVLOG(1) << "Wrong output length"; + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + // The const_cast for |data| can be removed if system NSS libraries are + // NSS 3.14.1 or later (NSS bug + // https://bugzilla.mozilla.org/show_bug.cgi?id=808218). + if (PK11_CipherOp(ctx.get(), out, &output_len, max_len, + const_cast<unsigned char*>(data), data_len) != SECSuccess) { + DVLOG(1) << "PK11_CipherOp failed"; + return SECFailure; + } + + if (PK11_Finalize(ctx.get()) != SECSuccess) { + DVLOG(1) << "PK11_Finalize failed"; + return SECFailure; + } + + if (static_cast<unsigned int>(output_len) != data_len) { + DVLOG(1) << "Wrong output length"; + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + if ((max_len - auth_tag_size) < static_cast<unsigned int>(output_len)) { + DVLOG(1) << "(max_len - kAuthTagSize) is less than output_len"; + PORT_SetError(SEC_ERROR_OUTPUT_LEN); + return SECFailure; + } + + crypto::GaloisHash ghash(ghash_key); + ghash.UpdateAdditional(gcm_params->pAAD, gcm_params->ulAADLen); + ghash.UpdateCiphertext(out, output_len); + ghash.Finish(out + output_len, auth_tag_size); + for (unsigned int i = 0; i < auth_tag_size; i++) { + out[output_len + i] ^= tag_mask[i]; + } + + *out_len = output_len + auth_tag_size; + return SECSuccess; +} + +} // namespace crypto diff --git a/crypto/aes_128_gcm_helpers_nss.h b/crypto/aes_128_gcm_helpers_nss.h new file mode 100644 index 0000000..dadc56e --- /dev/null +++ b/crypto/aes_128_gcm_helpers_nss.h @@ -0,0 +1,48 @@ +// Copyright 2015 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. + +#ifndef CRYPTO_AES_128_GCM_HELPERS_NSS_H_ +#define CRYPTO_AES_128_GCM_HELPERS_NSS_H_ + +#include <pk11pub.h> +#include <secerr.h> + +#include "crypto/crypto_export.h" + +namespace crypto { + +// When using the CKM_AES_GCM mechanism, one must consider that the mechanism +// had a bug in NSS 3.14.x (https://bugzilla.mozilla.org/show_bug.cgi?id=853285) +// which also lacks the PK11_Decrypt and PK11_Encrypt functions. +// (https://bugzilla.mozilla.org/show_bug.cgi?id=854063) +// +// While both these bugs were resolved in NSS 3.15, certain builds of Chromium +// may still be loading older versions of NSS as the system libraries. These +// helper methods emulate support by using CKM_AES_CTR and the GaloisHash. + +// Helper function for using PK11_Decrypt. |mechanism| must be set to +// CKM_AES_GCM for this method. +CRYPTO_EXPORT SECStatus PK11DecryptHelper(PK11SymKey* key, + CK_MECHANISM_TYPE mechanism, + SECItem* param, + unsigned char* out, + unsigned int* out_len, + unsigned int max_len, + const unsigned char* data, + unsigned int data_len); + +// Helper function for using PK11_Encrypt. |mechanism| must be set to +// CKM_AES_GCM for this method. +CRYPTO_EXPORT SECStatus PK11EncryptHelper(PK11SymKey* key, + CK_MECHANISM_TYPE mechanism, + SECItem* param, + unsigned char* out, + unsigned int* out_len, + unsigned int max_len, + const unsigned char* data, + unsigned int data_len); + +} // namespace crypto + +#endif // CRYPTO_AES_128_GCM_HELPERS_NSS_H_ diff --git a/crypto/aes_128_gcm_helpers_nss_unittest.cc b/crypto/aes_128_gcm_helpers_nss_unittest.cc new file mode 100644 index 0000000..938e909 --- /dev/null +++ b/crypto/aes_128_gcm_helpers_nss_unittest.cc @@ -0,0 +1,580 @@ +// Copyright 2015 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 "crypto/aes_128_gcm_helpers_nss.h" + +#include <pk11pub.h> +#include <secerr.h> +#include <string> + +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "crypto/nss_util.h" +#include "crypto/random.h" +#include "crypto/scoped_nss_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace crypto { + +namespace { + +// The AES GCM test vectors come from the gcmDecrypt128.rsp and +// gcmEncryptExtIV128.rsp files downloaded from +// http://csrc.nist.gov/groups/STM/cavp/index.html on 2013-02-01. The test +// vectors in that file look like this: +// +// [Keylen = 128] +// [IVlen = 96] +// [PTlen = 0] +// [AADlen = 0] +// [Taglen = 128] +// +// Count = 0 +// Key = cf063a34d4a9a76c2c86787d3f96db71 +// IV = 113b9785971864c83b01c787 +// CT = +// AAD = +// Tag = 72ac8493e3a5228b5d130a69d2510e42 +// PT = +// +// Count = 1 +// Key = a49a5e26a2f8cb63d05546c2a62f5343 +// IV = 907763b19b9b4ab6bd4f0281 +// CT = +// AAD = +// Tag = a2be08210d8c470a8df6e8fbd79ec5cf +// FAIL +// +// ... +// +// These files are huge (2.6 MB and 2.8 MB), so this file contains just a +// selection of test vectors. + +// Describes a group of test vectors that all have a given key length, IV +// length, plaintext length, AAD length, and tag length. +struct TestGroupInfo { + size_t key_len; + size_t iv_len; + size_t input_len; + size_t aad_len; + size_t tag_len; +}; + +// Each test vector consists of six strings of lowercase hexadecimal digits. +// The strings may be empty (zero length). A test vector with a NULL |key| +// marks the end of an array of test vectors. +struct TestVector { + // Input: + const char* key; + const char* iv; + const char* input; + const char* aad; + const char* tag; + + // Expected output: + const char* output; // An empty string "" means decryption or encryption + // succeeded and the plaintext is zero-length. NULL means + // that the decryption or encryption failed. +}; + +const TestGroupInfo test_group_info[] = { + {128, 96, 0, 0, 128}, + {128, 96, 0, 128, 128}, + {128, 96, 128, 0, 128}, + {128, 96, 408, 160, 128}, + {128, 96, 408, 720, 128}, + {128, 96, 104, 0, 128}, +}; + +const TestVector decryption_test_group_0[] = { + {"cf063a34d4a9a76c2c86787d3f96db71", + "113b9785971864c83b01c787", + "", + "", + "72ac8493e3a5228b5d130a69d2510e42", + ""}, + { + "a49a5e26a2f8cb63d05546c2a62f5343", + "907763b19b9b4ab6bd4f0281", + "", + "", + "a2be08210d8c470a8df6e8fbd79ec5cf", + NULL // FAIL + }, + {NULL}}; + +const TestVector decryption_test_group_1[] = { + { + "d1f6af919cde85661208bdce0c27cb22", + "898c6929b435017bf031c3c5", + "", + "7c5faa40e636bbc91107e68010c92b9f", + "ae45f11777540a2caeb128be8092468a", + NULL // FAIL + }, + {"2370e320d4344208e0ff5683f243b213", + "04dbb82f044d30831c441228", + "", + "d43a8e5089eea0d026c03a85178b27da", + "2a049c049d25aa95969b451d93c31c6e", + ""}, + {NULL}}; + +const TestVector decryption_test_group_2[] = { + {"e98b72a9881a84ca6b76e0f43e68647a", + "8b23299fde174053f3d652ba", + "5a3c1cf1985dbb8bed818036fdd5ab42", + "", + "23c7ab0f952b7091cd324835043b5eb5", + "28286a321293253c3e0aa2704a278032"}, + {"33240636cd3236165f1a553b773e728e", + "17c4d61493ecdc8f31700b12", + "47bb7e23f7bdfe05a8091ac90e4f8b2e", + "", + "b723c70e931d9785f40fd4ab1d612dc9", + "95695a5b12f2870b9cc5fdc8f218a97d"}, + { + "5164df856f1e9cac04a79b808dc5be39", + "e76925d5355e0584ce871b2b", + "0216c899c88d6e32c958c7e553daa5bc", + "", + "a145319896329c96df291f64efbe0e3a", + NULL // FAIL + }, + {NULL}}; + +const TestVector decryption_test_group_3[] = { + {"af57f42c60c0fc5a09adb81ab86ca1c3", + "a2dc01871f37025dc0fc9a79", + "b9a535864f48ea7b6b1367914978f9bfa087d854bb0e269bed8d279d2eea1210e48947" + "338b22f9bad09093276a331e9c79c7f4", + "41dc38988945fcb44faf2ef72d0061289ef8efd8", + "4f71e72bde0018f555c5adcce062e005", + "3803a0727eeb0ade441e0ec107161ded2d425ec0d102f21f51bf2cf9947c7ec4aa7279" + "5b2f69b041596e8817d0a3c16f8fadeb"}, + {"ebc753e5422b377d3cb64b58ffa41b61", + "2e1821efaced9acf1f241c9b", + "069567190554e9ab2b50a4e1fbf9c147340a5025fdbd201929834eaf6532325899ccb9" + "f401823e04b05817243d2142a3589878", + "b9673412fd4f88ba0e920f46dd6438ff791d8eef", + "534d9234d2351cf30e565de47baece0b", + "39077edb35e9c5a4b1e4c2a6b9bb1fce77f00f5023af40333d6d699014c2bcf4209c18" + "353a18017f5b36bfc00b1f6dcb7ed485"}, + { + "52bdbbf9cf477f187ec010589cb39d58", + "d3be36d3393134951d324b31", + "700188da144fa692cf46e4a8499510a53d90903c967f7f13e8a1bd8151a74adc4fe63e" + "32b992760b3a5f99e9a47838867000a9", + "93c4fc6a4135f54d640b0c976bf755a06a292c33", + "8ca4e38aa3dfa6b1d0297021ccf3ea5f", + NULL // FAIL + }, + {NULL}}; + +const TestVector decryption_test_group_4[] = { + {"da2bb7d581493d692380c77105590201", + "44aa3e7856ca279d2eb020c6", + "9290d430c9e89c37f0446dbd620c9a6b34b1274aeb6f911f75867efcf95b6feda69f1a" + "f4ee16c761b3c9aeac3da03aa9889c88", + "4cd171b23bddb3a53cdf959d5c1710b481eb3785a90eb20a2345ee00d0bb7868c367ab" + "12e6f4dd1dee72af4eee1d197777d1d6499cc541f34edbf45cda6ef90b3c024f9272d7" + "2ec1909fb8fba7db88a4d6f7d3d925980f9f9f72", + "9e3ac938d3eb0cadd6f5c9e35d22ba38", + "9bbf4c1a2742f6ac80cb4e8a052e4a8f4f07c43602361355b717381edf9fabd4cb7e3a" + "d65dbd1378b196ac270588dd0621f642"}, + {"d74e4958717a9d5c0e235b76a926cae8", + "0b7471141e0c70b1995fd7b1", + "e701c57d2330bf066f9ff8cf3ca4343cafe4894651cd199bdaaa681ba486b4a65c5a22" + "b0f1420be29ea547d42c713bc6af66aa", + "4a42b7aae8c245c6f1598a395316e4b8484dbd6e64648d5e302021b1d3fa0a38f46e22" + "bd9c8080b863dc0016482538a8562a4bd0ba84edbe2697c76fd039527ac179ec5506cf" + "34a6039312774cedebf4961f3978b14a26509f96", + "e192c23cb036f0b31592989119eed55d", + "840d9fb95e32559fb3602e48590280a172ca36d9b49ab69510f5bd552bfab7a306f85f" + "f0a34bc305b88b804c60b90add594a17"}, + { + "1986310c725ac94ecfe6422e75fc3ee7", + "93ec4214fa8e6dc4e3afc775", + "b178ec72f85a311ac4168f42a4b2c23113fbea4b85f4b9dabb74e143eb1b8b0a361e02" + "43edfd365b90d5b325950df0ada058f9", + "e80b88e62c49c958b5e0b8b54f532d9ff6aa84c8a40132e93e55b59fc24e8decf28463" + "139f155d1e8ce4ee76aaeefcd245baa0fc519f83a5fb9ad9aa40c4b21126013f576c42" + "72c2cb136c8fd091cc4539877a5d1e72d607f960", + "8b347853f11d75e81e8a95010be81f17", + NULL // FAIL + }, + {NULL}}; + +const TestVector decryption_test_group_5[] = { + {"387218b246c1a8257748b56980e50c94", + "dd7e014198672be39f95b69d", + "cdba9e73eaf3d38eceb2b04a8d", + "", + "ecf90f4a47c9c626d6fb2c765d201556", + "48f5b426baca03064554cc2b30"}, + {"294de463721e359863887c820524b3d4", + "3338b35c9d57a5d28190e8c9", + "2f46634e74b8e4c89812ac83b9", + "", + "dabd506764e68b82a7e720aa18da0abe", + "46a2e55c8e264df211bd112685"}, + {"28ead7fd2179e0d12aa6d5d88c58c2dc", + "5055347f18b4d5add0ae5c41", + "142d8210c3fb84774cdbd0447a", + "", + "5fd321d9cdb01952dc85f034736c2a7d", + "3b95b981086ee73cc4d0cc1422"}, + { + "7d7b6c988137b8d470c57bf674a09c87", + "9edf2aa970d016ac962e1fd8", + "a85b66c3cb5eab91d5bdc8bc0e", + "", + "dc054efc01f3afd21d9c2484819f569a", + NULL // FAIL + }, + {NULL}}; + +const TestVector encryption_test_group_0[] = { + {"11754cd72aec309bf52f7687212e8957", + "3c819d9a9bed087615030b65", + "", + "", + "250327c674aaf477aef2675748cf6971", + ""}, + {"ca47248ac0b6f8372a97ac43508308ed", + "ffd2b598feabc9019262d2be", + "", + "", + "60d20404af527d248d893ae495707d1a", + ""}, + {NULL}}; + +const TestVector encryption_test_group_1[] = { + {"77be63708971c4e240d1cb79e8d77feb", + "e0e00f19fed7ba0136a797f3", + "", + "7a43ec1d9c0a5a78a0b16533a6213cab", + "209fcc8d3675ed938e9c7166709dd946", + ""}, + {"7680c5d3ca6154758e510f4d25b98820", + "f8f105f9c3df4965780321f8", + "", + "c94c410194c765e3dcc7964379758ed3", + "94dca8edfcf90bb74b153c8d48a17930", + ""}, + {NULL}}; + +const TestVector encryption_test_group_2[] = { + {"7fddb57453c241d03efbed3ac44e371c", + "ee283a3fc75575e33efd4887", + "d5de42b461646c255c87bd2962d3b9a2", + "", + "b36d1df9b9d5e596f83e8b7f52971cb3", + "2ccda4a5415cb91e135c2a0f78c9b2fd"}, + {"ab72c77b97cb5fe9a382d9fe81ffdbed", + "54cc7dc2c37ec006bcc6d1da", + "007c5e5b3e59df24a7c355584fc1518d", + "", + "2b4401346697138c7a4891ee59867d0c", + "0e1bde206a07a9c2c1b65300f8c64997"}, + {NULL}}; + +const TestVector encryption_test_group_3[] = { + {"fe47fcce5fc32665d2ae399e4eec72ba", + "5adb9609dbaeb58cbd6e7275", + "7c0e88c88899a779228465074797cd4c2e1498d259b54390b85e3eef1c02df60e743f1" + "b840382c4bccaf3bafb4ca8429bea063", + "88319d6e1d3ffa5f987199166c8a9b56c2aeba5a", + "291ef1982e4defedaa2249f898556b47", + "98f4826f05a265e6dd2be82db241c0fbbbf9ffb1c173aa83964b7cf539304373636525" + "3ddbc5db8778371495da76d269e5db3e"}, + {"ec0c2ba17aa95cd6afffe949da9cc3a8", + "296bce5b50b7d66096d627ef", + "b85b3753535b825cbe5f632c0b843c741351f18aa484281aebec2f45bb9eea2d79d987" + "b764b9611f6c0f8641843d5d58f3a242", + "f8d00f05d22bf68599bcdeb131292ad6e2df5d14", + "890147971946b627c40016da1ecf3e77", + "a7443d31c26bdf2a1c945e29ee4bd344a99cfaf3aa71f8b3f191f83c2adfc7a0716299" + "5506fde6309ffc19e716eddf1a828c5a"}, + {NULL}}; + +const TestVector encryption_test_group_4[] = { + {"2c1f21cf0f6fb3661943155c3e3d8492", + "23cb5ff362e22426984d1907", + "42f758836986954db44bf37c6ef5e4ac0adaf38f27252a1b82d02ea949c8a1a2dbc0d6" + "8b5615ba7c1220ff6510e259f06655d8", + "5d3624879d35e46849953e45a32a624d6a6c536ed9857c613b572b0333e701557a713e" + "3f010ecdf9a6bd6c9e3e44b065208645aff4aabee611b391528514170084ccf587177f" + "4488f33cfb5e979e42b6e1cfc0a60238982a7aec", + "57a3ee28136e94c74838997ae9823f3a", + "81824f0e0d523db30d3da369fdc0d60894c7a0a20646dd015073ad2732bd989b14a222" + "b6ad57af43e1895df9dca2a5344a62cc"}, + {"d9f7d2411091f947b4d6f1e2d1f0fb2e", + "e1934f5db57cc983e6b180e7", + "73ed042327f70fe9c572a61545eda8b2a0c6e1d6c291ef19248e973aee6c312012f490" + "c2c6f6166f4a59431e182663fcaea05a", + "0a8a18a7150e940c3d87b38e73baee9a5c049ee21795663e264b694a949822b639092d" + "0e67015e86363583fcf0ca645af9f43375f05fdb4ce84f411dcbca73c2220dea03a201" + "15d2e51398344b16bee1ed7c499b353d6c597af8", + "21b51ca862cb637cdd03b99a0f93b134", + "aaadbd5c92e9151ce3db7210b8714126b73e43436d242677afa50384f2149b831f1d57" + "3c7891c2a91fbc48db29967ec9542b23"}, + {NULL}}; + +const TestVector encryption_test_group_5[] = { + {"fe9bb47deb3a61e423c2231841cfd1fb", + "4d328eb776f500a2f7fb47aa", + "f1cc3818e421876bb6b8bbd6c9", + "", + "43fd4727fe5cdb4b5b42818dea7ef8c9", + "b88c5c1977b35b517b0aeae967"}, + {"6703df3701a7f54911ca72e24dca046a", + "12823ab601c350ea4bc2488c", + "793cd125b0b84a043e3ac67717", + "", + "38e6bcd29962e5f2c13626b85a877101", + "b2051c80014f42f08735a7b0cd"}, + {NULL}}; + +const TestVector* const decryption_test_group_array[] = { + decryption_test_group_0, + decryption_test_group_1, + decryption_test_group_2, + decryption_test_group_3, + decryption_test_group_4, + decryption_test_group_5, +}; + +const TestVector* const encryption_test_group_array[] = { + encryption_test_group_0, + encryption_test_group_1, + encryption_test_group_2, + encryption_test_group_3, + encryption_test_group_4, + encryption_test_group_5, +}; + +bool DecodeHexString(const base::StringPiece& hex, std::string* bytes) { + bytes->clear(); + if (hex.empty()) + return true; + std::vector<uint8> v; + if (!base::HexStringToBytes(hex.as_string(), &v)) + return false; + if (!v.empty()) + bytes->assign(reinterpret_cast<const char*>(&v[0]), v.size()); + return true; +} + +class Aes128GcmHelpersTest : public ::testing::Test { + public: + enum Mode { DECRYPT, ENCRYPT }; + + void SetUp() override { EnsureNSSInit(); } + + bool DecryptOrEncrypt(Mode mode, + const base::StringPiece& input, + const base::StringPiece& key, + const base::StringPiece& nonce, + const base::StringPiece& aad, + size_t auth_tag_size, + std::string* output) { + DCHECK(output); + + const CK_ATTRIBUTE_TYPE cka_mode = + mode == DECRYPT ? CKA_DECRYPT : CKA_ENCRYPT; + + SECItem key_item; + key_item.type = siBuffer; + key_item.data = const_cast<unsigned char*>( + reinterpret_cast<const unsigned char*>(key.data())); + key_item.len = key.size(); + + crypto::ScopedPK11Slot slot(PK11_GetInternalSlot()); + DCHECK(slot); + + crypto::ScopedPK11SymKey aead_key( + PK11_ImportSymKey(slot.get(), CKM_AES_GCM, PK11_OriginUnwrap, cka_mode, + &key_item, nullptr)); + + CK_GCM_PARAMS gcm_params; + gcm_params.pIv = const_cast<unsigned char*>( + reinterpret_cast<const unsigned char*>(nonce.data())); + gcm_params.ulIvLen = nonce.size(); + + gcm_params.pAAD = const_cast<unsigned char*>( + reinterpret_cast<const unsigned char*>(aad.data())); + + gcm_params.ulAADLen = aad.size(); + + gcm_params.ulTagBits = auth_tag_size * 8; + + SECItem param; + param.type = siBuffer; + param.data = reinterpret_cast<unsigned char*>(&gcm_params); + param.len = sizeof(CK_GCM_PARAMS); + + size_t maximum_output_length = input.size(); + if (mode == ENCRYPT) + maximum_output_length += auth_tag_size; + + unsigned int output_length = 0; + unsigned char* raw_input = const_cast<unsigned char*>( + reinterpret_cast<const unsigned char*>(input.data())); + unsigned char* raw_output = reinterpret_cast<unsigned char*>( + WriteInto(output, maximum_output_length + 1 /* null */)); + + PK11Helper_TransformFunction* transform_function = + mode == DECRYPT ? PK11DecryptHelper : PK11EncryptHelper; + + const SECStatus result = transform_function( + aead_key.get(), CKM_AES_GCM, ¶m, raw_output, &output_length, + maximum_output_length, raw_input, input.size()); + + if (result != SECSuccess) + return false; + + const size_t expected_output_length = mode == DECRYPT + ? input.size() - auth_tag_size + : input.size() + auth_tag_size; + + EXPECT_EQ(expected_output_length, output_length); + + output->resize(expected_output_length); + return true; + } + + private: + // The prototype of PK11_Decrypt and PK11_Encrypt. + using PK11Helper_TransformFunction = SECStatus(PK11SymKey* symKey, + CK_MECHANISM_TYPE mechanism, + SECItem* param, + unsigned char* out, + unsigned int* outLen, + unsigned int maxLen, + const unsigned char* data, + unsigned int dataLen); +}; + +} // namespace + +TEST_F(Aes128GcmHelpersTest, RoundTrip) { + const std::string message = "Hello, world!"; + + const size_t kKeySize = 16; + const size_t kNonceSize = 16; + + std::string key, nonce; + RandBytes(WriteInto(&key, kKeySize + 1), kKeySize); + RandBytes(WriteInto(&nonce, kNonceSize + 1), kNonceSize); + + // AEAD_AES_128_GCM is defined with a default authentication tag size of 16, + // but RFC 5282 extends this to authentication tag sizes of 8 and 12 as well. + size_t auth_tag_size = base::RandInt(2, 4) * 4; + + std::string encrypted; + ASSERT_TRUE(DecryptOrEncrypt(ENCRYPT, message, key, nonce, + base::StringPiece(), auth_tag_size, &encrypted)); + + std::string decrypted; + ASSERT_TRUE(DecryptOrEncrypt(DECRYPT, encrypted, key, nonce, + base::StringPiece(), auth_tag_size, &decrypted)); + + EXPECT_EQ(message, decrypted); +} + +TEST_F(Aes128GcmHelpersTest, DecryptionVectors) { + for (size_t i = 0; i < arraysize(decryption_test_group_array); i++) { + SCOPED_TRACE(i); + const TestVector* test_vectors = decryption_test_group_array[i]; + const TestGroupInfo& test_info = test_group_info[i]; + + for (size_t j = 0; test_vectors[j].key != nullptr; j++) { + // If not present then decryption is expected to fail. + bool has_output = test_vectors[j].output; + + // Decode the test vector. + std::string key, iv, input, aad, tag, expected_output; + ASSERT_TRUE(DecodeHexString(test_vectors[j].key, &key)); + ASSERT_TRUE(DecodeHexString(test_vectors[j].iv, &iv)); + ASSERT_TRUE(DecodeHexString(test_vectors[j].input, &input)); + ASSERT_TRUE(DecodeHexString(test_vectors[j].aad, &aad)); + ASSERT_TRUE(DecodeHexString(test_vectors[j].tag, &tag)); + if (has_output) + ASSERT_TRUE(DecodeHexString(test_vectors[j].output, &expected_output)); + + // The test vector's lengths should look sane. Note that the lengths + // in |test_info| are in bits. + EXPECT_EQ(test_info.key_len, key.length() * 8); + EXPECT_EQ(test_info.iv_len, iv.length() * 8); + EXPECT_EQ(test_info.input_len, input.length() * 8); + EXPECT_EQ(test_info.aad_len, aad.length() * 8); + EXPECT_EQ(test_info.tag_len, tag.length() * 8); + if (has_output) + EXPECT_EQ(test_info.input_len, expected_output.length() * 8); + + const std::string ciphertext = input + tag; + std::string output; + + if (!DecryptOrEncrypt(DECRYPT, ciphertext, key, iv, aad, tag.length(), + &output)) { + EXPECT_FALSE(has_output); + continue; + } + + EXPECT_TRUE(has_output); + EXPECT_EQ(expected_output, output); + } + } +} + +TEST_F(Aes128GcmHelpersTest, EncryptionVectors) { + for (size_t i = 0; i < arraysize(encryption_test_group_array); i++) { + SCOPED_TRACE(i); + const TestVector* test_vectors = encryption_test_group_array[i]; + const TestGroupInfo& test_info = test_group_info[i]; + + for (size_t j = 0; test_vectors[j].key != nullptr; j++) { + // If not present then decryption is expected to fail. + bool has_output = test_vectors[j].output; + + // Decode the test vector. + std::string key, iv, input, aad, tag, expected_output; + ASSERT_TRUE(DecodeHexString(test_vectors[j].key, &key)); + ASSERT_TRUE(DecodeHexString(test_vectors[j].iv, &iv)); + ASSERT_TRUE(DecodeHexString(test_vectors[j].input, &input)); + ASSERT_TRUE(DecodeHexString(test_vectors[j].aad, &aad)); + ASSERT_TRUE(DecodeHexString(test_vectors[j].tag, &tag)); + if (has_output) + ASSERT_TRUE(DecodeHexString(test_vectors[j].output, &expected_output)); + + // The test vector's lengths should look sane. Note that the lengths + // in |test_info| are in bits. + EXPECT_EQ(test_info.key_len, key.length() * 8); + EXPECT_EQ(test_info.iv_len, iv.length() * 8); + EXPECT_EQ(test_info.input_len, input.length() * 8); + EXPECT_EQ(test_info.aad_len, aad.length() * 8); + EXPECT_EQ(test_info.tag_len, tag.length() * 8); + if (has_output) + EXPECT_EQ(test_info.input_len, expected_output.length() * 8); + + std::string output; + + if (!DecryptOrEncrypt(ENCRYPT, input, key, iv, aad, tag.length(), + &output)) { + EXPECT_FALSE(has_output); + continue; + } + + const std::string expected_output_with_tag = expected_output + tag; + + EXPECT_TRUE(has_output); + EXPECT_EQ(expected_output_with_tag, output); + } + } +} + +} // namespace crypto diff --git a/crypto/crypto.gyp b/crypto/crypto.gyp index e6bff0b..6327ce7 100644 --- a/crypto/crypto.gyp +++ b/crypto/crypto.gyp @@ -104,6 +104,8 @@ # TODO(joth): Use a glob to match exclude patterns once the # OpenSSL file set is complete. 'sources!': [ + 'aes_128_gcm_helpers_nss.cc', + 'aes_128_gcm_helpers_nss.h', 'ec_private_key_nss.cc', 'ec_signature_creator_nss.cc', 'encryptor_nss.cc', @@ -163,6 +165,7 @@ 'type': 'executable', 'sources': [ 'aead_openssl_unittest.cc', + 'aes_128_gcm_helpers_nss_unittest.cc', 'curve25519_unittest.cc', 'ec_private_key_unittest.cc', 'ec_signature_creator_unittest.cc', @@ -227,6 +230,9 @@ 'dependencies': [ '../third_party/boringssl/boringssl.gyp:boringssl', ], + 'sources!': [ + 'aes_128_gcm_helpers_nss_unittest.cc', + ], }, { 'sources!': [ 'openssl_bio_string_unittest.cc', diff --git a/crypto/crypto.gypi b/crypto/crypto.gypi index 73b3332..71ffbec 100644 --- a/crypto/crypto.gypi +++ b/crypto/crypto.gypi @@ -29,6 +29,8 @@ '<@(hmac_win64_related_sources)', 'aead_openssl.cc', 'aead_openssl.h', + 'aes_128_gcm_helpers_nss.cc', + 'aes_128_gcm_helpers_nss.h', 'apple_keychain.h', 'apple_keychain_ios.mm', 'apple_keychain_mac.mm', diff --git a/net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc b/net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc index f6c3d6d..aba387a 100644 --- a/net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc +++ b/net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc @@ -7,13 +7,7 @@ #include <pk11pub.h> #include <secerr.h> -#include "base/lazy_instance.h" -#include "crypto/ghash.h" -#include "crypto/scoped_nss_types.h" - -#if defined(USE_NSS_CERTS) -#include <dlfcn.h> -#endif +#include "crypto/aes_128_gcm_helpers_nss.h" using base::StringPiece; @@ -24,185 +18,16 @@ namespace { const size_t kKeySize = 16; const size_t kNoncePrefixSize = 4; -// On Linux, dynamically link against the system version of libnss3.so. In -// order to continue working on systems without up-to-date versions of NSS, -// lookup PK11_Decrypt with dlsym. - -// GcmSupportChecker is a singleton which caches the results of runtime symbol -// resolution of PK11_Decrypt. -class GcmSupportChecker { - public: - static PK11_DecryptFunction pk11_decrypt_func() { - return pk11_decrypt_func_; - } - - private: - friend struct base::DefaultLazyInstanceTraits<GcmSupportChecker>; - - GcmSupportChecker() { -#if !defined(USE_NSS_CERTS) - // Using a bundled version of NSS that is guaranteed to have this symbol. - pk11_decrypt_func_ = PK11_Decrypt; -#else - // Using system NSS libraries and PCKS #11 modules, which may not have the - // necessary function (PK11_Decrypt) or mechanism support (CKM_AES_GCM). - - // If PK11_Decrypt() was successfully resolved, then NSS will support - // AES-GCM directly. This was introduced in NSS 3.15. - pk11_decrypt_func_ = (PK11_DecryptFunction)dlsym(RTLD_DEFAULT, - "PK11_Decrypt"); -#endif - } - - // |pk11_decrypt_func_| stores the runtime symbol resolution of PK11_Decrypt. - static PK11_DecryptFunction pk11_decrypt_func_; -}; - -// static -PK11_DecryptFunction GcmSupportChecker::pk11_decrypt_func_ = nullptr; - -base::LazyInstance<GcmSupportChecker>::Leaky g_gcm_support_checker = - LAZY_INSTANCE_INITIALIZER; - -// Calls PK11_Decrypt if it's available. Otherwise, emulates CKM_AES_GCM using -// CKM_AES_CTR and the GaloisHash class. SECStatus My_Decrypt(PK11SymKey* key, CK_MECHANISM_TYPE mechanism, SECItem* param, unsigned char* out, unsigned int* out_len, unsigned int max_len, - const unsigned char* enc, - unsigned int enc_len) { - // If PK11_Decrypt() was successfully resolved or if bundled version of NSS is - // being used, then NSS will support AES-GCM directly. - PK11_DecryptFunction pk11_decrypt_func = - GcmSupportChecker::pk11_decrypt_func(); - if (pk11_decrypt_func != nullptr) { - return pk11_decrypt_func(key, mechanism, param, out, out_len, max_len, enc, - enc_len); - } - - // Otherwise, the user has an older version of NSS. Regrettably, NSS 3.14.x - // has a bug in the AES GCM code - // (https://bugzilla.mozilla.org/show_bug.cgi?id=853285), as well as missing - // the PK11_Decrypt function - // (https://bugzilla.mozilla.org/show_bug.cgi?id=854063), both of which are - // resolved in NSS 3.15. - - DCHECK_EQ(mechanism, static_cast<CK_MECHANISM_TYPE>(CKM_AES_GCM)); - DCHECK_EQ(param->len, sizeof(CK_GCM_PARAMS)); - - const CK_GCM_PARAMS* gcm_params = - reinterpret_cast<CK_GCM_PARAMS*>(param->data); - - DCHECK_EQ(gcm_params->ulTagBits, - static_cast<CK_ULONG>(Aes128Gcm12Decrypter::kAuthTagSize * 8)); - if (gcm_params->ulIvLen != 12u) { - DVLOG(1) << "ulIvLen is not equal to 12"; - PORT_SetError(SEC_ERROR_INPUT_LEN); - return SECFailure; - } - - SECItem my_param = { siBuffer, nullptr, 0 }; - - // Step 2. Let H = CIPH_K(128 '0' bits). - unsigned char ghash_key[16] = {0}; - crypto::ScopedPK11Context ctx(PK11_CreateContextBySymKey( - CKM_AES_ECB, CKA_ENCRYPT, key, &my_param)); - if (!ctx) { - DVLOG(1) << "PK11_CreateContextBySymKey failed"; - return SECFailure; - } - int output_len; - if (PK11_CipherOp(ctx.get(), ghash_key, &output_len, sizeof(ghash_key), - ghash_key, sizeof(ghash_key)) != SECSuccess) { - DVLOG(1) << "PK11_CipherOp failed"; - return SECFailure; - } - - PK11_Finalize(ctx.get()); - - if (output_len != sizeof(ghash_key)) { - DVLOG(1) << "Wrong output length"; - PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); - return SECFailure; - } - - // Step 3. If len(IV)=96, then let J0 = IV || 31 '0' bits || 1. - CK_AES_CTR_PARAMS ctr_params = {0}; - ctr_params.ulCounterBits = 32; - memcpy(ctr_params.cb, gcm_params->pIv, gcm_params->ulIvLen); - ctr_params.cb[12] = 0; - ctr_params.cb[13] = 0; - ctr_params.cb[14] = 0; - ctr_params.cb[15] = 1; - - my_param.type = siBuffer; - my_param.data = reinterpret_cast<unsigned char*>(&ctr_params); - my_param.len = sizeof(ctr_params); - - ctx.reset(PK11_CreateContextBySymKey(CKM_AES_CTR, CKA_ENCRYPT, key, - &my_param)); - if (!ctx) { - DVLOG(1) << "PK11_CreateContextBySymKey failed"; - return SECFailure; - } - - // Step 6. Calculate the encryption mask of GCTR_K(J0, ...). - unsigned char tag_mask[16] = {0}; - if (PK11_CipherOp(ctx.get(), tag_mask, &output_len, sizeof(tag_mask), - tag_mask, sizeof(tag_mask)) != SECSuccess) { - DVLOG(1) << "PK11_CipherOp failed"; - return SECFailure; - } - if (output_len != sizeof(tag_mask)) { - DVLOG(1) << "Wrong output length"; - PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); - return SECFailure; - } - - if (enc_len < Aes128Gcm12Decrypter::kAuthTagSize) { - PORT_SetError(SEC_ERROR_INPUT_LEN); - return SECFailure; - } - - // The const_cast for |enc| can be removed if system NSS libraries are - // NSS 3.14.1 or later (NSS bug - // https://bugzilla.mozilla.org/show_bug.cgi?id=808218). - if (PK11_CipherOp(ctx.get(), out, &output_len, max_len, - const_cast<unsigned char*>(enc), - enc_len - Aes128Gcm12Decrypter::kAuthTagSize) != SECSuccess) { - DVLOG(1) << "PK11_CipherOp failed"; - return SECFailure; - } - - PK11_Finalize(ctx.get()); - - if (static_cast<unsigned int>(output_len) != - enc_len - Aes128Gcm12Decrypter::kAuthTagSize) { - DVLOG(1) << "Wrong output length"; - PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); - return SECFailure; - } - - crypto::GaloisHash ghash(ghash_key); - ghash.UpdateAdditional(gcm_params->pAAD, gcm_params->ulAADLen); - ghash.UpdateCiphertext(enc, output_len); - unsigned char auth_tag[Aes128Gcm12Decrypter::kAuthTagSize]; - ghash.Finish(auth_tag, Aes128Gcm12Decrypter::kAuthTagSize); - for (unsigned int i = 0; i < Aes128Gcm12Decrypter::kAuthTagSize; i++) { - auth_tag[i] ^= tag_mask[i]; - } - - if (NSS_SecureMemcmp(auth_tag, enc + output_len, - Aes128Gcm12Decrypter::kAuthTagSize) != 0) { - PORT_SetError(SEC_ERROR_BAD_DATA); - return SECFailure; - } - - *out_len = output_len; - return SECSuccess; + const unsigned char* data, + unsigned int data_len) { + return crypto::PK11DecryptHelper(key, mechanism, param, out, out_len, max_len, + data, data_len); } } // namespace @@ -213,7 +38,6 @@ Aes128Gcm12Decrypter::Aes128Gcm12Decrypter() static_assert(kKeySize <= kMaxKeySize, "key size too big"); static_assert(kNoncePrefixSize <= kMaxNoncePrefixSize, "nonce prefix size too big"); - ignore_result(g_gcm_support_checker.Get()); } Aes128Gcm12Decrypter::~Aes128Gcm12Decrypter() {} diff --git a/net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc b/net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc index 6ad96f4..bc9b519 100644 --- a/net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc +++ b/net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc @@ -7,13 +7,7 @@ #include <pk11pub.h> #include <secerr.h> -#include "base/lazy_instance.h" -#include "crypto/ghash.h" -#include "crypto/scoped_nss_types.h" - -#if defined(USE_NSS_CERTS) -#include <dlfcn.h> -#endif +#include "crypto/aes_128_gcm_helpers_nss.h" using base::StringPiece; @@ -24,48 +18,6 @@ namespace { const size_t kKeySize = 16; const size_t kNoncePrefixSize = 4; -// On Linux, dynamically link against the system version of libnss3.so. In -// order to continue working on systems without up-to-date versions of NSS, -// lookup PK11_Encrypt with dlsym. - -// GcmSupportChecker is a singleton which caches the results of runtime symbol -// resolution of PK11_Encrypt. -class GcmSupportChecker { - public: - static PK11_EncryptFunction pk11_encrypt_func() { - return pk11_encrypt_func_; - } - - private: - friend struct base::DefaultLazyInstanceTraits<GcmSupportChecker>; - - GcmSupportChecker() { -#if !defined(USE_NSS_CERTS) - // Using a bundled version of NSS that is guaranteed to have this symbol. - pk11_encrypt_func_ = PK11_Encrypt; -#else - // Using system NSS libraries and PCKS #11 modules, which may not have the - // necessary function (PK11_Encrypt) or mechanism support (CKM_AES_GCM). - - // If PK11_Encrypt() was successfully resolved, then NSS will support - // AES-GCM directly. This was introduced in NSS 3.15. - pk11_encrypt_func_ = (PK11_EncryptFunction)dlsym(RTLD_DEFAULT, - "PK11_Encrypt"); -#endif - } - - // |pk11_encrypt_func_| stores the runtime symbol resolution of PK11_Encrypt. - static PK11_EncryptFunction pk11_encrypt_func_; -}; - -// static -PK11_EncryptFunction GcmSupportChecker::pk11_encrypt_func_ = nullptr; - -base::LazyInstance<GcmSupportChecker>::Leaky g_gcm_support_checker = - LAZY_INSTANCE_INITIALIZER; - -// Calls PK11_Encrypt if it's available. Otherwise, emulates CKM_AES_GCM using -// CKM_AES_CTR and the GaloisHash class. SECStatus My_Encrypt(PK11SymKey* key, CK_MECHANISM_TYPE mechanism, SECItem* param, @@ -74,134 +26,8 @@ SECStatus My_Encrypt(PK11SymKey* key, unsigned int max_len, const unsigned char* data, unsigned int data_len) { - // If PK11_Encrypt() was successfully resolved or if bundled version of NSS is - // being used, then NSS will support AES-GCM directly. - PK11_EncryptFunction pk11_encrypt_func = - GcmSupportChecker::pk11_encrypt_func(); - if (pk11_encrypt_func != nullptr) { - return pk11_encrypt_func(key, mechanism, param, out, out_len, max_len, data, - data_len); - } - - // Otherwise, the user has an older version of NSS. Regrettably, NSS 3.14.x - // has a bug in the AES GCM code - // (https://bugzilla.mozilla.org/show_bug.cgi?id=853285), as well as missing - // the PK11_Encrypt function - // (https://bugzilla.mozilla.org/show_bug.cgi?id=854063), both of which are - // resolved in NSS 3.15. - - DCHECK_EQ(mechanism, static_cast<CK_MECHANISM_TYPE>(CKM_AES_GCM)); - DCHECK_EQ(param->len, sizeof(CK_GCM_PARAMS)); - - if (max_len < static_cast<unsigned int>(Aes128Gcm12Encrypter::kAuthTagSize)) { - DVLOG(1) << "max_len is less than kAuthTagSize"; - PORT_SetError(SEC_ERROR_OUTPUT_LEN); - return SECFailure; - } - - const CK_GCM_PARAMS* gcm_params = - reinterpret_cast<CK_GCM_PARAMS*>(param->data); - - DCHECK_EQ(gcm_params->ulTagBits, - static_cast<CK_ULONG>(Aes128Gcm12Encrypter::kAuthTagSize * 8)); - if (gcm_params->ulIvLen != 12u) { - DVLOG(1) << "ulIvLen is not equal to 12"; - PORT_SetError(SEC_ERROR_INPUT_LEN); - return SECFailure; - } - - SECItem my_param = { siBuffer, nullptr, 0 }; - - // Step 1. Let H = CIPH_K(128 '0' bits). - unsigned char ghash_key[16] = {0}; - crypto::ScopedPK11Context ctx(PK11_CreateContextBySymKey( - CKM_AES_ECB, CKA_ENCRYPT, key, &my_param)); - if (!ctx) { - DVLOG(1) << "PK11_CreateContextBySymKey failed"; - return SECFailure; - } - int output_len; - if (PK11_CipherOp(ctx.get(), ghash_key, &output_len, sizeof(ghash_key), - ghash_key, sizeof(ghash_key)) != SECSuccess) { - DVLOG(1) << "PK11_CipherOp failed"; - return SECFailure; - } - - PK11_Finalize(ctx.get()); - - if (output_len != sizeof(ghash_key)) { - DVLOG(1) << "Wrong output length"; - PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); - return SECFailure; - } - - // Step 2. If len(IV)=96, then let J0 = IV || 31 '0' bits || 1. - CK_AES_CTR_PARAMS ctr_params = {0}; - ctr_params.ulCounterBits = 32; - memcpy(ctr_params.cb, gcm_params->pIv, gcm_params->ulIvLen); - ctr_params.cb[12] = 0; - ctr_params.cb[13] = 0; - ctr_params.cb[14] = 0; - ctr_params.cb[15] = 1; - - my_param.type = siBuffer; - my_param.data = reinterpret_cast<unsigned char*>(&ctr_params); - my_param.len = sizeof(ctr_params); - - ctx.reset(PK11_CreateContextBySymKey(CKM_AES_CTR, CKA_ENCRYPT, key, - &my_param)); - if (!ctx) { - DVLOG(1) << "PK11_CreateContextBySymKey failed"; - return SECFailure; - } - - // Step 6. Calculate the encryption mask of GCTR_K(J0, ...). - unsigned char tag_mask[16] = {0}; - if (PK11_CipherOp(ctx.get(), tag_mask, &output_len, sizeof(tag_mask), - tag_mask, sizeof(tag_mask)) != SECSuccess) { - DVLOG(1) << "PK11_CipherOp failed"; - return SECFailure; - } - if (output_len != sizeof(tag_mask)) { - DVLOG(1) << "Wrong output length"; - PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); - return SECFailure; - } - - // The const_cast for |data| can be removed if system NSS libraries are - // NSS 3.14.1 or later (NSS bug - // https://bugzilla.mozilla.org/show_bug.cgi?id=808218). - if (PK11_CipherOp(ctx.get(), out, &output_len, max_len, - const_cast<unsigned char*>(data), data_len) != SECSuccess) { - DVLOG(1) << "PK11_CipherOp failed"; - return SECFailure; - } - - PK11_Finalize(ctx.get()); - - if (static_cast<unsigned int>(output_len) != data_len) { - DVLOG(1) << "Wrong output length"; - PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); - return SECFailure; - } - - if ((max_len - Aes128Gcm12Encrypter::kAuthTagSize) < - static_cast<unsigned int>(output_len)) { - DVLOG(1) << "(max_len - kAuthTagSize) is less than output_len"; - PORT_SetError(SEC_ERROR_OUTPUT_LEN); - return SECFailure; - } - - crypto::GaloisHash ghash(ghash_key); - ghash.UpdateAdditional(gcm_params->pAAD, gcm_params->ulAADLen); - ghash.UpdateCiphertext(out, output_len); - ghash.Finish(out + output_len, Aes128Gcm12Encrypter::kAuthTagSize); - for (unsigned int i = 0; i < Aes128Gcm12Encrypter::kAuthTagSize; i++) { - out[output_len + i] ^= tag_mask[i]; - } - - *out_len = output_len + Aes128Gcm12Encrypter::kAuthTagSize; - return SECSuccess; + return crypto::PK11EncryptHelper(key, mechanism, param, out, out_len, max_len, + data, data_len); } } // namespace @@ -212,7 +38,6 @@ Aes128Gcm12Encrypter::Aes128Gcm12Encrypter() static_assert(kKeySize <= kMaxKeySize, "key size too big"); static_assert(kNoncePrefixSize <= kMaxNoncePrefixSize, "nonce prefix size too big"); - ignore_result(g_gcm_support_checker.Get()); } Aes128Gcm12Encrypter::~Aes128Gcm12Encrypter() {} |