blob: 2d4032c9e8daddd1efb81380831095dd117f8f51 [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/challenge_credentials/challenge_credentials_verify_key_operation.h"
#include <optional>
#include <utility>
#include <base/bind.h>
#include <base/check.h>
#include <base/check_op.h>
#include <base/logging.h>
#include <crypto/scoped_openssl_types.h>
#include <libhwsec/status.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include "cryptohome/tpm.h"
using brillo::Blob;
using hwsec::TPMErrorBase;
namespace cryptohome {
namespace {
// Size of the verification challenge.
constexpr int kChallengeByteCount = 20;
// Returns the signature algorithm to be used for the verification.
std::optional<structure::ChallengeSignatureAlgorithm> ChooseChallengeAlgorithm(
const structure::ChallengePublicKeyInfo& public_key_info) {
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;
}
int GetOpenSslSignatureAlgorithmNid(
structure::ChallengeSignatureAlgorithm algorithm) {
switch (algorithm) {
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha1:
return NID_sha1;
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha256:
return NID_sha256;
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha384:
return NID_sha384;
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha512:
return NID_sha512;
}
return 0;
}
bool IsValidSignature(const Blob& public_key_spki_der,
structure::ChallengeSignatureAlgorithm algorithm,
const Blob& input,
const Blob& signature) {
const int openssl_algorithm_nid = GetOpenSslSignatureAlgorithmNid(algorithm);
if (!openssl_algorithm_nid) {
LOG(ERROR)
<< "Unknown challenge algorithm for challenge signature verification";
return false;
}
const EVP_MD* const openssl_digest =
EVP_get_digestbynid(openssl_algorithm_nid);
if (!openssl_digest) {
LOG(ERROR) << "Error obtaining digest for challenge signature verification";
return false;
}
const unsigned char* asn1_ptr = public_key_spki_der.data();
crypto::ScopedEVP_PKEY openssl_public_key(
d2i_PUBKEY(NULL, &asn1_ptr, public_key_spki_der.size()));
if (!openssl_public_key) {
LOG(ERROR)
<< "Error loading public key for challenge signature verification";
return false;
}
crypto::ScopedEVP_MD_CTX openssl_context(EVP_MD_CTX_new());
if (!openssl_context) {
LOG(ERROR) << "Error creating challenge signature verification context";
return false;
}
if (!EVP_DigestVerifyInit(openssl_context.get(),
/*EVP_PKEY_CTX **pctx=*/nullptr, openssl_digest,
/*ENGINE *e=*/nullptr, openssl_public_key.get())) {
LOG(ERROR) << "Failed to initialize challenge signature verification";
return false;
}
if (!EVP_DigestVerifyUpdate(openssl_context.get(), input.data(),
input.size())) {
LOG(ERROR)
<< "Failed to update digest for challenge signature verification";
return false;
}
if (EVP_DigestVerifyFinal(openssl_context.get(), signature.data(),
signature.size()) != 1) {
LOG(ERROR) << "Challenge signature verification failed";
return false;
}
return true;
}
} // namespace
ChallengeCredentialsVerifyKeyOperation::ChallengeCredentialsVerifyKeyOperation(
KeyChallengeService* key_challenge_service,
Tpm* tpm,
const std::string& account_id,
const structure::ChallengePublicKeyInfo& public_key_info,
CompletionCallback completion_callback)
: ChallengeCredentialsOperation(key_challenge_service),
tpm_(tpm),
account_id_(account_id),
public_key_info_(public_key_info),
completion_callback_(std::move(completion_callback)) {}
ChallengeCredentialsVerifyKeyOperation::
~ChallengeCredentialsVerifyKeyOperation() = default;
void ChallengeCredentialsVerifyKeyOperation::Start() {
DCHECK(thread_checker_.CalledOnValidThread());
const brillo::Blob& public_key_spki_der =
public_key_info_.public_key_spki_der;
if (!public_key_info_.signature_algorithm.size()) {
LOG(ERROR) << "The key does not support any signature algorithm";
Complete(&completion_callback_, /*is_key_valid=*/false);
return;
}
const std::optional<structure::ChallengeSignatureAlgorithm>
chosen_challenge_algorithm = ChooseChallengeAlgorithm(public_key_info_);
if (!chosen_challenge_algorithm) {
LOG(ERROR) << "Failed to choose verification signature challenge algorithm";
Complete(&completion_callback_, /*is_key_valid=*/false);
return;
}
Blob challenge;
if (hwsec::Status err =
tpm_->GetRandomDataBlob(kChallengeByteCount, &challenge)) {
LOG(ERROR)
<< "Failed to generate random bytes for the verification challenge: "
<< err;
Complete(&completion_callback_, /*is_key_valid=*/false);
return;
}
MakeKeySignatureChallenge(
account_id_, public_key_spki_der, challenge, *chosen_challenge_algorithm,
base::BindOnce(
&ChallengeCredentialsVerifyKeyOperation::OnChallengeResponse,
weak_ptr_factory_.GetWeakPtr(), public_key_spki_der,
*chosen_challenge_algorithm, challenge));
}
void ChallengeCredentialsVerifyKeyOperation::Abort() {
DCHECK(thread_checker_.CalledOnValidThread());
Complete(&completion_callback_, /*is_key_valid=*/false);
// |this| can be already destroyed at this point.
}
void ChallengeCredentialsVerifyKeyOperation::OnChallengeResponse(
const Blob& public_key_spki_der,
structure::ChallengeSignatureAlgorithm challenge_algorithm,
const Blob& challenge,
std::unique_ptr<Blob> challenge_response) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!challenge_response) {
LOG(ERROR) << "Verification signature challenge failed";
Complete(&completion_callback_, /*is_key_valid=*/false);
return;
}
if (!IsValidSignature(public_key_spki_der, challenge_algorithm, challenge,
*challenge_response)) {
LOG(ERROR) << "Invalid signature for the verification challenge";
Complete(&completion_callback_, /*is_key_valid=*/false);
return;
}
Complete(&completion_callback_, /*is_key_valid=*/true);
}
} // namespace cryptohome