375 changes: 375 additions & 0 deletions tools/tpm2_pcrevent.c
@@ -0,0 +1,375 @@
//**********************************************************************;
// Copyright (c) 2015, Intel Corporation
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of Intel Corporation nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//**********************************************************************;

#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <getopt.h>

#include <sapi/tpm20.h>

#include "log.h"
#include "files.h"
#include "main.h"
#include "options.h"
#include "tpm2_alg_util.h"
#include "tpm2_password_util.h"
#include "tpm2_util.h"

typedef struct tpm_pcrevent_ctx tpm_pcrevent_ctx;
struct tpm_pcrevent_ctx {
TPMI_DH_PCR pcr;
FILE *input;
TPMS_AUTH_COMMAND session_data;
TSS2_SYS_CONTEXT *sapi_context;
};

#define TSS2_APP_PCREVENT_RC_FAILED (0x57 + 0x100 + TSS2_APP_ERROR_LEVEL)

static inline void swap_auths(TPMS_AUTH_COMMAND **auths) {

TPMS_AUTH_COMMAND *tmp = auths[0];
auths[0] = auths[1];
auths[1] = tmp;
}

static TPM_RC tpm_pcrevent_file(tpm_pcrevent_ctx *ctx, TPML_DIGEST_VALUES *result) {

TPMS_AUTH_COMMAND empty_auth = TPMS_AUTH_COMMAND_INIT(TPM_RS_PW);

TPMS_AUTH_COMMAND *all_auths[] = {
&ctx->session_data, /* auth for the pcr handle */
&empty_auth, /* auth for the sequence handle */

};

TSS2_SYS_CMD_AUTHS cmd_auth_array = TSS2_SYS_CMD_AUTHS_INIT(all_auths);
/*
* All the routines up to complete only use one of the two handles,
* so set size to 0
*/
cmd_auth_array.cmdAuthsCount = 1;

unsigned long file_size = 0;

FILE *input = ctx->input;

/* Suppress error reporting with NULL path */
bool res = files_get_file_size(input, &file_size, NULL);

/* If we can get the file size and its less than 1024, just do it in one hash invocation */
if (res && file_size <= BUFFER_SIZE(TPM2B_EVENT, buffer)) {

TPM2B_EVENT buffer = TPM2B_INIT(file_size);

res = files_read_bytes(ctx->input, buffer.t.buffer, buffer.t.size);
if (!res) {
LOG_ERR("Error reading input file!");
return TSS2_APP_PCREVENT_RC_FAILED;
}

return Tss2_Sys_PCR_Event(ctx->sapi_context, ctx->pcr, &cmd_auth_array,
&buffer, result,
NULL);
}

TPMI_DH_OBJECT sequence_handle;
TPM2B_AUTH nullAuth = TPM2B_EMPTY_INIT;

/*
* Size is either unknown because the FILE * is a fifo, or it's too big
* to do in a single hash call. Based on the size figure out the chunks
* to loop over, if possible. This way we can call Complete with data.
*/
TPM_RC rval = Tss2_Sys_HashSequenceStart(ctx->sapi_context, NULL, &nullAuth,
TPM_ALG_NULL, &sequence_handle, NULL);
if (rval != TPM_RC_SUCCESS) {
LOG_ERR("Tss2_Sys_HashSequenceStart failed: 0x%X", rval);
return rval;
}

/* If we know the file size, we decrement the amount read and terminate the loop
* when 1 block is left, else we go till feof.
*/
size_t left = file_size;
bool use_left = !!res;

TPM2B_MAX_BUFFER data;

/*
* swap the auths (leave count 1) so that
* the sequence auth is used
* for the update call.
*/
swap_auths(all_auths);

bool done = false;
while (!done) {

size_t bytes_read = fread(data.t.buffer, 1,
BUFFER_SIZE(typeof(data), buffer), input);
if (ferror(input)) {
LOG_ERR("Error reading from input file");
return TSS2_APP_PCREVENT_RC_FAILED;
}

data.t.size = bytes_read;

/* if data was read, update the sequence */
rval = Tss2_Sys_SequenceUpdate(ctx->sapi_context, sequence_handle,
&cmd_auth_array, &data, NULL);
if (rval != TPM_RC_SUCCESS) {
LOG_ERR("Tss2_Sys_SequenceUpdate failed: 0x%X", rval);
return rval;
}

if (use_left) {
left -= bytes_read;
if (left <= MAX_DIGEST_BUFFER) {
done = true;
continue;
}
} else if (feof(input)) {
done = true;
}
} /* end file read/hash update loop */

if (use_left) {
data.t.size = left;
bool res = files_read_bytes(input, data.t.buffer, left);
if (!res) {
LOG_ERR("Error reading from input file.");
return TSS2_APP_PCREVENT_RC_FAILED;
}
} else {
data.t.size = 0;
}

/*
* Swap back so the order is correct for the complete call
* and update the size to 2, as complete needs both the PCR
* and the sequence auths.
*/
swap_auths(all_auths);
cmd_auth_array.cmdAuthsCount = 2;

return Tss2_Sys_EventSequenceComplete(ctx->sapi_context, ctx->pcr,
sequence_handle, &cmd_auth_array, &data, result, NULL);
}

