blob: 155993ffda623d15a973df46b4783c71eea11771 [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 "libtpmcrypto/tpm1_impl.h"
#include <memory>
#include <string>
#include <base/logging.h>
#include <base/threading/platform_thread.h>
#include <base/time/time.h>
#include <brillo/secure_blob.h>
#include <trousers/scoped_tss_type.h>
using base::PlatformThread;
using base::TimeDelta;
using brillo::Blob;
using brillo::SecureBlob;
using trousers::ScopedTssContext;
using trousers::ScopedTssKey;
using trousers::ScopedTssMemory;
using trousers::ScopedTssNvStore;
using trousers::ScopedTssPcrs;
namespace tpmcrypto {
#define TPM_LOG(severity, result) \
LOG(severity) << "TPM error 0x" << std::hex << result << " (" \
<< Trspi_Error_String(result) << "): "
constexpr unsigned char kDefaultSrkAuth[] = {};
constexpr unsigned int kTpmConnectRetries = 10;
constexpr unsigned int kTpmConnectIntervalMs = 100;
Tpm1Impl::Tpm1Impl() = default;
Tpm1Impl::~Tpm1Impl() = default;
std::unique_ptr<Tpm> CreateTpmInstance() {
return std::make_unique<Tpm1Impl>();
}
bool Tpm1Impl::SealToPCR0(const SecureBlob& value, SecureBlob* sealed_value) {
CHECK(sealed_value);
ScopedTssContext context_handle;
TSS_HTPM tpm_handle;
if (!ConnectContextAsUser(context_handle.ptr(), &tpm_handle)) {
LOG(ERROR) << "SealToPCR0: Failed to connect to the TPM.";
return false;
}
// Load the Storage Root Key.
TSS_RESULT result;
ScopedTssKey srk_handle(context_handle);
if (!LoadSrk(context_handle, srk_handle.ptr(), &result)) {
TPM_LOG(ERROR, result) << "SealToPCR0: Failed to load SRK.";
return false;
}
// Check the SRK public key
unsigned int size_n = 0;
ScopedTssMemory public_srk(context_handle);
if (TPM_ERROR(
result = Tspi_Key_GetPubKey(srk_handle, &size_n, public_srk.ptr()))) {
TPM_LOG(ERROR, result) << "SealToPCR0: Unable to get the SRK public key";
return false;
}
// Create a PCRS object which holds the value of PCR0.
ScopedTssPcrs pcrs_handle(context_handle);
if (TPM_ERROR(result = Tspi_Context_CreateObject(
context_handle, TSS_OBJECT_TYPE_PCRS, TSS_PCRS_STRUCT_INFO,
pcrs_handle.ptr()))) {
TPM_LOG(ERROR, result)
<< "SealToPCR0: Error calling Tspi_Context_CreateObject";
return false;
}
// Create a ENCDATA object to receive the sealed data.
UINT32 pcr_len = 0;
ScopedTssMemory pcr_value(context_handle);
Tspi_TPM_PcrRead(tpm_handle, 0, &pcr_len, pcr_value.ptr());
Tspi_PcrComposite_SetPcrValue(pcrs_handle, 0, pcr_len, pcr_value.value());
ScopedTssKey enc_handle(context_handle);
if (TPM_ERROR(result = Tspi_Context_CreateObject(
context_handle, TSS_OBJECT_TYPE_ENCDATA, TSS_ENCDATA_SEAL,
enc_handle.ptr()))) {
TPM_LOG(ERROR, result)
<< "SealToPCR0: Error calling Tspi_Context_CreateObject";
return false;
}
// Seal the given value with the SRK.
if (TPM_ERROR(result = Tspi_Data_Seal(enc_handle, srk_handle, value.size(),
const_cast<BYTE*>(value.data()),
pcrs_handle))) {
TPM_LOG(ERROR, result) << "SealToPCR0: Error calling Tspi_Data_Seal";
return false;
}
// Extract the sealed value.
ScopedTssMemory enc_data(context_handle);
UINT32 enc_data_length = 0;
if (TPM_ERROR(result =
Tspi_GetAttribData(enc_handle, TSS_TSPATTRIB_ENCDATA_BLOB,
TSS_TSPATTRIB_ENCDATABLOB_BLOB,
&enc_data_length, enc_data.ptr()))) {
TPM_LOG(ERROR, result) << "SealToPCR0: Error calling Tspi_GetAttribData";
return false;
}
sealed_value->assign(&enc_data.value()[0],
&enc_data.value()[enc_data_length]);
return true;
}
bool Tpm1Impl::Unseal(const SecureBlob& sealed_value, SecureBlob* value) {
CHECK(value);
ScopedTssContext context_handle;
TSS_HTPM tpm_handle;
if (!ConnectContextAsUser(context_handle.ptr(), &tpm_handle)) {
LOG(ERROR) << "Unseal: Failed to connect to the TPM.";
return false;
}
// Load the Storage Root Key.
TSS_RESULT result;
ScopedTssKey srk_handle(context_handle);
if (!LoadSrk(context_handle, srk_handle.ptr(), &result)) {
TPM_LOG(ERROR, result) << "Unseal: Failed to load SRK.";
return false;
}
// Create an ENCDATA object with the sealed value.
ScopedTssKey enc_handle(context_handle);
if (TPM_ERROR(result = Tspi_Context_CreateObject(
context_handle, TSS_OBJECT_TYPE_ENCDATA, TSS_ENCDATA_SEAL,
enc_handle.ptr()))) {
TPM_LOG(ERROR, result) << "Unseal: Error calling Tspi_Context_CreateObject";
return false;
}
if (TPM_ERROR(result = Tspi_SetAttribData(
enc_handle, TSS_TSPATTRIB_ENCDATA_BLOB,
TSS_TSPATTRIB_ENCDATABLOB_BLOB, sealed_value.size(),
const_cast<BYTE*>(sealed_value.data())))) {
TPM_LOG(ERROR, result) << "Unseal: Error calling Tspi_SetAttribData";
return false;
}
// Unseal using the SRK.
ScopedTssMemory dec_data(context_handle);
UINT32 dec_data_length = 0;
if (TPM_ERROR(result = Tspi_Data_Unseal(enc_handle, srk_handle,
&dec_data_length, dec_data.ptr()))) {
TPM_LOG(ERROR, result) << "Unseal: Error calling Tspi_Data_Unseal";
return false;
}
value->assign(&dec_data.value()[0], &dec_data.value()[dec_data_length]);
brillo::SecureClear(dec_data.value(), dec_data_length);
return true;
}
TSS_HCONTEXT Tpm1Impl::ConnectContext() {
TSS_RESULT result;
TSS_HCONTEXT context_handle = 0;
if (!OpenAndConnectTpm(&context_handle, &result)) {
return 0;
}
return context_handle;
}
bool Tpm1Impl::OpenAndConnectTpm(TSS_HCONTEXT* context_handle,
TSS_RESULT* result) {
TSS_RESULT local_result;
ScopedTssContext local_context_handle;
if (TPM_ERROR(local_result =
Tspi_Context_Create(local_context_handle.ptr()))) {
TPM_LOG(ERROR, local_result) << "Error calling Tspi_Context_Create";
if (result)
*result = local_result;
return false;
}
for (unsigned int i = 0; i < kTpmConnectRetries; i++) {
LOG(INFO) << "Attempting to connect to TPM";
if (TPM_ERROR(local_result =
Tspi_Context_Connect(local_context_handle, NULL))) {
// If there was a communications failure, try sleeping a bit here--it may
// be that tcsd is still starting
if (ERROR_CODE(local_result) == TSS_E_COMM_FAILURE) {
LOG(INFO) << "Sleeping to wait for TPM";
PlatformThread::Sleep(
TimeDelta::FromMilliseconds(kTpmConnectIntervalMs));
} else {
TPM_LOG(ERROR, local_result) << "Error calling Tspi_Context_Connect";
if (result)
*result = local_result;
return false;
}
} else {
break;
}
}
if (TPM_ERROR(local_result)) {
TPM_LOG(ERROR, local_result) << "Error calling Tspi_Context_Connect";
if (result)
*result = local_result;
return false;
}
*context_handle = local_context_handle.release();
if (result)
*result = local_result;
return (*context_handle != 0);
}
bool Tpm1Impl::GetTpm(TSS_HCONTEXT context_handle, TSS_HTPM* tpm_handle) {
TSS_RESULT result;
TSS_HTPM local_tpm_handle;
if (TPM_ERROR(result = Tspi_Context_GetTpmObject(context_handle,
&local_tpm_handle))) {
TPM_LOG(ERROR, result) << "Error calling Tspi_Context_GetTpmObject";
return false;
}
*tpm_handle = local_tpm_handle;
return true;
}
bool Tpm1Impl::ConnectContextAsUser(TSS_HCONTEXT* context, TSS_HTPM* tpm) {
*context = 0;
*tpm = 0;
if ((*context = ConnectContext()) == 0) {
LOG(ERROR) << "ConnectContextAsUser: Could not open the TPM";
return false;
}
if (!GetTpm(*context, tpm)) {
LOG(ERROR) << "ConnectContextAsUser: failed to get a TPM object";
Tspi_Context_Close(*context);
*context = 0;
*tpm = 0;
return false;
}
return true;
}
bool Tpm1Impl::LoadSrk(TSS_HCONTEXT context_handle,
TSS_HKEY* srk_handle,
TSS_RESULT* result) const {
*result = TSS_SUCCESS;
// Load the Storage Root Key
TSS_UUID SRK_UUID = TSS_UUID_SRK;
ScopedTssKey local_srk_handle(context_handle);
if (TPM_ERROR(*result = Tspi_Context_LoadKeyByUUID(
context_handle, TSS_PS_TYPE_SYSTEM, SRK_UUID,
local_srk_handle.ptr()))) {
return false;
}
// Check if the SRK wants a password
UINT32 srk_authusage;
if (TPM_ERROR(*result = Tspi_GetAttribUint32(
local_srk_handle, TSS_TSPATTRIB_KEY_INFO,
TSS_TSPATTRIB_KEYINFO_AUTHUSAGE, &srk_authusage))) {
return false;
}
// Give it the password if needed
if (srk_authusage) {
TSS_HPOLICY srk_usage_policy;
if (TPM_ERROR(*result = Tspi_GetPolicyObject(
local_srk_handle, TSS_POLICY_USAGE, &srk_usage_policy))) {
return false;
}
*result = Tspi_Policy_SetSecret(srk_usage_policy, TSS_SECRET_MODE_PLAIN,
sizeof(kDefaultSrkAuth),
const_cast<BYTE*>(kDefaultSrkAuth));
if (TPM_ERROR(*result)) {
return false;
}
}
*srk_handle = local_srk_handle.release();
return true;
}
bool Tpm1Impl::GetNVAttributes(uint32_t index, uint32_t* attributes) {
CHECK(attributes);
ScopedTssContext context_handle;
TSS_HTPM tpm_handle;
if (!ConnectContextAsUser(context_handle.ptr(), &tpm_handle)) {
LOG(ERROR) << "GetNVAttributes: Failed to connect to the TPM.";
return false;
}
TSS_RESULT result;
UINT32 nv_index_data_length = sizeof(TPM_NV_DATA_PUBLIC);
ScopedTssMemory nv_index_data(context_handle);
if (TPM_ERROR(result = Tspi_TPM_GetCapability(
tpm_handle, TSS_TPMCAP_NV_INDEX, sizeof(index),
reinterpret_cast<BYTE*>(&index), &nv_index_data_length,
nv_index_data.ptr()))) {
TPM_LOG(ERROR, result) << "Error calling Tspi_TPM_GetCapability";
return false;
}
if (nv_index_data_length == 0) {
LOG(ERROR) << "The NV index public data length is not valid";
return false;
}
TPM_NV_DATA_PUBLIC nv_data_public;
UINT64 offset = 0;
if (TPM_ERROR(result = Trspi_UnloadBlob_NV_DATA_PUBLIC(
&offset, *nv_index_data.ptr(), &nv_data_public))) {
TPM_LOG(ERROR, result) << "Error unloading NV public data.";
return false;
}
*attributes = nv_data_public.permission.attributes;
return true;
}
bool Tpm1Impl::NVReadNoAuth(uint32_t index,
uint32_t offset,
size_t size,
std::string* data) {
CHECK(data);
ScopedTssContext context_handle;
TSS_HTPM tpm_handle;
if (!ConnectContextAsUser(context_handle.ptr(), &tpm_handle)) {
LOG(ERROR) << "NVReadNoAuth: Failed to connect to the TPM.";
return false;
}
// Create an NVRAM store object handle.
TSS_RESULT result;
ScopedTssNvStore nv_handle(context_handle);
result = Tspi_Context_CreateObject(context_handle, TSS_OBJECT_TYPE_NV, 0,
nv_handle.ptr());
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << "Could not acquire an NVRAM object handle";
return false;
}
result = Tspi_SetAttribUint32(nv_handle, TSS_TSPATTRIB_NV_INDEX, 0, index);
if (TPM_ERROR(result)) {
TPM_LOG(ERROR, result) << "Could not set index on NVRAM object: " << index;
return false;
}
SecureBlob blob(size);
// Read from NVRAM in conservatively small chunks. This is a limitation of the
// TPM that is left for the application layer to deal with. The maximum size
// that is supported here can vary between vendors / models, so we'll be
// conservative. FWIW, the Infineon chips seem to handle up to 1024.
const UINT32 kMaxDataSize = 128;
UINT32 offset_l = 0;
while (offset_l < size) {
UINT32 chunk_size = size - offset_l;
if (chunk_size > kMaxDataSize)
chunk_size = kMaxDataSize;
ScopedTssMemory space_data(context_handle);
if ((result = Tspi_NV_ReadValue(nv_handle, offset_l + offset, &chunk_size,
space_data.ptr()))) {
TPM_LOG(ERROR, result) << "Could not read from NVRAM space: " << index;
return false;
}
if (!space_data.value()) {
LOG(ERROR) << "No data read from NVRAM space: " << index;
return false;
}
CHECK(offset_l + chunk_size <= blob.size());
unsigned char* buffer = blob.data() + offset_l;
memcpy(buffer, space_data.value(), chunk_size);
offset_l += chunk_size;
}
*data = std::string(blob.begin(), blob.end());
return true;
}
} // namespace tpmcrypto