Add demo app for attestation service

Adds the platform_inspect deployment that builds a user-space app
that retrieves an attestation token, verifies it and pretty
prints the result.  The platform_inspect app may be extended
to inspect other aspects of the device firmware e.g. a list
of TS service providers.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I98efd0313f15fdfd50d92716341165afdb1f0ff5
diff --git a/components/app/platform-inspect/attest_report_fetcher.cpp b/components/app/platform-inspect/attest_report_fetcher.cpp
new file mode 100644
index 0000000..a600ca4
--- /dev/null
+++ b/components/app/platform-inspect/attest_report_fetcher.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cstring>
+#include <string>
+#include <vector>
+#include <service/attestation/client/psa/iat_client.h>
+#include <service/attestation/client/provision/attest_provision_client.h>
+#include <protocols/rpc/common/packed-c/encoding.h>
+#include <service_locator.h>
+#include <psa/crypto.h>
+#include <psa/initial_attestation.h>
+#include <provision/attest_provision.h>
+#include <qcbor/qcbor_spiffy_decode.h>
+#include <t_cose/t_cose_sign1_verify.h>
+
+#define IAK_KEY_BITS    (256)
+
+static bool fetch_and_verify(std::vector<uint8_t> &report, std::string &error_msg);
+static bool fetch_iak_public_key(psa_key_handle_t &iak_handle, std::string &error_msg);
+static bool verify_token(std::vector<uint8_t> &report, const uint8_t *token, size_t token_len,
+    psa_key_handle_t iak_handle, std::string &error_msg);
+
+bool fetch_attest_report(std::vector<uint8_t> &report, std::string &error_msg)
+{
+    bool success = false;
+    rpc_session_handle rpc_session_handle = NULL;
+    struct service_context *attest_service_context = NULL;
+    int status;
+
+    attest_service_context =
+        service_locator_query("sn:trustedfirmware.org:attestation:0", &status);
+
+    if (attest_service_context) {
+
+        struct rpc_caller *caller = NULL;
+        rpc_session_handle =
+            service_context_open(attest_service_context, TS_RPC_ENCODING_PACKED_C, &caller);
+
+        if (rpc_session_handle) {
+
+            psa_iat_client_init(caller);
+            attest_provision_client_init(caller);
+
+            success = fetch_and_verify(report, error_msg);
+        }
+        else {
+
+            error_msg = "Failed to open RPC session";
+        }
+    }
+    else {
+
+        error_msg = "Failed to discover attestation service provider";
+    }
+
+    /* Clean-up context */
+    psa_iat_client_deinit();
+    attest_provision_client_deinit();
+    service_context_close(attest_service_context, rpc_session_handle);
+    service_context_relinquish(attest_service_context);
+
+    return success;
+}
+
+static bool fetch_and_verify(std::vector<uint8_t> &report, std::string &error_msg)
+{
+    bool success = false;
+    uint8_t token_buf[PSA_INITIAL_ATTEST_MAX_TOKEN_SIZE];
+    uint8_t challenge[PSA_INITIAL_ATTEST_CHALLENGE_SIZE_32];
+    psa_key_handle_t iak_handle;
+    int status;
+
+    if (!fetch_iak_public_key(iak_handle, error_msg)) {
+
+        return false;
+    }
+
+    status = psa_generate_random(challenge, sizeof(challenge));
+
+    if (status != PSA_SUCCESS) {
+
+        error_msg = "Failed to generate challenge";
+        return false;
+    }
+
+    size_t token_size;
+
+    status =
+        psa_initial_attest_get_token(challenge, sizeof(challenge),
+            token_buf, sizeof(token_buf), &token_size);
+
+    if (status == PSA_SUCCESS) {
+
+        success = verify_token(report, token_buf, token_size, iak_handle, error_msg);
+    }
+    else {
+
+        error_msg = "Failed to fetch attestation token";
+    }
+
+    return success;
+}
+
+static bool fetch_iak_public_key(psa_key_handle_t &iak_handle, std::string &error_msg)
+{
+    size_t iak_pub_key_len = 0;
+    uint8_t iak_pub_key_buf[PSA_KEY_EXPORT_MAX_SIZE(
+        PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR(
+            PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_CURVE_SECP256R1)),
+            IAK_KEY_BITS)];
+
+    int status = attest_provision_export_iak_public_key(iak_pub_key_buf,
+        sizeof(iak_pub_key_buf), &iak_pub_key_len);
+
+    if (status == PSA_SUCCESS) {
+
+        psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
+
+        psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_VOLATILE);
+        psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_VERIFY_HASH);
+
+        psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
+        psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_CURVE_SECP256R1));
+        psa_set_key_bits(&attributes, IAK_KEY_BITS);
+
+        status = psa_import_key(&attributes, iak_pub_key_buf, iak_pub_key_len, &iak_handle);
+
+        if (status != PSA_SUCCESS) {
+
+            printf("psa_import_key status: %d\n", status);
+            error_msg = "Failed to set-up IAK for verify";
+        }
+
+        psa_reset_key_attributes(&attributes);
+    }
+    else {
+
+        error_msg = "Failed to export IAK public key";
+    }
+
+    return (status == PSA_SUCCESS);
+}
+
+static bool verify_token(std::vector<uint8_t> &report, const uint8_t *token, size_t token_len,
+    psa_key_handle_t iak_handle, std::string &error_msg)
+{
+    struct t_cose_sign1_verify_ctx verify_ctx;
+    struct t_cose_key key_pair;
+
+    key_pair.k.key_handle = iak_handle;
+    key_pair.crypto_lib = T_COSE_CRYPTO_LIB_PSA;
+    UsefulBufC signed_cose;
+    UsefulBufC report_body;
+
+    signed_cose.ptr = token;
+    signed_cose.len = token_len;
+
+    report_body.ptr = NULL;
+    report_body.len = 0;
+
+    t_cose_sign1_verify_init(&verify_ctx, 0);
+    t_cose_sign1_set_verification_key(&verify_ctx, key_pair);
+
+    int status = t_cose_sign1_verify(&verify_ctx, signed_cose, &report_body, NULL);
+
+    if (status == T_COSE_SUCCESS) {
+
+        report.resize(report_body.len);
+        memcpy(report.data(), report_body.ptr, report_body.len);
+    }
+    else {
+
+        error_msg = "Attestation token failed to verify";
+    }
+
+    return (status == T_COSE_SUCCESS);
+}
diff --git a/components/app/platform-inspect/attest_report_fetcher.h b/components/app/platform-inspect/attest_report_fetcher.h
new file mode 100644
index 0000000..75d171b
--- /dev/null
+++ b/components/app/platform-inspect/attest_report_fetcher.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef ATTEST_REPORT_FETCHER_H
+#define ATTEST_REPORT_FETCHER_H
+
+#include <string>
+#include <vector>
+#include <cstdint>
+
+/** \brief Fetch and verify an attestaton report
+ *
+ * \param[out] report       The CBOR encoded report
+ * \param[out] error_msg    Error message on failure
+ *
+ * \return Returns true if fetch successful
+ */
+bool fetch_attest_report(std::vector<uint8_t> &report, std::string &error_msg);
+
+
+#endif /* ATTEST_REPORT_FETCHER_H */
diff --git a/components/service/attestation/test/common/component.cmake b/components/app/platform-inspect/component.cmake
similarity index 79%
copy from components/service/attestation/test/common/component.cmake
copy to components/app/platform-inspect/component.cmake
index 5985c76..07200a9 100644
--- a/components/service/attestation/test/common/component.cmake
+++ b/components/app/platform-inspect/component.cmake
@@ -9,5 +9,6 @@
 endif()
 
 target_sources(${TGT} PRIVATE
-	"${CMAKE_CURRENT_LIST_DIR}/report_dump.cpp"
+	"${CMAKE_CURRENT_LIST_DIR}/platform_inspect.cpp"
+	"${CMAKE_CURRENT_LIST_DIR}/attest_report_fetcher.cpp"
 	)
