Newer
Older
mbed-os / platform / FEATURE_EXPERIMENTAL_API / FEATURE_PSA / TARGET_MBED_PSA_SRV / services / attestation / tfm_impl / t_cose / src / t_cose_sign1_sign.c
@Rajkumar Kanagaraj Rajkumar Kanagaraj on 21 Aug 2020 17 KB Move FEATURE_EXPERIMENTAL_API for PSA to platform
/*
 * t_cose_sign1_sign.c
 *
 * Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * See BSD-3-Clause license in README.md
 */

#include "t_cose_sign1_sign.h"
#include "qcbor.h"
#include "t_cose_defines.h"
#include "t_cose_crypto.h"
#include "t_cose_util.h"


/**
 * \file t_cose_sign1_sign.c
 *
 * \brief This implements t_cose signing
 */


/**
 * \brief Create a short-circuit signature
 *
 * \param[in] cose_alg_id       Algorithm ID. This is used only to make
 *                              the short-circuit signature the same size
 *                              as the real signature would be for the
 *                              particular algorithm.
 * \param[in] hash_to_sign      The bytes to sign. Typically, a hash of
 *                              a payload.
 * \param[in] signature_buffer  Pointer and length of buffer into which
 *                              the resulting signature is put.
 * \param[in] signature         Pointer and length of the signature
 *                              returned.
 *
 * \return This returns one of the error codes defined by \ref t_cose_err_t.
 *
 * This creates the short-circuit signature that is a concatenation of
 * hashes up to the expected size of the signature. This is a test
 * mode only has it has no security value. This is retained in
 * commercial production code as a useful test or demo that can run
 * even if key material is not set up or accessible.
 */
static inline enum t_cose_err_t
short_circuit_sign(int32_t cose_alg_id,
                   struct useful_buf_c hash_to_sign,
                   struct useful_buf signature_buffer,
                   struct useful_buf_c *signature)
{
    /* approximate stack use on 32-bit machine: local use: 16
     */
    enum t_cose_err_t return_value;
    size_t array_indx;
    size_t amount_to_copy;
    size_t sig_size;

    sig_size = t_cose_signature_size(cose_alg_id);

    if(sig_size > signature_buffer.len) {
        /* Buffer too small for this signature type */
        return_value = T_COSE_ERR_SIG_BUFFER_SIZE;
        goto Done;
    }

    /* Loop concatening copies of the hash to fill out to signature size */
    for(array_indx = 0; array_indx < sig_size; array_indx += hash_to_sign.len) {
        amount_to_copy = sig_size - array_indx;
        if(amount_to_copy > hash_to_sign.len) {
            amount_to_copy = hash_to_sign.len;
        }
        memcpy((uint8_t *)signature_buffer.ptr + array_indx,
               hash_to_sign.ptr,
               amount_to_copy);
    }
    signature->ptr = signature_buffer.ptr;
    signature->len = sig_size;
    return_value   = T_COSE_SUCCESS;

Done:
    return return_value;
}



/**
 * The maximum size of a CBOR Encoded \c COSE_Key that this
 * implementation can handle. \c COSE_Key is the serialization format
 * for public and other types of keys defined by [COSE (RFC 8152)]
 * (https://tools.ietf.org/html/rfc8152).
 *
 *  This can be increased to handle larger keys, but stack usage will
 *  go up with this increase.
 */
#define MAX_ENCODED_COSE_KEY_SIZE \
    1 + /* 1 byte to encode map */ \
    2 + /* 2 bytes to encode key type */ \
    2 + /* 2 bytes to encode curve */ \
    2 * /* the X and Y coordinates at 32 bytes each */ \
        (T_COSE_CRYPTO_EC_P256_COORD_SIZE + 1 + 2)


/**
 * \brief CBOR encode a public key as a \c COSE_Key
 *
 * \param[in] key_select  Identifies the public key to encode.
 *
 * \param[in] buffer_for_cose_key  Pointer and length of buffer into which
 *                              the encoded \c COSE_Key is put.
 * \param[in] cose_key         Pointer and length of the encoded \c COSE_Key.
 *
 * \return This returns one of the error codes defined by \ref t_cose_err_t.
 *
 * \c COSE_Key is the COSE-defined format for serializing a key for
 * transmission in a protocol. This function encodes an EC public key
 * expressed as an X and Y coordinate.
 */
