Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ and [COSE, RFC 9053](https://tools.ietf.org/html/rfc9053):
* COSE_Encrypt (multiple recipients) with ECDH + AES key wrap or just with AES key wrap
* AES CBC and CTR modes per [AES-CTR and AES-CBC, RFC 9459](https://tools.ietf.org/html/rfc9459)

[Fully-specified algorithm IDs, RFC 9864](https://tools.ietf.org/html/rfc9864) is also supported.

**Implemented in C with minimal dependency** – There are three main
dependencies: 1) [QCBOR](https://github.com/laurencelundblade/QCBOR),
2) A cryptographic library, 3) C99, <stdint.h>,
Expand Down
92 changes: 85 additions & 7 deletions crypto_adapters/t_cose_openssl_crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,46 @@ ecdsa_signature_cose_to_der(EVP_PKEY *key_evp,
}


static enum t_cose_err_t
check_ecc_key(const int32_t cose_algorithm_id,
EVP_PKEY *ec_key)
{
const EC_KEY *ec;
const EC_GROUP *group;
int nid ;

/* This method of getting the nid is slated for deprecation
* in OpenSSL because of new architecture in OpenSSL 3.x. However,
* it's unlikely this will be removed soon. The correct impementation
* would require an #ifdef on OpenSSL version. We haven't started
* doing that yet (in 2026). */
ec = EVP_PKEY_get0_EC_KEY(ec_key);
if(ec == NULL) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}
group = EC_KEY_get0_group(ec);
if(group == NULL) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}
nid = EC_GROUP_get_curve_name(group);

// TODO: generic mapping from nid to standard COSE EC2 curve ID
// TODO: fully-specify checking is not adaptor specific
switch(cose_algorithm_id) {
case T_COSE_ALGORITHM_ESP256:
if(nid != NID_X9_62_prime256v1) return T_COSE_ERR_WRONG_TYPE_OF_KEY;
break;
case T_COSE_ALGORITHM_ESP384:
if(nid != NID_secp384r1) return T_COSE_ERR_WRONG_TYPE_OF_KEY;
break;
case T_COSE_ALGORITHM_ESP512:
if(nid != NID_secp521r1) return T_COSE_ERR_WRONG_TYPE_OF_KEY;
break;
}

return T_COSE_SUCCESS;
}