diff --git a/components/app/platform-inspect/platform_inspect.cpp b/components/app/platform-inspect/platform_inspect.cpp
new file mode 100644
index 0000000..fd0e05b
--- /dev/null
+++ b/components/app/platform-inspect/platform_inspect.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cstdint>
+#include <cstdio>
+#include <string>
+#include <vector>
+#include <psa/crypto.h>
+#include <service_locator.h>
+#include <service/attestation/reporter/dump/raw/raw_report_dump.h>
+#include <service/attestation/reporter/dump/pretty/pretty_report_dump.h>
+#include "attest_report_fetcher.h"
+
+int main(int argc, char *argv[])
+{
+    int rval = -1;
+
+    psa_crypto_init();
+    service_locator_init();
+
+    /* Fetch platform info */
+    std::string error_msg;
+    std::vector<uint8_t> attest_report;
+
+    if (fetch_attest_report(attest_report, error_msg)) {
+
+        rval = pretty_report_dump(attest_report.data(), attest_report.size());
+    }
+    else {
+
+        printf("%s\n", error_msg.c_str());
+    }
+
+    return rval;
+}
diff --git a/components/common/cbor_dump/cbor_dump.c b/components/common/cbor_dump/cbor_dump.c
new file mode 100644
index 0000000..e711d21
--- /dev/null
+++ b/components/common/cbor_dump/cbor_dump.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <qcbor/qcbor_decode.h>
+#include "cbor_dump.h"
+
+/* Dump context structure */
+struct dump_context
+{
+    FILE *outfile;
+    unsigned int initial_indent;
+    const char *root_label;
+    const struct cbor_dictionary_entry *dictionary;
+    unsigned int dictionary_len;
+};
+
+static int dump_next_item(QCBORDecodeContext *decode_ctx, struct dump_context *dump_ctx);
+static void dump_indent(struct dump_context *dump_ctx, const QCBORItem *item);
+static void dump_label(struct dump_context *dump_ctx, const QCBORItem *item);
+static void dump_value_separator(struct dump_context *dump_ctx, const QCBORItem *item);
+static void dump_value(struct dump_context *dump_ctx, const QCBORItem *item);
+static void dump_text_string(struct dump_context *dump_ctx, const uint8_t *data, size_t len);
+static void dump_byte_string(struct dump_context *dump_ctx, const uint8_t *data, size_t len);
+static const char *dictionary_lookup(struct dump_context *dump_ctx, int64_t id);
+
+int cbor_dump(FILE *file,
+    const uint8_t *cbor, size_t cbor_len,
+    unsigned int indent, const char *root_label,
+    const struct cbor_dictionary_entry *dictionary, unsigned int dictionary_len)
+{
+    int status = -1;
+    UsefulBufC cbor_buf;
+    UsefulBuf mem_pool;
+    uint8_t mem_pool_space[cbor_len + QCBOR_DECODE_MIN_MEM_POOL_SIZE];
+
+    cbor_buf.ptr = cbor;
+    cbor_buf.len = cbor_len;
+
+    mem_pool.ptr = mem_pool_space;
+    mem_pool.len = sizeof(mem_pool_space);
+
+    QCBORDecodeContext decode_ctx;
+
+    QCBORDecode_Init(&decode_ctx, cbor_buf, QCBOR_DECODE_MODE_NORMAL);
+    status = QCBORDecode_SetMemPool(&decode_ctx, mem_pool, true);
+
+    if (status == QCBOR_SUCCESS) {
+
+        struct dump_context dump_ctx;
+
+        dump_ctx.outfile = file;
+        dump_ctx.initial_indent = indent;
+        dump_ctx.root_label = root_label;
+        dump_ctx.dictionary = dictionary;
+        dump_ctx.dictionary_len = dictionary_len;
+
+        while ((status = dump_next_item(&decode_ctx, &dump_ctx)) == QCBOR_SUCCESS);
+
+        // Hitting the end of data is not an error.
+        if (status == QCBOR_ERR_NO_MORE_ITEMS)
+        	status=QCBOR_SUCCESS;
+    }
+
+    return status;
+}
+
+static int dump_next_item(QCBORDecodeContext *decode_ctx, struct dump_context *dump_ctx)
+{
+    int status = -1;
+    QCBORItem item;
+
+    status = QCBORDecode_GetNext(decode_ctx, &item);
+
+    if (status == QCBOR_SUCCESS) {
+
+        dump_indent(dump_ctx, &item);
+        dump_label(dump_ctx, &item);
+        dump_value_separator(dump_ctx, &item);
+        dump_value(dump_ctx, &item);
+    }
+
+    return status;
+}
+
+static void dump_indent(struct dump_context *dump_ctx, const QCBORItem *item)
+{
+    unsigned int num_tabs = dump_ctx->initial_indent + item->uNestingLevel;
+
+    for (unsigned int i = 0; i < num_tabs; ++i) {
+
+        fprintf(dump_ctx->outfile, "    ");
+    }
+}
+
+static void dump_label(struct dump_context *dump_ctx, const QCBORItem *item)
+{
+    switch (item->uLabelType)
+    {
+        case QCBOR_TYPE_INT64:
+        case QCBOR_TYPE_UINT64:
+        {
+            const char *label_string = dictionary_lookup(dump_ctx, item->label.int64);
+            if (label_string)
+                fprintf(dump_ctx->outfile, "%s:", label_string);
+            else
+                fprintf(dump_ctx->outfile, "%ld:", item->label.int64);
+            break;
+        }
+        case QCBOR_TYPE_TEXT_STRING:
+            fprintf(dump_ctx->outfile, "%s:", (const char*)item->label.string.ptr);
+            break;
+        case QCBOR_TYPE_NONE:
+            if (item->uNestingLevel == 0 && dump_ctx->root_label) {
+                fprintf(dump_ctx->outfile, "%s:", dump_ctx->root_label);
+            }
+            break;
+        default:
+            break;
+    }
+}
+
+static void dump_value_separator(struct dump_context *dump_ctx, const QCBORItem *item)
+{
+    if ((item->uDataType == QCBOR_TYPE_ARRAY) ||
+        (item->uDataType == QCBOR_TYPE_MAP)) {
+
+        fprintf(dump_ctx->outfile, "\n");
+    }
+    else {
+
+        fprintf(dump_ctx->outfile, "\t");
+    }
+
+}
+
+static void dump_value(struct dump_context *dump_ctx, const QCBORItem *item)
+{
+    if (item->uDataType == QCBOR_TYPE_TEXT_STRING) {
+
+        dump_text_string(dump_ctx, (const char*)item->val.string.ptr, item->val.string.len);
+        fprintf(dump_ctx->outfile, "\n");
+    }
+    else if (item->uDataType == QCBOR_TYPE_BYTE_STRING) {
+
+        dump_byte_string(dump_ctx, (const char*)item->val.string.ptr, item->val.string.len);
+        fprintf(dump_ctx->outfile, "\n");
+    }
+    else if (item->uDataType == QCBOR_TYPE_INT64) {
+
+        fprintf(dump_ctx->outfile, "%ld\n", item->val.int64);
+    }
+    else if (item->uDataType == QCBOR_TYPE_UINT64) {
+
+        fprintf(dump_ctx->outfile, "%lu\n", item->val.uint64);
+    }
+    else if ((item->uDataType != QCBOR_TYPE_NONE) &&
+        (item->uDataType != QCBOR_TYPE_ARRAY) &&
+        (item->uDataType != QCBOR_TYPE_MAP))
+    {
+
+        fprintf(dump_ctx->outfile, "value %d", item->uDataType);
+    }
+}
+
+static void dump_text_string(struct dump_context *dump_ctx, const uint8_t *data, size_t len)
+{
+    char text_buf[len + 1];
+
+    memcpy(text_buf, data, len);
+    text_buf[len] = '\0';
+
+    fprintf(dump_ctx->outfile, "%s", text_buf);
+}
+
+static void dump_byte_string(struct dump_context *dump_ctx, const uint8_t *data, size_t len)
+{
+    for (size_t i = 0; i < len; ++i) {
+
+        fprintf(dump_ctx->outfile, "%02x ", data[i]);
+    }
+}
+
+static const char *dictionary_lookup(struct dump_context *dump_ctx, int64_t id)
+{
+    const char *match = NULL;
+
+    if (dump_ctx->dictionary) {
+
+        for (size_t i = 0; i < dump_ctx->dictionary_len; ++i) {
+
+            if (dump_ctx->dictionary[i].id == id) {
+
+                match = dump_ctx->dictionary[i].string;
+                break;
+            }
+        }
+    }
+
+    return match;
+}
diff --git a/components/common/cbor_dump/cbor_dump.h b/components/common/cbor_dump/cbor_dump.h
new file mode 100644
index 0000000..a0d8f94
--- /dev/null
+++ b/components/common/cbor_dump/cbor_dump.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef CBOR_DUMP_H
+#define CBOR_DUMP_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Dictionary entry for mapping a CBOR ID to a string.
+ */
+struct cbor_dictionary_entry
+{
+    int32_t     id;
+    const char  *string;
+};
+
+/**
+ * Dump decoded cbor to the specified file.
+ *
+ * \param[in] file          Dump to this file
+ * \param[in] cbor          Serialized cbor to decode
+ * \param[in] cbor_len      Length of the cbor
+ * \param[in] indent        Initial indentation of dump output
+ * \param[in] root_label    Root label or NULL if none.
+ * \param[in] dictionary    Dictionary of IDs to strings.  NULL if none.
+ * \param[in] dictionary_len Number of entries in the dictionary.
+ */
+int cbor_dump(FILE *file,
+    const uint8_t *cbor, size_t cbor_len,
+    unsigned int indent, const char *root_label,
+    const struct cbor_dictionary_entry *dictionary, unsigned int dictionary_len);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* CBOR_DUMP_H */
diff --git a/components/service/attestation/test/common/component.cmake b/components/common/cbor_dump/component.cmake
similarity index 89%
rename from components/service/attestation/test/common/component.cmake
rename to components/common/cbor_dump/component.cmake
index 5985c76..02aef54 100644
--- a/components/service/attestation/test/common/component.cmake
+++ b/components/common/cbor_dump/component.cmake
@@ -9,5 +9,5 @@
 endif()
 
 target_sources(${TGT} PRIVATE
-	"${CMAKE_CURRENT_LIST_DIR}/report_dump.cpp"
+	"${CMAKE_CURRENT_LIST_DIR}/cbor_dump.c"
 	)
