blob: 370beeb52e136584d554f0d3710efe4be0f494e0 [file] [log] [blame]
// Copyright 2015 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 <utility>
#include "chaps/chaps_utility.h"
#include "chaps/tpm2_utility_impl.h"
#include <base/bind.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/location.h>
#include <base/logging.h>
#include <base/macros.h>
#include <base/memory/ref_counted.h>
#include <base/sha1.h>
#include <base/stl_util.h>
#include <crypto/scoped_openssl_types.h>
#include <openssl/rsa.h>
#include <trunks/background_command_transceiver.h>
#include <trunks/command_transceiver.h>
#include <trunks/error_codes.h>
#include <trunks/tpm_generated.h>
#include <trunks/tpm_state.h>
#include <trunks/trunks_dbus_proxy.h>
#include <trunks/trunks_factory_impl.h>
using base::AutoLock;
using brillo::SecureBlob;
using std::map;
using std::set;
using trunks::kRSAStorageRootKey;
using trunks::TPM_RC;
using trunks::TPM_RC_SUCCESS;
using trunks::TrunksFactory;
namespace {
const struct {
trunks::TPM_ALG_ID id;
chaps::DigestAlgorithm alg;
} kSupportedDigestAlgorithms[] = {
{ trunks::TPM_ALG_SHA1, chaps::DigestAlgorithm::SHA1 },
{ trunks::TPM_ALG_SHA256, chaps::DigestAlgorithm::SHA256 },
{ trunks::TPM_ALG_SHA384, chaps::DigestAlgorithm::SHA384 },
{ trunks::TPM_ALG_SHA512, chaps::DigestAlgorithm::SHA512 },
};
// Extract the algorithm ID and the digest from PKCS1-v1_5 DigestInfo.
// See RFC-3447, section 9.2.
void ParseDigestInfo(const std::string& digest_info,
std::string* digest,
trunks::TPM_ALG_ID* digest_alg) {
for (size_t i = 0; i < arraysize(kSupportedDigestAlgorithms); ++i) {
std::string encoding =
GetDigestAlgorithmEncoding(kSupportedDigestAlgorithms[i].alg);
if (!digest_info.compare(0, encoding.size(), encoding)) {
*digest = digest_info.substr(encoding.size());
*digest_alg = kSupportedDigestAlgorithms[i].id;
return;
}
}
// Unknown algorithm - use "padding-only" signing scheme.
*digest = digest_info;
*digest_alg = trunks::TPM_ALG_NULL;
}
uint32_t GetIntegerExponent(const std::string& public_exponent) {
uint32_t exponent = 0;
for (size_t i = 0; i < public_exponent.size(); i++) {
exponent = exponent << 8;
exponent += public_exponent[i];
}
return exponent;
}
bool AddPKCS1Padding(const std::string& input,
size_t size,
std::string* result) {
if (input.size() + 11 > size) {
LOG(ERROR) << "Error adding PKCS1 padding: message too long: "
<< input.size() << " (target size " << size << ")";
return false;
}
result->assign("\x00\x01", 2);
result->append(size - input.size() - 3, '\xff');
result->append("\x00", 1);
result->append(input);
return true;
}
void InitTransceiver(trunks::CommandTransceiver* transceiver) {
if (!transceiver->Init()) {
LOG(ERROR) << "Error initializing transceiver.";
}
}
void TermTransceiver(std::unique_ptr<trunks::CommandTransceiver> transceiver) {
transceiver.reset();
}
} // namespace
namespace chaps {
class ScopedSession {
public:
#ifndef CHAPS_TPM2_USE_PER_OP_SESSIONS
ScopedSession(trunks::TrunksFactory* factory,
std::unique_ptr<trunks::HmacSession> *session) {}
#else
ScopedSession(trunks::TrunksFactory* factory,
std::unique_ptr<trunks::HmacSession> *session) {
target_session_ = session;
if (*target_session_) {
LOG(ERROR) << "Concurrent sessions";
}
std::unique_ptr<trunks::HmacSession> new_session =
factory->GetHmacSession();
TPM_RC result =
new_session->StartUnboundSession(false /* disable encryption */);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error starting an AuthorizationSession: "
<< trunks::GetErrorString(result);
*target_session_ = nullptr;
} else {
*target_session_ = std::move(new_session);
}
}
~ScopedSession() {
*target_session_ = nullptr;
}
private:
std::unique_ptr<trunks::HmacSession> *target_session_;
#endif
};
TPM2UtilityImpl::TPM2UtilityImpl()
: default_factory_(
new trunks::TrunksFactoryImpl()),
factory_(default_factory_.get()) {
if (!default_factory_->Initialize()) {
LOG(ERROR) << "Unable to initialize trunks.";
return;
}
#ifndef CHAPS_TPM2_USE_PER_OP_SESSIONS
session_ = factory_->GetHmacSession();
#endif
trunks_tpm_utility_ = factory_->GetTpmUtility();
}
TPM2UtilityImpl::TPM2UtilityImpl(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
: task_runner_(task_runner),
default_trunks_proxy_(new trunks::TrunksDBusProxy) {
task_runner->PostNonNestableTask(
FROM_HERE,
base::Bind(&InitTransceiver,
base::Unretained(default_trunks_proxy_.get())));
// We stitch the transceivers together. The call chain is:
// ChapsTPMUtility --> TrunksFactory --> BackgroundCommandTransceiver -->
// TrunksProxy
default_background_transceiver_.reset(
new trunks::BackgroundCommandTransceiver(
default_trunks_proxy_.get(),
task_runner));
default_factory_.reset(
new trunks::TrunksFactoryImpl(default_background_transceiver_.get()));
CHECK(default_factory_->Initialize());
factory_ = default_factory_.get();
#ifndef CHAPS_TPM2_USE_PER_OP_SESSIONS
session_ = factory_->GetHmacSession();
#endif
trunks_tpm_utility_ = factory_->GetTpmUtility();
}
TPM2UtilityImpl::TPM2UtilityImpl(TrunksFactory* factory)
: factory_(factory),
#ifndef CHAPS_TPM2_USE_PER_OP_SESSIONS
session_(factory_->GetHmacSession()),
#endif
trunks_tpm_utility_(factory_->GetTpmUtility()) {}
TPM2UtilityImpl::~TPM2UtilityImpl() {
for (const auto& it : slot_handles_) {
set<int> slot_handles = it.second;
for (const auto& it2 : slot_handles) {
if (factory_->GetTpm()->FlushContextSync(it2, NULL) != TPM_RC_SUCCESS) {
LOG(WARNING) << "Error flushing handle: " << it2;
}
}
}
// If we have a task runner, then that was the task runner used to initialize
// the |default_trunks_proxy_|. Destroy the proxy on that task runner to
// satisfy threading restrictions.
if (task_runner_) {
default_factory_.reset();
default_background_transceiver_.reset();
// TODO(ejcaruso): replace with DeleteSoon when libchrome has the unique_ptr
// specialization after the uprev
task_runner_->PostNonNestableTask(
FROM_HERE,
base::Bind(&TermTransceiver,
base::Passed(&default_trunks_proxy_)));
}
}
bool TPM2UtilityImpl::Init() {
AutoLock lock(lock_);
std::unique_ptr<trunks::TpmState> tpm_state = factory_->GetTpmState();
TPM_RC result;
result = tpm_state->Initialize();
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error getting TPM state information: "
<< trunks::GetErrorString(result);
return false;
}
// Check if firmware initialized the platform hierarchy.
if (tpm_state->IsPlatformHierarchyEnabled()) {
LOG(ERROR) << "Platform initialization not complete.";
return false;
}
// Check if ownership is taken. If not, TPMUtility initialization fails.
if (!tpm_state->IsOwnerPasswordSet() ||
!tpm_state->IsEndorsementPasswordSet() ||
!tpm_state->IsLockoutPasswordSet()) {
LOG(ERROR) << "TPM2Utility cannot be ready if the TPM is not owned.";
return false;
}
#ifndef CHAPS_TPM2_USE_PER_OP_SESSIONS
result = session_->StartUnboundSession(true /* enable encryption */);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error starting an AuthorizationSession: "
<< trunks::GetErrorString(result);
return false;
}
#endif
is_initialized_ = true;
return true;
}
bool TPM2UtilityImpl::IsTPMAvailable() {
AutoLock lock(lock_);
if (is_enabled_ready_) {
return is_enabled_;
}
// If the TPM works, it is available.
if (is_initialized_) {
is_enabled_ready_ = true;
is_enabled_ = true;
return true;
}
std::unique_ptr<trunks::TpmState> tpm_state = factory_->GetTpmState();
TPM_RC result = tpm_state->Initialize();
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error getting TPM state information: "
<< trunks::GetErrorString(result);
return false;
}
is_enabled_ = tpm_state->IsEnabled();
is_enabled_ready_ = true;
return is_enabled_;
}
bool TPM2UtilityImpl::Authenticate(int slot_id,
const SecureBlob& auth_data,
const std::string& auth_key_blob,
const std::string& encrypted_master_key,
SecureBlob* master_key) {
CHECK(master_key);
AutoLock lock(lock_);
int key_handle = 0;
if (!LoadKeyWithParentInternal(slot_id, auth_key_blob, auth_data,
kRSAStorageRootKey, &key_handle)) {
return false;
}
std::string master_key_str;
if (!UnbindInternal(key_handle, encrypted_master_key, &master_key_str)) {
return false;
}
*master_key = SecureBlob(master_key_str);
master_key_str.clear();
return true;
}
bool TPM2UtilityImpl::ChangeAuthData(int slot_id,
const SecureBlob& old_auth_data,
const SecureBlob& new_auth_data,
const std::string& old_auth_key_blob,
std::string* new_auth_key_blob) {
AutoLock lock(lock_);
int key_handle;
if (new_auth_data.size() > SHA256_DIGEST_SIZE) {
LOG(ERROR) << "Authorization cannot be larger than SHA256 Digest size.";
return false;
}
if (!LoadKeyWithParentInternal(slot_id, old_auth_key_blob, old_auth_data,
kRSAStorageRootKey, &key_handle)) {
LOG(ERROR) << "Error loading key under old authorization data.";
return false;
}
ScopedSession session_scope(factory_, &session_);
if (!session_) {
return false;
}
session_->SetEntityAuthorizationValue(old_auth_data.to_string());
TPM_RC result = trunks_tpm_utility_->ChangeKeyAuthorizationData(
key_handle,
new_auth_data.to_string(),
session_->GetDelegate(),
new_auth_key_blob);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error changing authorization data: "
<< trunks::GetErrorString(result);
return false;
}
result = factory_->GetTpm()->FlushContextSync(key_handle, NULL);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error unloading key under old authorization: "
<< trunks::GetErrorString(result);
return false;
}
slot_handles_[slot_id].erase(key_handle);
FlushHandle(key_handle);
return true;
}
bool TPM2UtilityImpl::GenerateRandom(int num_bytes, std::string* random_data) {
AutoLock lock(lock_);
TPM_RC result = trunks_tpm_utility_->GenerateRandom(num_bytes,
nullptr,
random_data);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error generating random data from the TPM: "
<< trunks::GetErrorString(result);
return false;
}
return true;
}
bool TPM2UtilityImpl::StirRandom(const std::string& entropy_data) {
AutoLock lock(lock_);
TPM_RC result = trunks_tpm_utility_->StirRandom(entropy_data, nullptr);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error seeding TPM random number generator: "
<< trunks::GetErrorString(result);
return false;
}
return true;
}
bool TPM2UtilityImpl::GenerateKey(int slot,
int modulus_bits,
const std::string& public_exponent,
const SecureBlob& auth_data,
std::string* key_blob,
int* key_handle) {
AutoLock lock(lock_);
if (public_exponent.size() > 4) {
LOG(ERROR) << "Incorrectly formatted public_exponent.";
return false;
}
if (auth_data.size() > SHA256_DIGEST_SIZE) {
LOG(ERROR) << "Authorization cannot be larger than SHA256 Digest size.";
return false;
}
if (modulus_bits < static_cast<int>(kMinModulusSize)) {
LOG(ERROR) << "Minimum modulus size is: " << kMinModulusSize;
}
ScopedSession session_scope(factory_, &session_);
if (!session_) {
return false;
}
session_->SetEntityAuthorizationValue(""); // SRK Authorization Value.
TPM_RC result = trunks_tpm_utility_->CreateRSAKeyPair(
trunks::TpmUtility::AsymmetricKeyUsage::kDecryptAndSignKey,
modulus_bits,
GetIntegerExponent(public_exponent),
auth_data.to_string(),
"", // Policy Digest
false, // use_only_policy_authorization
trunks::kNoCreationPCR,
session_->GetDelegate(),
key_blob,
nullptr);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error creating RSA key pair: "
<< trunks::GetErrorString(result);
return false;
}
if (!LoadKeyWithParentInternal(slot, *key_blob, auth_data,
kRSAStorageRootKey, key_handle)) {
return false;
}
return true;
}
bool TPM2UtilityImpl::GetPublicKey(int key_handle,
std::string* public_exponent,
std::string* modulus) {
AutoLock lock(lock_);
trunks::TPMT_PUBLIC public_data;
TPM_RC result = trunks_tpm_utility_->GetKeyPublicArea(key_handle,
&public_data);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error getting key public data: " << result;
return false;
}
public_exponent->clear();
result = trunks::Serialize_UINT32(public_data.parameters.rsa_detail.exponent,
public_exponent);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error serializing public exponent: " << result;
return false;
}
modulus->assign(StringFrom_TPM2B_PUBLIC_KEY_RSA(public_data.unique.rsa));
return true;
}
bool TPM2UtilityImpl::WrapKey(int slot,
const std::string& public_exponent,
const std::string& modulus,
const std::string& prime_factor,
const SecureBlob& auth_data,
std::string* key_blob,
int* key_handle) {
AutoLock lock(lock_);
if (public_exponent.size() > 4) {
LOG(ERROR) << "Incorrectly formatted public_exponent.";
return false;
}
if (auth_data.size() > SHA256_DIGEST_SIZE) {
LOG(ERROR) << "Authorization cannot be larger than SHA256 Digest size.";
return false;
}
if (modulus.size() < kMinModulusSize) {
LOG(ERROR) << "Minimum modulus size is: " << kMinModulusSize;
return false;
}
ScopedSession session_scope(factory_, &session_);
if (!session_) {
return false;
}
session_->SetEntityAuthorizationValue(""); // SRK Authorization Value.
TPM_RC result = trunks_tpm_utility_->ImportRSAKey(
trunks::TpmUtility::AsymmetricKeyUsage::kDecryptAndSignKey,
modulus,
GetIntegerExponent(public_exponent),
prime_factor,
auth_data.to_string(),
session_->GetDelegate(),
key_blob);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error importing RSA key to TPM: "
<< trunks::GetErrorString(result);
return false;
}
if (!LoadKeyWithParentInternal(slot, *key_blob, auth_data,
kRSAStorageRootKey, key_handle)) {
return false;
}
return true;
}
bool TPM2UtilityImpl::LoadKey(int slot,
const std::string& key_blob,
const SecureBlob& auth_data,
int* key_handle) {
AutoLock lock(lock_);
return LoadKeyWithParentInternal(slot,
key_blob,
auth_data,
kRSAStorageRootKey,
key_handle);
}
bool TPM2UtilityImpl::LoadKeyWithParent(int slot,
const std::string& key_blob,
const SecureBlob& auth_data,
int parent_key_handle,
int* key_handle) {
AutoLock lock(lock_);
return LoadKeyWithParentInternal(slot,
key_blob,
auth_data,
parent_key_handle,
key_handle);
}
void TPM2UtilityImpl::UnloadKeysForSlot(int slot) {
AutoLock Lock(lock_);
for (const auto& it : slot_handles_[slot]) {
if (factory_->GetTpm()->FlushContextSync(it, NULL) != TPM_RC_SUCCESS) {
LOG(WARNING) << "Error flushing handle: " << it;
}
FlushHandle(it);
}
slot_handles_.erase(slot);
}
bool TPM2UtilityImpl::Bind(int key_handle,
const std::string& input,
std::string* output) {
CHECK(output);
std::string modulus;
std::string exponent;
if (!GetPublicKey(key_handle, &exponent, &modulus)) {
return false;
}
if (input.size() > modulus.size() - 11) {
LOG(ERROR) << "Encryption plaintext is longer than RSA modulus.";
return false;
}
crypto::ScopedRSA rsa(RSA_new());
rsa.get()->n = BN_bin2bn(
reinterpret_cast<const unsigned char*>(modulus.data()),
modulus.size(),
nullptr);
rsa.get()->e = BN_bin2bn(
reinterpret_cast<const unsigned char*>(exponent.data()),
exponent.size(),
nullptr);
// RSA encrypt output should be size of the modulus.
output->resize(modulus.size());
int rsa_result = RSA_public_encrypt(
input.size(),
reinterpret_cast<const unsigned char*>(input.data()),
reinterpret_cast<unsigned char*>(string_as_array(output)),
rsa.get(),
RSA_PKCS1_PADDING);
if (rsa_result == -1) {
LOG(ERROR) << "Error performing RSA_public_encrypt.";
return false;
}
return true;
}
bool TPM2UtilityImpl::Unbind(int key_handle,
const std::string& input,
std::string* output) {
AutoLock lock(lock_);
return UnbindInternal(key_handle, input, output);
}
bool TPM2UtilityImpl::Sign(int key_handle,
const std::string& input,
std::string* signature) {
AutoLock Lock(lock_);
std::string auth_data = handle_auth_data_[key_handle].to_string();
ScopedSession session_scope(factory_, &session_);
if (!session_) {
return false;
}
session_->SetEntityAuthorizationValue(auth_data);
trunks::TPMT_PUBLIC public_area;
TPM_RC result = trunks_tpm_utility_->GetKeyPublicArea(key_handle,
&public_area);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error getting key public data: "
<< trunks::GetErrorString(result);
return false;
}
// If decryption is allowed for the key, do padding in software (the
// session layer already prepared the DigestInfo by prepending the algorithm
// ID) and perform raw RSA on TPM by sending Decrypt command with NULL scheme.
// Otherwise, strip the algorithm ID already prepended by the session level,
// send Sign command to the TPM with the original unencoded digest, and let
// TPM handle padding and encoding on its side.
// This is done to work with TPMs that don't support all required hashing
// algorithms, and for which the Decrypt attribute is set for signing keys.
if (public_area.object_attributes & trunks::kDecrypt) {
std::string padded_input;
if (!AddPKCS1Padding(input, public_area.unique.rsa.size, &padded_input)) {
return false;
}
result = trunks_tpm_utility_->AsymmetricDecrypt(key_handle,
trunks::TPM_ALG_NULL,
trunks::TPM_ALG_NULL,
padded_input,
session_->GetDelegate(),
signature);
} else {
std::string digest;
trunks::TPM_ALG_ID digest_alg;
ParseDigestInfo(input, &digest, &digest_alg);
result = trunks_tpm_utility_->Sign(key_handle,
trunks::TPM_ALG_RSASSA,
digest_alg,
digest,
false /* don't generate hash */,
session_->GetDelegate(),
signature);
}
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error performing sign operation: "
<< trunks::GetErrorString(result);
return false;
}
return true;
}
bool TPM2UtilityImpl::Verify(int key_handle,
const std::string& input,
const std::string& signature) {
std::string digest = base::SHA1HashString(input);
crypto::ScopedRSA rsa(RSA_new());
std::string modulus;
std::string exponent;
if (!GetPublicKey(key_handle, &exponent, &modulus)) {
return false;
}
rsa.get()->n = BN_bin2bn(
reinterpret_cast<const unsigned char*>(modulus.data()),
modulus.size(),
nullptr);
rsa.get()->e = BN_bin2bn(
reinterpret_cast<const unsigned char*>(exponent.data()),
exponent.size(),
nullptr);
if (RSA_verify(NID_sha1,
reinterpret_cast<const unsigned char*>(digest.data()),
digest.size(),
reinterpret_cast<const unsigned char*>(signature.data()),
signature.size(),
rsa.get()) != 1) {
LOG(ERROR) << "Signature was incorrect.";
return false;
}
return true;
}
bool TPM2UtilityImpl::IsSRKReady() {
return IsTPMAvailable() && Init();
}
bool TPM2UtilityImpl::LoadKeyWithParentInternal(int slot,
const std::string& key_blob,
const SecureBlob& auth_data,
int parent_key_handle,
int* key_handle) {
CHECK_EQ(parent_key_handle, static_cast<int>(kRSAStorageRootKey))
<< "Chaps with TPM2.0 only loads keys under the RSA SRK.";
if (auth_data.size() > SHA256_DIGEST_SIZE) {
LOG(ERROR) << "Authorization cannot be larger than SHA256 Digest size.";
return false;
}
ScopedSession session_scope(factory_, &session_);
if (!session_) {
return false;
}
session_->SetEntityAuthorizationValue(""); // SRK Authorization Value.
TPM_RC result = trunks_tpm_utility_->LoadKey(
key_blob,
session_->GetDelegate(),
reinterpret_cast<trunks::TPM_HANDLE*>(key_handle));
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error loading key into TPM: "
<< trunks::GetErrorString(result);
return false;
}
std::string key_name;
result = trunks_tpm_utility_->GetKeyName(*key_handle, &key_name);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error getting key name: " << trunks::GetErrorString(result);
return false;
}
handle_auth_data_[*key_handle] = auth_data;
handle_name_[*key_handle] = key_name;
slot_handles_[slot].insert(*key_handle);
return true;
}
bool TPM2UtilityImpl::UnbindInternal(int key_handle,
const std::string& input,
std::string* output) {
trunks::TPMT_PUBLIC public_data;
TPM_RC result = trunks_tpm_utility_->GetKeyPublicArea(key_handle,
&public_data);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error getting key public data: " << result;
return false;
}
if (input.size() > public_data.unique.rsa.size) {
LOG(ERROR) << "RSA decrypt ciphertext is larger than modulus.";
return false;
}
std::string auth_data = handle_auth_data_[key_handle].to_string();
ScopedSession session_scope(factory_, &session_);
if (!session_) {
return false;
}
session_->SetEntityAuthorizationValue(auth_data);
result = trunks_tpm_utility_->AsymmetricDecrypt(key_handle,
trunks::TPM_ALG_RSAES,
trunks::TPM_ALG_SHA1,
input,
session_->GetDelegate(),
output);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error performing unbind operation: "
<< trunks::GetErrorString(result);
return false;
}
return true;
}
void TPM2UtilityImpl::FlushHandle(int key_handle) {
handle_auth_data_.erase(key_handle);
handle_name_.erase(key_handle);
}
} // namespace chaps