blob: 7151e4ad41debf9e8704dfc01c7c8dcf47f92728 [file] [log] [blame]
// Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Contains the implementation of class Crypto
#include "crypto.h"
#include <limits>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <base/file_util.h>
#include <base/logging.h>
#include <chromeos/utility.h>
extern "C" {
#include <scrypt/scryptenc.h>
}
#include "cryptohome_common.h"
#include "old_vault_keyset.h"
#include "platform.h"
#include "username_passkey.h"
// Included last because it has conflicting defines
extern "C" {
#include <ecryptfs.h>
}
using std::string;
namespace cryptohome {
// The OpenSSL "magic" value that prefixes the encrypted data blob. This is
// only used in migrating legacy keysets, which were stored in this manner.
const std::string kOpenSSLMagic = "Salted__";
// The well-known exponent used when generating RSA keys. Cryptohome only
// generates one RSA key, which is the system-wide cryptohome key. This is the
// common public exponent.
const unsigned int kWellKnownExponent = 65537;
// An upper bound on the amount of memory that we allow Scrypt to use when
// performing key strengthening (32MB). A large size is okay since we only use
// Scrypt during the login process, before the user is logged in. This memory
// is managed (and freed) by the scrypt library.
const unsigned int kScryptMaxMem = 32 * 1024 * 1024;
// An upper bound on the amount of time we allow Scrypt to use when performing
// key strenthening (1/3s) for encryption.
const double kScryptMaxEncryptTime = 0.333;
// An upper bound on the amount of time we allow Scrypt to use when performing
// key strenthening (100s) for decryption. This number can be high because in
// practice, it doesn't mean much. It simply needs to be large enough that we
// can guarantee that the key derived during encryption can always be derived at
// decryption time, so the typical time is usually close to 1/3s. However,
// because sometimes other processes may interfere, we need it to be large
// enough to allow the same calculation to be made amidst other heavy use.
const double kScryptMaxDecryptTime = 100.0;
// Scrypt creates a header in the cipher text that we need to account for in
// buffer sizing.
const unsigned int kScryptHeaderLength = 128;
// The number of hash rounds we originally used when converting a password to a
// key. This is used when converting older cryptohome vault keysets.
const unsigned int kDefaultLegacyPasswordRounds = 1;
// The current number of hash rounds we use. Large enough to be a measurable
// amount of time, but not add too much overhead to login (around 10ms).
const unsigned int kDefaultPasswordRounds = 1337;
// AES key size in bytes (256-bit). This key size is used for all key creation,
// though we currently only use 128 bits for the eCryptfs File Encryption Key
// (FEK). Larger than 128-bit has too great of a CPU overhead on unaccelerated
// architectures.
const unsigned int kDefaultAesKeySize = 32;
Crypto::Crypto()
: use_tpm_(false),
load_tpm_(true),
tpm_(NULL),
fallback_to_scrypt_(true) {
}
Crypto::~Crypto() {
}
bool Crypto::Init() {
if ((use_tpm_ || load_tpm_) && tpm_ == NULL) {
tpm_ = Tpm::GetSingleton();
}
if (tpm_) {
tpm_->Init(this, true);
}
return true;
}
Crypto::CryptoError Crypto::EnsureTpm(bool disconnect_first) const {
Crypto::CryptoError result = Crypto::CE_NONE;
if (tpm_) {
if (disconnect_first) {
tpm_->Disconnect();
}
if (!tpm_->IsConnected()) {
Tpm::TpmRetryAction retry_action;
if (!tpm_->Connect(&retry_action)) {
result = TpmErrorToCrypto(retry_action);
}
}
}
return result;
}
void Crypto::GetSecureRandom(unsigned char* rand, unsigned int length) const {
// OpenSSL takes a signed integer. On the off chance that the user requests
// something too large, truncate it.
if (length > static_cast<unsigned int>(std::numeric_limits<int>::max())) {
length = std::numeric_limits<int>::max();
}
RAND_bytes(rand, length);
}
unsigned int Crypto::GetAesBlockSize() const {
return EVP_CIPHER_block_size(EVP_aes_256_cbc());
}
bool Crypto::AesEncrypt(const chromeos::Blob& plain_text, unsigned int start,
unsigned int count, const SecureBlob& key,
const SecureBlob& iv, PaddingScheme padding,
SecureBlob* encrypted) const {
return AesEncryptSpecifyBlockMode(plain_text, start, count, key, iv, padding,
kCbc, encrypted);
}
// AesEncryptSpecifyBlockMode encrypts the bytes in plain_text using AES,
// placing the output into encrypted. Aside from range constraints (start and
// count) and the key and initialization vector, this method has two parameters
// that control how the ciphertext is generated and are useful in encrypting
// specific types of data in cryptohome.
//
// First, padding specifies whether and how the plaintext is padded before
// encryption. The three options, described in the PaddingScheme enumeration
// are used as such:
// - kPaddingNone is used to mix the user's passkey (derived from the
// password) into the encrypted blob storing the vault keyset when the TPM
// is used. This is described in more detail in the README file. There is
// no padding in this case, and the size of plain_text needs to be a
// multiple of the AES block size (16 bytes)
// - kPaddingLibraryDefault merely defers padding to OpenSSL's implementation,
// which is PKCS#5. PKCS#5 padding pads the plaintext to the AES block
// size, extending an etire block if the plaintext is already a multiple of
// the block size. The padding is such that the decryption will weakly
// verify the contents, though as stated in the OpenSSL documents, this has
// a greater than 1/256 chance of random data passing the verification. It
// exists as an option merely for migrating older keysets, which used PKCS#5
// padding.
// - kPaddingCryptohomeDefault appends a SHA1 hash of the plaintext in
// plain_text before passing it to OpenSSL, which still uses PKCS#5 padding
// so that we do not have to re-implement block-multiple padding ourselves.
// This padding scheme allows us to strongly verify the plaintext on
// decryption, which is essential when, for example, test decrypting a nonce
// to test whether a password was correct (we do this in user_session.cc).
//
// The block mode switches between ECB and CBC. Generally, CBC is used for most
// AES crypto that we perform, since it is a better mode for us for data that is
// larger than the block size. We use ECB only when mixing the user passkey
// into the TPM-encrypted blob, since we only encrypt a single block of that
// data.
bool Crypto::AesEncryptSpecifyBlockMode(const chromeos::Blob& plain_text,
unsigned int start, unsigned int count,
const SecureBlob& key,
const SecureBlob& iv,
PaddingScheme padding,
BlockMode block_mode,
SecureBlob* encrypted) const {
// Verify that the range is within the data passed
if ((start > plain_text.size()) ||
((start + count) > plain_text.size()) ||
((start + count) < start)) {
return false;
}
if (count > static_cast<unsigned int>(std::numeric_limits<int>::max())) {
// EVP_EncryptUpdate takes a signed int
return false;
}
// First set the output size based on the padding scheme. No padding means
// that the input needs to be a multiple of the block size, and the output
// size is equal to the input size. Library default means we should allocate
// up to a full block additional for the PKCS#5 padding. Cryptohome default
// means we should allocate a full block additional for the PKCS#5 padding and
// enough for a SHA1 hash.
unsigned int block_size = GetAesBlockSize();
unsigned int needed_size = count;
switch (padding) {
case kPaddingCryptohomeDefault:
// The AES block size and SHA digest length are not enough for this to
// overflow, as needed_size is initialized to count, which must be <=
// INT_MAX, but needed_size is itself an unsigned. The block size and
// digest length are fixed by the algorithm.
needed_size += block_size + SHA_DIGEST_LENGTH;
break;
case kPaddingLibraryDefault:
// The AES block size is not enough for this to overflow, as needed_size
// is initialized to count, which must be <= INT_MAX, but needed_size is
// itself an unsigned. The block size is fixed by the algorithm.
needed_size += block_size;
break;
case kPaddingNone:
if (count % block_size) {
LOG(ERROR) << "Data size (" << count << ") was not a multiple "
<< "of the block size (" << block_size << ")";
return false;
}
break;
default:
LOG(ERROR) << "Invalid padding specified";
return false;
break;
}
SecureBlob cipher_text(needed_size);
// Set the block mode
const EVP_CIPHER* cipher;
switch (block_mode) {
case kCbc:
cipher = EVP_aes_256_cbc();
break;
case kEcb:
cipher = EVP_aes_256_ecb();
break;
default:
LOG(ERROR) << "Invalid block mode specified";
return false;
}
if (key.size() != static_cast<unsigned int>(EVP_CIPHER_key_length(cipher))) {
LOG(ERROR) << "Invalid key length of " << key.size()
<< ", expected " << EVP_CIPHER_key_length(cipher);
return false;
}
// ECB ignores the IV, so only check the IV length if we are using a different
// block mode.
if ((block_mode != kEcb) &&
(iv.size() != static_cast<unsigned int>(EVP_CIPHER_iv_length(cipher)))) {
LOG(ERROR) << "Invalid iv length of " << iv.size()
<< ", expected " << EVP_CIPHER_iv_length(cipher);
return false;
}
// Initialize the OpenSSL crypto context
EVP_CIPHER_CTX encryption_context;
EVP_CIPHER_CTX_init(&encryption_context);
EVP_EncryptInit_ex(&encryption_context, cipher, NULL,
static_cast<const unsigned char*>(key.const_data()),
static_cast<const unsigned char*>(iv.const_data()));
if (padding == kPaddingNone) {
EVP_CIPHER_CTX_set_padding(&encryption_context, 0);
}
// First, encrypt the plain_text data
unsigned int current_size = 0;
int encrypt_size = 0;
if (!EVP_EncryptUpdate(&encryption_context, &cipher_text[current_size],
&encrypt_size, &plain_text[start], count)) {
LOG(ERROR) << "EncryptUpdate failed";
EVP_CIPHER_CTX_cleanup(&encryption_context);
return false;
}
current_size += encrypt_size;
encrypt_size = 0;
// Next, if the padding uses the cryptohome default scheme, encrypt a SHA1
// hash of the preceding plain_text into the output data
if (padding == kPaddingCryptohomeDefault) {
SHA_CTX sha_context;
unsigned char md_value[SHA_DIGEST_LENGTH];
SHA1_Init(&sha_context);
SHA1_Update(&sha_context, &plain_text[start], count);
SHA1_Final(md_value, &sha_context);
if (!EVP_EncryptUpdate(&encryption_context, &cipher_text[current_size],
&encrypt_size, md_value, sizeof(md_value))) {
LOG(ERROR) << "EncryptUpdate failed";
EVP_CIPHER_CTX_cleanup(&encryption_context);
return false;
}
current_size += encrypt_size;
encrypt_size = 0;
}
// Finally, finish the encryption
if (!EVP_EncryptFinal_ex(&encryption_context, &cipher_text[current_size],
&encrypt_size)) {
LOG(ERROR) << "EncryptFinal failed";
EVP_CIPHER_CTX_cleanup(&encryption_context);
return false;
}
current_size += encrypt_size;
cipher_text.resize(current_size);
encrypted->swap(cipher_text);
EVP_CIPHER_CTX_cleanup(&encryption_context);
return true;
}
void Crypto::GetSha1(const chromeos::Blob& data, unsigned int start,
unsigned int count, SecureBlob* hash) const {
if (start > data.size() ||
((start + count) > data.size()) ||
((start + count) < start)) {
return;
}
SHA_CTX sha_context;
unsigned char md_value[SHA_DIGEST_LENGTH];
SHA1_Init(&sha_context);
SHA1_Update(&sha_context, &data[start], count);
SHA1_Final(md_value, &sha_context);
hash->resize(sizeof(md_value));
memcpy(hash->data(), md_value, sizeof(md_value));
}
bool Crypto::AesDecrypt(const chromeos::Blob& encrypted, unsigned int start,
unsigned int count, const SecureBlob& key,
const SecureBlob& iv, PaddingScheme padding,
SecureBlob* plain_text) const {
return AesDecryptSpecifyBlockMode(encrypted, start, count, key, iv, padding,
kCbc, plain_text);
}
// This is the reverse operation of AesEncryptSpecifyBlockMode above. See that
// method for a description of how padding and block_mode affect the crypto
// operations. This method automatically removes and verifies the padding, so
// plain_text (on success) will contain the original data.
//
// Note that a call to AesDecryptSpecifyBlockMode needs to have the same padding
// and block_mode as the corresponding encrypt call. Changing the block mode
// will drastically alter the decryption. And an incorrect PaddingScheme will
// result in the padding verification failing, for which the method call fails,
// even if the key and initialization vector were correct.
bool Crypto::AesDecryptSpecifyBlockMode(const chromeos::Blob& encrypted,
unsigned int start, unsigned int count,
const SecureBlob& key,
const SecureBlob& iv,
PaddingScheme padding,
BlockMode block_mode,
SecureBlob* plain_text) const {
if ((start > encrypted.size()) ||
((start + count) > encrypted.size()) ||
((start + count) < start)) {
return false;
}
SecureBlob local_plain_text(count);
if (local_plain_text.size() >
static_cast<unsigned int>(std::numeric_limits<int>::max())) {
// EVP_DecryptUpdate takes a signed int
return false;
}
int final_size = 0;
int decrypt_size = local_plain_text.size();
const EVP_CIPHER* cipher;
switch (block_mode) {
case kCbc:
cipher = EVP_aes_256_cbc();
break;
case kEcb:
cipher = EVP_aes_256_ecb();
break;
default:
LOG(ERROR) << "Invalid block mode specified: " << block_mode;
return false;
}
if (key.size() != static_cast<unsigned int>(EVP_CIPHER_key_length(cipher))) {
LOG(ERROR) << "Invalid key length of " << key.size()
<< ", expected " << EVP_CIPHER_key_length(cipher);
return false;
}
// ECB ignores the IV, so only check the IV length if we are using a different
// block mode.
if ((block_mode != kEcb) &&
(iv.size() != static_cast<unsigned int>(EVP_CIPHER_iv_length(cipher)))) {
LOG(ERROR) << "Invalid iv length of " << iv.size()
<< ", expected " << EVP_CIPHER_iv_length(cipher);
return false;
}
EVP_CIPHER_CTX decryption_context;
EVP_CIPHER_CTX_init(&decryption_context);
EVP_DecryptInit_ex(&decryption_context, cipher, NULL,
static_cast<const unsigned char*>(key.const_data()),
static_cast<const unsigned char*>(iv.const_data()));
if (padding == kPaddingNone) {
EVP_CIPHER_CTX_set_padding(&decryption_context, 0);
}
if (!EVP_DecryptUpdate(&decryption_context, &local_plain_text[0],
&decrypt_size, &encrypted[start], count)) {
LOG(ERROR) << "DecryptUpdate failed";
EVP_CIPHER_CTX_cleanup(&decryption_context);
return false;
}
if (!EVP_DecryptFinal_ex(&decryption_context, &local_plain_text[decrypt_size],
&final_size)) {
unsigned long err = ERR_get_error();
ERR_load_ERR_strings();
ERR_load_crypto_strings();
LOG(ERROR) << "DecryptFinal Error: " << err
<< ": " << ERR_lib_error_string(err)
<< ", " << ERR_func_error_string(err)
<< ", " << ERR_reason_error_string(err);
EVP_CIPHER_CTX_cleanup(&decryption_context);
return false;
}
final_size += decrypt_size;
if (padding == kPaddingCryptohomeDefault) {
if (final_size < SHA_DIGEST_LENGTH) {
LOG(ERROR) << "Plain text was too small.";
EVP_CIPHER_CTX_cleanup(&decryption_context);
return false;
}
final_size -= SHA_DIGEST_LENGTH;
SHA_CTX sha_context;
unsigned char md_value[SHA_DIGEST_LENGTH];
SHA1_Init(&sha_context);
SHA1_Update(&sha_context, &local_plain_text[0], final_size);
SHA1_Final(md_value, &sha_context);
const unsigned char* md_ptr =
static_cast<const unsigned char*>(local_plain_text.const_data());
md_ptr += final_size;
// TODO(fes): Port/use SafeMemcmp
if (memcmp(md_ptr, md_value, SHA_DIGEST_LENGTH)) {
LOG(ERROR) << "Digest verification failed.";
EVP_CIPHER_CTX_cleanup(&decryption_context);
return false;
}
}
local_plain_text.resize(final_size);
plain_text->swap(local_plain_text);
EVP_CIPHER_CTX_cleanup(&decryption_context);
return true;
}
bool Crypto::PasskeyToAesKey(const chromeos::Blob& passkey,
const chromeos::Blob& salt, unsigned int rounds,
SecureBlob* key, SecureBlob* iv) const {
if (salt.size() != PKCS5_SALT_LEN) {
LOG(ERROR) << "Bad salt size.";
return false;
}
const EVP_CIPHER* cipher = EVP_aes_256_cbc();
SecureBlob aes_key(EVP_CIPHER_key_length(cipher));
SecureBlob local_iv(EVP_CIPHER_iv_length(cipher));
// Convert the passkey to a key
if (!EVP_BytesToKey(cipher,
EVP_sha1(),
&salt[0],
&passkey[0],
passkey.size(),
rounds,
static_cast<unsigned char*>(aes_key.data()),
static_cast<unsigned char*>(local_iv.data()))) {
LOG(ERROR) << "Failure converting bytes to key";
return false;
}
key->swap(aes_key);
if (iv) {
iv->swap(local_iv);
}
return true;
}
bool Crypto::CreateRsaKey(unsigned int key_bits,
SecureBlob* n,
SecureBlob* p) const {
RSA* rsa = RSA_generate_key(key_bits, kWellKnownExponent, NULL, NULL);
if (rsa == NULL) {
LOG(ERROR) << "RSA key generation failed.";
return false;
}
SecureBlob local_n(BN_num_bytes(rsa->n));
if (BN_bn2bin(rsa->n, static_cast<unsigned char*>(local_n.data())) <= 0) {
LOG(ERROR) << "Unable to get modulus from RSA key.";
RSA_free(rsa);
return false;
}
SecureBlob local_p(BN_num_bytes(rsa->p));
if (BN_bn2bin(rsa->p, static_cast<unsigned char*>(local_p.data())) <= 0) {
LOG(ERROR) << "Unable to get private key from RSA key.";
RSA_free(rsa);
return false;
}
RSA_free(rsa);
n->swap(local_n);
p->swap(local_p);
return true;
}
void Crypto::PasskeyToKeysetKey(const chromeos::Blob& passkey,
const chromeos::Blob& salt, unsigned int iters,
SecureBlob* key) const {
unsigned int update_length = passkey.size();
unsigned int holder_size = SHA_DIGEST_LENGTH;
if (update_length > SHA_DIGEST_LENGTH) {
holder_size = update_length;
}
SecureBlob holder(holder_size);
memcpy(&holder[0], &passkey[0], update_length);
// Repeatedly hash the user passkey and salt to generate the key
for (unsigned int i = 0; i < iters; ++i) {
SHA_CTX sha_context;
unsigned char md_value[SHA_DIGEST_LENGTH];
SHA1_Init(&sha_context);
SHA1_Update(&sha_context, &salt[0], salt.size());
SHA1_Update(&sha_context, &holder[0], update_length);
SHA1_Final(md_value, &sha_context);
memcpy(&holder[0], md_value, SHA_DIGEST_LENGTH);
update_length = SHA_DIGEST_LENGTH;
}
holder.resize(update_length);
SecureBlob local_key(update_length * 2);
AsciiEncodeToBuffer(holder, static_cast<char*>(local_key.data()),
local_key.size());
key->swap(local_key);
}
bool Crypto::GetOrCreateSalt(const FilePath& path, unsigned int length,
bool force, SecureBlob* salt) const {
SecureBlob local_salt;
if (force || !file_util::PathExists(path)) {
// If this salt doesn't exist, automatically create it
local_salt.resize(length);
GetSecureRandom(static_cast<unsigned char*>(local_salt.data()),
local_salt.size());
unsigned int data_written = file_util::WriteFile(path,
static_cast<const char*>(local_salt.const_data()),
length);
if (data_written != length) {
LOG(ERROR) << "Could not write user salt";
return false;
}
} else {
// Otherwise just load the contents of the salt
int64 file_size;
if (!file_util::GetFileSize(path, &file_size)) {
LOG(ERROR) << "Could not get size of " << path.value();
return false;
}
// Compare to the max of a 32-bit signed integer
if (file_size > static_cast<int64>(std::numeric_limits<int>::max())) {
LOG(ERROR) << "File " << path.value() << " is too large: " << file_size;
return false;
}
local_salt.resize(file_size);
unsigned int data_read = file_util::ReadFile(path,
static_cast<char*>(local_salt.data()),
local_salt.size());
if (data_read != file_size) {
LOG(ERROR) << "Could not read entire file " << file_size;
return false;
}
}
salt->swap(local_salt);
return true;
}
bool Crypto::AddKeyset(const VaultKeyset& vault_keyset,
std::string* key_signature,
std::string* filename_key_signature) const {
// Add the File Encryption key (FEK) from the vault keyset. This is the key
// that is used to encrypt the file contents when it is persisted to the lower
// filesystem by eCryptfs.
*key_signature = chromeos::AsciiEncode(vault_keyset.FEK_SIG());
if (!PushVaultKey(vault_keyset.FEK(), *key_signature,
vault_keyset.FEK_SALT())) {
LOG(ERROR) << "Couldn't add ecryptfs key to keyring";
return false;
}
// Add the File Name Encryption Key (FNEK) from the vault keyset. This is the
// key that is used to encrypt the file name when it is persisted to the lower
// filesystem by eCryptfs.
*filename_key_signature = chromeos::AsciiEncode(vault_keyset.FNEK_SIG());
if (!PushVaultKey(vault_keyset.FNEK(), *filename_key_signature,
vault_keyset.FNEK_SALT())) {
LOG(ERROR) << "Couldn't add ecryptfs filename encryption key to keyring";
return false;
}
return true;
}
void Crypto::ClearKeyset() const {
Platform::ClearUserKeyring();
}
Crypto::CryptoError Crypto::TpmErrorToCrypto(
Tpm::TpmRetryAction retry_action) const {
switch (retry_action) {
case Tpm::Fatal:
return Crypto::CE_TPM_FATAL;
case Tpm::RetryCommFailure:
return Crypto::CE_TPM_COMM_ERROR;
case Tpm::RetryDefendLock:
return Crypto::CE_TPM_DEFEND_LOCK;
default:
return Crypto::CE_NONE;
}
}
bool Crypto::PushVaultKey(const SecureBlob& key, const std::string& key_sig,
const SecureBlob& salt) const {
DCHECK(key.size() == ECRYPTFS_MAX_KEY_BYTES);
DCHECK(key_sig.length() == (ECRYPTFS_SIG_SIZE * 2));
DCHECK(salt.size() == ECRYPTFS_SALT_SIZE);
struct ecryptfs_auth_tok auth_token;
generate_payload(&auth_token, const_cast<char*>(key_sig.c_str()),
const_cast<char*>(reinterpret_cast<const char*>(&salt[0])),
const_cast<char*>(reinterpret_cast<const char*>(&key[0])));
if (ecryptfs_add_auth_tok_to_keyring(&auth_token,
const_cast<char*>(key_sig.c_str())) < 0) {
LOG(ERROR) << "PushVaultKey failed";
}
return true;
}
void Crypto::PasswordToPasskey(const char* password,
const chromeos::Blob& salt,
SecureBlob* passkey) {
CHECK(password);
std::string ascii_salt = chromeos::AsciiEncode(salt);
// Convert a raw password to a password hash
SHA256_CTX sha_context;
SecureBlob md_value(SHA256_DIGEST_LENGTH);
SHA256_Init(&sha_context);
SHA256_Update(&sha_context,
reinterpret_cast<const unsigned char*>(ascii_salt.data()),
static_cast<unsigned int>(ascii_salt.length()));
SHA256_Update(&sha_context, password, strlen(password));
SHA256_Final(static_cast<unsigned char*>(md_value.data()), &sha_context);
md_value.resize(SHA256_DIGEST_LENGTH / 2);
SecureBlob local_passkey(SHA256_DIGEST_LENGTH);
AsciiEncodeToBuffer(md_value, static_cast<char*>(local_passkey.data()),
local_passkey.size());
passkey->swap(local_passkey);
}
void Crypto::AsciiEncodeToBuffer(const chromeos::Blob& blob, char* buffer,
unsigned int buffer_length) {
const char hex_chars[] = "0123456789abcdef";
unsigned int i = 0;
for (chromeos::Blob::const_iterator it = blob.begin();
it < blob.end() && (i + 1) < buffer_length; ++it) {
buffer[i++] = hex_chars[((*it) >> 4) & 0x0f];
buffer[i++] = hex_chars[(*it) & 0x0f];
}
if (i < buffer_length) {
buffer[i] = '\0';
}
}
bool Crypto::DecryptVaultKeyset(const SerializedVaultKeyset& serialized,
const chromeos::Blob& vault_key,
unsigned int* crypt_flags, CryptoError* error,
VaultKeyset* vault_keyset) const {
if (crypt_flags) {
*crypt_flags = 0;
}
if (error) {
*error = CE_NONE;
}
SecureBlob local_encrypted_keyset(serialized.wrapped_keyset().length());
serialized.wrapped_keyset().copy(
static_cast<char*>(local_encrypted_keyset.data()),
serialized.wrapped_keyset().length(), 0);
SecureBlob salt(serialized.salt().length());
serialized.salt().copy(static_cast<char*>(salt.data()),
serialized.salt().length(), 0);
// On decrypt, default to the legacy password rounds, and use the value in the
// SerializedVaultKeyset if it exists
unsigned int rounds;
if (serialized.has_password_rounds()) {
rounds = serialized.password_rounds();
} else {
rounds = kDefaultLegacyPasswordRounds;
}
SecureBlob local_vault_key(vault_key.begin(), vault_key.end());
// Check if the vault keyset was TPM-wrapped
if ((serialized.flags() & SerializedVaultKeyset::TPM_WRAPPED)
&& serialized.has_tpm_key()) {
if (crypt_flags) {
*crypt_flags |= SerializedVaultKeyset::TPM_WRAPPED;
}
// If the TPM is enabled but not owned, and the keyset is TPM wrapped, then
// it means the TPM has been cleared since the last login, and is not
// re-owned. In this case, the SRK is cleared and we cannot recover the
// keyset.
if (tpm_->IsEnabled() && !tpm_->IsOwned()) {
LOG(ERROR) << "Fatal error--the TPM is enabled but not owned, and this "
<< "keyset was wrapped by the TPM. It is impossible to "
<< "recover this keyset.";
if (error) {
*error = CE_TPM_FATAL;
}
return false;
}
Crypto::CryptoError local_error = EnsureTpm(false);
if (!tpm_->IsConnected()) {
LOG(ERROR) << "Vault keyset is wrapped by the TPM, but the TPM is "
<< "unavailable";
if (error) {
*error = local_error;
}
return false;
}
// Check if the public key for this keyset matches the public key on this
// TPM. If not, we cannot recover.
Tpm::TpmRetryAction retry_action;
if (serialized.has_tpm_public_key_hash()) {
SecureBlob serialized_pub_key_hash(
serialized.tpm_public_key_hash().length());
serialized.tpm_public_key_hash().copy(
static_cast<char*>(serialized_pub_key_hash.data()),
serialized.tpm_public_key_hash().length(),
0);
SecureBlob pub_key;
if (!tpm_->GetPublicKey(&pub_key, &retry_action)) {
LOG(ERROR) << "Unable to get the cryptohome public key from the TPM.";
if (error) {
// This is a fatal error only if we don't get a transient error code.
*error = TpmErrorToCrypto(retry_action);
}
return false;
}
SecureBlob pub_key_hash;
GetSha1(pub_key, 0, pub_key.size(), &pub_key_hash);
if ((serialized_pub_key_hash.size() != pub_key_hash.size()) ||
(memcmp(serialized_pub_key_hash.data(),
pub_key_hash.data(),
pub_key_hash.size()))) {
LOG(ERROR) << "Fatal key error--the cryptohome public key does not "
<< "match the one used to encrypt this keyset.";
if (error) {
*error = CE_TPM_FATAL;
}
return false;
}
}
SecureBlob tpm_key(serialized.tpm_key().length());
serialized.tpm_key().copy(static_cast<char*>(tpm_key.data()),
serialized.tpm_key().length(), 0);
if (!tpm_->Decrypt(tpm_key, vault_key, rounds, salt,
&local_vault_key, &retry_action)) {
LOG(ERROR) << "The TPM failed to unwrap the intermediate key with the "
<< "supplied credentials";
if (error) {
// This is a fatal error only if we don't get a transient error code.
*error = TpmErrorToCrypto(retry_action);
}
return false;
}
}
SecureBlob plain_text;
if (serialized.flags() & SerializedVaultKeyset::SCRYPT_WRAPPED) {
if (crypt_flags) {
*crypt_flags |= SerializedVaultKeyset::SCRYPT_WRAPPED;
}
size_t out_len = serialized.wrapped_keyset().length();
SecureBlob decrypted(out_len);
int scrypt_rc;
if ((scrypt_rc = scryptdec_buf(
static_cast<const uint8_t*>(local_encrypted_keyset.const_data()),
local_encrypted_keyset.size(),
static_cast<uint8_t*>(decrypted.data()),
&out_len,
static_cast<const uint8_t*>(&vault_key[0]),
vault_key.size(),
kScryptMaxMem,
100.0,
kScryptMaxDecryptTime))) {
LOG(ERROR) << "Scrypt decryption failed: " << scrypt_rc;
if (error) {
*error = CE_SCRYPT_CRYPTO;
}
return false;
}
decrypted.resize(out_len);
SecureBlob hash;
unsigned int hash_offset = decrypted.size() - SHA_DIGEST_LENGTH;
GetSha1(decrypted, 0, hash_offset, &hash);
if (memcmp(hash.data(), &decrypted[hash_offset], hash.size())) {
LOG(ERROR) << "Scrypt hash verification failed";
if (error) {
*error = CE_SCRYPT_CRYPTO;
}
return false;
}
decrypted.resize(hash_offset);
plain_text.swap(decrypted);
} else {
SecureBlob aes_key;
SecureBlob iv;
if (!PasskeyToAesKey(local_vault_key,
salt,
rounds,
&aes_key,
&iv)) {
LOG(ERROR) << "Failure converting passkey to key";
if (error) {
*error = CE_OTHER_FATAL;
}
return false;
}
if (!AesDecrypt(local_encrypted_keyset, 0, local_encrypted_keyset.size(),
aes_key, iv, kPaddingCryptohomeDefault, &plain_text)) {
LOG(ERROR) << "AES decryption failed.";
if (error) {
*error = CE_OTHER_CRYPTO;
}
return false;
}
}
vault_keyset->FromKeysBlob(plain_text);
// Check if the public key hash was stored
if (!serialized.has_tpm_public_key_hash() && serialized.has_tpm_key()) {
if (error) {
*error = CE_NO_PUBLIC_KEY_HASH;
}
}
return true;
}
bool Crypto::EncryptVaultKeyset(const VaultKeyset& vault_keyset,
const SecureBlob& vault_key,
const SecureBlob& vault_key_salt,
SerializedVaultKeyset* serialized) const {
SecureBlob keyset_blob;
if (!vault_keyset.ToKeysBlob(&keyset_blob)) {
LOG(ERROR) << "Failure serializing keyset to buffer";
return false;
}
unsigned int rounds = kDefaultPasswordRounds;
bool tpm_wrapped = false;
// The local_vault_key is used as the AES key to encrypt the user's vault
// keyset (vault keyset key, or VKK). We initialize it to the passed-in
// vault_key, which is the user's passkey.
SecureBlob local_vault_key(vault_key.begin(), vault_key.end());
SecureBlob tpm_key;
// Check if the vault keyset should be TPM-wrapped
if (use_tpm_) {
// Ignore the status here. When the TPM is not available, we merely fall
// back to scrypt.
EnsureTpm(false);
}
// If the TPM is available, then instead of using the user's passkey as the
// local_vault_key, we generate a random string of bytes to use as the VKK
// instead. In this case, the user's passkey is used with the TPM to store
// the encrypted VKK in the serialized keyset. To decrypt the vault keyset,
// the VKK must be decrypted using the TPM+user passkey, and then the VKK is
// used to decrypt the vault keyset. For now, we allow fall-through to
// non-TPM-wrapped if tpm_ is NULL. When this happens, it uses Scrypt to
// protect the vault keyset.
if (use_tpm_ && tpm_ != NULL && tpm_->IsConnected()) {
// If we are using the TPM, the local_vault_key becomes a
// randomly-generated AES key. It is this random key that actually encrypts
// the vault keyset, and itself is wrapped by the TPM.
local_vault_key.resize(kDefaultAesKeySize);
GetSecureRandom(static_cast<unsigned char*>(local_vault_key.data()),
local_vault_key.size());
Tpm::TpmRetryAction retry_action;
// Encrypt the VKK using the TPM and the user's passkey. The output is an
// encrypted blob in tpm_key, which is stored in the serialized vault
// keyset.
if (tpm_->Encrypt(local_vault_key, vault_key, rounds, vault_key_salt,
&tpm_key, &retry_action)) {
tpm_wrapped = true;
} else {
// Re-place the user's passkey into the VKK. This allows fallback to
// Scrypt-based protection when the TPM didn't work for any reason.
local_vault_key.resize(vault_key.size());
memcpy(local_vault_key.data(), vault_key.const_data(),
vault_key.size());
LOG(ERROR) << "The TPM failed to wrap the intermediate key with the "
<< "supplied credentials. The vault keyset will not be "
<< "further secured by the TPM.";
}
}
SecureBlob cipher_text;
bool scrypt_wrapped = false;
// If scrypt is enabled (it is by default, but it can be disabled for unit
// tests), and the TPM wasn't used, then we use scrypt to encrypt the keyset.
// Otherwise, we use AES with whatever the VKK is. When the TPM is available,
// the VKK is a random key (see above).
if (fallback_to_scrypt_ && !tpm_wrapped) {
// Append the SHA1 hash of the keyset blob
SecureBlob hash;
GetSha1(keyset_blob, 0, keyset_blob.size(), &hash);
SecureBlob local_keyset_blob(keyset_blob.size() + hash.size());
memcpy(&local_keyset_blob[0], keyset_blob.const_data(), keyset_blob.size());
memcpy(&local_keyset_blob[keyset_blob.size()], hash.data(), hash.size());
cipher_text.resize(local_keyset_blob.size() + kScryptHeaderLength);
int scrypt_rc;
if ((scrypt_rc = scryptenc_buf(
static_cast<const uint8_t*>(local_keyset_blob.const_data()),
local_keyset_blob.size(),
static_cast<uint8_t*>(cipher_text.data()),
static_cast<const uint8_t*>(&vault_key[0]),
vault_key.size(),
kScryptMaxMem,
100.0,
kScryptMaxEncryptTime))) {
LOG(ERROR) << "Scrypt encryption failed: " << scrypt_rc;
return false;
}
scrypt_wrapped = true;
} else {
SecureBlob aes_key;
SecureBlob iv;
if (!PasskeyToAesKey(local_vault_key, vault_key_salt,
rounds, &aes_key, &iv)) {
LOG(ERROR) << "Failure converting passkey to key";
return false;
}
if (!AesEncrypt(keyset_blob, 0, keyset_blob.size(), aes_key, iv,
kPaddingCryptohomeDefault, &cipher_text)) {
LOG(ERROR) << "AES encryption failed.";
return false;
}
}
unsigned int keyset_flags = SerializedVaultKeyset::NONE;
if (tpm_wrapped) {
// Store the TPM-encrypted key
keyset_flags = SerializedVaultKeyset::TPM_WRAPPED;
serialized->set_tpm_key(tpm_key.const_data(),
tpm_key.size());
// Store the hash of the cryptohome public key
SecureBlob pub_key;
Tpm::TpmRetryAction retry_action;
// Allow this to fail. It is not absolutely necessary; it allows us to
// detect a TPM clear. If this fails due to a transient issue, then on next
// successful login, the vault keyset will be re-saved anyway.
if (tpm_->GetPublicKey(&pub_key, &retry_action)) {
SecureBlob pub_key_hash;
GetSha1(pub_key, 0, pub_key.size(), &pub_key_hash);
serialized->set_tpm_public_key_hash(pub_key_hash.const_data(),
pub_key_hash.size());
}
}
if (scrypt_wrapped) {
keyset_flags |= SerializedVaultKeyset::SCRYPT_WRAPPED;
}
serialized->set_flags(keyset_flags);
serialized->set_salt(vault_key_salt.const_data(),
vault_key_salt.size());
serialized->set_wrapped_keyset(cipher_text.const_data(), cipher_text.size());
serialized->set_password_rounds(rounds);
return true;
}
// TODO(fes): In the future, this and EncryptVaultKeysetOld should be removed.
// "Future" here means when we are at a state where we no longer need to convert
// legacy cryptohome vaults to the current version.
bool Crypto::DecryptVaultKeysetOld(const chromeos::Blob& encrypted_keyset,
const chromeos::Blob& vault_key,
VaultKeyset* vault_keyset) const {
unsigned int header_size = kOpenSSLMagic.length() + PKCS5_SALT_LEN;
if (encrypted_keyset.size() < header_size) {
LOG(ERROR) << "Master key file too short";
return false;
}
// Grab the salt used in converting the passkey to a key (OpenSSL
// passkey-encrypted files have the format:
// Salted__<8-byte-salt><ciphertext>)
SecureBlob salt(PKCS5_SALT_LEN);
memcpy(salt.data(), &encrypted_keyset[kOpenSSLMagic.length()],
PKCS5_SALT_LEN);
SecureBlob aes_key;
SecureBlob iv;
if (!PasskeyToAesKey(vault_key,
salt,
kDefaultLegacyPasswordRounds,
&aes_key,
&iv)) {
LOG(ERROR) << "Failure converting passkey to key";
return false;
}
SecureBlob plain_text;
if (!AesDecryptSpecifyBlockMode(encrypted_keyset,
header_size,
encrypted_keyset.size() - header_size,
aes_key,
iv,
kPaddingLibraryDefault,
kEcb,
&plain_text)) {
LOG(ERROR) << "AES decryption failed.";
return false;
}
OldVaultKeyset old_keyset;
old_keyset.AssignBuffer(plain_text);
vault_keyset->FromVaultKeyset(old_keyset);
return true;
}
bool Crypto::EncryptVaultKeysetOld(const VaultKeyset& vault_keyset,
const SecureBlob& vault_key,
const SecureBlob& vault_key_salt,
SecureBlob* encrypted) const {
OldVaultKeyset old_keyset;
old_keyset.FromVaultKeyset(vault_keyset);
SecureBlob keyset_blob;
if (!old_keyset.ToBuffer(&keyset_blob)) {
LOG(ERROR) << "Failure serializing keyset to buffer";
return false;
}
SecureBlob aes_key;
SecureBlob iv;
if (!PasskeyToAesKey(vault_key,
vault_key_salt,
kDefaultLegacyPasswordRounds,
&aes_key,
&iv)) {
LOG(ERROR) << "Failure converting passkey to key";
return false;
}
SecureBlob cipher_text;
if (!AesEncryptSpecifyBlockMode(keyset_blob,
0,
keyset_blob.size(),
aes_key,
iv,
kPaddingLibraryDefault,
kEcb,
&cipher_text)) {
LOG(ERROR) << "AES encryption failed.";
return false;
}
unsigned int header_size = kOpenSSLMagic.length() + PKCS5_SALT_LEN;
SecureBlob final_blob(header_size + cipher_text.size());
memcpy(&final_blob[0], kOpenSSLMagic.c_str(), kOpenSSLMagic.length());
memcpy(&final_blob[kOpenSSLMagic.length()], vault_key_salt.const_data(),
PKCS5_SALT_LEN);
memcpy(&final_blob[header_size], cipher_text.data(), cipher_text.size());
encrypted->swap(final_blob);
return true;
}
} // namespace cryptohome