diff --git a/components/service/attestation/claims/claim.h b/components/service/attestation/claims/claim.h
index 1808f45..94ff0ad 100644
--- a/components/service/attestation/claims/claim.h
+++ b/components/service/attestation/claims/claim.h
@@ -86,7 +86,7 @@
  */
 struct measurement_claim_variant
 {
-    struct byte_string_claim_variant id;
+    struct text_string_claim_variant id;
     struct byte_string_claim_variant digest;
 };
 
diff --git a/components/service/attestation/claims/sources/event_log/event_log_claim_source.c b/components/service/attestation/claims/sources/event_log/event_log_claim_source.c
index 82603e4..b7273ab 100644
--- a/components/service/attestation/claims/sources/event_log/event_log_claim_source.c
+++ b/components/service/attestation/claims/sources/event_log/event_log_claim_source.c
@@ -253,8 +253,7 @@
                                     struct measurement_claim_variant *measurement,
                                     const void *limit)
 {
-    measurement->id.len = 0;
-    measurement->id.bytes = NULL;
+    measurement->id.string = NULL;
 
     if (((const uint8_t*)limit - sizeof(event2_data_t)) >= (const uint8_t*)event_data) {
 
@@ -262,8 +261,7 @@
 
         if (id_size) {
 
-            measurement->id.len = id_size;
-            measurement->id.bytes = (const uint8_t*)event_data + offsetof(event2_data_t, event);
+            measurement->id.string = (const uint8_t*)event_data + offsetof(event2_data_t, event);
         }
     }
 }
