/* Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* A lightweight TPM command library. * * The general idea is that TPM commands are array of bytes whose * fields are mostly compile-time constant. The goal is to build much * of the commands at compile time (or build time) and change some of * the fields at run time as needed. The code in * utility/tlcl_generator.c builds structures containing the commands, * as well as the offsets of the fields that need to be set at run * time. */ #include "2common.h" #include "2hmac.h" #include "2sha.h" #include "2sysincludes.h" #include "tlcl.h" #include "tlcl_internal.h" #include "tlcl_structures.h" /* Sets the size field of a TPM command. */ static inline void SetTpmCommandSize(uint8_t* buffer, uint32_t size) { ToTpmUint32(buffer + sizeof(uint16_t), size); } /* Gets the size field of a TPM command. */ __attribute__((unused)) static inline int TpmCommandSize(const uint8_t* buffer) { uint32_t size; FromTpmUint32(buffer + sizeof(uint16_t), &size); return (int) size; } /* Gets the size field of a TPM request or response. */ int TlclPacketSize(const uint8_t* packet) { return TpmCommandSize(packet); } /* Gets the code field of a TPM command. */ static inline int TpmCommandCode(const uint8_t* buffer) { uint32_t code; FromTpmUint32(buffer + sizeof(uint16_t) + sizeof(uint32_t), &code); return code; } /* Gets the return code field of a TPM result. */ static inline int TpmReturnCode(const uint8_t* buffer) { return TpmCommandCode(buffer); } /* Like TlclSendReceive below, but do not retry if NEEDS_SELFTEST or * DOING_SELFTEST errors are returned. */ static uint32_t TlclSendReceiveNoRetry(const uint8_t* request, uint8_t* response, int max_length) { uint32_t response_length = max_length; uint32_t result; #ifdef EXTRA_LOGGING VB2_DEBUG("TPM: command: %x%x %x%x%x%x %x%x%x%x\n", request[0], request[1], request[2], request[3], request[4], request[5], request[6], request[7], request[8], request[9]); #endif result = vb2ex_tpm_send_recv(request, TpmCommandSize(request), response, &response_length); if (TPM_SUCCESS != result) { /* Communication with TPM failed, so response is garbage */ VB2_DEBUG("TPM: command %#x send/receive failed: %#x\n", TpmCommandCode(request), result); return result; } /* Otherwise, use the result code from the response */ result = TpmReturnCode(response); /* TODO: add paranoia about returned response_length vs. max_length * (and possibly expected length from the response header). See * crosbug.com/17017 */ #ifdef EXTRA_LOGGING VB2_DEBUG("TPM: response: %x%x %x%x%x%x %x%x%x%x\n", response[0], response[1], response[2], response[3], response[4], response[5], response[6], response[7], response[8], response[9]); #endif VB2_DEBUG("TPM: command %#x returned %#x\n", TpmCommandCode(request), result); return result; } /* Sends a TPM command and gets a response. Returns 0 if success or the TPM * error code if error. In the firmware, waits for the self test to complete * if needed. In the host, reports the first error without retries. */ uint32_t TlclSendReceive(const uint8_t* request, uint8_t* response, int max_length) { uint32_t result = TlclSendReceiveNoRetry(request, response, max_length); /* When compiling for the firmware, hide command failures due to the * self test not having run or completed. */ #ifndef CHROMEOS_ENVIRONMENT /* If the command fails because the self test has not completed, try it * again after attempting to ensure that the self test has completed. */ if (result == TPM_E_NEEDS_SELFTEST || result == TPM_E_DOING_SELFTEST) { result = TlclContinueSelfTest(); if (result != TPM_SUCCESS) { return result; } #if defined(TPM_BLOCKING_CONTINUESELFTEST) || defined(VB_RECOVERY_MODE) /* Retry only once */ result = TlclSendReceiveNoRetry(request, response, max_length); #else /* This needs serious testing. The TPM specification says: * "iii. The caller MUST wait for the actions of * TPM_ContinueSelfTest to complete before reissuing the * command C1." But, if ContinueSelfTest is non-blocking, how * do we know that the actions have completed other than trying * again? */ do { result = TlclSendReceiveNoRetry(request, response, max_length); } while (result == TPM_E_DOING_SELFTEST); #endif } #endif /* ! defined(CHROMEOS_ENVIRONMENT) */ return result; } /* Sends a command and returns the error code. */ static uint32_t Send(const uint8_t* command) { uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; return TlclSendReceive(command, response, sizeof(response)); } #ifdef CHROMEOS_ENVIRONMENT struct auth_session { uint32_t handle; TPM_NONCE nonce_even; TPM_NONCE nonce_odd; uint8_t shared_secret[TPM_AUTH_DATA_LEN]; uint8_t valid; }; static uint32_t StartOIAPSession(struct auth_session* session, const uint8_t secret[TPM_AUTH_DATA_LEN]) { session->valid = 0; uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; uint32_t result = TlclSendReceive(tpm_oiap_cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) { return result; } uint32_t size; FromTpmUint32(response + sizeof(uint16_t), &size); if (size < kTpmResponseHeaderLength + sizeof(uint32_t) + sizeof(TPM_NONCE)) { return TPM_E_INVALID_RESPONSE; } const uint8_t* cursor = response + kTpmResponseHeaderLength; session->handle = ReadTpmUint32(&cursor); memcpy(session->nonce_even.nonce, cursor, sizeof(TPM_NONCE)); cursor += sizeof(TPM_NONCE); VB2_ASSERT(cursor - response <= TPM_LARGE_ENOUGH_COMMAND_SIZE); memcpy(session->shared_secret, secret, TPM_AUTH_DATA_LEN); session->valid = 1; return result; } static uint32_t StartOSAPSession( struct auth_session* session, uint16_t entity_type, uint32_t entity_value, const uint8_t entity_usage_auth[TPM_AUTH_DATA_LEN]) { session->valid = 0; /* Build OSAP command. */ struct s_tpm_osap_cmd cmd; memcpy(&cmd, &tpm_osap_cmd, sizeof(cmd)); ToTpmUint16(cmd.buffer + cmd.entityType, entity_type); ToTpmUint32(cmd.buffer + cmd.entityValue, entity_value); if (vb2ex_tpm_get_random(cmd.buffer + cmd.nonceOddOSAP, sizeof(TPM_NONCE)) != VB2_SUCCESS) { return TPM_E_INTERNAL_ERROR; } /* Send OSAP command. */ uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; uint32_t result = TlclSendReceive(cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) { return result; } /* Parse response. */ uint32_t size; FromTpmUint32(response + sizeof(uint16_t), &size); if (size < kTpmResponseHeaderLength + sizeof(uint32_t) + 2 * sizeof(TPM_NONCE)) { return TPM_E_INVALID_RESPONSE; } const uint8_t* cursor = response + kTpmResponseHeaderLength; session->handle = ReadTpmUint32(&cursor); memcpy(session->nonce_even.nonce, cursor, sizeof(TPM_NONCE)); cursor += sizeof(TPM_NONCE); const uint8_t* nonce_even_osap = cursor; cursor += sizeof(TPM_NONCE); VB2_ASSERT(cursor - response <= TPM_LARGE_ENOUGH_COMMAND_SIZE); /* Compute shared secret */ uint8_t hmac_input[2 * sizeof(TPM_NONCE)]; memcpy(hmac_input, nonce_even_osap, sizeof(TPM_NONCE)); memcpy(hmac_input + sizeof(TPM_NONCE), cmd.buffer + cmd.nonceOddOSAP, sizeof(TPM_NONCE)); struct vb2_hash mac; if (vb2_hmac_calculate(false, VB2_HASH_SHA1, entity_usage_auth, TPM_AUTH_DATA_LEN, hmac_input, sizeof(hmac_input), &mac)) { return TPM_E_INTERNAL_ERROR; } _Static_assert(sizeof(session->shared_secret) == VB2_SHA1_DIGEST_SIZE, "The output size should match the sha1 digest size."); memcpy(session->shared_secret, mac.raw, sizeof(session->shared_secret)); session->valid = 1; return result; } /* Fills in the authentication block at the end of the command. The command body * should already be initialized in |command_buffer|, and the included command * size should account for the auth block that gets filled in. */ static uint32_t AddRequestAuthBlock(struct auth_session* auth_session, uint8_t* command_buffer, uint32_t command_buffer_size, uint8_t continue_auth_session) { if (!auth_session->valid) { return TPM_E_AUTHFAIL; } /* Validity check to make sure the command buffer has sufficient space * to add the auth block at the end of the command. */ if (command_buffer_size < kTpmRequestHeaderLength) { return TPM_E_BUFFER_SIZE; } const uint32_t size = TpmCommandSize(command_buffer); if (size < kTpmResponseHeaderLength + kTpmRequestAuthBlockLength || size > command_buffer_size) { return TPM_E_INTERNAL_ERROR; } const uint32_t auth_offset = size - kTpmRequestAuthBlockLength; /* * The digest of the command is computed over the command buffer, but * excluding the leading tag and paramSize fields. */ struct vb2_sha1_context sha1_ctx; vb2_sha1_init(&sha1_ctx); vb2_sha1_update(&sha1_ctx, command_buffer + sizeof(uint16_t) + sizeof(uint32_t), auth_offset - sizeof(uint16_t) - sizeof(uint32_t)); uint8_t buf[TPM_SHA1_160_HASH_LEN + 2 * sizeof(TPM_NONCE) + 1]; vb2_sha1_finalize(&sha1_ctx, buf); /* Generate a fresh nonce. */ if (vb2ex_tpm_get_random(auth_session->nonce_odd.nonce, sizeof(TPM_NONCE)) != VB2_SUCCESS) { return TPM_E_INTERNAL_ERROR; } /* Append the authentication block to the command buffer. */ uint8_t* cursor = command_buffer + auth_offset; ToTpmUint32(cursor, auth_session->handle); cursor += sizeof(uint32_t); memcpy(cursor, auth_session->nonce_odd.nonce, sizeof(TPM_NONCE)); cursor += sizeof(TPM_NONCE); *cursor++ = continue_auth_session; /* Compute and append the MAC. */ memcpy(buf + TPM_SHA1_160_HASH_LEN, auth_session->nonce_even.nonce, sizeof(TPM_NONCE)); memcpy(buf + TPM_SHA1_160_HASH_LEN + sizeof(TPM_NONCE), auth_session->nonce_odd.nonce, sizeof(TPM_NONCE)); buf[TPM_SHA1_160_HASH_LEN + 2 * sizeof(TPM_NONCE)] = continue_auth_session; struct vb2_hash mac; if (vb2_hmac_calculate(false, VB2_HASH_SHA1, auth_session->shared_secret, sizeof(auth_session->shared_secret), buf, sizeof(buf), &mac)) { return TPM_E_AUTHFAIL; } memcpy(cursor, mac.sha1, sizeof(mac.sha1)); cursor += sizeof(mac.sha1); return TPM_SUCCESS; } static uint32_t CheckResponseAuthBlock(struct auth_session* auth_session, TPM_COMMAND_CODE ordinal, uint8_t* response_buffer, uint32_t response_buffer_size) { if (!auth_session->valid) { return TPM_E_AUTHFAIL; } if (response_buffer_size < kTpmResponseHeaderLength) { return TPM_E_INVALID_RESPONSE; } /* Parse and validate the actual response size from the response. */ uint32_t size; FromTpmUint32(response_buffer + sizeof(uint16_t), &size); if (size >= response_buffer_size || size < kTpmResponseHeaderLength + kTpmResponseAuthBlockLength) { return TPM_E_INVALID_RESPONSE; } uint32_t auth_offset = size - kTpmResponseAuthBlockLength; /* * The digest of the response is computed over the return code, ordinal, * response payload. */ struct vb2_sha1_context sha1_ctx; vb2_sha1_init(&sha1_ctx); vb2_sha1_update(&sha1_ctx, response_buffer + sizeof(uint16_t) + sizeof(uint32_t), sizeof(uint32_t)); uint8_t ordinal_buf[sizeof(ordinal)]; ToTpmUint32(ordinal_buf, ordinal); vb2_sha1_update(&sha1_ctx, ordinal_buf, sizeof(ordinal_buf)); vb2_sha1_update(&sha1_ctx, response_buffer + kTpmResponseHeaderLength, auth_offset - kTpmResponseHeaderLength); uint8_t hmac_input[TPM_SHA1_160_HASH_LEN + 2 * sizeof(TPM_NONCE) + 1]; vb2_sha1_finalize(&sha1_ctx, hmac_input); /* Compute the MAC. */ uint8_t* cursor = response_buffer + auth_offset; memcpy(hmac_input + TPM_SHA1_160_HASH_LEN, cursor, sizeof(TPM_NONCE)); cursor += sizeof(TPM_NONCE); memcpy(hmac_input + TPM_SHA1_160_HASH_LEN + sizeof(TPM_NONCE), auth_session->nonce_odd.nonce, sizeof(TPM_NONCE)); auth_session->valid = *cursor++; hmac_input[TPM_SHA1_160_HASH_LEN + 2 * sizeof(TPM_NONCE)] = auth_session->valid; struct vb2_hash mac; if (vb2_hmac_calculate(false, VB2_HASH_SHA1, auth_session->shared_secret, sizeof(auth_session->shared_secret), hmac_input, sizeof(hmac_input), &mac)) { auth_session->valid = 0; return TPM_E_AUTHFAIL; } /* Check the MAC. */ if (vb2_safe_memcmp(mac.sha1, cursor, sizeof(mac.sha1))) { auth_session->valid = 0; return TPM_E_AUTHFAIL; } /* Success, save the even nonce. */ memcpy(auth_session->nonce_even.nonce, response_buffer + auth_offset, sizeof(TPM_NONCE)); return TPM_SUCCESS; } #endif /* CHROMEOS_ENVIRONMENT */ /* Exported functions. */ uint32_t TlclLibInit(void) { return vb2ex_tpm_init(); } uint32_t TlclLibClose(void) { return vb2ex_tpm_close(); } uint32_t TlclStartup(void) { VB2_DEBUG("TPM: Startup\n"); return Send(tpm_startup_cmd.buffer); } uint32_t TlclSaveState(void) { VB2_DEBUG("TPM: SaveState\n"); return Send(tpm_savestate_cmd.buffer); } uint32_t TlclResume(void) { VB2_DEBUG("TPM: Resume\n"); return Send(tpm_resume_cmd.buffer); } uint32_t TlclSelfTestFull(void) { VB2_DEBUG("TPM: Self test full\n"); return Send(tpm_selftestfull_cmd.buffer); } uint32_t TlclContinueSelfTest(void) { uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; VB2_DEBUG("TPM: Continue self test\n"); /* Call the No Retry version of SendReceive to avoid recursion. */ return TlclSendReceiveNoRetry(tpm_continueselftest_cmd.buffer, response, sizeof(response)); } uint32_t TlclDefineSpace(uint32_t index, uint32_t perm, uint32_t size) { VB2_DEBUG("TPM: TlclDefineSpace(%#x, %#x, %d)\n", index, perm, size); return TlclDefineSpaceEx(NULL, 0, index, perm, size, NULL, 0); } #ifdef CHROMEOS_ENVIRONMENT uint32_t TlclUndefineSpace(uint32_t index) { VB2_DEBUG("TPM: TlclUndefineSpace(%#x)\n", index); return TlclUndefineSpaceEx(NULL, 0, index); } uint32_t TlclUndefineSpaceEx(const uint8_t* owner_auth, uint32_t owner_auth_size, uint32_t index) { return TlclDefineSpaceEx(owner_auth, owner_auth_size, index, 0, 0, NULL, 0); } #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) { uint32_t result; /* Build the request data. */ uint8_t cmd[sizeof(tpm_nv_definespace_cmd.buffer) + kTpmRequestAuthBlockLength]; memcpy(cmd, &tpm_nv_definespace_cmd, sizeof(tpm_nv_definespace_cmd)); ToTpmUint32(cmd + tpm_nv_definespace_cmd.index, index); ToTpmUint32(cmd + tpm_nv_definespace_cmd.perm, perm); ToTpmUint32(cmd + tpm_nv_definespace_cmd.size, size); if (auth_policy != NULL) { if (auth_policy_size != sizeof(TPM_NV_AUTH_POLICY)) { return TPM_E_BUFFER_SIZE; } const TPM_NV_AUTH_POLICY* policy = auth_policy; memcpy(cmd + tpm_nv_definespace_cmd.pcr_info_read, &policy->pcr_info_read, sizeof(policy->pcr_info_read)); memcpy(cmd + tpm_nv_definespace_cmd.pcr_info_write, &policy->pcr_info_write, sizeof(policy->pcr_info_write)); } #ifdef CHROMEOS_ENVIRONMENT struct auth_session auth_session; if (owner_auth) { if (owner_auth_size != TPM_AUTH_DATA_LEN) { return TPM_E_AUTHFAIL; } result = StartOSAPSession(&auth_session, TPM_ET_OWNER, 0, owner_auth); if (result != TPM_SUCCESS) { return result; } ToTpmUint32(cmd + sizeof(uint16_t), sizeof(cmd)); ToTpmUint16(cmd, TPM_TAG_RQU_AUTH1_COMMAND); result = AddRequestAuthBlock(&auth_session, cmd, sizeof(cmd), 0); if (result != TPM_SUCCESS) { return result; } } #endif /* Send the command. */ uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; result = TlclSendReceive(cmd, response, sizeof(response)); if (result != TPM_SUCCESS) { return result; } #ifdef CHROMEOS_ENVIRONMENT if (owner_auth) { result = CheckResponseAuthBlock(&auth_session, TPM_ORD_NV_DefineSpace, response, sizeof(response)); } #endif return result; } uint32_t TlclInitNvAuthPolicy(uint32_t pcr_selection_bitmap, const uint8_t pcr_values[][TPM_PCR_DIGEST], void* auth_policy, uint32_t* auth_policy_size) { uint32_t buffer_size = *auth_policy_size; *auth_policy_size = sizeof(TPM_NV_AUTH_POLICY); if (!auth_policy || buffer_size < sizeof(TPM_NV_AUTH_POLICY)) { return TPM_E_BUFFER_SIZE; } TPM_NV_AUTH_POLICY* policy = auth_policy; /* Note that although the struct definition allocates space for 3 bytes * worth of PCR selection, it is technically a variably-sized field in * the TPM structure definition. Since this is part of the policy * digest, we need to carefully match our selection sizes. For now, we * use 3 bytes, which aligns with TrouSerS behavior. */ TPM_PCR_SELECTION* select = &policy->pcr_info_read.pcrSelection; ToTpmUint16((uint8_t*)&select->sizeOfSelect, sizeof(select->pcrSelect)); select->pcrSelect[0] = (pcr_selection_bitmap >> 0) & 0xff; select->pcrSelect[1] = (pcr_selection_bitmap >> 8) & 0xff; select->pcrSelect[2] = (pcr_selection_bitmap >> 16) & 0xff; VB2_ASSERT((pcr_selection_bitmap & 0xff000000) == 0); /* Allow all localities except locality 3. Rationale: * * We don't actually care about restricting NVRAM access to specific * localities. In fact localities aren't used in Chrome OS and locality * 0 is used for everything. * * However, the TPM specification makes an effort to not allow NVRAM * spaces that do not have some write access control configured: When * defining a space, either at least one of OWNERWRITE, AUTHWRITE, * WRITEDEFINE, PPWRITE or a locality restriction must be specified (See * TPM_NV_DefineSpace command description in the spec). * * This complicates matters when defining an NVRAM space that should be * writable and lockable (for the remainder of the boot cycle) by the OS * even when the TPM is not owned: * * OWNERWRITE doesn't work because there might be no owner. * * PPWRITE restricts writing to firmware only. * * Use of WRITEDEFINE prevents use of WRITE_STCLEAR (i.e. the space * can either not be locked, or it would remain locked until next TPM * clear). * * AUTHWRITE looks workable at first sight (by setting a well-known * auth secret). However writes must use TPM_NV_WriteValueAuth, which * only works when the TPM is owned. Interestingly, the spec admits * to that being a mistake in the TPM_NV_WriteValueAuth comment but * asserts that the behavior is normative. * * Having ruled out all attributes, we're left with locality restriction * as the only option to pass the access control requirement check in * TPM_NV_DefineSpace. We choose to disallow locality 3, since that is * the most unlikely one to be used in practice, i.e. the spec says * locality 3 is for "Auxiliary components. Use of this is optional and, * if used, it is implementation dependent." */ policy->pcr_info_read.localityAtRelease = TPM_ALL_LOCALITIES & ~TPM_LOC_THREE; struct vb2_sha1_context sha1_ctx; vb2_sha1_init(&sha1_ctx); vb2_sha1_update(&sha1_ctx, (const uint8_t*)&policy->pcr_info_read.pcrSelection, sizeof(policy->pcr_info_read.pcrSelection)); uint32_t num_pcrs = 0; int i; for (i = 0; i < sizeof(pcr_selection_bitmap) * 8; ++i) { if ((1U << i) & pcr_selection_bitmap) { num_pcrs++; } } uint8_t pcrs_size[sizeof(uint32_t)]; ToTpmUint32(pcrs_size, num_pcrs * TPM_PCR_DIGEST); vb2_sha1_update(&sha1_ctx, pcrs_size, sizeof(pcrs_size)); for (i = 0; i < num_pcrs; ++i) { vb2_sha1_update(&sha1_ctx, pcr_values[i], TPM_PCR_DIGEST); } vb2_sha1_finalize(&sha1_ctx, policy->pcr_info_read.digestAtRelease.digest); /* Make the write policy an identical copy of the read auth policy. */ memcpy(&policy->pcr_info_write, &policy->pcr_info_read, sizeof(policy->pcr_info_read)); return TPM_SUCCESS; } uint32_t TlclWrite(uint32_t index, const void* data, uint32_t length) { struct s_tpm_nv_write_cmd cmd; uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; const int total_length = kTpmRequestHeaderLength + kWriteInfoLength + length; VB2_DEBUG("TPM: TlclWrite(%#x, %d)\n", index, length); memcpy(&cmd, &tpm_nv_write_cmd, sizeof(cmd)); VB2_ASSERT(total_length <= TPM_LARGE_ENOUGH_COMMAND_SIZE); SetTpmCommandSize(cmd.buffer, total_length); ToTpmUint32(cmd.buffer + tpm_nv_write_cmd.index, index); ToTpmUint32(cmd.buffer + tpm_nv_write_cmd.length, length); memcpy(cmd.buffer + tpm_nv_write_cmd.data, data, length); return TlclSendReceive(cmd.buffer, response, sizeof(response)); } uint32_t TlclRead(uint32_t index, void* data, uint32_t length) { struct s_tpm_nv_read_cmd cmd; uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; uint32_t result; VB2_DEBUG("TPM: TlclRead(%#x, %d)\n", index, length); memcpy(&cmd, &tpm_nv_read_cmd, sizeof(cmd)); ToTpmUint32(cmd.buffer + tpm_nv_read_cmd.index, index); ToTpmUint32(cmd.buffer + tpm_nv_read_cmd.length, length); result = TlclSendReceive(cmd.buffer, response, sizeof(response)); if (result == TPM_SUCCESS && length > 0) { const uint8_t* nv_read_cursor = response + kTpmResponseHeaderLength; uint32_t result_length = ReadTpmUint32(&nv_read_cursor); if (result_length > length) result_length = length; /* Truncate to fit buffer */ memcpy(data, nv_read_cursor, result_length); } return result; } uint32_t TlclPCRRead(uint32_t index, void* data, uint32_t length) { struct s_tpm_pcr_read_cmd cmd; uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; uint32_t result; VB2_DEBUG("TPM: TlclPCRRead(%#x, %d)\n", index, length); if (length < kPcrDigestLength) { return TPM_E_IOERROR; } memcpy(&cmd, &tpm_pcr_read_cmd, sizeof(cmd)); ToTpmUint32(cmd.buffer + tpm_pcr_read_cmd.pcrNum, index); result = TlclSendReceive(cmd.buffer, response, sizeof(response)); if (result == TPM_SUCCESS) { const uint8_t* pcr_read_cursor = response + kTpmResponseHeaderLength; memcpy(data, pcr_read_cursor, kPcrDigestLength); } return result; } uint32_t TlclWriteLock(uint32_t index) { VB2_DEBUG("TPM: Write lock %#x\n", index); return TlclWrite(index, NULL, 0); } uint32_t TlclReadLock(uint32_t index) { VB2_DEBUG("TPM: Read lock %#x\n", index); return TlclRead(index, NULL, 0); } uint32_t TlclAssertPhysicalPresence(void) { VB2_DEBUG("TPM: Asserting physical presence\n"); return Send(tpm_ppassert_cmd.buffer); } uint32_t TlclPhysicalPresenceCMDEnable(void) { VB2_DEBUG("TPM: Enable the physical presence command\n"); return Send(tpm_ppenable_cmd.buffer); } uint32_t TlclFinalizePhysicalPresence(void) { VB2_DEBUG("TPM: Enable PP cmd, disable HW pp, and set lifetime lock\n"); return Send(tpm_finalizepp_cmd.buffer); } uint32_t TlclAssertPhysicalPresenceResult(void) { uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; return TlclSendReceive(tpm_ppassert_cmd.buffer, response, sizeof(response)); } uint32_t TlclLockPhysicalPresence(void) { VB2_DEBUG("TPM: Lock physical presence\n"); return Send(tpm_pplock_cmd.buffer); } uint32_t TlclSetNvLocked(void) { VB2_DEBUG("TPM: Set NV locked\n"); return TlclDefineSpace(TPM_NV_INDEX_LOCK, 0, 0); } int TlclIsOwned(void) { uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE + TPM_PUBEK_SIZE]; uint32_t result; result = TlclSendReceive(tpm_readpubek_cmd.buffer, response, sizeof(response)); return (result != TPM_SUCCESS); } uint32_t TlclForceClear(void) { VB2_DEBUG("TPM: Force clear\n"); return Send(tpm_forceclear_cmd.buffer); } uint32_t TlclSetEnable(void) { VB2_DEBUG("TPM: Enabling TPM\n"); return Send(tpm_physicalenable_cmd.buffer); } uint32_t TlclClearEnable(void) { VB2_DEBUG("TPM: Disabling TPM\n"); return Send(tpm_physicaldisable_cmd.buffer); } uint32_t TlclSetDeactivated(uint8_t flag) { struct s_tpm_physicalsetdeactivated_cmd cmd; VB2_DEBUG("TPM: SetDeactivated(%d)\n", flag); memcpy(&cmd, &tpm_physicalsetdeactivated_cmd, sizeof(cmd)); *(cmd.buffer + cmd.deactivated) = flag; return Send(cmd.buffer); } uint32_t TlclGetPermanentFlags(TPM_PERMANENT_FLAGS* pflags) { uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; uint32_t size; uint32_t result = TlclSendReceive(tpm_getflags_cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) return result; FromTpmUint32(response + kTpmResponseHeaderLength, &size); /* TODO(crbug.com/379255): This fails. Find out why. * VB2_ASSERT(size == sizeof(TPM_PERMANENT_FLAGS)); */ memcpy(pflags, response + kTpmResponseHeaderLength + sizeof(size), sizeof(TPM_PERMANENT_FLAGS)); return result; } uint32_t TlclGetSTClearFlags(TPM_STCLEAR_FLAGS* vflags) { uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; uint32_t size; uint32_t result = TlclSendReceive(tpm_getstclearflags_cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) return result; FromTpmUint32(response + kTpmResponseHeaderLength, &size); /* Ugly assertion, but the struct is padded up by one byte. */ /* TODO(crbug.com/379255): This fails. Find out why. * VB2_ASSERT(size == 7 && sizeof(TPM_STCLEAR_FLAGS) - 1 == 7); */ memcpy(vflags, response + kTpmResponseHeaderLength + sizeof(size), sizeof(TPM_STCLEAR_FLAGS)); return result; } uint32_t TlclGetFlags(uint8_t* disable, uint8_t* deactivated, uint8_t *nvlocked) { TPM_PERMANENT_FLAGS pflags; uint32_t result = TlclGetPermanentFlags(&pflags); if (result == TPM_SUCCESS) { if (disable) *disable = pflags.disable; if (deactivated) *deactivated = pflags.deactivated; if (nvlocked) *nvlocked = pflags.nvLocked; VB2_DEBUG("TPM: Got flags disable=%d, deactivated=%d, " "nvlocked=%d\n", pflags.disable, pflags.deactivated, pflags.nvLocked); } return result; } uint32_t TlclSetGlobalLock(void) { uint32_t x; VB2_DEBUG("TPM: Set global lock\n"); return TlclWrite(TPM_NV_INDEX0, (uint8_t*) &x, 0); } uint32_t TlclExtend(int pcr_num, const uint8_t* in_digest, uint8_t* out_digest) { struct s_tpm_extend_cmd cmd; uint8_t response[kTpmResponseHeaderLength + kPcrDigestLength]; uint32_t result; memcpy(&cmd, &tpm_extend_cmd, sizeof(cmd)); ToTpmUint32(cmd.buffer + tpm_extend_cmd.pcrNum, pcr_num); memcpy(cmd.buffer + cmd.inDigest, in_digest, kPcrDigestLength); result = TlclSendReceive(cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) return result; memcpy(out_digest, response + kTpmResponseHeaderLength, kPcrDigestLength); return result; } uint32_t TlclGetPermissions(uint32_t index, uint32_t* permissions) { uint32_t dummy_attributes; TPM_NV_AUTH_POLICY dummy_policy; uint32_t dummy_policy_size = sizeof(dummy_policy); return TlclGetSpaceInfo(index, permissions, &dummy_attributes, &dummy_policy, &dummy_policy_size); } static int DecodePCRInfo(const uint8_t** cursor, const uint8_t* end, TPM_PCR_INFO_SHORT* pcr_info) { const size_t available_size = end - *cursor; if (available_size < sizeof(uint16_t)) { return 0; } uint16_t size_of_select = 0; FromTpmUint16(*cursor, &size_of_select); /* Compute the actual size of the encoded PCR selection (which is a * variable-length field). */ const size_t encoded_pcr_info_size = sizeof(TPM_PCR_INFO_SHORT) - sizeof(pcr_info->pcrSelection.pcrSelect) + size_of_select; if (available_size < encoded_pcr_info_size || size_of_select > sizeof(pcr_info->pcrSelection.pcrSelect)) { return 0; } memset(&pcr_info->pcrSelection, 0, sizeof(pcr_info->pcrSelection)); const size_t pcr_selection_size = sizeof(size_of_select) + size_of_select; memcpy(&pcr_info->pcrSelection, *cursor, pcr_selection_size); *cursor += pcr_selection_size; pcr_info->localityAtRelease = **cursor; (*cursor)++; memcpy(&pcr_info->digestAtRelease, *cursor, sizeof(TPM_COMPOSITE_HASH)); *cursor += sizeof(TPM_COMPOSITE_HASH); return 1; } uint32_t TlclGetSpaceInfo(uint32_t index, uint32_t *attributes, uint32_t *size, void* auth_policy, uint32_t* auth_policy_size) { TPM_NV_AUTH_POLICY* policy; uint32_t buffer_size = *auth_policy_size; *auth_policy_size = sizeof(TPM_NV_AUTH_POLICY); if (buffer_size < sizeof(TPM_NV_AUTH_POLICY)) { return TPM_E_BUFFER_SIZE; } policy = auth_policy; struct s_tpm_getspaceinfo_cmd cmd; memcpy(&cmd, &tpm_getspaceinfo_cmd, sizeof(cmd)); ToTpmUint32(cmd.buffer + tpm_getspaceinfo_cmd.index, index); uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; uint32_t result = TlclSendReceive(cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) { return result; } uint32_t response_size = TpmCommandSize(response); if (response_size > sizeof(response)) { return TPM_E_RESPONSE_TOO_LARGE; } const uint8_t* cursor = response + kTpmResponseHeaderLength; uint32_t cap_size = ReadTpmUint32(&cursor); if (cap_size > response_size - sizeof(cap_size) - kTpmResponseHeaderLength) { return TPM_E_INVALID_RESPONSE; } const uint8_t* end = cursor + cap_size; cursor += sizeof(uint16_t); /* skip tag */ uint32_t response_index = ReadTpmUint32(&cursor); if (index != response_index) { return TPM_E_INVALID_RESPONSE; } if (!DecodePCRInfo(&cursor, end, &policy->pcr_info_read) || !DecodePCRInfo(&cursor, end, &policy->pcr_info_write)) { return TPM_E_INVALID_RESPONSE; } /* Make sure that the remaining data in the buffer matches the size of * the remaining fields to decode. */ if (end - cursor != 2 * sizeof(uint32_t) + 3 * sizeof(uint8_t) + sizeof(uint16_t)) { return TPM_E_INVALID_RESPONSE; } cursor += sizeof(uint16_t); /* skip TPM_NV_ATTRIBUTES tag */ *attributes = ReadTpmUint32(&cursor); cursor += sizeof(uint8_t); /* skip bReadSTClear */ cursor += sizeof(uint8_t); /* skip bWriteSTClear */ cursor += sizeof(uint8_t); /* skip bWriteDefine */ *size = ReadTpmUint32(&cursor); return TPM_SUCCESS; } uint32_t TlclGetOwnership(uint8_t* owned) { uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; uint32_t size; uint32_t result = TlclSendReceive(tpm_getownership_cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) return result; FromTpmUint32(response + kTpmResponseHeaderLength, &size); /* TODO(crbug.com/379255): This fails. Find out why. * VB2_ASSERT(size == sizeof(*owned)); */ memcpy(owned, response + kTpmResponseHeaderLength + sizeof(size), sizeof(*owned)); return result; } uint32_t TlclGetRandom(uint8_t* data, uint32_t length, uint32_t *size) { struct s_tpm_get_random_cmd cmd; uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; uint32_t result; VB2_DEBUG("TPM: TlclGetRandom(%d)\n", length); memcpy(&cmd, &tpm_get_random_cmd, sizeof(cmd)); ToTpmUint32(cmd.buffer + tpm_get_random_cmd.bytesRequested, length); /* There must be room in the response buffer for the bytes. */ if (length > TPM_LARGE_ENOUGH_COMMAND_SIZE - kTpmResponseHeaderLength - sizeof(uint32_t)) { return TPM_E_IOERROR; } result = TlclSendReceive(cmd.buffer, response, sizeof(response)); if (result == TPM_SUCCESS) { const uint8_t* get_random_cursor = response + kTpmResponseHeaderLength; *size = ReadTpmUint32(&get_random_cursor); /* There must be room in the target buffer for the bytes. */ if (*size > length) { return TPM_E_RESPONSE_TOO_LARGE; } memcpy(data, get_random_cursor, *size); } return result; } uint32_t TlclGetVersion(uint32_t* vendor, uint64_t* firmware_version, uint8_t* vendor_specific_buf, size_t* vendor_specific_buf_size) { uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; uint32_t result = TlclSendReceive(tpm_getversionval_cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) return result; const uint8_t* cursor = response + kTpmResponseHeaderLength; uint32_t size = ReadTpmUint32(&cursor); /* Verify size >= sizeof(TPM_CAP_VERSION_INFO). */ const uint32_t kSizeofCapVersionInfo = 15; if (size < kSizeofCapVersionInfo || kTpmResponseHeaderLength + sizeof(size) + size > TPM_LARGE_ENOUGH_COMMAND_SIZE) { return TPM_E_IOERROR; } cursor += sizeof(uint16_t); /* tag */ cursor += sizeof(uint16_t); /* spec version */ *firmware_version = ReadTpmUint16(&cursor); cursor += sizeof(uint16_t); /* specLevel */ cursor += sizeof(uint8_t); /* errataRev */ *vendor = ReadTpmUint32(&cursor); if (vendor_specific_buf_size) { uint16_t vendor_specific_size = ReadTpmUint16(&cursor); if (size < kSizeofCapVersionInfo + vendor_specific_size) { return TPM_E_IOERROR; } if (vendor_specific_buf) { if (vendor_specific_size > *vendor_specific_buf_size) { vendor_specific_size = *vendor_specific_buf_size; } memcpy(vendor_specific_buf, cursor, vendor_specific_size); cursor += vendor_specific_size; } *vendor_specific_buf_size = vendor_specific_size; } return TPM_SUCCESS; } static void ParseIFXFirmwarePackage(const uint8_t** cursor, TPM_IFX_FIRMWAREPACKAGE* firmware_package) { firmware_package->FwPackageIdentifier = ReadTpmUint32(cursor); firmware_package->Version = ReadTpmUint32(cursor); firmware_package->StaleVersion = ReadTpmUint32(cursor); } uint32_t TlclIFXFieldUpgradeInfo(TPM_IFX_FIELDUPGRADEINFO* info) { uint32_t vendor; uint64_t firmware_version; uint32_t result = TlclGetVersion(&vendor, &firmware_version, NULL, NULL); if (result != TPM_SUCCESS) { return result; } if (vendor != 0x49465800) { return TPM_E_BAD_ORDINAL; } uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; result = TlclSendReceive(tpm_ifx_fieldupgradeinforequest2_cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) { return result; } const uint8_t* cursor = response + kTpmResponseHeaderLength; uint16_t size = ReadTpmUint16(&cursor); /* Comments below indicate skipped fields of unknown purpose that are * marked "internal" in the firmware updater source. */ cursor += sizeof(uint16_t); /* internal1 */ info->wMaxDataSize = ReadTpmUint16(&cursor); cursor += sizeof(uint16_t); /* sSecurityModuleLogic.internal1 */ cursor += sizeof(uint32_t); /* sSecurityModuleLogic.internal2 */ cursor += sizeof(uint8_t[34]); /* sSecurityModuleLogic.internal3 */ ParseIFXFirmwarePackage(&cursor, &info->sBootloaderFirmwarePackage); uint16_t fw_entry_count = ReadTpmUint16(&cursor); if (fw_entry_count > ARRAY_SIZE(info->sFirmwarePackages)) { return TPM_E_INVALID_RESPONSE; } uint16_t i; for (i = 0; i < fw_entry_count; ++i) { ParseIFXFirmwarePackage(&cursor, &info->sFirmwarePackages[i]); } info->wSecurityModuleStatus = ReadTpmUint16(&cursor); ParseIFXFirmwarePackage(&cursor, &info->sProcessFirmwarePackage); cursor += sizeof(uint16_t); /* internal6 */ cursor += sizeof(uint8_t[6]); /* internal7 */ info->wFieldUpgradeCounter = ReadTpmUint16(&cursor); uint32_t parsed_bytes = cursor - response; VB2_ASSERT(parsed_bytes <= TPM_LARGE_ENOUGH_COMMAND_SIZE); if (parsed_bytes > kTpmResponseHeaderLength + sizeof(size) + size) { return TPM_E_INVALID_RESPONSE; } return result; } #ifdef CHROMEOS_ENVIRONMENT static uint32_t ParseRsaKeyParms(const uint8_t* buffer, const uint8_t* end, uint32_t* key_len, uint32_t* num_primes, uint32_t* exponent) { if (end - buffer < 3 * sizeof(uint32_t)) { return TPM_E_INVALID_RESPONSE; } *key_len = ReadTpmUint32(&buffer); *num_primes = ReadTpmUint32(&buffer); uint32_t exponent_size = ReadTpmUint32(&buffer); if (end - buffer < exponent_size) { return TPM_E_INVALID_RESPONSE; } if (exponent_size == 0) { *exponent = 0x10001; } else if (exponent_size <= sizeof(*exponent)) { *exponent = 0; int i; for (i = 0; i < exponent_size; ++i) { *exponent |= (*buffer++) << (8 * i); } } else { return TPM_E_INTERNAL_ERROR; } return TPM_SUCCESS; } static uint32_t ParseTpmPubKey(const uint8_t** buffer, const uint8_t* end, uint32_t* algorithm, uint16_t* enc_scheme, uint16_t* sig_scheme, uint32_t* key_len, uint32_t* num_primes, uint32_t* exponent, uint8_t* modulus, uint32_t* modulus_size) { uint32_t result = TPM_SUCCESS; if (end - *buffer < 2 * sizeof(uint32_t) + 2 * sizeof(uint16_t)) { return TPM_E_INVALID_RESPONSE; } *algorithm = ReadTpmUint32(buffer); *enc_scheme = ReadTpmUint16(buffer); *sig_scheme = ReadTpmUint16(buffer); uint32_t parm_size = ReadTpmUint32(buffer); if (end - *buffer < parm_size) { return TPM_E_INVALID_RESPONSE; } if (*algorithm == TPM_ALG_RSA) { result = ParseRsaKeyParms(*buffer, *buffer + parm_size, key_len, num_primes, exponent); if (result != TPM_SUCCESS) { return result; } } else { return TPM_E_INTERNAL_ERROR; } *buffer += parm_size; if (end - *buffer < sizeof(uint32_t)) { return TPM_E_INVALID_RESPONSE; } uint32_t actual_modulus_size = ReadTpmUint32(buffer); if (end - *buffer < actual_modulus_size) { return TPM_E_INVALID_RESPONSE; } if (modulus && *modulus_size >= actual_modulus_size) { memcpy(modulus, *buffer, actual_modulus_size); } else { result = TPM_E_BUFFER_SIZE; } *modulus_size = actual_modulus_size; *buffer += actual_modulus_size; return result; } uint32_t TlclReadPubek(uint32_t* public_exponent, uint8_t* modulus, uint32_t* modulus_size) { struct s_tpm_readpubek_cmd cmd; memcpy(&cmd, &tpm_readpubek_cmd, sizeof(cmd)); if (vb2ex_tpm_get_random(cmd.buffer + tpm_readpubek_cmd.antiReplay, sizeof(TPM_NONCE)) != VB2_SUCCESS) { return TPM_E_INTERNAL_ERROR; } /* The response contains the public endorsement key, so use a large * response buffer. */ uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE + TPM_RSA_2048_LEN]; uint32_t result = TlclSendReceive(cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) { return result; } const uint8_t* cursor = response + kTpmResponseHeaderLength; const uint8_t* end = response + sizeof(response); /* Parse the key */ uint32_t algorithm; uint16_t enc_scheme; uint16_t sig_scheme; uint32_t key_len; uint32_t num_primes; result = ParseTpmPubKey(&cursor, end, &algorithm, &enc_scheme, &sig_scheme, &key_len, &num_primes, public_exponent, modulus, modulus_size); if (result != TPM_SUCCESS) { return result; } /* Parse the checksum */ if (end - cursor < TPM_SHA1_160_HASH_LEN) { return TPM_E_INVALID_RESPONSE; } const uint8_t* checksum = cursor; cursor += TPM_SHA1_160_HASH_LEN; /* Check the digest */ struct vb2_sha1_context sha1_ctx; vb2_sha1_init(&sha1_ctx); vb2_sha1_update(&sha1_ctx, response + kTpmResponseHeaderLength, checksum - (response + kTpmResponseHeaderLength)); vb2_sha1_update(&sha1_ctx, cmd.buffer + tpm_readpubek_cmd.antiReplay, sizeof(TPM_NONCE)); uint8_t digest[TPM_SHA1_160_HASH_LEN]; vb2_sha1_finalize(&sha1_ctx, digest); if (vb2_safe_memcmp(digest, checksum, sizeof(digest))) { return TPM_E_AUTHFAIL; } /* Validate expectations for the EK. */ if (algorithm != TPM_ALG_RSA || enc_scheme != TPM_ES_RSAESOAEP_SHA1_MGF1 || sig_scheme != TPM_SS_NONE || key_len != 2048 || num_primes != 2) { return TPM_E_INVALID_RESPONSE; } return result; } uint32_t TlclTakeOwnership(const uint8_t enc_owner_auth[TPM_RSA_2048_LEN], const uint8_t enc_srk_auth[TPM_RSA_2048_LEN], const uint8_t owner_auth[TPM_AUTH_DATA_LEN]) { /* Start an OAIP session. */ struct auth_session auth_session; uint32_t result = StartOIAPSession(&auth_session, owner_auth); if (result != TPM_SUCCESS) { return result; } /* Build the TakeOwnership command. */ struct s_tpm_takeownership_cmd cmd; memcpy(&cmd, &tpm_takeownership_cmd, sizeof(cmd)); memcpy(cmd.buffer + tpm_takeownership_cmd.encOwnerAuth, enc_owner_auth, TPM_RSA_2048_LEN); memcpy(cmd.buffer + tpm_takeownership_cmd.encSrkAuth, enc_srk_auth, TPM_RSA_2048_LEN); result = AddRequestAuthBlock(&auth_session, cmd.buffer, sizeof(cmd.buffer), 0); if (result != TPM_SUCCESS) { return result; } /* The response buffer needs to be large to hold the public half of the * generated SRK. */ uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE + TPM_RSA_2048_LEN]; result = TlclSendReceive(cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) { return result; } /* Check the auth tag on the response. */ result = CheckResponseAuthBlock(&auth_session, TPM_ORD_TakeOwnership, response, sizeof(response)); if (result != TPM_SUCCESS) { return result; } return TPM_SUCCESS; } uint32_t TlclCreateDelegationFamily(uint8_t family_label) { struct s_tpm_create_delegation_family_cmd cmd; memcpy(&cmd, &tpm_create_delegation_family_cmd, sizeof(cmd)); cmd.buffer[tpm_create_delegation_family_cmd.familyLabel] = family_label; return Send(cmd.buffer); } uint32_t TlclReadDelegationFamilyTable(TPM_FAMILY_TABLE_ENTRY *table, uint32_t* table_size) { uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE]; uint32_t result = TlclSendReceive(tpm_delegate_read_table_cmd.buffer, response, sizeof(response)); if (result != TPM_SUCCESS) { return result; } uint32_t size; FromTpmUint32(response + sizeof(uint16_t), &size); if (size < kTpmRequestHeaderLength + sizeof(uint32_t) || size > sizeof(response)) { return TPM_E_INVALID_RESPONSE; } const uint8_t* cursor = response + kTpmRequestHeaderLength; uint32_t table_bytes = ReadTpmUint32(&cursor); if (table_bytes > size - (cursor - response)) { return TPM_E_INVALID_RESPONSE; } const uint32_t table_entry_size = sizeof(uint16_t) + sizeof(uint8_t) + 3 * sizeof(uint32_t); uint32_t table_entries = table_bytes / table_entry_size; int i; for (i = 0; i < table_entries; ++i) { if (i >= *table_size || !table) { result = TPM_E_BUFFER_SIZE; break; } table[i].tag = ReadTpmUint16(&cursor); table[i].familyLabel = *cursor++; table[i].familyID = ReadTpmUint32(&cursor); table[i].verificationCount = ReadTpmUint32(&cursor); table[i].flags = ReadTpmUint32(&cursor); } *table_size = table_entries; return result; } #endif /* CHROMEOS_ENVIRONMENT */