diff options
-rw-r--r-- | common/Kconfig | 10 | ||||
-rw-r--r-- | common/Makefile | 1 | ||||
-rw-r--r-- | common/cmd_tpm_test.c | 564 |
3 files changed, 575 insertions, 0 deletions
diff --git a/common/Kconfig b/common/Kconfig index bacc4e01cd..2c42b8e4d0 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -635,6 +635,16 @@ config CMD_TPM command requires a suitable TPM on your board and the correct driver must be enabled. +config CMD_TPM_TEST + bool "Enable the 'tpm test' command" + depends on CMD_TPM + help + This provides a a series of tests to confirm that the TPM is working + correctly. The tests cover initialisation, non-volatile RAM, extend, + global lock and checking that timing is within expectations. The + tests pass correctly on Infineon TPMs but may need to be adjusted + for other devices. + endmenu endmenu diff --git a/common/Makefile b/common/Makefile index dc82433e90..f4ba8782f5 100644 --- a/common/Makefile +++ b/common/Makefile @@ -169,6 +169,7 @@ obj-$(CONFIG_CMD_TIME) += cmd_time.o obj-$(CONFIG_CMD_TRACE) += cmd_trace.o obj-$(CONFIG_SYS_HUSH_PARSER) += cmd_test.o obj-$(CONFIG_CMD_TPM) += cmd_tpm.o +obj-$(CONFIG_CMD_TPM_TEST) += cmd_tpm_test.o obj-$(CONFIG_CMD_TSI148) += cmd_tsi148.o obj-$(CONFIG_CMD_UBI) += cmd_ubi.o obj-$(CONFIG_CMD_UBIFS) += cmd_ubifs.o diff --git a/common/cmd_tpm_test.c b/common/cmd_tpm_test.c new file mode 100644 index 0000000000..65332d1117 --- /dev/null +++ b/common/cmd_tpm_test.c @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2015 Google, Inc + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <command.h> +#include <environment.h> +#include <tpm.h> + +/* Prints error and returns on failure */ +#define TPM_CHECK(tpm_command) do { \ + uint32_t result; \ + \ + result = (tpm_command); \ + if (result != TPM_SUCCESS) { \ + printf("TEST FAILED: line %d: " #tpm_command ": 0x%x\n", \ + __LINE__, result); \ + return result; \ + } \ +} while (0) + +#define INDEX0 0xda70 +#define INDEX1 0xda71 +#define INDEX2 0xda72 +#define INDEX3 0xda73 +#define INDEX_INITIALISED 0xda80 +#define PHYS_PRESENCE 4 +#define PRESENCE 8 + +static uint32_t TlclStartupIfNeeded(void) +{ + uint32_t result = tpm_startup(TPM_ST_CLEAR); + + return result == TPM_INVALID_POSTINIT ? TPM_SUCCESS : result; +} + +static int test_timer(void) +{ + printf("get_timer(0) = %lu\n", get_timer(0)); + return 0; +} + +static uint32_t tpm_get_flags(uint8_t *disable, uint8_t *deactivated, + uint8_t *nvlocked) +{ + struct tpm_permanent_flags pflags; + uint32_t result; + + result = tpm_get_permanent_flags(&pflags); + if (result) + return result; + if (disable) + *disable = pflags.disable; + if (deactivated) + *deactivated = pflags.deactivated; + if (nvlocked) + *nvlocked = pflags.nv_locked; + debug("TPM: Got flags disable=%d, deactivated=%d, nvlocked=%d\n", + pflags.disable, pflags.deactivated, pflags.nv_locked); + + return 0; +} + +static uint32_t tpm_set_global_lock(void) +{ + uint32_t x; + + debug("TPM: Set global lock\n"); + return tpm_nv_write_value(INDEX0, (uint8_t *)&x, 0); +} + +static uint32_t tpm_nv_write_value_lock(uint32_t index) +{ + debug("TPM: Write lock 0x%x\n", index); + + return tpm_nv_write_value(index, NULL, 0); +} + +static uint32_t tpm_nv_set_locked(void) +{ + debug("TPM: Set NV locked\n"); + + return tpm_nv_define_space(TPM_NV_INDEX_LOCK, 0, 0); +} + +static int tpm_is_owned(void) +{ + uint8_t response[TPM_PUBEK_SIZE]; + uint32_t result; + + result = tpm_read_pubek(response, sizeof(response)); + + return result != TPM_SUCCESS; +} + +static int test_early_extend(void) +{ + uint8_t value_in[20]; + uint8_t value_out[20]; + + printf("Testing earlyextend ..."); + tpm_init(); + TPM_CHECK(tpm_startup(TPM_ST_CLEAR)); + TPM_CHECK(tpm_continue_self_test()); + TPM_CHECK(tpm_extend(1, value_in, value_out)); + printf("done\n"); + return 0; +} + +static int test_early_nvram(void) +{ + uint32_t x; + + printf("Testing earlynvram ..."); + tpm_init(); + TPM_CHECK(tpm_startup(TPM_ST_CLEAR)); + TPM_CHECK(tpm_continue_self_test()); + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); + TPM_CHECK(tpm_nv_read_value(INDEX0, (uint8_t *)&x, sizeof(x))); + printf("done\n"); + return 0; +} + +static int test_early_nvram2(void) +{ + uint32_t x; + + printf("Testing earlynvram2 ..."); + tpm_init(); + TPM_CHECK(tpm_startup(TPM_ST_CLEAR)); + TPM_CHECK(tpm_continue_self_test()); + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); + TPM_CHECK(tpm_nv_write_value(INDEX0, (uint8_t *)&x, sizeof(x))); + printf("done\n"); + return 0; +} + +static int test_enable(void) +{ + uint8_t disable = 0, deactivated = 0; + + printf("Testing enable ...\n"); + tpm_init(); + TPM_CHECK(TlclStartupIfNeeded()); + TPM_CHECK(tpm_self_test_full()); + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); + TPM_CHECK(tpm_get_flags(&disable, &deactivated, NULL)); + printf("\tdisable is %d, deactivated is %d\n", disable, deactivated); + TPM_CHECK(tpm_physical_enable()); + TPM_CHECK(tpm_physical_set_deactivated(0)); + TPM_CHECK(tpm_get_flags(&disable, &deactivated, NULL)); + printf("\tdisable is %d, deactivated is %d\n", disable, deactivated); + if (disable == 1 || deactivated == 1) + printf("\tfailed to enable or activate\n"); + printf("\tdone\n"); + return 0; +} + +#define reboot() do { \ + printf("\trebooting...\n"); \ + reset_cpu(0); \ +} while (0) + +static int test_fast_enable(void) +{ + uint8_t disable = 0, deactivated = 0; + int i; + + printf("Testing fastenable ...\n"); + tpm_init(); + TPM_CHECK(TlclStartupIfNeeded()); + TPM_CHECK(tpm_self_test_full()); + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); + TPM_CHECK(tpm_get_flags(&disable, &deactivated, NULL)); + printf("\tdisable is %d, deactivated is %d\n", disable, deactivated); + for (i = 0; i < 2; i++) { + TPM_CHECK(tpm_force_clear()); + TPM_CHECK(tpm_get_flags(&disable, &deactivated, NULL)); + printf("\tdisable is %d, deactivated is %d\n", disable, + deactivated); + assert(disable == 1 && deactivated == 1); + TPM_CHECK(tpm_physical_enable()); + TPM_CHECK(tpm_physical_set_deactivated(0)); + TPM_CHECK(tpm_get_flags(&disable, &deactivated, NULL)); + printf("\tdisable is %d, deactivated is %d\n", disable, + deactivated); + assert(disable == 0 && deactivated == 0); + } + printf("\tdone\n"); + return 0; +} + +static int test_global_lock(void) +{ + uint32_t zero = 0; + uint32_t result; + uint32_t x; + + printf("Testing globallock ...\n"); + tpm_init(); + TPM_CHECK(TlclStartupIfNeeded()); + TPM_CHECK(tpm_self_test_full()); + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); + TPM_CHECK(tpm_nv_read_value(INDEX0, (uint8_t *)&x, sizeof(x))); + TPM_CHECK(tpm_nv_write_value(INDEX0, (uint8_t *)&zero, + sizeof(uint32_t))); + TPM_CHECK(tpm_nv_read_value(INDEX1, (uint8_t *)&x, sizeof(x))); + TPM_CHECK(tpm_nv_write_value(INDEX1, (uint8_t *)&zero, + sizeof(uint32_t))); + TPM_CHECK(tpm_set_global_lock()); + /* Verifies that write to index0 fails */ + x = 1; + result = tpm_nv_write_value(INDEX0, (uint8_t *)&x, sizeof(x)); + assert(result == TPM_AREA_LOCKED); + TPM_CHECK(tpm_nv_read_value(INDEX0, (uint8_t *)&x, sizeof(x))); + assert(x == 0); + /* Verifies that write to index1 is still possible */ + x = 2; + TPM_CHECK(tpm_nv_write_value(INDEX1, (uint8_t *)&x, sizeof(x))); + TPM_CHECK(tpm_nv_read_value(INDEX1, (uint8_t *)&x, sizeof(x))); + assert(x == 2); + /* Turns off PP */ + tpm_tsc_physical_presence(PHYS_PRESENCE); + /* Verifies that write to index1 fails */ + x = 3; + result = tpm_nv_write_value(INDEX1, (uint8_t *)&x, sizeof(x)); + assert(result == TPM_BAD_PRESENCE); + TPM_CHECK(tpm_nv_read_value(INDEX1, (uint8_t *)&x, sizeof(x))); + assert(x == 2); + printf("\tdone\n"); + return 0; +} + +static int test_lock(void) +{ + printf("Testing lock ...\n"); + tpm_init(); + tpm_startup(TPM_ST_CLEAR); + tpm_self_test_full(); + tpm_tsc_physical_presence(PRESENCE); + tpm_nv_write_value_lock(INDEX0); + printf("\tLocked 0x%x\n", INDEX0); + printf("\tdone\n"); + return 0; +} + +static void initialise_spaces(void) +{ + uint32_t zero = 0; + uint32_t perm = TPM_NV_PER_WRITE_STCLEAR | TPM_NV_PER_PPWRITE; + + printf("\tInitialising spaces\n"); + tpm_nv_set_locked(); /* useful only the first time */ + tpm_nv_define_space(INDEX0, perm, 4); + tpm_nv_write_value(INDEX0, (uint8_t *)&zero, 4); + tpm_nv_define_space(INDEX1, perm, 4); + tpm_nv_write_value(INDEX1, (uint8_t *)&zero, 4); + tpm_nv_define_space(INDEX2, perm, 4); + tpm_nv_write_value(INDEX2, (uint8_t *)&zero, 4); + tpm_nv_define_space(INDEX3, perm, 4); + tpm_nv_write_value(INDEX3, (uint8_t *)&zero, 4); + perm = TPM_NV_PER_READ_STCLEAR | TPM_NV_PER_WRITE_STCLEAR | + TPM_NV_PER_PPWRITE; + tpm_nv_define_space(INDEX_INITIALISED, perm, 1); +} + +static int test_readonly(void) +{ + uint8_t c; + uint32_t index_0, index_1, index_2, index_3; + int read0, read1, read2, read3; + + printf("Testing readonly ...\n"); + tpm_init(); + tpm_startup(TPM_ST_CLEAR); + tpm_self_test_full(); + tpm_tsc_physical_presence(PRESENCE); + /* + * Checks if initialisation has completed by trying to read-lock a + * space that's created at the end of initialisation + */ + if (tpm_nv_read_value(INDEX_INITIALISED, &c, 0) == TPM_BADINDEX) { + /* The initialisation did not complete */ + initialise_spaces(); + } + + /* Checks if spaces are OK or messed up */ + read0 = tpm_nv_read_value(INDEX0, (uint8_t *)&index_0, sizeof(index_0)); + read1 = tpm_nv_read_value(INDEX1, (uint8_t *)&index_1, sizeof(index_1)); + read2 = tpm_nv_read_value(INDEX2, (uint8_t *)&index_2, sizeof(index_2)); + read3 = tpm_nv_read_value(INDEX3, (uint8_t *)&index_3, sizeof(index_3)); + if (read0 || read1 || read2 || read3) { + printf("Invalid contents\n"); + return 0; + } + + /* + * Writes space, and locks it. Then attempts to write again. + * I really wish I could use the imperative. + */ + index_0 += 1; + if (tpm_nv_write_value(INDEX0, (uint8_t *)&index_0, sizeof(index_0) != + TPM_SUCCESS)) { + error("\tcould not write index 0\n"); + } + tpm_nv_write_value_lock(INDEX0); + if (tpm_nv_write_value(INDEX0, (uint8_t *)&index_0, sizeof(index_0)) == + TPM_SUCCESS) + error("\tindex 0 is not locked\n"); + + printf("\tdone\n"); + return 0; +} + +static int test_redefine_unowned(void) +{ + uint32_t perm; + uint32_t result; + uint32_t x; + + printf("Testing redefine_unowned ..."); + tpm_init(); + TPM_CHECK(TlclStartupIfNeeded()); + TPM_CHECK(tpm_self_test_full()); + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); + assert(!tpm_is_owned()); + + /* Ensures spaces exist. */ + TPM_CHECK(tpm_nv_read_value(INDEX0, (uint8_t *)&x, sizeof(x))); + TPM_CHECK(tpm_nv_read_value(INDEX1, (uint8_t *)&x, sizeof(x))); + + /* Redefines spaces a couple of times. */ + perm = TPM_NV_PER_PPWRITE | TPM_NV_PER_GLOBALLOCK; + TPM_CHECK(tpm_nv_define_space(INDEX0, perm, 2 * sizeof(uint32_t))); + TPM_CHECK(tpm_nv_define_space(INDEX0, perm, sizeof(uint32_t))); + perm = TPM_NV_PER_PPWRITE; + TPM_CHECK(tpm_nv_define_space(INDEX1, perm, 2 * sizeof(uint32_t))); + TPM_CHECK(tpm_nv_define_space(INDEX1, perm, sizeof(uint32_t))); + + /* Sets the global lock */ + tpm_set_global_lock(); + + /* Verifies that index0 cannot be redefined */ + result = tpm_nv_define_space(INDEX0, perm, sizeof(uint32_t)); + assert(result == TPM_AREA_LOCKED); + + /* Checks that index1 can */ + TPM_CHECK(tpm_nv_define_space(INDEX1, perm, 2 * sizeof(uint32_t))); + TPM_CHECK(tpm_nv_define_space(INDEX1, perm, sizeof(uint32_t))); + + /* Turns off PP */ + tpm_tsc_physical_presence(PHYS_PRESENCE); + + /* Verifies that neither index0 nor index1 can be redefined */ + result = tpm_nv_define_space(INDEX0, perm, sizeof(uint32_t)); + assert(result == TPM_BAD_PRESENCE); + result = tpm_nv_define_space(INDEX1, perm, sizeof(uint32_t)); + assert(result == TPM_BAD_PRESENCE); + + printf("done\n"); + return 0; +} + +#define PERMPPGL (TPM_NV_PER_PPWRITE | TPM_NV_PER_GLOBALLOCK) +#define PERMPP TPM_NV_PER_PPWRITE + +static int test_space_perm(void) +{ + uint32_t perm; + + printf("Testing spaceperm ..."); + tpm_init(); + TPM_CHECK(TlclStartupIfNeeded()); + TPM_CHECK(tpm_continue_self_test()); + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); + TPM_CHECK(tpm_get_permissions(INDEX0, &perm)); + assert((perm & PERMPPGL) == PERMPPGL); + TPM_CHECK(tpm_get_permissions(INDEX1, &perm)); + assert((perm & PERMPP) == PERMPP); + printf("done\n"); + return 0; +} + +static int test_startup(void) +{ + uint32_t result; + printf("Testing startup ...\n"); + + tpm_init(); + result = tpm_startup(TPM_ST_CLEAR); + if (result != 0 && result != TPM_INVALID_POSTINIT) + printf("\ttpm startup failed with 0x%x\n", result); + result = tpm_get_flags(NULL, NULL, NULL); + if (result != 0) + printf("\ttpm getflags failed with 0x%x\n", result); + printf("\texecuting SelfTestFull\n"); + tpm_self_test_full(); + result = tpm_get_flags(NULL, NULL, NULL); + if (result != 0) + printf("\ttpm getflags failed with 0x%x\n", result); + printf("\tdone\n"); + return 0; +} + +/* + * Runs [op] and ensures it returns success and doesn't run longer than + * [time_limit] in milliseconds. + */ +#define TTPM_CHECK(op, time_limit) do { \ + ulong start, time; \ + uint32_t __result; \ + \ + start = get_timer(0); \ + __result = op; \ + if (__result != TPM_SUCCESS) { \ + printf("\t" #op ": error 0x%x\n", __result); \ + return -1; \ + } \ + time = get_timer(start); \ + printf("\t" #op ": %lu ms\n", time); \ + if (time > (ulong)time_limit) { \ + printf("\t" #op " exceeded " #time_limit " ms\n"); \ + } \ +} while (0) + + +static int test_timing(void) +{ + uint32_t x; + uint8_t in[20], out[20]; + + printf("Testing timing ..."); + tpm_init(); + TTPM_CHECK(TlclStartupIfNeeded(), 50); + TTPM_CHECK(tpm_continue_self_test(), 100); + TTPM_CHECK(tpm_self_test_full(), 1000); + TTPM_CHECK(tpm_tsc_physical_presence(PRESENCE), 100); + TTPM_CHECK(tpm_nv_write_value(INDEX0, (uint8_t *)&x, sizeof(x)), 100); + TTPM_CHECK(tpm_nv_read_value(INDEX0, (uint8_t *)&x, sizeof(x)), 100); + TTPM_CHECK(tpm_extend(0, in, out), 200); + TTPM_CHECK(tpm_set_global_lock(), 50); + TTPM_CHECK(tpm_tsc_physical_presence(PHYS_PRESENCE), 100); + printf("done\n"); + return 0; +} + +#define TPM_MAX_NV_WRITES_NOOWNER 64 + +static int test_write_limit(void) +{ + printf("Testing writelimit ...\n"); + int i; + uint32_t result; + + tpm_init(); + TPM_CHECK(TlclStartupIfNeeded()); + TPM_CHECK(tpm_self_test_full()); + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); + TPM_CHECK(tpm_force_clear()); + TPM_CHECK(tpm_physical_enable()); + TPM_CHECK(tpm_physical_set_deactivated(0)); + + for (i = 0; i < TPM_MAX_NV_WRITES_NOOWNER + 2; i++) { + printf("\twriting %d\n", i); + result = tpm_nv_write_value(INDEX0, (uint8_t *)&i, sizeof(i)); + switch (result) { + case TPM_SUCCESS: + break; + case TPM_MAXNVWRITES: + assert(i >= TPM_MAX_NV_WRITES_NOOWNER); + default: + error("\tunexpected error code %d (0x%x)\n", + result, result); + } + } + + /* Reset write count */ + TPM_CHECK(tpm_force_clear()); + TPM_CHECK(tpm_physical_enable()); + TPM_CHECK(tpm_physical_set_deactivated(0)); + + /* Try writing again. */ + TPM_CHECK(tpm_nv_write_value(INDEX0, (uint8_t *)&i, sizeof(i))); + printf("\tdone\n"); + return 0; +} + +#define VOIDTEST(XFUNC) \ + int do_test_##XFUNC(cmd_tbl_t *cmd_tbl, int flag, int argc, \ + char * const argv[]) \ + { \ + return test_##XFUNC(); \ + } + +#define VOIDENT(XNAME) \ + U_BOOT_CMD_MKENT(XNAME, 0, 1, do_test_##XNAME, "", ""), + +VOIDTEST(early_extend) +VOIDTEST(early_nvram) +VOIDTEST(early_nvram2) +VOIDTEST(enable) +VOIDTEST(fast_enable) +VOIDTEST(global_lock) +VOIDTEST(lock) +VOIDTEST(readonly) +VOIDTEST(redefine_unowned) +VOIDTEST(space_perm) +VOIDTEST(startup) +VOIDTEST(timing) +VOIDTEST(write_limit) +VOIDTEST(timer) + +static cmd_tbl_t cmd_cros_tpm_sub[] = { + VOIDENT(early_extend) + VOIDENT(early_nvram) + VOIDENT(early_nvram2) + VOIDENT(enable) + VOIDENT(fast_enable) + VOIDENT(global_lock) + VOIDENT(lock) + VOIDENT(readonly) + VOIDENT(redefine_unowned) + VOIDENT(space_perm) + VOIDENT(startup) + VOIDENT(timing) + VOIDENT(write_limit) + VOIDENT(timer) +}; + +static int do_tpmtest(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) +{ + cmd_tbl_t *c; + + printf("argc = %d, argv = ", argc); + do { + int i = 0; + + for (i = 0; i < argc; i++) + printf(" %s", argv[i]); + printf("\n------\n"); + } while (0); + argc--; + argv++; + c = find_cmd_tbl(argv[0], cmd_cros_tpm_sub, + ARRAY_SIZE(cmd_cros_tpm_sub)); + return c ? c->cmd(cmdtp, flag, argc, argv) : cmd_usage(cmdtp); +} + +U_BOOT_CMD(tpmtest, 2, 1, do_tpmtest, "TPM tests", + "\n\tearly_extend\n" + "\tearly_nvram\n" + "\tearly_nvram2\n" + "\tenable\n" + "\tfast_enable\n" + "\tglobal_lock\n" + "\tlock\n" + "\treadonly\n" + "\tredefine_unowned\n" + "\tspace_perm\n" + "\tstartup\n" + "\ttiming\n" + "\twrite_limit\n"); |