blob: ba1d2a0de67f466758b6fc1f5bf46ac27fe35d02 [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_bound_to_pcr_auth_block.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/logging.h>
#include <base/optional.h>
#include <brillo/secure_blob.h>
#include "cryptohome/crypto.h"
#include "cryptohome/crypto/aes.h"
#include "cryptohome/crypto/scrypt.h"
#include "cryptohome/crypto/secure_blob_util.h"
#include "cryptohome/crypto_error.h"
#include "cryptohome/cryptohome_keys_manager.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/tpm.h"
#include "cryptohome/tpm_auth_block_utils.h"
#include "cryptohome/vault_keyset.pb.h"
using hwsec::error::TPMError;
using hwsec::error::TPMErrorBase;
using hwsec_foundation::error::CreateErrorWrap;
namespace cryptohome {
TpmBoundToPcrAuthBlock::TpmBoundToPcrAuthBlock(
Tpm* tpm, CryptohomeKeysManager* cryptohome_keys_manager)
: AuthBlock(kTpmBackedPcrBound),
tpm_(tpm),
cryptohome_key_loader_(
cryptohome_keys_manager->GetKeyLoader(CryptohomeKeyType::kRSA)),
utils_(tpm, cryptohome_key_loader_) {
CHECK(tpm != nullptr);
CHECK(cryptohome_key_loader_ != nullptr);
// Create the scrypt thread.
// TODO(yich): Create another thread in userdataauth and passing it to here.
base::Thread::Options options;
options.message_pump_type = base::MessagePumpType::IO;
scrypt_thread_ = std::make_unique<base::Thread>("scrypt_thread");
scrypt_thread_->StartWithOptions(options);
scrypt_task_runner_ = scrypt_thread_->task_runner();
}
base::Optional<AuthBlockState> TpmBoundToPcrAuthBlock::Create(
const AuthInput& user_input, KeyBlobs* key_blobs, CryptoError* error) {
const brillo::SecureBlob& vault_key = user_input.user_input.value();
const brillo::SecureBlob& salt = user_input.salt.value();
const std::string& obfuscated_username =
user_input.obfuscated_username.value();
// 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 base::nullopt;
const auto vkk_key = CreateSecureRandomBlob(kDefaultAesKeySize);
brillo::SecureBlob pass_blob(kDefaultPassBlobSize);
brillo::SecureBlob vkk_iv(kAesBlockSize);
if (!DeriveSecretsScrypt(vault_key, salt, {&pass_blob, &vkk_iv}))
return base::nullopt;
std::map<uint32_t, std::string> default_pcr_map =
tpm_->GetPcrMap(obfuscated_username, false /* use_extended_pcr */);
std::map<uint32_t, std::string> extended_pcr_map =
tpm_->GetPcrMap(obfuscated_username, true /* use_extended_pcr */);
// Encrypt the VKK using the TPM and the user's passkey. The output is two
// encrypted blobs, sealed to PCR in |tpm_key| and |extended_tpm_key|,
// which are stored in the serialized vault keyset.
TpmKeyHandle cryptohome_key = cryptohome_key_loader_->GetCryptohomeKey();
brillo::SecureBlob auth_value;
if (TPMErrorBase err =
tpm_->GetAuthValue(cryptohome_key, pass_blob, &auth_value)) {
LOG(ERROR) << "Failed to get auth value: " << *err;
return base::nullopt;
}
brillo::SecureBlob tpm_key;
if (TPMErrorBase err = tpm_->SealToPcrWithAuthorization(
vkk_key, auth_value, default_pcr_map, &tpm_key)) {
LOG(ERROR) << "Failed to wrap vkk with creds: " << *err;
return base::nullopt;
}
brillo::SecureBlob extended_tpm_key;
if (TPMErrorBase err = tpm_->SealToPcrWithAuthorization(
vkk_key, auth_value, extended_pcr_map, &extended_tpm_key)) {
LOG(ERROR) << "Failed to wrap vkk with creds for extended PCR: " << *err;
return base::nullopt;
}
AuthBlockState auth_block_state;
AuthBlockState::TpmBoundToPcrAuthBlockState* auth_state =
auth_block_state.mutable_tpm_bound_to_pcr_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;
if (TPMErrorBase err =
tpm_->GetPublicKeyHash(cryptohome_key, &pub_key_hash)) {
LOG(ERROR) << "Failed to get the TPM public key hash: " << *err;
} else {
auth_state->set_tpm_public_key_hash(pub_key_hash.data(),
pub_key_hash.size());
}
auth_state->set_scrypt_derived(true);
auth_state->set_tpm_key(tpm_key.data(), tpm_key.size());
auth_state->set_extended_tpm_key(extended_tpm_key.data(),
extended_tpm_key.size());
// Pass back the vkk_key and vkk_iv so the generic secret wrapping can use it.
key_blobs->vkk_key = vkk_key;
// 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;
return auth_block_state;
}
bool TpmBoundToPcrAuthBlock::Derive(const AuthInput& auth_input,
const AuthBlockState& state,
KeyBlobs* key_out_data,
CryptoError* error) {
if (!state.has_tpm_bound_to_pcr_state()) {
DLOG(FATAL) << "Called with an invalid auth block state";
return false;
}
const AuthBlockState::TpmBoundToPcrAuthBlockState& tpm_state =
state.tpm_bound_to_pcr_state();
if (!tpm_state.scrypt_derived()) {
LOG(ERROR) << "All TpmBoundtoPcr operations should be scrypt derived.";
return false;
}
brillo::SecureBlob tpm_public_key_hash;
if (tpm_state.has_tpm_public_key_hash()) {
tpm_public_key_hash.assign(tpm_state.tpm_public_key_hash().begin(),
tpm_state.tpm_public_key_hash().end());
}
if (!utils_.CheckTPMReadiness(tpm_state.has_tpm_key(),
tpm_state.has_tpm_public_key_hash(),
tpm_public_key_hash, error)) {
return false;
}
key_out_data->vkk_iv = brillo::SecureBlob(kAesBlockSize);
key_out_data->vkk_key = brillo::SecureBlob(kDefaultAesKeySize);
bool locked_to_single_user = auth_input.locked_to_single_user.value_or(false);
brillo::SecureBlob salt(tpm_state.salt().begin(), tpm_state.salt().end());
std::string tpm_key_str = locked_to_single_user ? tpm_state.extended_tpm_key()
: tpm_state.tpm_key();
brillo::SecureBlob tpm_key(tpm_key_str.begin(), tpm_key_str.end());
if (!DecryptTpmBoundToPcr(auth_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;
if (tpm_state.has_wrapped_reset_seed()) {
key_out_data->wrapped_reset_seed = brillo::SecureBlob();
key_out_data->wrapped_reset_seed.value().assign(
tpm_state.wrapped_reset_seed().begin(),
tpm_state.wrapped_reset_seed().end());
}
if (!tpm_state.has_tpm_public_key_hash() && error) {
*error = CryptoError::CE_NO_PUBLIC_KEY_HASH;
}
return true;
}
bool TpmBoundToPcrAuthBlock::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);
// Prepare the parameters for scrypt.
std::vector<brillo::SecureBlob*> gen_secrets{&pass_blob, vkk_iv};
bool derive_result = false;
base::WaitableEvent done(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
// Derive secrets on scrypt task runner.
scrypt_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](const brillo::SecureBlob& passkey, const brillo::SecureBlob& salt,
std::vector<brillo::SecureBlob*> gen_secrets, bool* result,
base::WaitableEvent* done) {
*result =
DeriveSecretsScrypt(passkey, salt, std::move(gen_secrets));
done->Signal();
},
vault_key, salt, gen_secrets, &derive_result, &done));
// Preload the sealed data while deriving secrets in scrypt.
ScopedKeyHandle preload_handle;
TPMErrorBase err;
for (int i = 0; i < kTpmDecryptMaxRetries; ++i) {
err = tpm_->PreloadSealedData(tpm_key, &preload_handle);
if (err == nullptr)
break;
if (!TpmAuthBlockUtils::TPMErrorIsRetriable(err))
break;
}
// Wait for the scrypt finished.
done.Wait();
if (!derive_result) {
LOG(ERROR) << "scrypt derivation failed";
return false;
}
if (err) {
LOG(ERROR) << "Failed to preload the sealed data: " << *err;
return false;
}
// On TPM1.2 devices, preloading sealed data is meaningless and
// UnsealWithAuthorization will check the preload_handle not containing any
// value.
base::Optional<TpmKeyHandle> handle;
if (preload_handle.has_value()) {
handle = preload_handle.value();
}
for (int i = 0; i < kTpmDecryptMaxRetries; ++i) {
TpmKeyHandle cryptohome_key = cryptohome_key_loader_->GetCryptohomeKey();
brillo::SecureBlob auth_value;
if (TPMErrorBase auth_err =
tpm_->GetAuthValue(cryptohome_key, pass_blob, &auth_value)) {
err = CreateErrorWrap<TPMError>(std::move(auth_err),
"Failed to get auth value");
// TODO(yich): Reload cryptohome key might be a better choice.
break;
}
std::map<uint32_t, std::string> pcr_map({{kTpmSingleUserPCR, ""}});
err = tpm_->UnsealWithAuthorization(handle, tpm_key, auth_value, pcr_map,
vkk_key);
if (err == nullptr)
return true;
if (!TpmAuthBlockUtils::TPMErrorIsRetriable(err))
break;
// If the error is retriable, reload the key first.
if (!cryptohome_key_loader_->ReloadCryptohomeKey()) {
LOG(ERROR) << "Unable to reload Cryptohome key.";
break;
}
}
if (err) {
LOG(ERROR) << "Failed to unwrap VKK with creds: " << *err;
}
*error = TpmAuthBlockUtils::TPMErrorToCrypto(err);
return false;
}
} // namespace cryptohome