| Index: base/crypto/symmetric_key_win.cc
|
| ===================================================================
|
| --- base/crypto/symmetric_key_win.cc (revision 43354)
|
| +++ base/crypto/symmetric_key_win.cc (working copy)
|
| @@ -4,13 +4,340 @@
|
|
|
| #include "base/crypto/symmetric_key.h"
|
|
|
| +#include <windows.h>
|
| +#include <wincrypt.h>
|
| +
|
| +#include "base/basictypes.h"
|
| +#include "base/scoped_ptr.h"
|
| +
|
| namespace base {
|
|
|
| -// TODO(albertb): Implement on Windows.
|
| +namespace {
|
|
|
| +#pragma pack(push, 1)
|
| +// The following is a non-public Microsoft header documented in MSDN under
|
| +// CryptImportKey / CryptExportKey. Following the header is the byte array of
|
| +// the actual plaintext key.
|
| +struct PlaintextBlobHeader {
|
| + BLOBHEADER hdr;
|
| + DWORD cbKeySize;
|
| +};
|
| +#pragma pack(pop)
|
| +
|
| +
|
| +// CryptoAPI makes use of three distinct ALG_IDs for AES, rather than just
|
| +// CALG_AES (which exists, but depending on the functions you are calling, may
|
| +// result in function failure, wheras the subtype would succeed)
|
| +ALG_ID GetAESAlgIDForKeySize(size_t key_size_in_bits) {
|
| + // Only AES-128/-192/-256 is supported in CryptoAPI.
|
| + switch (key_size_in_bits) {
|
| + case 128:
|
| + return CALG_AES_128;
|
| + case 192:
|
| + return CALG_AES_192;
|
| + case 256:
|
| + return CALG_AES_256;
|
| + default:
|
| + return 0;
|
| + }
|
| +};
|
| +
|
| +// Imports a raw/plaintext key of |key_size| stored in |*key_data| into a new
|
| +// key created for the specified |provider|. |alg| contains the algorithm of
|
| +// the key being imported.
|
| +// If |key_data| is intended to be used as an HMAC key, then |alg| should be
|
| +// CALG_HMAC.
|
| +// If successful, returns true and stores the imported key in |*key|.
|
| +bool ImportRawKey(HCRYPTPROV provider, ALG_ID alg, BYTE* key_data,
|
| + DWORD key_size, ScopedHCRYPTKEY* key) {
|
| + DCHECK_GT(key_size, 0);
|
| +
|
| + BYTE* actual_key = key_data;
|
| + DWORD actual_size = key_size;
|
| +
|
| + scoped_array<BYTE> tmp_data;
|
| +
|
| + actual_size = sizeof(PlaintextBlobHeader) + key_size;
|
| +
|
| + tmp_data.reset(new BYTE[actual_size]);
|
| + actual_key = tmp_data.get();
|
| + memcpy(actual_key + sizeof(PlaintextBlobHeader), key_data, key_size);
|
| + PlaintextBlobHeader* key_header
|
| + = reinterpret_cast<PlaintextBlobHeader*>(actual_key);
|
| + memset(key_header, 0, sizeof(PlaintextBlobHeader));
|
| +
|
| + key_header->cbKeySize = key_size;
|
| +
|
| + key_header->hdr.bType = PLAINTEXTKEYBLOB;
|
| + key_header->hdr.bVersion = CUR_BLOB_VERSION;
|
| + key_header->hdr.aiKeyAlg = alg;
|
| +
|
| + HCRYPTKEY unsafe_key = NULL;
|
| + DWORD dwFlags = CRYPT_EXPORTABLE;
|
| + if (alg == CALG_HMAC) {
|
| + // Though it may appear odd that IPSEC and RC2 are being used, this is
|
| + // done in accordance with Microsoft's FIPS 140-2 Security Policy for the
|
| + // RSA Enhanced Provider, as the approved means of using arbitrary HMAC
|
| + // key material.
|
| + key_header->hdr.aiKeyAlg = CALG_RC2;
|
| + dwFlags |= CRYPT_IPSEC_HMAC_KEY;
|
| + }
|
| +
|
| + BOOL ok = CryptImportKey(provider, actual_key, actual_size, NULL,
|
| + dwFlags, &unsafe_key);
|
| +
|
| + // Clean-up the temporary copy of key, regardless of whether it was imported
|
| + // sucessfully or not
|
| + SecureZeroMemory(tmp_data.get(), actual_size);
|
| +
|
| + if (!ok)
|
| + return false;
|
| +
|
| + key->reset(unsafe_key);
|
| + return true;
|
| +}
|
| +
|
| +// Attempts to generate a random AES key of |key_size_in_bits. Returns true if
|
| +// generation is successful, storing the generated key in |*key| and the key
|
| +// provider (CSP) in |*provider|.
|
| +bool GenerateAESKey(size_t key_size_in_bits, ScopedHCRYPTPROV* provider,
|
| + ScopedHCRYPTKEY* key) {
|
| + DCHECK(provider);
|
| + DCHECK(key);
|
| +
|
| + ALG_ID alg = GetAESAlgIDForKeySize(key_size_in_bits);
|
| + if (alg == 0)
|
| + return false;
|
| +
|
| + ScopedHCRYPTPROV safe_provider;
|
| + // Note: The only time NULL is safe to be passed as pszContainerName is when
|
| + // dwFlags contains CRYPT_VERIFYCONTEXT, as all keys generated and/or used
|
| + // will be treated as ephemeral keys and not persisted.
|
| + BOOL ok = CryptAcquireContext(safe_provider.receive(), NULL, NULL,
|
| + PROV_RSA_AES, CRYPT_VERIFYCONTEXT);
|
| + if (!ok)
|
| + return false;
|
| +
|
| + ScopedHCRYPTKEY safe_key;
|
| + // In the FIPS 140-2 Security Policy for CAPI on XP/Vista+, Microsoft notes
|
| + // that CryptGenKey makes use of the same functionality exposed via
|
| + // CryptGenRandom. The reason this is being used, as opposed to
|
| + // CryptGenRandom and CryptImportKey is for compliance with the security
|
| + // policy
|
| + ok = CryptGenKey(safe_provider.get(), alg, CRYPT_EXPORTABLE,
|
| + safe_key.receive());
|
| + if (!ok)
|
| + return false;
|
| +
|
| + key->swap(safe_key);
|
| + provider->swap(safe_provider);
|
| +
|
| + return true;
|
| +}
|
| +
|
| +// Attempts to generate a random, |key_size_in_bits|-long HMAC key, for use
|
| +// with the hash function |alg|.
|
| +// |key_size_in_bits| must be >= 1/2 the hash size of |alg|, for security.
|
| +// Returns true if generation is successful, storing the generated key in
|
| +// |*key| and the key provider (CSP) in |*provider|.
|
| +bool GenerateHMACKey(size_t key_size_in_bits, ALG_ID alg,
|
| + ScopedHCRYPTPROV* provider, ScopedHCRYPTKEY* key,
|
| + scoped_array<BYTE>* raw_key) {
|
| + DCHECK(provider);
|
| + DCHECK(key);
|
| +
|
| + ScopedHCRYPTPROV safe_provider;
|
| + // See comment in GenerateAESKey as to why NULL is acceptable for the
|
| + // container name.
|
| + BOOL ok = CryptAcquireContext(safe_provider.receive(), NULL, NULL,
|
| + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
|
| + if (!ok)
|
| + return false;
|
| +
|
| + ScopedHCRYPTHASH safe_hash;
|
| + ok = CryptCreateHash(safe_provider.get(), alg, NULL, 0, safe_hash.receive());
|
| + if (!ok)
|
| + return false;
|
| +
|
| + DWORD hash_size = 0;
|
| + DWORD param_size = sizeof(hash_size);
|
| + ok = CryptGetHashParam(safe_hash, HP_HASHSIZE,
|
| + reinterpret_cast<BYTE*>(&hash_size), ¶m_size, 0);
|
| + if (!ok || hash_size == 0)
|
| + return false;
|
| +
|
| + // An HMAC key must be >= L/2, where L is the output size of the hash
|
| + // function being used.
|
| + if (key_size_in_bits < (hash_size / 2 * 8) || (key_size_in_bits % 8) != 0)
|
| + return false;
|
| +
|
| +
|
| + DWORD key_size_in_bytes = key_size_in_bits / 8;
|
| + scoped_array<BYTE> random(new BYTE[key_size_in_bytes]);
|
| + ok = CryptGenRandom(safe_provider, key_size_in_bytes,
|
| + random.get());
|
| + if (!ok)
|
| + return false;
|
| +
|
| + ScopedHCRYPTKEY safe_key;
|
| + if (!ImportRawKey(safe_provider, CALG_HMAC, random.get(),
|
| + key_size_in_bytes, &safe_key))
|
| + return false;
|
| +
|
| + key->swap(safe_key);
|
| + provider->swap(safe_provider);
|
| + raw_key->swap(random);
|
| +
|
| + return true;
|
| +}
|
| +
|
| +// Attempts to create an HMAC hash instance using the specified |provider|
|
| +// and |key|. The inner hash function will be |hash_alg|. If successful,
|
| +// returns true and stores the hash in |*hash|.
|
| +bool CreateHMACHash(HCRYPTPROV provider, HCRYPTKEY key, ALG_ID hash_alg,
|
| + ScopedHCRYPTHASH* hash) {
|
| + ScopedHCRYPTHASH safe_hash;
|
| + BOOL ok = FALSE;
|
| +
|
| + ok = CryptCreateHash(provider, CALG_HMAC, key, 0, safe_hash.receive());
|
| + if (!ok)
|
| + return false;
|
| +
|
| + HMAC_INFO hmac_info;
|
| + memset(&hmac_info, 0, sizeof(hmac_info));
|
| + hmac_info.HashAlgid = hash_alg;
|
| +
|
| + ok = CryptSetHashParam(safe_hash, HP_HMAC_INFO,
|
| + reinterpret_cast<const BYTE*>(&hmac_info), 0);
|
| + if (!ok)
|
| + return false;
|
| +
|
| + hash->swap(safe_hash);
|
| + return true;
|
| +}
|
| +
|
| +// Performs a single iteration of the PBKDF2 function F for the specified
|
| +// |block_number| using the PRF |hash|, writing the output to |*output_buf|.
|
| +// |output_buf| must have enough space to accomodate the output of the PRF
|
| +// specified by |hash|.
|
| +// Returns true if the block was successfully computed.
|
| +bool ComputePBKDF2Block(HCRYPTPROV provider, HCRYPTKEY key, HCRYPTHASH hash,
|
| + const std::string& salt, size_t iterations,
|
| + size_t block_number, BYTE* output_buf) {
|
| + // From RFC2898:
|
| + // 3. <snip> The function F is defined as the exclusive-or sum of the first
|
| + // c iterates of the underlying pseudorandom function PRF applied to the
|
| + // password P and the concatenation of the salt S and the block index i:
|
| + // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
|
| + // where
|
| + // U_1 = PRF(P, S || INT (i))
|
| + // U_2 = PRF(P, U_1)
|
| + // ...
|
| + // U_c = PRF(P, U_{c-1})
|
| + ScopedHCRYPTHASH safe_hash;
|
| + BOOL ok = CryptDuplicateHash(hash, 0, 0, safe_hash.receive());
|
| + if (!ok)
|
| + return false;
|
| +
|
| + DWORD hash_size = 0;
|
| + DWORD param_size = sizeof(hash_size);
|
| +
|
| + ok = CryptGetHashParam(safe_hash, HP_HASHSIZE,
|
| + reinterpret_cast<BYTE*>(&hash_size), ¶m_size, 0);
|
| + if (!ok || hash_size == 0)
|
| + return false;
|
| +
|
| + // Iteration U_1: Compute PRF for S.
|
| + ok = CryptHashData(safe_hash,
|
| + reinterpret_cast<const BYTE*>(salt.data()), salt.size(),
|
| + 0);
|
| + if (!ok)
|
| + return false;
|
| +
|
| + // Iteration U_1: and append (big-endian) INT (i).
|
| + // TODO(rsleevi): Platform endian checks?
|
| + uint32 big_endian_block_number = ((block_number << 24) & 0xFF000000) |
|
| + ((block_number << 8) & 0x00FF0000) |
|
| + ((block_number >> 8) & 0x0000FF00) |
|
| + ((block_number >> 24) & 0x000000FF);
|
| + ok = CryptHashData(safe_hash,
|
| + reinterpret_cast<const BYTE*>(&big_endian_block_number),
|
| + 4, 0);
|
| +
|
| + scoped_array<BYTE> output(new BYTE[hash_size]);
|
| + scoped_array<BYTE> hash_val(new BYTE[hash_size]);
|
| +
|
| + DWORD size = hash_size;
|
| + ok = CryptGetHashParam(safe_hash, HP_HASHVAL, hash_val.get(),
|
| + &size, 0);
|
| + if (!ok)
|
| + return false;
|
| +
|
| + memcpy(output.get(), hash_val.get(), hash_size);
|
| +
|
| + // Iteration 2 - c: Compute U_{iteration} by applying the HMAC-SHA1 PRF to
|
| + // U_{iteration - 1} with key |key|, then xor the resultant hash with
|
| + // |output|, which contains U_1 ^ U_2 ^ ... ^ U_{iteration - 1}
|
| + for (size_t iteration = 2; iteration <= iterations; ++iteration) {
|
| + safe_hash.reset();
|
| + ok = CryptDuplicateHash(hash, 0, 0, safe_hash.receive());
|
| + if (!ok)
|
| + return false;
|
| +
|
| + ok = CryptHashData(safe_hash, hash_val.get(), hash_size, 0);
|
| + if (!ok)
|
| + return false;
|
| +
|
| + size = hash_size;
|
| + ok = CryptGetHashParam(safe_hash, HP_HASHVAL, hash_val.get(), &size,
|
| + 0);
|
| + if (!ok || size != hash_size)
|
| + return false;
|
| +
|
| + for (int i = 0; i < hash_size; ++i)
|
| + output[i] ^= hash_val[i];
|
| + }
|
| +
|
| + memcpy(output_buf, output.get(), hash_size);
|
| + return true;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| // static
|
| SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm,
|
| size_t key_size_in_bits) {
|
| + DCHECK_GE(key_size_in_bits, 8);
|
| +
|
| + ScopedHCRYPTPROV provider_handle;
|
| + ScopedHCRYPTKEY key_handle;
|
| +
|
| + bool ok = false;
|
| +
|
| + scoped_array<BYTE> raw_key;
|
| + if (algorithm == AES) {
|
| + ok = GenerateAESKey(key_size_in_bits, &provider_handle, &key_handle);
|
| + } else if (algorithm == HMAC_SHA1) {
|
| + ok = GenerateHMACKey(key_size_in_bits, CALG_SHA1, &provider_handle,
|
| + &key_handle, &raw_key);
|
| + }
|
| +
|
| + if (ok) {
|
| + size_t key_size_in_bytes = key_size_in_bits / 8;
|
| + if (raw_key == NULL)
|
| + key_size_in_bytes = 0;
|
| +
|
| + SymmetricKey* result = new SymmetricKey(provider_handle.release(),
|
| + key_handle.release(),
|
| + raw_key.get(),
|
| + key_size_in_bytes);
|
| + if (raw_key != NULL)
|
| + SecureZeroMemory(raw_key.get(), key_size_in_bytes);
|
| +
|
| + return result;
|
| + }
|
| +
|
| + NOTREACHED();
|
| return NULL;
|
| }
|
|
|
| @@ -20,11 +347,146 @@
|
| const std::string& salt,
|
| size_t iterations,
|
| size_t key_size_in_bits) {
|
| - return NULL;
|
| + // CryptoAPI lacks native routines to perform PBKDF2 derivation as specified
|
| + // in RFC 2898, so it must be manually implemented. Only HMAC-SHA1 is
|
| + // supported as the PRF.
|
| + if (algorithm != HMAC_SHA1 && algorithm != AES)
|
| + return NULL;
|
| +
|
| + // While not used until the end, sanity-check the input before proceding with
|
| + // the expensive computation.
|
| + DWORD provider_type = 0;
|
| + ALG_ID alg = 0;
|
| + switch (algorithm) {
|
| + case AES:
|
| + provider_type = PROV_RSA_AES;
|
| + alg = GetAESAlgIDForKeySize(key_size_in_bits);
|
| + break;
|
| + case HMAC_SHA1:
|
| + provider_type = PROV_RSA_FULL;
|
| + alg = CALG_HMAC;
|
| + break;
|
| + default:
|
| + NOTREACHED();
|
| + }
|
| + if (alg == 0 || provider_type == 0)
|
| + return NULL;
|
| +
|
| + ScopedHCRYPTPROV provider;
|
| + BOOL ok = CryptAcquireContext(provider.receive(), NULL, NULL, provider_type,
|
| + CRYPT_VERIFYCONTEXT);
|
| + if (!ok)
|
| + return false;
|
| +
|
| + // Convert the user password into a key suitable to be fed into the PRF
|
| + // function.
|
| + ScopedHCRYPTKEY password_as_key;
|
| + BYTE* password_as_bytes =
|
| + const_cast<BYTE*>(reinterpret_cast<const BYTE*>(password.data()));
|
| + if (!ImportRawKey(provider, CALG_HMAC, password_as_bytes,
|
| + password.size(), &password_as_key))
|
| + return NULL;
|
| +
|
| + // Configure the PRF function. Only HMAC variants are supported, with the
|
| + // only hash function supported being SHA1.
|
| + // TODO(rsleevi): Support SHA-256 on XP SP3+.
|
| + ScopedHCRYPTHASH prf;
|
| + if (!CreateHMACHash(provider, password_as_key, CALG_SHA1, &prf))
|
| + return false;
|
| +
|
| + DWORD hLen = 0;
|
| + DWORD param_size = sizeof(hLen);
|
| + ok = CryptGetHashParam(prf, HP_HASHSIZE,
|
| + reinterpret_cast<BYTE*>(&hLen), ¶m_size, 0);
|
| + if (!ok || hLen == 0)
|
| + return false;
|
| +
|
| + // 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and stop.
|
| + size_t dkLen = key_size_in_bits / 8;
|
| + DCHECK_GT(dkLen, 0);
|
| +
|
| + if ((dkLen / hLen) > 0xFFFFFFFF) {
|
| + DLOG(ERROR) << "Derived key too long.";
|
| + return NULL;
|
| + }
|
| +
|
| + // 2. Let l be the number of hLen-octet blocks in the derived key,
|
| + // rounding up, and let r be the number of octets in the last
|
| + // block:
|
| + size_t l = dkLen / hLen;
|
| + size_t r = hLen - (dkLen % hLen);
|
| + if (r != hLen) ++l;
|
| +
|
| + DCHECK_GT(l, 0);
|
| +
|
| + size_t total_generated_size = l * hLen;
|
| + scoped_array<BYTE> generated_key(new BYTE[total_generated_size]);
|
| + BYTE* block_offset = generated_key.get();
|
| +
|
| + // 3. For each block of the derived key apply the function F defined below
|
| + // to the password P, the salt S, the iteration count c, and the block
|
| + // index to compute the block:
|
| + // T_1 = F (P, S, c, 1)
|
| + // T_2 = F (P, S, c, 2)
|
| + // ...
|
| + // T_l = F (P, S, c, l)
|
| + // <snip>
|
| + // 4. Concatenate the blocks and extract the first dkLen octets to produce
|
| + // a derived key DK:
|
| + // DK = T_1 || T_2 || ... || T_l<0..r-1>
|
| + bool error = false;
|
| + for (size_t cur_block = 1; cur_block <= l && !error; ++cur_block) {
|
| + if (!ComputePBKDF2Block(provider, password_as_key, prf,
|
| + salt, iterations, cur_block, block_offset)) {
|
| + error = true;
|
| + }
|
| + block_offset += hLen;
|
| + }
|
| +
|
| + if (error)
|
| + return NULL;
|
| +
|
| + // Convert the derived key bytes into a key handle for the desired algorithm.
|
| + ScopedHCRYPTKEY key;
|
| + if (!ImportRawKey(provider, alg, generated_key.get(),
|
| + dkLen, &key))
|
| + return NULL;
|
| +
|
| + SymmetricKey* result = new SymmetricKey(provider.release(), key.release(),
|
| + generated_key.get(), dkLen);
|
| +
|
| + SecureZeroMemory(generated_key.get(), total_generated_size);
|
| +
|
| + return result;
|
| }
|
|
|
| bool SymmetricKey::GetRawKey(std::string* raw_key) {
|
| - return false;
|
| + // Short circuit for when the key was supplied during initialization
|
| + if (!raw_key_.empty()) {
|
| + *raw_key = raw_key_;
|
| + return true;
|
| + }
|
| +
|
| + DWORD size = 0;
|
| + BOOL ok = CryptExportKey(key_, NULL, PLAINTEXTKEYBLOB, 0, NULL, &size);
|
| + if (!ok && GetLastError() != ERROR_MORE_DATA)
|
| + return false;
|
| +
|
| + scoped_array<BYTE> result(new BYTE[size]);
|
| +
|
| + ok = CryptExportKey(key_, NULL, PLAINTEXTKEYBLOB, 0,
|
| + result.get(), &size);
|
| + if (!ok)
|
| + return false;
|
| +
|
| + PlaintextBlobHeader* header =
|
| + reinterpret_cast<PlaintextBlobHeader*>(result.get());
|
| + raw_key->assign(reinterpret_cast<char*>(result.get() + sizeof(*header)),
|
| + header->cbKeySize);
|
| +
|
| + SecureZeroMemory(result.get(), size);
|
| +
|
| + return true;
|
| }
|
|
|
| } // namespace base
|
|
|