blob: c25be8fe1293ed0aa9e441704ee0689ac036727d [file] [log] [blame]
// Copyright 2019 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/challenge_credentials/challenge_credentials_generate_new_operation.h"
#include <optional>
#include <utility>
#include <base/bind.h>
#include <base/check.h>
#include <base/check_op.h>
#include <base/logging.h>
#include <libhwsec/frontend/cryptohome/frontend.h>
#include <libhwsec/status.h>
#include "cryptohome/challenge_credentials/challenge_credentials_constants.h"
#include "cryptohome/error/location_utils.h"
using brillo::Blob;
using brillo::CombineBlobs;
using brillo::SecureBlob;
using cryptohome::error::CryptohomeTPMError;
using cryptohome::error::ErrorAction;
using cryptohome::error::ErrorActionSet;
using hwsec::TPMErrorBase;
using hwsec::TPMRetryAction;
using hwsec_foundation::status::MakeStatus;
using hwsec_foundation::status::OkStatus;
using hwsec_foundation::status::StatusChain;
namespace cryptohome {
namespace {
// Size, in bytes, of the secret value that will be sealed by HWSec signature
// sealing.
constexpr int kSecretSizeBytes = 32;
// Returns the signature algorithm that should be used for signing salt from the
// set of algorithms supported by the given key. Returns nullopt when no
// suitable algorithm was found.
std::optional<structure::ChallengeSignatureAlgorithm>
ChooseSaltSignatureAlgorithm(
const structure::ChallengePublicKeyInfo& public_key_info) {
DCHECK(public_key_info.signature_algorithm.size());
std::optional<structure::ChallengeSignatureAlgorithm>
currently_chosen_algorithm;
// Respect the input's algorithm prioritization, with the exception of
// considering SHA-1 as the least preferred option.
for (auto algo : public_key_info.signature_algorithm) {
currently_chosen_algorithm = algo;
if (*currently_chosen_algorithm !=
structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha1)
break;
}
return currently_chosen_algorithm;
}
using HwsecAlgorithm = hwsec::CryptohomeFrontend::SignatureSealingAlgorithm;
HwsecAlgorithm ConvertAlgorithm(
structure::ChallengeSignatureAlgorithm algorithm) {
switch (algorithm) {
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha1:
return HwsecAlgorithm::kRsassaPkcs1V15Sha1;
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha256:
return HwsecAlgorithm::kRsassaPkcs1V15Sha256;
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha384:
return HwsecAlgorithm::kRsassaPkcs1V15Sha384;
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha512:
return HwsecAlgorithm::kRsassaPkcs1V15Sha512;
}
NOTREACHED();
return static_cast<HwsecAlgorithm>(algorithm);
}
} // namespace
ChallengeCredentialsGenerateNewOperation::
ChallengeCredentialsGenerateNewOperation(
KeyChallengeService* key_challenge_service,
hwsec::CryptohomeFrontend* hwsec,
const std::string& account_id,
const structure::ChallengePublicKeyInfo& public_key_info,
const std::string& obfuscated_username,
CompletionCallback completion_callback)
: ChallengeCredentialsOperation(key_challenge_service),
account_id_(account_id),
public_key_info_(public_key_info),
obfuscated_username_(obfuscated_username),
completion_callback_(std::move(completion_callback)),
hwsec_(hwsec) {}
ChallengeCredentialsGenerateNewOperation::
~ChallengeCredentialsGenerateNewOperation() = default;
void ChallengeCredentialsGenerateNewOperation::Start() {
DCHECK(thread_checker_.CalledOnValidThread());
TPMStatus status = StartProcessing();
if (!status.ok()) {
LOG(ERROR) << "Failed to start the generation operation";
Abort(std::move(status));
// |this| can be already destroyed at this point.
}
}
void ChallengeCredentialsGenerateNewOperation::Abort(TPMStatus status) {
DCHECK(thread_checker_.CalledOnValidThread());
TPMStatus return_status =
MakeStatus<CryptohomeTPMError>(CRYPTOHOME_ERR_LOC(kLocChalCredNewAborted))
.Wrap(std::move(status));
// Invalidate weak pointers in order to cancel all jobs that are currently
// waiting, to prevent them from running and consuming resources after our
// abortion (in case |this| doesn't get destroyed immediately).
//
// Note that the already issued challenge requests don't get cancelled, so
// their responses will be just ignored should they arrive later. The request
// cancellation is not supported by the challenges IPC API currently, neither
// it is supported by the API for smart card drivers in Chrome OS.
weak_ptr_factory_.InvalidateWeakPtrs();
Complete(&completion_callback_, std::move(return_status));
// |this| can be already destroyed at this point.
}
TPMStatus ChallengeCredentialsGenerateNewOperation::StartProcessing() {
if (!hwsec_) {
LOG(ERROR) << "Signature sealing is disabled";
return MakeStatus<CryptohomeTPMError>(
CRYPTOHOME_ERR_LOC(kLocChalCredNewNoBackend),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
TPMRetryAction::kNoRetry);
}
if (!public_key_info_.signature_algorithm.size()) {
LOG(ERROR) << "The key does not support any signature algorithm";
return MakeStatus<CryptohomeTPMError>(
CRYPTOHOME_ERR_LOC(kLocChalCredNewNoAlgorithm),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
TPMRetryAction::kNoRetry);
}
TPMStatus status = GenerateSalt();
if (!status.ok()) {
return status;
}
status = StartGeneratingSaltSignature();
if (!status.ok()) {
return status;
}
// TODO(crbug.com/842791): This is buggy: |this| may be already deleted by
// that point, in case when the salt's challenge request failed synchronously.
status = CreateTpmProtectedSecret();
if (!status.ok()) {
return status;
}
ProceedIfComputationsDone();
return OkStatus<CryptohomeTPMError>();
}
TPMStatus ChallengeCredentialsGenerateNewOperation::GenerateSalt() {
hwsec::StatusOr<Blob> salt_random_bytes =
hwsec_->GetRandomBlob(kChallengeCredentialsSaltRandomByteCount);
if (!salt_random_bytes.ok()) {
LOG(ERROR) << "Failed to generate random bytes for the salt: "
<< salt_random_bytes.status();
return MakeStatus<CryptohomeTPMError>(
CRYPTOHOME_ERR_LOC(kLocChalCredNewGenerateRandomSaltFailed),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState,
ErrorAction::kReboot}),
TPMRetryAction::kReboot)
.Wrap(MakeStatus<CryptohomeTPMError>(
std::move(salt_random_bytes).status()));
}
DCHECK_EQ(kChallengeCredentialsSaltRandomByteCount,
salt_random_bytes->size());
// IMPORTANT: Make sure the salt is prefixed with a constant. See the comment
// on GetChallengeCredentialsSaltConstantPrefix() for details.
salt_ = CombineBlobs(
{GetChallengeCredentialsSaltConstantPrefix(), salt_random_bytes.value()});
return OkStatus<CryptohomeTPMError>();
}
TPMStatus
ChallengeCredentialsGenerateNewOperation::StartGeneratingSaltSignature() {
DCHECK(!salt_.empty());
std::optional<structure::ChallengeSignatureAlgorithm>
chosen_salt_signature_algorithm =
ChooseSaltSignatureAlgorithm(public_key_info_);
if (!chosen_salt_signature_algorithm) {
LOG(ERROR) << "Failed to choose salt signature algorithm";
return MakeStatus<CryptohomeTPMError>(
CRYPTOHOME_ERR_LOC(kLocChalCredNewCantChooseSaltSignatureAlgorithm),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
TPMRetryAction::kNoRetry);
}
salt_signature_algorithm_ = *chosen_salt_signature_algorithm;
MakeKeySignatureChallenge(
account_id_, public_key_info_.public_key_spki_der, salt_,
salt_signature_algorithm_,
base::BindOnce(
&ChallengeCredentialsGenerateNewOperation::OnSaltChallengeResponse,
weak_ptr_factory_.GetWeakPtr()));
return OkStatus<CryptohomeTPMError>();
}
TPMStatus ChallengeCredentialsGenerateNewOperation::CreateTpmProtectedSecret() {
hwsec::StatusOr<SecureBlob> tpm_protected_secret_value =
hwsec_->GetRandomSecureBlob(kSecretSizeBytes);
if (!tpm_protected_secret_value.ok()) {
LOG(ERROR) << "Failed to generated random secure blob: "
<< tpm_protected_secret_value.status();
TPMStatus status = MakeStatus<CryptohomeTPMError>(
std::move(tpm_protected_secret_value).status());
return MakeStatus<CryptohomeTPMError>(
CRYPTOHOME_ERR_LOC(kLocChalCredGenRandFailed))
.Wrap(std::move(status));
}
std::vector<HwsecAlgorithm> key_sealing_algorithms;
for (auto algo : public_key_info_.signature_algorithm) {
key_sealing_algorithms.push_back(ConvertAlgorithm(algo));
}
hwsec::StatusOr<hwsec::SignatureSealedData> sealed_data =
hwsec_->SealWithSignatureAndCurrentUser(
obfuscated_username_, tpm_protected_secret_value.value(),
public_key_info_.public_key_spki_der, key_sealing_algorithms);
if (!sealed_data.ok()) {
LOG(ERROR) << "Failed to create hardware-protected secret: "
<< sealed_data.status();
TPMStatus status =
MakeStatus<CryptohomeTPMError>(std::move(sealed_data).status());
return MakeStatus<CryptohomeTPMError>(
CRYPTOHOME_ERR_LOC(kLocChalCredNewSealFailed))
.Wrap(std::move(status));
}
tpm_protected_secret_value_ = std::make_unique<SecureBlob>(
std::move(tpm_protected_secret_value).value());
tpm_sealed_secret_data_ = std::move(sealed_data).value();
return OkStatus<CryptohomeTPMError>();
}
void ChallengeCredentialsGenerateNewOperation::OnSaltChallengeResponse(
TPMStatusOr<std::unique_ptr<Blob>> salt_signature) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!salt_signature.ok()) {
LOG(ERROR) << "Salt signature challenge failed";
Abort(std::move(salt_signature).status());
// |this| can be already destroyed at this point.
return;
}
salt_signature_ = std::move(salt_signature).value();
ProceedIfComputationsDone();
}
void ChallengeCredentialsGenerateNewOperation::ProceedIfComputationsDone() {
if (!salt_signature_ || !tpm_protected_secret_value_)
return;
auto signature_challenge_info =
std::make_unique<structure::SignatureChallengeInfo>(
ConstructKeysetSignatureChallengeInfo());
auto passkey = std::make_unique<brillo::SecureBlob>(
ConstructPasskey(*tpm_protected_secret_value_, *salt_signature_));
Complete(&completion_callback_,
ChallengeCredentialsHelper::GenerateNewOrDecryptResult(
std::move(signature_challenge_info), std::move(passkey)));
// |this| can be already destroyed at this point.
}
structure::SignatureChallengeInfo ChallengeCredentialsGenerateNewOperation::
ConstructKeysetSignatureChallengeInfo() const {
structure::SignatureChallengeInfo keyset_signature_challenge_info;
keyset_signature_challenge_info.public_key_spki_der =
public_key_info_.public_key_spki_der;
keyset_signature_challenge_info.sealed_secret = tpm_sealed_secret_data_;
keyset_signature_challenge_info.salt = salt_;
keyset_signature_challenge_info.salt_signature_algorithm =
salt_signature_algorithm_;
return keyset_signature_challenge_info;
}
} // namespace cryptohome