[RFC kvm-unit-tests 22/27] arm: Add a library to verify tokens using the QCBOR library

Joey Gouly
2023-01-27T12:06:04Z

From: Mate Toth-Pal <mate.toth-pal@arm.com>

Add a library wrapper around the QCBOR for parsing the Arm CCA attestation
tokens.

Signed-off-by: Mate Toth-Pal <mate.toth-pal@arm.com>
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Joey Gouly <joey.gouly@arm.com>
---
arm/Makefile.arm64 | 7 +-
lib/token_verifier/attest_defines.h | 50 +++
lib/token_verifier/token_dumper.c | 158 ++++++++
lib/token_verifier/token_dumper.h | 15 +
lib/token_verifier/token_verifier.c | 591 ++++++++++++++++++++++++++++
lib/token_verifier/token_verifier.h | 77 ++++
6 files changed, 897 insertions(+), 1 deletion(-)
create mode 100644 lib/token_verifier/attest_defines.h
create mode 100644 lib/token_verifier/token_dumper.c
create mode 100644 lib/token_verifier/token_dumper.h
create mode 100644 lib/token_verifier/token_verifier.c
create mode 100644 lib/token_verifier/token_verifier.h

diff --git a/arm/Makefile.arm64 b/arm/Makefile.arm64
index 8d450de9..f57d0a95 100644
--- a/arm/Makefile.arm64
+++ b/arm/Makefile.arm64
@@ -11,6 +11,7 @@ arch_LDFLAGS += -z notext
CFLAGS += -mstrict-align
CFLAGS += -I $(SRCDIR)/lib/qcbor/inc
CFLAGS += -DQCBOR_DISABLE_FLOAT_HW_USE -DQCBOR_DISABLE_PREFERRED_FLOAT -DUSEFULBUF_DISABLE_ALL_FLOAT
+CFLAGS += -I $(SRCDIR)/lib/token_verifier

mno_outline_atomics := $(call cc-option, -mno-outline-atomics, "")
CFLAGS += $(mno_outline_atomics)
@@ -28,6 +29,9 @@ cflatobjs += lib/arm64/spinlock.o
cflatobjs += lib/arm64/gic-v3-its.o lib/arm64/gic-v3-its-cmd.o
cflatobjs += lib/arm64/rsi.o
cflatobjs += lib/qcbor/src/qcbor_decode.o lib/qcbor/src/UsefulBuf.o
+cflatobjs += lib/token_verifier/token_verifier.o
+cflatobjs += lib/token_verifier/token_dumper.o
+

OBJDIRS += lib/arm64

@@ -44,4 +48,5 @@ include $(SRCDIR)/$(TEST_DIR)/Makefile.common

