blob: 55441a34a035c16ccf72a1710b7333577467b7e8 [file] [log] [blame]
// Copyright 2022 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/revocation.h"
#include <map>
#include <utility>
#include <vector>
#include <brillo/secure_blob.h>
#include <libhwsec-foundation/crypto/secure_blob_util.h>
#include <libhwsec-foundation/crypto/scrypt.h>
#include <libhwsec-foundation/crypto/aes.h>
#include <libhwsec-foundation/crypto/hkdf.h>
#include "cryptohome/auth_blocks/auth_block_state.h"
#include "cryptohome/crypto_error.h"
#include "cryptohome/key_objects.h"
#include "cryptohome/le_credential_manager.h"
#include "cryptohome/tpm.h"
using ::hwsec_foundation::CreateSecureRandomBlob;
using ::hwsec_foundation::DeriveSecretsScrypt;
using ::hwsec_foundation::Hkdf;
using ::hwsec_foundation::HkdfHash;
using ::hwsec_foundation::kDefaultAesKeySize;
namespace cryptohome {
namespace 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.
LECredentialManager::DelaySchedule GetDelaySchedule() {
return std::map<uint32_t, uint32_t>{{UINT32_MAX, 1}};
}
CryptoError RevokeLECredErrorToCryptoError(LECredError le_error) {
switch (le_error) {
case LE_CRED_ERROR_INVALID_LABEL:
case LE_CRED_ERROR_HASH_TREE:
// Do not return an error here. RemoveCredential returns:
// - LE_CRED_ERROR_INVALID_LABEL for invalid label.
// - LE_CRED_ERROR_HASH_TREE 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;
case LE_CRED_ERROR_UNCLASSIFIED:
case LE_CRED_ERROR_LE_LOCKED:
return CryptoError::CE_OTHER_CRYPTO;
default:
return CryptoError::CE_OTHER_CRYPTO;
}
}
bool DeriveSecret(const brillo::SecureBlob& key,
const brillo::SecureBlob& 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::SecureBlob(),
/*result_len=*/gen_secret->size(), gen_secret)) {
LOG(ERROR) << "HKDF failed during secret derivation.";
return false;
}
return true;
}
} // namespace
bool IsRevocationSupported(Tpm* tpm) {
return tpm->GetLECredentialBackend() &&
tpm->GetLECredentialBackend()->IsSupported();
}
CryptoError Create(LECredentialManager* le_manager,
RevocationState* revocation_state,
KeyBlobs* key_blobs) {
DCHECK(le_manager);
if (!key_blobs->vkk_key.has_value() || key_blobs->vkk_key.value().empty()) {
LOG(ERROR) << "Failed to create secret: vkk_key is not set";
return CryptoError::CE_OTHER_CRYPTO;
}
// The secret generated by AuthBlock.
brillo::SecureBlob per_credential_secret = key_blobs->vkk_key.value();
brillo::SecureBlob salt =
CreateSecureRandomBlob(CRYPTOHOME_DEFAULT_KEY_SALT_SIZE);
// 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::SecureBlob(kLeSecretInfo),
&le_secret) ||
!DeriveSecret(per_credential_secret, brillo::SecureBlob(kKdfSkeyInfo),
&kdf_skey)) {
return CryptoError::CE_OTHER_CRYPTO;
}
// Generate a random high entropy secret to be stored in LECredentialManager.
brillo::SecureBlob he_secret = CreateSecureRandomBlob(kDefaultSecretSize);
// Label for the credential stored in LECredentialManager.
uint64_t label;
// 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).
// - We don't set valid_pcr_criteria because PCR binding is expected to be
// already done by the AuthBlock.
LECredStatus ret = le_manager->InsertCredential(
/*le_secret=*/le_secret,
/*he_secret=*/he_secret,
/*reset_secret=*/brillo::SecureBlob(),
/*delay_sched=*/GetDelaySchedule(),
/*valid_pcr_criteria=*/ValidPcrCriteria(), &label);
if (!ret.ok())
return ret->local_crypto_error();
revocation_state->le_label = label;
// Combine he_secret with kdf_skey:
brillo::SecureBlob vkk_key;
if (!Hkdf(HkdfHash::kSha256,
/*key=*/brillo::SecureBlob::Combine(he_secret, kdf_skey),
/*info=*/brillo::SecureBlob(),
/*salt=*/brillo::SecureBlob(kHESecretHkdfData),
/*result_len=*/0, &vkk_key)) {
LOG(ERROR) << "vkk_key HKDF derivation failed";
return CryptoError::CE_OTHER_CRYPTO;
}
key_blobs->vkk_key = std::move(vkk_key);
return CryptoError::CE_NONE;
}
CryptoError Derive(LECredentialManager* le_manager,
const RevocationState& revocation_state,
KeyBlobs* key_blobs) {
DCHECK(le_manager);
if (!key_blobs->vkk_key.has_value() || key_blobs->vkk_key.value().empty()) {
LOG(ERROR) << "Failed to derive secret: vkk_key is not set";
return 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 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::SecureBlob(kLeSecretInfo),
&le_secret) ||
!DeriveSecret(per_credential_secret, brillo::SecureBlob(kKdfSkeyInfo),
&kdf_skey)) {
return CryptoError::CE_OTHER_CRYPTO;
}
// The secret that is stored in LECredentialManager.
brillo::SecureBlob he_secret;
// Note: reset_secret is not used, see Create().
brillo::SecureBlob reset_secret;
LECredStatus ret = le_manager->CheckCredential(
/*label=*/revocation_state.le_label.value(),
/*le_secret=*/le_secret,
/*he_secret=*/&he_secret,
/*reset_secret=*/&reset_secret);
if (!ret.ok())
return ret->local_crypto_error();
// Combine he_secret with kdf_skey:
brillo::SecureBlob vkk_key;
if (!Hkdf(HkdfHash::kSha256,
/*key=*/brillo::SecureBlob::Combine(he_secret, kdf_skey),
/*info=*/brillo::SecureBlob(),
/*salt=*/brillo::SecureBlob(kHESecretHkdfData),
/*result_len=*/0, &vkk_key)) {
LOG(ERROR) << "vkk_key HKDF derivation failed";
return CryptoError::CE_OTHER_CRYPTO;
}
key_blobs->vkk_key = std::move(vkk_key);
return CryptoError::CE_NONE;
}
CryptoError Revoke(LECredentialManager* le_manager,
const RevocationState& revocation_state) {
DCHECK(le_manager);
if (!revocation_state.le_label.has_value()) {
LOG(ERROR)
<< "Failed to revoke secret: revocation_state.le_label is not set";
return CryptoError::CE_OTHER_CRYPTO;
}
LECredStatus ret = le_manager->RemoveCredential(
/*label=*/revocation_state.le_label.value());
if (!ret.ok()) {
LOG(ERROR) << "RemoveCredential failed with error: " << ret;
return RevokeLECredErrorToCryptoError(ret->local_lecred_error());
}
return CryptoError::CE_NONE;
}
} // namespace revocation
} // namespace cryptohome