| /* Copyright (c) 2012 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. |
| */ |
| |
| /* 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" |
| #include "vboot_api.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 = VbExTpmSendReceive(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 (VbExTpmGetRandom(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)); |
| if (hmac(VB2_HASH_SHA1, entity_usage_auth, TPM_AUTH_DATA_LEN, |
| hmac_input, sizeof(hmac_input), session->shared_secret, |
| sizeof(session->shared_secret))) { |
| return TPM_E_INTERNAL_ERROR; |
| } |
| 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; |
| } |
| |
| /* Sanity 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 (VbExTpmGetRandom(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; |
| if (hmac(VB2_HASH_SHA1, auth_session->shared_secret, |
| sizeof(auth_session->shared_secret), buf, sizeof(buf), cursor, |
| TPM_SHA1_160_HASH_LEN)) { |
| return TPM_E_AUTHFAIL; |
| } |
| cursor += TPM_SHA1_160_HASH_LEN; |
| |
| 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; |
| uint8_t mac[TPM_SHA1_160_HASH_LEN]; |
| if (hmac(VB2_HASH_SHA1, auth_session->shared_secret, |
| sizeof(auth_session->shared_secret), hmac_input, |
| sizeof(hmac_input), mac, sizeof(mac))) { |
| auth_session->valid = 0; |
| return TPM_E_AUTHFAIL; |
| } |
| |
| /* Check the MAC. */ |
| if (vb2_safe_memcmp(mac, cursor, sizeof(mac))) { |
| 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 VbExTpmInit(); |
| } |
| |
| uint32_t TlclLibClose(void) |
| { |
| return VbExTpmClose(); |
| } |
| |
| 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 (VbExTpmGetRandom(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(uint8_t enc_owner_auth[TPM_RSA_2048_LEN], |
| uint8_t enc_srk_auth[TPM_RSA_2048_LEN], |
| 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 */ |