arch_clean: arm_clean
$(RM) lib/arm64/.*.d \
- lib/qcbor/src/.*.d
+ lib/qcbor/src/.*.d \
+ lib/token_verifier/.*.d
diff --git a/lib/token_verifier/attest_defines.h b/lib/token_verifier/attest_defines.h
new file mode 100644
index 00000000..daf51c5f
--- /dev/null
+++ b/lib/token_verifier/attest_defines.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Arm Limited.
+ * All rights reserved.
+ */
+
+#ifndef __ATTEST_DEFINES_H__
+#define __ATTEST_DEFINES_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TAG_COSE_SIGN1 (18)
+#define TAG_CCA_TOKEN (399)
+
+#define CCA_PLAT_TOKEN (44234) /* 0xACCA */
+#define CCA_REALM_DELEGATED_TOKEN (44241)
+
+/* CCA Platform Attestation Token */
+#define CCA_PLAT_CHALLENGE (10) /* EAT nonce */
+#define CCA_PLAT_INSTANCE_ID (256) /* EAT ueid */
+#define CCA_PLAT_PROFILE (265) /* EAT profile */
+#define CCA_PLAT_SECURITY_LIFECYCLE (2395)
+#define CCA_PLAT_IMPLEMENTATION_ID (2396)
+#define CCA_PLAT_SW_COMPONENTS (2399)
+#define CCA_PLAT_VERIFICATION_SERVICE (2400)
+#define CCA_PLAT_CONFIGURATION (2401)
+#define CCA_PLAT_HASH_ALGO_ID (2402)
+
+/* CCA Realm Delegated Attestation Token */
+#define CCA_REALM_CHALLENGE (10) /* EAT nonce */
+#define CCA_REALM_PERSONALIZATION_VALUE (44235)
+#define CCA_REALM_HASH_ALGO_ID (44236)
+#define CCA_REALM_PUB_KEY (44237)
+#define CCA_REALM_INITIAL_MEASUREMENT (44238)
+#define CCA_REALM_EXTENSIBLE_MEASUREMENTS (44239)
+#define CCA_REALM_PUB_KEY_HASH_ALGO_ID (44240)
+
+/* Software components */
+#define CCA_SW_COMP_MEASUREMENT_VALUE (2)
+#define CCA_SW_COMP_VERSION (4)
+#define CCA_SW_COMP_SIGNER_ID (5)
+#define CCA_SW_COMP_HASH_ALGORITHM (6)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ATTEST_DEFINES_H__ */
diff --git a/lib/token_verifier/token_dumper.c b/lib/token_verifier/token_dumper.c
new file mode 100644
index 00000000..15f17956
--- /dev/null
+++ b/lib/token_verifier/token_dumper.c
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Arm Limited.
+ * All rights reserved.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include "attest_defines.h"
+#include "token_dumper.h"
+
+#define COLUMN_WIDTH "20"
+
+void print_raw_token(const char *token, size_t size)
+{
+ int i;
+ char byte;
+
+ printf("\r\nCopy paste token to www.cbor.me\r\n");
+ for (i = 0; i < size; ++i) {
+ byte = token[i];
+ if (byte == 0)
+ printf("0x%#02x ", byte);
+ else
+ printf("0x%02x ", byte);
+ if (((i + 1) % 8) == 0)
+ printf("\r\n");
+ }
+ printf("\r\n");
+}
+
+static void print_indent(int indent_level)
+{
+ int i;
+
+ for (i = 0; i < indent_level; ++i) {
+ printf(" ");
+ }
+}
+
+static void print_byte_string(const char *name, int index,
+ struct q_useful_buf_c buf)
+{
+ int i;
+
+ printf("%-"COLUMN_WIDTH"s (#%d) = [", name, index);
+ for (i = 0; i < buf.len; ++i) {
+ printf("%02x", ((uint8_t *)buf.ptr)[i]);
+ }
+ printf("]\r\n");
+}
+
+static void print_text(const char *name, int index, struct q_useful_buf_c buf)
+{
+ int i;
+
+ printf("%-"COLUMN_WIDTH"s (#%d) = \"", name, index);
+ for (i = 0; i < buf.len; ++i) {
+ printf("%c", ((uint8_t *)buf.ptr)[i]);
+ }
+ printf("\"\r\n");
+}
+
+static void print_claim(struct claim_t *claim, int indent_level)
+{
+ print_indent(indent_level);
+ if (claim->present) {
+ switch (claim->type) {
+ case CLAIM_INT64:
+ printf("%-"COLUMN_WIDTH"s (#%" PRId64 ") = %" PRId64
+ "\r\n", claim->title,
+ claim->key, claim->int_data);
+ break;
+ case CLAIM_BOOL:
+ printf("%-"COLUMN_WIDTH"s (#%" PRId64 ") = %s\r\n",
+ claim->title, claim->key,
+ claim->bool_data?"true":"false");
+ break;
+ case CLAIM_BSTR:
+ print_byte_string(claim->title, claim->key,
+ claim->buffer_data);
+ break;
+ case CLAIM_TEXT:
+ print_text(claim->title, claim->key,
+ claim->buffer_data);
+ break;
+ default:
+ printf("* Internal error at %s:%d.\r\n", __FILE__,
+ (int)__LINE__);
+ break;
+ }
+ } else {
+ printf("* Missing%s claim with key: %" PRId64 " (%s)\r\n",
+ claim->mandatory?" mandatory":"",
+ claim->key, claim->title);
+ }
+}
+
+static void print_cose_sign1_wrapper(const char *token_type,
+ struct claim_t *cose_sign1_wrapper)
+{
+ printf("\r\n== %s Token cose header:\r\n", token_type);
+ print_claim(cose_sign1_wrapper + 0, 0);
+ /* Don't print wrapped token bytestring */
+ print_claim(cose_sign1_wrapper + 2, 0);
+ printf("== End of %s Token cose header\r\n\r\n", token_type);
+}
+
+void print_token(struct attestation_claims *claims)
+{
+ int i;
+
+ print_cose_sign1_wrapper("Realm", claims->realm_cose_sign1_wrapper);
+
+ printf("\r\n== Realm Token:\r\n");
+ /* print the claims except the last one. That is printed in detail
+ * below.
+ */
+ for (i = 0; i < CLAIM_COUNT_REALM_TOKEN; ++i) {
+ struct claim_t *claim = claims->realm_token_claims + i;
+
+ print_claim(claim, 0);
+ }
+
+ printf("%-"COLUMN_WIDTH"s (#%d)\r\n", "Realm measurements",
+ CCA_REALM_EXTENSIBLE_MEASUREMENTS);
+ for (i = 0; i < CLAIM_COUNT_REALM_EXTENSIBLE_MEASUREMENTS; ++i) {
+ struct claim_t *claim = claims->realm_measurement_claims + i;
+
+ print_claim(claim, 1);
+ }
+ printf("== End of Realm Token.\r\n");
+
+ print_cose_sign1_wrapper("Platform", claims->plat_cose_sign1_wrapper);
+
+ printf("\r\n== Platform Token:\r\n");
+ for (i = 0; i < CLAIM_COUNT_PLATFORM_TOKEN; ++i) {
+ struct claim_t *claim = claims->plat_token_claims + i;
+
+ print_claim(claim, 0);
+ }
+ printf("== End of Platform Token\r\n\r\n");
+
+ printf("\r\n== Platform Token SW components:\r\n");
+
+ for (i = 0; i < MAX_SW_COMPONENT_COUNT; ++i) {
+ struct sw_component_t *component =
+ claims->sw_component_claims + i;
+
+ if (component->present) {
+ printf(" SW component #%d:\r\n", i);
+ for (int j = 0; j < CLAIM_COUNT_SW_COMPONENT; ++j) {
+ print_claim(component->claims + j, 2);
+ }
+ }
+ }
+ printf("== End of Platform Token SW components\r\n\r\n");
+}
diff --git a/lib/token_verifier/token_dumper.h b/lib/token_verifier/token_dumper.h
new file mode 100644
index 00000000..96cc0744
--- /dev/null
+++ b/lib/token_verifier/token_dumper.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Arm Limited.
+ * All rights reserved.
+ */
+
+#ifndef __TOKEN_DUMPER_H__
+#define __TOKEN_DUMPER_H__
+
+#include "token_verifier.h"
+
+void print_raw_token(const char *token, size_t size);
+void print_token(struct attestation_claims *claims);
+
+#endif /* __TOKEN_DUMPER_H__ */
diff --git a/lib/token_verifier/token_verifier.c b/lib/token_verifier/token_verifier.c
new file mode 100644
index 00000000..ba2a89f6
--- /dev/null
+++ b/lib/token_verifier/token_verifier.c
@@ -0,0 +1,591 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Arm Limited.
+ * All rights reserved.
+ */
+
+#include <libcflat.h>
+#include <inttypes.h>
+#include <qcbor/qcbor_decode.h>
+#include <qcbor/qcbor_spiffy_decode.h>
+#include "attest_defines.h"
+#include "token_verifier.h"
+#include "token_dumper.h"
+
+#define SHA256_SIZE 32
+#define SHA512_SIZE 64
+
+#define RETURN_ON_DECODE_ERROR(p_context) \
+ do { \
+ QCBORError ret; \
+ ret = QCBORDecode_GetError(p_context); \
+ if (ret != QCBOR_SUCCESS) { \
+ printf("QCBOR decode failed with error at %s:%d." \
+ " err = %d\r\n", \
+ __FILE__, (int)__LINE__, (int)ret); \
+ return TOKEN_VERIFICATION_ERR_QCBOR(ret); \
+ } \
+ } while (0)
+
+static void init_claim(struct claim_t *claim,
+ bool mandatory, enum claim_data_type type,
+ int64_t key, const char *title, bool present)
+{
+ claim->mandatory = mandatory;
+ claim->type = type;
+ claim->key = key;
+ claim->title = title;
+ claim->present = present;
+}
+
+static int init_cose_wrapper_claim(struct claim_t *cose_sign1_wrapper)
+{
+ struct claim_t *c;
+
+ /* The cose wrapper looks like the following:
+ * - Protected header (bytestring).
+ * - Unprotected header: might contain 0 items. This is a map. Due to
+ * the way this thing is implemented, it is not in the below list,
+ * but is handled in the verify_token_cose_sign1_wrapping
+ * function.
+ * - Payload: Platform token (bytestring). The content is passed for
+ * verify_platform_token.
+ * - Signature.
+ */
+ c = cose_sign1_wrapper;
+ /* This structure is in an array, so the key is not used */
+ init_claim(c++, true, CLAIM_BSTR, 0, "Protected header", false);
+ init_claim(c++, true, CLAIM_BSTR, 0, "Platform token payload", false);
+ init_claim(c++, true, CLAIM_BSTR, 0, "Signature", false);
+ if (c > cose_sign1_wrapper + CLAIM_COUNT_COSE_SIGN1_WRAPPER) {
+ return TOKEN_VERIFICATION_ERR_INIT_ERROR;
+ }
+ return 0;
+}
+
+static int init_claims(struct attestation_claims *attest_claims)
+{
+ int i;
+ int ret;
+ struct claim_t *c;
+ /* TODO: All the buffer overwrite checks are happening too late.
+ * Either remove, or find a better way.
+ */
+ c = attest_claims->realm_token_claims;
+ init_claim(c++, true, CLAIM_BSTR, CCA_REALM_CHALLENGE, "Realm challenge", false);
+ init_claim(c++, true, CLAIM_BSTR, CCA_REALM_PERSONALIZATION_VALUE, "Realm personalization value", false);
+ init_claim(c++, true, CLAIM_TEXT, CCA_REALM_HASH_ALGO_ID, "Realm hash algo id", false);
+ init_claim(c++, true, CLAIM_TEXT, CCA_REALM_PUB_KEY_HASH_ALGO_ID, "Realm public key hash algo id", false);
+ init_claim(c++, true, CLAIM_BSTR, CCA_REALM_PUB_KEY, "Realm signing public key", false);
+ init_claim(c++, true, CLAIM_BSTR, CCA_REALM_INITIAL_MEASUREMENT, "Realm initial measurement", false);
+ /* Realm extensible measurements are not present here as they are
+ * encoded as a CBOR array, and it is handled specially in
+ * verify_realm_token().
+ */
+ if (c > attest_claims->realm_token_claims + CLAIM_COUNT_REALM_TOKEN) {
+ return TOKEN_VERIFICATION_ERR_INIT_ERROR;
+ }
+
+ ret = init_cose_wrapper_claim(attest_claims->realm_cose_sign1_wrapper);
+ if (ret != 0) {
+ return ret;
+ }
+ ret = init_cose_wrapper_claim(attest_claims->plat_cose_sign1_wrapper);
+ if (ret != 0) {
+ return ret;
+ }
+
+ c = attest_claims->plat_token_claims;
+ init_claim(c++, true, CLAIM_BSTR, CCA_PLAT_CHALLENGE, "Challenge", false);
+ init_claim(c++, false, CLAIM_TEXT, CCA_PLAT_VERIFICATION_SERVICE, "Verification service", false);
+ init_claim(c++, true, CLAIM_TEXT, CCA_PLAT_PROFILE, "Profile", false);
+ init_claim(c++, true, CLAIM_BSTR, CCA_PLAT_INSTANCE_ID, "Instance ID", false);
+ init_claim(c++, true, CLAIM_BSTR, CCA_PLAT_IMPLEMENTATION_ID, "Implementation ID", false);
+ init_claim(c++, true, CLAIM_INT64, CCA_PLAT_SECURITY_LIFECYCLE, "Lifecycle", false);
+ init_claim(c++, true, CLAIM_BSTR, CCA_PLAT_CONFIGURATION, "Configuration", false);
+ init_claim(c++, true, CLAIM_TEXT, CCA_PLAT_HASH_ALGO_ID, "Platform hash algo", false);
+ if (c > attest_claims->plat_token_claims +
+ CLAIM_COUNT_PLATFORM_TOKEN) {
+ return TOKEN_VERIFICATION_ERR_INIT_ERROR;
+ }
+
+ for (i = 0; i < CLAIM_COUNT_REALM_EXTENSIBLE_MEASUREMENTS; ++i) {
+ c = attest_claims->realm_measurement_claims + i;
+ init_claim(c, true, CLAIM_BSTR, i,
+ "Realm extensible measurements", false);
+ }
+
+ for (i = 0; i < MAX_SW_COMPONENT_COUNT; ++i) {
+ struct sw_component_t *component =
+ attest_claims->sw_component_claims + i;
+
+ component->present = false;
+ c = component->claims;
+ init_claim(c++, false, CLAIM_TEXT, CCA_SW_COMP_HASH_ALGORITHM, "Hash algo.", false);
+ init_claim(c++, true, CLAIM_BSTR, CCA_SW_COMP_MEASUREMENT_VALUE, "Meas. val.", false);
+ init_claim(c++, false, CLAIM_TEXT, CCA_SW_COMP_VERSION, "Version", false);
+ init_claim(c++, true, CLAIM_BSTR, CCA_SW_COMP_SIGNER_ID, "Signer ID", false);
+ if (c > component->claims + CLAIM_COUNT_SW_COMPONENT) {
+ return TOKEN_VERIFICATION_ERR_INIT_ERROR;
+ }
+ }
+ return TOKEN_VERIFICATION_ERR_SUCCESS;
+}
+
+static int handle_claim_decode_error(const struct claim_t *claim,
+ QCBORError err)
+{
+ if (err == QCBOR_ERR_LABEL_NOT_FOUND) {
+ if (claim->mandatory) {
+ printf("Mandatory claim with key %" PRId64 " (%s) is "
+ "missing from token.\r\n", claim->key,
+ claim->title);
+ return TOKEN_VERIFICATION_ERR_MISSING_MANDATORY_CLAIM;
+ }
+ } else {
+ printf("Decode failed with error at %s:%d. err = %d key = %"
+ PRId64 " (%s).\r\n", __FILE__, (int)__LINE__, err,
+ claim->key, claim->title);
+ return TOKEN_VERIFICATION_ERR_QCBOR(err);
+ }
+ return TOKEN_VERIFICATION_ERR_SUCCESS;
+}
+
+/* Consume claims from a map.
+ *
+ * This function iterates on the array 'claims', and looks up items with the
+ * specified keys. If a claim flagged as mandatory is not found, an error is
+ * returned. The function doesn't checks for extra items. So if the map contains
+ * items with keys that are not in the claims array, no error is reported.
+ *
+ * The map needs to be 'entered' before calling this function, and be 'exited'
+ * after it returns.
+ */
+static int get_claims_from_map(QCBORDecodeContext *p_context,
+ struct claim_t *claims,
+ size_t num_of_claims)
+{
+ QCBORError err;
+ int token_verification_error;
+ int i;
+
+ for (i = 0; i < num_of_claims; ++i) {
+ struct claim_t *claim = claims + i;
+
+ switch (claim->type) {
+ case CLAIM_INT64:
+ QCBORDecode_GetInt64InMapN(p_context, claim->key,
+ &(claim->int_data));
+ break;
+ case CLAIM_BOOL:
+ QCBORDecode_GetBoolInMapN(p_context, claim->key,
+ &(claim->bool_data));
+ break;
+ case CLAIM_BSTR:
+ QCBORDecode_GetByteStringInMapN(p_context, claim->key,
+ &(claim->buffer_data));
+ break;
+ case CLAIM_TEXT:
+ QCBORDecode_GetTextStringInMapN(p_context, claim->key,
+ &(claim->buffer_data));
+ break;
+ default:
+ printf("Internal error at %s:%d.\r\n",
+ __FILE__, (int)__LINE__);
+ return TOKEN_VERIFICATION_ERR_INTERNAL_ERROR;
+ }
+ err = QCBORDecode_GetAndResetError(p_context);
+ if (err == QCBOR_SUCCESS) {
+ claim->present = true;
+ } else {
+ token_verification_error =
+ handle_claim_decode_error(claim, err);
+ if (token_verification_error !=
+ TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return token_verification_error;
+ }
+ }
+ }
+ return TOKEN_VERIFICATION_ERR_SUCCESS;
+}
+
+/* Consume a single claim from an array and from the top level.
+ *
+ * The claim's 'key' and 'mandatory' attribute is not used in this function.
+ * The claim is considered mandatory.
+ */
+static int get_claim(QCBORDecodeContext *p_context, struct claim_t *claim)
+{
+ QCBORError err;
+
+ switch (claim->type) {
+ case CLAIM_INT64:
+ QCBORDecode_GetInt64(p_context, &(claim->int_data));
+ break;
+ case CLAIM_BOOL:
+ QCBORDecode_GetBool(p_context, &(claim->bool_data));
+ break;
+ case CLAIM_BSTR:
+ QCBORDecode_GetByteString(p_context, &(claim->buffer_data));
+ break;
+ case CLAIM_TEXT:
+ QCBORDecode_GetTextString(p_context, &(claim->buffer_data));
+ break;
+ default:
+ printf("Internal error at %s:%d.\r\n",
+ __FILE__, (int)__LINE__);
+ break;
+ }
+ err = QCBORDecode_GetAndResetError(p_context);
+ if (err == QCBOR_SUCCESS) {
+ claim->present = true;
+ return TOKEN_VERIFICATION_ERR_SUCCESS;
+ }
+ printf("Decode failed with error at %s:%d. err = %d claim: \"%s\".\r\n",
+ __FILE__, (int)__LINE__, err, claim->title);
+ return TOKEN_VERIFICATION_ERR_QCBOR(err);
+}
+
+/* Consume claims from an array and from the top level.
+ *
+ * This function iterates on the array 'claims', and gets an item for each
+ * element. If the array or the cbor runs out of elements before reaching the
+ * end of the 'claims' array, then error is returned.
+ *
+ * The claim's 'key' and 'mandatory' attribute is not used in this function.
+ * All the elements considered mandatory.
+ */
+static int get_claims(QCBORDecodeContext *p_context, struct claim_t *claims,
+ size_t num_of_claims)
+{
+ QCBORError err;
+ int i;
+
+ for (i = 0; i < num_of_claims; ++i) {
+ struct claim_t *claim = claims + i;
+
+ err = get_claim(p_context, claim);
+ if (err != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return err;
+ }
+ }
+ return TOKEN_VERIFICATION_ERR_SUCCESS;
+}
+
+static int verify_platform_token(struct q_useful_buf_c buf,
+ struct attestation_claims *attest_claims)
+{
+ QCBORDecodeContext context;
+ int err;
+ int label, index;
+
+ QCBORDecode_Init(&context, buf, QCBOR_DECODE_MODE_NORMAL);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ QCBORDecode_EnterMap(&context, NULL);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ err = get_claims_from_map(&context,
+ attest_claims->plat_token_claims,
+ CLAIM_COUNT_PLATFORM_TOKEN);
+ if (err != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return err;
+ }
+
+ label = CCA_PLAT_SW_COMPONENTS;
+ QCBORDecode_EnterArrayFromMapN(&context, label);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ index = 0;
+ while (1) {
+ QCBORDecode_EnterMap(&context, NULL);
+ if (QCBORDecode_GetError(&context) == QCBOR_ERR_NO_MORE_ITEMS) {
+ /* This is OK. We just reached the end of the array.
+ * Break from the loop.
+ */
+ break;
+ }
+
+ if (index >= MAX_SW_COMPONENT_COUNT) {
+ printf("Not enough slots in sw_component_claims.\r\n");
+ printf("Increase MAX_SW_COMPONENT_COUNT in %s.\r\n",
+ __FILE__);
+ return TOKEN_VERIFICATION_ERR_INTERNAL_ERROR;
+ }
+
+ err = get_claims_from_map(&context,
+ attest_claims->sw_component_claims[index].claims,
+ CLAIM_COUNT_SW_COMPONENT);
+ if (err != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return err;
+ }
+ attest_claims->sw_component_claims[index].present = true;
+
+ QCBORDecode_ExitMap(&context);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ ++index;
+ }
+ /* We only get here if the decode error code was a
+ * QCBOR_ERR_NO_MORE_ITEMS which is expected when the end of an array is
+ * reached. In this case the processing must be continued, so clear the
+ * error.
+ */
+ QCBORDecode_GetAndResetError(&context);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ QCBORDecode_ExitArray(&context);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ QCBORDecode_ExitMap(&context);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ QCBORDecode_Finish(&context);
+
+ return TOKEN_VERIFICATION_ERR_SUCCESS;
+}
+
+static bool verify_length_of_measurement(size_t len)
+{
+ size_t allowed_lengths[] = {SHA256_SIZE, SHA512_SIZE};
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(allowed_lengths); ++i) {
+ if (len == allowed_lengths[i])
+ return true;
+ }
+
+ return false;
+}
+
+static int verify_realm_token(struct q_useful_buf_c buf,
+ struct attestation_claims *attest_claims)
+{
+ QCBORDecodeContext context;
+ int err;
+ int i;
+
+ QCBORDecode_Init(&context, buf, QCBOR_DECODE_MODE_NORMAL);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ QCBORDecode_EnterMap(&context, NULL);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ err = get_claims_from_map(&context, attest_claims->realm_token_claims,
+ CLAIM_COUNT_REALM_TOKEN);
+ if (err != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return err;
+ }
+
+ /* Now get the realm extensible measurements */
+ QCBORDecode_EnterArrayFromMapN(&context,
+ CCA_REALM_EXTENSIBLE_MEASUREMENTS);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ err = get_claims(&context,
+ attest_claims->realm_measurement_claims,
+ CLAIM_COUNT_REALM_EXTENSIBLE_MEASUREMENTS);
+ if (err != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return err;
+ }
+
+ for (i = 0; i < CLAIM_COUNT_REALM_EXTENSIBLE_MEASUREMENTS; ++i) {
+ struct claim_t *claims =
+ attest_claims->realm_measurement_claims;
+ struct q_useful_buf_c buf = claims[i].buffer_data;
+
+ if (!verify_length_of_measurement(buf.len)) {
+ return TOKEN_VERIFICATION_ERR_INVALID_CLAIM_LEN;
+ }
+ }
+
+ QCBORDecode_ExitArray(&context);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ QCBORDecode_ExitMap(&context);
+ QCBORDecode_Finish(&context);
+
+ return TOKEN_VERIFICATION_ERR_SUCCESS;
+}
+
+/* Returns a pointer to the wrapped token in: 'token_payload'.
+ * Returns the claims in the wrapper in cose_sign1_wrapper.
+ */
+static int verify_token_cose_sign1_wrapping(
+ struct q_useful_buf_c token,
+ struct q_useful_buf_c *token_payload,
+ struct claim_t *cose_sign1_wrapper)
+{
+ QCBORDecodeContext context;
+ QCBORItem item;
+ int err;
+
+ QCBORDecode_Init(&context, token, QCBOR_DECODE_MODE_NORMAL);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ /* Check COSE tag. */
+ QCBORDecode_PeekNext(&context, &item);
+ if (!QCBORDecode_IsTagged(&context, &item,
+ TAG_COSE_SIGN1)) {
+ return TOKEN_VERIFICATION_ERR_INVALID_COSE_TAG;
+ }
+
+ QCBORDecode_EnterArray(&context, NULL);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ /* Protected header */
+ err = get_claim(&context, cose_sign1_wrapper);
+ if (err != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return err;
+ }
+
+ /* Unprotected header. The map is always present, but may contain 0
+ * items.
+ */
+ QCBORDecode_EnterMap(&context, NULL);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ /* Skip the content for now. */
+
+ QCBORDecode_ExitMap(&context);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ /* Payload */
+ err = get_claim(&context, cose_sign1_wrapper + 1);
+ if (err != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return err;
+ }
+
+ /* Signature */
+ err = get_claim(&context, cose_sign1_wrapper + 2);
+ if (err != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return err;
+ }
+
+ QCBORDecode_ExitArray(&context);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ *token_payload = cose_sign1_wrapper[1].buffer_data;
+
+ return TOKEN_VERIFICATION_ERR_SUCCESS;
+}
+
+static int verify_cca_token(struct q_useful_buf_c token,
+ struct q_useful_buf_c *platform_token,
+ struct q_useful_buf_c *realm_token)
+{
+ QCBORDecodeContext context;
+ QCBORItem item;
+ QCBORError err;
+
+ QCBORDecode_Init(&context, token, QCBOR_DECODE_MODE_NORMAL);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ /* ================== Check CCA_TOKEN tag =========================== */
+ QCBORDecode_PeekNext(&context, &item);
+ if (!QCBORDecode_IsTagged(&context, &item, TAG_CCA_TOKEN)) {
+ return TOKEN_VERIFICATION_ERR_INVALID_COSE_TAG;
+ }
+
+ /* ================== Get the the platform token ==================== */
+ QCBORDecode_EnterMap(&context, NULL);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ /*
+ * First element is the CCA platfrom token which is a
+ * COSE_Sign1_Tagged object. It has byte stream wrapper.
+ */
+ QCBORDecode_GetByteStringInMapN(&context, CCA_PLAT_TOKEN,
+ platform_token);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ /* ================== Get the the realm token ======================= */
+ /*
+ * Second element is the delegated realm token which is a
+ * COSE_Sign1_Tagged object. It has byte stream wrapper.
+ */
+ QCBORDecode_GetByteStringInMapN(&context, CCA_REALM_DELEGATED_TOKEN,
+ realm_token);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ QCBORDecode_ExitMap(&context);
+ RETURN_ON_DECODE_ERROR(&context);
+
+ /* Finishing up the decoding of the top-level wrapper */
+ err = QCBORDecode_Finish(&context);
+ if (err != QCBOR_SUCCESS) {
+ printf("QCBOR decode failed with error at %s:%d. err = %d\r\n",
+ __FILE__, (int)__LINE__, (int)err);
+ return TOKEN_VERIFICATION_ERR_QCBOR(err);
+ }
+
+ return TOKEN_VERIFICATION_ERR_SUCCESS;
+}
+
+/*
+ * This function expect two COSE_Sing1_Tagged object wrapped with a tagged map:
+ *
+ * cca-token = #6.44234(cca-token-map) ; 44234 = 0xACCA
+ *
+ * cca-platform-token = COSE_Sign1_Tagged
+ * cca-realm-delegated-token = COSE_Sign1_Tagged
+ *
+ * cca-token-map = {
+ * 0 => cca-platform-token
+ * 1 => cca-realm-delegated-token
+ * }
+ *
+ * COSE_Sign1_Tagged = #6.18(COSE_Sign1)
+ */
+int verify_token(const char *token, size_t size,
+ struct attestation_claims *attest_claims)
+{
+ /* TODO: do signature check */
+ /* TODO: Add tag check on tokens */
+ struct q_useful_buf_c buf = {token, size};
+ int ret;
+ struct q_useful_buf_c realm_token;
+ struct q_useful_buf_c realm_token_payload;
+ struct q_useful_buf_c platform_token;
+ struct q_useful_buf_c platform_token_payload;
+
+ ret = init_claims(attest_claims);
+ if (ret != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return ret;
+ }
+
+ /* Verify top-level token map and extract the two sub-tokens */
+ ret = verify_cca_token(buf, &platform_token, &realm_token);
+ if (ret != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return ret;
+ }
+
+ /* Verify the COSE_Sign1 wrapper of the realm token */
+ ret = verify_token_cose_sign1_wrapping(realm_token,
+ &realm_token_payload,
+ attest_claims->realm_cose_sign1_wrapper);
+ if (ret != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return ret;
+ }
+ /* Verify the payload of the realm token */
+ ret = verify_realm_token(realm_token_payload, attest_claims);
+ if (ret != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return ret;
+ }
+
+ /* Verify the COSE_Sign1 wrapper of the platform token */
+ ret = verify_token_cose_sign1_wrapping(platform_token,
+ &platform_token_payload,
+ attest_claims->plat_cose_sign1_wrapper);
+ if (ret != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return ret;
+ }
+ /* Verify the payload of the platform token */
+ ret = verify_platform_token(platform_token_payload, attest_claims);
+ if (ret != TOKEN_VERIFICATION_ERR_SUCCESS) {
+ return ret;
+ }
+
+ return TOKEN_VERIFICATION_ERR_SUCCESS;
+}
+
diff --git a/lib/token_verifier/token_verifier.h b/lib/token_verifier/token_verifier.h
new file mode 100644
index 00000000..ec3ab9c9
--- /dev/null
+++ b/lib/token_verifier/token_verifier.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Arm Limited.
+ * All rights reserved.
+ */
+
+#ifndef __TOKEN_VERIFIER_H__
+#define __TOKEN_VERIFIER_H__
+
+#include <qcbor/qcbor_decode.h>
+
+#define TOKEN_VERIFICATION_ERR_SUCCESS 0
+#define TOKEN_VERIFICATION_ERR_INIT_ERROR 1
+#define TOKEN_VERIFICATION_ERR_MISSING_MANDATORY_CLAIM 2
+#define TOKEN_VERIFICATION_ERR_INVALID_COSE_TAG 3
+#define TOKEN_VERIFICATION_ERR_INVALID_CLAIM_LEN 4
+#define TOKEN_VERIFICATION_ERR_INTERNAL_ERROR 5
+#define TOKEN_VERIFICATION_ERR_QCBOR(qcbor_err) (1000 + qcbor_err)
+
+/* Number of realm extensible measurements (REM) */
+#define REM_COUNT 4
+
+#define MAX_SW_COMPONENT_COUNT 16
+
+#define CLAIM_COUNT_REALM_TOKEN 6
+#define CLAIM_COUNT_COSE_SIGN1_WRAPPER 3
+#define CLAIM_COUNT_PLATFORM_TOKEN 8
+#define CLAIM_COUNT_REALM_EXTENSIBLE_MEASUREMENTS REM_COUNT
+#define CLAIM_COUNT_SW_COMPONENT 4
+
+/* This tells how the data should be interpreted in the claim_t struct, and not
+ * necessarily is the same as the item's major type in the token.
+ */
+enum claim_data_type {
+ CLAIM_INT64,
+ CLAIM_BOOL,
+ CLAIM_BSTR,
+ CLAIM_TEXT,
+};
+
+struct claim_t {
+ /* 'static' */
+ bool mandatory;
+ enum claim_data_type type;
+ int64_t key;
+ const char *title;
+
+ /* filled during verification */
+ bool present;
+ union {
+ int64_t int_data;
+ bool bool_data;
+ /* Used for text and bytestream as well */
+ /* TODO: Add expected length check as well? */
+ struct q_useful_buf_c buffer_data;
+ };
+};
+
+struct sw_component_t {
+ bool present;
+ struct claim_t claims[CLAIM_COUNT_SW_COMPONENT];
+};
+
+struct attestation_claims {
+ struct claim_t realm_cose_sign1_wrapper[CLAIM_COUNT_COSE_SIGN1_WRAPPER];
+ struct claim_t realm_token_claims[CLAIM_COUNT_REALM_TOKEN];
+ struct claim_t realm_measurement_claims[CLAIM_COUNT_REALM_EXTENSIBLE_MEASUREMENTS];
+ struct claim_t plat_cose_sign1_wrapper[CLAIM_COUNT_COSE_SIGN1_WRAPPER];
+ struct claim_t plat_token_claims[CLAIM_COUNT_PLATFORM_TOKEN];
+ struct sw_component_t sw_component_claims[MAX_SW_COMPONENT_COUNT];
+};
+
+/* Returns TOKEN_VERIFICATION_ERR* */
+int verify_token(const char *token, size_t size,
+ struct attestation_claims *attest_claims);
+
+#endif /* __TOKEN_VERIFIER_H__ */
--
2.17.1