blob: b2a67eaf0b54b191b2f198b85f962281d64f8094 [file] [log] [blame]
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cryptohome/auth_blocks/pin_weaver_auth_block.h"
#include <libhwsec-foundation/crypto/scrypt.h>
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/vault_keyset.h"
#include <limits>
#include <map>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include <base/check.h>
#include <base/check_op.h>
#include <base/logging.h>
#include <brillo/secure_blob.h>
#include <libhwsec-foundation/crypto/aes.h>
#include <libhwsec-foundation/crypto/hmac.h>
#include <libhwsec-foundation/crypto/secure_blob_util.h>
#include <libhwsec-foundation/crypto/sha.h>
#include "cryptohome/error/location_utils.h"
#include "cryptohome/flatbuffer_schemas/auth_block_state.h"
#include "cryptohome/vault_keyset.pb.h"
using ::cryptohome::error::CryptohomeCryptoError;
using ::cryptohome::error::ErrorAction;
using ::cryptohome::error::ErrorActionSet;
using ::hwsec_foundation::CreateSecureRandomBlob;
using ::hwsec_foundation::DeriveSecretsScrypt;
using ::hwsec_foundation::HmacSha256;
using ::hwsec_foundation::kAesBlockSize;
using ::hwsec_foundation::kDefaultAesKeySize;
using ::hwsec_foundation::Sha256;
using ::hwsec_foundation::status::MakeStatus;
using ::hwsec_foundation::status::OkStatus;
using ::hwsec_foundation::status::StatusChain;
namespace cryptohome {
namespace {
constexpr int kDefaultSecretSize = 32;
void LogLERetCode(int le_error) {
switch (le_error) {
case LE_CRED_ERROR_NO_FREE_LABEL:
LOG(ERROR) << "No free label available in hash tree.";
break;
case LE_CRED_ERROR_HASH_TREE:
LOG(ERROR) << "Hash tree error.";
break;
}
}
// String used as vector in HMAC operation to derive vkk_seed from High Entropy
// secret.
constexpr char kHESecretHmacData[] = "vkk_seed";
// A default delay schedule to be used for LE Credentials.
// The format for a delay schedule entry is as follows:
//
// (number_of_incorrect_attempts, delay before_next_attempt)
//
// The default schedule is for the first 5 incorrect attempts to have no delay,
// and no further attempts allowed.
constexpr uint32_t kAttemptsLimit = 5;
constexpr uint32_t kInfiniteDelay = std::numeric_limits<uint32_t>::max();
constexpr struct {
uint32_t attempts;
uint32_t delay;
} kDefaultDelaySchedule[] = {
{kAttemptsLimit, kInfiniteDelay},
};
} // namespace
PinWeaverAuthBlock::PinWeaverAuthBlock(
LECredentialManager* le_manager,
CryptohomeKeysManager* cryptohome_keys_manager)
: SyncAuthBlock(kLowEntropyCredential),
le_manager_(le_manager),
cryptohome_key_loader_(
cryptohome_keys_manager->GetKeyLoader(CryptohomeKeyType::kRSA)) {
CHECK_NE(le_manager, nullptr);
CHECK_NE(cryptohome_key_loader_, nullptr);
}
CryptoStatus PinWeaverAuthBlock::Create(const AuthInput& auth_input,
AuthBlockState* auth_block_state,
KeyBlobs* key_blobs) {
DCHECK(key_blobs);
if (!auth_input.user_input.has_value()) {
LOG(ERROR) << "Missing user_input";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocPinWeaverAuthBlockNoUserInputInCreate),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
if (!auth_input.obfuscated_username.has_value()) {
LOG(ERROR) << "Missing obfuscated_username";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocPinWeaverAuthBlockNoUsernameInCreate),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
if (!auth_input.reset_secret.has_value() &&
!auth_input.reset_seed.has_value()) {
LOG(ERROR) << "Missing reset_secret or reset_seed";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocPinWeaverAuthBlockNoResetSecretOrResetSeedInCreate),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
PinWeaverAuthBlockState pin_auth_state;
brillo::SecureBlob reset_secret;
brillo::SecureBlob salt =
CreateSecureRandomBlob(CRYPTOHOME_DEFAULT_KEY_SALT_SIZE);
if (auth_input.reset_secret.has_value()) {
// This case be used for USS as we do not have the concept of reset seed and
// salt there.
reset_secret = auth_input.reset_secret.value();
} else {
// At this point we know auth_input reset_seed is set. The expectation is
// that this branch of code would be deprecated once we move fully to USS
// world.
pin_auth_state.reset_salt = CreateSecureRandomBlob(kAesBlockSize);
reset_secret = HmacSha256(pin_auth_state.reset_salt.value(),
auth_input.reset_seed.value());
}
// This may not be needed, but is retained to maintain the original logic.
if (!cryptohome_key_loader_->HasCryptohomeKey())
cryptohome_key_loader_->Init();
brillo::SecureBlob le_secret(kDefaultSecretSize);
brillo::SecureBlob kdf_skey(kDefaultSecretSize);
if (!DeriveSecretsScrypt(auth_input.user_input.value(), salt,
{&le_secret, &kdf_skey})) {
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocPinWeaverAuthBlockScryptDeriveFailedInCreate),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
// Create a randomly generated high entropy secret, derive VKKSeed from it,
// and use that to generate a VKK. The High Entropy secret will be stored in
// the LECredentialManager, along with the LE secret (which is |le_secret|
// here).
brillo::SecureBlob he_secret = CreateSecureRandomBlob(kDefaultSecretSize);
// Derive the VKK_seed by performing an HMAC on he_secret.
brillo::SecureBlob vkk_seed =
HmacSha256(he_secret, brillo::BlobFromString(kHESecretHmacData));
// Generate and store random new IVs for file-encryption keys and
// chaps key encryption.
const auto fek_iv = CreateSecureRandomBlob(kAesBlockSize);
const auto chaps_iv = CreateSecureRandomBlob(kAesBlockSize);
brillo::SecureBlob vkk_key = HmacSha256(kdf_skey, vkk_seed);
key_blobs->vkk_key = vkk_key;
key_blobs->vkk_iv = fek_iv;
key_blobs->chaps_iv = chaps_iv;
// Once we are able to correctly set up the VaultKeyset encryption,
// store the Low Entropy and High Entropy credential in the
// LECredentialManager.
// Use the default delay schedule for now.
std::map<uint32_t, uint32_t> delay_sched;
for (const auto& entry : kDefaultDelaySchedule) {
delay_sched[entry.attempts] = entry.delay;
}
std::vector<hwsec::OperationPolicySetting> policies = {
hwsec::OperationPolicySetting{
.device_config_settings =
hwsec::DeviceConfigSettings{
.current_user =
hwsec::DeviceConfigSettings::CurrentUserSetting{
.username = std::nullopt,
},
},
},
hwsec::OperationPolicySetting{
.device_config_settings =
hwsec::DeviceConfigSettings{
.current_user =
hwsec::DeviceConfigSettings::CurrentUserSetting{
.username = auth_input.obfuscated_username.value(),
},
},
},
};
uint64_t label;
LECredStatus ret = le_manager_->InsertCredential(
policies, le_secret, he_secret, reset_secret, delay_sched, &label);
if (!ret.ok()) {
LogLERetCode(ret->local_lecred_error());
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocPinWeaverAuthBlockInsertCredentialFailedInCreate))
.Wrap(std::move(ret));
}
pin_auth_state.le_label = label;
pin_auth_state.salt = std::move(salt);
*auth_block_state = AuthBlockState{.state = std::move(pin_auth_state)};
return OkStatus<CryptohomeCryptoError>();
}
CryptoStatus PinWeaverAuthBlock::Derive(const AuthInput& auth_input,
const AuthBlockState& state,
KeyBlobs* key_blobs) {
if (!auth_input.user_input.has_value()) {
LOG(ERROR) << "Missing user_input";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocPinWeaverAuthBlockNoUserInputInDerive),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
const PinWeaverAuthBlockState* auth_state;
if (!(auth_state = std::get_if<PinWeaverAuthBlockState>(&state.state))) {
LOG(ERROR) << "Invalid AuthBlockState";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocPinWeaverAuthBlockInvalidBlockStateInDerive),
ErrorActionSet(
{ErrorAction::kDevCheckUnexpectedState, ErrorAction::kAuth}),
CryptoError::CE_OTHER_CRYPTO);
}
brillo::SecureBlob le_secret(kDefaultAesKeySize);
brillo::SecureBlob kdf_skey(kDefaultAesKeySize);
if (!auth_state->le_label.has_value()) {
LOG(ERROR) << "Invalid PinWeaverAuthBlockState: missing le_label";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocPinWeaverAuthBlockNoLabelInDerive),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState,
ErrorAction::kAuth, ErrorAction::kDeleteVault}),
CryptoError::CE_OTHER_CRYPTO);
}
if (!auth_state->salt.has_value()) {
LOG(ERROR) << "Invalid PinWeaverAuthBlockState: missing salt";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocPinWeaverAuthBlockNoSaltInDerive),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState,
ErrorAction::kAuth, ErrorAction::kDeleteVault}),
CryptoError::CE_OTHER_CRYPTO);
}
brillo::SecureBlob salt = auth_state->salt.value();
if (!DeriveSecretsScrypt(auth_input.user_input.value(), salt,
{&le_secret, &kdf_skey})) {
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocPinWeaverAuthBlockDeriveScryptFailedInDerive),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_FATAL);
}
key_blobs->reset_secret = brillo::SecureBlob();
// Note: Yes it is odd to pass the IV from the auth state into the key blobs
// without performing any operation on the data. However, the fact that the
// IVs are pre-generated in the VaultKeyset for PinWeaver credentials is an
// implementation detail. The AuthBlocks are designed to hide those
// implementation details, so this goes here.
if (auth_state->chaps_iv.has_value()) {
key_blobs->chaps_iv = auth_state->chaps_iv.value();
}
if (auth_state->fek_iv.has_value()) {
key_blobs->vkk_iv = auth_state->fek_iv.value();
}
// Try to obtain the High Entropy Secret from the LECredentialManager.
brillo::SecureBlob he_secret;
LECredStatus ret = le_manager_->CheckCredential(
auth_state->le_label.value(), le_secret, &he_secret,
&key_blobs->reset_secret.value());
if (!ret.ok()) {
// Replace the error with CE_CREDENTIAL_LOCKED if it is caused by invalid LE
// secret and locked.
if (ret->local_lecred_error() == LE_CRED_ERROR_INVALID_LE_SECRET &&
IsLocked(auth_state->le_label.value())) {
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocPinWeaverAuthBlockCheckCredLockedInDerive),
ErrorActionSet({ErrorAction::kAuth}),
CryptoError::CE_CREDENTIAL_LOCKED)
.Wrap(std::move(ret));
}
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(
kLocPinWeaverAuthBlockCheckCredFailedInDerive))
.Wrap(std::move(ret));
}
brillo::SecureBlob vkk_seed =
HmacSha256(he_secret, brillo::BlobFromString(kHESecretHmacData));
key_blobs->vkk_key = HmacSha256(kdf_skey, vkk_seed);
return OkStatus<CryptohomeCryptoError>();
}
bool PinWeaverAuthBlock::IsLocked(uint64_t label) {
LECredStatusOr<uint32_t> delay = le_manager_->GetDelayInSeconds(label);
if (!delay.ok()) {
LOG(ERROR)
<< "Failed to obtain the delay in seconds in pinweaver auth block: "
<< std::move(delay).status();
return false;
}
// The pin is locked forever.
if (delay.value() == kInfiniteDelay) {
return true;
}
return false;
}
} // namespace cryptohome