static enum t_cose_err_t
t_cose_encode_cose_key(int32_t key_select,
                       struct useful_buf buffer_for_cose_key,
                       struct useful_buf_c *cose_key)
{
    /* approximate stack use on 32-bit machine:
     * local use: 328
     * with calls: 370
     */
    enum t_cose_err_t         return_value;
    QCBORError                qcbor_result;
    QCBOREncodeContext        cbor_encode_ctx;
    USEFUL_BUF_MAKE_STACK_UB( buffer_for_x_coord,
                                  T_COSE_CRYPTO_EC_P256_COORD_SIZE);
    USEFUL_BUF_MAKE_STACK_UB( buffer_for_y_coord,
                                  T_COSE_CRYPTO_EC_P256_COORD_SIZE);
    struct useful_buf_c       x_coord;
    struct useful_buf_c       y_coord;
    int32_t                   cose_curve_id;
    struct useful_buf_c       encoded_key_id;

    /* Get the public key x and y */
    return_value = t_cose_crypto_get_ec_pub_key(key_select,
                                                NULL_USEFUL_BUF_C,
                                                &cose_curve_id,
                                                buffer_for_x_coord,
                                                buffer_for_y_coord,
                                                &x_coord,
                                                &y_coord);
    if(return_value != T_COSE_SUCCESS) {
        goto Done;
    }

    /* Encode it into a COSE_Key structure */
    QCBOREncode_Init(&cbor_encode_ctx, buffer_for_cose_key);
    QCBOREncode_OpenMap(&cbor_encode_ctx);
    QCBOREncode_AddInt64ToMapN(&cbor_encode_ctx,
                               COSE_KEY_COMMON_KTY,
                               COSE_KEY_TYPE_EC2);
    QCBOREncode_AddInt64ToMapN(&cbor_encode_ctx,
                               COSE_KEY_PARAM_CRV,
                               cose_curve_id);
    QCBOREncode_AddBytesToMapN(&cbor_encode_ctx,
                               COSE_KEY_PARAM_X_COORDINATE,
                               x_coord);
    QCBOREncode_AddBytesToMapN(&cbor_encode_ctx,
                               COSE_KEY_PARAM_Y_COORDINATE,
                               y_coord);
    QCBOREncode_CloseMap(&cbor_encode_ctx);

    qcbor_result = QCBOREncode_Finish(&cbor_encode_ctx, &encoded_key_id);
    if(qcbor_result != QCBOR_SUCCESS) {
        /* Mainly means that the COSE_Key was too big for buffer_for_cose_key */
        return_value = T_COSE_ERR_PROTECTED_HEADERS;
        goto Done;
    }

    /* Finish up and return */
    *cose_key = encoded_key_id;
    return_value = T_COSE_SUCCESS;

Done:
    return return_value;
}


/**
 * \brief SHA-256 hash a buffer in one quick function
 *
 * \param[in] bytes_to_hash The bytes to be hashed.
 *
 * \param[in] buffer_for_hash  Pointer and length into which
 *                                   the resulting hash is put.
 * \param[out] hash           Pointer and length of the
 *                                   resulting hash.
 * \return This returns one of the error codes defined by \ref t_cose_err_t.
 *
 * Simple wrapper for start, update and finish interface to a hash.
 *
 * Having this as a separate function helps keep stack usage down and
 * is convenient.
 */
static enum t_cose_err_t quick_sha256(struct useful_buf_c bytes_to_hash,
                                      struct useful_buf buffer_for_hash,
                                      struct useful_buf_c *hash)
{
    /* approximate stack use on 32-bit machine:
     local use: 132
     */
    enum t_cose_err_t           return_value = 0;
    struct t_cose_crypto_hash   hash_ctx;

    return_value = t_cose_crypto_hash_start(&hash_ctx,
                                            COSE_ALG_SHA256_PROPRIETARY);
    if(return_value) {
        goto Done;
    }
    t_cose_crypto_hash_update(&hash_ctx, bytes_to_hash);
    return_value = t_cose_crypto_hash_finish(&hash_ctx,
                                             buffer_for_hash,
                                             hash);

Done:
    return return_value;
}


/**
 * \brief Make a key ID based on the public key to go in the kid
 * unprotected header.
 *
 * \param[in] key_select         Identifies the public key.
 * \param[in] buffer_for_key_id  Pointer and length into which
 *                               the resulting key ID is put.
 * \param[out] key_id            Pointer and length of the
 *                               resulting key ID.
 *
 * \return This returns one of the error codes defined by \ref t_cose_err_t.
 *
 *
 * See t_cose_sign1_init() for documentation of the key ID format
 * created here.
 */
