diff --git a/README.md b/README.md index d1fa5b1c..36852b9b 100644 --- a/README.md +++ b/README.md @@ -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, , diff --git a/crypto_adapters/t_cose_openssl_crypto.c b/crypto_adapters/t_cose_openssl_crypto.c index e537ee7b..d45488dd 100644 --- a/crypto_adapters/t_cose_openssl_crypto.c +++ b/crypto_adapters/t_cose_openssl_crypto.c @@ -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. * @@ -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) { @@ -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; @@ -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; } @@ -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; } @@ -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; } @@ -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; } @@ -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; } diff --git a/crypto_adapters/t_cose_psa_crypto.c b/crypto_adapters/t_cose_psa_crypto.c index c9d0d81c..c961e3ae 100644 --- a/crypto_adapters/t_cose_psa_crypto.c +++ b/crypto_adapters/t_cose_psa_crypto.c @@ -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. * @@ -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, @@ -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) { @@ -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; @@ -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 */ diff --git a/test/run_tests.c b/test/run_tests.c index ef820da8..de8b6879 100644 --- a/test/run_tests.c +++ b/test/run_tests.c @@ -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), diff --git a/test/t_cose_sign_verify_test.c b/test/t_cose_sign_verify_test.c index 9f9096c8..dc45a560 100644 --- a/test/t_cose_sign_verify_test.c +++ b/test/t_cose_sign_verify_test.c @@ -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 */ diff --git a/test/t_cose_sign_verify_test.h b/test/t_cose_sign_verify_test.h index 77e31871..c9d6e6c1 100644 --- a/test/t_cose_sign_verify_test.h +++ b/test/t_cose_sign_verify_test.h @@ -1,7 +1,7 @@ /* * t_cose_sign_verify_test.h * - * Copyright 2019, 2022, Laurence Lundblade + * Copyright 2019, 2022, 2026, Laurence Lundblade * Copyright (c) 2023, Arm Limited. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause @@ -36,6 +36,10 @@ int32_t sign_verify_basic_test(void); int32_t sign_verify_sig_fail_test(void); +/* Make sure fully-specified alg ID error out on the wrong key/curve */ +int32_t +wrong_key_sign_test(void); + /* * Make a CWT and compare it to the one in the CWT RFC */