blob: ca99fa0e0e8a6acc7ecb3f7839437d284d42fb05 [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 "hwsec-test-utils/fake_pca_agent/pca_enroll_v1.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <arpa/inet.h>
#include <base/check_op.h>
#include <base/hash/sha1.h>
#include <base/logging.h>
#include <crypto/scoped_openssl_types.h>
#include <openssl/err.h>
#include <trousers/scoped_tss_type.h>
#include <trousers/trousers.h>
#include <trousers/tss.h>
#include "hwsec-test-utils/common/attestation_crypto.h"
#include "hwsec-test-utils/common/openssl_utility.h"
#include "hwsec-test-utils/fake_pca_agent/issue_certificate.h"
#include "hwsec-test-utils/fake_pca_agent/tpm1_struct_utils.h"
#include "hwsec-test-utils/well_known_key_pairs/well_known_key_pairs.h"
namespace hwsec_test_utils {
namespace fake_pca_agent {
namespace {
constexpr int kExpectedPcrLength = 20;
constexpr int kAesKeySize = 32;
constexpr int kAesBlockSize = 16;
// Uses RSA_padding_add_PKCS1_OAEP_mgf1 to generate the padded data.
base::Optional<std::string> OaepPaddingWithParam(
const std::string& data,
size_t rsa_key_size,
const std::string& encoding_param) {
std::unique_ptr<unsigned char[]> output(
std::make_unique<unsigned char[]>(rsa_key_size));
if (RSA_padding_add_PKCS1_OAEP_mgf1(
output.get(), rsa_key_size,
reinterpret_cast<const unsigned char*>(data.data()), data.length(),
reinterpret_cast<const unsigned char*>(encoding_param.data()),
encoding_param.length(), EVP_sha1(), EVP_sha1()) != 1) {
LOG(ERROR) << __func__
<< ": Failed to call RSA_padding_add_PKCS1_OAEP_mgf1: "
<< GetOpenSSLError();
return {};
}
return std::string(output.get(), output.get() + rsa_key_size);
}
} // namespace
bool PcaEnrollV1::Preprocess() {
identity_key_ = TpmPublicKeyToEVP(request_.identity_public_key(),
/*public_key_digest=*/nullptr);
if (!identity_key_) {
LOG(ERROR) << __func__ << ": Failed to parse identity key.";
return false;
}
crypto::ScopedEVP_PKEY ca_encryption_key =
well_known_key_pairs::GetCaEncryptionkey();
std::string decrypted_data;
attestation_crypto::ReturnStatus decrypt_status = attestation_crypto::Decrypt(
request_.encrypted_endorsement_credential(), ca_encryption_key,
attestation_crypto::KeyDeriverDirect(), &decrypted_data);
if (decrypt_status != attestation_crypto::ReturnStatus::kSuccess) {
LOG(ERROR) << __func__
<< ": Failed to decrypt serialized key info; status code: "
<< static_cast<int>(decrypt_status);
return {};
}
const unsigned char* asn1_ptr =
reinterpret_cast<const unsigned char*>(decrypted_data.data());
crypto::ScopedX509 x509(
d2i_X509(nullptr, &asn1_ptr, decrypted_data.length()));
if (!x509) {
LOG(ERROR) << __func__
<< ": Failed to call d2i_X509: " << GetOpenSSLError();
return false;
}
endorsement_key_.reset(X509_get_pubkey(x509.get()));
if (!endorsement_key_) {
LOG(ERROR) << __func__
<< ": Failed to call X509_get_pubkey: " << GetOpenSSLError();
return false;
}
return true;
}
bool PcaEnrollV1::Verify() {
const std::vector<const attestation::Quote*> quotes = {
&request_.pcr0_quote(), &request_.pcr1_quote()};
// Don't early return; instead, run through all pcr quotes to get more
// information for debugging.
bool any_failed_check = false;
for (int i = 0; i < quotes.size(); ++i) {
if (!EVPDigestVerify(identity_key_, EVP_sha1(), quotes[i]->quoted_data(),
quotes[i]->quote())) {
LOG(ERROR) << __func__ << "Failed to verify quote of PCR" << i << '.';
any_failed_check = true;
}
if (quotes[i]->quoted_pcr_value().size() != kExpectedPcrLength) {
LOG(ERROR) << __func__ << ": Unexpected PCR" << i
<< " value length: " << quotes[i]->quoted_pcr_value().size();
any_failed_check = true;
} else {
const std::string digest = base::SHA1HashString(
ToPcrComposite(i, quotes[i]->quoted_pcr_value()));
const TPM_QUOTE_INFO& tpm_quote_info =
*reinterpret_cast<const TPM_QUOTE_INFO*>(
quotes[i]->quoted_data().data());
if (memcmp(&tpm_quote_info.fixed, "QUOT", sizeof(tpm_quote_info.fixed)) !=
0) {
LOG(ERROR) << __func__ << ": Mismatched 'fixed' field.";
any_failed_check = true;
}
if (memcmp(digest.data(), tpm_quote_info.compositeHash.digest,
digest.length()) != 0) {
LOG(ERROR) << __func__ << ": Mismatched digest for PCR" << i << ".";
any_failed_check = true;
}
}
}
return !any_failed_check;
}
bool PcaEnrollV1::Generate() {
base::Optional<std::string> cert_der = IssueTestCertificateDer(identity_key_);
if (!cert_der) {
LOG(ERROR) << __func__
<< ": Failed to issue a test certificate for the identity key.";
return false;
}
// Generate an AES key and IV.
base::Optional<std::string> aes_key = GetRandom(kAesKeySize);
if (!aes_key) {
LOG(ERROR) << __func__ << ": Failed to create aes key.";
return false;
}
base::Optional<std::string> iv = GetRandom(kAesBlockSize);
if (!iv) {
LOG(ERROR) << __func__ << ": Failed to create IV.";
return false;
}
// Encrypt the certificate.
base::Optional<std::string> encrypted_cert =
EVPAesEncrypt(*cert_der, EVP_aes_256_cbc(), *aes_key, *iv);
if (!encrypted_cert) {
LOG(ERROR) << __func__ << ": Failed to encrypt certificate.";
}
const std::string encrypted_credential = *iv + *encrypted_cert;
// Construct |TPM_ASYM_CA_CONTENTS|.
TPM_ASYM_CA_CONTENTS asym_ac_contents = {};
asym_ac_contents.sessionKey.algId = TPM_ALG_AES256;
asym_ac_contents.sessionKey.encScheme = TPM_ES_SYM_CBC_PKCS5PAD;
asym_ac_contents.sessionKey.size = kAesKeySize;
asym_ac_contents.sessionKey.data =
reinterpret_cast<BYTE*>(const_cast<char*>(aes_key->data()));
const std::string aik_public_key_digest =
base::SHA1HashString(request_.identity_public_key());
CHECK_EQ(sizeof(asym_ac_contents.idDigest.digest),
aik_public_key_digest.length());
memcpy(asym_ac_contents.idDigest.digest, aik_public_key_digest.data(),
aik_public_key_digest.length());
const std::string asym_ac_contents_blob = Serialize(&asym_ac_contents);
// Encrypt the TPM_ASYM_CA_CONTENTS with the EK public key.
// The padding scheme and parameters are defined at Part I, section 31.1.1 of
// TPM spec part 1.
base::Optional<std::string> padded_asym_ac_contents_blob =
OaepPaddingWithParam(asym_ac_contents_blob,
RSA_size(EVP_PKEY_get0_RSA(endorsement_key_.get())),
"TCPA");
if (!padded_asym_ac_contents_blob) {
LOG(ERROR) << __func__ << ": Failed to add padding.";
return false;
}
base::Optional<std::string> encrypted_asym_content = EVPRsaEncrypt(
endorsement_key_, *padded_asym_ac_contents_blob, RSA_NO_PADDING);
if (!encrypted_asym_content) {
LOG(ERROR) << __func__ << ": Failed to encrypt TPM_ASYM_CA_CONTENTS.";
return false;
}
// Construct a TPM_SYM_CA_ATTESTATION structure.
TPM_SYM_CA_ATTESTATION sym_ca_attestation = {};
// Only set up the credential because trousers doesn't use the algorithm
// field.
sym_ca_attestation.credSize = encrypted_credential.length();
sym_ca_attestation.credential =
reinterpret_cast<BYTE*>(const_cast<char*>(encrypted_credential.data()));
// Last, construct the response.
encrypted_identity_credential_ = attestation::EncryptedIdentityCredential();
encrypted_identity_credential_->set_asym_ca_contents(*encrypted_asym_content);
encrypted_identity_credential_->set_sym_ca_attestation(
Serialize(&sym_ca_attestation));
encrypted_identity_credential_->set_tpm_version(attestation::TPM_1_2);
return true;
}
bool PcaEnrollV1::Write(attestation::AttestationEnrollmentResponse* response) {
if (!encrypted_identity_credential_) {
return false;
}
*response->mutable_encrypted_identity_credential() =
*encrypted_identity_credential_;
return true;
}
} // namespace fake_pca_agent
} // namespace hwsec_test_utils