blob: 3a9e63fe313e93e78ad6640215659b07431552ce [file] [log] [blame] [edit]
// Copyright 2022 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/revocation.h"
#include <cstdint>
#include <map>
#include <optional>
#include <utility>
#include <vector>
#include <brillo/secure_blob.h>
#include <libhwsec/backend/pinweaver_manager/pinweaver_manager.h>
#include <libhwsec-foundation/crypto/aes.h>
#include <libhwsec-foundation/crypto/hkdf.h>
#include <libhwsec-foundation/crypto/scrypt.h>
#include <libhwsec-foundation/crypto/secure_blob_util.h>
#include "cryptohome/crypto_error.h"
#include "cryptohome/cryptohome_common.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/error/cryptohome_crypto_error.h"
#include "cryptohome/error/cryptohome_tpm_error.h"
#include "cryptohome/error/locations.h"
#include "cryptohome/flatbuffer_schemas/auth_block_state.h"
#include "cryptohome/key_objects.h"
using ::cryptohome::error::CryptohomeCryptoError;
using ::cryptohome::error::ErrorActionSet;
using ::cryptohome::error::PossibleAction;
using ::cryptohome::error::PrimaryAction;
using ::hwsec_foundation::CreateSecureRandomBlob;
using ::hwsec_foundation::DeriveSecretsScrypt;
using ::hwsec_foundation::Hkdf;
using ::hwsec_foundation::HkdfHash;
using ::hwsec_foundation::kDefaultAesKeySize;
using ::hwsec_foundation::status::MakeStatus;
using ::hwsec_foundation::status::OkStatus;
namespace cryptohome::revocation {
namespace {
constexpr int kDefaultSecretSize = 32;
// String used as vector in HKDF operation to derive vkk_key from he_secret.
const char kHESecretHkdfData[] = "hkdf_data";
// String used as info in HKDF operation to derive le_secret from
// per_credential_secret.
const char kLeSecretInfo[] = "le_secret_info";
// String used as info in HKDF operation to derive kdf_skey from
// per_credential_secret.
const char kKdfSkeyInfo[] = "kdf_skey_info";
// The format for a delay schedule entry is as follows:
// (number_of_incorrect_attempts, delay before_next_attempt)
// The delay is not needed for revocation, so we set
// number_of_incorrect_attempts to UINT32_MAX.
hwsec::PinWeaverManagerFrontend::DelaySchedule GetDelaySchedule() {
return std::map<uint32_t, uint32_t>{{UINT32_MAX, 1}};
}
CryptoError RevokeTPMRetryActionToCryptoError(
hwsec::TPMRetryAction retry_action) {
switch (retry_action) {
case hwsec::TPMRetryAction::kNoRetry:
case hwsec::TPMRetryAction::kSpaceNotFound:
// Do not return an error here. RemoveCredential returns:
// - TPMRetryAction::kSpaceNotFound for invalid label.
// - TPMRetryAction::kNoRetry for hash tree error (implies that all state
// in PinWeaver is lost). Both of these cases are considered as "success"
// for revocation.
return CryptoError::CE_NONE;
default:
return CryptoError::CE_OTHER_CRYPTO;
}
}
bool DeriveSecret(const brillo::SecureBlob& key,
const brillo::Blob& hkdf_info,
brillo::SecureBlob* gen_secret) {
// Note: the key is high entropy, so the salt can be empty.
if (!Hkdf(HkdfHash::kSha256, /*key=*/key,
/*info=*/hkdf_info,
/*salt=*/brillo::Blob(),
/*result_len=*/gen_secret->size(), gen_secret)) {
LOG(ERROR) << "HKDF failed for revocation during secret derivation.";
return false;
}
return true;
}
} // namespace
bool IsRevocationSupported(const hwsec::CryptohomeFrontend* hwsec) {
hwsec::StatusOr<bool> enabled = hwsec->IsPinWeaverEnabled();
return enabled.ok() && *enabled;
}
CryptoStatus Create(const hwsec::PinWeaverManagerFrontend* hwsec_pw_manager,
RevocationState* revocation_state,
KeyBlobs* key_blobs) {
if (!key_blobs->vkk_key.has_value() || key_blobs->vkk_key.value().empty()) {
LOG(ERROR) << "Failed to create secret for revocation: vkk_key is not set";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocRevocationNoVkkKeyInCreate),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
// The secret generated by AuthBlock.
brillo::SecureBlob per_credential_secret = key_blobs->vkk_key.value();
brillo::SecureBlob salt =
CreateSecureRandomBlob(kCryptohomeDefaultKeySaltSize);
// Derive two secrets from per_credential_secret:
// le_secret to be stored in LECredentialManager,
// kdf_skey to be combined with he_secret for vkk_key generation.
brillo::SecureBlob le_secret(kDefaultSecretSize);
brillo::SecureBlob kdf_skey(kDefaultSecretSize);
if (!DeriveSecret(per_credential_secret,
brillo::BlobFromString(kLeSecretInfo), &le_secret) ||
!DeriveSecret(per_credential_secret, brillo::BlobFromString(kKdfSkeyInfo),
&kdf_skey)) {
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocRevocationDeriveSecretsFailedInCreate),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
// Generate a random high entropy secret to be stored in LECredentialManager.
brillo::SecureBlob he_secret = CreateSecureRandomBlob(kDefaultSecretSize);
// Note:
// - We send an empty blob as reset_secret because resetting the delay counter
// will not compromise security (we send MAX_UINT32 attempts for the delay
// schedule). The size should still be kDefaultSecretSize.
// - We don't set policies because PCR binding is expected to be
// already done by the AuthBlock.
hwsec::StatusOr<uint64_t> result = hwsec_pw_manager->InsertCredential(
/*policies=*/std::vector<hwsec::OperationPolicySetting>(),
/*le_secret=*/le_secret,
/*he_secret=*/he_secret,
/*reset_secret=*/brillo::SecureBlob(kDefaultSecretSize),
/*delay_schedule=*/GetDelaySchedule(),
/*expiration_delay=*/std::nullopt);
if (!result.ok()) {
LOG(ERROR) << "InsertCredential failed for revocation with error "
<< result.err_status();
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocRevocationInsertCredentialFailedInCreate))
.Wrap(MakeStatus<error::CryptohomeTPMError>(
std::move(result).err_status()));
}
revocation_state->le_label = result.value();
// Combine he_secret with kdf_skey:
brillo::SecureBlob vkk_key;
if (!Hkdf(HkdfHash::kSha256,
/*key=*/brillo::SecureBlob::Combine(he_secret, kdf_skey),
/*info=*/brillo::Blob(),
/*salt=*/brillo::BlobFromString(kHESecretHkdfData),
/*result_len=*/0, &vkk_key)) {
LOG(ERROR) << "vkk_key HKDF derivation failed for revocation";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocRevocationHkdfFailedInCreate),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
key_blobs->vkk_key = std::move(vkk_key);
return OkStatus<CryptohomeCryptoError>();
}
CryptoStatus Derive(const hwsec::PinWeaverManagerFrontend* hwsec_pw_manager,
const RevocationState& revocation_state,
KeyBlobs* key_blobs) {
if (!key_blobs->vkk_key.has_value() || key_blobs->vkk_key.value().empty()) {
LOG(ERROR) << "Failed to derive secret for revocation: vkk_key is not set";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocRevocationNoVkkKeyInDerive),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
if (!revocation_state.le_label.has_value()) {
LOG(ERROR)
<< "Failed to derive secret: revocation_state.le_label is not set";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocRevocationNoLeLabelInDerive),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
// The secret generated by AuthBlock.
brillo::SecureBlob per_credential_secret = key_blobs->vkk_key.value();
brillo::SecureBlob le_secret(kDefaultSecretSize);
brillo::SecureBlob kdf_skey(kDefaultSecretSize);
if (!DeriveSecret(per_credential_secret,
brillo::BlobFromString(kLeSecretInfo), &le_secret) ||
!DeriveSecret(per_credential_secret, brillo::BlobFromString(kKdfSkeyInfo),
&kdf_skey)) {
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocRevocationDeriveSecretsFailedInDerive),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
hwsec::StatusOr<hwsec::PinWeaverManagerFrontend::CheckCredentialReply>
result = hwsec_pw_manager->CheckCredential(
/*label=*/revocation_state.le_label.value(),
/*le_secret=*/le_secret);
if (!result.ok()) {
LOG(ERROR) << "CheckCredential failed for revocation with error "
<< result.err_status();
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocRevocationCheckCredentialFailedInDerive))
.Wrap(MakeStatus<error::CryptohomeTPMError>(
std::move(result).err_status()));
}
// Combine he_secret with kdf_skey:
brillo::SecureBlob vkk_key;
if (!Hkdf(HkdfHash::kSha256,
/*key=*/
brillo::SecureBlob::Combine(result->he_secret, kdf_skey),
/*info=*/brillo::Blob(),
/*salt=*/brillo::BlobFromString(kHESecretHkdfData),
/*result_len=*/0, &vkk_key)) {
LOG(ERROR) << "vkk_key HKDF derivation failed for revocation";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocRevocationHkdfFailedInDerive),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
key_blobs->vkk_key = std::move(vkk_key);
return OkStatus<CryptohomeCryptoError>();
}
CryptoStatus Revoke(AuthBlockType auth_block_type,
const hwsec::PinWeaverManagerFrontend* hwsec_pw_manager,
const RevocationState& revocation_state) {
if (!revocation_state.le_label.has_value()) {
LOG(ERROR)
<< "Failed to revoke secret: revocation_state.le_label is not set";
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocRevocationNoLeLabelInRevoke),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
CryptoError::CE_OTHER_CRYPTO);
}
hwsec::Status result = hwsec_pw_manager->RemoveCredential(
/*label=*/revocation_state.le_label.value());
if (!result.ok()) {
LOG(ERROR) << "RemoveCredential failed for revocation with error: "
<< result.err_status();
ReportRevokeCredentialResult(auth_block_type, result->ToTPMRetryAction());
CryptoError revoke_error =
RevokeTPMRetryActionToCryptoError(result->ToTPMRetryAction());
if (revoke_error == CryptoError::CE_NONE) {
// This case is considered a success - do not return an error here. See
// the comment in `RevokeTPMRetryActionToCryptoError`.
return OkStatus<CryptohomeCryptoError>();
}
// Note: the local error must be overridden with revoke_error.
return MakeStatus<CryptohomeCryptoError>(
CRYPTOHOME_ERR_LOC(kLocRevocationRemoveCredentialFailedInRevoke),
ErrorActionSet({}), revoke_error)
.Wrap(MakeStatus<error::CryptohomeTPMError>(
std::move(result).err_status()));
}
ReportRevokeCredentialResult(auth_block_type, hwsec::TPMRetryAction::kNone);
return OkStatus<CryptohomeCryptoError>();
}
} // namespace cryptohome::revocation