static inline enum t_cose_err_t get_keyid(int32_t key_select,
                                          struct useful_buf buffer_for_key_id,
                                          struct useful_buf_c *key_id)
{
    /* approximate stack use on 32-bit machine:
     * local use: 100
     * with calls inlined: 560
     * with calls not inlined: 428
     */
    enum t_cose_err_t           return_value;
    USEFUL_BUF_MAKE_STACK_UB(   buffer_for_cose_key,
                                    MAX_ENCODED_COSE_KEY_SIZE);
    struct useful_buf_c         cose_key;

    /* Doing the COSE encoding and the hashing in separate functions
     * called from here reduces the stack usage in this function by a
     * lot
     */

    /* Get the key and encode it as a COSE_Key  */
    return_value = t_cose_encode_cose_key(key_select,
                                          buffer_for_cose_key,
                                          &cose_key);
    if(return_value != T_COSE_SUCCESS) {
        goto Done;
    }

    /* SHA256 hash of it is all we care about in the end */
    return_value = quick_sha256(cose_key, buffer_for_key_id, key_id);

Done:
    return return_value;
}


/**
 * \brief  Makes the protected headers for COSE.
 *
 * \param[in] cose_alg_id  The COSE algorithm ID to put in the headers.
 *
 * \param[in] buffer_for_header  Pointer and length into which
 *                               the resulting encoded protected
 *                               headers is put.
 *
 * \return The pointer and length of the protected headers is
 * returned, or \c NULL_USEFUL_BUF_C if this fails.
 *
 * The protected headers are returned in fully encoded CBOR format as
 * they are added to the \c COSE_Sign1 as a binary string. This is
 * different from the unprotected headers which are not handled this
 * way.
 *
 * This returns \c NULL_USEFUL_BUF_C if buffer_for_header was too
 * small. See also definition of \ref T_COSE_SIGN1_MAX_PROT_HEADER
 */
static inline struct useful_buf_c
make_protected_header(int32_t cose_alg_id,
                      struct useful_buf buffer_for_header)
{
    /* approximate stack use on 32-bit machine:
     * local use: 170
     * with calls: 210
     */
    struct useful_buf_c protected_headers;
    QCBORError          qcbor_result;
    QCBOREncodeContext  cbor_encode_ctx;
    struct useful_buf_c return_value;

    QCBOREncode_Init(&cbor_encode_ctx, buffer_for_header);
    QCBOREncode_OpenMap(&cbor_encode_ctx);
    QCBOREncode_AddInt64ToMapN(&cbor_encode_ctx,
                               COSE_HEADER_PARAM_ALG,
                               cose_alg_id);
    QCBOREncode_CloseMap(&cbor_encode_ctx);
    qcbor_result = QCBOREncode_Finish(&cbor_encode_ctx, &protected_headers);

    if(qcbor_result == QCBOR_SUCCESS) {
        return_value = protected_headers;
    } else {
        return_value = NULL_USEFUL_BUF_C;
    }

    return return_value;
}


/**
 * \brief Add the unprotected headers to a CBOR encoding context
 *
 * \param[in] cbor_encode_ctx  CBOR encoding context to output to
 * \param[in] kid              The key ID to go into the kid header.
 *
 * No error is returned. If an error occurred it will be returned when
 * \c QCBOR_Finish() is called on \c cbor_encode_ctx.
 *
 * The unprotected headers added by this are just the key ID
 */
static inline void add_unprotected_headers(QCBOREncodeContext *cbor_encode_ctx,
                                           struct useful_buf_c kid)
{
    QCBOREncode_OpenMap(cbor_encode_ctx);
    QCBOREncode_AddBytesToMapN(cbor_encode_ctx, COSE_HEADER_PARAM_KID, kid);
    QCBOREncode_CloseMap(cbor_encode_ctx);
}


/*
 * Public function. See t_cose_sign1_sign.h
 */
