text: // 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), &param_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
  