blob: 64aec1dabd669de2ac0e2039270b3c492b71ffc0 [file] [log] [blame]
// Copyright 2018 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/signature_sealing_backend_tpm2_impl.h"
#include <cstring>
#include <optional>
#include <set>
#include <stdint.h>
#include <string>
#include <utility>
#include <base/check.h>
#include <base/check_op.h>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <base/notreached.h>
#include <base/numerics/safe_conversions.h>
#include <base/threading/thread_checker.h>
#include <brillo/secure_blob.h>
#include <cryptohome/proto_bindings/key.pb.h>
#include <google/protobuf/repeated_field.h>
#include <libhwsec/error/tpm2_error.h>
#include <libhwsec/status.h>
#include <trunks/error_codes.h>
#include <trunks/policy_session.h>
#include <trunks/tpm_generated.h>
#include <trunks/trunks_factory_impl.h>
#include <trunks/hmac_session.h>
#include <trunks/tpm_utility.h>
#include <trunks/authorization_delegate.h>
#include "cryptohome/signature_sealed_data.pb.h"
#include "cryptohome/tpm2_impl.h"
using brillo::Blob;
using brillo::BlobFromString;
using brillo::BlobToString;
using brillo::CombineBlobs;
using brillo::SecureBlob;
using hwsec::TPM2Error;
using hwsec::TPMError;
using hwsec::TPMErrorBase;
using hwsec::TPMRetryAction;
using hwsec_foundation::error::CreateError;
using hwsec_foundation::error::WrapError;
using trunks::GetErrorString;
using trunks::TPM_ALG_ID;
using trunks::TPM_ALG_NULL;
using trunks::TPM_RC;
using trunks::TPM_RC_SUCCESS;
namespace cryptohome {
namespace {
// Size, in bytes, of the secret value that is generated by
// SignatureSealingBackendTpm2Impl::CreateSealedSecret().
constexpr int kSecretSizeBytes = 32;
class UnsealingSessionTpm2Impl final
: public SignatureSealingBackend::UnsealingSession {
public:
UnsealingSessionTpm2Impl(
Tpm2Impl* tpm,
Tpm2Impl::TrunksClientContext* trunks,
const Blob& srk_wrapped_secret,
const Blob& public_key_spki_der,
structure::ChallengeSignatureAlgorithm algorithm,
TPM_ALG_ID scheme,
TPM_ALG_ID hash_alg,
std::unique_ptr<trunks::PolicySession> policy_session,
const Blob& policy_session_tpm_nonce);
UnsealingSessionTpm2Impl(const UnsealingSessionTpm2Impl&) = delete;
UnsealingSessionTpm2Impl& operator=(const UnsealingSessionTpm2Impl&) = delete;
~UnsealingSessionTpm2Impl() override;
// UnsealingSession:
structure::ChallengeSignatureAlgorithm GetChallengeAlgorithm() override;
Blob GetChallengeValue() override;
hwsec::Status Unseal(const Blob& signed_challenge_value,
SecureBlob* unsealed_value) override;
private:
// Unowned.
Tpm2Impl* const tpm_;
// Unowned.
Tpm2Impl::TrunksClientContext* const trunks_;
const Blob srk_wrapped_secret_;
const Blob public_key_spki_der_;
const structure::ChallengeSignatureAlgorithm algorithm_;
const TPM_ALG_ID scheme_;
const TPM_ALG_ID hash_alg_;
const std::unique_ptr<trunks::PolicySession> policy_session_;
const Blob policy_session_tpm_nonce_;
base::ThreadChecker thread_checker_;
};
// Obtains the TPM 2.0 signature scheme and hashing algorithms that correspond
// to the provided challenge signature algorithm.
bool GetAlgIdsByAlgorithm(structure::ChallengeSignatureAlgorithm algorithm,
TPM_ALG_ID* scheme,
TPM_ALG_ID* hash_alg) {
switch (algorithm) {
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha1:
*scheme = trunks::TPM_ALG_RSASSA;
*hash_alg = trunks::TPM_ALG_SHA1;
return true;
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha256:
*scheme = trunks::TPM_ALG_RSASSA;
*hash_alg = trunks::TPM_ALG_SHA256;
return true;
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha384:
*scheme = trunks::TPM_ALG_RSASSA;
*hash_alg = trunks::TPM_ALG_SHA384;
return true;
case structure::ChallengeSignatureAlgorithm::kRsassaPkcs1V15Sha512:
*scheme = trunks::TPM_ALG_RSASSA;
*hash_alg = trunks::TPM_ALG_SHA512;
return true;
}
NOTREACHED();
return false;
}
UnsealingSessionTpm2Impl::UnsealingSessionTpm2Impl(
Tpm2Impl* tpm,
Tpm2Impl::TrunksClientContext* trunks,
const Blob& srk_wrapped_secret,
const Blob& public_key_spki_der,
structure::ChallengeSignatureAlgorithm algorithm,
TPM_ALG_ID scheme,
TPM_ALG_ID hash_alg,
std::unique_ptr<trunks::PolicySession> policy_session,
const Blob& policy_session_tpm_nonce)
: tpm_(tpm),
trunks_(trunks),
srk_wrapped_secret_(srk_wrapped_secret),
public_key_spki_der_(public_key_spki_der),
algorithm_(algorithm),
scheme_(scheme),
hash_alg_(hash_alg),
policy_session_(std::move(policy_session)),
policy_session_tpm_nonce_(policy_session_tpm_nonce) {}
UnsealingSessionTpm2Impl::~UnsealingSessionTpm2Impl() {
DCHECK(thread_checker_.CalledOnValidThread());
}
structure::ChallengeSignatureAlgorithm
UnsealingSessionTpm2Impl::GetChallengeAlgorithm() {
DCHECK(thread_checker_.CalledOnValidThread());
return algorithm_;
}
Blob UnsealingSessionTpm2Impl::GetChallengeValue() {
DCHECK(thread_checker_.CalledOnValidThread());
const Blob expiration_blob(4); // zero expiration (4-byte integer)
return CombineBlobs({policy_session_tpm_nonce_, expiration_blob});
}
hwsec::Status UnsealingSessionTpm2Impl::Unseal(
const Blob& signed_challenge_value, SecureBlob* unsealed_value) {
DCHECK(thread_checker_.CalledOnValidThread());
// Start a TPM authorization session.
std::unique_ptr<trunks::HmacSession> session =
trunks_->factory->GetHmacSession();
if (auto err = CreateError<TPM2Error>(
trunks_->tpm_utility->StartSession(session.get()))) {
return WrapError<TPMError>(std::move(err), "Error starting hmac session");
}
// Load the protection public key onto the TPM.
ScopedKeyHandle key_handle;
if (!tpm_->LoadPublicKeyFromSpki(
public_key_spki_der_, AsymmetricKeyUsage::kSignKey, scheme_,
hash_alg_, session->GetDelegate(), &key_handle)) {
return CreateError<TPMError>("Error loading protection key",
TPMRetryAction::kNoRetry);
}
std::string key_name;
if (auto err = CreateError<TPM2Error>(
trunks_->tpm_utility->GetKeyName(key_handle.value(), &key_name))) {
return WrapError<TPMError>(std::move(err), "Failed to get key name");
}
// Update the policy with the signature.
trunks::TPMT_SIGNATURE signature;
memset(&signature, 0, sizeof(trunks::TPMT_SIGNATURE));
signature.sig_alg = scheme_;
signature.signature.rsassa.hash = hash_alg_;
signature.signature.rsassa.sig =
trunks::Make_TPM2B_PUBLIC_KEY_RSA(BlobToString(signed_challenge_value));
if (auto err = CreateError<TPM2Error>(policy_session_->PolicySigned(
key_handle.value(), key_name, BlobToString(policy_session_tpm_nonce_),
std::string() /* cp_hash */, std::string() /* policy_ref */,
0 /* expiration */, signature, session->GetDelegate()))) {
return WrapError<TPMError>(
std::move(err),
"Error restricting policy to signature with the public key");
}
// Obtain the resulting policy digest.
std::string policy_digest;
if (auto err =
CreateError<TPM2Error>(policy_session_->GetDigest(&policy_digest))) {
return WrapError<TPMError>(std::move(err), "Error getting policy digest");
}
// Unseal the secret value.
std::string unsealed_value_string;
if (auto err = CreateError<TPM2Error>(trunks_->tpm_utility->UnsealData(
BlobToString(srk_wrapped_secret_), policy_session_->GetDelegate(),
&unsealed_value_string))) {
return WrapError<TPMError>(std::move(err), "Error unsealing object");
}
*unsealed_value = SecureBlob(unsealed_value_string);
return nullptr;
}
std::map<uint32_t, std::string> ToStrPcrMap(
const std::map<uint32_t, brillo::Blob>& pcr_map) {
std::map<uint32_t, std::string> str_pcr_map;
for (const auto& [index, value] : pcr_map) {
str_pcr_map[index] = brillo::BlobToString(value);
}
return str_pcr_map;
}
hwsec::Status GetPcrPolicyDigest(
trunks::PolicySession* policy_session,
const std::map<uint32_t, brillo::Blob>& pcr_map,
std::string* pcr_policy_digest) {
std::map<uint32_t, std::string> str_pcr_map = ToStrPcrMap(pcr_map);
// Run PolicyPCR against the PCR set.
if (auto err =
CreateError<TPM2Error>(policy_session->PolicyPCR(str_pcr_map))) {
return WrapError<TPMError>(std::move(err),
"Error restricting policy to PCRs");
}
// Remember the policy digest for the PCR set.
if (auto err = CreateError<TPM2Error>(
policy_session->GetDigest(pcr_policy_digest))) {
return WrapError<TPMError>(std::move(err), "Error getting policy digest");
}
// Restart the policy session.
if (auto err = CreateError<TPM2Error>(policy_session->PolicyRestart())) {
return WrapError<TPMError>(std::move(err),
"Error restarting the policy session");
}
return nullptr;
}
} // namespace
SignatureSealingBackendTpm2Impl::SignatureSealingBackendTpm2Impl(Tpm2Impl* tpm)
: tpm_(tpm) {}
SignatureSealingBackendTpm2Impl::~SignatureSealingBackendTpm2Impl() = default;
hwsec::Status SignatureSealingBackendTpm2Impl::CreateSealedSecret(
const Blob& public_key_spki_der,
const std::vector<structure::ChallengeSignatureAlgorithm>& key_algorithms,
const std::string& obfuscated_username,
const Blob& /* delegate_blob */,
const Blob& /* delegate_secret */,
SecureBlob* secret_value,
structure::SignatureSealedData* sealed_secret_data) {
// Choose the algorithm. Respect the input's algorithm prioritization, with
// the exception of considering SHA-1 as the least preferred option.
TPM_ALG_ID scheme = TPM_ALG_NULL;
TPM_ALG_ID hash_alg = TPM_ALG_NULL;
for (auto algorithm : key_algorithms) {
TPM_ALG_ID current_scheme = TPM_ALG_NULL;
TPM_ALG_ID current_hash_alg = TPM_ALG_NULL;
if (GetAlgIdsByAlgorithm(algorithm, &current_scheme, &current_hash_alg)) {
scheme = current_scheme;
hash_alg = current_hash_alg;
if (hash_alg != trunks::TPM_ALG_SHA1)
break;
}
}
if (scheme == TPM_ALG_NULL) {
return CreateError<TPMError>("Error choosing the signature algorithm",
TPMRetryAction::kNoRetry);
}
// Start a TPM authorization session.
Tpm2Impl::TrunksClientContext* trunks = nullptr;
if (!tpm_->GetTrunksContext(&trunks)) {
return CreateError<TPMError>("Failed to get trunks context",
TPMRetryAction::kNoRetry);
}
std::unique_ptr<trunks::HmacSession> session =
trunks->factory->GetHmacSession();
if (auto err = CreateError<TPM2Error>(
trunks->tpm_utility->StartSession(session.get()))) {
return WrapError<TPMError>(std::move(err), "Error starting hmac session");
}
// Load the protection public key onto the TPM.
ScopedKeyHandle key_handle;
if (!tpm_->LoadPublicKeyFromSpki(
public_key_spki_der, AsymmetricKeyUsage::kSignKey, scheme, hash_alg,
session->GetDelegate(), &key_handle)) {
return CreateError<TPMError>("Error loading protection key",
TPMRetryAction::kNoRetry);
}
std::string key_name;
if (auto err = CreateError<TPM2Error>(
trunks->tpm_utility->GetKeyName(key_handle.value(), &key_name))) {
return WrapError<TPMError>(std::move(err), "Failed to get key name");
}
// Start a trial policy session for sealing the secret value.
std::unique_ptr<trunks::PolicySession> policy_session =
trunks->factory->GetTrialSession();
if (auto err = CreateError<TPM2Error>(
policy_session->StartUnboundSession(true, false))) {
return WrapError<TPMError>(std::move(err),
"Error starting a trial session");
}
std::map<uint32_t, brillo::Blob> default_pcr_map =
tpm_->GetPcrMap(obfuscated_username, /*use_extended_pcr=*/false);
std::map<uint32_t, brillo::Blob> extended_pcr_map =
tpm_->GetPcrMap(obfuscated_username, /*use_extended_pcr=*/true);
// Calculate policy digests for each of the sets of PCR restrictions
// separately. Rewind each time the policy session back to the initial state.
std::vector<std::string> pcr_policy_digests;
std::string default_pcr_policy_digest;
if (hwsec::Status err = GetPcrPolicyDigest(
policy_session.get(), default_pcr_map, &default_pcr_policy_digest)) {
return WrapError<TPMError>(std::move(err),
"Error getting default PCR policy digest");
}
pcr_policy_digests.push_back(default_pcr_policy_digest);
std::string extended_pcr_policy_digest;
if (hwsec::Status err = GetPcrPolicyDigest(
policy_session.get(), default_pcr_map, &extended_pcr_policy_digest)) {
return WrapError<TPMError>(std::move(err),
"Error getting default PCR policy digest");
}
pcr_policy_digests.push_back(extended_pcr_policy_digest);
// Apply PolicyOR for restricting to the disjunction of the specified sets of
// PCR restrictions.
if (auto err = CreateError<TPM2Error>(
policy_session->PolicyOR(pcr_policy_digests))) {
return WrapError<TPMError>(
std::move(err),
"Error restricting policy to logical disjunction of PCRs");
}
// Update the policy with an empty signature that refers to the public key.
trunks::TPMT_SIGNATURE signature;
memset(&signature, 0, sizeof(trunks::TPMT_SIGNATURE));
signature.sig_alg = scheme;
signature.signature.rsassa.hash = hash_alg;
signature.signature.rsassa.sig =
trunks::Make_TPM2B_PUBLIC_KEY_RSA(std::string());
if (auto err = CreateError<TPM2Error>(policy_session->PolicySigned(
key_handle.value(), key_name, std::string() /* nonce */,
std::string() /* cp_hash */, std::string() /* policy_ref */,
0 /* expiration */, signature, session->GetDelegate()))) {
return WrapError<TPMError>(
std::move(err),
"Error restricting policy to signature with the public key");
}
// Obtain the resulting policy digest.
std::string policy_digest;
if (auto err =
CreateError<TPM2Error>(policy_session->GetDigest(&policy_digest))) {
return WrapError<TPMError>(std::move(err), "Error getting policy digest");
}
if (policy_digest.size() != SHA256_DIGEST_SIZE) {
return CreateError<TPMError>("Unexpected policy digest size",
TPMRetryAction::kNoRetry);
}
// Generate the secret value randomly.
if (auto err =
tpm_->GetRandomDataSecureBlob(kSecretSizeBytes, secret_value)) {
return WrapError<TPMError>(std::move(err),
"Error generating random secret");
}
DCHECK_EQ(secret_value->size(), kSecretSizeBytes);
// Seal the secret value.
std::string sealed_value;
if (auto err = CreateError<TPM2Error>(trunks->tpm_utility->SealData(
secret_value->to_string(), policy_digest, "",
/*require_admin_with_policy=*/true, session->GetDelegate(),
&sealed_value))) {
return WrapError<TPMError>(std::move(err), "Error sealing secret data");
}
// Fill the resulting proto with data required for unsealing.
structure::Tpm2PolicySignedData data;
data.public_key_spki_der = public_key_spki_der;
data.srk_wrapped_secret = BlobFromString(sealed_value);
data.scheme = scheme;
data.hash_alg = hash_alg;
data.default_pcr_policy_digest = BlobFromString(default_pcr_policy_digest);
data.extended_pcr_policy_digest = BlobFromString(extended_pcr_policy_digest);
*sealed_secret_data = data;
return nullptr;
}
hwsec::Status SignatureSealingBackendTpm2Impl::CreateUnsealingSession(
const structure::SignatureSealedData& sealed_secret_data,
const Blob& public_key_spki_der,
const std::vector<structure::ChallengeSignatureAlgorithm>& key_algorithms,
const std::set<uint32_t>& pcr_set,
const Blob& /* delegate_blob */,
const Blob& /* delegate_secret */,
bool /* locked_to_single_user */,
std::unique_ptr<SignatureSealingBackend::UnsealingSession>*
unsealing_session) {
// Validate the parameters.
auto* sealed_secret_data_ptr =
std::get_if<structure::Tpm2PolicySignedData>(&sealed_secret_data);
if (!sealed_secret_data_ptr) {
return CreateError<TPMError>(
"Sealed data is empty or uses unexpected method",
TPMRetryAction::kNoRetry);
}
const structure::Tpm2PolicySignedData& data = *sealed_secret_data_ptr;
if (data.public_key_spki_der.empty()) {
return CreateError<TPMError>("Empty public key", TPMRetryAction::kNoRetry);
}
if (data.srk_wrapped_secret.empty()) {
return CreateError<TPMError>("Empty SRK wrapped secret",
TPMRetryAction::kNoRetry);
}
if (!data.scheme.has_value()) {
return CreateError<TPMError>("Empty scheme", TPMRetryAction::kNoRetry);
}
if (!data.hash_alg.has_value()) {
return CreateError<TPMError>("Empty hash algorithm",
TPMRetryAction::kNoRetry);
}
if (data.public_key_spki_der != public_key_spki_der) {
return CreateError<TPMError>("Wrong subject public key info",
TPMRetryAction::kNoRetry);
}
if (!base::IsValueInRangeForNumericType<TPM_ALG_ID>(data.scheme.value())) {
return CreateError<TPMError>("Error parsing signature scheme",
TPMRetryAction::kNoRetry);
}
const TPM_ALG_ID scheme = static_cast<TPM_ALG_ID>(data.scheme.value());
if (!base::IsValueInRangeForNumericType<TPM_ALG_ID>(data.hash_alg.value())) {
return CreateError<TPMError>("Error parsing signature hash algorithm",
TPMRetryAction::kNoRetry);
}
const TPM_ALG_ID hash_alg = static_cast<TPM_ALG_ID>(data.hash_alg.value());
std::optional<structure::ChallengeSignatureAlgorithm> chosen_algorithm;
for (auto algorithm : key_algorithms) {
TPM_ALG_ID current_scheme = TPM_ALG_NULL;
TPM_ALG_ID current_hash_alg = TPM_ALG_NULL;
if (GetAlgIdsByAlgorithm(algorithm, &current_scheme, &current_hash_alg) &&
current_scheme == scheme && current_hash_alg == hash_alg) {
chosen_algorithm = algorithm;
break;
}
}
if (!chosen_algorithm) {
return CreateError<TPMError>("Key doesn't support required algorithm",
TPMRetryAction::kNoRetry);
}
// Obtain the trunks context to be used for the whole unsealing session.
Tpm2Impl::TrunksClientContext* trunks = nullptr;
if (!tpm_->GetTrunksContext(&trunks)) {
return CreateError<TPMError>("Failed to get trunks context",
TPMRetryAction::kNoRetry);
}
// Start a policy session that will be used for obtaining the TPM nonce and
// unsealing the secret value.
std::unique_ptr<trunks::PolicySession> policy_session =
trunks->factory->GetPolicySession();
if (auto err = CreateError<TPM2Error>(
policy_session->StartUnboundSession(true, false))) {
return WrapError<TPMError>(std::move(err),
"Error starting a policy session");
}
if (data.default_pcr_policy_digest.empty() ||
data.extended_pcr_policy_digest.empty()) {
return CreateError<TPMError>("Empty PCR policy digests",
TPMRetryAction::kNoRetry);
}
if (data.default_pcr_policy_digest.size() != SHA256_DIGEST_SIZE ||
data.extended_pcr_policy_digest.size() != SHA256_DIGEST_SIZE) {
return CreateError<TPMError>("Invalid policy digest size",
TPMRetryAction::kNoRetry);
}
// The PCR map with empty user name.
std::map<uint32_t, std::string> pcr_map;
// Create a PCR map with empty strings to use current PCR values.
for (uint32_t pcr_index : pcr_set) {
pcr_map[pcr_index] = std::string();
}
if (auto err = CreateError<TPM2Error>(policy_session->PolicyPCR(pcr_map))) {
return WrapError<TPMError>(std::move(err),
"Error restricting policy to PCRs");
}
// Update the policy with the disjunction of their policy digests.
// Note: The order of items in this vector is important, it must match the
// order used in the CreateSealedSecret() function, and should never change
// due to backwards compatibility.
std::vector<std::string> pcr_policy_digests;
pcr_policy_digests.push_back(BlobToString(data.default_pcr_policy_digest));
pcr_policy_digests.push_back(BlobToString(data.extended_pcr_policy_digest));
if (auto err = CreateError<TPM2Error>(
policy_session->PolicyOR(pcr_policy_digests))) {
return WrapError<TPMError>(
std::move(err),
"Error restricting policy to logical disjunction of PCRs");
}
// Obtain the TPM nonce.
std::string tpm_nonce;
if (!policy_session->GetDelegate()->GetTpmNonce(&tpm_nonce)) {
return CreateError<TPMError>("Error obtaining TPM nonce",
TPMRetryAction::kNoRetry);
}
// Create the unsealing session that will keep the required state.
*unsealing_session = std::make_unique<UnsealingSessionTpm2Impl>(
tpm_, trunks, data.srk_wrapped_secret, public_key_spki_der,
*chosen_algorithm, scheme, hash_alg, std::move(policy_session),
BlobFromString(tpm_nonce));
return nullptr;
}
} // namespace cryptohome