blob: b97159e73f81d7909ddebec06009fef150f4b4db [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/* Copyright 2020 IBM Corp. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <skiboot.h>
#include <opal-api.h>
#include <tssskiboot.h>
#include <tpm_chip.h>
#include <ibmtss/tssfile.h>
#include <ibmtss/TPM_Types.h>
#include <ibmtss/tssmarshal.h>
#include <ibmtss/tssresponsecode.h>
#define TSS_MAX_NV_BUFFER_SIZE 1024
/*
* Helper to string-fy TSS error response codes.
*/
static void tss_error_trace(const char *function, TPM_RC rc)
{
const char *msg;
const char *submsg;
const char *num;
prlog(PR_ERR, "%s: failed, rc %08x\n", function, rc);
TSS_ResponseCode_toString(&msg, &submsg, &num, rc);
prlog(PR_ERR, "%s%s%s\n", msg, submsg, num);
}
/*
* @brief Reads the public and name area of a NV Index.
* @param nv_index The target NV index to read public info from.
* @param nv_public buffer to save public are read from nv index
* @param nv_name buffer to save nv index name.
*/
int tss_nv_read_public(TPMI_RH_NV_INDEX nv_index, TPMS_NV_PUBLIC *nv_public,
TPM2B_NAME *nv_name)
{
NV_ReadPublic_Out *out = NULL;
NV_ReadPublic_In *in = NULL;
TSS_CONTEXT *context = NULL;
TPM_RC rc = OPAL_SUCCESS;
if (!nv_public || !nv_name) {
rc = OPAL_PARAMETER;
goto cleanup;
}
in = zalloc(sizeof(NV_ReadPublic_In));
if (!in) {
rc = OPAL_NO_MEM;
goto cleanup;
}
out = zalloc(sizeof(NV_ReadPublic_Out));
if (!out) {
rc = OPAL_NO_MEM;
goto cleanup;
}
rc = TSS_Create(&context);
if (rc) {
tss_error_trace("tss_nv_read_public", rc);
rc = OPAL_NO_MEM;
goto cleanup;
}
in->nvIndex = nv_index;
rc = TSS_Execute(context,
(RESPONSE_PARAMETERS *) out,
(COMMAND_PARAMETERS *) in,
NULL,
TPM_CC_NV_ReadPublic,
TPM_RH_NULL, NULL, 0);
if (!rc) {
memcpy(nv_public, &out->nvPublic, sizeof(TPMS_NV_PUBLIC));
memcpy(nv_name, &out->nvName, sizeof(TPM2B_NAME));
}
else
tss_error_trace("tss_nv_read_public", rc);
cleanup:
free(in);
free(out);
TSS_Delete(context);
return rc;
}
/* @brief This command reads a value from an area previously defined using
* nv_define_space
* @param nv_index The target NV index to read from.
* @param buffer buffer to save the data read.
* @param buffer_size size of the buffer, to avoid overflow.
* @param offset position where to start the nv read operation.
*/
int tss_nv_read(TPMI_RH_NV_INDEX nv_index, void *buffer,
size_t buffer_size, uint16_t offset)
{
TSS_CONTEXT *context = NULL;
NV_Read_Out *out = NULL;
NV_Read_In *in = NULL;
TPM_RC rc = OPAL_SUCCESS;
int64_t buffer_remaining;
if (!buffer) {
rc = OPAL_PARAMETER;
goto cleanup;
}
in = zalloc(sizeof(NV_Read_In));
if (!in) {
rc = OPAL_NO_MEM;
goto cleanup;
}
out = zalloc(sizeof(NV_Read_Out));
if (!out) {
rc = OPAL_NO_MEM;
goto cleanup;
}
rc = TSS_Create(&context);
if (rc) {
tss_error_trace("tss_nv_read", rc);
rc = OPAL_NO_MEM;
goto cleanup;
}
in->nvIndex = nv_index;
in->authHandle = nv_index;
buffer_remaining = buffer_size;
while (buffer_remaining > 0) {
in->offset = offset;
in->size = MIN(TSS_MAX_NV_BUFFER_SIZE, buffer_remaining);
rc = TSS_Execute(context,
(RESPONSE_PARAMETERS *) out,
(COMMAND_PARAMETERS *) in,
NULL,
TPM_CC_NV_Read,
TPM_RS_PW, NULL, 0,
TPM_RH_NULL, NULL, 0);
if (rc) {
tss_error_trace("tss_nv_read", rc);
goto cleanup;
}
memcpy(buffer, out->data.b.buffer, in->size);
buffer += TSS_MAX_NV_BUFFER_SIZE;
buffer_remaining -= TSS_MAX_NV_BUFFER_SIZE;
offset += TSS_MAX_NV_BUFFER_SIZE;
}
cleanup:
TSS_Delete(context);
free(in);
free(out);
return rc;
}
/* @brief This command writes a value in an area previously defined using
* nv_define_space
* @param nv_index The target NV index to write to.
* @param buffer buffer containing the data write.
* @param buffer_size size of the buffer to write.
* @param offset position where to start the nv write operation.
*/
int tss_nv_write(TPMI_RH_NV_INDEX nv_index, void *buffer,
size_t buffer_size, uint16_t offset)
{
TSS_CONTEXT *context = NULL;
NV_Write_In *in = NULL;
TPM_RC rc = OPAL_SUCCESS;
int64_t buffer_remaining;
if (!buffer) {
rc = OPAL_PARAMETER;
goto cleanup;
}
in = zalloc(sizeof(NV_Write_In));
if (!in) {
rc = OPAL_NO_MEM;
goto cleanup;
}
rc = TSS_Create(&context);
if (rc) {
tss_error_trace("tss_nv_write", rc);
rc = OPAL_NO_MEM;
goto cleanup;
}
in->nvIndex = nv_index;
in->authHandle = TPM_RH_PLATFORM;
buffer_remaining = buffer_size;
while (buffer_remaining > 0) {
in->offset = offset;
rc = TSS_TPM2B_Create(&in->data.b, buffer,
MIN(TSS_MAX_NV_BUFFER_SIZE, buffer_remaining),
sizeof(in->data.t.buffer));
if (rc) {
tss_error_trace("tss_nv_write", rc);
goto cleanup;
}
rc = TSS_Execute(context,
NULL,
(COMMAND_PARAMETERS *) in,
NULL,
TPM_CC_NV_Write,
TPM_RS_PW, NULL, 0,
TPM_RH_NULL, NULL, 0);
if (rc) {
tss_error_trace("tss_nv_write", rc);
goto cleanup;
}
buffer += TSS_MAX_NV_BUFFER_SIZE;
buffer_remaining -= TSS_MAX_NV_BUFFER_SIZE;
offset += TSS_MAX_NV_BUFFER_SIZE;
}
cleanup:
TSS_Delete(context);
free(in);
return rc;
}
/*
* @brief This command locks an area, pointed by the index and previously
* defined using nv_define_space, preventing further writing operations on it.
* @param nv_index The target NV index to lock.
*/
int tss_nv_write_lock(TPMI_RH_NV_INDEX nv_index)
{
TSS_CONTEXT *context = NULL;
NV_WriteLock_In *in = NULL;
TPM_RC rc = OPAL_SUCCESS;
in = zalloc(sizeof(NV_WriteLock_In));
if (!in) {
rc = OPAL_NO_MEM;
goto cleanup;
}
rc = TSS_Create(&context);
if (rc) {
rc = OPAL_NO_MEM;
goto cleanup;
}
in->authHandle = TPM_RH_PLATFORM;
in->nvIndex = nv_index;
rc = TSS_Execute(context,
NULL,
(COMMAND_PARAMETERS *) in,
NULL,
TPM_CC_NV_WriteLock,
TPM_RS_PW, NULL, 0,
TPM_RH_NULL, NULL, 0);
if (rc)
tss_error_trace("tss_nv_write_lock", rc);
cleanup:
TSS_Delete(context);
free(in);
return rc;
}
/*
* @brief This command defines the area pointed by nv index and its attributes.
* @param nv_index The target NV index to define.
* @param data_size size of the area to be defined.
*/
int tss_nv_define_space(TPMI_RH_NV_INDEX nv_index, uint16_t data_size)
{
NV_DefineSpace_In *in = NULL;
TSS_CONTEXT *context = NULL;
TPM_RC rc = OPAL_SUCCESS;
in = zalloc(sizeof(NV_DefineSpace_In));
if (!in) {
rc = OPAL_NO_MEM;
goto cleanup;
}
rc = TSS_Create(&context);
if (rc) {
rc = OPAL_NO_MEM;
goto cleanup;
}
in->authHandle = TPM_RH_PLATFORM;
in->publicInfo.nvPublic.nvIndex = nv_index;
in->publicInfo.nvPublic.dataSize = data_size;
/* password is NULL so b.size is 0 */
in->auth.b.size = 0;
/* Empty policy, so size is 0 */
in->publicInfo.nvPublic.authPolicy.t.size = 0;
/* Used algorithm is SHA256 */
in->publicInfo.nvPublic.nameAlg = TPM_ALG_SHA256;
/*
* This carries the flags set according to default settings, excepting
* for what is set by this function parameters. Further customization
* will require a different setup for nvAttribute flags as is done in
* TSS's code.
*/
in->publicInfo.nvPublic.attributes.val = (TPMA_NVA_PPWRITE |
TPMA_NVA_ORDINARY |
TPMA_NVA_WRITE_STCLEAR |
TPMA_NVA_AUTHREAD |
TPMA_NVA_PLATFORMCREATE |
TPMA_NVA_NO_DA);
rc = TSS_Execute(context,
NULL,
(COMMAND_PARAMETERS *)in,
NULL,
TPM_CC_NV_DefineSpace,
TPM_RS_PW, NULL, 0,
TPM_RH_NULL, NULL, 0);
if (rc) {
tss_error_trace("tss_nv_define_space", rc);
switch(rc) {
case TPM_RC_NV_DEFINED:
rc = OPAL_WRONG_STATE;
break;
default:
break;
}
}
cleanup:
TSS_Delete(context);
free(in);
return rc;
}
/*
* @brief Extends a PCR using the given hashes and digest
* @param pcr_handle The PCR to be extended
* @param alg_hashes A pointer to an array of hash algorithms, each
* one used to extend its respective PCR bank.
* @param alg_hash_count The number of elements in alg_hashes array
* @param digests The digest data.
*/
int tss_pcr_extend(TPMI_DH_PCR pcr_handle, TPMI_ALG_HASH *alg_hashes,
uint8_t alg_hash_count, const uint8_t **digests)
{
TSS_CONTEXT *context = NULL;
uint32_t rc = OPAL_SUCCESS;
PCR_Extend_In *in = NULL;
uint16_t digest_size;
if (!alg_hashes || !digests || pcr_handle >= IMPLEMENTATION_PCR) {
rc = OPAL_PARAMETER;
goto cleanup;
}
in = zalloc(sizeof(PCR_Extend_In));
if (!in) {
rc = OPAL_NO_MEM;
goto cleanup;
}
rc = TSS_Create(&context);
if (rc) {
tss_error_trace("tss_pcr_extend", rc);
rc = OPAL_NO_MEM;
goto cleanup;
}
if (alg_hash_count >= HASH_COUNT) {
rc = OPAL_PARAMETER;
goto cleanup;
}
in->digests.count = alg_hash_count;
in->pcrHandle = pcr_handle;
for (uint8_t i=0; i < alg_hash_count; i++) {
in->digests.digests[i].hashAlg = alg_hashes[i];
/* memset zeroes first to assure the digest data is zero
* padded.*/
memset((uint8_t*) &in->digests.digests[i].digest, 0,
sizeof(TPMU_HA));
digest_size = 0;
/* Marshal the digest in order to obtain its size. This is a
* commonly used pattern in TSS.
*/
rc = TSS_TPMU_HA_Marshalu((const TPMU_HA *)digests[i],
&digest_size, NULL, NULL ,
alg_hashes[i]);
if (rc)
goto cleanup;
memcpy((uint8_t*) &in->digests.digests[i].digest, digests[i],
digest_size);
}
rc = TSS_Execute(context,
NULL,
(COMMAND_PARAMETERS *) in,
NULL,
TPM_CC_PCR_Extend,
TPM_RS_PW, NULL, 0,
TPM_RH_NULL, NULL, 0);
if (rc)
tss_error_trace("tss_pcr_extend", rc);
cleanup:
TSS_Delete(context);
free(in);
return rc;
}
/*
* @brief reads pcr values of a given pcr handle.
* @param pcr_handle The PCR to be extended
* @param alg_hashes A pointer to an array of hash algorithms, each
* one used to extend its respective PCR bank.
* @param alg_hash_count The length of alg hashes array
*/
int tss_pcr_read(TPMI_DH_PCR pcr_handle, TPMI_ALG_HASH *alg_hashes,
uint8_t alg_hash_count)
{
TSS_CONTEXT *context = NULL;
PCR_Read_Out *out = NULL;
PCR_Read_In *in = NULL;
uint32_t rc = OPAL_SUCCESS;
if (!alg_hashes) {
rc = OPAL_PARAMETER;
goto cleanup;
}
in = zalloc(sizeof(PCR_Read_In));
if (!in) {
rc = OPAL_NO_MEM;
goto cleanup;
}
out = zalloc(sizeof(PCR_Read_Out));
if (!out) {
rc = OPAL_NO_MEM;
goto cleanup;
}
rc = TSS_Create(&context);
if (!rc) {
tss_error_trace("tss_pcr_read", rc);
rc = OPAL_NO_MEM;
goto cleanup;
}
in->pcrSelectionIn.count = alg_hash_count;
for (int i=0; i < alg_hash_count; i++) {
in->pcrSelectionIn.pcrSelections[i].hash = alg_hashes[i];
in->pcrSelectionIn.pcrSelections[i].sizeofSelect = 3;
in->pcrSelectionIn.pcrSelections[i].pcrSelect[0] = 0;
in->pcrSelectionIn.pcrSelections[i].pcrSelect[1] = 0;
in->pcrSelectionIn.pcrSelections[i].pcrSelect[2] = 0;
in->pcrSelectionIn.pcrSelections[i].pcrSelect[pcr_handle/8] = 1 << (pcr_handle % 8);
}
rc = TSS_Execute(context,
(RESPONSE_PARAMETERS *) out,
(COMMAND_PARAMETERS *) in,
NULL,
TPM_CC_PCR_Read,
TPM_RH_NULL, NULL, 0);
if (rc)
tss_error_trace("tss_pcr_read", rc);
cleanup:
TSS_Delete(context);
free(in);
free(out);
return rc;
}
/*
* @brief returns next bytes_requested bytes from the TPM RNG
* @param buffer Buffer to save the generated numbers.
* @param bytes_requested How many random bytes are requested.
*/
int tss_get_random_number(void *buffer, uint16_t bytes_requested)
{
TSS_CONTEXT *context = NULL;
GetRandom_Out *out = NULL;
TPM_RC rc = OPAL_SUCCESS;
GetRandom_In *in = NULL;
void *p_buffer = buffer;
if (!buffer) {
rc = OPAL_PARAMETER;
goto cleanup;
}
in = zalloc(sizeof(GetRandom_In));
if (!in) {
rc = OPAL_NO_MEM;
goto cleanup;
}
out = zalloc(sizeof(GetRandom_Out));
if (!out) {
rc = OPAL_NO_MEM;
goto cleanup;
}
rc = TSS_Create(&context);
if (rc) {
tss_error_trace("tss_get_random_number", rc);
rc = OPAL_NO_MEM;
goto cleanup;
}
/*
* Even though we request a specific number of bytes, there is no
* guarantee that TPM will return that number of bytes, so we ask again
* until we reach the desired total of bytes or rng function fails
*/
for (uint16_t bytes_copied = 0; bytes_copied < bytes_requested; ) {
in->bytesRequested = bytes_requested - bytes_copied;
rc = TSS_Execute(context,
(RESPONSE_PARAMETERS *)out,
(COMMAND_PARAMETERS *)in,
NULL, TPM_CC_GetRandom,
TPM_RH_NULL, NULL, 0);
if (!rc){
memcpy(p_buffer, out->randomBytes.t.buffer,
out->randomBytes.t.size);
bytes_copied += out->randomBytes.t.size;
p_buffer += bytes_copied;
/* explicitly clean up output's buffer from memory on
* every iteration, since the size will vary, to avoid
* some kind of exploitation.
*/
memset(out->randomBytes.t.buffer, 0,
out->randomBytes.t.size);
}
else {
tss_error_trace("tss_get_random_number", rc);
break;
}
}
cleanup:
TSS_Delete(context);
free(in);
free(out);
return rc;
}
/* local helper to generate random password without zeroes */
static int generate_random_passwd(char *passwd, uint16_t passwd_len)
{
TPM_RC rc = OPAL_SUCCESS;
char *buffer = NULL;
int bytes_copied;
int i;
buffer = zalloc(passwd_len);
if (!buffer) {
rc = OPAL_NO_MEM;
goto cleanup;
}
bytes_copied = 0;
while ((rc == 0) && (bytes_copied < passwd_len)) {
rc = tss_get_random_number(buffer, passwd_len);
if (rc)
goto cleanup;
/* Copy as many bytes as were received or until bytes requested */
for (i = 0; (i < passwd_len) &&
(bytes_copied < passwd_len); i++) {
/* Skip zero bytes */
if (buffer[i] == 0)
continue;
passwd[bytes_copied] = buffer[i];
bytes_copied++;
}
}
cleanup:
free(buffer);
return rc;
}
/*
* @brief This command allows the authorization secret for a hierarchy to be
* changed.
*/
int tss_set_platform_auth(void)
{
HierarchyChangeAuth_In *in = NULL;
TSS_CONTEXT *context = NULL;
TPM_RC rc = OPAL_SUCCESS;
char *key_passwd = NULL;
in = zalloc(sizeof(HierarchyChangeAuth_In));
if (!in) {
rc = OPAL_NO_MEM;
goto cleanup;
}
key_passwd = zalloc(TSS_AUTH_PASSWD_LEN + 1);
if (!key_passwd) {
rc = OPAL_NO_MEM;
goto cleanup;
}
rc = TSS_Create(&context);
if (rc) {
tss_error_trace("tss_set_platform_auth", rc);
rc = OPAL_NO_MEM;
goto cleanup;
}
rc = generate_random_passwd(key_passwd, TSS_AUTH_PASSWD_LEN);
if (rc) {
tss_error_trace("Failed to generate the auth password", rc);
goto cleanup;
}
key_passwd[TSS_AUTH_PASSWD_LEN] = 0;
in->authHandle = TPM_RH_PLATFORM;
rc = TSS_TPM2B_StringCopy(&in->newAuth.b, key_passwd,
sizeof(in->newAuth.t.buffer));
if (rc) {
tss_error_trace("tss_set_platform_auth", rc);
goto cleanup;
}
rc = TSS_Execute(context,
NULL,
(COMMAND_PARAMETERS *)in,
NULL,
TPM_CC_HierarchyChangeAuth,
TPM_RS_PW, NULL, 0,
TPM_RH_NULL, NULL, 0);
if (rc)
tss_error_trace("tss_set_platform_auth", rc);
cleanup:
TSS_Delete(context);
free(in);
/* explicitly clean up password from memory to avoid some kind of
* exploitation.
*/
memset(key_passwd, 0, TSS_AUTH_PASSWD_LEN + 1);
free(key_passwd);
return rc;
}
/*
* @brief returns a list of defined NV indices
* @param pcr_handle The PCR to be extended
* @param alg_hashes A pointer to an array of hash algorithms, each
* one used to extend its respective PCR bank.
* @param alg_hash_count The length of alg hashes array
*/
int tss_get_defined_nv_indices(TPMI_RH_NV_INDEX **indices, size_t *count)
{
TSS_CONTEXT *context = NULL;
GetCapability_In *in = NULL;
GetCapability_Out *out = NULL;
uint32_t rc = OPAL_SUCCESS;
TPML_HANDLE *handles;
in = zalloc(sizeof(GetCapability_In));
if (!in) {
rc = OPAL_NO_MEM;
goto cleanup;
}
out = zalloc(sizeof(GetCapability_Out));
if (!out) {
rc = OPAL_NO_MEM;
goto cleanup;
}
rc = TSS_Create(&context);
if (rc) {
tss_error_trace("tss_check_nv_index", rc);
rc = OPAL_NO_MEM;
goto cleanup;
}
in->capability = 1;
in->property = 0x01000000;
in->propertyCount = 64;
rc = TSS_Execute(context,
(RESPONSE_PARAMETERS *) out,
(COMMAND_PARAMETERS *) in,
NULL,
TPM_CC_GetCapability,
TPM_RH_NULL, NULL, 0);
if (rc) {
tss_error_trace("tss_check_nv_index", rc);
goto cleanup;
}
handles = (TPML_HANDLE *) &out->capabilityData.data;
*count = handles->count;
*indices = malloc(*count * sizeof(TPMI_RH_NV_INDEX));
if (!indices) {
rc = OPAL_NO_MEM;
goto cleanup;
}
memcpy(*indices, handles->handle, *count * sizeof(TPMI_RH_NV_INDEX));
cleanup:
TSS_Delete(context);
free(in);
free(out);
return rc;
}
int tss_nv_undefine_space(TPMI_RH_NV_INDEX nv_index)
{
int rc;
TSS_CONTEXT *context = NULL;
NV_UndefineSpace_In in;
rc = TSS_Create(&context);
if (rc) {
tss_error_trace("tss_check_nv_undefine_index", rc);
rc = OPAL_NO_MEM;
return rc;
}
in.authHandle = TPM_RH_PLATFORM;
in.nvIndex = nv_index;
rc = TSS_Execute(context, NULL,
(COMMAND_PARAMETERS *) &in,
NULL,
TPM_CC_NV_UndefineSpace,
TPM_RS_PW, NULL, 0,
TPM_RH_NULL, NULL, 0);
if (rc)
tss_error_trace("tss_check_nv_index", rc);
TSS_Delete(context);
return rc;
}