/**
* \brief Common checks and conversions for signing and verification key.
*
Expand All @@ -521,9 +561,13 @@ ecdsa_signature_cose_to_der(EVP_PKEY *key_evp,
* It pulls the OpenSSL key out of \c t_cose_key and checks it.
*/
static enum t_cose_err_t
key_convert(struct t_cose_key t_cose_key, EVP_PKEY **return_ossl_ec_key)
key_convert(struct t_cose_key t_cose_key,
const int32_t cose_algorithm_id,
EVP_PKEY **return_ossl_ec_key)
{
enum t_cose_err_t return_value;
int key_len_bits;
int key_type;

/* Check the signing key and get it out of the union */
if(t_cose_key.key.ptr == NULL) {
Expand All @@ -532,7 +576,41 @@ key_convert(struct t_cose_key t_cose_key, EVP_PKEY **return_ossl_ec_key)
}
*return_ossl_ec_key = (EVP_PKEY *)t_cose_key.key.ptr;

return_value = T_COSE_SUCCESS;
/* For fully-specified algorithms, fully check the key */
key_len_bits = EVP_PKEY_bits(*return_ossl_ec_key);
key_type = EVP_PKEY_base_id(*return_ossl_ec_key);

switch(cose_algorithm_id) {
case T_COSE_ALGORITHM_ESP256:
if(key_type != EVP_PKEY_EC) {
return_value = T_COSE_ERR_WRONG_TYPE_OF_KEY;
goto Done;
}
return_value = check_ecc_key(cose_algorithm_id, *return_ossl_ec_key);
break;

case T_COSE_ALGORITHM_ESP384:
if(key_type != EVP_PKEY_EC) {
return_value = T_COSE_ERR_WRONG_TYPE_OF_KEY;
goto Done;
}
return_value = check_ecc_key(cose_algorithm_id, *return_ossl_ec_key);
break;

case T_COSE_ALGORITHM_ESP512:
if(key_type != EVP_PKEY_EC) {
return_value = T_COSE_ERR_WRONG_TYPE_OF_KEY;
goto Done;
}
return_value = check_ecc_key(cose_algorithm_id, *return_ossl_ec_key);
break;

default:
/* Assuming the PSA internals error out on an EC key
* used with the RSA alg and such. This just checks
* for fully-specified COSE algorithms */
return_value = T_COSE_SUCCESS;
}

Done:
return return_value;
Expand Down Expand Up @@ -589,7 +667,7 @@ enum t_cose_err_t t_cose_crypto_sig_size(int32_t cose_algorithm_id,
enum t_cose_err_t return_value;
EVP_PKEY *signing_key_evp;

return_value = key_convert(signing_key, &signing_key_evp);
return_value = key_convert(signing_key, cose_algorithm_id, &signing_key_evp);
if(return_value != T_COSE_SUCCESS) {
return return_value;
}
Expand Down Expand Up @@ -771,7 +849,7 @@ t_cose_crypto_sign(const int32_t cose_algorithm_id,

/* Pull the pointer to the OpenSSL-format EVP_PKEY out of the
* t_cose key structure. */
return_value = key_convert(signing_key, &signing_key_evp);
return_value = key_convert(signing_key, cose_algorithm_id, &signing_key_evp);
if(return_value != T_COSE_SUCCESS) {
goto Done2;
}
Expand Down Expand Up @@ -912,7 +990,7 @@ t_cose_crypto_verify(const int32_t cose_algorithm_id,

/* Get the verification key in an EVP_PKEY structure which is what
* is needed for sig verification. */
return_value = key_convert(verification_key, &verification_key_evp);
return_value = key_convert(verification_key, cose_algorithm_id, &verification_key_evp);
if(return_value != T_COSE_SUCCESS) {
goto Done;
}
Expand Down Expand Up @@ -1201,7 +1279,7 @@ t_cose_crypto_sign_eddsa(struct t_cose_key signing_key,

(void)crypto_context; /* This crypto adaptor doesn't use this */

return_value = key_convert(signing_key, &signing_key_evp);
return_value = key_convert(signing_key, T_COSE_ALGORITHM_EDDSA, &signing_key_evp);
if(return_value != T_COSE_SUCCESS) {
goto Done;
}
Expand Down Expand Up @@ -1264,7 +1342,7 @@ t_cose_crypto_verify_eddsa(struct t_cose_key verification_key,

(void)crypto_context; /* This crypto adaptor doesn't use this */

return_value = key_convert(verification_key, &verification_key_evp);
return_value = key_convert(verification_key, T_COSE_ALGORITHM_EDDSA, &verification_key_evp);
if(return_value != T_COSE_SUCCESS) {
goto Done;
}
Expand Down
92 changes: 92 additions & 0 deletions crypto_adapters/t_cose_psa_crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,79 @@ static psa_algorithm_t cose_alg_id_to_psa_alg_id(int32_t cose_alg_id)
}


/* Enforcement for fully-specified algorithm IDs */
static enum t_cose_err_t
check_key_against_algorithm(const int32_t cose_algorithm_id,
const psa_key_id_t signing_key_psa)
{
psa_status_t status;
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_key_type_t key_type;
psa_ecc_family_t curve_family;
size_t key_len_bits;

status = psa_get_key_attributes(signing_key_psa, &attributes);
if (status != PSA_SUCCESS) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}

key_type = psa_get_key_type(&attributes);

switch(cose_algorithm_id) {
case T_COSE_ALGORITHM_ESP256:
if(!PSA_KEY_TYPE_IS_ECC(key_type)) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}
curve_family = PSA_KEY_TYPE_ECC_GET_FAMILY(key_type);
if(curve_family != PSA_ECC_FAMILY_SECP_R1) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}

key_len_bits = psa_get_key_bits(&attributes);
if(key_len_bits != 256) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}
return T_COSE_SUCCESS;

case T_COSE_ALGORITHM_ESP384:
if(!PSA_KEY_TYPE_IS_ECC(key_type)) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}
curve_family = PSA_KEY_TYPE_ECC_GET_FAMILY(key_type);
if(curve_family != PSA_ECC_FAMILY_SECP_R1) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}

key_len_bits = psa_get_key_bits(&attributes);
if(key_len_bits != 384) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}
return T_COSE_SUCCESS;