enum t_cose_err_t t_cose_sign1_init(struct t_cose_sign1_ctx *me,
                                    bool short_circuit_sign,
                                    int32_t cose_alg_id,
                                    int32_t key_select,
                                    QCBOREncodeContext *cbor_encode_ctx)
{
    /* approximate stack use on 32-bit machine:
     * local use: 66
     * with calls inlined: 900
     * with calls not inlined: 500
     */

    int32_t                     hash_alg;
    enum t_cose_err_t           return_value;
    USEFUL_BUF_MAKE_STACK_UB(   buffer_for_kid, T_COSE_CRYPTO_SHA256_SIZE);
    struct useful_buf_c         kid;
    struct useful_buf           buffer_for_protected_header;

    /* Check the cose_alg_id now by getting the hash alg as an early
     error check even though it is not used until later. */
    hash_alg = hash_alg_id_from_sig_alg_id(cose_alg_id);
    if(hash_alg == INT32_MAX) {
        return T_COSE_ERR_UNSUPPORTED_SIGNING_ALG;
    }

    /* Remember all the parameters in the context */
    me->cose_algorithm_id   = cose_alg_id;
    me->key_select          = key_select;
    me->short_circuit_sign  = short_circuit_sign;
    me->cbor_encode_ctx     = cbor_encode_ctx;

    /* Get the key id because it goes into the headers that are about
     to be made. */
    if(short_circuit_sign) {
        return_value = get_short_circuit_kid(buffer_for_kid, &kid);
    } else {
        return_value = get_keyid(key_select, buffer_for_kid, &kid);
    }
    if(return_value) {
        goto Done;
    }

    /* Get started with the tagged array that holds the four parts of
     a cose single signed message */
    QCBOREncode_AddTag(cbor_encode_ctx, CBOR_TAG_COSE_SIGN1);
    QCBOREncode_OpenArray(cbor_encode_ctx);

    /* The protected headers, which are added as a wrapped bstr  */
    buffer_for_protected_header =
        USEFUL_BUF_FROM_BYTE_ARRAY(me->buffer_for_protected_headers);
    me->protected_headers = make_protected_header(cose_alg_id,
                                                  buffer_for_protected_header);
    if(useful_buf_c_is_null(me->protected_headers)) {
        /* The sizing of storage for protected headers is
          off (should never happen in tested, released code) */
        return_value = T_COSE_SUCCESS;
        goto Done;
    }
    QCBOREncode_AddBytes(cbor_encode_ctx, me->protected_headers);

    /* The Unprotected headers */
    add_unprotected_headers(cbor_encode_ctx, kid);

    /* Any failures in CBOR encoding will be caught in finish
     when the CBOR encoding is closed off. No need to track
     here as the CBOR encoder tracks it internally. */

    return_value = T_COSE_SUCCESS;

Done:
    return return_value;
}


/*
 * Public function. See t_cose_sign1_sign.h
 */
enum t_cose_err_t t_cose_sign1_finish(struct t_cose_sign1_ctx *me,
                                      struct useful_buf_c signed_payload)
{
    /* approximate stack use on 32-bit machine:
     *   local use: 116
     * with calls inline: 500
     * with calls not inlined; 450
     */
    enum t_cose_err_t          return_value;
    /* pointer and length of the completed tbs hash */
    struct useful_buf_c        tbs_hash;
    /* Pointer and length of the completed signature */
    struct useful_buf_c        signature;
    /* Buffer for the actual signature */
    USEFUL_BUF_MAKE_STACK_UB(  buffer_for_signature,
                                   T_COSE_MAX_EC_SIG_SIZE);
    /* Buffer for the tbs hash. Only big enough for SHA256 */
    USEFUL_BUF_MAKE_STACK_UB(  buffer_for_tbs_hash,
                                   T_COSE_CRYPTO_SHA256_SIZE);

    /* Create the hash of the to-be-signed bytes. Inputs to the hash
     * are the protected headers, the payload that getting signed, the
     * cose signature alg from which the hash alg is determined. The
     * cose_algorithm_id was checked in t_cose_sign1_init() so it
     * doesn't need to be checked here.
     */
    return_value = create_tbs_hash(me->cose_algorithm_id,
                                   buffer_for_tbs_hash,
                                   &tbs_hash,
                                   me->protected_headers,
                                   signed_payload);
    if(return_value) {
        goto Done;
    }

    /* Compute the signature using public key crypto. The key selector
     * and algorithm ID are passed in to know how and what to sign
     * with. The hash of the TBS bytes are what is signed. A buffer in
     * which to place the signature is passed in and the signature is
     * returned.
     *
     * Short-circuit signing is invoked if requested. It does no
     * public key operation and requires no key. It is just a test
     * mode that always works.
     */
    if(me->short_circuit_sign) {
        return_value = short_circuit_sign(me->cose_algorithm_id,
                                          tbs_hash,
                                          buffer_for_signature,
                                          &signature);
    } else {
        return_value = t_cose_crypto_pub_key_sign(me->cose_algorithm_id,
                                                  me->key_select,
                                                  tbs_hash,
                                                  buffer_for_signature,
                                                  &signature);
    }
    if(return_value) {
        goto Done;
    }

    /* Add signature to CBOR and close out the array */
    QCBOREncode_AddBytes(me->cbor_encode_ctx, signature);
    QCBOREncode_CloseArray(me->cbor_encode_ctx);

    /* CBOR encoding errors are tracked in the CBOR encoding context
     and handled in the layer above this */

Done:
    return return_value;
}