diff options
author | rvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-14 17:37:14 +0000 |
---|---|---|
committer | rvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-14 17:37:14 +0000 |
commit | 4b559b4ddffc0b7f688019bcb80658f05e063af7 (patch) | |
tree | 0be21d8914de707f5125d2cb66733cbcf088606c /crypto/symmetric_key_win.cc | |
parent | 056dd45d610de34312344445d7b078a31f4a1e20 (diff) | |
download | chromium_src-4b559b4ddffc0b7f688019bcb80658f05e063af7.zip chromium_src-4b559b4ddffc0b7f688019bcb80658f05e063af7.tar.gz chromium_src-4b559b4ddffc0b7f688019bcb80658f05e063af7.tar.bz2 |
Move crypto files out of base, to a top level directory.
src/crypto is now an independent project that contains our
cryptographic primitives (except md5 and sha1).
This removes the base dependency from nss, openssl and sqlite.
BUG=76996
TEST=none
Review URL: http://codereview.chromium.org/6805019
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@81611 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'crypto/symmetric_key_win.cc')
-rw-r--r-- | crypto/symmetric_key_win.cc | 536 |
1 files changed, 536 insertions, 0 deletions
diff --git a/crypto/symmetric_key_win.cc b/crypto/symmetric_key_win.cc new file mode 100644 index 0000000..d2034e0 --- /dev/null +++ b/crypto/symmetric_key_win.cc @@ -0,0 +1,536 @@ +// Copyright (c) 2011 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/symmetric_key.h" + +#include <winsock2.h> // For htonl. + +#include <vector> + +// TODO(wtc): replace scoped_array by std::vector. +#include "base/memory/scoped_ptr.h" + +namespace crypto { + +namespace { + +// 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; +}; + +// 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, whereas 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: + NOTREACHED(); + 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|. +// TODO(wtc): use this function in hmac_win.cc. +bool ImportRawKey(HCRYPTPROV provider, + ALG_ID alg, + const void* key_data, DWORD key_size, + ScopedHCRYPTKEY* key) { + DCHECK_GT(key_size, 0); + + DWORD actual_size = sizeof(PlaintextBlobHeader) + key_size; + std::vector<BYTE> tmp_data(actual_size); + BYTE* actual_key = &tmp_data[0]; + 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->hdr.bType = PLAINTEXTKEYBLOB; + key_header->hdr.bVersion = CUR_BLOB_VERSION; + key_header->hdr.aiKeyAlg = alg; + + key_header->cbKeySize = key_size; + + HCRYPTKEY unsafe_key = NULL; + DWORD flags = 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; + flags |= CRYPT_IPSEC_HMAC_KEY; + } + + BOOL ok = + CryptImportKey(provider, actual_key, actual_size, 0, flags, &unsafe_key); + + // Clean up the temporary copy of key, regardless of whether it was imported + // sucessfully or not. + SecureZeroMemory(actual_key, 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 pszContainer 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; +} + +// Returns true if the HMAC key size meets the requirement of FIPS 198 +// Section 3. |alg| is the hash function used in the HMAC. +bool CheckHMACKeySize(size_t key_size_in_bits, ALG_ID alg) { + DWORD hash_size = 0; + switch (alg) { + case CALG_SHA1: + hash_size = 20; + break; + case CALG_SHA_256: + hash_size = 32; + break; + case CALG_SHA_384: + hash_size = 48; + break; + case CALG_SHA_512: + hash_size = 64; + break; + } + if (hash_size == 0) + return false; + + // An HMAC key must be >= L/2, where L is the output size of the hash + // function being used. + return (key_size_in_bits >= (hash_size / 2 * 8) && + (key_size_in_bits % 8) == 0); +} + +// 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); + DCHECK(raw_key); + + if (!CheckHMACKeySize(key_size_in_bits, alg)) + return false; + + 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; + + 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; + bool rv = ImportRawKey(safe_provider, CALG_HMAC, random.get(), + key_size_in_bytes, &safe_key); + if (rv) { + key->swap(safe_key); + provider->swap(safe_provider); + raw_key->swap(random); + } + + SecureZeroMemory(random.get(), key_size_in_bytes); + return rv; +} + +// 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|. +// TODO(wtc): use this function in hmac_win.cc. +bool CreateHMACHash(HCRYPTPROV provider, + HCRYPTKEY key, + ALG_ID hash_alg, + ScopedHCRYPTHASH* hash) { + ScopedHCRYPTHASH safe_hash; + BOOL 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; +} + +// Computes a block of the derived key using the PBKDF2 function F for the +// specified |block_index| 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(HCRYPTHASH hash, + DWORD hash_size, + const std::string& salt, + size_t iterations, + uint32 block_index, + BYTE* output_buf) { + // From RFC 2898: + // 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, NULL, 0, safe_hash.receive()); + if (!ok) + 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). + uint32 big_endian_block_index = htonl(block_index); + ok = CryptHashData(safe_hash, + reinterpret_cast<BYTE*>(&big_endian_block_index), + sizeof(big_endian_block_index), 0); + + std::vector<BYTE> hash_value(hash_size); + + DWORD size = hash_size; + ok = CryptGetHashParam(safe_hash, HP_HASHVAL, &hash_value[0], &size, 0); + if (!ok || size != hash_size) + return false; + + memcpy(output_buf, &hash_value[0], hash_size); + + // Iteration 2 - c: Compute U_{iteration} by applying the PRF to + // U_{iteration - 1}, 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, NULL, 0, safe_hash.receive()); + if (!ok) + return false; + + ok = CryptHashData(safe_hash, &hash_value[0], hash_size, 0); + if (!ok) + return false; + + size = hash_size; + ok = CryptGetHashParam(safe_hash, HP_HASHVAL, &hash_value[0], &size, 0); + if (!ok || size != hash_size) + return false; + + for (int i = 0; i < hash_size; ++i) + output_buf[i] ^= hash_value[i]; + } + + return true; +} + +} // namespace + +SymmetricKey::~SymmetricKey() { + // TODO(wtc): create a "secure" string type that zeroes itself in the + // destructor. + if (!raw_key_.empty()) + SecureZeroMemory(const_cast<char *>(raw_key_.data()), raw_key_.size()); +} + +// static +SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm, + size_t key_size_in_bits) { + DCHECK_GE(key_size_in_bits, 8); + + ScopedHCRYPTPROV provider; + ScopedHCRYPTKEY key; + + bool ok = false; + scoped_array<BYTE> raw_key; + + switch (algorithm) { + case AES: + ok = GenerateAESKey(key_size_in_bits, &provider, &key); + break; + case HMAC_SHA1: + ok = GenerateHMACKey(key_size_in_bits, CALG_SHA1, &provider, + &key, &raw_key); + break; + } + + if (!ok) { + NOTREACHED(); + return NULL; + } + + 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.release(), + key.release(), + raw_key.get(), + key_size_in_bytes); + if (raw_key != NULL) + SecureZeroMemory(raw_key.get(), key_size_in_bytes); + + return result; +} + +// static +SymmetricKey* SymmetricKey::DeriveKeyFromPassword(Algorithm algorithm, + const std::string& password, + const std::string& salt, + size_t iterations, + size_t key_size_in_bits) { + // CryptoAPI lacks routines to perform PBKDF2 derivation as specified + // in RFC 2898, so it must be manually implemented. Only HMAC-SHA1 is + // supported as the PRF. + + // While not used until the end, sanity-check the input before proceeding + // 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(); + break; + } + if (provider_type == 0 || alg == 0) + return NULL; + + ScopedHCRYPTPROV provider; + BOOL ok = CryptAcquireContext(provider.receive(), NULL, NULL, provider_type, + CRYPT_VERIFYCONTEXT); + if (!ok) + return NULL; + + // 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 NULL; + + 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 NULL; + + // 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 - 1) / hLen; + DCHECK_GT(L, 0); + + size_t total_generated_size = L * hLen; + std::vector<BYTE> generated_key(total_generated_size); + BYTE* block_offset = &generated_key[0]; + + // 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> + for (uint32 block_index = 1; block_index <= L; ++block_index) { + if (!ComputePBKDF2Block(prf, hLen, salt, iterations, block_index, + block_offset)) + return NULL; + block_offset += hLen; + } + + // Convert the derived key bytes into a key handle for the desired algorithm. + ScopedHCRYPTKEY key; + if (!ImportRawKey(provider, alg, &generated_key[0], dkLen, &key)) + return NULL; + + SymmetricKey* result = new SymmetricKey(provider.release(), key.release(), + &generated_key[0], dkLen); + + SecureZeroMemory(&generated_key[0], total_generated_size); + + return result; +} + +// static +SymmetricKey* SymmetricKey::Import(Algorithm algorithm, + const std::string& raw_key) { + DWORD provider_type = 0; + ALG_ID alg = 0; + switch (algorithm) { + case AES: + provider_type = PROV_RSA_AES; + alg = GetAESAlgIDForKeySize(raw_key.size() * 8); + break; + case HMAC_SHA1: + provider_type = PROV_RSA_FULL; + alg = CALG_HMAC; + break; + default: + NOTREACHED(); + break; + } + if (provider_type == 0 || alg == 0) + return NULL; + + ScopedHCRYPTPROV provider; + BOOL ok = CryptAcquireContext(provider.receive(), NULL, NULL, provider_type, + CRYPT_VERIFYCONTEXT); + if (!ok) + return NULL; + + ScopedHCRYPTKEY key; + if (!ImportRawKey(provider, alg, raw_key.data(), raw_key.size(), &key)) + return NULL; + + return new SymmetricKey(provider.release(), key.release(), + raw_key.data(), raw_key.size()); +} + +bool SymmetricKey::GetRawKey(std::string* raw_key) { + // Short circuit for when the key was supplied to the constructor. + if (!raw_key_.empty()) { + *raw_key = raw_key_; + return true; + } + + DWORD size = 0; + BOOL ok = CryptExportKey(key_, 0, PLAINTEXTKEYBLOB, 0, NULL, &size); + if (!ok) + return false; + + std::vector<BYTE> result(size); + + ok = CryptExportKey(key_, 0, PLAINTEXTKEYBLOB, 0, &result[0], &size); + if (!ok) + return false; + + PlaintextBlobHeader* header = + reinterpret_cast<PlaintextBlobHeader*>(&result[0]); + raw_key->assign(reinterpret_cast<char*>(&result[sizeof(*header)]), + header->cbKeySize); + + SecureZeroMemory(&result[0], size); + + return true; +} + +SymmetricKey::SymmetricKey(HCRYPTPROV provider, + HCRYPTKEY key, + const void* key_data, size_t key_size_in_bytes) + : provider_(provider), key_(key) { + if (key_data) { + raw_key_.assign(reinterpret_cast<const char*>(key_data), + key_size_in_bytes); + } +} + +} // namespace crypto |