blob: 2e15e3179a99e0a367f7a97459ec6ab8b2d9b198 [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_tpm1_impl.h"
#include <algorithm>
#include <iterator>
#include <string>
#include <utility>
#include <base/check.h>
#include <base/check_op.h>
#include <base/logging.h>
#include <base/memory/free_deleter.h>
#include <brillo/secure_blob.h>
#include <crypto/libcrypto-compat.h>
#include <crypto/scoped_openssl_types.h>
#include <libhwsec/error/tpm1_error.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <trousers/scoped_tss_type.h>
#include <trousers/tss.h>
#include <trousers/trousers.h> // NOLINT(build/include_alpha) - needs tss.h
#include "cryptohome/crypto/rsa.h"
#include "cryptohome/crypto/sha.h"
#include "cryptohome/key.pb.h"
#include "cryptohome/signature_sealed_data.pb.h"
#include "cryptohome/tpm1_static_utils.h"
#include "cryptohome/tpm_impl.h"
using brillo::Blob;
using brillo::BlobFromString;
using brillo::BlobToString;
using brillo::CombineBlobs;
using brillo::SecureBlob;
using hwsec::error::TPM1Error;
using hwsec::error::TPMError;
using hwsec::error::TPMErrorBase;
using hwsec::error::TPMRetryAction;
using hwsec_foundation::error::CreateError;
using hwsec_foundation::error::WrapError;
using trousers::ScopedTssContext;
using trousers::ScopedTssKey;
using trousers::ScopedTssMemory;
using trousers::ScopedTssObject;
using trousers::ScopedTssPolicy;
using ScopedByteArray = std::unique_ptr<BYTE, base::FreeDeleter>;
namespace cryptohome {
namespace {
// Size, in bytes, of the secret value that is generated by
// SignatureSealingBackendTpm1Impl::CreateSealedSecret().
//
// The choice of this constant is driven solely by the requirement to contain
// sufficient amount of entropy for whatever goal the SignatureSealingBackend is
// used.
constexpr int kSecretSizeBytes = 32;
// Size of the AuthData blob to be randomly generated.
//
// The choice of this constant is dictated by the desire to provide sufficient
// amount of entropy as the authorization secret for the TPM_Seal command (but
// with taking into account that this authorization value is hashed by SHA-1
// by Trousers anyway).
constexpr int kAuthDataSizeBytes = 32;
// Size of the migration destination key to be generated. Note that the choice
// of this size is constrained by restrictions from the TPM 1.2 specs.
constexpr int kMigrationDestinationKeySizeBits = 2048;
constexpr int kMigrationDestinationKeySizeBytes =
kMigrationDestinationKeySizeBits / 8;
constexpr int kMigrationDestinationKeySizeFlag = TSS_KEY_SIZE_2048;
// Size of the certified migratable key to be created. Note that the choice of
// this size is dictated by restrictions from the TPM 1.2 specs.
constexpr int kCmkKeySizeBits = 2048;
constexpr int kCmkKeySizeBytes = kCmkKeySizeBits / 8;
constexpr int kCmkPrivateKeySizeBytes = kCmkKeySizeBytes / 2;
constexpr int kCmkKeySizeFlag = TSS_KEY_SIZE_2048;
// The RSA OAEP label parameter specified to be used by the TPM 1.2 specs (see
// TPM 1.2 Part 1 Section 31.1.1 "TPM_ES_RSAESOAEP_SHA1_MGF1").
constexpr char kTpmRsaOaepLabel[] = {'T', 'C', 'P', 'A'};
// Sizes of the two parts of the migrated CMK private key blob: as described in
// TPM 1.2 Part 3 Section 11.9 ("TPM_CMK_CreateBlob"), one part goes into the
// OAEP seed and the rest goes into the TPM_MIGRATE_ASYMKEY struct.
constexpr int kMigratedCmkPrivateKeySeedPartSizeBytes = 16;
constexpr int kMigratedCmkPrivateKeyRestPartSizeBytes = 112;
static_assert(kMigratedCmkPrivateKeySeedPartSizeBytes == SHA_DIGEST_LENGTH - 4,
"Invalid private key seed part size constant");
static_assert(kMigratedCmkPrivateKeySeedPartSizeBytes +
kMigratedCmkPrivateKeyRestPartSizeBytes ==
kCmkPrivateKeySizeBytes,
"Invalid private key part size constants");
// Size of the TPM_MIGRATE_ASYMKEY structure containing the part of the migrated
// private key blob.
constexpr int kTpmMigrateAsymkeyBlobSize =
sizeof(TPM_PAYLOAD_TYPE) /* for payload */ +
SHA_DIGEST_LENGTH /* for usageAuth.authdata */ +
SHA_DIGEST_LENGTH /* for pubDataDigest.digest */ +
sizeof(UINT32) /* for partPrivKeyLen */ +
kMigratedCmkPrivateKeyRestPartSizeBytes /* for *partPrivKey */;
// Scoped wrapper of the TPM_KEY12 struct.
class ScopedKey12 final {
public:
ScopedKey12() { memset(&value_, 0, sizeof(TPM_KEY12)); }
ScopedKey12(const ScopedKey12&) = delete;
ScopedKey12& operator=(const ScopedKey12&) = delete;
~ScopedKey12() {
free(value_.algorithmParms.parms);
free(value_.pubKey.key);
free(value_.encData);
free(value_.PCRInfo);
}
const TPM_KEY12& operator*() const { return value_; }
const TPM_KEY12* operator->() const { return &value_; }
TPM_KEY12* ptr() { return &value_; }
private:
TPM_KEY12 value_;
};
class UnsealingSessionTpm1Impl final
: public SignatureSealingBackend::UnsealingSession {
public:
UnsealingSessionTpm1Impl(TpmImpl* tpm,
const Blob& srk_wrapped_cmk,
const Blob& cmk_wrapped_auth_data,
const Blob& pcr_bound_secret,
const Blob& public_key_spki_der,
const Blob& delegate_blob,
const Blob& delegate_secret,
const Blob& cmk_pubkey,
const Blob& protection_key_pubkey,
crypto::ScopedRSA migration_destination_rsa,
const Blob& migration_destination_key_pubkey);
UnsealingSessionTpm1Impl(const UnsealingSessionTpm1Impl&) = delete;
UnsealingSessionTpm1Impl& operator=(const UnsealingSessionTpm1Impl&) = delete;
~UnsealingSessionTpm1Impl() override;
// UnsealingSession:
ChallengeSignatureAlgorithm GetChallengeAlgorithm() override;
Blob GetChallengeValue() override;
TPMErrorBase Unseal(const Blob& signed_challenge_value,
SecureBlob* unsealed_value) override;
private:
// Unowned.
TpmImpl* const tpm_;
// The blob of the CMK wrapped by the SRK.
const Blob srk_wrapped_cmk_;
// The AuthData blob encrypted by the CMK using the RSAES-OAEP MGF1 algorithm.
const Blob cmk_wrapped_auth_data_;
// The secret blob, which is bound to some PCR values with the AuthData value
// that is stored encrypted in |cmk_wrapped_auth_data_|.
const Blob pcr_bound_secret_;
// The DER-encoded Subject Public Key Info of the protection key.
const Blob public_key_spki_der_;
// The blob for the owner delegation.
const Blob delegate_blob_;
// The delegate secret for the delegate blob.
const Blob delegate_secret_;
// The TPM_PUBKEY blob of the CMK.
const Blob cmk_pubkey_;
// The SHA-1 digest of |cmk_pubkey_|.
const Blob cmk_pubkey_digest_;
// The TPM_PUBKEY blob of the protection key.
const Blob protection_key_pubkey_;
// The SHA-1 digest of |protection_key_pubkey_|.
const Blob protection_key_pubkey_digest_;
// The private RSA key of the migration destination key.
const crypto::ScopedRSA migration_destination_rsa_;
// The TPM_PUBKEY blob of the migration destination key.
const Blob migration_destination_key_pubkey_;
// The SHA-1 digest of |migration_destination_key_pubkey_|.
const Blob migration_destination_key_pubkey_digest_;
// The SHA-1 digest of the TPM_MSA_COMPOSITE structure containing a sole
// reference to |protection_key_pubkey_digest_|.
const Blob msa_composite_digest_;
};
// Extracts the public modulus from the OpenSSL RSA struct.
bool GetRsaModulus(const RSA& rsa, Blob* modulus) {
modulus->resize(RSA_size(&rsa));
const BIGNUM* n;
RSA_get0_key(&rsa, &n, nullptr, nullptr);
if (BN_bn2bin(n, modulus->data()) != modulus->size()) {
LOG(ERROR) << "Failed to extract RSA modulus: size mismatch";
return false;
}
return true;
}
// Parses the public key that is protecting the sealed data. The key size in
// bits is returned via |key_size_bits|, and the RSA key public modulus via
// |key_modulus|.
bool ParseProtectionKeySpki(const Blob& public_key_spki_der,
int* key_size_bits,
Blob* key_modulus) {
const unsigned char* asn1_ptr = public_key_spki_der.data();
crypto::ScopedEVP_PKEY pkey(
d2i_PUBKEY(nullptr, &asn1_ptr, public_key_spki_der.size()));
if (!pkey) {
LOG(ERROR) << "Error parsing protection public key: Failed to parse "
"Subject Public Key Info DER";
return false;
}
crypto::ScopedRSA rsa(EVP_PKEY_get1_RSA(pkey.get()));
if (!rsa) {
LOG(ERROR) << "Error parsing protection public key: Non-RSA key";
return false;
}
const BIGNUM* e;
RSA_get0_key(rsa.get(), nullptr, &e, nullptr);
const BN_ULONG key_exponent_word = BN_get_word(e);
if (key_exponent_word != kWellKnownExponent) {
// Trousers only supports the well-known exponent, failing internally on
// incorrect data serialization when other exponents are used.
LOG(ERROR) << "Error parsing protection public key: Exponent must be "
<< kWellKnownExponent;
return false;
}
*key_size_bits = RSA_size(rsa.get()) * 8;
if (*key_size_bits != 1024 && *key_size_bits != 2048) {
LOG(ERROR) << "Error parsing protection public key: Unsupported key size";
return false;
}
if (!GetRsaModulus(*rsa, key_modulus)) {
LOG(ERROR)
<< "Error parsing protection public key: Failed to extract key modulus";
return false;
}
return true;
}
// Parses the public key that is protecting the sealed data and loads it into
// Trousers. The key size in bits is returned via |key_size_bits|, and the
// Trousers handle of the loaded public key via |key_handle|.
bool ParseAndLoadProtectionKey(TpmImpl* const tpm,
TSS_HCONTEXT tpm_context,
const Blob& public_key_spki_der,
int* key_size_bits,
TSS_HKEY* key_handle) {
Blob key_modulus;
if (!ParseProtectionKeySpki(public_key_spki_der, key_size_bits,
&key_modulus)) {
LOG(ERROR) << "Failed to parse protection public key";
return false;
}
UINT32 key_size_flag = 0;
switch (*key_size_bits) {
case 1024:
key_size_flag = TSS_KEY_SIZE_1024;
break;
case 2048:
key_size_flag = TSS_KEY_SIZE_2048;
break;
default:
LOG(ERROR) << "Wrong size of protection public key";
return false;
}
if (!tpm->CreateRsaPublicKeyObject(
tpm_context, key_modulus,
TSS_KEY_VOLATILE | TSS_KEY_TYPE_SIGNING | key_size_flag,
TSS_SS_RSASSAPKCS1V15_SHA1, TSS_ES_NONE, key_handle)) {
LOG(ERROR) << "Failed to load protection public key";
return false;
}
return true;
}
// Loads the migration destination public key into Trousers. The loaded key
// handle is returned via |key_handle|.
bool LoadMigrationDestinationPublicKey(TpmImpl* const tpm,
TSS_HCONTEXT tpm_context,
const RSA& migration_destination_rsa,
TSS_HKEY* key_handle) {
Blob key_modulus;
if (!GetRsaModulus(migration_destination_rsa, &key_modulus)) {
LOG(ERROR) << "Error loading migration destination public key: Failed to "
"extract key modulus";
return false;
}
if (!tpm->CreateRsaPublicKeyObject(tpm_context, key_modulus,
TSS_KEY_VOLATILE | TSS_KEY_TYPE_STORAGE |
kMigrationDestinationKeySizeFlag,
TSS_SS_NONE, TSS_ES_RSAESOAEP_SHA1_MGF1,
key_handle)) {
LOG(ERROR) << "Error loading migration destination public key";
return false;
}
return true;
}
// Obtains via the TPM_AuthorizeMigrationKey command the migration authorization
// blob for the given migration destination key. Returns the authorization blob
// via |migration_authorization_blob|.
bool ObtainMigrationAuthorization(TSS_HCONTEXT tpm_context,
TSS_HTPM tpm_handle,
TSS_HKEY migration_destination_key_handle,
Blob* migration_authorization_blob) {
uint32_t migration_authorization_blob_buf_size = 0;
ScopedTssMemory migration_authorization_blob_buf(tpm_context);
TSS_RESULT tss_result = Tspi_TPM_AuthorizeMigrationTicket(
tpm_handle, migration_destination_key_handle,
TSS_MS_RESTRICT_APPROVE_DOUBLE, &migration_authorization_blob_buf_size,
migration_authorization_blob_buf.ptr());
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error obtaining the migration authorization: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
migration_authorization_blob->assign(
migration_authorization_blob_buf.value(),
migration_authorization_blob_buf.value() +
migration_authorization_blob_buf_size);
return true;
}
// Obtains via the TPM_CMK_CreateTicket command the CMK migration signature
// ticket for the signature of the challenge. Returns the ticket via
// |cmk_migration_signature_ticket|.
bool ObtainCmkMigrationSignatureTicket(
TpmImpl* tpm,
TSS_HCONTEXT tpm_context,
TSS_HTPM tpm_handle,
TSS_HKEY protection_key_handle,
const Blob& migration_destination_key_pubkey,
const Blob& cmk_pubkey,
const Blob& protection_key_pubkey,
const Blob& signed_challenge_value,
Blob* cmk_migration_signature_ticket) {
ScopedTssObject<TSS_HMIGDATA> migdata_handle(tpm_context);
TSS_RESULT tss_result = Tspi_Context_CreateObject(
tpm_context, TSS_OBJECT_TYPE_MIGDATA, 0, migdata_handle.ptr());
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error creating the CMK migration data object: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(
migdata_handle, TSS_MIGATTRIB_MIGRATIONBLOB,
TSS_MIGATTRIB_MIG_DESTINATION_PUBKEY_BLOB,
migration_destination_key_pubkey.size(),
const_cast<BYTE*>(migration_destination_key_pubkey.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error setting the CMK migration destination public key: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(migdata_handle, TSS_MIGATTRIB_MIGRATIONBLOB,
TSS_MIGATTRIB_MIG_SOURCE_PUBKEY_BLOB,
cmk_pubkey.size(),
const_cast<BYTE*>(cmk_pubkey.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error setting the CMK migration source public key: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(
migdata_handle, TSS_MIGATTRIB_MIGRATIONBLOB,
TSS_MIGATTRIB_MIG_AUTHORITY_PUBKEY_BLOB, protection_key_pubkey.size(),
const_cast<BYTE*>(protection_key_pubkey.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error setting the CMK migration authority public key: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(
migdata_handle, TSS_MIGATTRIB_TICKET_DATA, TSS_MIGATTRIB_TICKET_SIG_VALUE,
signed_challenge_value.size(),
const_cast<BYTE*>(signed_challenge_value.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error setting the CMK migration signed challenge data: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_TPM_CMKCreateTicket(tpm_handle, protection_key_handle,
migdata_handle);
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error obtaining the CMK migration signature ticket: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
SecureBlob local_cmk_migration_signature_ticket;
if (TPM1Error err = tpm->GetDataAttribute(
tpm_context, migdata_handle, TSS_MIGATTRIB_TICKET_DATA,
TSS_MIGATTRIB_TICKET_SIG_TICKET,
&local_cmk_migration_signature_ticket)) {
LOG(ERROR) << "Error reading the CMK migration signature ticket: " << *err;
return false;
}
// TODO(emaxx): Replace with a direct usage of Blob for the attribute read.
cmk_migration_signature_ticket->assign(
local_cmk_migration_signature_ticket.begin(),
local_cmk_migration_signature_ticket.end());
return true;
}
// Performs the migration of the CMK, passed in |srk_wrapped_cmk|, onto the key
// specified by |migration_destination_key_pubkey|, using the migration
// authorization from |migration_authorization_blob| and the CMK migration
// signature ticket from |cmk_migration_signature_ticket| for authorizing the
// migration. Returns the TPM_KEY12 blob of the migrated CMK via
// |migrated_cmk_key12_blob|, and the migration random XOR mask via
// |migration_random_blob| (see ExtractCmkPrivateKeyFromMigratedBlob() for the
// details).
bool MigrateCmk(TpmImpl* tpm,
TSS_HCONTEXT tpm_context,
TSS_HTPM tpm_handle,
TSS_HKEY srk_handle,
const Blob& srk_wrapped_cmk,
const Blob& migration_destination_key_pubkey,
const Blob& cmk_pubkey,
const Blob& protection_key_pubkey,
const Blob& migration_authorization_blob,
const Blob& cmk_migration_signature_ticket,
Blob* migrated_cmk_key12_blob,
Blob* migration_random_blob) {
// Load the wrapped CMK into Trousers.
ScopedTssObject<TSS_HMIGDATA> wrapped_cmk_handle(tpm_context);
TSS_RESULT tss_result = Tspi_Context_CreateObject(
tpm_context, TSS_OBJECT_TYPE_RSAKEY, 0, wrapped_cmk_handle.ptr());
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error creating the wrapped certified migratable key object: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(
wrapped_cmk_handle, TSS_TSPATTRIB_KEY_BLOB, TSS_TSPATTRIB_KEYBLOB_BLOB,
srk_wrapped_cmk.size(), const_cast<BYTE*>(srk_wrapped_cmk.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error setting the wrapped certified migratable key blob: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
// Prepare the parameters object for the migration command.
ScopedTssObject<TSS_HMIGDATA> migdata_handle(tpm_context);
tss_result = Tspi_Context_CreateObject(tpm_context, TSS_OBJECT_TYPE_MIGDATA,
0, migdata_handle.ptr());
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error creating the CMK migration data object: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(
migdata_handle, TSS_MIGATTRIB_MIGRATIONBLOB,
TSS_MIGATTRIB_MIG_DESTINATION_PUBKEY_BLOB,
migration_destination_key_pubkey.size(),
const_cast<BYTE*>(migration_destination_key_pubkey.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error setting the CMK migration destination public key: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(migdata_handle, TSS_MIGATTRIB_MIGRATIONBLOB,
TSS_MIGATTRIB_MIG_SOURCE_PUBKEY_BLOB,
cmk_pubkey.size(),
const_cast<BYTE*>(cmk_pubkey.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error setting the CMK migration source public key: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(
migdata_handle, TSS_MIGATTRIB_MIGRATIONBLOB,
TSS_MIGATTRIB_MIG_AUTHORITY_PUBKEY_BLOB, protection_key_pubkey.size(),
const_cast<BYTE*>(protection_key_pubkey.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error setting the CMK migration authority public key: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(
migdata_handle, TSS_MIGATTRIB_MIGRATIONBLOB,
TSS_MIGATTRIB_MIG_MSALIST_PUBKEY_BLOB, protection_key_pubkey.size(),
const_cast<BYTE*>(protection_key_pubkey.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR)
<< "Error setting the CMK migration selection authority public key: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(
migdata_handle, TSS_MIGATTRIB_MIGRATIONTICKET, 0,
migration_authorization_blob.size(),
const_cast<BYTE*>(migration_authorization_blob.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error setting the CMK migration authorization: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(
migdata_handle, TSS_MIGATTRIB_TICKET_DATA,
TSS_MIGATTRIB_TICKET_SIG_TICKET, cmk_migration_signature_ticket.size(),
const_cast<BYTE*>(cmk_migration_signature_ticket.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error setting the CMK migration signature ticket: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
// Perform the migration and extract the resulting data.
UINT32 migration_random_buf_size = 0;
ScopedTssMemory migration_random_buf(tpm_context);
tss_result = Tspi_Key_CMKCreateBlob(
wrapped_cmk_handle, srk_handle, migdata_handle,
&migration_random_buf_size, migration_random_buf.ptr());
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error performing the certified migratable key migration: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
migration_random_blob->assign(
migration_random_buf.value(),
migration_random_buf.value() + migration_random_buf_size);
SecureBlob local_migrated_cmk_key12_blob;
if (TPM1Error err = tpm->GetDataAttribute(
tpm_context, migdata_handle, TSS_MIGATTRIB_MIGRATIONBLOB,
TSS_MIGATTRIB_MIG_XOR_BLOB, &local_migrated_cmk_key12_blob)) {
LOG(ERROR) << "Failed to read the migrated key blob " << *err;
return false;
}
// TODO(emaxx): Replace with a direct usage of Blob for the attribute read.
migrated_cmk_key12_blob->assign(local_migrated_cmk_key12_blob.begin(),
local_migrated_cmk_key12_blob.end());
return true;
}
// Returns the digest of the blob of the TPM_MSA_COMPOSITE structure containing
// a sole reference to the specified key (whose TPM_PUBKEY blob is passed via
// |msa_pubkey_digest|).
Blob BuildMsaCompositeDigest(const Blob& msa_pubkey_digest) {
// Build the structure.
DCHECK_EQ(TPM_SHA1_160_HASH_LEN, msa_pubkey_digest.size());
TPM_DIGEST digest;
memcpy(digest.digest, msa_pubkey_digest.data(), msa_pubkey_digest.size());
TPM_MSA_COMPOSITE msa_composite;
msa_composite.MSAlist = 1;
msa_composite.migAuthDigest = &digest;
// Serialize the structure.
UINT64 serializing_offset = 0;
Trspi_LoadBlob_MSA_COMPOSITE(&serializing_offset, nullptr, &msa_composite);
Blob msa_composite_blob(serializing_offset);
serializing_offset = 0;
Trspi_LoadBlob_MSA_COMPOSITE(&serializing_offset, msa_composite_blob.data(),
&msa_composite);
return Sha1(msa_composite_blob);
}
// Obtains via the TPM_CMK_ApproveMA command the migration authority approval
// ticket for the given TPM_MSA_COMPOSITE structure blob. Returns the ticket via
// |ma_approval_ticket|.
bool ObtainMaApprovalTicket(TpmImpl* const tpm,
TSS_HCONTEXT tpm_context,
TSS_HTPM tpm_handle,
const Blob& msa_composite_digest,
Blob* ma_approval_ticket) {
ScopedTssObject<TSS_HMIGDATA> migdata_handle(tpm_context);
TSS_RESULT tss_result = Tspi_Context_CreateObject(
tpm_context, TSS_OBJECT_TYPE_MIGDATA, 0, migdata_handle.ptr());
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error creating migration data object: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_SetAttribData(
migdata_handle, TSS_MIGATTRIB_AUTHORITY_DATA,
TSS_MIGATTRIB_AUTHORITY_DIGEST, msa_composite_digest.size(),
const_cast<BYTE*>(msa_composite_digest.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error setting migration selection authority: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
tss_result = Tspi_TPM_CMKApproveMA(tpm_handle, migdata_handle);
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error obtaining migration authority approval ticket: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
SecureBlob local_ma_approval_ticket;
if (TPM1Error err = tpm->GetDataAttribute(
tpm_context, migdata_handle, TSS_MIGATTRIB_AUTHORITY_DATA,
TSS_MIGATTRIB_AUTHORITY_APPROVAL_HMAC, &local_ma_approval_ticket)) {
LOG(ERROR) << "Error reading migration authority approval ticket: " << *err;
return false;
}
// TODO(emaxx): Replace with a direct usage of Blob for the attribute read.
ma_approval_ticket->assign(local_ma_approval_ticket.begin(),
local_ma_approval_ticket.end());
return true;
}
// Parses the TPM_KEY12 blob and returns its "encData" field blob.
bool ParseEncDataFromKey12Blob(const Blob& key12_blob, Blob* enc_data) {
ScopedKey12 key12;
UINT64 key12_parsing_offset = 0;
TSS_RESULT tss_result = Trspi_UnloadBlob_KEY12(
&key12_parsing_offset, const_cast<BYTE*>(key12_blob.data()), key12.ptr());
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Failed to parse the migrated key TPM_KEY12 blob: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
if (key12_parsing_offset != key12_blob.size()) {
LOG(ERROR) << "Failed to parse the migrated key TPM_KEY12 blob due to size "
"mismatch";
return false;
}
enc_data->assign(key12->encData, key12->encData + key12->encSize);
return true;
}
// Applies to the given blob the element-to-element bitwise XOR against the
// other blob.
void XorBytes(uint8_t* inplace_target_begin,
const uint8_t* other_begin,
size_t size) {
for (size_t index = 0; index < size; ++index)
inplace_target_begin[index] ^= other_begin[index];
}
// Obtains the value from its MGF1-masked representation in |masked_value|. The
// input value for the MGF1 mask is passed via |mgf_input_value|. Returns the
// result via |value|; its length, on success, is guaranteed to be the same as
// the |masked_value|'s one.
bool UnmaskWithMgf1(const SecureBlob& masked_value,
const SecureBlob& mgf_input_value,
SecureBlob* value) {
if (masked_value.empty()) {
LOG(ERROR) << "Bad MGF1-masked value";
return false;
}
if (mgf_input_value.empty()) {
LOG(ERROR) << "Bad MGF1 input value";
return false;
}
SecureBlob mask(masked_value.size());
TSS_RESULT tss_result = Trspi_MGF1(TSS_HASH_SHA1, mgf_input_value.size(),
const_cast<BYTE*>(mgf_input_value.data()),
mask.size(), mask.data());
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Failed to generate the MGF1 mask: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
*value = masked_value;
XorBytes(value->data(), mask.data(), value->size());
return true;
}
// Performs the RSA OAEP MGF1 decoding of the encoded blob |encoded_blob| using
// the OAEP label parameter equal to |oaep_label|. The size |message_length|
// specifies the expected size of the returned message.
// Returns the decoded message via |message| and the OAEP seed via |seed|.
// Note that this custom implementation is used instead of the one from OpenSSL,
// because we need to get the seed back and OpenSSL doesn't return it.
bool DecodeOaepMgf1Encoding(const Blob& encoded_blob,
size_t message_length,
const Blob& oaep_label,
SecureBlob* seed,
SecureBlob* message) {
// The comments in this function below refer to the notation that corresponds
// to the "RSAES-OAEP Encryption Scheme" Algorithm specification and
// supporting documentation (2000), the "EME-OAEP-Decode" section.
// The correspondence between the function parameters and the terms in the
// specification is:
// * |encoded_blob| - "EM";
// * |message_length| - "mLen";
// * |oaep_label| - "P";
// * |seed| - "seed";
// * |message| - "M".
// Note that as the MGF1 mask is used which is based on SHA-1, the "hLen" term
// corresponds to |SHA_DIGEST_LENGTH|.
const size_t blob_size = encoded_blob.size();
// Step #1 is omitted as not applicable to our implementation - the length of
// |oaep_label| can't realistically reach the size constraint of SHA-1.
// Step #2. The total length of the encoded message is formed by the length of
// "seed" (which is equal to "hLen"), the length of "pHash" (which is also
// equal to "hLen"), the length of the original message, and the length of the
// "01" octet (which is 1 byte).
const size_t minimum_blob_size = 2 * SHA_DIGEST_LENGTH + 1 + message_length;
if (blob_size < minimum_blob_size) {
LOG(ERROR) << "Failed to parse the blob: the size is too small";
return false;
}
// Step #3. Split "EM" into "maskedSeed" and "maskedDB".
const SecureBlob masked_seed(encoded_blob.begin(),
encoded_blob.begin() + SHA_DIGEST_LENGTH);
const SecureBlob masked_padded_message(
encoded_blob.begin() + SHA_DIGEST_LENGTH, encoded_blob.end());
// Steps ##4-5. Unmask "maskedSeed" to obtain "seed".
if (!UnmaskWithMgf1(masked_seed, masked_padded_message, seed)) {
LOG(ERROR) << "Failed to unmask the seed";
return false;
}
// Steps ##6-7. Unmask "maskedDB" into "DB".
SecureBlob padded_message;
if (!UnmaskWithMgf1(masked_padded_message, *seed, &padded_message)) {
LOG(ERROR) << "Failed to unmask the message";
return false;
}
// Steps ##8-10. Extract "M" from "DB", extract "pHash" from "DB" and check it
// against "P", and verify the zeros/ones padding that covers the rest.
const Blob obtained_label_digest(padded_message.begin(),
padded_message.begin() + SHA_DIGEST_LENGTH);
const Blob obtained_zeroes_ones_padding(
padded_message.begin() + SHA_DIGEST_LENGTH,
padded_message.end() - message_length);
message->assign(padded_message.end() - message_length, padded_message.end());
DCHECK_EQ(padded_message.size(), obtained_label_digest.size() +
obtained_zeroes_ones_padding.size() +
message->size());
if (obtained_label_digest != Sha1(oaep_label)) {
LOG(ERROR) << "Incorrect OAEP label";
return false;
}
const Blob expected_zeroes_ones_padding =
CombineBlobs({Blob(obtained_zeroes_ones_padding.size() - 1), Blob(1, 1)});
if (obtained_zeroes_ones_padding != expected_zeroes_ones_padding) {
LOG(ERROR) << "Incorrect zeroes block in OAEP padding";
return false;
}
return true;
}
// Parses an unsigned four-byte integer from the given position in the blob in
// the TPM endianness.
uint32_t DecodeTpmUint32(const uint8_t* begin) {
UINT64 parsing_offset = 0;
uint32_t result = 0;
Trspi_UnloadBlob_UINT32(&parsing_offset, &result, const_cast<BYTE*>(begin));
DCHECK_EQ(4, parsing_offset);
return result;
}
// Parses the RSA secret prime from the TPM_MIGRATE_ASYMKEY blob and the seed
// blob.
bool ParseRsaSecretPrimeFromTpmMigrateAsymkeyBlob(
const SecureBlob& tpm_migrate_asymkey_blob,
const SecureBlob& tpm_migrate_asymkey_oaep_seed_blob,
SecureBlob* secret_prime_blob) {
DCHECK_EQ(SHA_DIGEST_LENGTH, tpm_migrate_asymkey_oaep_seed_blob.size());
// The binary layout, as specified in TPM 1.2 Part 3 Section 11.9
// ("TPM_CMK_CreateBlob"), is:
// * |tpm_migrate_asymkey_oaep_seed_blob| (called "K1" in the specification):
// is of |SHA_DIGEST_LENGTH| bytes length, and is structured as following:
// * the first 4 bytes contain a four-byte integer - the size of the private
// key in bytes (obtained from TPM_STORE_PRIVKEY.keyLength);
// * the rest are the first |kMigratedCmkPrivateKeySeedPartSizeBytes| bytes
// of the private key;
// * |tpm_migrate_asymkey_blob| (called "M1" in the specification): the binary
// dump of the TPM_MIGRATE_ASYMKEY structure, of which we are looking only
// at:
// * the first field |payload| of length 1 byte, which has to be equal to
// |TPM_PT_CMK_MIGRATE|;
// * the last field |partPrivKey|, which contains the last
// |kMigratedCmkPrivateKeyRestPartSizeBytes| bytes of the private key;
// * the last but one field |partPrivKeyLen| of length 4 bytes, which is a
// four-byte integer that has to be equal to
// |kMigratedCmkPrivateKeyRestPartSizeBytes|.
// We parse and validate this data below:
// Parse and validate the keyLength field of the TPM_STORE_PRIVKEY structure.
DCHECK_GE(tpm_migrate_asymkey_oaep_seed_blob.size(), 4);
const uint32_t tpm_store_privkey_key_length =
DecodeTpmUint32(tpm_migrate_asymkey_oaep_seed_blob.data());
if (tpm_store_privkey_key_length != kCmkPrivateKeySizeBytes) {
LOG(ERROR) << "Wrong migrated private key size";
return false;
}
// Extract the part of the private key from the OAEP seed.
const SecureBlob tpm_store_privkey_key_seed_part_blob(
tpm_migrate_asymkey_oaep_seed_blob.begin() + 4,
tpm_migrate_asymkey_oaep_seed_blob.end());
DCHECK_EQ(kMigratedCmkPrivateKeySeedPartSizeBytes,
tpm_store_privkey_key_seed_part_blob.size());
// Validate the TPM_MIGRATE_ASYMKEY blob size.
if (tpm_migrate_asymkey_blob.size() <
kMigratedCmkPrivateKeyRestPartSizeBytes + 4) {
LOG(ERROR) << "Wrong length of TPM_MIGRATE_ASYMKEY blob";
return false;
}
// Parse and validate the payload field of the TPM_MIGRATE_ASYMKEY structure.
const int tpm_migrate_asymkey_payload = tpm_migrate_asymkey_blob[0];
if (tpm_migrate_asymkey_payload != TPM_PT_CMK_MIGRATE) {
LOG(ERROR) << "Wrong migration payload type";
return false;
}
// Extract the part of the private key from the TPM_MIGRATE_ASYMKEY blob.
const SecureBlob tpm_store_privkey_key_rest_part_blob(
tpm_migrate_asymkey_blob.end() - kMigratedCmkPrivateKeyRestPartSizeBytes,
tpm_migrate_asymkey_blob.end());
// Parse and validate the partPrivKeyLen field of the TPM_MIGRATE_ASYMKEY
// structure.
const uint32_t tpm_migrate_asymkey_part_priv_key_length = DecodeTpmUint32(
&tpm_migrate_asymkey_blob[tpm_migrate_asymkey_blob.size() -
kMigratedCmkPrivateKeyRestPartSizeBytes - 4]);
if (tpm_migrate_asymkey_part_priv_key_length !=
kMigratedCmkPrivateKeyRestPartSizeBytes) {
LOG(ERROR) << "Wrong size of the private key part in TPM_MIGRATE_ASYMKEY";
return false;
}
// Assemble the resulting secret prime blob.
*secret_prime_blob =
SecureBlob::Combine(tpm_store_privkey_key_seed_part_blob,
tpm_store_privkey_key_rest_part_blob);
DCHECK_EQ(kCmkPrivateKeySizeBytes, secret_prime_blob->size());
return true;
}
// Generates the Certified Migratable Key, associated with the protection public
// key (via the TPM_MSA_COMPOSITE digest passed by |msa_composite_digest|). The
// |ma_approval_ticket| should contain ticket obtained from the
// TPM_CMK_ApproveMA command. Returns the CMK TPM_PUBKEY blob via |cmk_pubkey|
// and the wrapped CMK blob via |srk_wrapped_cmk|.
bool GenerateCmk(TpmImpl* const tpm,
TSS_HCONTEXT tpm_context,
TSS_HTPM tpm_handle,
TSS_HKEY srk_handle,
const Blob& msa_composite_digest,
const Blob& ma_approval_ticket,
Blob* cmk_pubkey,
Blob* srk_wrapped_cmk) {
// Create the Certified Migratable Key object. Note that the actual key
// generation isn't happening at this point yet.
ScopedTssKey cmk_handle(tpm_context);
TSS_RESULT tss_result = Tspi_Context_CreateObject(
tpm_context, TSS_OBJECT_TYPE_RSAKEY,
TSS_KEY_STRUCT_KEY12 | TSS_KEY_VOLATILE | TSS_KEY_TYPE_STORAGE |
TSS_KEY_AUTHORIZATION | TSS_KEY_MIGRATABLE |
TSS_KEY_CERTIFIED_MIGRATABLE | kCmkKeySizeFlag,
cmk_handle.ptr());
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Failed to create certified migratable key object: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
// Set the parameter to make the created CMK associated with the protection
// public key (via the TPM_MSA_COMPOSITE digest).
tss_result = Tspi_SetAttribData(
cmk_handle, TSS_TSPATTRIB_KEY_CMKINFO,
TSS_TSPATTRIB_KEYINFO_CMK_MA_DIGEST, msa_composite_digest.size(),
const_cast<BYTE*>(msa_composite_digest.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Failed to set migration authority digest: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
// Set the parameter to pass the migration authority approval ticket to the
// CMK creation procedure.
tss_result = Tspi_SetAttribData(cmk_handle, TSS_TSPATTRIB_KEY_CMKINFO,
TSS_TSPATTRIB_KEYINFO_CMK_MA_APPROVAL,
ma_approval_ticket.size(),
const_cast<BYTE*>(ma_approval_ticket.data()));
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Failed to set migration authority approval ticket: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
// Add the usage policy to the CMK. The policy will effectively disallow the
// usage of the CMK for signing/decryption, as the policy's password is
// discarded.
ScopedTssPolicy usage_policy_handle(tpm_context);
if (!tpm->CreatePolicyWithRandomPassword(tpm_context, TSS_POLICY_USAGE,
usage_policy_handle.ptr())) {
LOG(ERROR) << "Failed to create the usage policy";
return false;
}
tss_result = Tspi_Policy_AssignToObject(usage_policy_handle, cmk_handle);
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Error assigning the usage policy to the key: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
// Add the migration policy to the CMK. The policy will effectively disallow
// the usage of the CMK for non-certified migration, as the policy's password
// is discarded.
ScopedTssPolicy migration_policy_handle(tpm_context);
if (!tpm->CreatePolicyWithRandomPassword(tpm_context, TSS_POLICY_MIGRATION,
migration_policy_handle.ptr())) {
LOG(ERROR) << "Failed to create the usage policy";
return false;
}
tss_result = Tspi_Policy_AssignToObject(migration_policy_handle, cmk_handle);
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Failed to set the migration policy to the key: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
// Trigger the CMK generation and extract the resulting blobs.
tss_result =
Tspi_Key_CreateKey(cmk_handle, srk_handle, 0 /* hPcrComposite */);
if (TPM_ERROR(tss_result)) {
LOG(ERROR) << "Failed to create the certified migratable key: "
<< FormatTrousersErrorCode(tss_result);
return false;
}
SecureBlob local_cmk_pubkey;
if (TPM1Error err = tpm->GetDataAttribute(
tpm_context, cmk_handle, TSS_TSPATTRIB_KEY_BLOB,
TSS_TSPATTRIB_KEYBLOB_PUBLIC_KEY, &local_cmk_pubkey)) {
LOG(ERROR) << "Failed to read the certified migratable public key: "
<< *err;
return false;
}
// TODO(emaxx): Replace with a direct usage of Blob for the attribute read.
cmk_pubkey->assign(local_cmk_pubkey.begin(), local_cmk_pubkey.end());
SecureBlob local_srk_wrapped_cmk;
if (TPM1Error err = tpm->GetDataAttribute(
tpm_context, cmk_handle, TSS_TSPATTRIB_KEY_BLOB,
TSS_TSPATTRIB_KEYBLOB_BLOB, &local_srk_wrapped_cmk)) {
LOG(ERROR) << "Failed to read the certified migratable key: " << *err;
return false;
}
// TODO(emaxx): Replace with a direct usage of Blob for the attribute read.
srk_wrapped_cmk->assign(local_srk_wrapped_cmk.begin(),
local_srk_wrapped_cmk.end());
return true;
}
// Given the list of alternative sets of PCR-bound items, returns the one that
// is currently satisfied. Returns null if none is satisfied.
const SignatureSealedData_Tpm12PcrBoundItem* GetSatisfiedPcrRestriction(
const google::protobuf::RepeatedPtrField<
SignatureSealedData_Tpm12PcrBoundItem>& pcr_bound_items,
Tpm* tpm) {
std::map<uint32_t, Blob> current_pcr_values;
for (const auto& pcr_bound_item_proto : pcr_bound_items) {
bool is_satisfied = true;
for (const auto& pcr_value_proto : pcr_bound_item_proto.pcr_values()) {
const uint32_t pcr_index = pcr_value_proto.pcr_index();
if (!current_pcr_values.count(pcr_index)) {
Blob pcr_value;
if (!tpm->ReadPCR(pcr_index, &pcr_value)) {
is_satisfied = false;
break;
}
current_pcr_values.emplace(pcr_index, pcr_value);
}
if (current_pcr_values[pcr_index] !=
BlobFromString(pcr_value_proto.pcr_value())) {
is_satisfied = false;
break;
}
}
if (is_satisfied)
return &pcr_bound_item_proto;
}
return nullptr;
}
UnsealingSessionTpm1Impl::UnsealingSessionTpm1Impl(
TpmImpl* tpm,
const Blob& srk_wrapped_cmk,
const Blob& cmk_wrapped_auth_data,
const Blob& pcr_bound_secret,
const Blob& public_key_spki_der,
const Blob& delegate_blob,
const Blob& delegate_secret,
const Blob& cmk_pubkey,
const Blob& protection_key_pubkey,
crypto::ScopedRSA migration_destination_rsa,
const Blob& migration_destination_key_pubkey)
: tpm_(tpm),
srk_wrapped_cmk_(srk_wrapped_cmk),
cmk_wrapped_auth_data_(cmk_wrapped_auth_data),
pcr_bound_secret_(pcr_bound_secret),
public_key_spki_der_(public_key_spki_der),
delegate_blob_(delegate_blob),
delegate_secret_(delegate_secret),
cmk_pubkey_(cmk_pubkey),
cmk_pubkey_digest_(Sha1(cmk_pubkey_)),
protection_key_pubkey_(protection_key_pubkey),
protection_key_pubkey_digest_(Sha1(protection_key_pubkey_)),
migration_destination_rsa_(std::move(migration_destination_rsa)),
migration_destination_key_pubkey_(migration_destination_key_pubkey),
migration_destination_key_pubkey_digest_(
Sha1(migration_destination_key_pubkey_)),
msa_composite_digest_(
BuildMsaCompositeDigest(protection_key_pubkey_digest_)) {}
UnsealingSessionTpm1Impl::~UnsealingSessionTpm1Impl() = default;
ChallengeSignatureAlgorithm UnsealingSessionTpm1Impl::GetChallengeAlgorithm() {
return CHALLENGE_RSASSA_PKCS1_V1_5_SHA1;
}
Blob UnsealingSessionTpm1Impl::GetChallengeValue() {
return CombineBlobs({protection_key_pubkey_digest_,
migration_destination_key_pubkey_digest_,
cmk_pubkey_digest_});
}
TPMErrorBase UnsealingSessionTpm1Impl::Unseal(
const Blob& signed_challenge_value, SecureBlob* unsealed_value) {
// Obtain the TPM context and handle with the required authorization.
ScopedTssContext tpm_context;
TSS_HTPM tpm_handle = 0;
if (!tpm_->ConnectContextAsDelegate(delegate_blob_, delegate_secret_,
tpm_context.ptr(), &tpm_handle)) {
return CreateError<TPMError>("Failed to connect to the TPM",
TPMRetryAction::kNoRetry);
}
// Load the required keys into Trousers.
ScopedTssKey srk_handle(tpm_context);
if (TPM1Error err = tpm_->LoadSrk(tpm_context, srk_handle.ptr())) {
return WrapError<TPMError>(std::move(err), "Failed to load the SRK");
}
int protection_key_size_bits = 0;
ScopedTssKey protection_key_handle(tpm_context);
if (!ParseAndLoadProtectionKey(tpm_, tpm_context, public_key_spki_der_,
&protection_key_size_bits,
protection_key_handle.ptr())) {
return CreateError<TPMError>("Failed to load the protection public key",
TPMRetryAction::kNoRetry);
}
ScopedTssKey migration_destination_key_handle(tpm_context);
if (!LoadMigrationDestinationPublicKey(
tpm_, tpm_context, *migration_destination_rsa_,
migration_destination_key_handle.ptr())) {
return CreateError<TPMError>("Failed to load the migration destination key",
TPMRetryAction::kNoRetry);
}
// Validity check the received signature blob.
if (signed_challenge_value.size() != protection_key_size_bits / 8) {
return CreateError<TPMError>("Wrong size of challenge signature blob",
TPMRetryAction::kNoRetry);
}
// Obtain the migration authorization blob for the migration destination key.
Blob migration_authorization_blob;
if (!ObtainMigrationAuthorization(tpm_context, tpm_handle,
migration_destination_key_handle,
&migration_authorization_blob)) {
return CreateError<TPMError>("Failed to obtain the migration authorization",
TPMRetryAction::kNoRetry);
}
// Obtain the CMK migration signature ticket for the signed challenge blob.
Blob cmk_migration_signature_ticket;
if (!ObtainCmkMigrationSignatureTicket(
tpm_, tpm_context, tpm_handle, protection_key_handle,
migration_destination_key_pubkey_, cmk_pubkey_,
protection_key_pubkey_, signed_challenge_value,
&cmk_migration_signature_ticket)) {
return CreateError<TPMError>(
"Failed to obtain the CMK migration signature ticket",
TPMRetryAction::kNoRetry);
}
// Perform the migration of the CMK onto the migration destination key.
Blob migrated_cmk_key12_blob;
Blob migration_random_blob;
if (!MigrateCmk(tpm_, tpm_context, tpm_handle, srk_handle, srk_wrapped_cmk_,
migration_destination_key_pubkey_, cmk_pubkey_,
protection_key_pubkey_, migration_authorization_blob,
cmk_migration_signature_ticket, &migrated_cmk_key12_blob,
&migration_random_blob)) {
return CreateError<TPMError>(
"Failed to migrate the certified migratable key",
TPMRetryAction::kNoRetry);
}
// Decrypt and decode the CMK private key.
crypto::ScopedRSA cmk_private_key = ExtractCmkPrivateKeyFromMigratedBlob(
migrated_cmk_key12_blob, migration_random_blob, cmk_pubkey_,
cmk_pubkey_digest_, msa_composite_digest_,
migration_destination_rsa_.get());
if (!cmk_private_key) {
return CreateError<TPMError>(
"Failed to extract the certified migratable private key",
TPMRetryAction::kNoRetry);
}
// Decrypt the AuthData value.
SecureBlob auth_data;
if (!RsaOaepDecrypt(SecureBlob(cmk_wrapped_auth_data_),
SecureBlob() /* oaep_label */, cmk_private_key.get(),
&auth_data)) {
return CreateError<TPMError>("Failed to decrypt the authorization data",
TPMRetryAction::kNoRetry);
}
// Unseal the secret value bound to PCRs and the AuthData value.
brillo::SecureBlob auth_value;
if (TPMErrorBase err =
tpm_->GetAuthValue(base::nullopt, auth_data, &auth_value)) {
return WrapError<TPMError>(std::move(err), "Failed to get auth value");
}
if (TPMErrorBase err = tpm_->UnsealWithAuthorization(
base::nullopt, SecureBlob(pcr_bound_secret_), auth_value,
{} /* pcr_map */, unsealed_value)) {
return WrapError<TPMError>(std::move(err),
"Failed to unseal the secret value");
}
return nullptr;
}
} // namespace
SignatureSealingBackendTpm1Impl::SignatureSealingBackendTpm1Impl(TpmImpl* tpm)
: tpm_(tpm) {}
SignatureSealingBackendTpm1Impl::~SignatureSealingBackendTpm1Impl() = default;
TPMErrorBase SignatureSealingBackendTpm1Impl::CreateSealedSecret(
const Blob& public_key_spki_der,
const std::vector<ChallengeSignatureAlgorithm>& key_algorithms,
const std::vector<std::map<uint32_t, brillo::Blob>>& pcr_restrictions,
const Blob& delegate_blob,
const Blob& delegate_secret,
brillo::SecureBlob* secret_value,
SignatureSealedData* sealed_secret_data) {
// TODO(chromium:806788,b:77799573): Support absence of PCR restrictions.
DCHECK(!pcr_restrictions.empty());
// Only the |kRsassaPkcs1V15Sha1| algorithm is supported.
if (std::find(key_algorithms.begin(), key_algorithms.end(),
CHALLENGE_RSASSA_PKCS1_V1_5_SHA1) == key_algorithms.end()) {
return CreateError<TPMError>(
"The key doesn't support RSASSA-PKCS1-v1_5 with SHA-1",
TPMRetryAction::kNoRetry);
}
// Obtain the TPM context and handle with the required authorization.
ScopedTssContext tpm_context;
TSS_HTPM tpm_handle = 0;
if (!tpm_->ConnectContextAsDelegate(delegate_blob, delegate_secret,
tpm_context.ptr(), &tpm_handle)) {
return CreateError<TPMError>("Failed to connect to the TPM",
TPMRetryAction::kCommunication);
}
// Load the protection public key into Trousers. Obtain its TPM_PUBKEY blob
// and build the blob of the TPM_MSA_COMPOSITE structure containing a sole
// reference to this key.
int protection_key_size_bits = 0;
ScopedTssKey protection_key_handle(tpm_context);
if (!ParseAndLoadProtectionKey(tpm_, tpm_context, public_key_spki_der,
&protection_key_size_bits,
protection_key_handle.ptr())) {
return CreateError<TPMError>("Failed to load the protection public key",
TPMRetryAction::kNoRetry);
}
SecureBlob protection_key_pubkey;
if (tpm_->GetDataAttribute(
tpm_context, protection_key_handle, TSS_TSPATTRIB_KEY_BLOB,
TSS_TSPATTRIB_KEYBLOB_PUBLIC_KEY, &protection_key_pubkey)) {
return CreateError<TPMError>("Failed to load the protection public key",
TPMRetryAction::kLater);
}
const Blob protection_key_pubkey_digest =
Sha1(Blob(protection_key_pubkey.begin(), protection_key_pubkey.end()));
const Blob msa_composite_digest =
BuildMsaCompositeDigest(protection_key_pubkey_digest);
// Obtain the migration authority approval ticket for the TPM_MSA_COMPOSITE
// structure.
Blob ma_approval_ticket;
if (!ObtainMaApprovalTicket(tpm_, tpm_context, tpm_handle,
msa_composite_digest, &ma_approval_ticket)) {
return CreateError<TPMError>(
"Failed to obtain the migration authority approval ticket",
TPMRetryAction::kNoRetry);
}
// Load the SRK.
ScopedTssKey srk_handle(tpm_context);
if (TPM1Error err = tpm_->LoadSrk(tpm_context, srk_handle.ptr())) {
return WrapError<TPMError>(std::move(err), "Failed to load the SRK");
}
// Generate the Certified Migratable Key, associated with the protection
// public key (via the TPM_MSA_COMPOSITE digest). Obtain the resulting wrapped
// CMK blob and the TPM_PUBKEY blob.
Blob cmk_pubkey;
Blob srk_wrapped_cmk;
if (!GenerateCmk(tpm_, tpm_context, tpm_handle, srk_handle,
msa_composite_digest, ma_approval_ticket, &cmk_pubkey,
&srk_wrapped_cmk)) {
return CreateError<TPMError>(
"Failed to generate the certified migratable key",
TPMRetryAction::kNoRetry);
}
// Generate the AuthData value randomly.
SecureBlob auth_data;
if (TPMErrorBase err =
tpm_->GetRandomDataSecureBlob(kAuthDataSizeBytes, &auth_data)) {
return WrapError<TPMError>(std::move(err),
"Failed to generate the authorization data");
}
DCHECK_EQ(auth_data.size(), kAuthDataSizeBytes);
// Encrypt the AuthData value.
crypto::ScopedRSA cmk_rsa = ParseRsaFromTpmPubkeyBlob(cmk_pubkey);
if (!cmk_rsa) {
return CreateError<TPMError>(
"Failed to create OpenSSL public key object for the certified "
"migratable key",
TPMRetryAction::kNoRetry);
}
Blob cmk_wrapped_auth_data;
if (!RsaOaepEncrypt(auth_data, cmk_rsa.get(), &cmk_wrapped_auth_data)) {
return CreateError<TPMError>("Failed to encrypt authorization data",
TPMRetryAction::kNoRetry);
}
// Generate the secret value randomly.
if (TPMErrorBase err =
tpm_->GetRandomDataSecureBlob(kSecretSizeBytes, secret_value)) {
return WrapError<TPMError>(std::move(err),
"Error generating random secret");
}
DCHECK_EQ(secret_value->size(), kSecretSizeBytes);
// Bind the secret value to each of the specified sets of PCR restrictions.
DCHECK(!pcr_restrictions.empty());
std::vector<Blob> pcr_bound_secret_values;
for (const auto& pcr_values : pcr_restrictions) {
DCHECK(!pcr_values.empty());
// Bind the secret value to the current set of PCR restrictions.
SecureBlob pcr_bound_secret_value;
std::map<uint32_t, std::string> pcr_values_strings;
for (const auto& pcr_index_and_value : pcr_values) {
pcr_values_strings[pcr_index_and_value.first] =
BlobToString(pcr_index_and_value.second);
}
brillo::SecureBlob auth_value;
if (TPMErrorBase err =
tpm_->GetAuthValue(base::nullopt, auth_data, &auth_value)) {
return WrapError<TPMError>(std::move(err), "Failed to get auth value");
}
if (TPMErrorBase err = tpm_->SealToPcrWithAuthorization(
*secret_value, auth_data, pcr_values_strings,
&pcr_bound_secret_value)) {
return WrapError<TPMError>(
std::move(err),
"Error binding the secret value to PCRs and authorization data");
}
pcr_bound_secret_values.push_back(
Blob(pcr_bound_secret_value.begin(), pcr_bound_secret_value.end()));
}
// Fill the resulting proto with data required for unsealing.
sealed_secret_data->Clear();
SignatureSealedData_Tpm12CertifiedMigratableKeyData* const data_proto =
sealed_secret_data->mutable_tpm12_certified_migratable_key_data();
data_proto->set_public_key_spki_der(BlobToString(public_key_spki_der));
data_proto->set_srk_wrapped_cmk(BlobToString(srk_wrapped_cmk));
data_proto->set_cmk_pubkey(BlobToString(cmk_pubkey));
data_proto->set_cmk_wrapped_auth_data(BlobToString(cmk_wrapped_auth_data));
DCHECK_EQ(pcr_restrictions.size(), pcr_bound_secret_values.size());
for (size_t restriction_index = 0;
restriction_index < pcr_restrictions.size(); ++restriction_index) {
const auto& pcr_values = pcr_restrictions[restriction_index];
SignatureSealedData_Tpm12PcrBoundItem* const pcr_bound_item_proto =
data_proto->add_pcr_bound_items();
for (const auto& pcr_index_and_value : pcr_values) {
SignatureSealedData_PcrValue* const pcr_value_proto =
pcr_bound_item_proto->add_pcr_values();
pcr_value_proto->set_pcr_index(pcr_index_and_value.first);
pcr_value_proto->set_pcr_value(BlobToString(pcr_index_and_value.second));
}
pcr_bound_item_proto->set_bound_secret(
BlobToString(pcr_bound_secret_values[restriction_index]));
}
return nullptr;
}
TPMErrorBase SignatureSealingBackendTpm1Impl::CreateUnsealingSession(
const SignatureSealedData& sealed_secret_data,
const Blob& public_key_spki_der,
const std::vector<ChallengeSignatureAlgorithm>& key_algorithms,
const Blob& delegate_blob,
const Blob& delegate_secret,
std::unique_ptr<SignatureSealingBackend::UnsealingSession>*
unsealing_session) {
// Validate the parameters.
if (!sealed_secret_data.has_tpm12_certified_migratable_key_data()) {
return CreateError<TPMError>(
"Sealed data is empty or uses unexpected method",
TPMRetryAction::kNoRetry);
}
const SignatureSealedData_Tpm12CertifiedMigratableKeyData& data_proto =
sealed_secret_data.tpm12_certified_migratable_key_data();
if (data_proto.public_key_spki_der() != BlobToString(public_key_spki_der)) {
return CreateError<TPMError>("Wrong subject public key info",
TPMRetryAction::kNoRetry);
}
if (std::find(key_algorithms.begin(), key_algorithms.end(),
CHALLENGE_RSASSA_PKCS1_V1_5_SHA1) == key_algorithms.end()) {
return CreateError<TPMError>(
"Failed to choose the algorithm: the key doesn't support "
"RSASSA-PKCS1-v1_5 with SHA-1",
TPMRetryAction::kNoRetry);
}
// Determine the satisfied set of PCR restrictions.
const SignatureSealedData_Tpm12PcrBoundItem* const
satisfied_pcr_bound_item_proto =
GetSatisfiedPcrRestriction(data_proto.pcr_bound_items(), tpm_);
if (!satisfied_pcr_bound_item_proto) {
return CreateError<TPMError>("None of PCR restrictions is satisfied",
TPMRetryAction::kNoRetry);
}
// Obtain the TPM context and handle with the required authorization.
ScopedTssContext tpm_context;
TSS_HTPM tpm_handle = 0;
if (!tpm_->ConnectContextAsDelegate(delegate_blob, delegate_secret,
tpm_context.ptr(), &tpm_handle)) {
return CreateError<TPMError>("Failed to connect to the TPM",
TPMRetryAction::kNoRetry);
}
// Obtain the TPM_PUBKEY blob for the protection key.
int protection_key_size_bits = 0;
ScopedTssKey protection_key_handle(tpm_context);
if (!ParseAndLoadProtectionKey(tpm_, tpm_context, public_key_spki_der,
&protection_key_size_bits,
protection_key_handle.ptr())) {
return CreateError<TPMError>("Failed to load the protection public key",
TPMRetryAction::kNoRetry);
}
SecureBlob protection_key_pubkey;
if (tpm_->GetDataAttribute(
tpm_context, protection_key_handle, TSS_TSPATTRIB_KEY_BLOB,
TSS_TSPATTRIB_KEYBLOB_PUBLIC_KEY, &protection_key_pubkey)) {
return CreateError<TPMError>("Failed to read the protection public key",
TPMRetryAction::kNoRetry);
}
// Generate the migration destination RSA key. Onto this key the CMK private
// key will be migrated; to complete the unsealing, the decryption operation
// using the migration destination key will be performed. The security
// properties of the migration destination key aren't crucial, besides the
// reasonable amount of entropy, therefore generating using OpenSSL is fine.
//
// TODO(crbug.com/909700): Do the RSA key generation in background in advance.
crypto::ScopedRSA migration_destination_rsa(RSA_new());
crypto::ScopedBIGNUM e(BN_new());
if (!migration_destination_rsa || !e) {
return CreateError<TPMError>(
"Failed to allocate the migration destination key",
TPMRetryAction::kNoRetry);
}
if (!BN_set_word(e.get(), kWellKnownExponent) ||
!RSA_generate_key_ex(migration_destination_rsa.get(),
kMigrationDestinationKeySizeBits, e.get(),
nullptr)) {
return CreateError<TPMError>(
"Failed to generate the migration destination key",
TPMRetryAction::kNoRetry);
}
// Obtain the TPM_PUBKEY blob for the migration destination key.
ScopedTssKey migration_destination_key_handle(tpm_context);
if (!LoadMigrationDestinationPublicKey(
tpm_, tpm_context, *migration_destination_rsa,
migration_destination_key_handle.ptr())) {
return CreateError<TPMError>("Failed to load the migration destination key",
TPMRetryAction::kNoRetry);
}
SecureBlob migration_destination_key_pubkey;
if (tpm_->GetDataAttribute(tpm_context, migration_destination_key_handle,
TSS_TSPATTRIB_KEY_BLOB,
TSS_TSPATTRIB_KEYBLOB_PUBLIC_KEY,
&migration_destination_key_pubkey)) {
return CreateError<TPMError>(
"Failed to read the migration destination public key",
TPMRetryAction::kNoRetry);
}
*unsealing_session = std::make_unique<UnsealingSessionTpm1Impl>(
tpm_, BlobFromString(data_proto.srk_wrapped_cmk()),
BlobFromString(data_proto.cmk_wrapped_auth_data()),
BlobFromString(satisfied_pcr_bound_item_proto->bound_secret()),
public_key_spki_der, delegate_blob, delegate_secret,
BlobFromString(data_proto.cmk_pubkey()),
Blob(protection_key_pubkey.begin(), protection_key_pubkey.end()),
std::move(migration_destination_rsa),
Blob(migration_destination_key_pubkey.begin(),
migration_destination_key_pubkey.end()));
return nullptr;
}
crypto::ScopedRSA ExtractCmkPrivateKeyFromMigratedBlob(
const Blob& migrated_cmk_key12_blob,
const Blob& migration_random_blob,
const Blob& cmk_pubkey,
const Blob& cmk_pubkey_digest,
const Blob& msa_composite_digest,
RSA* migration_destination_rsa) {
// Load the encrypted TPM_MIGRATE_ASYMKEY blob from the TPM_KEY12 blob.
// Note that this encrypted TPM_MIGRATE_ASYMKEY blob was generated by taking
// the TPM_MIGRATE_ASYMKEY blob, applying the RSA OAEP *encoding* (not
// encryption), XOR'ing it with the migration random XOR-mask, applying the
// RSA OAEP *encryption* (not encoding). We'll unwind this to obtain the
// original TPM_MIGRATE_ASYMKEY blob below.
Blob encrypted_tpm_migrate_asymkey_blob;
if (!ParseEncDataFromKey12Blob(migrated_cmk_key12_blob,
&encrypted_tpm_migrate_asymkey_blob)) {
LOG(ERROR) << "Failed to parse the encrypted TPM_MIGRATE_ASYMKEY blob from "
"the TPM_KEY12 blob";
return nullptr;
}
if (encrypted_tpm_migrate_asymkey_blob.size() !=
kMigrationDestinationKeySizeBytes) {
LOG(ERROR) << "Failed to parse the encrypted TPM_MIGRATE_ASYMKEY blob due "
"to size mismatch";
return nullptr;
}
// Perform the RSA OAEP decryption of the encrypted TPM_MIGRATE_ASYMKEY blob,
// using the custom OAEP label parameter as prescribed by the TPM 1.2 specs.
SecureBlob decrypted_tpm_migrate_asymkey_blob;
if (!RsaOaepDecrypt(
SecureBlob(encrypted_tpm_migrate_asymkey_blob),
SecureBlob(std::begin(kTpmRsaOaepLabel), std::end(kTpmRsaOaepLabel)),
migration_destination_rsa, &decrypted_tpm_migrate_asymkey_blob)) {
LOG(ERROR)
<< "Failed to RSA-decrypt the encrypted TPM_MIGRATE_ASYMKEY blob";
return nullptr;
}
if (decrypted_tpm_migrate_asymkey_blob.size() !=
migration_random_blob.size()) {
LOG(ERROR)
<< "Failed to decrypt TPM_MIGRATE_ASYMKEY blob due to size mismatch";
return nullptr;
}
// XOR the decrypted TPM_MIGRATE_ASYMKEY blob with the migration random
// XOR-mask.
DCHECK_EQ(decrypted_tpm_migrate_asymkey_blob.size(),
migration_random_blob.size());
Blob xored_decrypted_tpm_migrate_asymkey_blob(
decrypted_tpm_migrate_asymkey_blob.begin(),
decrypted_tpm_migrate_asymkey_blob.end());
XorBytes(xored_decrypted_tpm_migrate_asymkey_blob.data(),
migration_random_blob.data(),
xored_decrypted_tpm_migrate_asymkey_blob.size());
// Perform the RSA OAEP decoding (not decryption) of the XOR'ed decrypted
// TPM_MIGRATE_ASYMKEY blob.
// The OAEP label parameter is equal to concatenation of
// |msa_composite_digest| and |cmk_pubkey_digest|.
// The OAEP seed parameter is extracted as well, because it contains a part of
// the private key data.
// Note that our own implementation of OAEP decoding is used instead of the
// OpenSSL's one, as the latter doesn't return the decoded seed.
const Blob tpm_migrate_asymkey_oaep_label_blob =
CombineBlobs({msa_composite_digest, cmk_pubkey_digest});
SecureBlob tpm_migrate_asymkey_oaep_seed_blob;
SecureBlob tpm_migrate_asymkey_blob;
if (!DecodeOaepMgf1Encoding(
xored_decrypted_tpm_migrate_asymkey_blob, kTpmMigrateAsymkeyBlobSize,
tpm_migrate_asymkey_oaep_label_blob,
&tpm_migrate_asymkey_oaep_seed_blob, &tpm_migrate_asymkey_blob)) {
LOG(ERROR) << "Failed to perform RSA OAEP decoding of the XOR'ed decrypted "
"TPM_MIGRATE_ASYMKEY blob";
return nullptr;
}
// Parse the resulting CMK's secret prime from the TPM_MIGRATE_ASYMKEY blob
// and the seed blob.
SecureBlob cmk_secret_prime;
if (!ParseRsaSecretPrimeFromTpmMigrateAsymkeyBlob(
tpm_migrate_asymkey_blob, tpm_migrate_asymkey_oaep_seed_blob,
&cmk_secret_prime)) {
LOG(ERROR)
<< "Failed to parse the private key from the TPM_MIGRATE_ASYMKEY blob";
return nullptr;
}
DCHECK_EQ(kCmkPrivateKeySizeBytes, cmk_secret_prime.size());
// Build the OpenSSL RSA structure holding the private key.
crypto::ScopedRSA cmk_rsa = ParseRsaFromTpmPubkeyBlob(cmk_pubkey);
if (!cmk_rsa) {
LOG(ERROR) << "Failed to create OpenSSL public key object for the "
"certified migratable key";
return nullptr;
}
if (!FillRsaPrivateKeyFromSecretPrime(cmk_secret_prime, cmk_rsa.get())) {
LOG(ERROR) << "Failed to create OpenSSL private key object for the "
"certified migratable key";
return nullptr;
}
return cmk_rsa;
}
} // namespace cryptohome