diff --git a/components/service/attestation/claims/sources/event_log/test/tcg_event_log_test.cpp b/components/service/attestation/claims/sources/event_log/test/tcg_event_log_test.cpp
index c4865fc..f155fee 100644
--- a/components/service/attestation/claims/sources/event_log/test/tcg_event_log_test.cpp
+++ b/components/service/attestation/claims/sources/event_log/test/tcg_event_log_test.cpp
@@ -54,9 +54,8 @@
                     mock_event_Log_measurement(measurement_count);
 
             /* Check extracted values are as expected */
-            MEMCMP_EQUAL(expected->id,
-                    sw_component_claim.variant.measurement.id.bytes,
-                    sw_component_claim.variant.measurement.id.len);
+            STRCMP_EQUAL(expected->id,
+                    sw_component_claim.variant.measurement.id.string);
             MEMCMP_EQUAL(expected->digest,
                     sw_component_claim.variant.measurement.digest.bytes,
                     sw_component_claim.variant.measurement.digest.len);
diff --git a/components/service/attestation/test/common/component.cmake b/components/service/attestation/reporter/dump/pretty/component.cmake
similarity index 88%
copy from components/service/attestation/test/common/component.cmake
copy to components/service/attestation/reporter/dump/pretty/component.cmake
index 5985c76..fa6636b 100644
--- a/components/service/attestation/test/common/component.cmake
+++ b/components/service/attestation/reporter/dump/pretty/component.cmake
@@ -9,5 +9,5 @@
 endif()
 
 target_sources(${TGT} PRIVATE
-	"${CMAKE_CURRENT_LIST_DIR}/report_dump.cpp"
+	"${CMAKE_CURRENT_LIST_DIR}/pretty_report_dump.c"
 	)
