diff --git a/CMakeLists.txt b/CMakeLists.txt index 26829dcc..2d3b9c14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,8 @@ set(T_COSE_SRC_COMMON src/t_cose_encrypt_dec.c src/t_cose_recipient_dec_keywrap.c src/t_cose_recipient_enc_keywrap.c + src/t_cose_recipient_dec_esdh.c + src/t_cose_recipient_enc_esdh.c src/t_cose_qcbor_gap.c ) diff --git a/Makefile.common b/Makefile.common index 133e8595..0e995fa7 100644 --- a/Makefile.common +++ b/Makefile.common @@ -33,6 +33,9 @@ SRC_OBJ=src/t_cose_util.o \ src/t_cose_encrypt_dec.o \ src/t_cose_recipient_dec_keywrap.o \ src/t_cose_recipient_enc_keywrap.o \ + src/t_cose_recipient_dec_esdh.o \ + src/t_cose_recipient_enc_esdh.o \ + src/hpke.o \ src/t_cose_qcbor_gap.o @@ -69,6 +72,10 @@ PUBLIC_INTERFACE=inc/t_cose/q_useful_buf.h \ inc/t_cose/t_cose_recipient_dec_keywrap.h \ inc/t_cose/t_cose_recipient_enc.h \ inc/t_cose/t_cose_recipient_enc_keywrap.h \ + inc/t_cose/t_cose_recipient_dec_esdh.h \ + inc/t_cose/t_cose_recipient_enc.h \ + inc/t_cose/t_cose_recipient_enc_keywrap.h \ + inc/t_cose/t_cose_recipient_enc_esdh.h \ inc/t_cose/t_cose_sign1_sign.h \ inc/t_cose/t_cose_sign1_verify.h \ inc/t_cose/t_cose_sign_sign.h \ @@ -186,6 +193,10 @@ src/t_cose_recipient_enc_keywrap.o: src/t_cose_recipient_enc_keywrap.c src/t_cose_recipient_dec_keywrap.o: src/t_cose_recipient_dec_keywrap.c +src/t_cose_recpient_enc_esdh.o: src/t_cose_recipient_enc_esdh.c + +src/t_cose_recpient_dec_esdh.o: src/t_cose_recipient_dec_esdh.c + # ---- test dependencies ----- test/t_cose_test.o: test/t_cose_test.h \ test/t_cose_make_test_messages.h \ diff --git a/crypto_adapters/t_cose_openssl_crypto.c b/crypto_adapters/t_cose_openssl_crypto.c index 753465cd..f7d06713 100644 --- a/crypto_adapters/t_cose_openssl_crypto.c +++ b/crypto_adapters/t_cose_openssl_crypto.c @@ -1411,6 +1411,48 @@ t_cose_crypto_free_symmetric_key(struct t_cose_key key) } +/* + * See documentation in t_cose_crypto.h + */ +enum t_cose_err_t +t_cose_crypto_export_public_key(struct t_cose_key key, + struct q_useful_buf pk_buffer, + size_t *pk_len) +{ + /* TBD: This is a dummy function */ + *pk_len = 0; + return T_COSE_SUCCESS; +} + + +/* + * See documentation in t_cose_crypto.h + */ +enum t_cose_err_t +t_cose_crypto_generate_key(struct t_cose_key *ephemeral_key, + int32_t cose_algorithm_id) +{ + /* TBD: This is a dummy function */ + return T_COSE_SUCCESS; +} + + +/* + * See documentation in t_cose_crypto.h + */ +enum t_cose_err_t +t_cose_crypto_key_agreement(const int32_t cose_algorithm_id, + struct t_cose_key private_key, + struct t_cose_key public_key, + struct q_useful_buf symmetric_key, + size_t *symmetric_key_len + ) +{ + /* TBD: This is a dummy function */ + *symmetric_key_len = 0; + return T_COSE_SUCCESS; +} + /* Compute size of ciphertext, given size of plaintext. Returns * SIZE_MAX if the algorithm is unknown. Also returns the tag * length. */ diff --git a/crypto_adapters/t_cose_psa_crypto.c b/crypto_adapters/t_cose_psa_crypto.c index d7dd1e54..9e05c0cb 100644 --- a/crypto_adapters/t_cose_psa_crypto.c +++ b/crypto_adapters/t_cose_psa_crypto.c @@ -726,14 +726,17 @@ t_cose_crypto_generate_key(struct t_cose_key *ephemeral_key, psa_status_t status; switch (cose_algorithm_id) { + case T_COSE_ELLIPTIC_CURVE_P_256: case T_COSE_HPKE_KEM_ID_P256: type = PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1); key_bitlen = 256; break; + case T_COSE_ELLIPTIC_CURVE_P_384: case T_COSE_HPKE_KEM_ID_P384: type = PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1); key_bitlen = 384; break; + case T_COSE_ELLIPTIC_CURVE_P_521: case T_COSE_HPKE_KEM_ID_P521: type = PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1); key_bitlen = 521; @@ -1325,6 +1328,59 @@ t_cose_crypto_aead_decrypt(const int32_t cose_algorithm_id, +/* + * See documentation in t_cose_crypto.h + */ + +enum t_cose_err_t +t_cose_crypto_key_agreement(const int32_t cose_algorithm_id, + struct t_cose_key private_key, + struct t_cose_key public_key, + struct q_useful_buf symmetric_key, + size_t *symmetric_key_len + ) +{ + psa_status_t status; + size_t pubKey_len; + enum t_cose_err_t return_value; + psa_algorithm_t key_agreement_alg; + Q_USEFUL_BUF_MAKE_STACK_UB(pubKey, T_COSE_EXPORT_PUBLIC_KEY_MAX_SIZE ); + + switch(cose_algorithm_id) { + case T_COSE_ALGORITHM_ECDH_ES_A128KW: + case T_COSE_ALGORITHM_ECDH_ES_A192KW: + case T_COSE_ALGORITHM_ECDH_ES_A256KW: + key_agreement_alg = PSA_ALG_ECDH; + break; + default: + return T_COSE_ERR_UNSUPPORTED_CONTENT_KEY_DISTRIBUTION_ALG; + } + + /* Export public key for use with PSA Crypto API */ + return_value = t_cose_crypto_export_public_key( + public_key, + pubKey, + &pubKey_len); + + if (return_value != T_COSE_SUCCESS) { + return(return_value); + } + + /* Produce ECDH derived key */ + status = psa_raw_key_agreement( key_agreement_alg, // algorithm id + private_key.key.handle, // client secret key + pubKey.ptr, pubKey_len, // server public key + symmetric_key.ptr, // buffer to store derived key + symmetric_key.len, // length of the buffer for derived key + symmetric_key_len ); // length of derived key + if( status != PSA_SUCCESS ) + { + return T_COSE_ERR_KEY_AGREEMENT_FAIL; + } + + return T_COSE_SUCCESS; +} + /* * See documentation in t_cose_crypto.h diff --git a/crypto_adapters/t_cose_test_crypto.c b/crypto_adapters/t_cose_test_crypto.c index 9a7cd801..d895be87 100644 --- a/crypto_adapters/t_cose_test_crypto.c +++ b/crypto_adapters/t_cose_test_crypto.c @@ -389,6 +389,54 @@ t_cose_crypto_verify_eddsa(struct t_cose_key verification_key, } +/* + * See documentation in t_cose_crypto.h + */ +enum t_cose_err_t +t_cose_crypto_export_public_key(struct t_cose_key key, + struct q_useful_buf pk_buffer, + size_t *pk_len) +{ + (void)key; + (void)pk_buffer; + (void)pk_len; + return T_COSE_ERR_PUBLIC_KEY_EXPORT_FAILED; +} + + +/* + * See documentation in t_cose_crypto.h + */ +enum t_cose_err_t +t_cose_crypto_generate_key(struct t_cose_key *ephemeral_key, + int32_t cose_algorithm_id) +{ + (void)ephemeral_key; + (void)cose_algorithm_id; + return T_COSE_ERR_KEY_GENERATION_FAILED; +} + + +/* + * See documentation in t_cose_crypto.h + */ +enum t_cose_err_t +t_cose_crypto_key_agreement(const int32_t cose_algorithm_id, + struct t_cose_key private_key, + struct t_cose_key public_key, + struct q_useful_buf symmetric_key, + size_t *symmetric_key_len + ) +{ + (void)cose_algorithm_id; + (void)private_key; + (void)public_key; + (void)symmetric_key; + (void)symmetric_key_len; + return T_COSE_ERR_KEY_AGREEMENT_FAIL; +} + + /* * See documentation in t_cose_crypto.h */ diff --git a/examples/encryption_examples.c b/examples/encryption_examples.c index 20a1dfcc..0c42c1df 100644 --- a/examples/encryption_examples.c +++ b/examples/encryption_examples.c @@ -21,6 +21,10 @@ #include "print_buf.h" #include "init_keys.h" +#define PAYLOAD "This is the payload" +#define TEST_SENDER_IDENTITY "sender" +#define TEST_RECIPIENT_IDENTITY "recipient" + /* This file is crypto-library independent. It works for OpenSSL, Mbed * TLS and others. The key initialization, which *is* crypto-library @@ -255,3 +259,194 @@ key_wrap_example(void) + + +#ifndef T_COSE_DISABLE_ESDH + +#include "t_cose/t_cose_recipient_enc_esdh.h" +#include "t_cose/t_cose_recipient_dec_esdh.h" + +int32_t +esdh_example(void) +{ + enum t_cose_err_t result; + struct t_cose_key skR; + struct t_cose_key pkR; + struct t_cose_encrypt_enc enc_ctx; + struct t_cose_recipient_enc_esdh recipient; + struct t_cose_info_t info; + struct q_useful_buf_c cose_encrypted_message; + Q_USEFUL_BUF_MAKE_STACK_UB ( cose_encrypt_message_buffer, 400); + + printf("\n---- START EXAMPLE ESDH ----\n"); + printf("Create COSE_Encrypt with attached payload using ESDH\n"); + + /* Create a key pair. This is a fixed test key pair. The creation + * of this key pair is crypto-library dependent because t_cose_key + * is crypto-library dependent. See t_cose_key.h and the examples + * to understand key-pair creation better. */ + result = init_fixed_test_ec_encryption_key(T_COSE_ELLIPTIC_CURVE_P_256, + &pkR, /* out: public key to be used for encryption */ + &skR); /* out: corresponding private key for decryption */ + if(result != T_COSE_SUCCESS) { + goto Done; + } + + /* Initialize the encryption context telling it we want + * a COSE_Encrypt (not a COSE_Encrypt0) because we're doing ECDH with a + * COSE_Recipient. Also tell it the AEAD algorithm for the + * body of the message. + */ + t_cose_encrypt_enc_init(&enc_ctx, + T_COSE_OPT_MESSAGE_TYPE_ENCRYPT, + T_COSE_ALGORITHM_A128GCM); + + /* Create the recipient object telling it the algorithm and the public key + * for the COSE_Recipient it's going to make. + */ + t_cose_recipient_enc_esdh_init(&recipient, + T_COSE_ALGORITHM_ECDH_ES_A128KW, /* content key distribution id */ + T_COSE_ELLIPTIC_CURVE_P_256); /* curve id */ + + t_cose_recipient_enc_esdh_set_key(&recipient, + pkR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY)); + + /* Set Context Info structure */ + info.enc_alg = T_COSE_ALGORITHM_A128GCM; + info.sender_identity_type_id = 1; + info.recipient_identity_type_id = 1; + info.sender_identity = Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_SENDER_IDENTITY); + info.recipient_identity = Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY); + info.enc_ctx = &enc_ctx; + + t_cose_recipient_enc_esdh_set_info(&recipient, &info); + + /* Give the recipient object to the main encryption context. + * (Only one recipient is set here, but there could be more). + */ + t_cose_encrypt_add_recipient(&enc_ctx, + (struct t_cose_recipient_enc *)&recipient); + + /* Now do the actual encryption */ + result = t_cose_encrypt_enc(&enc_ctx, /* in: encryption context */ + Q_USEFUL_BUF_FROM_SZ_LITERAL(PAYLOAD), /* in: payload to encrypt */ + NULL_Q_USEFUL_BUF_C, /* in/unused: AAD */ + cose_encrypt_message_buffer, /* in: buffer for COSE_Encrypt */ + &cose_encrypted_message); /* out: COSE_Encrypt */ + + if (result != T_COSE_SUCCESS) { + printf("error encrypting (%d)\n", result); + goto Done; + } + + print_useful_buf("\nCOSE_Encrypt: ", cose_encrypted_message); + + /* TBD: Decryption goes in here. + * Assume everything worked fine. + */ + result = 0; + +Done: + printf("---- %s EXAMPLE ESDH (%d) ----\n\n", + result ? "FAILED" : "COMPLETED", result); + return (int32_t)result; +} + +int32_t +esdh_example_detached(void) +{ + enum t_cose_err_t result; + struct t_cose_key skR; + struct t_cose_key pkR; + struct t_cose_encrypt_enc enc_ctx; + struct t_cose_recipient_enc_esdh recipient; + struct t_cose_info_t info; + struct q_useful_buf_c cose_encrypted_message; + Q_USEFUL_BUF_MAKE_STACK_UB ( cose_encrypt_message_buffer, 400); + struct q_useful_buf_c encrypted_detached_payload; + Q_USEFUL_BUF_MAKE_STACK_UB ( encrypted_detached_ciphertext_buffer, 50); + + printf("\n---- START EXAMPLE ESDH ----\n"); + printf("Create COSE_Encrypt with detached payload using ESDH\n"); + + /* Create a key pair. This is a fixed test key pair. The creation + * of this key pair is crypto-library dependent because t_cose_key + * is crypto-library dependent. See t_cose_key.h and the examples + * to understand key-pair creation better. */ + result = init_fixed_test_ec_encryption_key(T_COSE_ELLIPTIC_CURVE_P_256, + &pkR, /* out: public key to be used for encryption */ + &skR); /* out: corresponding private key for decryption */ + if(result != T_COSE_SUCCESS) { + goto Done; + } + + /* Initialize the encryption context telling it we want + * a COSE_Encrypt (not a COSE_Encrypt0) because we're doing ECDH with a + * COSE_Recipient. Also tell it the AEAD algorithm for the + * body of the message. + */ + t_cose_encrypt_enc_init(&enc_ctx, + T_COSE_OPT_MESSAGE_TYPE_ENCRYPT, + T_COSE_ALGORITHM_A128GCM); + + /* Create the recipient object telling it the algorithm and the public key + * for the COSE_Recipient it's going to make. + */ + t_cose_recipient_enc_esdh_init(&recipient, + T_COSE_ALGORITHM_ECDH_ES_A128KW, /* content key distribution id */ + T_COSE_ELLIPTIC_CURVE_P_256); /* curve id */ + + t_cose_recipient_enc_esdh_set_key(&recipient, + pkR, + Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY)); + + /* Set Context Info structure */ + info.enc_alg = T_COSE_ALGORITHM_A128GCM; + info.sender_identity_type_id = 1; + info.recipient_identity_type_id = 1; + info.sender_identity = Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_SENDER_IDENTITY); + info.recipient_identity = Q_USEFUL_BUF_FROM_SZ_LITERAL(TEST_RECIPIENT_IDENTITY); + info.enc_ctx = &enc_ctx; + + t_cose_recipient_enc_esdh_set_info(&recipient, &info); + + /* Give the recipient object to the main encryption context. + * (Only one recipient is set here, but there could be more). + */ + t_cose_encrypt_add_recipient(&enc_ctx, + (struct t_cose_recipient_enc *)&recipient); + + /* Now do the actual encryption */ + result = t_cose_encrypt_enc_detached(&enc_ctx, /* in: encryption context */ + Q_USEFUL_BUF_FROM_SZ_LITERAL(PAYLOAD), /* in: payload to encrypt */ + NULL_Q_USEFUL_BUF_C, /* in/unused: AAD */ + encrypted_detached_ciphertext_buffer, /* in: buffer for detached ciphertext */ + cose_encrypt_message_buffer, /* in: buffer for COSE_Encrypt */ + &encrypted_detached_payload, /* out: encrypted detached */ + &cose_encrypted_message); /* out: COSE_Encrypt */ + + if (result != T_COSE_SUCCESS) { + printf("error encrypting (%d)\n", result); + goto Done; + } + + print_useful_buf("COSE_Encrypt: ", cose_encrypted_message); + print_useful_buf("Detached Ciphertext: ", encrypted_detached_payload); + + /* TBD: Decryption goes in here. + * Assume everything worked fine. + */ + result = 0; + +Done: + printf("---- %s EXAMPLE ESDH (%d) ----\n\n", + result ? "FAILED" : "COMPLETED", result); + return (int32_t)result; + + /* Free test keys */ + free_fixed_test_ec_encryption_key(pkR); + free_fixed_test_ec_encryption_key(skR); + +} +#endif /* !T_COSE_DISABLE_ESDH */ diff --git a/examples/encryption_examples.h b/examples/encryption_examples.h index 1609cf44..66d5deb8 100644 --- a/examples/encryption_examples.h +++ b/examples/encryption_examples.h @@ -24,4 +24,8 @@ int32_t hpke_example(void); int32_t hpke_example_detached(void); +int32_t esdh_example(void); + +int32_t esdh_example_detached(void); + #endif /* encryption_examples_h */ diff --git a/examples/examples_main.c b/examples/examples_main.c index 2402477d..9ca121f8 100644 --- a/examples/examples_main.c +++ b/examples/examples_main.c @@ -39,6 +39,11 @@ static test_entry s_tests[] = { #ifndef T_COSE_DISABLE_KEYWRAP TEST_ENTRY(key_wrap_example), #endif /* !T_COSE_DISABLE_KEYWRAP */ + +#ifndef T_COSE_DISABLE_ESDH + TEST_ENTRY(esdh_example), + TEST_ENTRY(esdh_example_detached), +#endif /* !T_COSE_DISABLE_ESDH */ }; diff --git a/examples/init_keys_psa.c b/examples/init_keys_psa.c index 025b349a..15eac1a5 100644 --- a/examples/init_keys_psa.c +++ b/examples/init_keys_psa.c @@ -185,9 +185,6 @@ void free_fixed_signing_key(struct t_cose_key key_pair) psa_destroy_key((psa_key_handle_t)key_pair.key.handle); } - - - /* * Public function, see init_keys.h */ @@ -196,6 +193,7 @@ init_fixed_test_ec_encryption_key(uint32_t cose_ec_curve_id, struct t_cose_key *public_key, struct t_cose_key *private_key) { + psa_status_t status; psa_key_attributes_t attributes; psa_key_type_t type_public; @@ -221,8 +219,7 @@ init_fixed_test_ec_encryption_key(uint32_t cose_ec_curve_id, case T_COSE_ELLIPTIC_CURVE_P_521: type_public = PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1); type_private = PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1); - key_bytes = Q_USEFUL_BUF_FROM_BYTE_ARRAY_LITERAL(ec_P_521_priv_key_raw); - key_bitlen = 521; + key_bitlen = 521; break; default: return T_COSE_ERR_UNSUPPORTED_ELLIPTIC_CURVE_ALG; @@ -260,6 +257,7 @@ init_fixed_test_ec_encryption_key(uint32_t cose_ec_curve_id, status = psa_copy_key((mbedtls_svc_key_id_t)private_key->key.handle, &attributes, (mbedtls_svc_key_id_t *)&(public_key->key.handle)); + if (status != PSA_SUCCESS) { return T_COSE_ERR_PRIVATE_KEY_IMPORT_FAILED; } diff --git a/inc/t_cose/t_cose_common.h b/inc/t_cose/t_cose_common.h index 1c96ce8b..06fd0a99 100644 --- a/inc/t_cose/t_cose_common.h +++ b/inc/t_cose/t_cose_common.h @@ -255,7 +255,26 @@ struct t_cose_crypto_hpke_suite_t { uint16_t aead_id; // Authenticated Encryption with Associated Data id }; +/*! + * \brief ESDH ciphersuite + */ +struct t_cose_crypto_esdh_suite_t { + int16_t ckd_id; // Content Key Distribution id + int16_t curve_id; // Curve id +}; + +/*! + * \brief Data items needed for information context structure + */ +struct t_cose_info_t { + int32_t enc_alg; // encryption algorithm + uint8_t sender_identity_type_id; // sender identity type + struct q_useful_buf_c sender_identity; // sender identity + uint8_t recipient_identity_type_id; // recipient identity type + struct q_useful_buf_c recipient_identity; // recipient identity + struct t_cose_encrypt_enc *enc_ctx; // encryption context +}; /* Private value. Intentionally not documented for Doxygen. * This is the size allocated for the encoded protected headers. It @@ -611,13 +630,16 @@ enum t_cose_err_t { /** The HMAC did not successfully verify. */ T_COSE_ERR_HMAC_VERIFY = 80, + /** The key agreement failed. */ + T_COSE_ERR_KEY_AGREEMENT_FAIL = 81, + /** General unsupported operation failure. */ - T_COSE_ERR_UNSUPPORTED = 81, + T_COSE_ERR_UNSUPPORTED = 82, /* A signing operation is in progress. The function returning this value * can be called again until it returns \ref T_COSE_SUCCESS or error. */ - T_COSE_ERR_SIG_IN_PROGRESS = 82, + T_COSE_ERR_SIG_IN_PROGRESS = 83, }; diff --git a/inc/t_cose/t_cose_encrypt_enc.h b/inc/t_cose/t_cose_encrypt_enc.h index 6b408ee7..517c84b9 100644 --- a/inc/t_cose/t_cose_encrypt_enc.h +++ b/inc/t_cose/t_cose_encrypt_enc.h @@ -150,6 +150,8 @@ struct t_cose_encrypt_enc { struct t_cose_key cek; struct t_cose_parameter *added_body_parameters; struct q_useful_buf extern_enc_struct_buffer; + struct q_useful_buf extern_hash_buffer; + int32_t hash_cose_algorithm_id; }; diff --git a/inc/t_cose/t_cose_recipient_dec_esdh.h b/inc/t_cose/t_cose_recipient_dec_esdh.h new file mode 100644 index 00000000..6baced89 --- /dev/null +++ b/inc/t_cose/t_cose_recipient_dec_esdh.h @@ -0,0 +1,105 @@ +/* + * t_cose_recipient_dec_esdh.h + * + * Copyright (c) 2022, Arm Limited. All rights reserved. + * Copyright (c) 2023, Laurence Lundblade. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * See BSD-3-Clause license in README.md + */ + +#ifndef __T_COSE_RECIPIENT_DEC_ESDH_H__ +#define __T_COSE_RECIPIENT_DEC_ESDH_H__ + +#include +#include +#include "t_cose/t_cose_parameters.h" +#include "t_cose/t_cose_recipient_dec.h" +#include "t_cose/t_cose_key.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + + +/* This is the decoder for COSE_Recipients of type ESDH. +* To use this make an instance of it, initialize it and set +* the sKR, and add it as a t_cose_recipient_dec to t_cose_encrypt_dec. +* When t_cose_encrypt_dec() is called to process the cose message +* a callback will be made to this code to process COSE_Recpients +* it encounters that might be of type ESDH. +*/ +struct t_cose_recipient_dec_esdh { + /* Private data structure */ + + /* t_cose_recipient_dec must be the first item for the polymorphism to + * work. This structure, t_cose_recipient_enc_keywrap, will sometimes be + * uses as a t_cose_recipient_enc. + */ + struct t_cose_recipient_dec base; + + struct t_cose_key skr; + struct q_useful_buf_c kid; +}; + + +static void +t_cose_recipient_dec_esdh_init(struct t_cose_recipient_dec_esdh *context); + + + +/* Set the secret key of the receiver, the skR, the one that will be used to + * by the DH key agreement in ESDH. The kid + * is optional. */ + +// TODO: describe and implement the rules for kid matching + +static void +t_cose_recipient_dec_esdh_set_skr(struct t_cose_recipient_dec_esdh *context, + struct t_cose_key skr, + struct q_useful_buf_c kid); + + +/* ========================================================================= + BEGINNING OF PRIVATE INLINE IMPLEMENTATION + ========================================================================= */ + + +enum t_cose_err_t +t_cose_recipient_dec_esdh_cb_private(struct t_cose_recipient_dec *me_x, + const struct t_cose_header_location loc, + const struct t_cose_alg_and_bits ce_alg, + QCBORDecodeContext *cbor_decoder, + struct q_useful_buf cek_buffer, + struct t_cose_parameter_storage *p_storage, + struct t_cose_parameter **params, + struct q_useful_buf_c *cek); + + +static inline void +t_cose_recipient_dec_esdh_init(struct t_cose_recipient_dec_esdh *me) +{ + memset(me, 0, sizeof(*me)); + + me->base.decode_cb = t_cose_recipient_dec_esdh_cb_private; +} + + +static inline void +t_cose_recipient_dec_esdh_set_skr(struct t_cose_recipient_dec_esdh *me, + struct t_cose_key skr, + struct q_useful_buf_c kid) +{ + me->skr = skr; + me->kid = kid; +} + + +#ifdef __cplusplus +} +#endif + +#endif /* __T_COSE_RECIPIENT_DEC_ESDH_H__ */ diff --git a/inc/t_cose/t_cose_recipient_enc_esdh.h b/inc/t_cose/t_cose_recipient_enc_esdh.h new file mode 100644 index 00000000..cd7325f0 --- /dev/null +++ b/inc/t_cose/t_cose_recipient_enc_esdh.h @@ -0,0 +1,124 @@ +/* + * t_cose_recipient_enc_esdh.h + * + * Copyright (c) 2022, Arm Limited. All rights reserved. + * Copyright (c) 2023, Laurence Lundblade. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * See BSD-3-Clause license in README.md + */ + +#ifndef __T_COSE_RECIPIENT_ENC_ESDH_H__ +#define __T_COSE_RECIPIENT_ENC_ESDH_H__ + +#include +#include +#include "t_cose/t_cose_parameters.h" +#include "t_cose/t_cose_common.h" +#include "t_cose/t_cose_key.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +struct t_cose_recipient_enc_esdh { + /* Private data structure */ + + /* t_cose_recipient_enc must be the first item for the polymorphism to + * work. This structure, t_cose_recipient_enc_esdh, will sometimes be + * uses as a t_cose_recipient_enc. + */ + struct t_cose_recipient_enc e; + + struct t_cose_key pkR; /* recipient public key */ + struct q_useful_buf_c kid; + struct t_cose_crypto_esdh_suite_t esdh_suite; + struct t_cose_parameter *added_params; + struct t_cose_info_t *info; +}; + + + +/** + * @brief Initialize the creator COSE_Recipient for ESDH content key distribution. + + * @param[in] ckd_id The content key distribution algorithm ID. + * @param[in] curve_id The curve ID. + * + * This must be called not only to set the keywrap id, the content key distribution (ckd) id, and + * the curve id, but also because this sets up the recipient callbacks. That is when all the real + * work of content key distribution gets done. + * + * If unknown algorithm IDs are passed, an error will occur when t_cose_encrypt_enc() is + * called and the error code will be returned there. + */ +static void +t_cose_recipient_enc_esdh_init(struct t_cose_recipient_enc_esdh *context, + int16_t ckd_id, + int16_t curve_id); + + +/** + * @brief Sets the recipient key, pkR + * + * The kid is optional and can be NULL + */ +static void +t_cose_recipient_enc_esdh_set_key(struct t_cose_recipient_enc_esdh *context, + struct t_cose_key recipient, + struct q_useful_buf_c kid); + + +/** + * @brief Sets the info structure + */ +static inline void +t_cose_recipient_enc_esdh_set_info(struct t_cose_recipient_enc_esdh *me, + struct t_cose_info_t *info); + +/* ========================================================================= + BEGINNING OF PRIVATE INLINE IMPLEMENTATION + ========================================================================= */ + +enum t_cose_err_t +t_cose_recipient_create_esdh_cb_private(struct t_cose_recipient_enc *me_x, + struct q_useful_buf_c cek, + const struct t_cose_alg_and_bits ce_alg, + QCBOREncodeContext *cbor_encoder); + + +static inline void +t_cose_recipient_enc_esdh_init(struct t_cose_recipient_enc_esdh *me, + int16_t ckd_id, + int16_t curve_id + ) +{ + memset(me, 0, sizeof(*me)); + me->e.creat_cb = t_cose_recipient_create_esdh_cb_private; + me->esdh_suite.ckd_id = ckd_id; + me->esdh_suite.curve_id = curve_id; +} + +static inline void +t_cose_recipient_enc_esdh_set_info(struct t_cose_recipient_enc_esdh *me, + struct t_cose_info_t *info) +{ + me->info = info; +} + +static inline void +t_cose_recipient_enc_esdh_set_key(struct t_cose_recipient_enc_esdh *me, + struct t_cose_key pkR, + struct q_useful_buf_c kid) +{ + me->pkR = pkR; + me->kid = kid; +} + +#ifdef __cplusplus +} +#endif + +#endif /* __T_COSE_RECIPIENT_ENC_ESDH_H__ */ diff --git a/inc/t_cose/t_cose_standard_constants.h b/inc/t_cose/t_cose_standard_constants.h index 1bd4298b..3822c33b 100644 --- a/inc/t_cose/t_cose_standard_constants.h +++ b/inc/t_cose/t_cose_standard_constants.h @@ -145,6 +145,16 @@ */ #define T_COSE_HEADER_ALG_PARAM_HPKE_SENDER_INFO -4 + +/** + * \def T_COSE_HEADER_ALG_PARAM_EPHEMERAL_KEY + * + * \brief Label of COSE header algorithm parameter that indicates an ephemeral key. + * + */ +#define T_COSE_HEADER_ALG_PARAM_EPHEMERAL_KEY -1 + + /* ------------- COSE Algorithms ---------------------------- * https://www.iana.org/assignments/cose/cose.xhtml#algorithms */ @@ -284,6 +294,28 @@ */ #define T_COSE_ALGORITHM_SHA_512 -44 + +/** + * \def T_COSE_ALGORITHM_ECDH_ES_A256KW + * + * \brief ECDH ES w/ Concat KDF and AES Key Wrap w/ 256-bit key + */ +#define T_COSE_ALGORITHM_ECDH_ES_A256KW -31 + +/** + * \def T_COSE_ALGORITHM_ECDH_ES_A192KW + * + * \brief ECDH ES w/ Concat KDF and AES Key Wrap w/ 192-bit key + */ +#define T_COSE_ALGORITHM_ECDH_ES_A192KW -30 + +/** + * \def T_COSE_ALGORITHM_ECDH_ES_A128KW + * + * \brief ECDH ES w/ Concat KDF and AES Key Wrap w/ 128-bit key + */ +#define T_COSE_ALGORITHM_ECDH_ES_A128KW -29 + /** * \def COSE_ALGORITHM_A256KW * diff --git a/src/t_cose_crypto.h b/src/t_cose_crypto.h index 1e80f6d0..1c4b3bbb 100644 --- a/src/t_cose_crypto.h +++ b/src/t_cose_crypto.h @@ -131,6 +131,11 @@ extern "C" { (input_length) + 1) + \ T_COSE_CIPHER_IV_MAX_SIZE) +/** The maximum output size of the symmetric produced by a key agreement algorithm, in bytes. + * If we support an ECC curve with 521 bits, then the value below must be set to 66 bytes + * because ceil( 521 / 8 ) = 66 bytes. + */ +#define T_COSE_RAW_KEY_AGREEMENT_OUTPUT_MAX_SIZE 66 #define T_COSE_EC_P256_SIG_SIZE 64 /* size for secp256r1 */ #define T_COSE_EC_P384_SIG_SIZE 96 /* size for secp384r1 */ @@ -583,6 +588,10 @@ struct t_cose_crypto_hmac { /* TODO: should this vary with T_COSE_CRYPTO_MAX_HASH_SIZE? */ #define T_COSE_CRYPTO_HMAC_TAG_MAX_SIZE T_COSE_CRYPTO_SHA512_SIZE +/** + * Maximum size of the hash output + */ +#define T_COSE_CRYPTO_MAX_HASH_SIZE T_COSE_CRYPTO_SHA512_SIZE /** * Max size of an HMAC key. RFC 2160 which says the key should be the block size of the hash @@ -833,9 +842,28 @@ t_cose_crypto_get_random(struct q_useful_buf buffer, size_t number, struct q_useful_buf_c *random); -/* TBD: Generate key */ +/** + * \brief Requests generation of a public / private key. + * + * \param[in] key t_cose_key structure to hold the key pair + * + * \param[in] cose_algorithm_id algorithm identifier + * (e.g. for a NIST P256r1 curve) + * + * This function will either return a key in form of a t_cose_key + * structure, or produce an error. + * + * \retval T_COSE_SUCCESS + * Successfully generated a public/private key pair/ + * + * \retval T_COSE_ERR_UNSUPPORTED_KEM_ALG + * Unknown algorithm + * + * \retval T_COSE_ERR_KEY_GENERATION_FAILED + * Key generation failed + */ enum t_cose_err_t -t_cose_crypto_generate_key(struct t_cose_key *ephemeral_key, +t_cose_crypto_generate_key(struct t_cose_key *key, int32_t cose_algorithm_id); /** @@ -1144,6 +1172,27 @@ void t_cose_crypto_free_symmetric_key(struct t_cose_key key); +/** + * \brief Key Agreement + * + * Computes a shared secret based on the provided key agreement + * algorithm. Is used to generate a ECDHE-derived symmetric key. + * + * \param[in] cose_algorithm_id Algorithm id. + * \param[in] private_key Private key. + * \param[in] public_key Public key. + * \param[in,out] symmetric_key Buffer where to place the derived symmetric key. + * \param[in,out] symmetric_key_len Length of the derived key. + * + * \return Error code. + */ +enum t_cose_err_t +t_cose_crypto_key_agreement(const int32_t cose_algorithm_id, + struct t_cose_key private_key, + struct t_cose_key public_key, + struct q_useful_buf symmetric_key, + size_t *symmetric_key_len + ); /** * \brief Elliptic curve diffie-helman. diff --git a/src/t_cose_encrypt_enc.c b/src/t_cose_encrypt_enc.c index 04487b6a..6e889da6 100644 --- a/src/t_cose_encrypt_enc.c +++ b/src/t_cose_encrypt_enc.c @@ -52,6 +52,11 @@ t_cose_encrypt_enc_detached(struct t_cose_encrypt_enc *me, struct q_useful_buf_c encrypt_output; bool is_cose_encrypt0; struct t_cose_recipient_enc *recipient; +#ifndef T_COSE_DISABLE_ESDH + struct t_cose_crypto_hash hash_ctx; + Q_USEFUL_BUF_MAKE_STACK_UB( hash_buffer, T_COSE_CRYPTO_MAX_HASH_SIZE); + struct q_useful_buf_c hash; +#endif /* T_COSE_DISABLE_ESDH */ /* ---- Figure out the COSE message type ---- */ @@ -190,7 +195,6 @@ t_cose_encrypt_enc_detached(struct t_cose_encrypt_enc *me, *encrypted_detached = encrypt_output; } - /* ---- COSE_Recipients for COSE_Encrypt message ---- */ if ( !is_cose_encrypt0 ) { for(recipient = me->recipients_list; diff --git a/src/t_cose_recipient_dec_esdh.c b/src/t_cose_recipient_dec_esdh.c new file mode 100644 index 00000000..dd17c4c4 --- /dev/null +++ b/src/t_cose_recipient_dec_esdh.c @@ -0,0 +1,183 @@ +/* + * t_cose_recipient_dec_esdh.c + * + * Copyright (c) 2022, Arm Limited. All rights reserved. + * Copyright (c) 2023, Laurence Lundblade. All rights reserved. + + * SPDX-License-Identifier: BSD-3-Clause + * + * See BSD-3-Clause license in README.md + * + */ + +#ifndef T_COSE_DISABLE_ESDH + +#include +#include "qcbor/qcbor_spiffy_decode.h" +#include "t_cose/t_cose_recipient_dec_esdh.h" /* Interface implemented */ +#include "t_cose/t_cose_encrypt_enc.h" +#include "t_cose/t_cose_common.h" +#include "t_cose/q_useful_buf.h" +#include "t_cose/t_cose_standard_constants.h" +#include "t_cose_crypto.h" +#include "t_cose_util.h" + + +// TODO: maybe rearrange this to align with what happens in crypto adaptor layer +struct esdh_sender_info { + uint64_t kem_id; + uint64_t kdf_id; + uint64_t aead_id; + struct q_useful_buf_c enc; +}; + +static enum t_cose_err_t +esdh_sender_info_decode_cb(void *cb_context, + QCBORDecodeContext *cbor_decoder, + struct t_cose_parameter *parameter) +{ + if(parameter->label != T_COSE_HEADER_ALG_PARAM_HPKE_SENDER_INFO) { + return 0; + } + // TODO: this will have to cascade to an external supplied + // special header decoder too + struct esdh_sender_info *sender_info = (struct esdh_sender_info *)cb_context; + + QCBORDecode_EnterArray(cbor_decoder, NULL); + QCBORDecode_GetUInt64(cbor_decoder, &(sender_info->kem_id)); + QCBORDecode_GetUInt64(cbor_decoder, &(sender_info->kdf_id)); + QCBORDecode_GetUInt64(cbor_decoder, &(sender_info->aead_id)); + QCBORDecode_GetByteString(cbor_decoder, &(sender_info->enc)); + QCBORDecode_ExitArray(cbor_decoder); + if(QCBORDecode_GetError(cbor_decoder)) { + sender_info->kem_id = UINT64_MAX; /* This indicates failure */ + } + + // TODO: more error handling + return 0; +} + + +/* This is an implementation of t_cose_recipient_dec_cb */ +enum t_cose_err_t +t_cose_recipient_dec_esdh_cb_private(struct t_cose_recipient_dec *me_x, + const struct t_cose_header_location loc, + const struct t_cose_alg_and_bits ce_alg, + QCBORDecodeContext *cbor_decoder, + struct q_useful_buf cek_buffer, + struct t_cose_parameter_storage *p_storage, + struct t_cose_parameter **params, + struct q_useful_buf_c *cek) +{ + struct t_cose_recipient_dec_esdh *me; + QCBORError result; + int64_t alg = 0; + struct q_useful_buf_c cek_encrypted; + struct q_useful_buf_c protected_params; + enum t_cose_err_t cose_result; + struct esdh_sender_info sender_info; + int psa_ret; + MakeUsefulBufOnStack(enc_struct_buf, 50); // TODO: allow this to be + // supplied externally + struct q_useful_buf_c enc_struct; + + me = (struct t_cose_recipient_dec_esdh *)me_x; + + /* One recipient */ + QCBORDecode_EnterArray(cbor_decoder, NULL); + + cose_result = t_cose_headers_decode(cbor_decoder, /* in: decoder to read from */ + loc, /* in: location in COSE message*/ + esdh_sender_info_decode_cb, /* in: callback for specials */ + &sender_info, /* in: context for callback */ + p_storage, /* in: parameter storage */ + params, /* out: list of decoded params */ + &protected_params /* out: encoded prot params */ + ); + if(cose_result != T_COSE_SUCCESS) { + goto Done; + } + + /* Recipient array contains AES Key Wrap algorithm. + * The KEK used to encrypt the CEK with AES-KW is then + * found in an inner recipient array. + */ + alg = t_cose_find_parameter_alg_id(*params, false); + if (alg != T_COSE_ALGORITHM_A128KW && + alg != T_COSE_ALGORITHM_A192KW && + alg != T_COSE_ALGORITHM_A256KW) + return T_COSE_ERR_UNSUPPORTED_CONTENT_KEY_DISTRIBUTION_ALG; + + // TODO: put kid processing back in + + /* get CEK */ + QCBORDecode_GetByteString(cbor_decoder, &cek_encrypted); + + + /* TBD: Here we need to look at the inner recipient structure */ + + /* Close out decoding and error check */ + QCBORDecode_ExitArray(cbor_decoder); + result = QCBORDecode_GetError(cbor_decoder); + if (result != QCBOR_SUCCESS) { + return(T_COSE_ERR_CBOR_MANDATORY_FIELD_MISSING); + } + + /* --- Make the Enc_structure ---- */ + cose_result = create_enc_structure("Enc_Recipient", /* in: context string */ + protected_params, + NULL_Q_USEFUL_BUF_C, /* in: Externally supplied AAD */ + enc_struct_buf, + &enc_struct); + if(cose_result != T_COSE_SUCCESS) { + goto Done; + } + + /* + // TODO: There is a big rearrangement necessary when the crypto adaptation + // layer calls for ESDH are sorted out. Lots of work to complete that... + esdh_suite_t suite; + size_t cek_len_in_out; + + // TODO: check that the sender_info decode happened correctly + // before proceeding + suite.aead_id = (uint16_t)sender_info.aead_id; + suite.kdf_id = (uint16_t)sender_info.kdf_id; + suite.kem_id = (uint16_t)sender_info.kem_id; + + cek_len_in_out = cek_buffer.len; + + psa_ret = mbedtls_esdh_decrypt( + ESDH_MODE_BASE, // ESDH mode + suite, // ciphersuite + NULL, 0, NULL, // PSK for authentication + 0, NULL, // pkS + (psa_key_handle_t)me->skr.key.handle, // skR handle + sender_info.enc.len, // pkE_len + sender_info.enc.ptr, // pkE + cek_encrypted.len, // Ciphertext length + cek_encrypted.ptr, // Ciphertext + // TODO: fix the const-ness all the way down so the cast can be removed + enc_struct.len, (uint8_t *)(uintptr_t)enc_struct.ptr, // AAD + 0, NULL, // Info + &cek_len_in_out, // Plaintext length + cek_buffer.ptr // Plaintext + ); + + if (psa_ret != 0) { + return(T_COSE_ERR_ESDH_DECRYPT_FAIL); + } + + cek->ptr = cek_buffer.ptr; + cek->len = cek_len_in_out; +*/ +Done: + return(cose_result); +} + +#else /* T_COSE_DISABLE_ESDH */ + +/* Place holder for compiler tools that don't like files with no functions */ +void t_cose_recipient_dec_esdh_placeholder(void) {} + +#endif /* T_COSE_DISABLE_ESDH */ diff --git a/src/t_cose_recipient_enc_esdh.c b/src/t_cose_recipient_enc_esdh.c new file mode 100644 index 00000000..c028022f --- /dev/null +++ b/src/t_cose_recipient_enc_esdh.c @@ -0,0 +1,354 @@ +/** + * \file t_cose_recipient_enc_esdh.c + * + * Copyright (c) 2022, Arm Limited. All rights reserved. + * Copyright (c) 2023, Laurence Lundblade. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * See BSD-3-Clause license in README.md + * + */ + +#ifndef T_COSE_DISABLE_ESDH + +#include +#include "qcbor/qcbor_encode.h" +#include "t_cose/t_cose_recipient_enc.h" +#include "t_cose/t_cose_recipient_enc_esdh.h" /* Interface implemented */ +#include "t_cose/t_cose_encrypt_enc.h" +#include "t_cose/t_cose_common.h" +#include "t_cose/q_useful_buf.h" +#include "t_cose/t_cose_standard_constants.h" +#include "t_cose_crypto.h" +#include "t_cose_util.h" +#include "t_cose/t_cose_recipient_enc_keywrap.h" + +/* + * See documentation in t_cose_recipient_enc_esdh.h + */ +enum t_cose_err_t +t_cose_recipient_create_esdh_cb_private(struct t_cose_recipient_enc *me_x, + struct q_useful_buf_c cek, + const struct t_cose_alg_and_bits ce_alg, + QCBOREncodeContext *cbor_encoder) +{ + struct q_useful_buf_c protected_params; + uint8_t encrypted_cek[T_COSE_CIPHER_ENCRYPT_OUTPUT_MAX_SIZE(T_COSE_MAX_SYMMETRIC_KEY_LENGTH)]; + size_t encrypted_cek_len = T_COSE_CIPHER_ENCRYPT_OUTPUT_MAX_SIZE(T_COSE_MAX_SYMMETRIC_KEY_LENGTH); + uint8_t kek[T_COSE_CIPHER_ENCRYPT_OUTPUT_MAX_SIZE(T_COSE_MAX_SYMMETRIC_KEY_LENGTH)]; + size_t kek_len = T_COSE_CIPHER_ENCRYPT_OUTPUT_MAX_SIZE(T_COSE_MAX_SYMMETRIC_KEY_LENGTH); + struct q_useful_buf_c cek_encrypted_cbor; + size_t pkR_len = T_COSE_EXPORT_PUBLIC_KEY_MAX_SIZE; + uint8_t pkR[T_COSE_EXPORT_PUBLIC_KEY_MAX_SIZE] = {0}; + size_t pkE_len = T_COSE_EXPORT_PUBLIC_KEY_MAX_SIZE; + uint8_t pkE[T_COSE_EXPORT_PUBLIC_KEY_MAX_SIZE] = {0}; + enum t_cose_err_t return_value; + struct t_cose_key ephemeral_key; + MakeUsefulBufOnStack( info_struct_buf, 200); // TODO: allow this to be + // supplied externally + MakeUsefulBufOnStack( protected_hdr_buffer, 50); + QCBOREncodeContext protected_hdr_ctx; + struct q_useful_buf_c protected_hdr; + UsefulBufC protected_hdrC; + + struct q_useful_buf_c info_struct; + QCBOREncodeContext ephemeral_key_struct; + MakeUsefulBufOnStack( ephemeral_buf, 150); // TODO: Avoid use of constant + QCBORError ret = QCBOR_SUCCESS; + size_t output_kek_len; + UsefulBufC scratch; + struct t_cose_parameter params[2]; + struct q_useful_buf encrypted_cek_destination; + struct q_useful_buf_c encrypted_cek_result; + struct q_useful_buf_c protected_params_not; + struct t_cose_key kek_handle; + size_t target_kek_len; + int32_t hash_alg; + int32_t kw_alg; + size_t ecdhe_derived_key_len; + struct t_cose_recipient_enc_esdh *context; + Q_USEFUL_BUF_MAKE_STACK_UB(derived_key, T_COSE_RAW_KEY_AGREEMENT_OUTPUT_MAX_SIZE ); + + context = (struct t_cose_recipient_enc_esdh *)me_x; + + switch (context->esdh_suite.ckd_id) + { + case T_COSE_ALGORITHM_ECDH_ES_A128KW: + target_kek_len = 16; + break; + case T_COSE_ALGORITHM_ECDH_ES_A192KW: + target_kek_len = 24; + break; + case T_COSE_ALGORITHM_ECDH_ES_A256KW: + target_kek_len = 32; + break; + default: + return(T_COSE_ERR_UNSUPPORTED_ENCRYPTION_ALG); + } + + /* Create ephemeral key */ + return_value = t_cose_crypto_generate_key(&ephemeral_key, + context->esdh_suite.curve_id); + if (return_value != T_COSE_SUCCESS) { + return(return_value); + } + + /* Export pkR */ + return_value = t_cose_crypto_export_public_key( + context->pkR, + (struct q_useful_buf) {.ptr=pkR, .len=pkR_len}, + &pkR_len); + + if (return_value != T_COSE_SUCCESS) { + return(return_value); + } + + /* Export pkE */ + return_value = t_cose_crypto_export_public_key( + ephemeral_key, + (struct q_useful_buf) {.ptr=pkE, .len=pkE_len}, + &pkE_len); + + if (return_value != T_COSE_SUCCESS) { + return(return_value); + } + + /* Create COSE_recipient array */ +// QCBOREncode_OpenArray(cbor_encoder); + + /* --- Make Info structure ---- */ + + /* Create structure for protected header */ + QCBOREncode_Init(&protected_hdr_ctx, protected_hdr_buffer); + QCBOREncode_BstrWrap(&protected_hdr_ctx); + QCBOREncode_OpenMap(&protected_hdr_ctx); + + QCBOREncode_AddInt64ToMapN(&protected_hdr_ctx, + T_COSE_HEADER_PARAM_ALG, + context->esdh_suite.ckd_id); + + QCBOREncode_CloseMap(&protected_hdr_ctx); + + QCBOREncode_CloseBstrWrap2(&protected_hdr_ctx, + false, + &protected_hdr); + + ret = QCBOREncode_Finish(&protected_hdr_ctx, &protected_hdrC); + + if (ret != QCBOR_SUCCESS) { + return(T_COSE_ERR_CBOR_NOT_WELL_FORMED); + } + + return_value = create_info_structure(context->info->enc_alg, + context->info->sender_identity_type_id, + context->info->sender_identity, + context->info->recipient_identity_type_id, + context->info->recipient_identity, + protected_hdr, + context->info->enc_ctx->hash_cose_algorithm_id, + (struct q_useful_buf_c) + {.ptr = context->info->enc_ctx->extern_hash_buffer.ptr, + .len = context->info->enc_ctx->extern_hash_buffer.len + }, + info_struct_buf, + &info_struct); + + if (return_value != T_COSE_SUCCESS) { + return(return_value); + } + + /* --- Generation of ECDHE derived key with HKDF applied afterwards ---- */ + return_value = t_cose_crypto_key_agreement( + context->esdh_suite.ckd_id, // Content Key Distribution Method + ephemeral_key, // Private Key + context->pkR, // Public Key + derived_key, // Derived Key + &ecdhe_derived_key_len); + + if(return_value) { + return T_COSE_ERR_KEY_AGREEMENT_FAIL; + } + + /* Determine hash algorithm for HKDF and the length of the + * KEK to be produced. The length of the KEK aligns with + * the AES KW algorithm selected. + * + * All three ES-DH content key distribution algorithms use SHA256 + * with the HKDF-based key derivation function. + */ + + switch(context->esdh_suite.ckd_id) { + case T_COSE_ALGORITHM_ECDH_ES_A128KW: + output_kek_len = 128/8; + hash_alg = T_COSE_ALGORITHM_SHA_256; + break; + case T_COSE_ALGORITHM_ECDH_ES_A192KW: + output_kek_len = 192/8; + hash_alg = T_COSE_ALGORITHM_SHA_256; + break; + case T_COSE_ALGORITHM_ECDH_ES_A256KW: + output_kek_len = 256/8; + hash_alg = T_COSE_ALGORITHM_SHA_256; + break; + default: + return T_COSE_ERR_UNSUPPORTED_CONTENT_KEY_DISTRIBUTION_ALG; + } + + /* HKDF-based Key Derivation */ + return_value = t_cose_crypto_hkdf(hash_alg, // Hash Algorithm + NULL_Q_USEFUL_BUF_C, // Empty Salt + (struct q_useful_buf_c) // IKM + { + derived_key.ptr, + ecdhe_derived_key_len + }, + info_struct, // Info Context Structure + (struct q_useful_buf) // OKM + { + kek, + output_kek_len + }); + if(return_value) { + return T_COSE_ERR_HKDF_FAIL; + } + + /* Free ephemeral key (which is not a symmetric key!) */ + /* TBD: Rename the function to t_cose_crypto_free_key() */ + t_cose_crypto_free_symmetric_key(ephemeral_key); + + if (return_value != T_COSE_SUCCESS) { + return(return_value); + } + + switch(context->esdh_suite.ckd_id) + { + case T_COSE_ALGORITHM_ECDH_ES_A256KW: + return_value = t_cose_crypto_make_symmetric_key_handle(T_COSE_ALGORITHM_A256GCM, + (struct q_useful_buf_c) {.ptr = kek, .len = output_kek_len}, + &kek_handle); + kw_alg = T_COSE_ALGORITHM_A256KW; + break; + case T_COSE_ALGORITHM_ECDH_ES_A192KW: + return_value = t_cose_crypto_make_symmetric_key_handle(T_COSE_ALGORITHM_A192GCM, + (struct q_useful_buf_c) {.ptr = kek, .len = output_kek_len}, + &kek_handle); + kw_alg = T_COSE_ALGORITHM_A192KW; + break; + case T_COSE_ALGORITHM_ECDH_ES_A128KW: + return_value = t_cose_crypto_make_symmetric_key_handle(T_COSE_ALGORITHM_A128GCM, + (struct q_useful_buf_c) {.ptr = kek, .len = output_kek_len}, + &kek_handle); + kw_alg = T_COSE_ALGORITHM_A128KW; + break; + default: + return(T_COSE_ERR_UNSUPPORTED_CONTENT_KEY_DISTRIBUTION_ALG); + } + + if (return_value != T_COSE_SUCCESS) { + return(return_value); + } + + /* Add protected header */ + QCBOREncode_BstrWrap(cbor_encoder); + QCBOREncode_OpenMap(cbor_encoder); + + QCBOREncode_AddInt64ToMapN(cbor_encoder, + T_COSE_HEADER_PARAM_ALG, + context->esdh_suite.ckd_id); + + QCBOREncode_CloseMap(cbor_encoder); + + QCBOREncode_CloseBstrWrap2(cbor_encoder, + false, + &protected_params); + + /* Add unprotected Header */ + QCBOREncode_OpenMap(cbor_encoder); + + /* Create ephemeral COSE_Key structure + * + * / ephemeral / -1:{ + * / kty / 1: int (2), + * / crv / -1: int / tstr, + * / x / -2: bstr, + * / y / -3: bstr / bool + * } + */ + + /* Create ephemeral key parameter map */ + QCBOREncode_OpenMapInMapN(cbor_encoder, T_COSE_HEADER_ALG_PARAM_EPHEMERAL_KEY); + + /* -- add kty paramter */ + QCBOREncode_AddInt64ToMapN(cbor_encoder, + T_COSE_KEY_COMMON_KTY, + T_COSE_KEY_TYPE_EC2); + + /* -- add crv parameter */ + QCBOREncode_AddInt64ToMapN(cbor_encoder, + T_COSE_KEY_PARAM_CRV, + context->esdh_suite.curve_id); + + /* x_len is calculated as ( pkE_len - 1) / 2 */ + + /* -- add x parameter */ + QCBOREncode_AddBytesToMapN(cbor_encoder, + T_COSE_KEY_PARAM_X_COORDINATE, + (struct q_useful_buf_c) + { + pkE + 1, + (pkE_len - 1) / 2 + } + ); + + /* -- add y parameter */ + QCBOREncode_AddBytesToMapN(cbor_encoder, + T_COSE_KEY_PARAM_Y_COORDINATE, + (struct q_useful_buf_c) + { + &pkE[(pkE_len - 1) / 2 + 1], + (pkE_len - 1) / 2 + } + ); + + /* Close ephemeral parameter map */ + QCBOREncode_CloseMap(cbor_encoder); + + /* Add kid to unprotected map */ + QCBOREncode_AddBytesToMapN(cbor_encoder, + T_COSE_HEADER_PARAM_KID, + context->kid); + + /* Close unprotected map */ + QCBOREncode_CloseMap(cbor_encoder); + + /* Do the keywrap directly into the output buffer + * t_cose_crypto_kw_wrap() will catch incorrect algorithm ID errors + */ + QCBOREncode_OpenBytes(cbor_encoder, &encrypted_cek_destination); + return_value = t_cose_crypto_kw_wrap(kw_alg, // key wrap algorithm + kek_handle, // key encryption key + cek, // "plaintext" = cek + encrypted_cek_destination, + &encrypted_cek_result); + if (return_value != T_COSE_SUCCESS) { + return(return_value); + } + QCBOREncode_CloseBytes(cbor_encoder, encrypted_cek_result.len); + + /* Close recipient array */ +// QCBOREncode_CloseArray(cbor_encoder); + + /* Free KEK */ + t_cose_crypto_free_symmetric_key(kek_handle); + + return(T_COSE_SUCCESS); +} + + +#else + +/* Place holder for compiler tools that don't like files with no functions */ +void t_cose_recipient_enc_esdh_placeholder(void) {} + +#endif /* T_COSE_DISABLE_ESDH */ diff --git a/src/t_cose_util.c b/src/t_cose_util.c index ba70810f..96d297c4 100644 --- a/src/t_cose_util.c +++ b/src/t_cose_util.c @@ -218,6 +218,27 @@ create_tbs(const struct t_cose_sign_inputs *sign_inputs, } } +/** + * \brief Returns the key length (in bits) of a given encryption algo. + * + * @param cose_algorithm_id Crypto algorithm. + * + * Returns the key length (in bits) or 0 in case of an + * unknown algorithm id. + */ +static unsigned int +bits_in_content_encryption_alg(int32_t cose_algorithm_id) +{ + switch(cose_algorithm_id) { + case T_COSE_ALGORITHM_AES128CCM_16_128: + case T_COSE_ALGORITHM_A128GCM: return 128; + case T_COSE_ALGORITHM_A192GCM: return 192; + case T_COSE_ALGORITHM_AES256CCM_16_128: + case T_COSE_ALGORITHM_A256GCM: return 256; + } + return 0; +} + /** * \brief Hash an encoded bstr without actually encoding it in memory. @@ -390,6 +411,180 @@ create_enc_structure(const char *context_string, } +/* The context information structure is used to ensure that the + * derived keying material is "bound" to the context of the transaction. + * + * The structure described below is based on Section 5.2 of RFC 9053 + * with further details added in draft-ietf-suit-firmware-encryption. + * + * Note: The structure below is work in progress and likely to be changed. + * + * The following CDDL describes the content of the context information + * structure used for ES-DH. + * + * identity-kid = 1 + * identity-x5u = 2 + * identity-x5t = 3 + * identity-x5chain = 4 + * identity-x5bag = 5 + * + * identity-type-id /= identity-kid + * identity-type-id /= identity-x5u + * identity-type-id /= identity-x5t + * identity-type-id /= identity-x5chain + * identity-type-id /= identity-x5bag + * + * Identity = [ + * type : identity-type-id, + * // content of the parameter in the COSE structure of the indicated + * bytes : bstr, + * ] + * + * PartyInfoSender = ( + * identity : bstr "author" + * nonce : nil, + * other : bstr .cbor Identity + * ) + * + * PartyInfoRecipient = ( + * identity : bstr "recipient", + * nonce : nil, + * other : bstr .cbor Identity + * ) + * + * COSE_KDF_Context = [ + * // algorithm ID for payload encryption (e.g. AES-GCM-256) + * AlgorithmID : int, + * PartyUInfo : [ PartyInfoSender ], + * PartyVInfo : [ PartyInfoRecipient ], + * SuppPubInfo : [ + * // Length of the algorithm indicated in the AlgorithmID + * keyDataLength : uint, + * // serialized content of the recipients-inner.protected field + * // e.g. h'a1013818' from Figure 7 in draft-ietf-suit-firmware-encryption-11 + * protected : bstr .cbor recipients-inner.protected, + * other: bstr .size 0 + * ], + * // hash of the encrypted payload/firmware in form of a SUIT_Digest structure + * SuppPrivInfo : bstr .cbor SUIT_Digest + * ] + * + */ +enum t_cose_err_t +create_info_structure(int32_t enc_alg, + uint8_t sender_identity_type_id, + struct q_useful_buf_c sender_identity, + uint8_t recipient_identity_type_id, + struct q_useful_buf_c recipient_identity, + struct q_useful_buf_c protected_headers, + const int32_t hash_algorithm_id, + struct q_useful_buf_c hash_encrypted_payload, + struct q_useful_buf buffer_for_info, + struct q_useful_buf_c *info_structure) +{ + QCBOREncodeContext cbor_encoder; + QCBORError err; + struct q_useful_buf_c other; + + QCBOREncode_Init(&cbor_encoder, buffer_for_info); + QCBOREncode_OpenArray(&cbor_encoder); + + /* -----------AlgorithmID---------------*/ + + QCBOREncode_AddInt64(&cbor_encoder, enc_alg); + + /* -----------PartyInfoSender---------------*/ + + /* Add PartyUInfo - Sender Identity Array */ + QCBOREncode_OpenArray(&cbor_encoder); + /* identity: here a constant */ + QCBOREncode_AddSZString(&cbor_encoder, "author"); + /* nonce: nil because not used */ + QCBOREncode_AddText(&cbor_encoder, NULLUsefulBufC); + /* other: identity structure for the sender */ + QCBOREncode_BstrWrap(&cbor_encoder); + QCBOREncode_OpenArray(&cbor_encoder); + + /* Identity type */ + QCBOREncode_AddUInt64(&cbor_encoder, sender_identity_type_id); + + /* Identity of the sender */ + QCBOREncode_AddBytes(&cbor_encoder, sender_identity); + + QCBOREncode_CloseArray(&cbor_encoder); + + QCBOREncode_CloseBstrWrap2(&cbor_encoder, + false, + &other); + + QCBOREncode_CloseArray(&cbor_encoder); + + /* -----------PartyInfoRecipient---------------*/ + + /* Add PartyUInfo - Recipient Identity Array */ + QCBOREncode_OpenArray(&cbor_encoder); + /* identity: here a constant */ + QCBOREncode_AddSZString(&cbor_encoder, "recipient"); + /* nonce: nil because not used */ + QCBOREncode_AddText(&cbor_encoder, NULLUsefulBufC); + /* other: identity structure for the recipient */ + QCBOREncode_BstrWrap(&cbor_encoder); + QCBOREncode_OpenArray(&cbor_encoder); + + /* Identity type */ + QCBOREncode_AddUInt64(&cbor_encoder, recipient_identity_type_id); + + /* Identity of the recipient */ + QCBOREncode_AddBytes(&cbor_encoder, recipient_identity); + + QCBOREncode_CloseArray(&cbor_encoder); + + QCBOREncode_CloseBstrWrap2(&cbor_encoder, + false, + &other); + + QCBOREncode_CloseArray(&cbor_encoder); + + /* -----------SuppPubInfo---------------*/ + + QCBOREncode_OpenArray(&cbor_encoder); + + /* keyDataLength */ + QCBOREncode_AddUInt64(&cbor_encoder, bits_in_content_encryption_alg(enc_alg)); + + /* recipients-inner.protected header */ + QCBOREncode_AddBytes(&cbor_encoder, protected_headers); + + /* other */ + QCBOREncode_AddText(&cbor_encoder, NULLUsefulBufC); + + QCBOREncode_CloseArray(&cbor_encoder); + + /* -----------SuppPrivInfo----------- */ + QCBOREncode_BstrWrap(&cbor_encoder); + QCBOREncode_OpenArray(&cbor_encoder); + + /* hash of the encrypted payload/firmware in form of a SUIT_Digest structure */ + /* Likely to be removed in the future */ +// QCBOREncode_AddInt64(&cbor_encoder, hash_algorithm_id); + +// QCBOREncode_AddBytes(&cbor_encoder, hash_encrypted_payload); + + QCBOREncode_CloseArray(&cbor_encoder); + + QCBOREncode_CloseBstrWrap2(&cbor_encoder, + false, + &other); + + QCBOREncode_CloseArray(&cbor_encoder); + + err = QCBOREncode_Finish(&cbor_encoder, info_structure); + if(err) { + return T_COSE_ERR_FAIL; // TODO: improve error mapping + } + return T_COSE_SUCCESS; +} + #ifndef T_COSE_DISABLE_SHORT_CIRCUIT_SIGN /* This is a random hard coded kid (key ID) that is used to indicate * short-circuit signing. It is OK to hard code this as the diff --git a/src/t_cose_util.h b/src/t_cose_util.h index 2b1ccfdd..cd4fd756 100644 --- a/src/t_cose_util.h +++ b/src/t_cose_util.h @@ -209,6 +209,21 @@ create_enc_structure(const char *context_string, struct q_useful_buf_c *enc_structure); +/* + * Create the info structure for ESDH-based content key distribution, + * as described draft-ietf-suit-firmware-encryption. + */ +enum t_cose_err_t +create_info_structure(int32_t enc_alg, + uint8_t sender_identity_type_id, + struct q_useful_buf_c sender_identity, + uint8_t recipient_identity_type_id, + struct q_useful_buf_c recipient_identity, + struct q_useful_buf_c protected_headers, + const int32_t hash_algorithm_id, + struct q_useful_buf_c hash_encrypted_payload, + struct q_useful_buf buffer_for_info, + struct q_useful_buf_c *info_structure); #ifndef T_COSE_DISABLE_SHORT_CIRCUIT_SIGN