blob: 38b5c067299805af8d484ae0a71d02df60041cc5 [file] [log] [blame]
/*
* attest_token_decode_common.c
*
* Copyright (c) 2019, Laurence Lundblade.
* Copyright (c) 2020, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.md
*/
#include "attest_token_decode.h"
#include "attest.h"
#include "q_useful_buf.h"
#include "qcbor_util.h"
/**
* \file attest_token_decode_common.c
*
* \brief Common functions in attestation token decoder.
*
* This decodes and verifies an attestation token giving access to the
* data items in the token. The data items are also known as claims.
*
* This is written primarily as tests for the token encoder, though it
* close to a full commercial token decoder. The main thing missing is
* a thorough test suite for it. Test before commercial use is
* important as this is a parser / decoder and thus subject to attack
* by malicious input. It does however, use QCBOR for most base
* parsing, and QCBOR is thoroughly tested and commercial.
*
* This is oriented around the Arm-defined initial attestation token.
*
* \c uint_fast8_t is used for type and nest levels. They are
* 8-bit quantities, but making using uint8_t variables
* and parameters can result in bigger, slower code.
* \c uint_fast8_t is part of \c <stdint.h>. It is not
* used in structures where it is more important to keep
* the size smaller.
*/
/**
* Compute the bit indicating a claim is present
*/
#define CLAIM_PRESENT_BIT(item_index) (0x01U << (item_index))
/*
* Public function. See attest_token_decode.h
*/
void attest_token_decode_init(struct attest_token_decode_context *me,
uint32_t options)
{
memset(me, 0, sizeof(struct attest_token_decode_context));
me->options = options;
me->last_error = ATTEST_TOKEN_ERR_NO_VALID_TOKEN;
}
/*
* Public function. See attest_token_decode.h
*/
enum attest_token_err_t
attest_token_decode_get_bstr(struct attest_token_decode_context *me,
int32_t label,
struct q_useful_buf_c *claim)
{
enum attest_token_err_t return_value;
QCBORItem item;
if(me->last_error != ATTEST_TOKEN_ERR_SUCCESS) {
return_value = me->last_error;
*claim = NULL_Q_USEFUL_BUF_C;
goto Done;
}
return_value = qcbor_util_get_top_level_item_in_map(me->payload,
label,
QCBOR_TYPE_BYTE_STRING,
&item);
if(return_value != ATTEST_TOKEN_ERR_SUCCESS) {
goto Done;
}
*claim = item.val.string;
Done:
return return_value;
}
/*
* Public function. See attest_token_decode.h
*/
enum attest_token_err_t
attest_token_decode_get_tstr(struct attest_token_decode_context *me,
int32_t label,
struct q_useful_buf_c *claim)
{
enum attest_token_err_t return_value;
QCBORItem item;
if(me->last_error != ATTEST_TOKEN_ERR_SUCCESS) {
return_value = me->last_error;
*claim = NULL_Q_USEFUL_BUF_C;
goto Done;
}
return_value = qcbor_util_get_top_level_item_in_map(me->payload,
label,
QCBOR_TYPE_TEXT_STRING,
&item);
if(return_value != ATTEST_TOKEN_ERR_SUCCESS) {
goto Done;
}
*claim = item.val.string;
Done:
return return_value;
}
/*
* Public function. See attest_token_decode.h
*/
enum attest_token_err_t
attest_token_decode_get_int(struct attest_token_decode_context *me,
int32_t label,
int64_t *integer)
{
enum attest_token_err_t return_value;
QCBORItem item;
QCBORDecodeContext decode_context;
if(me->last_error != ATTEST_TOKEN_ERR_SUCCESS) {
return_value = me->last_error;
*integer = 0;
goto Done;
}
QCBORDecode_Init(&decode_context, me->payload, QCBOR_DECODE_MODE_NORMAL);
return_value = qcbor_util_get_item_in_map(&decode_context,
label,
&item);
if(return_value != ATTEST_TOKEN_ERR_SUCCESS) {
goto Done;
}
if(QCBORDecode_Finish(&decode_context)) {
return_value = ATTEST_TOKEN_ERR_CBOR_STRUCTURE;
}
if(item.uDataType == QCBOR_TYPE_INT64) {
*integer = item.val.int64;
} else if(item.uDataType == QCBOR_TYPE_UINT64) {
if(item.val.uint64 < INT64_MAX) {
*integer = (int64_t)item.val.uint64;
} else {
return_value = ATTEST_TOKEN_ERR_INTEGER_VALUE;
}
} else {
return_value = ATTEST_TOKEN_ERR_CBOR_TYPE;
}
Done:
return return_value;
}
/*
* Public function. See attest_token_decode.h
*/
enum attest_token_err_t
attest_token_decode_get_uint(struct attest_token_decode_context *me,
int32_t label,
uint64_t *integer)
{
enum attest_token_err_t return_value;
QCBORItem item;
QCBORDecodeContext decode_context;
if(me->last_error != ATTEST_TOKEN_ERR_SUCCESS) {
return_value = me->last_error;
*integer = 0;
goto Done;
}
QCBORDecode_Init(&decode_context, me->payload, QCBOR_DECODE_MODE_NORMAL);
return_value = qcbor_util_get_item_in_map(&decode_context,
label,
&item);
if(return_value != 0) {
goto Done;
}
if(QCBORDecode_Finish(&decode_context)) {
return_value = ATTEST_TOKEN_ERR_CBOR_STRUCTURE;
}
if(item.uDataType == QCBOR_TYPE_UINT64) {
*integer = item.val.uint64;
} else if(item.uDataType == QCBOR_TYPE_INT64) {
if(item.val.int64 >= 0) {
*integer = (uint64_t)item.val.int64;
} else {
return_value = ATTEST_TOKEN_ERR_INTEGER_VALUE;
}
} else {
return_value = ATTEST_TOKEN_ERR_CBOR_TYPE;
}
Done:
return return_value;
}
/*
* Public function. See attest_token_decode.h
*/
enum attest_token_err_t
attest_token_decode_get_payload(struct attest_token_decode_context *me,
struct q_useful_buf_c *payload)
{
enum attest_token_err_t return_value;
if(me->last_error != ATTEST_TOKEN_ERR_SUCCESS) {
return_value = me->last_error;
*payload = NULL_Q_USEFUL_BUF_C;
goto Done;
}
if(q_useful_buf_c_is_null_or_empty(me->payload)) {
return_value = ATTEST_TOKEN_ERR_NO_VALID_TOKEN;
goto Done;
}
*payload = me->payload;
return_value = ATTEST_TOKEN_ERR_SUCCESS;
Done:
return return_value;
}
/*
* Public function. See attest_token_decode.h
*/
enum attest_token_err_t
attest_token_decode_get_iat_simple(struct attest_token_decode_context *me,
struct attest_token_iat_simple_t *items)
{
struct qcbor_util_items_to_get_t list[NUMBER_OF_ITEMS+1];
QCBORDecodeContext decode_context;
int64_t client_id_64;
enum attest_token_err_t return_value;
/* Set all q_useful_bufs to NULL and flags to 0 */
memset(items, 0, sizeof(struct attest_token_iat_simple_t));
/* Re use flags as array indexes because it works nicely */
list[NONCE_FLAG].label = EAT_CBOR_ARM_LABEL_CHALLENGE;
list[UEID_FLAG].label = EAT_CBOR_ARM_LABEL_UEID;
list[BOOT_SEED_FLAG].label = EAT_CBOR_ARM_LABEL_BOOT_SEED;
list[HW_VERSION_FLAG].label = EAT_CBOR_ARM_LABEL_HW_VERSION;
list[IMPLEMENTATION_ID_FLAG].label = EAT_CBOR_ARM_LABEL_IMPLEMENTATION_ID;
list[CLIENT_ID_FLAG].label = EAT_CBOR_ARM_LABEL_CLIENT_ID;
list[SECURITY_LIFECYCLE_FLAG].label = EAT_CBOR_ARM_LABEL_SECURITY_LIFECYCLE;
list[PROFILE_DEFINITION_FLAG].label = EAT_CBOR_ARM_LABEL_PROFILE_DEFINITION;
list[ORIGINATION_FLAG].label = EAT_CBOR_ARM_LABEL_ORIGINATION;
list[NUMBER_OF_ITEMS].label = 0; /* terminate the list. */
if(me->last_error != ATTEST_TOKEN_ERR_SUCCESS) {
return_value = me->last_error;
goto Done;
}
QCBORDecode_Init(&decode_context, me->payload, QCBOR_DECODE_MODE_NORMAL);
return_value = qcbor_util_get_items_in_map(&decode_context,
list);
if(return_value != ATTEST_TOKEN_ERR_SUCCESS) {
goto Done;
}
/* ---- NONCE ---- */
if(list[NONCE_FLAG].item.uDataType == QCBOR_TYPE_BYTE_STRING) {
items->nonce = list[NONCE_FLAG].item.val.string;
items->item_flags |= CLAIM_PRESENT_BIT(NONCE_FLAG);
}
/* ---- UEID -------*/
if(list[UEID_FLAG].item.uDataType == QCBOR_TYPE_BYTE_STRING) {
items->ueid = list[UEID_FLAG].item.val.string;
items->item_flags |= CLAIM_PRESENT_BIT(UEID_FLAG);
}
/* ---- BOOT SEED -------*/
if(list[BOOT_SEED_FLAG].item.uDataType == QCBOR_TYPE_BYTE_STRING) {
items->boot_seed = list[BOOT_SEED_FLAG].item.val.string;
items->item_flags |= CLAIM_PRESENT_BIT(BOOT_SEED_FLAG);\
}
/* ---- HW VERSION -------*/
if(list[HW_VERSION_FLAG].item.uDataType == QCBOR_TYPE_TEXT_STRING) {
items->hw_version = list[HW_VERSION_FLAG].item.val.string;
items->item_flags |= CLAIM_PRESENT_BIT(HW_VERSION_FLAG);
}
/* ----IMPLEMENTATION ID -------*/
if(list[IMPLEMENTATION_ID_FLAG].item.uDataType == QCBOR_TYPE_BYTE_STRING) {
items->implementation_id = list[IMPLEMENTATION_ID_FLAG].item.val.string;
items->item_flags |= CLAIM_PRESENT_BIT(IMPLEMENTATION_ID_FLAG);
}
/* ----CLIENT ID -------*/
if(list[CLIENT_ID_FLAG].item.uDataType == QCBOR_TYPE_INT64) {
client_id_64 = list[CLIENT_ID_FLAG].item.val.int64;
if(client_id_64 < INT32_MAX || client_id_64 > INT32_MIN) {
items->client_id = (int32_t)client_id_64;
items->item_flags |= CLAIM_PRESENT_BIT(CLIENT_ID_FLAG);
}
}
/* ----SECURITY LIFECYCLE -------*/
if(list[SECURITY_LIFECYCLE_FLAG].item.uDataType == QCBOR_TYPE_INT64) {
if(list[SECURITY_LIFECYCLE_FLAG].item.val.int64 < UINT32_MAX) {
items->security_lifecycle =
(uint32_t)list[SECURITY_LIFECYCLE_FLAG].item.val.int64;
items->item_flags |=CLAIM_PRESENT_BIT(SECURITY_LIFECYCLE_FLAG);
}
}
/* ---- PROFILE_DEFINITION -------*/
if(list[PROFILE_DEFINITION_FLAG].item.uDataType == QCBOR_TYPE_TEXT_STRING) {
items->profile_definition=list[PROFILE_DEFINITION_FLAG].item.val.string;
items->item_flags |= CLAIM_PRESENT_BIT(PROFILE_DEFINITION_FLAG);
}
/* ---- ORIGINATION -------*/
if(list[ORIGINATION_FLAG].item.uDataType == QCBOR_TYPE_TEXT_STRING) {
items->origination = list[ORIGINATION_FLAG].item.val.string;
items->item_flags |= CLAIM_PRESENT_BIT(ORIGINATION_FLAG);
}
Done:
return return_value;
}
/*
* Public function. See attest_token_decode.h
*/
enum attest_token_err_t
attest_token_get_num_sw_components(struct attest_token_decode_context *me,
uint32_t *num_sw_components)
{
enum attest_token_err_t return_value;
QCBORItem item;
if(me->last_error != ATTEST_TOKEN_ERR_SUCCESS) {
return_value = me->last_error;
goto Done;
}
return_value = qcbor_util_get_top_level_item_in_map(me->payload,
EAT_CBOR_ARM_LABEL_SW_COMPONENTS,
QCBOR_TYPE_ARRAY,
&item);
if(return_value != ATTEST_TOKEN_ERR_SUCCESS) {
if(return_value != ATTEST_TOKEN_ERR_NOT_FOUND) {
/* Something very wrong. Bail out passing on the return_value */
goto Done;
} else {
/* Now decide if it was intentionally left out. */
return_value = qcbor_util_get_top_level_item_in_map(me->payload,
EAT_CBOR_ARM_LABEL_NO_SW_COMPONENTS,
QCBOR_TYPE_INT64,
&item);
if(return_value == ATTEST_TOKEN_ERR_SUCCESS) {
if(item.val.int64 == NO_SW_COMPONENT_FIXED_VALUE) {
/* Successful omission of SW components. Pass on the
* success return_value */
*num_sw_components = 0;
} else {
/* Indicator for no SW components malformed */
return_value = ATTEST_TOKEN_ERR_SW_COMPONENTS_MISSING;
}
} else if(return_value == ATTEST_TOKEN_ERR_NOT_FOUND) {
/* Should have been an indicator for no SW components */
return_value = ATTEST_TOKEN_ERR_SW_COMPONENTS_MISSING;
}
}
} else {
/* The SW components claim exists */
if(item.val.uCount == 0) {
/* Empty SW component not allowed */
return_value = ATTEST_TOKEN_ERR_SW_COMPONENTS_MISSING;
} else {
/* SUCESSS! Pass on the success return_value */
/* Note that this assumes the array is definite length */
*num_sw_components = item.val.uCount;
}
}
Done:
return return_value;
}
/**
* \brief Decode a single SW component
*
* \param[in] decode_context The CBOR decoder context to decode from
* \param[in] sw_component_item The top-level map item for this SW
* component.
* \param[out] sw_component The structure to fill in with decoded data.
*
* \return An error from \ref attest_token_err_t.
*
*/
static inline enum attest_token_err_t
decode_sw_component(QCBORDecodeContext *decode_context,
const QCBORItem *sw_component_item,
struct attest_token_sw_component_t *sw_component)
{
enum attest_token_err_t return_value;
QCBORItem claim_item;
QCBORError cbor_error;
uint_fast8_t next_nest_level; /* nest levels are 8-bit, but a uint8_t
var is often slower and more code */
if(sw_component_item->uDataType != QCBOR_TYPE_MAP) {
return_value = ATTEST_TOKEN_ERR_CBOR_STRUCTURE;
goto Done;
}
/* Zero it, setting booleans to false, pointers to NULL and
lengths to 0 */
memset(sw_component, 0, sizeof(struct attest_token_sw_component_t));
return_value = ATTEST_TOKEN_ERR_SUCCESS;
while(1) {
cbor_error = QCBORDecode_GetNext(decode_context, &claim_item);
if(cbor_error != QCBOR_SUCCESS) {
/* no tolerance for any errors here */
return_value = ATTEST_TOKEN_ERR_CBOR_NOT_WELL_FORMED;
goto Done;
}
if(claim_item.uLabelType == QCBOR_TYPE_INT64) {
switch(claim_item.label.int64) {
case EAT_CBOR_SW_COMPONENT_MEASUREMENT_TYPE:
if(claim_item.uDataType != QCBOR_TYPE_TEXT_STRING) {
return_value = ATTEST_TOKEN_ERR_CBOR_TYPE;
goto Done;
}
sw_component->measurement_type = claim_item.val.string;
sw_component->item_flags |=
CLAIM_PRESENT_BIT(SW_MEASUREMENT_TYPE_FLAG);
break;
case EAT_CBOR_SW_COMPONENT_MEASUREMENT_VALUE:
if(claim_item.uDataType != QCBOR_TYPE_BYTE_STRING) {
return_value = ATTEST_TOKEN_ERR_CBOR_TYPE;
goto Done;
}
sw_component->measurement_val = claim_item.val.string;
sw_component->item_flags |=
CLAIM_PRESENT_BIT(SW_MEASURMENT_VAL_FLAG);
break;
case EAT_CBOR_SW_COMPONENT_VERSION:
if(claim_item.uDataType != QCBOR_TYPE_TEXT_STRING) {
return_value = ATTEST_TOKEN_ERR_CBOR_TYPE;
goto Done;
}
sw_component->version = claim_item.val.string;
sw_component->item_flags |=
CLAIM_PRESENT_BIT(SW_VERSION_FLAG);
break;
case EAT_CBOR_SW_COMPONENT_SIGNER_ID:
if(claim_item.uDataType != QCBOR_TYPE_BYTE_STRING) {
return_value = ATTEST_TOKEN_ERR_CBOR_TYPE;
goto Done;
}
sw_component->signer_id = claim_item.val.string;
sw_component->item_flags |=
CLAIM_PRESENT_BIT(SW_SIGNER_ID_FLAG);
break;
case EAT_CBOR_SW_COMPONENT_MEASUREMENT_DESC:
if(claim_item.uDataType != QCBOR_TYPE_TEXT_STRING) {
return_value = ATTEST_TOKEN_ERR_CBOR_TYPE;
goto Done;
}
sw_component->measurement_desc = claim_item.val.string;
sw_component->item_flags |=
CLAIM_PRESENT_BIT(SW_MEASUREMENT_DESC_FLAG);
break;
}
}
if(qcbor_util_consume_item(decode_context,
&claim_item,
&next_nest_level)) {
return_value = ATTEST_TOKEN_ERR_CBOR_NOT_WELL_FORMED;
goto Done;
}
if(next_nest_level < sw_component_item->uNextNestLevel) {
/* Got all the items in the map */
break;
}
}
Done:
return return_value;
}
/*
* Public function. See attest_token_decode.h
*/
enum attest_token_err_t
attest_token_get_sw_component(struct attest_token_decode_context *me,
uint32_t requested_index,
struct attest_token_sw_component_t *sw_components)
{
enum attest_token_err_t return_value;
QCBORItem sw_components_array_item;
QCBORDecodeContext decode_context;
QCBORItem sw_component_item;
QCBORError qcbor_error;
uint_fast8_t exit_array_level;
if(me->last_error != ATTEST_TOKEN_ERR_SUCCESS) {
return_value = me->last_error;
goto Done;
}
QCBORDecode_Init(&decode_context, me->payload, QCBOR_DECODE_MODE_NORMAL);
/* Find the map containing all the SW Components */
return_value = qcbor_util_decode_to_labeled_item(&decode_context,
EAT_CBOR_ARM_LABEL_SW_COMPONENTS,
&sw_components_array_item);
if(return_value != ATTEST_TOKEN_ERR_SUCCESS) {
goto Done;
}
if(sw_components_array_item.uDataType != QCBOR_TYPE_ARRAY) {
return_value = ATTEST_TOKEN_ERR_CBOR_TYPE;
goto Done;
}
exit_array_level = sw_components_array_item.uNextNestLevel;
/* Loop over contents of SW Components array */
while(1) {
qcbor_error = QCBORDecode_GetNext(&decode_context, &sw_component_item);
if(qcbor_error) {
/* no tolerance for any errors here */
return_value = ATTEST_TOKEN_ERR_CBOR_NOT_WELL_FORMED;
goto Done;
}
if(sw_component_item.uNextNestLevel <= exit_array_level) {
/* Next item will be outside the array */
return_value = ATTEST_TOKEN_ERR_NOT_FOUND;
/* The end of the array containing SW components
and didn't get to the requested_index. */
goto Done;
}
if(requested_index == 0) {
/* Found the one of interest. Decode it and break out */
return_value = decode_sw_component(&decode_context,
&sw_component_item,
sw_components);
break; /* The normal, non-error exit from this loop */
}
/* Every member in the array counts even if they are not
* what is expected */
requested_index--;
if(qcbor_util_consume_item(&decode_context, &sw_component_item, NULL)) {
return_value = ATTEST_TOKEN_ERR_CBOR_NOT_WELL_FORMED;
goto Done;
}
}
Done:
return return_value;
}