diff --git a/components/service/attestation/reporter/dump/pretty/pretty_report_dump.c b/components/service/attestation/reporter/dump/pretty/pretty_report_dump.c
new file mode 100644
index 0000000..4b57550
--- /dev/null
+++ b/components/service/attestation/reporter/dump/pretty/pretty_report_dump.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <common/cbor_dump/cbor_dump.h>
+#include <protocols/service/attestation/packed-c/eat.h>
+
+int pretty_report_dump(const uint8_t *report, size_t len)
+{
+    static const struct cbor_dictionary_entry eat_dictionary[] =
+    {
+        {EAT_ARM_PSA_CLAIM_ID_PROFILE_DEFINITION,       "profile_definition"},
+        {EAT_ARM_PSA_CLAIM_ID_CLIENT_ID,                "client_id"},
+        {EAT_ARM_PSA_CLAIM_ID_SECURITY_LIFECYCLE,       "security_lifecycle"},
+        {EAT_ARM_PSA_CLAIM_ID_IMPLEMENTATION_ID,        "implementation_id"},
+        {EAT_ARM_PSA_CLAIM_ID_BOOT_SEED,                "boot_seed"},
+        {EAT_ARM_PSA_CLAIM_ID_HW_VERSION,               "hw_version"},
+        {EAT_ARM_PSA_CLAIM_ID_SW_COMPONENTS,            "sw_components"},
+        {EAT_ARM_PSA_CLAIM_ID_NO_SW_COMPONENTS,         "no_sw_components"},
+        {EAT_ARM_PSA_CLAIM_ID_CHALLENGE,                "challenge"},
+        {EAT_ARM_PSA_CLAIM_ID_INSTANCE_ID,              "instance_id"},
+        {EAT_ARM_PSA_CLAIM_ID_VERIFIER,                 "verifier_id"},
+        {EAT_SW_COMPONENT_CLAIM_ID_MEASUREMENT_TYPE,    "type"},
+        {EAT_SW_COMPONENT_CLAIM_ID_MEASUREMENT_VALUE,   "digest"}
+    };
+
+    return cbor_dump(stdout, report, len,
+        0, "attestation_report",
+        eat_dictionary, sizeof(eat_dictionary)/sizeof(struct cbor_dictionary_entry));
+}
diff --git a/components/service/attestation/reporter/dump/pretty/pretty_report_dump.h b/components/service/attestation/reporter/dump/pretty/pretty_report_dump.h
new file mode 100644
index 0000000..0d24b30
--- /dev/null
+++ b/components/service/attestation/reporter/dump/pretty/pretty_report_dump.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef PRETTY_REPORT_DUMP_H
+#define PRETTY_REPORT_DUMP_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Pretty print an attestation report
+ */
+int pretty_report_dump(const uint8_t *report, size_t len);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PRETTY_REPORT_DUMP_H */
diff --git a/components/service/attestation/test/common/component.cmake b/components/service/attestation/reporter/dump/raw/component.cmake
similarity index 88%
copy from components/service/attestation/test/common/component.cmake
copy to components/service/attestation/reporter/dump/raw/component.cmake
index 5985c76..55ae88a 100644
--- a/components/service/attestation/test/common/component.cmake
+++ b/components/service/attestation/reporter/dump/raw/component.cmake
@@ -9,5 +9,5 @@
 endif()
 
 target_sources(${TGT} PRIVATE
-	"${CMAKE_CURRENT_LIST_DIR}/report_dump.cpp"
+	"${CMAKE_CURRENT_LIST_DIR}/raw_report_dump.cpp"
 	)