case T_COSE_ALGORITHM_ESP512:
if(!PSA_KEY_TYPE_IS_ECC(key_type)) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}
curve_family = PSA_KEY_TYPE_ECC_GET_FAMILY(key_type);
if(curve_family != PSA_ECC_FAMILY_SECP_R1) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}

key_len_bits = psa_get_key_bits(&attributes);
if(key_len_bits != 521) {
return T_COSE_ERR_WRONG_TYPE_OF_KEY;
}
return T_COSE_SUCCESS;

default:
/* Assuming the PSA internals error out on an EC key
* used with the RSA alg and such. This just checks
* for fully-specified COSE algorithms */
return T_COSE_SUCCESS;
}
}


/**
* \brief Map a PSA error into a t_cose error for signing.
*
Expand Down Expand Up @@ -198,6 +271,11 @@ t_cose_crypto_verify(int32_t cose_algorithm_id,

verification_key_psa = (psa_key_handle_t)verification_key.key.handle;

return_value = check_key_against_algorithm(cose_algorithm_id, verification_key_psa);
if (return_value != T_COSE_SUCCESS) {
goto Done;
}

psa_result = psa_verify_hash(verification_key_psa,
psa_alg_id,
hash_to_verify.ptr,
Expand Down Expand Up @@ -239,6 +317,11 @@ t_cose_crypto_sign(int32_t cose_algorithm_id,

signing_key_psa = (psa_key_handle_t)signing_key.key.handle;

return_value = check_key_against_algorithm(cose_algorithm_id,signing_key_psa );
if (return_value != T_COSE_SUCCESS) {
goto Done;
}

/* Determine signature size and validate against buffer size */
return_value = t_cose_crypto_sig_size(cose_algorithm_id, signing_key, &signature_len);
if (return_value != T_COSE_SUCCESS) {
Expand Down Expand Up @@ -302,6 +385,11 @@ t_cose_crypto_sign_restart(const bool started,

signing_key_psa = (psa_key_handle_t)signing_key.key.handle;

return_value = check_key_against_algorithm(cose_algorithm_id,signing_key_psa );
if (return_value != T_COSE_SUCCESS) {
goto Done;
}

if(crypto_context == NULL) {
return_value = T_COSE_ERR_FAIL;
goto Done;
Expand Down Expand Up @@ -1554,6 +1642,10 @@ t_cose_crypto_ecdh(struct t_cose_key private_key,
MakeUsefulBufOnStack(public_key_buf, T_COSE_EXPORT_PUBLIC_KEY_MAX_SIZE);
size_t pub_key_len;

/* TODO: check key for fully specified algorithms. Some one needs
* to define fully specified ecdh algorithms first though. It's not
* in RFC 9864, which is unfortunate. */

/* Export public key */
psa_status = psa_export_public_key((mbedtls_svc_key_id_t)public_key.key.handle, /* in: Key handle */
public_key_buf.ptr, /* in: PK buffer */
Expand Down
1 change: 1 addition & 0 deletions test/run_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ static test_entry s_tests[] = {
TEST_ENTRY(sign_verify_basic_test),
TEST_ENTRY(sign_verify_make_cwt_test),
TEST_ENTRY(sign_verify_sig_fail_test),
TEST_ENTRY(wrong_key_sign_test),
TEST_ENTRY(sign_verify_get_size_test),
TEST_ENTRY(sign_verify_known_good_test),
TEST_ENTRY(sign_verify_unsupported_test),
Expand Down
82 changes: 82 additions & 0 deletions test/t_cose_sign_verify_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,88 @@ int32_t sign_verify_sig_fail_test(void)
}


/*
* Public function, see t_cose_sign_verify_test.h
*/
int32_t
wrong_key_sign_test(void)
{
int32_t return_value;
struct t_cose_sign1_sign_ctx sign_ctx;
enum t_cose_err_t result;
Q_USEFUL_BUF_MAKE_STACK_UB( signed_cose_buffer, 300);
struct q_useful_buf_c signed_cose;
struct t_cose_key key_pair;
struct q_useful_buf_c payload;
struct t_cose_sign1_verify_ctx verify_ctx;

if (!t_cose_is_algorithm_supported(T_COSE_ALGORITHM_ES256) ||
!t_cose_is_algorithm_supported(T_COSE_ALGORITHM_ES384) ||
!t_cose_is_algorithm_supported(T_COSE_ALGORITHM_ES512)) {
return INT32_MIN; /* Means no testing was actually done */
}

/* Make an ES 384 key pair */
result = init_fixed_test_signing_key(T_COSE_ALGORITHM_ES384, &key_pair);
if(result != T_COSE_SUCCESS) {
return 1000 + (int32_t)result;
}

/* -- Try to sign for ESP256 and fail -- */
t_cose_sign1_sign_init(&sign_ctx, 0, T_COSE_ALGORITHM_ESP256);
t_cose_sign1_set_signing_key(&sign_ctx, key_pair, NULL_Q_USEFUL_BUF_C);

result = t_cose_sign1_sign(&sign_ctx,
Q_USEFUL_BUF_FROM_SZ_LITERAL("payload"),
signed_cose_buffer,
&signed_cose);
if(result != T_COSE_ERR_WRONG_TYPE_OF_KEY) {
return_value = 2000 + (int32_t)result;
goto Done;
}

/* -- Make an ESP384 sig for the verify test -- */
t_cose_sign1_sign_init(&sign_ctx, 0, T_COSE_ALGORITHM_ESP384);
t_cose_sign1_set_signing_key(&sign_ctx, key_pair, NULL_Q_USEFUL_BUF_C);

result = t_cose_sign1_sign(&sign_ctx,
Q_USEFUL_BUF_FROM_SZ_LITERAL("payload"),
signed_cose_buffer,
&signed_cose);
if(result != T_COSE_SUCCESS) {
return_value = 3000 + (int32_t)result;
goto Done;
}

/* Switch to an ES512 key */
free_fixed_signing_key(key_pair);
result = init_fixed_test_signing_key(T_COSE_ALGORITHM_ES512, &key_pair);
if(result) {
return 4000 + (int32_t)result;
}

/* Try to verify an ESP384 sign with an ES521 key and fail */
t_cose_sign1_verify_init(&verify_ctx, 0);
t_cose_sign1_set_verification_key(&verify_ctx, key_pair);

result = t_cose_sign1_verify(&verify_ctx,
signed_cose, /* COSE to verify */
&payload, /* Payload from signed_cose */
NULL); /* Don't return parameters */
if(result != T_COSE_ERR_WRONG_TYPE_OF_KEY) {
return_value = 5000 + (int32_t)result;
goto Done;
}

return_value = 0;

Done:
free_fixed_signing_key(key_pair);

return return_value;
}


/*
* Public function, see t_cose_sign_verify_test.h
*/
Expand Down
Loading
Loading