blob: 9183e2cf130433774b651cd312ab5110201ee939 [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/auth_blocks/tpm_not_bound_to_pcr_auth_block.h"
#include <map>
#include <string>
#include <utility>
#include <variant>
#include <base/check.h>
#include <base/logging.h>
#include <brillo/secure_blob.h>
#include <libhwsec/status.h>
#include <libhwsec-foundation/crypto/aes.h>
#include <libhwsec-foundation/crypto/hmac.h>
#include <libhwsec-foundation/crypto/scrypt.h>
#include <libhwsec-foundation/crypto/secure_blob_util.h>
#include "cryptohome/auth_blocks/tpm_auth_block_utils.h"
#include "cryptohome/crypto.h"
#include "cryptohome/crypto_error.h"
#include "cryptohome/cryptohome_keys_manager.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/error/location_utils.h"
#include "cryptohome/tpm.h"
#include "cryptohome/vault_keyset.pb.h"
using cryptohome::error::CryptohomeCryptoError;
using cryptohome::error::ErrorAction;
using cryptohome::error::ErrorActionSet;
using hwsec::TPMErrorBase;
using hwsec::TPMRetryAction;
using hwsec_foundation::CreateSecureRandomBlob;
using hwsec_foundation::DeriveSecretsScrypt;
using hwsec_foundation::HmacSha256;
using hwsec_foundation::kAesBlockSize;
using hwsec_foundation::kDefaultAesKeySize;
using hwsec_foundation::kDefaultLegacyPasswordRounds;
using hwsec_foundation::kTpmDecryptMaxRetries;
using hwsec_foundation::PasskeyToAesKey;
using hwsec_foundation::status::MakeStatus;
using hwsec_foundation::status::OkStatus;
using hwsec_foundation::status::StatusChain;
namespace cryptohome {
TpmNotBoundToPcrAuthBlock::TpmNotBoundToPcrAuthBlock(
Tpm* tpm, CryptohomeKeysManager* cryptohome_keys_manager)
: SyncAuthBlock(kTpmBackedNonPcrBound),
tpm_(tpm),
cryptohome_key_loader_(
cryptohome_keys_manager->GetKeyLoader(CryptohomeKeyType::kRSA)),
utils_(tpm, cryptohome_key_loader_) {
CHECK(tpm != nullptr);
CHECK(cryptohome_key_loader_ != nullptr);
}
CryptoStatus TpmNotBoundToPcrAuthBlock::Derive(const AuthInput& auth_input,
const AuthBlockState& state,
KeyBlobs* key_out_data) {
if (!auth_input.user_input.has_value()) {
LOG(ERROR) << "Missing user_input";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocTpmNotBoundToPcrAuthBlockNoUserInputInDerive),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
const TpmNotBoundToPcrAuthBlockState* tpm_state;
if (!(tpm_state =
std::get_if<TpmNotBoundToPcrAuthBlockState>(&state.state))) {
LOG(ERROR) << "Invalid AuthBlockState";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockInvalidBlockStateInDerive),
ErrorActionSet(
{ErrorAction::kDevCheckUnexpectedState, ErrorAction::kAuth}),
CryptoError::CE_OTHER_CRYPTO);
}
if (!tpm_state->salt.has_value()) {
LOG(ERROR) << "Invalid TpmNotBoundToPcrAuthBlockState: missing salt";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocTpmNotBoundToPcrAuthBlockNoSaltInDerive),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState,
ErrorAction::kAuth, ErrorAction::kDeleteVault}),
CryptoError::CE_OTHER_CRYPTO);
}
if (!tpm_state->tpm_key.has_value()) {
LOG(ERROR) << "Invalid TpmNotBoundToPcrAuthBlockState: missing tpm_key";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocTpmNotBoundToPcrAuthBlockNoTpmKeyInDerive),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState,
ErrorAction::kAuth, ErrorAction::kDeleteVault}),
CryptoError::CE_OTHER_CRYPTO);
}
if (!tpm_state->scrypt_derived.has_value()) {
LOG(ERROR)
<< "Invalid TpmNotBoundToPcrAuthBlockState: missing scrypt_derived";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockNoScryptDerivedInDerive),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState,
ErrorAction::kAuth, ErrorAction::kDeleteVault}),
CryptoError::CE_OTHER_CRYPTO);
}
brillo::SecureBlob tpm_public_key_hash;
if (tpm_state->tpm_public_key_hash.has_value()) {
tpm_public_key_hash = tpm_state->tpm_public_key_hash.value();
}
CryptoStatus error = utils_.CheckTPMReadiness(
tpm_state->tpm_key.has_value(),
tpm_state->tpm_public_key_hash.has_value(), tpm_public_key_hash);
if (!error.ok()) {
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockTpmNotReadyInDerive))
.Wrap(std::move(error));
}
key_out_data->vkk_iv = brillo::SecureBlob(kAesBlockSize);
key_out_data->vkk_key = brillo::SecureBlob(kDefaultAesKeySize);
brillo::SecureBlob salt = tpm_state->salt.value();
brillo::SecureBlob tpm_key = tpm_state->tpm_key.value();
error = DecryptTpmNotBoundToPcr(*tpm_state, auth_input.user_input.value(),
tpm_key, salt, &key_out_data->vkk_iv.value(),
&key_out_data->vkk_key.value());
if (!error.ok()) {
if (!tpm_state->tpm_public_key_hash.has_value()) {
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocTpmNotBoundToPcrAuthBlockNoPubKeyHashInDerive),
ErrorActionSet(
{ErrorAction::kDevCheckUnexpectedState, ErrorAction::kAuth}),
CryptoError::CE_NO_PUBLIC_KEY_HASH);
}
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockDecryptFailedInDerive),
ErrorActionSet(
{ErrorAction::kIncorrectAuth, ErrorAction::kAuth}))
.Wrap(std::move(error));
}
key_out_data->chaps_iv = key_out_data->vkk_iv;
return OkStatus<CryptohomeCryptoError>();
}
CryptoStatus TpmNotBoundToPcrAuthBlock::Create(const AuthInput& user_input,
AuthBlockState* auth_block_state,
KeyBlobs* key_blobs) {
if (!user_input.user_input.has_value()) {
LOG(ERROR) << "Missing user_input";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocTpmNotBoundToPcrAuthBlockNoUserInputInCreate),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
const brillo::SecureBlob& vault_key = user_input.user_input.value();
brillo::SecureBlob salt =
CreateSecureRandomBlob(CRYPTOHOME_DEFAULT_KEY_SALT_SIZE);
// If the cryptohome key isn't loaded, try to load it.
if (!cryptohome_key_loader_->HasCryptohomeKey())
cryptohome_key_loader_->Init();
// If the key still isn't loaded, fail the operation.
if (!cryptohome_key_loader_->HasCryptohomeKey()) {
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockNoCryptohomeKeyInCreate),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kRetry,
ErrorAction::kPowerwash}),
CryptoError::CE_TPM_CRYPTO);
}
const auto local_blob = CreateSecureRandomBlob(kDefaultAesKeySize);
brillo::SecureBlob tpm_key;
brillo::SecureBlob aes_skey(kDefaultAesKeySize);
brillo::SecureBlob kdf_skey(kDefaultAesKeySize);
brillo::SecureBlob vkk_iv(kAesBlockSize);
if (!DeriveSecretsScrypt(vault_key, salt, {&aes_skey, &kdf_skey, &vkk_iv})) {
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockScryptDeriveFailedInCreate),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
// 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.
for (int i = 0; i < kTpmDecryptMaxRetries; i++) {
hwsec::Status err =
tpm_->EncryptBlob(cryptohome_key_loader_->GetCryptohomeKey(),
local_blob, aes_skey, &tpm_key);
if (err == nullptr) {
break;
}
if (!TpmAuthBlockUtils::TPMErrorIsRetriable(err)) {
LOG(ERROR) << "Failed to wrap vkk with creds: " << err;
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockEncryptFailedInCreate),
ErrorActionSet({ErrorAction::kReboot,
ErrorAction::kDevCheckUnexpectedState}))
.Wrap(TpmAuthBlockUtils::TPMErrorToCryptohomeCryptoError(
std::move(err)));
}
// If the error is retriable, reload the key first.
if (!cryptohome_key_loader_->ReloadCryptohomeKey()) {
LOG(ERROR) << "Unable to reload Cryptohome key while createing "
"TpmNotBoundToPcrAuthBlock:"
<< err;
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockReloadKeyFailedInCreate),
ErrorActionSet({ErrorAction::kReboot}), CryptoError::CE_TPM_REBOOT);
}
}
TpmNotBoundToPcrAuthBlockState auth_state;
// 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.
brillo::SecureBlob pub_key_hash;
hwsec::Status err = tpm_->GetPublicKeyHash(
cryptohome_key_loader_->GetCryptohomeKey(), &pub_key_hash);
if (err != nullptr) {
LOG(ERROR) << "Failed to get tpm public key hash: " << err;
} else {
auth_state.tpm_public_key_hash = pub_key_hash;
}
auth_state.scrypt_derived = true;
auth_state.tpm_key = tpm_key;
auth_state.salt = std::move(salt);
// Pass back the vkk_key and vkk_iv so the generic secret wrapping can use it.
key_blobs->vkk_key = HmacSha256(kdf_skey, local_blob);
// Note that one might expect the IV to be part of the AuthBlockState. But
// since it's taken from the scrypt output, it's actually created by the auth
// block, not used to initialize the auth block.
key_blobs->vkk_iv = vkk_iv;
key_blobs->chaps_iv = vkk_iv;
*auth_block_state = AuthBlockState{.state = std::move(auth_state)};
return OkStatus<CryptohomeCryptoError>();
}
CryptoStatus TpmNotBoundToPcrAuthBlock::DecryptTpmNotBoundToPcr(
const TpmNotBoundToPcrAuthBlockState& tpm_state,
const brillo::SecureBlob& vault_key,
const brillo::SecureBlob& tpm_key,
const brillo::SecureBlob& salt,
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 = tpm_state.password_rounds.has_value()
? tpm_state.password_rounds.value()
: kDefaultLegacyPasswordRounds;
// TODO(b/204200132): check if this branch is unnecessary.
if (tpm_state.scrypt_derived.value()) {
if (!DeriveSecretsScrypt(vault_key, salt, {&aes_skey, &kdf_skey, vkk_iv})) {
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockScryptDeriveFailedInDecrypt),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_FATAL);
}
} else {
PasskeyToAesKey(vault_key, salt, rounds, &aes_skey, NULL);
}
for (int i = 0; i < kTpmDecryptMaxRetries; i++) {
hwsec::Status err =
tpm_->DecryptBlob(cryptohome_key_loader_->GetCryptohomeKey(), tpm_key,
aes_skey, &local_vault_key);
if (err == nullptr) {
break;
}
if (!TpmAuthBlockUtils::TPMErrorIsRetriable(err)) {
LOG(ERROR) << "Failed to unwrap VKK with creds: " << err;
ReportCryptohomeError(kDecryptAttemptWithTpmKeyFailed);
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockDecryptFailedInDecrypt),
ErrorActionSet({ErrorAction::kReboot,
ErrorAction::kDevCheckUnexpectedState,
ErrorAction::kAuth}),
CryptoError::CE_TPM_REBOOT)
.Wrap(TpmAuthBlockUtils::TPMErrorToCryptohomeCryptoError(
std::move(err)));
}
// If the error is retriable, reload the key first.
if (!cryptohome_key_loader_->ReloadCryptohomeKey()) {
LOG(ERROR) << "Unable to reload Cryptohome key while decrypting "
"TpmNotBoundToPcrAuthBlock:"
<< err;
ReportCryptohomeError(kDecryptAttemptWithTpmKeyFailed);
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockReloadKeyFailedInDecrypt),
ErrorActionSet({ErrorAction::kReboot}), CryptoError::CE_TPM_REBOOT);
}
}
// TODO(zuan): Handle cases in which all retries failed.
// TODO(b/204200132): check if this branch is unnecessary.
if (tpm_state.scrypt_derived.value()) {
*vkk_key = HmacSha256(kdf_skey, local_vault_key);
} else {
if (!PasskeyToAesKey(local_vault_key, salt, rounds, vkk_key, vkk_iv)) {
LOG(ERROR) << "Failure converting IVKK to VKK.";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocTpmNotBoundToPcrAuthBlockVKKConversionFailedInDecrypt),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_FATAL);
}
}
return OkStatus<CryptohomeCryptoError>();
}
} // namespace cryptohome