blob: e4695d12f9f086a888ceb9dff9bee2b3071b8502 [file] [log] [blame]
/* Copyright 2016 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.
*
* Some TPM constants and type definitions for standalone compilation for use
* in the firmware
*/
#include "2common.h"
#include "2sysincludes.h"
#include "tlcl.h"
#include "tpm2_marshaling.h"
/*
* TODO(chromium:1032930): Originally accessed by including secdata_tpm.h.
* This file moved to depthcharge, and vboot shouldn't need to know the indices
* of different TPM spaces anyways. But since the vboot TPM 2.0 implementation
* uses TPM 1.2 primitives as its API, TlclSetGlobalLock (TPM 1.2) needs to use
* the firmware space index to emulate a TlclWriteLock call (TPM 2.0).
*/
#define FIRMWARE_NV_INDEX 0x1007
/* Global buffer for deserialized responses. */
struct tpm2_response tpm2_resp;
/*
* Serializes and sends the command, gets back the response and
* parses it into the provided buffer.
*
* @command: command code.
* @command_body: command-specific payload.
* @response: pointer to the buffer to place the parsed response to.
*
* Returns the result of processing the command:
* - if an error happened at marshaling, sending, receiving or unmarshaling
* stages, returns the error code;
* - if the received response was successfully unmarshaled, returns success
* regardless of the received response code.
*/
static uint32_t tpm_get_response(TPM_CC command,
void *command_body,
struct tpm2_response *response)
{
/* Command/response buffer. */
static uint8_t cr_buffer[TPM_BUFFER_SIZE];
int out_size;
uint32_t res;
uint32_t in_size;
out_size = tpm_marshal_command(command, command_body,
cr_buffer, sizeof(cr_buffer));
if (out_size < 0) {
VB2_DEBUG("command %#x, failed to serialize\n", command);
return TPM_E_WRITE_FAILURE;
}
in_size = sizeof(cr_buffer);
res = vb2ex_tpm_send_recv(cr_buffer, out_size, cr_buffer, &in_size);
if (res != TPM_SUCCESS) {
VB2_DEBUG("tpm transaction failed for %#x with error %#x\n",
command, res);
return res;
}
if (tpm_unmarshal_response(command, cr_buffer, in_size, response) < 0) {
VB2_DEBUG("command %#x, failed to parse response\n", command);
return TPM_E_READ_FAILURE;
}
VB2_DEBUG("command %#x, return code %#x\n", command,
response->hdr.tpm_code);
return TPM_SUCCESS;
}
/*
* Same as tpm_get_response() but, if the response was successfully received,
* returns the received response code. The set of errors returned by the
* communication stack doesn't overlap with the set of errors returned by the
* TPM, so it's always possible to distinguish the two. In case of communication
* errors, the caller should not check other fields of response, as the response
* is likely not filled. In any case, it is recommended that callers, who need
* to work with response fields even if a non-zero response code was received
* from the TPM, use tpm_get_response() and explicitly check the response code
* themselves.
*/
static uint32_t tpm_send_receive(TPM_CC command,
void *command_body,
struct tpm2_response *response)
{
uint32_t rv = tpm_get_response(command, command_body, response);
return rv ? rv : response->hdr.tpm_code;
}
/*
* Same as tpm_send_receive() for callers that care only about the return code.
*/
static uint32_t tpm_get_response_code(TPM_CC command, void *command_body)
{
return tpm_send_receive(command, command_body, &tpm2_resp);
}
static uint32_t tlcl_read_ph_disabled(void)
{
uint32_t rv;
TPM_STCLEAR_FLAGS flags;
rv = TlclGetSTClearFlags(&flags);
if (rv == TPM_SUCCESS)
tpm_set_ph_disabled(!flags.phEnable);
return rv;
}
uint32_t TlclLibInit(void)
{
uint32_t rv;
rv = vb2ex_tpm_init();
if (rv != TPM_SUCCESS)
return rv;
rv = tlcl_read_ph_disabled();
if (rv != TPM_SUCCESS)
TlclLibClose();
return rv;
}
uint32_t TlclLibClose(void)
{
return vb2ex_tpm_close();
}
uint32_t TlclSendReceive(const uint8_t *request, uint8_t *response,
int max_length)
{
uint32_t rv, resp_size;
resp_size = max_length;
rv = vb2ex_tpm_send_recv(request, tpm_get_packet_size(request),
response, &resp_size);
return rv ? rv : tpm_get_packet_response_code(response);
}
int TlclPacketSize(const uint8_t *packet)
{
return tpm_get_packet_size(packet);
}
uint32_t TlclStartup(void)
{
struct tpm2_startup_cmd startup;
startup.startup_type = TPM_SU_CLEAR;
return tpm_get_response_code(TPM2_Startup, &startup);
}
uint32_t TlclSaveState(void)
{
struct tpm2_shutdown_cmd shutdown;
shutdown.shutdown_type = TPM_SU_STATE;
return tpm_get_response_code(TPM2_Shutdown, &shutdown);
}
uint32_t TlclResume(void)
{
struct tpm2_startup_cmd startup;
startup.startup_type = TPM_SU_STATE;
return tpm_get_response_code(TPM2_Startup, &startup);
}
uint32_t TlclSelfTestFull(void)
{
struct tpm2_self_test_cmd self_test;
self_test.full_test = 1;
return tpm_get_response_code(TPM2_SelfTest, &self_test);
}
uint32_t TlclContinueSelfTest(void)
{
struct tpm2_self_test_cmd self_test;
self_test.full_test = 0;
return tpm_get_response_code(TPM2_SelfTest, &self_test);
}
uint32_t TlclDefineSpace(uint32_t index, uint32_t perm, uint32_t size)
{
return TlclDefineSpaceEx(NULL, 0, index, perm, size, NULL, 0);
}
#ifdef CHROMEOS_ENVIRONMENT
uint32_t TlclUndefineSpace(uint32_t index)
{
return TlclUndefineSpaceEx(NULL, 0, index);
}
uint32_t TlclUndefineSpaceEx(const uint8_t* owner_auth,
uint32_t owner_auth_size,
uint32_t index)
{
struct tpm2_nv_undefine_space_cmd undefine_space;
uint32_t permissions;
uint32_t rv;
/* Authentication support is not implemented. */
VB2_ASSERT(owner_auth == NULL && owner_auth_size == 0);
/* get the publicInfo of index */
rv = TlclGetPermissions(index, &permissions);
if (rv != TPM_SUCCESS) {
return rv;
}
undefine_space.nvIndex = HR_NV_INDEX + index;
undefine_space.use_platform_auth =
(permissions & TPMA_NV_PLATFORMCREATE) > 0;
return tpm_get_response_code(TPM2_NV_UndefineSpace, &undefine_space);
}
#endif /* CHROMEOS_ENVIRONMENT */
uint32_t TlclDefineSpaceEx(const uint8_t* owner_auth, uint32_t owner_auth_size,
uint32_t index, uint32_t perm, uint32_t size,
const void* auth_policy, uint32_t auth_policy_size)
{
struct tpm2_nv_define_space_cmd define_space;
/* Authentication support is not implemented. */
VB2_ASSERT(owner_auth == NULL && owner_auth_size == 0);
/* For backwards-compatibility, if no READ or WRITE permissions are set,
* assume readable/writeable with empty auth value.
*/
if (!(perm & TPMA_NV_MASK_WRITE))
perm |= TPMA_NV_AUTHWRITE;
if (!(perm & TPMA_NV_MASK_READ))
perm |= TPMA_NV_AUTHREAD;
memset(&define_space, 0, sizeof(define_space));
define_space.publicInfo.nvIndex = HR_NV_INDEX + index;
define_space.publicInfo.dataSize = size;
define_space.publicInfo.attributes = perm;
define_space.publicInfo.nameAlg = TPM_ALG_SHA256;
if (auth_policy && auth_policy_size > 0) {
define_space.publicInfo.authPolicy.size = auth_policy_size;
define_space.publicInfo.authPolicy.buffer =
(uint8_t*) auth_policy;
}
return tpm_get_response_code(TPM2_NV_DefineSpace, &define_space);
}
uint32_t TlclInitNvAuthPolicy(uint32_t pcr_selection_bitmap,
const uint8_t pcr_values[][TPM_PCR_DIGEST],
void* auth_policy, uint32_t* auth_policy_size)
{
/* Actual PCR selection isn't implemented. */
VB2_ASSERT(pcr_selection_bitmap == 0);
*auth_policy_size = 0;
return TPM_SUCCESS;
}
/**
* Issue a ForceClear. The TPM error code is returned.
*/
uint32_t TlclForceClear(void)
{
return tpm_get_response_code(TPM2_Clear, NULL);
}
uint32_t TlclSetDeactivated(uint8_t flag)
{
VB2_DEBUG("NOT YET IMPLEMENTED\n");
return TPM_SUCCESS;
}
uint32_t TlclSetEnable(void)
{
VB2_DEBUG("NOT YET IMPLEMENTED\n");
return TPM_SUCCESS;
}
uint32_t TlclGetFlags(uint8_t* disable,
uint8_t* deactivated,
uint8_t *nvlocked)
{
/* For TPM2 the flags are always the same */
if (disable)
*disable = 0;
if (deactivated)
*deactivated = 0;
if (nvlocked)
*nvlocked = 1;
return TPM_SUCCESS;
}
int TlclIsOwned(void)
{
VB2_DEBUG("NOT YET IMPLEMENTED\n");
return 0;
}
uint32_t TlclExtend(int pcr_num, const uint8_t *in_digest, uint8_t *out_digest)
{
VB2_DEBUG("NOT YET IMPLEMENTED\n");
return TPM_SUCCESS;
}
static uint32_t tlcl_nv_read_public(uint32_t index,
struct nv_read_public_response **presp)
{
struct tpm2_response *response = &tpm2_resp;
struct tpm2_nv_read_public_cmd read_pub;
uint32_t rv;
memset(&read_pub, 0, sizeof(read_pub));
read_pub.nvIndex = HR_NV_INDEX + index;
rv = tpm_send_receive(TPM2_NV_ReadPublic, &read_pub, response);
if (rv == TPM_SUCCESS)
*presp = &response->nv_read_public;
return rv;
}
/**
* Get the permission bits for the NVRAM space with |index|.
*/
uint32_t TlclGetPermissions(uint32_t index, uint32_t *permissions)
{
uint32_t rv;
struct nv_read_public_response *resp;
rv = tlcl_nv_read_public(index, &resp);
if (rv == TPM_SUCCESS)
*permissions = resp->nvPublic.attributes;
return rv;
}
uint32_t TlclGetSpaceInfo(uint32_t index, uint32_t *attributes, uint32_t *size,
void* auth_policy, uint32_t* auth_policy_size)
{
uint32_t rv;
struct nv_read_public_response *resp;
rv = tlcl_nv_read_public(index, &resp);
if (rv != TPM_SUCCESS)
return rv;
*attributes = resp->nvPublic.attributes;
*size = resp->nvPublic.dataSize;
if (resp->nvPublic.authPolicy.size > *auth_policy_size) {
return TPM_E_BUFFER_SIZE;
}
*auth_policy_size = resp->nvPublic.authPolicy.size;
memcpy(auth_policy, resp->nvPublic.authPolicy.buffer,
*auth_policy_size);
return TPM_SUCCESS;
}
static uint32_t tlcl_get_capability(TPM_CAP cap, TPM_PT property,
struct get_capability_response **presp)
{
struct tpm2_response *response = &tpm2_resp;
struct tpm2_get_capability_cmd getcap;
uint32_t rv;
getcap.capability = cap;
getcap.property = property;
getcap.property_count = 1;
rv = tpm_send_receive(TPM2_GetCapability, &getcap, response);
if (rv == TPM_SUCCESS)
*presp = &response->cap;
return rv;
}
static uint32_t tlcl_get_tpm_property(TPM_PT property, uint32_t *pvalue)
{
uint32_t rv;
struct get_capability_response *resp;
TPML_TAGGED_TPM_PROPERTY *tpm_prop;
rv = tlcl_get_capability(TPM_CAP_TPM_PROPERTIES, property, &resp);
if (rv != TPM_SUCCESS)
return rv;
if (resp->capability_data.capability != TPM_CAP_TPM_PROPERTIES)
return TPM_E_IOERROR;
tpm_prop = &resp->capability_data.data.tpm_properties;
if ((tpm_prop->count != 1) ||
(tpm_prop->tpm_property[0].property != property))
return TPM_E_IOERROR;
*pvalue = tpm_prop->tpm_property[0].value;
return TPM_SUCCESS;
}
uint32_t TlclGetPermanentFlags(TPM_PERMANENT_FLAGS *pflags)
{
return tlcl_get_tpm_property(TPM_PT_PERMANENT,
(uint32_t *)pflags);
}
uint32_t TlclGetSTClearFlags(TPM_STCLEAR_FLAGS *pflags)
{
return tlcl_get_tpm_property(TPM_PT_STARTUP_CLEAR,
(uint32_t *)pflags);
}
uint32_t TlclGetOwnership(uint8_t *owned)
{
uint32_t rv;
TPM_PERMANENT_FLAGS flags;
*owned = 0;
rv = TlclGetPermanentFlags(&flags);
if (rv == TPM_SUCCESS)
*owned = flags.ownerAuthSet;
return rv;
}
static uint32_t tlcl_lock_nv_write(uint32_t index)
{
struct tpm2_nv_write_lock_cmd nv_wl;
nv_wl.nvIndex = HR_NV_INDEX + index;
return tpm_get_response_code(TPM2_NV_WriteLock, &nv_wl);
}
static uint32_t tlcl_disable_platform_hierarchy(void)
{
struct tpm2_hierarchy_control_cmd hc;
uint32_t rv;
hc.enable = TPM_RH_PLATFORM;
hc.state = 0;
rv = tpm_get_response_code(TPM2_Hierarchy_Control, &hc);
if (rv == TPM_SUCCESS)
tpm_set_ph_disabled(1);
return rv;
}
/**
* The name of the function was kept to maintain the existing TPM API, but
* TPM2.0 does not use the global lock to protect the FW rollback counter.
* Instead it calls WriteLock for the FW NVRAM index to prevent future
* writes to it.
*
* It first checks if the platform hierarchy is already disabled, and does
* nothing, if so. Otherwise, WriteLock for the index obviously fails.
*/
uint32_t TlclSetGlobalLock(void)
{
if (tpm_is_ph_disabled())
return TPM_SUCCESS;
else
return tlcl_lock_nv_write(FIRMWARE_NV_INDEX);
}
/**
* Turn off physical presence and locks it off until next reboot. The TPM
* error code is returned.
*
* The name of the function was kept to maintain the existing TPM API, but
* TPM2.0 does not have to use the Physical Presence concept. Instead it just
* removes platform authorization - this makes sure that firmware and kernel
* rollback counter spaces can not be modified.
*
* It also explicitly locks the kernel rollback counter space (the FW rollback
* counter space was locked before RW firmware started.)
*/
uint32_t TlclLockPhysicalPresence(void)
{
if (tpm_is_ph_disabled())
return TPM_SUCCESS;
return tlcl_disable_platform_hierarchy();
}
uint32_t TlclRead(uint32_t index, void* data, uint32_t length)
{
struct tpm2_nv_read_cmd nv_readc;
struct tpm2_response *response = &tpm2_resp;
uint32_t rv;
memset(&nv_readc, 0, sizeof(nv_readc));
nv_readc.nvIndex = HR_NV_INDEX + index;
nv_readc.size = length;
rv = tpm_send_receive(TPM2_NV_Read, &nv_readc, response);
/* Need to map tpm error codes into internal values. */
switch (rv) {
case TPM_SUCCESS:
break;
case 0x28b:
return TPM_E_BADINDEX;
default:
return rv;
}
if (length > response->nvr.buffer.t.size)
return TPM_E_RESPONSE_TOO_LARGE;
if (length < response->nvr.buffer.t.size)
return TPM_E_READ_EMPTY;
memcpy(data, response->nvr.buffer.t.buffer, length);
return TPM_SUCCESS;
}
uint32_t TlclWrite(uint32_t index, const void *data, uint32_t length)
{
struct tpm2_nv_write_cmd nv_writec;
memset(&nv_writec, 0, sizeof(nv_writec));
nv_writec.nvIndex = HR_NV_INDEX + index;
nv_writec.data.t.size = length;
nv_writec.data.t.buffer = data;
return tpm_get_response_code(TPM2_NV_Write, &nv_writec);
}
uint32_t TlclPCRRead(uint32_t index, void *data, uint32_t length)
{
VB2_DEBUG("NOT YET IMPLEMENTED\n");
return TPM_SUCCESS;
}
uint32_t TlclWriteLock(uint32_t index)
{
struct tpm2_nv_write_lock_cmd nv_writelockc;
memset(&nv_writelockc, 0, sizeof(nv_writelockc));
nv_writelockc.nvIndex = HR_NV_INDEX | index;
return tpm_get_response_code(TPM2_NV_WriteLock, &nv_writelockc);
}
uint32_t TlclReadLock(uint32_t index)
{
struct tpm2_nv_read_lock_cmd nv_readlockc;
memset(&nv_readlockc, 0, sizeof(nv_readlockc));
nv_readlockc.nvIndex = HR_NV_INDEX | index;
return tpm_get_response_code(TPM2_NV_ReadLock, &nv_readlockc);
}
uint32_t TlclGetRandom(uint8_t *data, uint32_t length, uint32_t *size)
{
uint32_t rv;
struct tpm2_get_random_cmd random;
struct get_random_response *response = &tpm2_resp.random;
size_t max_len, offset;
offset = offsetof(struct tpm2_response, random.random_bytes.buffer);
max_len = TPM_BUFFER_SIZE - offset;
if (length > max_len)
return TPM_E_BUFFER_SIZE;
random.bytes_requested = length;
rv = tpm_send_receive(TPM2_GetRandom, &random, &tpm2_resp);
if (rv != TPM_SUCCESS)
return rv;
*size = response->random_bytes.size;
if (*size > length)
return TPM_E_RESPONSE_TOO_LARGE;
memcpy(data, response->random_bytes.buffer, *size);
return rv;
}
// Converts TPM_PT_VENDOR_STRING_x |value| to an array of bytes in |buf|.
// Returns the number of bytes in the array.
// |buf| should be at least 4 bytes long.
static size_t tlcl_vendor_string_parse(uint32_t value, uint8_t* buf)
{
size_t len = 0;
int shift = 24;
for (; len < 4; shift -= 8) {
uint8_t byte = (value >> shift) & 0xffu;
if (!byte)
break;
buf[len++] = byte;
}
return len;
}
uint32_t TlclGetVersion(uint32_t* vendor, uint64_t* firmware_version,
uint8_t* vendor_specific_buf,
size_t* vendor_specific_buf_size)
{
uint32_t result = tlcl_get_tpm_property(TPM_PT_MANUFACTURER, vendor);
if (result != TPM_SUCCESS)
return result;
uint32_t version_1;
uint32_t version_2;
result = tlcl_get_tpm_property(TPM_PT_FIRMWARE_VERSION_1, &version_1);
if (result != TPM_SUCCESS)
return result;
result = tlcl_get_tpm_property(TPM_PT_FIRMWARE_VERSION_2, &version_2);
if (result != TPM_SUCCESS)
return result;
*firmware_version = ((uint64_t) version_1 << 32) | version_2;
if (!vendor_specific_buf_size)
return TPM_SUCCESS;
size_t total_size = 0;
uint32_t prop_id;
uint8_t prop_string[16];
for (prop_id = TPM_PT_VENDOR_STRING_1;
prop_id <= TPM_PT_VENDOR_STRING_4;
++prop_id) {
uint32_t prop_value;
result = tlcl_get_tpm_property(prop_id, &prop_value);
if (result != TPM_SUCCESS)
break;
size_t prop_len = tlcl_vendor_string_parse(
prop_value, prop_string + total_size);
VB2_ASSERT(prop_len <= 4 &&
total_size + prop_len <= sizeof(prop_string));
total_size += prop_len;
if (prop_len < 4)
break;
}
if (vendor_specific_buf) {
if (total_size > *vendor_specific_buf_size)
total_size = *vendor_specific_buf_size;
memcpy(vendor_specific_buf, prop_string, total_size);
}
*vendor_specific_buf_size = total_size;
return TPM_SUCCESS;
}
uint32_t TlclIFXFieldUpgradeInfo(TPM_IFX_FIELDUPGRADEINFO* info)
{
VB2_DEBUG("NOT YET IMPLEMENTED\n");
return TPM_E_IOERROR;
}