From 3f9c713d3bff4abf417edf0f74e0b049bfd1b31f Mon Sep 17 00:00:00 2001 From: jetwhiz Date: Fri, 3 May 2019 08:03:45 -0400 Subject: [PATCH] Add ability to check quotes and output PCR values for quotes Add new tpm2_checkquote tool for checking quotes Pull shared functionality from tpm2_pcrlist into pcr library is_pcr_select_bit_set moved into tpm2_util library Add new functionality to openssl, pcr and util libraries The tpm2_quote tool can now output PCR hash lists Signed-off-by: jetwhiz --- CHANGELOG.md | 2 + Makefile.am | 3 + lib/pcr.c | 232 ++++++++++++++++ lib/pcr.h | 27 ++ lib/tpm2_openssl.c | 163 +++++++++++ lib/tpm2_openssl.h | 57 ++++ lib/tpm2_util.c | 148 ++++++++++ lib/tpm2_util.h | 33 +++ man/tpm2_checkquote.1.md | 95 +++++++ man/tpm2_quote.1.md | 9 +- test/system/test_tpm2_checkquote.sh | 86 ++++++ test/system/test_tpm2_quote.sh | 12 +- tools/tpm2_checkquote.c | 409 ++++++++++++++++++++++++++++ tools/tpm2_pcrlist.c | 205 +------------- tools/tpm2_quote.c | 124 ++++++++- 15 files changed, 1398 insertions(+), 207 deletions(-) create mode 100644 man/tpm2_checkquote.1.md create mode 100755 test/system/test_tpm2_checkquote.sh create mode 100644 tools/tpm2_checkquote.c diff --git a/CHANGELOG.md b/CHANGELOG.md index a8e4f39afde..7e83c6b7e6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Changelog ### 3.2.0 - next +* tpm2_checkquote: Introduce new tool for checking validity of quotes. +* tpm2_quote: Add ability to output PCR values for quotes. * tpm2_makecredential: add support for executing tool off-TPM. * tpm2_pcrreset: introduce new tool for resetting PCRs. * tpm2_quote: Fix AK auth password not being used. diff --git a/Makefile.am b/Makefile.am index 2195537ce01..854c24a03e3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -59,6 +59,7 @@ LDADD = \ # keep me sorted bin_PROGRAMS = \ + tools/tpm2_checkquote \ tools/tpm2_activatecredential \ tools/tpm2_certify \ tools/tpm2_create \ @@ -145,6 +146,7 @@ lib_libcommon_a_SOURCES = \ TOOL_SRC := tools/tpm2_tool.c tools/tpm2_tool.h +tools_tpm2_checkquote_SOURCES = tools/tpm2_checkquote.c $(TOOL_SRC) tools_tpm2_create_SOURCES = tools/tpm2_create.c $(TOOL_SRC) tools_tpm2_createprimary_SOURCES = tools/tpm2_createprimary.c $(TOOL_SRC) tools_tpm2_getcap_SOURCES = tools/tpm2_getcap.c $(TOOL_SRC) @@ -259,6 +261,7 @@ if HAVE_MAN_PAGES man1_MANS := \ man/man1/tpm2_activatecredential.1 \ man/man1/tpm2_certify.1 \ + man/man1/tpm2_checkquote.1 \ man/man1/tpm2_create.1 \ man/man1/tpm2_createpolicy.1 \ man/man1/tpm2_createprimary.1 \ diff --git a/lib/pcr.c b/lib/pcr.c index 5552b336f66..9b00dd6e0e3 100644 --- a/lib/pcr.c +++ b/lib/pcr.c @@ -33,13 +33,21 @@ #include #include +#include #include #include "pcr.h" #include "log.h" +#include "tpm2_tool.h" #include "tpm2_util.h" #include "tpm2_alg_util.h" +static inline void set_pcr_select_size(TPMS_PCR_SELECTION *pcr_selection, + UINT8 size) { + + pcr_selection->sizeofSelect = size; +} + static int pcr_get_id(const char *arg, UINT32 *pcrId) { UINT32 n = 0; @@ -96,6 +104,111 @@ static bool pcr_parse_selection(const char *str, size_t len, TPMS_PCR_SELECTION return true; } +static void shrink_pcr_selection(TPML_PCR_SELECTION *s) { + + UINT32 i, j; + + //seek for the first empty item + for (i = 0; i < s->count; i++) + if (!s->pcrSelections[i].hash) + break; + j = i + 1; + + for (; i < s->count; i++) { + if (!s->pcrSelections[i].hash) { + for (; j < s->count; j++) + if (s->pcrSelections[j].hash) + break; + if (j >= s->count) + break; + + memcpy(&s->pcrSelections[i], &s->pcrSelections[j], sizeof(s->pcrSelections[i])); + s->pcrSelections[j].hash = 0; + j++; + } + } + + s->count = i; +} + +static void pcr_update_pcr_selections(TPML_PCR_SELECTION *s1, TPML_PCR_SELECTION *s2) { + UINT32 i1, i2, j; + for (i2 = 0; i2 < s2->count; i2++) { + for (i1 = 0; i1 < s1->count; i1++) { + if (s2->pcrSelections[i2].hash != s1->pcrSelections[i1].hash) + continue; + + for (j = 0; j < s1->pcrSelections[i1].sizeofSelect; j++) + s1->pcrSelections[i1].pcrSelect[j] &= + ~s2->pcrSelections[i2].pcrSelect[j]; + } + } +} + +static bool pcr_unset_pcr_sections(TPML_PCR_SELECTION *s) { + UINT32 i, j; + for (i = 0; i < s->count; i++) { + for (j = 0; j < s->pcrSelections[i].sizeofSelect; j++) { + if (s->pcrSelections[i].pcrSelect[j]) { + return false; + } + } + } + + return true; +} + +bool pcr_print_pcr_struct(TPML_PCR_SELECTION *pcrSelect, tpm2_pcrs *pcrs) { + + UINT32 vi = 0, di = 0, i; + bool result = true; + + tpm2_tool_output("pcrs:\n"); + + // Loop through all PCR/hash banks + for (i = 0; i < pcrSelect->count; i++) { + const char *alg_name = tpm2_alg_util_algtostr(pcrSelect->pcrSelections[i].hash); + + tpm2_tool_output(" %s:\n", alg_name); + + // Loop through all PCRs in this bank + UINT8 pcr_id; + for (pcr_id = 0; pcr_id < pcrSelect->pcrSelections[i].sizeofSelect * 8; pcr_id++) { + if (!tpm2_util_is_pcr_select_bit_set(&pcrSelect->pcrSelections[i], + pcr_id)) { + // skip non-selected banks + continue; + } + if (vi >= pcrs->count || di >= pcrs->pcr_values[vi].count) { + LOG_ERR("Something wrong, trying to print but nothing more"); + return false; + } + + // Print out PCR ID + tpm2_tool_output(" %-2d: 0x", pcr_id); + + // Print out current PCR digest value + TPM2B_DIGEST *b = &pcrs->pcr_values[vi].digests[di]; + int k; + for (k = 0; k < b->size; k++) { + tpm2_tool_output("%02X", b->buffer[k]); + } + tpm2_tool_output("\n"); + + if (++di < pcrs->pcr_values[vi].count) { + continue; + } + + di = 0; + if (++vi < pcrs->count) { + continue; + } + } + } + + return result; +} + bool pcr_parse_selections(const char *arg, TPML_PCR_SELECTION *pcrSels) { const char *strLeft = arg; @@ -194,3 +307,122 @@ TSS2_RC get_max_supported_pcrs(TSS2_SYS_CONTEXT *sapi_context, UINT32 *max_pcrs) return TPM2_RC_SUCCESS; } + +bool pcr_get_banks(TSS2_SYS_CONTEXT *sapi_context, TPMS_CAPABILITY_DATA *capability_data, tpm2_algorithm *algs) { + + TPMI_YES_NO more_data; + UINT32 rval; + + rval = TSS2_RETRY_EXP(Tss2_Sys_GetCapability(sapi_context, no_argument, TPM2_CAP_PCRS, no_argument, required_argument, + &more_data, capability_data, 0)); + if (rval != TPM2_RC_SUCCESS) { + LOG_ERR( + "GetCapability: Get PCR allocation status Error. TPM Error:0x%x......", + rval); + return false; + } + + unsigned i; + + // If the TPM support more bank algorithm that we currently + // able to manage, throw an error + if (capability_data->data.assignedPCR.count > sizeof(algs->alg)) { + LOG_ERR("Current implementation does not support more than %zu banks, " + "got %" PRIu32 " banks supported by TPM", + sizeof(algs->alg), + capability_data->data.assignedPCR.count); + return false; + } + + for (i = 0; i < capability_data->data.assignedPCR.count; i++) { + algs->alg[i] = + capability_data->data.assignedPCR.pcrSelections[i].hash; + } + algs->count = capability_data->data.assignedPCR.count; + + return true; +} + +bool pcr_init_pcr_selection(TPMS_CAPABILITY_DATA *cap_data, TPML_PCR_SELECTION *pcr_sel, TPMI_ALG_HASH alg_id) { + + UINT32 i, j; + + pcr_sel->count = 0; + + for (i = 0; i < cap_data->data.assignedPCR.count; i++) { + if (alg_id && (cap_data->data.assignedPCR.pcrSelections[i].hash != alg_id)) + continue; + pcr_sel->pcrSelections[pcr_sel->count].hash = cap_data->data.assignedPCR.pcrSelections[i].hash; + set_pcr_select_size(&pcr_sel->pcrSelections[pcr_sel->count], cap_data->data.assignedPCR.pcrSelections[i].sizeofSelect); + for (j = 0; j < pcr_sel->pcrSelections[pcr_sel->count].sizeofSelect; j++) + pcr_sel->pcrSelections[pcr_sel->count].pcrSelect[j] = cap_data->data.assignedPCR.pcrSelections[i].pcrSelect[j]; + pcr_sel->count++; + } + + if (pcr_sel->count == 0) + return false; + + return true; +} + +bool pcr_check_pcr_selection(TPMS_CAPABILITY_DATA *cap_data, TPML_PCR_SELECTION *pcr_sel) { + + UINT32 i, j, k; + + for (i = 0; i < pcr_sel->count; i++) { + for (j = 0; j < cap_data->data.assignedPCR.count; j++) { + if (pcr_sel->pcrSelections[i].hash == cap_data->data.assignedPCR.pcrSelections[j].hash) { + for (k = 0; k < pcr_sel->pcrSelections[i].sizeofSelect; k++) + pcr_sel->pcrSelections[i].pcrSelect[k] &= cap_data->data.assignedPCR.pcrSelections[j].pcrSelect[k]; + break; + } + } + + if (j >= cap_data->data.assignedPCR.count) { + const char *alg_name = tpm2_alg_util_algtostr(pcr_sel->pcrSelections[i].hash); + LOG_WARN("Ignore unsupported bank/algorithm: %s(0x%04x)", alg_name, pcr_sel->pcrSelections[i].hash); + pcr_sel->pcrSelections[i].hash = 0; //mark it as to be removed + } + } + + shrink_pcr_selection(pcr_sel); + if (pcr_sel->count == 0) + return false; + + return true; +} + +bool pcr_read_pcr_values(TSS2_SYS_CONTEXT *sapi_context, TPML_PCR_SELECTION *pcrSelections, tpm2_pcrs *pcrs) { + + TPML_PCR_SELECTION pcr_selection_tmp; + TPML_PCR_SELECTION pcr_selection_out; + UINT32 pcr_update_counter; + + //1. prepare pcrSelectionIn with g_pcrSelections + memcpy(&pcr_selection_tmp, pcrSelections, sizeof(pcr_selection_tmp)); + + //2. call pcr_read + pcrs->count = 0; + do { + UINT32 rval = TSS2_RETRY_EXP(Tss2_Sys_PCR_Read(sapi_context, no_argument, &pcr_selection_tmp, + &pcr_update_counter, &pcr_selection_out, + &pcrs->pcr_values[pcrs->count], 0)); + + if (rval != TPM2_RC_SUCCESS) { + LOG_ERR("read pcr failed. tpm error 0x%0x", rval); + return -1; + } + + //3. unmask pcrSelectionOut bits from pcrSelectionIn + pcr_update_pcr_selections(&pcr_selection_tmp, &pcr_selection_out); + + //4. goto step 2 if pcrSelctionIn still has bits set + } while (++pcrs->count < sizeof(pcrs->pcr_values) && !pcr_unset_pcr_sections(&pcr_selection_tmp)); + + if (pcrs->count >= sizeof(pcrs->pcr_values) && !pcr_unset_pcr_sections(&pcr_selection_tmp)) { + LOG_ERR("too much pcrs to get! try to split into multiple calls..."); + return false; + } + + return true; +} diff --git a/lib/pcr.h b/lib/pcr.h index ad6946b3c04..82d5dd696d4 100644 --- a/lib/pcr.h +++ b/lib/pcr.h @@ -35,8 +35,35 @@ #include +typedef struct tpm2_algorithm tpm2_algorithm; +struct tpm2_algorithm { + int count; + TPMI_ALG_HASH alg[TPM2_NUM_PCR_BANKS]; +}; + +typedef struct tpm2_pcrs tpm2_pcrs; +struct tpm2_pcrs { + size_t count; + TPML_DIGEST pcr_values[TPM2_MAX_PCRS]; +}; + +/** + * Echo out all PCR banks according to g_pcrSelection & g_pcrs->. + * @param pcrSelect + * Description of which PCR registers are selected. + * @param pcrs + * Struct containing PCR digests. + * @return + * True on success, false otherwise. + */ +bool pcr_print_pcr_struct(TPML_PCR_SELECTION *pcrSelect, tpm2_pcrs *pcrs); + bool pcr_parse_selections(const char *arg, TPML_PCR_SELECTION *pcrSels); bool pcr_parse_list(const char *str, size_t len, TPMS_PCR_SELECTION *pcrSel); TSS2_RC get_max_supported_pcrs(TSS2_SYS_CONTEXT *sapi_context, UINT32 *max_pcrs); +bool pcr_get_banks(TSS2_SYS_CONTEXT *sapi_context, TPMS_CAPABILITY_DATA *capability_data, tpm2_algorithm *algs); +bool pcr_init_pcr_selection(TPMS_CAPABILITY_DATA *cap_data, TPML_PCR_SELECTION *pcr_sel, TPMI_ALG_HASH alg_id); +bool pcr_check_pcr_selection(TPMS_CAPABILITY_DATA *cap_data, TPML_PCR_SELECTION *pcr_sel); +bool pcr_read_pcr_values(TSS2_SYS_CONTEXT *sapi_context, TPML_PCR_SELECTION *pcrSelections, tpm2_pcrs *pcrs); #endif /* SRC_PCR_H_ */ diff --git a/lib/tpm2_openssl.c b/lib/tpm2_openssl.c index 0bfc95bd1ef..8d7314cba8e 100644 --- a/lib/tpm2_openssl.c +++ b/lib/tpm2_openssl.c @@ -44,9 +44,26 @@ #include "files.h" #include "log.h" #include "tpm2_alg_util.h" +#include "tpm_kdfa.h" #include "tpm2_openssl.h" #include "tpm2_util.h" +int tpm2_openssl_halgid_from_tpmhalg(TPMI_ALG_HASH algorithm) { + + switch (algorithm) { + case TPM2_ALG_SHA1: + return NID_sha1; + case TPM2_ALG_SHA256: + return NID_sha256; + case TPM2_ALG_SHA384: + return NID_sha384; + case TPM2_ALG_SHA512: + return NID_sha512; + default: + return NID_sha256; + } + /* no return, not possible */ +} const EVP_MD *tpm2_openssl_halg_from_tpmhalg(TPMI_ALG_HASH algorithm) { @@ -122,6 +139,127 @@ void tpm2_openssl_cipher_free(EVP_CIPHER_CTX *ctx) { #endif } +bool tpm2_openssl_hash_compute_data(TPMI_ALG_HASH halg, + BYTE *buffer, UINT16 length, TPM2B_DIGEST *digest) { + + bool result = false; + + const EVP_MD *md = tpm2_openssl_halg_from_tpmhalg(halg); + if (!md) { + return false; + } + + EVP_MD_CTX *mdctx = EVP_MD_CTX_create(); + if (!mdctx) { + LOG_ERR("%s", get_openssl_err()); + return false; + } + + int rc = EVP_DigestInit_ex(mdctx, md, NULL); + if (!rc) { + LOG_ERR("%s", get_openssl_err()); + goto out; + } + + rc = EVP_DigestUpdate(mdctx, buffer, length); + if (!rc) { + LOG_ERR("%s", get_openssl_err()); + goto out; + } + + unsigned size = EVP_MD_size(md); + rc = EVP_DigestFinal_ex(mdctx, digest->buffer, &size); + if (!rc) { + LOG_ERR("%s", get_openssl_err()); + goto out; + } + + digest->size = size; + + result = true; + +out: + EVP_MD_CTX_destroy(mdctx); + return result; +} + +// show all PCR banks according to g_pcrSelection & g_pcrs-> +bool tpm2_openssl_hash_pcr_banks(TPMI_ALG_HASH hashAlg, + TPML_PCR_SELECTION *pcrSelect, + tpm2_pcrs *pcrs, TPM2B_DIGEST *digest) { + + UINT32 vi = 0, di = 0, i; + bool result = false; + + const EVP_MD *md = tpm2_openssl_halg_from_tpmhalg(hashAlg); + if (!md) { + return false; + } + + EVP_MD_CTX *mdctx = EVP_MD_CTX_create(); + if (!mdctx) { + LOG_ERR("%s", get_openssl_err()); + return false; + } + + int rc = EVP_DigestInit_ex(mdctx, md, NULL); + if (!rc) { + LOG_ERR("%s", get_openssl_err()); + goto out; + } + + // Loop through all PCR/hash banks + for (i = 0; i < pcrSelect->count; i++) { + + // Loop through all PCRs in this bank + UINT8 pcr_id; + for (pcr_id = 0; pcr_id < pcrSelect->pcrSelections[i].sizeofSelect * 8; pcr_id++) { + if (!tpm2_util_is_pcr_select_bit_set(&pcrSelect->pcrSelections[i], + pcr_id)) { + // skip non-selected banks + continue; + } + if (vi >= pcrs->count || di >= pcrs->pcr_values[vi].count) { + LOG_ERR("Something wrong, trying to print but nothing more"); + goto out; + } + + // Update running digest (to compare with quote) + TPM2B_DIGEST *b = &pcrs->pcr_values[vi].digests[di]; + rc = EVP_DigestUpdate(mdctx, b->buffer, b->size); + if (!rc) { + LOG_ERR("%s", get_openssl_err()); + goto out; + } + + if (++di < pcrs->pcr_values[vi].count) { + continue; + } + + di = 0; + if (++vi < pcrs->count) { + continue; + } + } + } + + // Finalize running digest + unsigned size = EVP_MD_size(md); + rc = EVP_DigestFinal_ex(mdctx, digest->buffer, &size); + if (!rc) { + LOG_ERR("%s", get_openssl_err()); + goto out; + } + + digest->size = size; + + result = true; + +out: + EVP_MD_CTX_destroy(mdctx); + return result; +} + digester tpm2_openssl_halg_to_digester(TPMI_ALG_HASH halg) { switch(halg) { @@ -160,3 +298,28 @@ digester tpm2_openssl_halg_to_digester(TPMI_ALG_HASH halg) { */ typedef bool (*pfn_ossl_pw_handler)(const char *passin, char **pass); + + +RSA *tpm2_openssl_get_public_RSA_from_pem(FILE *f, const char *path) { + + /* + * Public PEM files appear in two formats: + * 1. PEM format, read with PEM_read_RSA_PUBKEY + * 2. PKCS#1 format, read with PEM_read_RSAPublicKey + * + * See: + * - https://stackoverflow.com/questions/7818117/why-i-cant-read-openssl-generated-rsa-pub-key-with-pem-read-rsapublickey + */ + RSA *pub = PEM_read_RSA_PUBKEY(f, NULL, NULL, NULL); + if (!pub) { + pub = PEM_read_RSAPublicKey(f, NULL, NULL, NULL); + } + + if (!pub) { + ERR_print_errors_fp (stderr); + LOG_ERR("Reading public PEM file \"%s\" failed", path); + return NULL; + } + + return pub; +} diff --git a/lib/tpm2_openssl.h b/lib/tpm2_openssl.h index d749cb350ac..d3f4a0d7a32 100644 --- a/lib/tpm2_openssl.h +++ b/lib/tpm2_openssl.h @@ -34,6 +34,8 @@ #include #include +#include "pcr.h" + #if (OPENSSL_VERSION_NUMBER < 0x1010000fL && !defined(LIBRESSL_VERSION_NUMBER)) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) /* OpenSSL 1.1.0 */ #define LIB_TPM2_OPENSSL_OPENSSL_PRE11 #endif @@ -60,6 +62,16 @@ int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d); */ typedef unsigned char *(*digester)(const unsigned char *d, size_t n, unsigned char *md); +/** + + * Get an openssl hash algorithm ID from a tpm hashing algorithm ID. + * @param algorithm + * The tpm algorithm to get the corresponding openssl version of. + * @return + * The openssl hash algorithm id. + */ +int tpm2_openssl_halgid_from_tpmhalg(TPMI_ALG_HASH algorithm); + /** * Get an openssl message digest from a tpm hashing algorithm. * @param algorithm @@ -86,6 +98,39 @@ EVP_CIPHER_CTX *tpm2_openssl_cipher_new(void); */ void tpm2_openssl_cipher_free(EVP_CIPHER_CTX *ctx); +/** + * Hash a byte buffer. + * @param halg + * The hashing algorithm to use. + * @param buffer + * The byte buffer to be hashed. + * @param length + * The length of the byte buffer to hash. +^ * @param digest +^ * The result of hashing digests with halg. + * @return + * true on success, false on error. + */ +bool tpm2_openssl_hash_compute_data(TPMI_ALG_HASH halg, + BYTE *buffer, UINT16 length, TPM2B_DIGEST *digest); + +/** + * Hash a list of PCR digests, supporting multiple banks. + * @param halg + * The hashing algorithm to use. + * @param pcrSelect + * The list that specifies which PCRs are selected. + * @param pcrs + * The list of PCR banks, each containing a list of PCR digests to hash. +^ * @param digest +^ * The result of hashing digests with halg. + * @return + * true on success, false on error. + */ +bool tpm2_openssl_hash_pcr_banks(TPMI_ALG_HASH hashAlg, + TPML_PCR_SELECTION *pcrSelect, + tpm2_pcrs *pcrs, TPM2B_DIGEST *digest); + /** * Returns a function pointer capable of performing the * given digest from a TPMI_HASH_ALG. @@ -105,4 +150,16 @@ enum tpm2_openssl_load_rc { }; +/** + * Retrieves a public portion of an RSA key from a PEM file. + * + * @param f + * The FILE object that is open for reading the path. + * @param path + * The path to load from. + * @return + * The public structure. + */ +RSA* tpm2_openssl_get_public_RSA_from_pem(FILE *f, const char *path); + #endif /* LIB_TPM2_OPENSSL_H_ */ diff --git a/lib/tpm2_util.c b/lib/tpm2_util.c index 57d6c762a70..edfda4a8b0b 100644 --- a/lib/tpm2_util.c +++ b/lib/tpm2_util.c @@ -41,6 +41,154 @@ #include "tpm2_tool.h" #include "tpm2_util.h" + +bool tpm2_util_get_digest_from_quote(TPM2B_ATTEST *quoted, TPM2B_DIGEST *digest, TPM2B_DATA *extraData) { + TPM2_GENERATED magic; + TPMI_ST_ATTEST type; + UINT16 nameSize = 0; + UINT32 i = 0; + + // Ensure required headers are at least there + if (quoted->size < 6) { + LOG_ERR("Malformed TPM2B_ATTEST headers"); + return false; + } + + memcpy(&magic, "ed->attestationData[i], 4);i += 4; + memcpy(&type, "ed->attestationData[i], 2);i += 2; + if (!tpm2_util_is_big_endian()) { + magic = tpm2_util_endian_swap_32(magic); + type = tpm2_util_endian_swap_16(type); + } + + if (magic != TPM2_GENERATED_VALUE) { + LOG_ERR("Malformed TPM2_GENERATED magic value"); + return false; + } + + if (type != TPM2_ST_ATTEST_QUOTE) { + LOG_ERR("Malformed TPMI_ST_ATTEST quote value"); + return false; + } + + // Qualified signer name (skip) + if (i+2 >= quoted->size) { + LOG_ERR("Malformed TPM2B_NAME value"); + return false; + } + memcpy(&nameSize, "ed->attestationData[i], 2);i += 2; + if (!tpm2_util_is_big_endian()) { + nameSize = tpm2_util_endian_swap_16(nameSize); + } + i += nameSize; + + // Extra data (skip) + if (i+2 >= quoted->size) { + LOG_ERR("Malformed TPM2B_DATA value"); + return false; + } + memcpy(&extraData->size, "ed->attestationData[i], 2);i += 2; + if (!tpm2_util_is_big_endian()) { + extraData->size = tpm2_util_endian_swap_16(extraData->size); + } + if (extraData->size+i > quoted->size) { + LOG_ERR("Malformed extraData TPM2B_DATA value"); + return false; + } + memcpy(&extraData->buffer, "ed->attestationData[i], extraData->size);i += extraData->size; + + // Clock info (skip) + i += 17; + if (i >= quoted->size) { + LOG_ERR("Malformed TPMS_CLOCK_INFO value"); + return false; + } + + // Firmware info (skip) + i += 8; + if (i >= quoted->size) { + LOG_ERR("Malformed firmware version value"); + return false; + } + + // PCR select info + UINT8 sos; + TPMI_ALG_HASH hashAlg; + UINT32 pcrSelCount = 0, j = 0; + if (i+4 >= quoted->size) { + LOG_ERR("Malformed TPML_PCR_SELECTION value"); + return false; + } + memcpy(&pcrSelCount, "ed->attestationData[i], 4);i += 4; + if (!tpm2_util_is_big_endian()) { + pcrSelCount = tpm2_util_endian_swap_32(pcrSelCount); + } + for (j = 0; j < pcrSelCount; j++) { + // Hash + if (i+2 >= quoted->size) { + LOG_ERR("Malformed TPMS_PCR_SELECTION value"); + return false; + } + memcpy(&hashAlg, "ed->attestationData[i], 2);i += 2; + if (!tpm2_util_is_big_endian()) { + hashAlg = tpm2_util_endian_swap_16(hashAlg); + } + + // SizeOfSelected + if (i+1 >= quoted->size) { + LOG_ERR("Malformed TPMS_PCR_SELECTION value"); + return false; + } + memcpy(&sos, "ed->attestationData[i], 1);i += 1; + + // PCR Select (skip) + i += sos; + if (i >= quoted->size) { + LOG_ERR("Malformed TPMS_PCR_SELECTION value"); + return false; + } + } + + // Digest + if (i+2 >= quoted->size) { + LOG_ERR("Malformed TPM2B_DIGEST value"); + return false; + } + memcpy(&digest->size, "ed->attestationData[i], 2);i += 2; + if (!tpm2_util_is_big_endian()) { + digest->size = tpm2_util_endian_swap_16(digest->size); + } + + if (digest->size+i > quoted->size) { + LOG_ERR("Malformed TPM2B_DIGEST value"); + return false; + } + memcpy(&digest->buffer, "ed->attestationData[i], digest->size); + + return true; +} + +// verify that the quote digest equals the digest we calculated +bool tpm2_util_verify_digests(TPM2B_DIGEST *quoteDigest, TPM2B_DIGEST *pcrDigest) { + + // Sanity check -- they should at least be same size! + if (quoteDigest->size != pcrDigest->size) { + LOG_ERR("FATAL ERROR: PCR values failed to match quote's digest!"); + return false; + } + + // Compare running digest with quote's digest + int k; + for (k = 0; k < quoteDigest->size; k++) { + if (quoteDigest->buffer[k] != pcrDigest->buffer[k]) { + LOG_ERR("FATAL ERROR: PCR values failed to match quote's digest!"); + return false; + } + } + + return true; +} + bool tpm2_util_concat_buffer(TPM2B_MAX_BUFFER *result, TPM2B *append) { if (!result || !append) { diff --git a/lib/tpm2_util.h b/lib/tpm2_util.h index e803dc1c30e..8b77c9e5374 100644 --- a/lib/tpm2_util.h +++ b/lib/tpm2_util.h @@ -111,6 +111,30 @@ struct TPM2B { int tpm2_util_hex_to_byte_structure(const char *inStr, UINT16 *byteLenth, BYTE *byteBuffer); +/** + * Pulls the TPM2B_DIGEST out of a TPM2B_ATTEST quote. + * @param quoted + * The attestation quote structure. +^ * @param digest +^ * The digest from the quote. +^ * @param extraData +^ * The extraData from the quote. + * @return + * True on success, false otherwise. + */ +bool tpm2_util_get_digest_from_quote(TPM2B_ATTEST *quoted, TPM2B_DIGEST *digest, TPM2B_DATA *extraData); + +/** + * Compares two digests to ensure they are equal (for validation). + * @param quoteDigest + * The digest from the quote. + * @param pcrDigest + * The digest calculated off-TMP from the PCRs. + * @return + * True on success, false otherwise. + */ +bool tpm2_util_verify_digests(TPM2B_DIGEST *quoteDigest, TPM2B_DIGEST *pcrDigest); + /** * Appends a TPM2B_DIGEST buffer to a TPM2B_MAX buffer. * @param result @@ -170,6 +194,15 @@ static inline void tpm2_util_print_tpm2b(TPM2B *buffer) { void tpm2_util_print_tpm2b(TPM2B *buffer); +/** + * Determines if given PCR value is selected in TPMS_PCR_SELECTION structure. + * @param pcr_selection the TPMS_PCR_SELECTION structure to check pcr against. + * @param pcr the PCR ID to check selection status of. + */ +static inline bool tpm2_util_is_pcr_select_bit_set(TPMS_PCR_SELECTION *pcr_selection, UINT32 pcr) { + return (pcr_selection->pcrSelect[((pcr) / 8)] & (1 << ((pcr) % 8))); +} + /** * Copies a tpm2b from dest to src and clears dest if src is NULL. * If src is NULL, it is a NOP. diff --git a/man/tpm2_checkquote.1.md b/man/tpm2_checkquote.1.md new file mode 100644 index 00000000000..00bb4bee9a7 --- /dev/null +++ b/man/tpm2_checkquote.1.md @@ -0,0 +1,95 @@ +% tpm2_checkquote(1) tpm2-tools | General Commands Manual +% +% JANUARY 2019 + +# NAME + +**tpm2_checkquote**(1) - Validates a quote provided by a TPM. + +# SYNOPSIS + +**tpm2_checkquote** [*OPTIONS*] + +# DESCRIPTION + +**tpm2_checkquote**(1) - Uses the public portion of the provided key to validate a quote +generated by a TPM. This will validate the signature against the quote message and, if +provided, verify that the qualifying data and PCR values match those in the quote. + +# OPTIONS + + * **-c**, **--key-context**=_KEY\_CONTEXT\_OBJECT_: + + Context object for the key context used for the operation. Either a file + or a handle number. See section "Context Object Format". + + * **-G**, **--halg**=_HASH\_ALGORITHM_: + + The hash algorithm used to digest the message. + Algorithms should follow the "formatting standards", see section + "Algorithm Specifiers". + Also, see section "Supported Hash Algorithms" for a list of supported hash + algorithms. + + * **-m**, **--message**=_MSG\_FILE_: + + The quote message that makes up the data that is signed by the TPM. + + * **-s**, **--sig**=_SIG\_FILE_: + + The input signature file of the signature to be validated. + + * **-f**, **--format**: + + Set the input signature file to a specified format. The default is the TPM2.0 **TPMT_SIGNATURE** + data format, however different schemes can be selected if the data came from an external + source like OpenSSL. The tool currently only supports rsassa. + + Algorithms should follow the "formatting standards", see section + "Algorithm Specifiers". + Also, see section "Supported Signing Schemes" for a list of supported hash + algorithms. + + * **-p**, **--pcrs**: + + PCR output file, optional, records the list of PCR values that were included + in the quote. + + * **-q**, **--qualify-data**: + + Data given as a hex string that was used to qualify the quote. This is typically + used to add a nonce against replay attacks. + +[common options](common/options.md) + +[common tcti options](common/tcti.md) + +[context object format](common/ctxobj.md) + +[authorization formatting](common/password.md) + +[supported hash algorithms](common/hash.md) + +[supported signing schemes](common/signschemes.md) + +[algorithm specifiers](common/alg.md) + +# EXAMPLES + +## Generate a quote with a TPM, then verify it +``` +tpm2_createprimary -H e -g sha256 -G rsa -C primary.ctx +tpm2_create -g sha256 -G rsa -u ak.pub -r ak.priv -c primary.ctx +tpm2_load -c primary.ctx -u ak.pub -r ak.priv -n ak.name -C ak.ctx +tpm2_readpublic -c ak.ctx -o akpub.pem -f pem + +tpm2_quote -c ak.ctx -L sha256:15,16,22 -q abc123 -m quote.out -s sig.out -p pcrs.out -G sha256 + +tpm2_checkquote -c akpub.pem -m quote.out -s sig.out -p pcrs.out -G sha256 -q abc123 +``` + +# RETURNS + +0 on success or 1 on failure. + +[footer](common/footer.md) diff --git a/man/tpm2_quote.1.md b/man/tpm2_quote.1.md index 88c37e040c1..491848201d9 100644 --- a/man/tpm2_quote.1.md +++ b/man/tpm2_quote.1.md @@ -53,6 +53,13 @@ Format selection for the signature output file. See section "Signature Format Specifiers". + * **-p**, **--pcrs**: + + PCR output file, optional, records the list of PCR values as defined + by **-l** or **-L**. Note that only the digest of these values is stored in the + signed quote message -- these values themselves are not signed or + stored in the message. + * **-q**, **--qualify-data**: Data given as a Hex string to qualify the quote, optional. This is typically @@ -63,7 +70,7 @@ * **-G**, **--sig-hash-algorithm**: - Hash algorithm for signature. + Hash algorithm for signature. Required if **-p** is given. [common options](common/options.md) diff --git a/test/system/test_tpm2_checkquote.sh b/test/system/test_tpm2_checkquote.sh new file mode 100755 index 00000000000..670e3a737d1 --- /dev/null +++ b/test/system/test_tpm2_checkquote.sh @@ -0,0 +1,86 @@ +#!/bin/bash +#;**********************************************************************; +# +# Copyright (c) 2019 Massachusetts Institute of Technology. +# 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. +#;**********************************************************************; + +source test_helpers.sh + +alg_primary_obj=sha256 +alg_primary_key=rsa +alg_create_obj=sha256 +alg_create_key=rsa + +file_primary_key_ctx=context.p_"$alg_primary_obj"_"$alg_primary_key" +file_quote_key_pub=opu_"$alg_create_obj"_"$alg_create_key" +file_quote_key_priv=opr_"$alg_create_obj"_"$alg_create_key" +file_quote_key_name=name.load_"$alg_primary_obj"_"$alg_primary_key"-"$alg_create_obj"_"$alg_create_key" +file_quote_key_ctx=ctx_load_out_"$alg_primary_obj"_"$alg_primary_key"-"$alg_create_obj"_"$alg_create_key" +output_ak_pub_pem=akpub.pem +output_quote=quote.out +output_quotesig=quotesig.out +output_quotepcr=quotepcr.out + +maxdigest=$(tpm2_getcap -c properties-fixed | grep TPM_PT_MAX_DIGEST | sed -r -e 's/.*(0x[0-9a-f]+)/\1/g') +if ! [[ "$maxdigest" =~ ^(0x)*[0-9]+$ ]] ; then + echo "error: not a number, got: \"$maxdigest\"" >&2 + exit 1 +fi + +nonce=12345abcde12345abcde12345abcde12345abcde12345abcde12345abcde12345abcde12345abcde12345abcde12345abcde +nonce=${nonce:0:2*$maxdigest} + +cleanup() { + rm -f $file_primary_key_ctx $file_quote_key_pub $file_quote_key_priv \ + $file_quote_key_name $file_quote_key_ctx $output_ak_pub_pem \ + $output_quote $output_quotesig $output_quotepcr +} +trap cleanup EXIT + +cleanup + + +tpm2_takeownership -c + +# Key generation +tpm2_createprimary -Q -H e -g $alg_primary_obj -G $alg_primary_key -C $file_primary_key_ctx +tpm2_create -Q -g $alg_create_obj -G $alg_create_key -u $file_quote_key_pub -r $file_quote_key_priv -c $file_primary_key_ctx +tpm2_load -Q -c $file_primary_key_ctx -u $file_quote_key_pub -r $file_quote_key_priv -n $file_quote_key_name -C $file_quote_key_ctx + +# Get the PEM version of pub ak cert +tpm2_readpublic -Q -c $file_quote_key_ctx -o $output_ak_pub_pem -f pem + +# Quoting +tpm2_quote -Q -c $file_quote_key_ctx -L sha256:15,16,22 -q $nonce -m $output_quote -s $output_quotesig -p $output_quotepcr -G $alg_primary_obj + +# Verify quote +tpm2_checkquote -Q -c $output_ak_pub_pem -m $output_quote -s $output_quotesig -p $output_quotepcr -G $alg_primary_obj -q $nonce + +exit 0 diff --git a/test/system/test_tpm2_quote.sh b/test/system/test_tpm2_quote.sh index 231bed326ec..aa06a3d7040 100755 --- a/test/system/test_tpm2_quote.sh +++ b/test/system/test_tpm2_quote.sh @@ -52,6 +52,8 @@ Handle_ek_quote=0x81010017 Handle_ak_quote2=0x81010018 Handle_ak_quote3=0x81010019 +toss_out=junk.out + maxdigest=$(tpm2_getcap -c properties-fixed | grep TPM_PT_MAX_DIGEST | sed -r -e 's/.*(0x[0-9a-f]+)/\1/g') if ! [[ "$maxdigest" =~ ^(0x)*[0-9]+$ ]] ; then echo "error: not a number, got: \"$maxdigest\"" >&2 @@ -69,7 +71,7 @@ trap onerror ERR cleanup() { rm -f $file_primary_key_ctx $file_quote_key_pub $file_quote_key_priv \ - $file_quote_key_name $file_quote_key_ctx ek.pub2 ak.pub2 ak.name_2 \ + $file_quote_key_name $file_quote_key_ctx $toss_out ek.pub2 ak.pub2 ak.name_2 \ tpm2_evictcontrol -Q -Ao -H $Handle_ek_quote 2>/dev/null || true tpm2_evictcontrol -Q -Ao -H $Handle_ak_quote 2>/dev/null || true @@ -90,21 +92,21 @@ tpm2_load -Q -c $file_primary_key_ctx -u $file_quote_key_pub -r $file_quote_ke tpm2_quote -Q -c $file_quote_key_ctx -g $alg_quote -l 16,17,18 -q $nonce -tpm2_quote -Q -c $file_quote_key_ctx -L $alg_quote:16,17,18+$alg_quote1:16,17,18 -q $nonce +tpm2_quote -Q -c $file_quote_key_ctx -L $alg_quote:16,17,18+$alg_quote1:16,17,18 -q $nonce -m $toss_out -s $toss_out -p $toss_out -G $alg_primary_obj #####handle testing tpm2_evictcontrol -Q -A o -c $file_quote_key_ctx -S $Handle_ak_quote -tpm2_quote -Q -k $Handle_ak_quote -g $alg_quote -l 16,17,18 -q $nonce +tpm2_quote -Q -k $Handle_ak_quote -g $alg_quote -l 16,17,18 -q $nonce -m $toss_out -s $toss_out -p $toss_out -G $alg_primary_obj -tpm2_quote -Q -k $Handle_ak_quote -L $alg_quote:16,17,18+$alg_quote1:16,17,18 -q $nonce +tpm2_quote -Q -k $Handle_ak_quote -L $alg_quote:16,17,18+$alg_quote1:16,17,18 -q $nonce -m $toss_out -s $toss_out -p $toss_out -G $alg_primary_obj #####AK tpm2_getpubek -Q -H $Handle_ek_quote -g 0x01 -f ek.pub2 tpm2_getpubak -Q -E $Handle_ek_quote -k $Handle_ak_quote2 -f ak.pub2 -n ak.name_2 -tpm2_quote -Q -k $Handle_ak_quote -g $alg_quote -l 16,17,18 -q $nonce +tpm2_quote -Q -k $Handle_ak_quote -g $alg_quote -l 16,17,18 -q $nonce -m $toss_out -s $toss_out -p $toss_out -G $alg_primary_obj #####AK with password tpm2_getpubak -Q -E $Handle_ek_quote -k $Handle_ak_quote3 -f ak.pub2 -n ak.name_2 -P abc123 diff --git a/tools/tpm2_checkquote.c b/tools/tpm2_checkquote.c new file mode 100644 index 00000000000..0efd7f3ca88 --- /dev/null +++ b/tools/tpm2_checkquote.c @@ -0,0 +1,409 @@ +//**********************************************************************; +// Copyright (c) 2019 Massachusetts Institute of Technology. +// 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 +#include +#include +#include + +#include + +#include + +#include "files.h" +#include "log.h" +#include "pcr.h" +#include "tpm2_alg_util.h" +#include "conversion.h" +#include "tpm_hash.h" +#include "tpm2_openssl.h" +#include "tpm2_options.h" +#include "tpm2_tool.h" +#include "tpm2_util.h" + +typedef struct tpm2_verifysig_ctx tpm2_verifysig_ctx; +struct tpm2_verifysig_ctx { + union { + struct { + UINT8 halg :1; + UINT8 msg :1; + UINT8 sig :1; + UINT8 pcr :1; + UINT8 extra :1; + UINT8 key_context :1; + UINT8 fmt; + }; + UINT8 all; + } flags; + TPMI_ALG_SIG_SCHEME format; + TPMI_ALG_HASH halg; + TPM2B_DIGEST msgHash; + TPM2B_DIGEST pcrHash; + TPM2B_DIGEST quoteHash; + TPM2B_DATA quoteExtraData; + TPM2B_DATA extraData; + TPMT_SIGNATURE signature; + char *msg_file_path; + char *sig_file_path; + char *out_file_path; + char *pcr_file_path; + const char *pubkey_file_path; +}; + +tpm2_verifysig_ctx ctx = { + .format = TPM2_ALG_ERROR, + .halg = TPM2_ALG_SHA1, + .msgHash = TPM2B_TYPE_INIT(TPM2B_DIGEST, buffer), + .pcrHash = TPM2B_TYPE_INIT(TPM2B_DIGEST, buffer), + .quoteHash = TPM2B_TYPE_INIT(TPM2B_DIGEST, buffer), + .quoteExtraData = TPM2B_TYPE_INIT(TPM2B_DATA, buffer), + .extraData = TPM2B_TYPE_INIT(TPM2B_DATA, buffer), +}; + +static bool verify_signature() { + + bool result = false; + + // Read in the AKpub they provided as an RSA object + FILE *pubkey_input = fopen(ctx.pubkey_file_path, "rb"); + if (!pubkey_input) { + LOG_ERR("Could not open RSA pubkey input file \"%s\" error: \"%s\"", + ctx.pubkey_file_path, strerror(errno)); + return false; + } + RSA *pubKey = tpm2_openssl_get_public_RSA_from_pem(pubkey_input, ctx.pubkey_file_path); + if (pubKey == NULL) { + LOG_ERR("Failed to load RSA public key from file"); + goto err; + } + + // Get the signature ready + if (ctx.signature.sigAlg != TPM2_ALG_RSASSA) { + LOG_ERR("Only RSASSA is supported for signatures"); + goto err; + } + TPM2B_PUBLIC_KEY_RSA sig = ctx.signature.signature.rsassa.sig; + tpm2_tool_output("sigBuffer: "); + tpm2_util_hexdump(sig.buffer, sig.size, true); + tpm2_tool_output("\n"); + + // Verify the signature matches message digest + int opensslHash = tpm2_openssl_halgid_from_tpmhalg(ctx.signature.signature.rsassa.hash); + if (!RSA_verify(opensslHash, ctx.msgHash.buffer, ctx.msgHash.size, + sig.buffer, sig.size, pubKey)) { + LOG_ERR("Error validating signed message with public key provided"); + goto err; + } + + // Ensure nonce is the same as given + if (ctx.flags.extra) { + if ( + ctx.quoteExtraData.size != ctx.extraData.size + || memcmp(ctx.quoteExtraData.buffer, ctx.extraData.buffer, ctx.extraData.size) != 0 + ) { + LOG_ERR("Error validating nonce from quote"); + goto err; + } + } + + // Also ensure digest from quote matches PCR digest + if (ctx.flags.pcr) { + if (!tpm2_util_verify_digests(&ctx.quoteHash, &ctx.pcrHash)) { + LOG_ERR("Error validating PCR composite against signed message"); + goto err; + } + } + + result = true; + +err: + if (pubkey_input) { + fclose(pubkey_input); + } + + RSA_free(pubKey); + + return result; +} + +static TPM2B_ATTEST *message_from_file(const char *msg_file_path) { + + unsigned long size; + + bool result = files_get_file_size_path(msg_file_path, &size); + if (!result) { + return NULL; + } + + if (!size) { + LOG_ERR("The msg file \"%s\" is empty", msg_file_path); + return NULL; + } + + TPM2B_ATTEST *msg = (TPM2B_ATTEST *) calloc(1, sizeof(TPM2B_ATTEST) + size); + if (!msg) { + LOG_ERR("OOM"); + return NULL; + } + + UINT16 tmp = msg->size = size; + if (!files_load_bytes_from_path(msg_file_path, msg->attestationData, &tmp)) { + free(msg); + return NULL; + } + return msg; +} + +static bool pcrs_from_file(const char *pcr_file_path, + TPML_PCR_SELECTION *pcrSel, tpm2_pcrs *pcrs) { + + bool result = false; + unsigned long size; + + if (!files_get_file_size_path(pcr_file_path, &size)) { + return false; + } + + if (!size) { + LOG_ERR("The pcr file \"%s\" is empty", pcr_file_path); + return false; + } + + FILE *pcr_input = fopen(pcr_file_path, "rb"); + if (!pcr_input) { + LOG_ERR("Could not open PCRs input file \"%s\" error: \"%s\"", + pcr_file_path, strerror(errno)); + goto out; + } + + // Import TPML_PCR_SELECTION structure to pcr outfile + if (fread(pcrSel, sizeof(TPML_PCR_SELECTION), 1, pcr_input) != 1) { + LOG_ERR("Failed to read PCR selection from file"); + goto out; + } + + // Import PCR digests to pcr outfile + if (fread(&pcrs->count, sizeof(UINT32), 1, pcr_input) != 1) { + LOG_ERR("Failed to read PCR digests header from file"); + goto out; + } + + UINT32 j; + for (j = 0; j < pcrs->count; j++) { + if (fread(&pcrs->pcr_values[j], sizeof(TPML_DIGEST), 1, pcr_input) != 1) { + LOG_ERR("Failed to read PCR digest from file"); + goto out; + } + } + + result = true; + +out: + if (pcr_input) { + fclose(pcr_input); + } + + return result; +} + +static bool init() { + + /* check flags for mismatches */ + if (!(ctx.pubkey_file_path && ctx.flags.sig && ctx.flags.msg && ctx.flags.halg)) { + LOG_ERR( + "--pubkey (-c), --msg (-m), --halg (-g) and --sig (-s) are required"); + return false; + } + + TPM2B_ATTEST *msg = NULL; + TPML_PCR_SELECTION pcrSel; + tpm2_pcrs pcrs; + bool return_value = false; + + if (ctx.flags.msg) { + msg = message_from_file(ctx.msg_file_path); + if (!msg) { + /* message_from_file() logs specific error no need to here */ + return false; + } + } + + if (ctx.flags.sig) { + bool res = files_load_signature(ctx.sig_file_path, &ctx.signature); + if (!res) { + goto err; + } + } + + /* If no digest is specified, compute it */ + if (!ctx.flags.msg) { + /* + * This is a redundant check since main() checks this case, but we'll add it here to silence any + * complainers. + */ + LOG_ERR("No digest set and no message file to compute from, cannot compute message hash!"); + goto err; + } + + if (ctx.flags.pcr) { + if (!pcrs_from_file(ctx.pcr_file_path, &pcrSel, &pcrs)) { + /* pcrs_from_file() logs specific error no need to here */ + goto err; + } + + if (!tpm2_openssl_hash_pcr_banks(ctx.halg, &pcrSel, &pcrs, &ctx.pcrHash)) { + LOG_ERR("Failed to hash PCR values related to quote!"); + goto err; + } + if (!pcr_print_pcr_struct(&pcrSel, &pcrs)) { + LOG_ERR("Failed to print PCR values related to quote!"); + goto err; + } + tpm2_tool_output("calcDigest: "); + tpm2_util_hexdump(ctx.pcrHash.buffer, ctx.pcrHash.size, true); + tpm2_tool_output("\n"); + } + + // Figure out the extra data (nonce) from this message + if (!tpm2_util_get_digest_from_quote(msg, &ctx.quoteHash, &ctx.quoteExtraData)) { + LOG_ERR("Failed to get digest from quote!"); + goto err; + } + + // Figure out the digest for this message + bool res = tpm2_openssl_hash_compute_data(ctx.halg, msg->attestationData, + msg->size, &ctx.msgHash); + if (!res) { + LOG_ERR("Compute message hash failed!"); + goto err; + } + tpm2_tool_output("msgDigest: "); + tpm2_util_hexdump(ctx.msgHash.buffer, ctx.msgHash.size, true); + tpm2_tool_output("\n"); + + return_value = true; + +err: + free(msg); + return return_value; + +} + +static bool on_option(char key, char *value) { + + switch (key) { + case 'c': + ctx.pubkey_file_path = value; + break; + case 'G': { + ctx.halg = tpm2_alg_util_from_optarg(value); + if (ctx.halg == TPM2_ALG_ERROR) { + LOG_ERR("Unable to convert algorithm, got: \"%s\"", value); + return false; + } + ctx.flags.halg = 1; + } + break; + case 'm': { + ctx.msg_file_path = value; + ctx.flags.msg = 1; + } + break; + case 'f': { + ctx.format = tpm2_alg_util_from_optarg(value); + if (ctx.format == TPM2_ALG_ERROR) { + LOG_ERR("Unknown signing scheme, got: \"%s\"", value); + return false; + } + + ctx.flags.fmt = 1; + } break; + case 'q': + ctx.extraData.size = sizeof(ctx.extraData) - 2; + if(tpm2_util_hex_to_byte_structure(value, &ctx.extraData.size, ctx.extraData.buffer) != 0) + { + LOG_ERR("Could not convert \"%s\" from a hex string to byte array!", value); + return false; + } + ctx.flags.extra = 1; + break; + case 's': + ctx.sig_file_path = value; + ctx.flags.sig = 1; + break; + case 'p': + ctx.pcr_file_path = value; + ctx.flags.pcr = 1; + break; + /* no default */ + } + + return true; +} + +bool tpm2_tool_onstart(tpm2_options **opts) { + + const struct option topts[] = { + { "halg", required_argument, NULL, 'G' }, + { "message", required_argument, NULL, 'm' }, + { "format", required_argument, NULL, 'f' }, + { "sig", required_argument, NULL, 's' }, + { "pcrs", required_argument, NULL, 'p' }, + { "pubkey", required_argument, NULL, 'c' }, + { "qualify-data", required_argument, NULL, 'q' }, + }; + + + *opts = tpm2_options_new("G:m:f:s:t:c:p:q:", ARRAY_LEN(topts), topts, + on_option, NULL, TPM2_OPTIONS_NO_SAPI); + + return *opts != NULL; +} + +int tpm2_tool_onrun(TSS2_SYS_CONTEXT *sapi_context, tpm2_option_flags flags) { + + UNUSED(sapi_context); + UNUSED(flags); + + /* initialize and process */ + bool res = init(); + if (!res) { + return 1; + } + + res = verify_signature(); + if (!res) { + LOG_ERR("Verify signature failed!"); + return 1; + } + + return 0; +} diff --git a/tools/tpm2_pcrlist.c b/tools/tpm2_pcrlist.c index 581bcecbd63..9a1ee457ce1 100644 --- a/tools/tpm2_pcrlist.c +++ b/tools/tpm2_pcrlist.c @@ -44,17 +44,6 @@ #include "tpm2_alg_util.h" #include "tpm2_tool.h" -typedef struct tpm2_algorithm tpm2_algorithm; -struct tpm2_algorithm { - int count; - TPMI_ALG_HASH alg[8]; //XXX Why 8? -}; - -typedef struct tpm2_pcrs tpm2_pcrs; -struct tpm2_pcrs { - size_t count; - TPML_DIGEST pcr_values[24]; //XXX Why 24? -}; typedef struct listpcr_context listpcr_context; struct listpcr_context { @@ -85,163 +74,6 @@ static listpcr_context ctx = { }, }; -static inline void set_pcr_select_size(TPMS_PCR_SELECTION *pcr_selection, - UINT8 size) { - - pcr_selection->sizeofSelect = size; -} - -static bool is_pcr_select_bit_set(TPMS_PCR_SELECTION *pcr_selection, UINT32 pcr) { - - return (pcr_selection->pcrSelect[((pcr) / 8)] & (1 << ((pcr) % 8))); -} - -static void update_pcr_selections(TPML_PCR_SELECTION *s1, TPML_PCR_SELECTION *s2) { - - UINT32 i1, i2, j; - for (i2 = 0; i2 < s2->count; i2++) { - for (i1 = 0; i1 < s1->count; i1++) { - if (s2->pcrSelections[i2].hash != s1->pcrSelections[i1].hash) - continue; - - for (j = 0; j < s1->pcrSelections[i1].sizeofSelect; j++) - s1->pcrSelections[i1].pcrSelect[j] &= - ~s2->pcrSelections[i2].pcrSelect[j]; - } - } -} - -static bool unset_pcr_sections(TPML_PCR_SELECTION *s) { - - UINT32 i, j; - for (i = 0; i < s->count; i++) { - for (j = 0; j < s->pcrSelections[i].sizeofSelect; j++) { - if (s->pcrSelections[i].pcrSelect[j]) { - return false; - } - } - } - - return true; -} - -static bool read_pcr_values(TSS2_SYS_CONTEXT *sapi_context) { - - TPML_PCR_SELECTION pcr_selection_tmp; - TPML_PCR_SELECTION pcr_selection_out; - UINT32 pcr_update_counter; - - //1. prepare pcrSelectionIn with g_pcrSelections - memcpy(&pcr_selection_tmp, &ctx.pcr_selections, sizeof(pcr_selection_tmp)); - - //2. call pcr_read - ctx.pcrs.count = 0; - do { - UINT32 rval = TSS2_RETRY_EXP(Tss2_Sys_PCR_Read(sapi_context, no_argument, &pcr_selection_tmp, - &pcr_update_counter, &pcr_selection_out, - &ctx.pcrs.pcr_values[ctx.pcrs.count], 0)); - - if (rval != TPM2_RC_SUCCESS) { - LOG_ERR("read pcr failed. tpm error 0x%0x", rval); - return -1; - } - - //3. unmask pcrSelectionOut bits from pcrSelectionIn - update_pcr_selections(&pcr_selection_tmp, &pcr_selection_out); - - //4. goto step 2 if pcrSelctionIn still has bits set - } while (++ctx.pcrs.count < 24 && !unset_pcr_sections(&pcr_selection_tmp)); - - if (ctx.pcrs.count >= 24 && !unset_pcr_sections(&pcr_selection_tmp)) { - LOG_ERR("too much pcrs to get! try to split into multiple calls..."); - return false; - } - - return true; -} - -static bool init_pcr_selection(void) { - - TPMS_CAPABILITY_DATA *cap_data = &ctx.cap_data; - TPML_PCR_SELECTION *pcr_sel = &ctx.pcr_selections; - UINT32 i, j; - - TPMI_ALG_HASH alg_id = ctx.selected_algorithm; - - pcr_sel->count = 0; - - for (i = 0; i < cap_data->data.assignedPCR.count; i++) { - if (alg_id && (cap_data->data.assignedPCR.pcrSelections[i].hash != alg_id)) - continue; - pcr_sel->pcrSelections[pcr_sel->count].hash = cap_data->data.assignedPCR.pcrSelections[i].hash; - set_pcr_select_size(&pcr_sel->pcrSelections[pcr_sel->count], cap_data->data.assignedPCR.pcrSelections[i].sizeofSelect); - for (j = 0; j < pcr_sel->pcrSelections[pcr_sel->count].sizeofSelect; j++) - pcr_sel->pcrSelections[pcr_sel->count].pcrSelect[j] = cap_data->data.assignedPCR.pcrSelections[i].pcrSelect[j]; - pcr_sel->count++; - } - - if (pcr_sel->count == 0) - return false; - - return true; -} - -static void shrink_pcr_selection(TPML_PCR_SELECTION *s) { - - UINT32 i, j; - - //seek for the first empty item - for (i = 0; i < s->count; i++) - if (!s->pcrSelections[i].hash) - break; - j = i + 1; - - for (; i < s->count; i++) { - if (!s->pcrSelections[i].hash) { - for (; j < s->count; j++) - if (s->pcrSelections[j].hash) - break; - if (j >= s->count) - break; - - memcpy(&s->pcrSelections[i], &s->pcrSelections[j], sizeof(s->pcrSelections[i])); - s->pcrSelections[j].hash = 0; - j++; - } - } - - s->count = i; -} - -static bool check_pcr_selection(void) { - - TPMS_CAPABILITY_DATA *cap_data = &ctx.cap_data; - TPML_PCR_SELECTION *pcr_sel = &ctx.pcr_selections; - UINT32 i, j, k; - - for (i = 0; i < pcr_sel->count; i++) { - for (j = 0; j < cap_data->data.assignedPCR.count; j++) { - if (pcr_sel->pcrSelections[i].hash == cap_data->data.assignedPCR.pcrSelections[j].hash) { - for (k = 0; k < pcr_sel->pcrSelections[i].sizeofSelect; k++) - pcr_sel->pcrSelections[i].pcrSelect[k] &= cap_data->data.assignedPCR.pcrSelections[j].pcrSelect[k]; - break; - } - } - - if (j >= cap_data->data.assignedPCR.count) { - const char *alg_name = tpm2_alg_util_algtostr(pcr_sel->pcrSelections[i].hash); - LOG_WARN("Ignore unsupported bank/algorithm: %s(0x%04x)", alg_name, pcr_sel->pcrSelections[i].hash); - pcr_sel->pcrSelections[i].hash = 0; //mark it as to be removed - } - } - - shrink_pcr_selection(pcr_sel); - if (pcr_sel->count == 0) - return false; - - return true; -} - // show all PCR banks according to g_pcrSelection & g_pcrs-> static bool show_pcr_values(void) { @@ -255,7 +87,7 @@ static bool show_pcr_values(void) { UINT32 pcr_id; for (pcr_id = 0; pcr_id < ctx.pcr_selections.pcrSelections[i].sizeofSelect * 8; pcr_id++) { - if (!is_pcr_select_bit_set(&ctx.pcr_selections.pcrSelections[i], + if (!tpm2_util_is_pcr_select_bit_set(&ctx.pcr_selections.pcrSelections[i], pcr_id)) { continue; } @@ -296,10 +128,10 @@ static bool show_pcr_values(void) { static bool show_selected_pcr_values(TSS2_SYS_CONTEXT *sapi_context, bool check) { - if (check && !check_pcr_selection()) + if (check && !pcr_check_pcr_selection(&ctx.cap_data, &ctx.pcr_selections)) return false; - if (!read_pcr_values(sapi_context)) + if (!pcr_read_pcr_values(sapi_context, &ctx.pcr_selections, &ctx.pcrs)) return false; if (!show_pcr_values()) @@ -310,7 +142,7 @@ static bool show_selected_pcr_values(TSS2_SYS_CONTEXT *sapi_context, bool check) static bool show_all_pcr_values(TSS2_SYS_CONTEXT *sapi_context) { - if (!init_pcr_selection()) + if (!pcr_init_pcr_selection(&ctx.cap_data, &ctx.pcr_selections, ctx.selected_algorithm)) return false; return show_selected_pcr_values(sapi_context, false); @@ -318,37 +150,12 @@ static bool show_all_pcr_values(TSS2_SYS_CONTEXT *sapi_context) { static bool show_alg_pcr_values(TSS2_SYS_CONTEXT *sapi_context) { - if (!init_pcr_selection()) + if (!pcr_init_pcr_selection(&ctx.cap_data, &ctx.pcr_selections, ctx.selected_algorithm)) return false; return show_selected_pcr_values(sapi_context, false); } -static bool get_banks(TSS2_SYS_CONTEXT *sapi_context) { - - TPMI_YES_NO more_data; - TPMS_CAPABILITY_DATA *capability_data = &ctx.cap_data; - UINT32 rval; - - rval = TSS2_RETRY_EXP(Tss2_Sys_GetCapability(sapi_context, no_argument, TPM2_CAP_PCRS, no_argument, required_argument, - &more_data, capability_data, 0)); - if (rval != TPM2_RC_SUCCESS) { - LOG_ERR( - "GetCapability: Get PCR allocation status Error. TPM Error:0x%x......", - rval); - return false; - } - - unsigned i; - for (i = 0; i < capability_data->data.assignedPCR.count; i++) { - ctx.algs.alg[i] = - capability_data->data.assignedPCR.pcrSelections[i].hash; - } - ctx.algs.count = capability_data->data.assignedPCR.count; - - return true; -} - static void show_banks(tpm2_algorithm *g_banks) { tpm2_tool_output("Supported Bank/Algorithm:"); @@ -432,7 +239,7 @@ int tpm2_tool_onrun(TSS2_SYS_CONTEXT *sapi_context, tpm2_option_flags flags) { } } - success = get_banks(sapi_context); + success = pcr_get_banks(sapi_context, &ctx.cap_data, &ctx.algs); if (!success) { goto error; } diff --git a/tools/tpm2_quote.c b/tools/tpm2_quote.c index 05b6d641656..2efba240340 100644 --- a/tools/tpm2_quote.c +++ b/tools/tpm2_quote.c @@ -42,6 +42,7 @@ #include "conversion.h" #include "tpm2_alg_util.h" #include "tpm2_password_util.h" +#include "tpm2_openssl.h" #include "tpm2_tool.h" #include "tpm2_util.h" @@ -54,15 +55,27 @@ static TPMS_AUTH_COMMAND sessionData = TPMS_AUTH_COMMAND_INIT(TPM2_RS_PW); static char *outFilePath; static char *signature_path; static char *message_path; +static char *pcr_path; +static FILE *pcr_output; +static TPMS_CAPABILITY_DATA cap_data; static signature_format sig_format; static TPMI_ALG_HASH sig_hash_algorithm; +static tpm2_algorithm algs = { + .count = 3, + .alg = { + TPM2_ALG_SHA1, + TPM2_ALG_SHA256, + TPM2_ALG_SHA384 + } +}; static TPM2B_DATA qualifyingData = TPM2B_EMPTY_INIT; static TPML_PCR_SELECTION pcrSelections; static bool is_auth_session; static TPMI_SH_AUTH_SESSION auth_session_handle; -static int k_flag, c_flag, l_flag, g_flag, L_flag, o_flag, G_flag, P_flag; +static int k_flag, c_flag, l_flag, g_flag, L_flag, o_flag, G_flag, P_flag, p_flag; static char *contextFilePath; static TPM2_HANDLE akHandle; +static tpm2_pcrs pcrs; static void PrintBuffer( UINT8 *buffer, UINT32 size ) { @@ -74,6 +87,40 @@ static void PrintBuffer( UINT8 *buffer, UINT32 size ) tpm2_tool_output("\n"); } + +// write all PCR banks according to g_pcrSelection & g_pcrs-> +static bool write_pcr_values() { + + // PCR output to file wasn't requested + if (pcr_output == NULL) { + return true; + } + + // Export TPML_PCR_SELECTION structure to pcr outfile + if (fwrite(&pcrSelections, + sizeof(TPML_PCR_SELECTION), 1, + pcr_output) != 1) { + LOG_ERR("write to output file failed: %s", strerror(errno)); + return false; + } + + // Export PCR digests to pcr outfile + if (fwrite(&pcrs.count, sizeof(UINT32), 1, pcr_output) != 1) { + LOG_ERR("write to output file failed: %s", strerror(errno)); + return false; + } + + UINT32 j; + for (j = 0; j < pcrs.count; j++) { + if (fwrite(&pcrs.pcr_values[j], sizeof(TPML_DIGEST), 1, pcr_output) != 1) { + LOG_ERR("write to output file failed: %s", strerror(errno)); + return false; + } + } + + return true; +} + static bool write_output_files(TPM2B_ATTEST *quoted, TPMT_SIGNATURE *signature) { bool res = true; @@ -87,6 +134,8 @@ static bool write_output_files(TPM2B_ATTEST *quoted, TPMT_SIGNATURE *signature) quoted->size); } + res &= write_pcr_values(); + return res; } @@ -125,7 +174,53 @@ static int quote(TSS2_SYS_CONTEXT *sapi_context, TPM2_HANDLE akHandle, TPML_PCR_ PrintBuffer( (UINT8 *)&signature, sizeof(signature) ); //PrintTPMT_SIGNATURE(&signature); + if (pcr_output) { + // Filter out invalid/unavailable PCR selections + if (!pcr_check_pcr_selection(&cap_data, &pcrSelections)) { + LOG_ERR("Failed to filter unavailable PCR values for quote!"); + return false; + } + + // Gather PCR values from the TPM (the quote doesn't have them!) + if (!pcr_read_pcr_values(sapi_context, &pcrSelections, &pcrs)) { + LOG_ERR("Failed to retrieve PCR values related to quote!"); + return false; + } + + // Grab the digest from the quote + TPM2B_DIGEST quoteDigest = TPM2B_TYPE_INIT(TPM2B_DIGEST, buffer); + TPM2B_DATA extraData = TPM2B_TYPE_INIT(TPM2B_DATA, buffer); + if (!tpm2_util_get_digest_from_quote("ed, "eDigest, &extraData)) { + LOG_ERR("Failed to get digest from quote!"); + return false; + } + + // Print out PCR values as output + if (!pcr_print_pcr_struct(&pcrSelections, &pcrs)) { + LOG_ERR("Failed to print PCR values related to quote!"); + return false; + } + + // Calculate the digest from our selected PCR values (to ensure correctness) + TPM2B_DIGEST pcr_digest = TPM2B_TYPE_INIT(TPM2B_DIGEST, buffer); + if (!tpm2_openssl_hash_pcr_banks(sig_hash_algorithm, &pcrSelections, &pcrs, &pcr_digest)) { + LOG_ERR("Failed to hash PCR values related to quote!"); + return false; + } + tpm2_tool_output("calcDigest: "); + tpm2_util_hexdump(pcr_digest.buffer, pcr_digest.size, true); + tpm2_tool_output("\n"); + + // Make sure digest from quote matches calculated PCR digest + if (!tpm2_util_verify_digests("eDigest, &pcr_digest)) { + LOG_ERR("Error validating calculated PCR composite with quote"); + return false; + } + } + + // Write everything out bool res = write_output_files("ed, &signature); + return res == true ? 0 : 1; } @@ -206,6 +301,10 @@ static bool on_option(char key, char *value) { case 'm': message_path = optarg; break; + case 'p': + pcr_path = optarg; + p_flag = 1; + break; case 'f': sig_format = tpm2_parse_signature_format(optarg); @@ -239,11 +338,12 @@ bool tpm2_tool_onstart(tpm2_options **opts) { { "input-session-handle", required_argument, NULL, 'S' }, { "signature", required_argument, NULL, 's' }, { "message", required_argument, NULL, 'm' }, + { "pcrs", required_argument, NULL, 'p' }, { "format", required_argument, NULL, 'f' }, { "sig-hash-algorithm", required_argument, NULL, 'G' } }; - *opts = tpm2_options_new("k:c:P:l:g:L:S:q:s:m:f:G:", ARRAY_LEN(topts), topts, + *opts = tpm2_options_new("k:c:P:l:g:L:S:q:s:m:p:f:G:", ARRAY_LEN(topts), topts, on_option, NULL, TPM2_OPTIONS_SHOW_USAGE); return *opts != NULL; @@ -270,5 +370,25 @@ int tpm2_tool_onrun(TSS2_SYS_CONTEXT *sapi_context, tpm2_option_flags flags) { sessionData.hmac.size = 0; } + if (p_flag) { + if (!G_flag) { + LOG_ERR("Must specify -G if -p is requested."); + return -1; + } + pcr_output = fopen(pcr_path, "wb+"); + if (!pcr_output) { + LOG_ERR("Could not open PCR output file \"%s\" error: \"%s\"", + pcr_path, strerror(errno)); + return 1; + } + } + + if (!pcr_get_banks(sapi_context, &cap_data, &algs)) { + if (pcr_output) { + fclose(pcr_output); + } + return 1; + } + return quote(sapi_context, akHandle, &pcrSelections); } -- 2.21.0