diff --git a/components/service/attestation/test/common/report_dump.cpp b/components/service/attestation/reporter/dump/raw/raw_report_dump.cpp
similarity index 87%
rename from components/service/attestation/test/common/report_dump.cpp
rename to components/service/attestation/reporter/dump/raw/raw_report_dump.cpp
index 65f4a85..1ac0e59 100644
--- a/components/service/attestation/test/common/report_dump.cpp
+++ b/components/service/attestation/reporter/dump/raw/raw_report_dump.cpp
@@ -5,9 +5,9 @@
  */
 
 #include <stdio.h>
-#include "report_dump.h"
+#include "raw_report_dump.h"
 
-void report_dump(const uint8_t *report, size_t len)
+void raw_report_dump(const uint8_t *report, size_t len)
 {
     size_t bytes_in_row = 0;
     size_t byte_count = 0;
diff --git a/components/service/attestation/test/common/report_dump.h b/components/service/attestation/reporter/dump/raw/raw_report_dump.h
similarity index 75%
rename from components/service/attestation/test/common/report_dump.h
rename to components/service/attestation/reporter/dump/raw/raw_report_dump.h
index 81becc5..aae3732 100644
--- a/components/service/attestation/test/common/report_dump.h
+++ b/components/service/attestation/reporter/dump/raw/raw_report_dump.h
@@ -4,8 +4,8 @@
  * SPDX-License-Identifier: BSD-3-Clause
  */
 
-#ifndef REPORT_DUMP_H
-#define REPORT_DUMP_H
+#ifndef RAW_REPORT_DUMP_H
+#define RAW_REPORT_DUMP_H
 
 #include <stddef.h>
 #include <stdint.h>
@@ -19,11 +19,11 @@
  * output to stdout. This is useful for viewing the report contents
  * using an external CBOR decoder.
  */
-void report_dump(const uint8_t *report, size_t len);
+void raw_report_dump(const uint8_t *report, size_t len);
 
 
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
 
-#endif /* REPORT_DUMP_H */
+#endif /* RAW_REPORT_DUMP_H */
diff --git a/components/service/attestation/reporter/psa/eat_serializer.c b/components/service/attestation/reporter/psa/eat_serializer.c
index 88b595d..240c674 100644
--- a/components/service/attestation/reporter/psa/eat_serializer.c
+++ b/components/service/attestation/reporter/psa/eat_serializer.c
@@ -113,10 +113,10 @@
         {
             UsefulBufC byte_string;
             QCBOREncode_OpenMap(encode_ctx);
-            byte_string.ptr = claim->variant.measurement.id.bytes;
-            byte_string.len = claim->variant.measurement.id.len;
-            QCBOREncode_AddBytesToMapN(encode_ctx,
-                EAT_SW_COMPONENT_CLAIM_ID_MEASUREMENT_TYPE, byte_string);
+            QCBOREncode_AddSZStringToMapN(encode_ctx,
+                EAT_SW_COMPONENT_CLAIM_ID_MEASUREMENT_TYPE,
+                claim->variant.measurement.id.string);
+
             byte_string.ptr = claim->variant.measurement.digest.bytes;
             byte_string.len = claim->variant.measurement.digest.len;
             QCBOREncode_AddBytesToMapN(encode_ctx,
diff --git a/components/service/attestation/test/component/attestation_reporter_tests.cpp b/components/service/attestation/test/component/attestation_reporter_tests.cpp
index 984af99..d0d0948 100644
--- a/components/service/attestation/test/component/attestation_reporter_tests.cpp
+++ b/components/service/attestation/test/component/attestation_reporter_tests.cpp
@@ -16,8 +16,8 @@
 #include <service/attestation/claims/sources/null_lifecycle/null_lifecycle_claim_source.h>
 #include <service/attestation/claims/sources/instance_id/instance_id_claim_source.h>
 #include <service/attestation/reporter/attest_report.h>
+#include <service/attestation/reporter/dump/raw/raw_report_dump.h>
 #include <service/attestation/key_mngr/attest_key_mngr.h>
-#include <service/attestation/test/common/report_dump.h>
 #include <protocols/service/attestation/packed-c/eat.h>
 #include <CppUTest/TestHarness.h>
 
@@ -200,7 +200,7 @@
                 mock_event_Log_measurement(sw_component_count);
 
             /* Check measurement id */
-            QCBORDecode_GetByteStringInMapN(&decode_ctx,
+             QCBORDecode_GetTextStringInMapN(&decode_ctx,
                     EAT_SW_COMPONENT_CLAIM_ID_MEASUREMENT_TYPE, &property);
             LONGS_EQUAL(QCBOR_SUCCESS, QCBORDecode_GetError(&decode_ctx));
             CHECK_TRUE(property.ptr);
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index 2a8556a..6b2f9e5 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -51,12 +51,12 @@
 		"components/service/attestation/claims/sources/event_log/mock"
 		"components/service/attestation/claims/sources/event_log/test"
 		"components/service/attestation/reporter/psa"
+		"components/service/attestation/reporter/dump/raw"
 		"components/service/attestation/key_mngr"
 		"components/service/attestation/provider"
 		"components/service/attestation/provider/serializer/packed-c"
 		"components/service/attestation/client/psa"
 		"components/service/attestation/client/provision"
-		"components/service/attestation/test/common"
 		"components/service/attestation/test/component"
 		"components/service/attestation/test/service"
 		"components/service/crypto/client/cpp"
diff --git a/deployments/platform-inspect/arm-linux/CMakeLists.txt b/deployments/platform-inspect/arm-linux/CMakeLists.txt
new file mode 100644
index 0000000..f4d02c0
--- /dev/null
+++ b/deployments/platform-inspect/arm-linux/CMakeLists.txt
@@ -0,0 +1,45 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+cmake_minimum_required(VERSION 3.16)
+include(../../deployment.cmake REQUIRED)
+
+#-------------------------------------------------------------------------------
+#  Required for use of stdout/stderr file pointers from the application.
+#
+#-------------------------------------------------------------------------------
+set(CMAKE_POSITION_INDEPENDENT_CODE True)
+
+#-------------------------------------------------------------------------------
+#  The CMakeLists.txt for building the platform-inspect deployment for arm-linux
+#
+#-------------------------------------------------------------------------------
+include(${TS_ROOT}/environments/arm-linux/env.cmake)
+project(trusted-services LANGUAGES CXX C)
+add_executable(platform-inspect)
+target_include_directories(platform-inspect PRIVATE "${TOP_LEVEL_INCLUDE_DIRS}")
+
+#-------------------------------------------------------------------------------
+#  Extend with components that are common across all deployments of
+#  platform-inspect
+#
+#-------------------------------------------------------------------------------
+include(../platform-inspect.cmake REQUIRED)
+
+#-------------------------------------------------------------------------------
+#  Define library options and dependencies.
+#
+#-------------------------------------------------------------------------------
+env_set_link_options(TGT platform-inspect)
+target_link_libraries(platform-inspect PRIVATE stdc++ gcc m)
+
+#-------------------------------------------------------------------------------
+#  Linker option to enable repeated searches for undefined references.
+#  Required to resolve dependencies between t_cose and qcbor libraries.
+#-------------------------------------------------------------------------------
+target_link_options(platform-inspect PRIVATE
+	-Wl,--start-group
+	)
diff --git a/deployments/platform-inspect/linux-pc/CMakeLists.txt b/deployments/platform-inspect/linux-pc/CMakeLists.txt
new file mode 100644
index 0000000..52a5834
--- /dev/null
+++ b/deployments/platform-inspect/linux-pc/CMakeLists.txt
@@ -0,0 +1,24 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+cmake_minimum_required(VERSION 3.16)
+include(../../deployment.cmake REQUIRED)
+
+#-------------------------------------------------------------------------------
+#  The CMakeLists.txt for building the platform-inspect deployment for linux-pc
+#
+#-------------------------------------------------------------------------------
+include(${TS_ROOT}/environments/linux-pc/env.cmake)
+project(trusted-services LANGUAGES CXX C)
+add_executable(platform-inspect)
+target_include_directories(platform-inspect PRIVATE "${TOP_LEVEL_INCLUDE_DIRS}")
+
+#-------------------------------------------------------------------------------
+#  Extend with components that are common across all deployments of
+#  platform-inspect
+#
+#-------------------------------------------------------------------------------
+include(../platform-inspect.cmake REQUIRED)
diff --git a/deployments/platform-inspect/platform-inspect.cmake b/deployments/platform-inspect/platform-inspect.cmake
new file mode 100644
index 0000000..a8952f4
--- /dev/null
+++ b/deployments/platform-inspect/platform-inspect.cmake
@@ -0,0 +1,69 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+
+#-------------------------------------------------------------------------------
+#  The base build file shared between deployments of 'platform-inspect' for
+#  different environments.
+#-------------------------------------------------------------------------------
+
+#-------------------------------------------------------------------------------
+#  Use libts for locating and accessing trusted services. An appropriate version
+#  of libts will be imported for the enviroment in which platform-inspect is
+#  built.
+#-------------------------------------------------------------------------------
+include(${TS_ROOT}/deployments/libts/libts-import.cmake)
+target_link_libraries(platform-inspect PRIVATE libts)
+
+#-------------------------------------------------------------------------------
+#  Components that are common accross all deployments
+#
+#-------------------------------------------------------------------------------
+add_components(
+	TARGET "platform-inspect"
+	BASE_DIR ${TS_ROOT}
+	COMPONENTS
+		"components/app/platform-inspect"
+		"components/common/tlv"
+		"components/common/cbor_dump"
+		"components/service/common/include"
+		"components/service/attestation/include"
+		"components/service/attestation/client/psa"
+		"components/service/attestation/client/provision"
+		"components/service/attestation/reporter/dump/raw"
+		"components/service/attestation/reporter/dump/pretty"
+)
+
+#-------------------------------------------------------------------------------
+#  Components used from external projects
+#
+#-------------------------------------------------------------------------------
+
+# Configuration for mbedcrypto
+set(MBEDTLS_USER_CONFIG_FILE
+	"${TS_ROOT}/components/service/crypto/client/cpp/config_mbedtls_user.h"
+	CACHE STRING "Configuration file for mbedcrypto")
+
+# Mbed TLS provides libmbedcrypto
+include(../../../external/MbedTLS/MbedTLS.cmake)
+target_link_libraries(platform-inspect PRIVATE mbedcrypto)
+
+# Qcbor
+include(${TS_ROOT}/external/qcbor/qcbor.cmake)
+target_link_libraries(platform-inspect PRIVATE qcbor)
+
+# t_cose
+include(${TS_ROOT}/external/t_cose/t_cose.cmake)
+target_link_libraries(platform-inspect PRIVATE t_cose)
+
+#-------------------------------------------------------------------------------
+#  Define install content.
+#
+#-------------------------------------------------------------------------------
+if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+	set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "location to install build output to." FORCE)
+endif()
+install(TARGETS platform-inspect RUNTIME DESTINATION ${TS_ENV}/bin)
diff --git a/tools/b-test/test_data.yaml b/tools/b-test/test_data.yaml
index 32c6337..3e7e36c 100644
--- a/tools/b-test/test_data.yaml
+++ b/tools/b-test/test_data.yaml
@@ -83,3 +83,13 @@
       params:
             - "-GUnix Makefiles"
             - "-DSP_DEV_KIT_DIR=$SP_DEV_KIT_DIR"
+    - name: "platform-inspect-arm-linux"
+      src: "$TS_ROOT/deployments/platform-inspect/arm-linux"
+      os_id : "GNU/Linux"
+      params:
+          - "-GUnix Makefiles"
+    - name: "platform-inspect-pc-linux"
+      src: "$TS_ROOT/deployments/platform-inspect/linux-pc"
+      os_id : "GNU/Linux"
+      params:
+          - "-GUnix Makefiles"