/* Copyright 2016 The ChromiumOS Authors * 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) { struct tpm2_pcr_extend_cmd pcr_ext_cmd; pcr_ext_cmd.pcrHandle = HR_PCR + pcr_num; pcr_ext_cmd.digests.count = 1; pcr_ext_cmd.digests.digests[0].hashAlg = TPM_ALG_SHA256; memcpy(pcr_ext_cmd.digests.digests[0].digest.sha256, in_digest, sizeof(pcr_ext_cmd.digests.digests[0].digest.sha256)); return tpm_get_response_code(TPM2_PCR_Extend, &pcr_ext_cmd); } 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; /* * 0x14a = RC_NV_UNINITIALIZED (space created but not written) * 0x28b = RC_HANDLE(2) ("unknown handle" = space does not exist) */ case 0x14a: case 0x28b: return TPM_E_BADINDEX; default: return rv; } if (length > response->nvr.buffer.size) return TPM_E_RESPONSE_TOO_LARGE; if (length < response->nvr.buffer.size) return TPM_E_READ_EMPTY; memcpy(data, response->nvr.buffer.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.size = length; nv_writec.data.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; } uint32_t TlclReadPublic(uint32_t handle, uint8_t *data, uint32_t *length) { struct tpm2_read_public_cmd cmd; struct tpm2_response *response = &tpm2_resp; uint32_t rv; memset(&cmd, 0, sizeof(cmd)); cmd.object_handle = handle; rv = tpm_send_receive(TPM2_ReadPublic, &cmd, response); /* Need to map tpm error codes into internal values. */ switch (rv) { case TPM_SUCCESS: break; case 0x8b: case 0x18b: return TPM_E_BADINDEX; default: return rv; } if (*length < response->read_pub.buffer.size) return TPM_E_RESPONSE_TOO_LARGE; *length = response->read_pub.buffer.size; memcpy(data, response->read_pub.buffer.buffer, response->read_pub.buffer.size); return TPM_SUCCESS; } uint32_t TlclEvictControl(uint32_t auth_handle, uint32_t object_handle, uint32_t persistent_handle) { struct tpm2_evict_control_cmd cmd; memset(&cmd, 0, sizeof(cmd)); cmd.auth = auth_handle; cmd.object_handle = object_handle; cmd.persistent_handle = persistent_handle; return tpm_get_response_code(TPM2_EvictControl, &cmd); } uint32_t TlclCreatePrimary(uint32_t primary_handle, const void *tmpl, uint32_t tmpl_length, uint32_t *object_handle) { uint32_t rv; struct tpm2_create_primary_cmd cmd; struct tpm2_response *response = &tpm2_resp; uint8_t in_sensitive[4] = {0, 0, 0, 0}; memset(&cmd, 0, sizeof(cmd)); cmd.primary_handle = primary_handle; cmd.in_sensitive.size = sizeof(in_sensitive); cmd.in_sensitive.buffer = in_sensitive; cmd.in_public.size = tmpl_length; cmd.in_public.buffer = tmpl; rv = tpm_send_receive(TPM2_CreatePrimary, &cmd, response); if (rv) return rv; *object_handle = response->create_primary.object_handle; return TPM_SUCCESS; }