static bool do_hmac_and_output(tpm_pcrevent_ctx *ctx) {

TPML_DIGEST_VALUES digests;
TPM_RC rval = tpm_pcrevent_file(ctx, &digests);
if (rval != TPM_RC_SUCCESS) {
LOG_ERR("tpm_pcrevent_file() failed: 0x%X", rval);
return false;
}

UINT32 i;
for (i = 0; i < digests.count; i++) {
TPMT_HA *d = &digests.digests[i];

TOOL_OUTPUT("%s:", tpm2_alg_util_algtostr(d->hashAlg));

BYTE *bytes;
size_t size;
switch (d->hashAlg) {
case TPM_ALG_SHA1:
bytes = d->digest.sha1;
size = sizeof(d->digest.sha1);
break;
case TPM_ALG_SHA256:
bytes = d->digest.sha256;
size = sizeof(d->digest.sha256);
break;
case TPM_ALG_SHA384:
bytes = d->digest.sha384;
size = sizeof(d->digest.sha384);
break;
case TPM_ALG_SHA512:
bytes = d->digest.sha512;
size = sizeof(d->digest.sha512);
break;
case TPM_ALG_SM3_256:
bytes = d->digest.sm3_256;
size = sizeof(d->digest.sm3_256);
break;
default: {
LOG_WARN("Unknown digest to convert!");
// print something so the format doesn't change
// on this case.
static BYTE byte = 0;
bytes = &byte;
size = sizeof(byte);
}
}

size_t j;
for (j = 0; j < size; j++) {
TOOL_OUTPUT("%02x", bytes[j]);
}

TOOL_OUTPUT("\n");

}

return true;
}

static bool init(int argc, char *argv[], tpm_pcrevent_ctx *ctx) {

static const struct option long_options[] = {
{ "pcr-index", required_argument, NULL, 'i' },
{ "input-session-handle", required_argument, NULL, 'S' },
{ "password", required_argument, NULL, 'P' },
{ NULL, no_argument, NULL, '\0' }
};

union {
struct {
UINT8 i : 1;
UINT8 S : 1;
UINT8 P : 1;
UINT8 unused : 5;
};
UINT8 all;
} flags = { .all = 0 };

int opt;
bool res;
while ((opt = getopt_long(argc, argv, "i:P:S:", long_options, NULL))
!= -1) {
switch (opt) {
case 'i':
res = tpm2_util_string_to_uint32(optarg, &ctx->pcr);
if (!res) {
LOG_ERR("Could not convert \"%s\", to a pcr index.", argv[1]);
return false;
}
flags.i = 1;
break;
case 'S': {
bool result = tpm2_util_string_to_uint32(optarg,
&ctx->session_data.sessionHandle);
if (!result) {
LOG_ERR(
"Could not convert session handle to number, got: \"%s\"",
optarg);
return false;
}
}
flags.S = 1;
break;
case 'P': {
bool result = tpm2_password_util_from_optarg(optarg,
&ctx->session_data.hmac);
if (!result) {
LOG_ERR("Invalid key handle password, got\"%s\"", optarg);
return false;
}
}
flags.P = 1;
break;
case '?':
LOG_ERR("Unknown Argument: %c", optopt);
return false;
default:
LOG_ERR("?? getopt returned character code 0%o ??", opt);
return false;
}
}

if (flags.S && flags.P) {
LOG_ERR("Cannot specify both -P and -S options.");
return false;
}

if ((flags.S || flags.P) && !flags.i) {
LOG_ERR("Must specify a PCR index via -i with the -%c option.",
flags.P ? 'P' : 'S');
return false;
}

size_t cnt = argc - optind;
if (cnt > 1) {
LOG_ERR("Expected a single FILE argument, got: %zu", cnt);
return false;
}

if (cnt) {
ctx->input = fopen(argv[optind], "rb");
if (!ctx->input) {
LOG_ERR("Error opening file \"%s\", error: %s", argv[optind],
strerror(errno));
return false;
}
}

return true;
}

int execute_tool(int argc, char *argv[], char *envp[], common_opts_t *opts,
TSS2_SYS_CONTEXT *sapi_context) {

(void) opts;
(void) envp;

int rc = 1;

tpm_pcrevent_ctx ctx = {
.pcr = TPM_RH_NULL,
.input = stdin,
.session_data = TPMS_AUTH_COMMAND_INIT(TPM_RS_PW),
.sapi_context = sapi_context,
};

bool result = init(argc, argv, &ctx);
if (!result) {
goto out;
}

result = do_hmac_and_output(&ctx);
if (!result) {
goto out;
}

rc = 0;
out:
if (ctx.input && ctx.input != stdin) {
fclose(ctx.input);
}

return rc;
}