blob: 1791685bdaa0ecd27827a48137dba2a725012496 [file] [log] [blame]
// Copyright 2022 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 "u2fd/sign_manager/sign_manager_tpm_v1.h"
#include <string>
#include <utility>
#include <vector>
#include <brillo/secure_blob.h>
#include <chromeos/cbor/values.h>
#include <chromeos/cbor/writer.h>
#include <tpm_manager/client/tpm_manager_utility.h>
#include <trousers/scoped_tss_type.h>
#include <trousers/trousers.h>
#include <trousers/tss.h>
#define TPM_LOG(severity, result) \
LOG(severity) << "TPM error 0x" << std::hex << result << " (" \
<< Trspi_Error_String(result) << "): "
namespace u2f {
namespace {
using brillo::SecureBlob;
using trousers::ScopedTssContext;
using trousers::ScopedTssKey;
using trousers::ScopedTssMemory;
using trousers::ScopedTssPcrs;
using trousers::ScopedTssPolicy;
using ScopedTssHash = trousers::ScopedTssObject<TSS_HHASH>;
// COSE key parameters.
// https://tools.ietf.org/html/rfc8152#section-7.1
const int kCoseKeyKtyLabel = 1;
const int kCoseKeyKtyRsa = 3;
const int kCoseKeyAlgLabel = 3;
const int kCoseKeyAlgRs256 = -257;
// COSE key type parameters.
// https://tools.ietf.org/html/rfc8152#section-13.1.1
const int kCoseRsaKeyNLabel = -1;
const int kCoseRsaKeyELabel = -2;
constexpr unsigned char kSha256DigestInfo[] = {
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
std::vector<uint8_t> EncodeCredentialPublicKeyInCBOR(
const std::string& public_exponent, const std::string& modulus) {
cbor::Value::MapValue cbor_map;
cbor_map[cbor::Value(kCoseKeyKtyLabel)] = cbor::Value(kCoseKeyKtyRsa);
cbor_map[cbor::Value(kCoseKeyAlgLabel)] = cbor::Value(kCoseKeyAlgRs256);
cbor_map[cbor::Value(kCoseRsaKeyNLabel)] =
cbor::Value(modulus, cbor::Value::Type::BYTE_STRING);
cbor_map[cbor::Value(kCoseRsaKeyELabel)] =
cbor::Value(public_exponent, cbor::Value::Type::BYTE_STRING);
return *cbor::Writer::Write(cbor::Value(std::move(cbor_map)));
}
BYTE* StringAsTSSBuffer(std::string* s) {
return reinterpret_cast<BYTE*>(std::data(*s));
}
std::string TSSBufferAsString(const BYTE* buffer, size_t length) {
return std::string(reinterpret_cast<const char*>(buffer), length);
}
} // namespace
SignManagerTpmV1::SignManagerTpmV1() : tpm_manager_utility_(nullptr) {}
bool SignManagerTpmV1::IsReady() {
return SetupSrk();
}
bool SignManagerTpmV1::Sign(const std::string& key_blob,
const std::string& data_to_sign,
const brillo::SecureBlob& auth_data,
std::string* signature_der) {
if (!SetupSrk()) {
LOG(ERROR) << "SRK is not ready.";
return false;
}
// Load key before signing.
ScopedTssKey key(context_handle_);
std::string mutable_key_blob(key_blob);
BYTE* key_blob_buffer = StringAsTSSBuffer(&mutable_key_blob);
TSS_RESULT result =
Tspi_Context_LoadKeyByBlob(context_handle_, srk_handle_, key_blob.size(),
key_blob_buffer, key.ptr());
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to load key.";
return false;
}
if (!CreateKeyPolicy(key, auth_data, true)) {
return false;
}
// Construct an ASN.1 DER DigestInfo.
std::string digest_to_sign(std::begin(kSha256DigestInfo),
std::end(kSha256DigestInfo));
digest_to_sign += data_to_sign;
// Create a hash object to hold the digest.
ScopedTssHash hash_handle(context_handle_);
result = Tspi_Context_CreateObject(context_handle_, TSS_OBJECT_TYPE_HASH,
TSS_HASH_OTHER, hash_handle.ptr());
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to create hash object.";
return false;
}
result = Tspi_Hash_SetHashValue(hash_handle, digest_to_sign.size(),
StringAsTSSBuffer(&digest_to_sign));
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to set hash data.";
return false;
}
UINT32 length = 0;
ScopedTssMemory buffer(context_handle_);
result = Tspi_Hash_Sign(hash_handle, key, &length, buffer.ptr());
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to generate signature.";
return false;
}
signature_der->assign(TSSBufferAsString(buffer.value(), length));
return true;
}
bool SignManagerTpmV1::CreateKey(KeyType key_type,
const brillo::SecureBlob& auth_data,
std::string* key_blob,
std::vector<uint8_t>* public_key_cbor) {
if (!SetupSrk()) {
LOG(ERROR) << "SRK is not ready.";
return false;
}
if (key_type != KeyType::kRsa) {
LOG(ERROR) << "Only RSA supported on TPM v1.2.";
return false;
}
// Create a non-migratable RSA key.
ScopedTssKey key(context_handle_);
UINT32 init_flags = TSS_KEY_TYPE_SIGNING | TSS_KEY_NOT_MIGRATABLE |
TSS_KEY_VOLATILE | TSS_KEY_AUTHORIZATION |
TSS_KEY_SIZE_2048;
TSS_RESULT result = Tspi_Context_CreateObject(
context_handle_, TSS_OBJECT_TYPE_RSAKEY, init_flags, key.ptr());
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to create object.";
return false;
}
if (!CreateKeyPolicy(key, auth_data, false)) {
return false;
}
result = Tspi_Key_CreateKey(key, srk_handle_, 0);
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to create key.";
return false;
}
result = Tspi_Key_LoadKey(key, srk_handle_);
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to load key.";
return false;
}
std::string public_exponent, modulus;
// Get the public key.
if (!GetDataAttribute(context_handle_, key, TSS_TSPATTRIB_RSAKEY_INFO,
TSS_TSPATTRIB_KEYINFO_RSA_EXPONENT, &public_exponent)) {
LOG(ERROR) << __func__ << ": Failed to read public exponent.";
return false;
}
if (!GetDataAttribute(context_handle_, key, TSS_TSPATTRIB_RSAKEY_INFO,
TSS_TSPATTRIB_KEYINFO_RSA_MODULUS, &modulus)) {
LOG(ERROR) << __func__ << ": Failed to read modulus.";
return false;
}
*public_key_cbor = EncodeCredentialPublicKeyInCBOR(public_exponent, modulus);
// Get the key blob.
if (!GetDataAttribute(context_handle_, key, TSS_TSPATTRIB_KEY_BLOB,
TSS_TSPATTRIB_KEYBLOB_BLOB, key_blob)) {
LOG(ERROR) << __func__ << ": Failed to read key blob.";
return false;
}
result = Tspi_Key_UnloadKey(key);
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to unload key.";
// Don't need to return false here since we already successfully created the
// key and obtained all the data.
}
return true;
}
bool SignManagerTpmV1::CreateKeyPolicy(TSS_HKEY key,
const SecureBlob& auth_data,
bool auth_only) {
ScopedTssPolicy policy(context_handle_);
TSS_RESULT result = Tspi_Context_CreateObject(
context_handle_, TSS_OBJECT_TYPE_POLICY, TSS_POLICY_USAGE, policy.ptr());
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to create policy.";
return false;
}
if (auth_data.empty()) {
result = Tspi_Policy_SetSecret(policy, TSS_SECRET_MODE_NONE, 0, NULL);
} else {
result =
Tspi_Policy_SetSecret(policy, TSS_SECRET_MODE_PLAIN, auth_data.size(),
const_cast<BYTE*>(auth_data.data()));
}
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to set auth value for key.";
return false;
}
if (!auth_only) {
result = Tspi_SetAttribUint32(key, TSS_TSPATTRIB_KEY_INFO,
TSS_TSPATTRIB_KEYINFO_SIGSCHEME,
TSS_SS_RSASSAPKCS1V15_DER);
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to set scheme.";
return false;
}
}
result = Tspi_Policy_AssignToObject(policy.release(), key);
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << ": Failed to assign policy to key.";
return false;
}
return true;
}
bool SignManagerTpmV1::IsTpmReady() {
if (is_ready_) {
return true;
}
tpm_manager::LocalData local_data;
bool is_enabled = false;
bool is_owned = false;
if (!tpm_manager_utility_) {
tpm_manager_utility_ = tpm_manager::TpmManagerUtility::GetSingleton();
if (!tpm_manager_utility_) {
LOG(ERROR) << __func__ << ": Failed to get tpm_manager utility.";
return false;
}
}
if (!tpm_manager_utility_->GetTpmStatus(&is_enabled, &is_owned,
&local_data)) {
LOG(ERROR) << __func__ << ": Failed to get tpm status from tpm_manager.";
return false;
}
is_ready_ = is_enabled && is_owned;
return is_ready_;
}
bool SignManagerTpmV1::ConnectContextAsUser(ScopedTssContext* context,
TSS_HTPM* tpm) {
*tpm = 0;
TSS_RESULT result;
if (TPM_ERROR(result = Tspi_Context_Create(context->ptr()))) {
TPM_LOG(ERROR, result) << __func__ << ": Error calling Tspi_Context_Create";
return false;
}
if (TPM_ERROR(result = Tspi_Context_Connect(*context, nullptr))) {
TPM_LOG(ERROR, result) << __func__
<< ": Error calling Tspi_Context_Connect";
return false;
}
if (TPM_ERROR(result = Tspi_Context_GetTpmObject(*context, tpm))) {
TPM_LOG(ERROR, result) << __func__
<< ": Error calling Tspi_Context_GetTpmObject";
return false;
}
return true;
}
bool SignManagerTpmV1::SetupSrk() {
if (!IsTpmReady()) {
return false;
}
if (srk_handle_) {
return true;
}
if (!InitializeContextHandle(__func__)) {
return false;
}
srk_handle_.reset(context_handle_, 0);
if (!LoadSrk(context_handle_, &srk_handle_)) {
LOG(ERROR) << __func__ << ": Failed to load SRK.";
return false;
}
// In order to wrap a key with the SRK we need access to the SRK public key
// and we need to get it manually. Once it's in the key object, we don't need
// to do this again.
UINT32 length = 0;
ScopedTssMemory buffer(context_handle_);
TSS_RESULT result;
result = Tspi_Key_GetPubKey(srk_handle_, &length, buffer.ptr());
if (result != TSS_SUCCESS) {
TPM_LOG(INFO, result) << __func__ << ": Failed to read SRK public key.";
return false;
}
return true;
}
bool SignManagerTpmV1::LoadSrk(TSS_HCONTEXT context_handle,
ScopedTssKey* srk_handle) {
TSS_RESULT result;
TSS_UUID uuid = TSS_UUID_SRK;
if (TPM_ERROR(result = Tspi_Context_LoadKeyByUUID(context_handle,
TSS_PS_TYPE_SYSTEM, uuid,
srk_handle->ptr()))) {
TPM_LOG(ERROR, result) << __func__
<< ": Error calling Tspi_Context_LoadKeyByUUID";
return false;
}
// Check if the SRK wants a password.
UINT32 auth_usage;
if (TPM_ERROR(result = Tspi_GetAttribUint32(
*srk_handle, TSS_TSPATTRIB_KEY_INFO,
TSS_TSPATTRIB_KEYINFO_AUTHUSAGE, &auth_usage))) {
TPM_LOG(ERROR, result) << __func__
<< ": Error calling Tspi_GetAttribUint32";
return false;
}
if (auth_usage) {
// Give it an empty password if needed.
TSS_HPOLICY usage_policy;
if (TPM_ERROR(result = Tspi_GetPolicyObject(*srk_handle, TSS_POLICY_USAGE,
&usage_policy))) {
TPM_LOG(ERROR, result)
<< __func__ << ": Error calling Tspi_GetPolicyObject";
return false;
}
BYTE empty_password[] = {};
if (TPM_ERROR(result =
Tspi_Policy_SetSecret(usage_policy, TSS_SECRET_MODE_PLAIN,
0, empty_password))) {
TPM_LOG(ERROR, result)
<< __func__ << ": Error calling Tspi_Policy_SetSecret";
return false;
}
}
return true;
}
bool SignManagerTpmV1::GetDataAttribute(TSS_HCONTEXT context,
TSS_HOBJECT object,
TSS_FLAG flag,
TSS_FLAG sub_flag,
std::string* data) {
UINT32 length = 0;
ScopedTssMemory buffer(context);
TSS_RESULT result =
Tspi_GetAttribData(object, flag, sub_flag, &length, buffer.ptr());
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << __func__ << "Failed to read object attribute.";
return false;
}
data->assign(TSSBufferAsString(buffer.value(), length));
return true;
}
bool SignManagerTpmV1::InitializeContextHandle(
const std::string& consumer_name) {
if (!static_cast<TSS_HCONTEXT>(context_handle_) || !tpm_handle_) {
context_handle_.reset();
if (!ConnectContextAsUser(&context_handle_, &tpm_handle_)) {
LOG(ERROR) << __func__ << ": Failed to connect to the TPM.";
return false;
}
}
return true;
}
} // namespace u2f