blob: 79f8d8e2f203931777a2ded96e5144749e7f34cd [file] [log] [blame]
// Copyright 2020 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.
#include "cryptohome/tpm_auth_block.h"
#include <map>
#include <string>
#include <base/optional.h>
#include "cryptohome/crypto.h"
#include "cryptohome/crypto_error.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/cryptolib.h"
#include "cryptohome/tpm.h"
#include "cryptohome/tpm_init.h"
#include "vault_keyset.pb.h" // NOLINT(build/include)
namespace cryptohome {
namespace {
CryptoError TpmErrorToCrypto(Tpm::TpmRetryAction retry_action) {
switch (retry_action) {
case Tpm::kTpmRetryFatal:
// All errors mapped here will cause re-creating the cryptohome if
// they occur when decrypting the keyset.
return CryptoError::CE_TPM_FATAL;
case Tpm::kTpmRetryCommFailure:
case Tpm::kTpmRetryInvalidHandle:
case Tpm::kTpmRetryLoadFail:
case Tpm::kTpmRetryLater:
return CryptoError::CE_TPM_COMM_ERROR;
case Tpm::kTpmRetryDefendLock:
return CryptoError::CE_TPM_DEFEND_LOCK;
case Tpm::kTpmRetryReboot:
return CryptoError::CE_TPM_REBOOT;
default:
// TODO(chromium:709646): kTpmRetryFailNoRetry maps here now. Find
// a better corresponding CryptoError.
return CryptoError::CE_NONE;
}
}
bool TpmErrorIsRetriable(Tpm::TpmRetryAction retry_action) {
return retry_action == Tpm::kTpmRetryLoadFail ||
retry_action == Tpm::kTpmRetryInvalidHandle ||
retry_action == Tpm::kTpmRetryCommFailure;
}
} // namespace
TpmAuthBlock::TpmAuthBlock(Tpm* tpm, TpmInit* tpm_init)
: tpm_(tpm), tpm_init_(tpm_init) {
CHECK(tpm != nullptr);
CHECK(tpm_init_ != nullptr);
}
bool TpmAuthBlock::Derive(const AuthInput& user_input,
const AuthBlockState& state,
KeyBlobs* key_out_data,
CryptoError* error) {
DCHECK(key_out_data);
CHECK(state.vault_keyset != base::nullopt);
const SerializedVaultKeyset& serialized = state.vault_keyset.value();
if (!serialized.has_tpm_key()) {
LOG(ERROR) << "Decrypting with TPM, but no tpm key present";
ReportCryptohomeError(kDecryptAttemptButTpmKeyMissing);
PopulateError(error, CryptoError::CE_TPM_FATAL);
return false;
}
// 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.";
ReportCryptohomeError(kDecryptAttemptButTpmNotOwned);
PopulateError(error, CryptoError::CE_TPM_FATAL);
return false;
}
if (!tpm_init_->HasCryptohomeKey()) {
tpm_init_->SetupTpm(/*load_key=*/true);
}
if (!tpm_init_->HasCryptohomeKey()) {
LOG(ERROR) << "Vault keyset is wrapped by the TPM, but the TPM is "
<< "unavailable";
ReportCryptohomeError(kDecryptAttemptButTpmNotAvailable);
PopulateError(error, CryptoError::CE_TPM_COMM_ERROR);
return false;
}
// This is a sanity check that the keys still match.
if (serialized.has_tpm_public_key_hash()) {
if (!IsTPMPubkeyHash(serialized.tpm_public_key_hash(), error)) {
LOG(ERROR) << "TPM public key hash mismatch.";
ReportCryptohomeError(kDecryptAttemptButTpmKeyMismatch);
return false;
}
}
key_out_data->vkk_iv = brillo::SecureBlob(kAesBlockSize);
key_out_data->vkk_key = brillo::SecureBlob(kDefaultAesKeySize);
bool locked_to_single_user =
user_input.locked_to_single_user.value_or(false);
brillo::SecureBlob salt(serialized.salt().begin(), serialized.salt().end());
brillo::SecureBlob tpm_key =
GetTpmKeyFromSerialized(serialized, locked_to_single_user);
bool is_pcr_bound = serialized.flags() & SerializedVaultKeyset::PCR_BOUND;
if (is_pcr_bound) {
if (!DecryptTpmBoundToPcr(user_input.user_input.value(), tpm_key, salt,
error, &key_out_data->vkk_iv.value(),
&key_out_data->vkk_key.value())) {
return false;
}
} else {
if (!DecryptTpmNotBoundToPcr(
serialized, user_input.user_input.value(), tpm_key, salt, error,
&key_out_data->vkk_iv.value(), &key_out_data->vkk_key.value())) {
return false;
}
}
key_out_data->chaps_iv = key_out_data->vkk_iv;
key_out_data->authorization_data_iv = key_out_data->vkk_iv;
key_out_data->wrapped_reset_seed = brillo::SecureBlob();
key_out_data->wrapped_reset_seed.value().assign(
serialized.wrapped_reset_seed().begin(),
serialized.wrapped_reset_seed().end());
if (!serialized.has_tpm_public_key_hash() && error) {
*error = CryptoError::CE_NO_PUBLIC_KEY_HASH;
}
return true;
}
bool TpmAuthBlock::IsTPMPubkeyHash(const std::string& hash,
CryptoError* error) const {
brillo::SecureBlob pub_key_hash;
Tpm::TpmRetryAction retry_action =
tpm_->GetPublicKeyHash(tpm_init_->GetCryptohomeKey(), &pub_key_hash);
if (retry_action == Tpm::kTpmRetryLoadFail ||
retry_action == Tpm::kTpmRetryInvalidHandle) {
if (!tpm_init_->ReloadCryptohomeKey()) {
LOG(ERROR) << "Unable to reload key.";
retry_action = Tpm::kTpmRetryFailNoRetry;
} else {
retry_action =
tpm_->GetPublicKeyHash(tpm_init_->GetCryptohomeKey(), &pub_key_hash);
}
}
if (retry_action != Tpm::kTpmRetryNone) {
LOG(ERROR) << "Unable to get the cryptohome public key from the TPM.";
ReportCryptohomeError(kCannotReadTpmPublicKey);
PopulateError(error, TpmErrorToCrypto(retry_action));
return false;
}
if ((hash.size() != pub_key_hash.size()) ||
(brillo::SecureMemcmp(hash.data(), pub_key_hash.data(),
pub_key_hash.size()))) {
PopulateError(error, CryptoError::CE_TPM_FATAL);
return false;
}
return true;
}
brillo::SecureBlob TpmAuthBlock::GetTpmKeyFromSerialized(
const SerializedVaultKeyset& serialized,
bool locked_to_single_user) const {
bool is_pcr_bound = serialized.flags() & SerializedVaultKeyset::PCR_BOUND;
auto tpm_key_data = (is_pcr_bound && locked_to_single_user)
? serialized.extended_tpm_key()
: serialized.tpm_key();
return brillo::SecureBlob(tpm_key_data);
}
bool TpmAuthBlock::DecryptTpmBoundToPcr(const brillo::SecureBlob& vault_key,
const brillo::SecureBlob& tpm_key,
const brillo::SecureBlob& salt,
CryptoError* error,
brillo::SecureBlob* vkk_iv,
brillo::SecureBlob* vkk_key) const {
brillo::SecureBlob pass_blob(kDefaultPassBlobSize);
if (!CryptoLib::DeriveSecretsScrypt(vault_key, salt, {&pass_blob, vkk_iv})) {
return false;
}
Tpm::TpmRetryAction retry_action;
for (int i = 0; i < kTpmDecryptMaxRetries; ++i) {
std::map<uint32_t, std::string> pcr_map({{kTpmSingleUserPCR, ""}});
retry_action = tpm_->UnsealWithAuthorization(
tpm_init_->GetCryptohomeKey(), tpm_key, pass_blob, pcr_map, vkk_key);
if (retry_action == Tpm::kTpmRetryNone)
return true;
if (!TpmErrorIsRetriable(retry_action))
break;
// If the error is retriable, reload the key first.
if (!tpm_init_->ReloadCryptohomeKey()) {
LOG(ERROR) << "Unable to reload Cryptohome key.";
break;
}
}
LOG(ERROR) << "Failed to unwrap vkk with creds.";
*error = TpmErrorToCrypto(retry_action);
return false;
}
bool TpmAuthBlock::DecryptTpmNotBoundToPcr(
const SerializedVaultKeyset& serialized,
const brillo::SecureBlob& vault_key,
const brillo::SecureBlob& tpm_key,
const brillo::SecureBlob& salt,
CryptoError* error,
brillo::SecureBlob* vkk_iv,
brillo::SecureBlob* vkk_key) const {
brillo::SecureBlob aes_skey(kDefaultAesKeySize);
brillo::SecureBlob kdf_skey(kDefaultAesKeySize);
brillo::SecureBlob local_vault_key(vault_key.begin(), vault_key.end());
unsigned int rounds;
if (serialized.has_password_rounds()) {
rounds = serialized.password_rounds();
} else {
rounds = kDefaultLegacyPasswordRounds;
}
bool scrypt_derived =
serialized.flags() & SerializedVaultKeyset::SCRYPT_DERIVED;
if (scrypt_derived) {
if (!CryptoLib::DeriveSecretsScrypt(vault_key, salt,
{&aes_skey, &kdf_skey, vkk_iv})) {
PopulateError(error, CryptoError::CE_OTHER_FATAL);
return false;
}
} else {
CryptoLib::PasskeyToAesKey(vault_key, salt, rounds, &aes_skey, NULL);
}
for (int i = 0; i < kTpmDecryptMaxRetries; i++) {
Tpm::TpmRetryAction retry_action =
tpm_->DecryptBlob(tpm_init_->GetCryptohomeKey(), tpm_key, aes_skey,
std::map<uint32_t, std::string>(), &local_vault_key);
if (retry_action == Tpm::kTpmRetryNone)
break;
if (!TpmErrorIsRetriable(retry_action)) {
LOG(ERROR) << "Failed to unwrap vkk with creds.";
ReportCryptohomeError(kDecryptAttemptWithTpmKeyFailed);
*error = TpmErrorToCrypto(retry_action);
return false;
}
// If the error is retriable, reload the key first.
if (!tpm_init_->ReloadCryptohomeKey()) {
LOG(ERROR) << "Unable to reload Cryptohome key.";
ReportCryptohomeError(kDecryptAttemptWithTpmKeyFailed);
*error = TpmErrorToCrypto(Tpm::kTpmRetryFailNoRetry);
return false;
}
}
if (scrypt_derived) {
*vkk_key = CryptoLib::HmacSha256(kdf_skey, local_vault_key);
} else {
if (!CryptoLib::PasskeyToAesKey(local_vault_key, salt, rounds, vkk_key,
vkk_iv)) {
LOG(ERROR) << "Failure converting IVKK to VKK";
PopulateError(error, CryptoError::CE_OTHER_FATAL);
return false;
}
}
return true;
}
} // namespace cryptohome