diff --git a/crypto_adapters/t_cose_openssl_crypto.c b/crypto_adapters/t_cose_openssl_crypto.c index 1d11a596..7776791b 100644 --- a/crypto_adapters/t_cose_openssl_crypto.c +++ b/crypto_adapters/t_cose_openssl_crypto.c @@ -1368,7 +1368,14 @@ t_cose_crypto_make_symmetric_key_handle(int32_t cose_algorithm_id, T_COSE_ALGORITHM_A256KW, T_COSE_ALGORITHM_HMAC256, T_COSE_ALGORITHM_HMAC384, - T_COSE_ALGORITHM_HMAC512}; + T_COSE_ALGORITHM_HMAC512, + T_COSE_ALGORITHM_A128CTR, + T_COSE_ALGORITHM_A192CTR, + T_COSE_ALGORITHM_A256CTR, + T_COSE_ALGORITHM_A128CBC, + T_COSE_ALGORITHM_A192CBC, + T_COSE_ALGORITHM_A256CBC, + }; if(!t_cose_check_list(cose_algorithm_id, symmetric_algs)) { /* This check could be disabled when usage guards are disabled */ @@ -1520,6 +1527,239 @@ aead_byte_count(const int32_t cose_algorithm_id, } } +/* Compute size of ciphertext, given size of plaintext. Returns + * SIZE_MAX if the algorithm is unknown. Also returns the padding + * length. */ +static size_t +non_aead_byte_count(const int32_t cose_algorithm_id, + size_t plain_text_len, + size_t *padding_length) +{ + /* This works for CTR (counter) and CBC, non AEAD algorithms, + * but can be augmented for others. + * + * For both CTR and CBC as used by COSE and HPKE, the authentication tag is not + * appended. + * + * For CTR the ciphertext length is the same as the plaintext length. + * (This is not true of other ciphers). + * https://crypto.stackexchange.com/questions/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode + * + * For CBC the plaintext will be padded from 1 bytes to the block size. + * The block size is 16 bytes for all A128CBC, A192CBC and A256CBC. + * If the plaintext size is 30 bytes, 2 byte-padding will be inserted, + * and each padding byte has value 0x02. + */ + + switch(cose_algorithm_id) { + case T_COSE_ALGORITHM_A128CTR: + case T_COSE_ALGORITHM_A192CTR: + case T_COSE_ALGORITHM_A256CTR: + *padding_length = 0; + break; + case T_COSE_ALGORITHM_A128CBC: + case T_COSE_ALGORITHM_A192CBC: + case T_COSE_ALGORITHM_A256CBC: + *padding_length = 16 - plain_text_len % 16; + break; + } + + if(plain_text_len > (SIZE_MAX - *padding_length)) { + /* The extremely rare case where plain_text_len + * is almost SIZE_MAX in length and the length + * additions below will fail. This error is not + * the right one, but the case is so rare that + * it's not worth the trouble of making up some + * other error. This check is here primarily + * for static analyzers. */ + return SIZE_MAX; + } + + switch(cose_algorithm_id) { + case T_COSE_ALGORITHM_A128CTR: + case T_COSE_ALGORITHM_A192CTR: + case T_COSE_ALGORITHM_A256CTR: + case T_COSE_ALGORITHM_A128CBC: + case T_COSE_ALGORITHM_A192CBC: + case T_COSE_ALGORITHM_A256CBC: + return plain_text_len + *padding_length; + default: return SIZE_MAX;; + } +} + +/* + * See documentation in t_cose_crypto.h + */ +enum t_cose_err_t +t_cose_crypto_non_aead_encrypt(const int32_t cose_algorithm_id, + struct t_cose_key key, + struct q_useful_buf_c nonce, + struct q_useful_buf_c plaintext, + struct q_useful_buf ciphertext_buffer, + struct q_useful_buf_c *ciphertext) +{ + EVP_CIPHER_CTX *evp_context; + int ossl_result; + const EVP_CIPHER *evp_cipher; + int expected_key_length; + size_t expected_iv_length; + int buffer_bytes_used; + int bytes_output; + size_t expected_output_length; + size_t padding_length; + enum t_cose_err_t return_value; + + /* ------- Plaintext and ciphertext lengths -------*/ + /* + * This is the critical length check that makes the rest of the + * calls to OpenSSL that write to the output buffer safe because + * OpenSSL by itself doesn't isn't safe. You cannot tell it the + * length of the buffer it is writing to. + * + * Here's the text from the openssl documentation: + * + * For most ciphers and modes, the amount of data written can + * be anything from zero bytes to (inl + cipher_block_size - 1) + * bytes. For wrap cipher modes, the amount of data written can + * be anything from zero bytes to (inl + cipher_block_size) + * bytes. For stream ciphers, the amount of data written can be + * anything from zero bytes to inl bytes. + */ + + /* output-length-check */ + /* This assumes that OpenSSL outputs exactly the number + * of bytes this call calculates. + * Note that the OpenSSL automatically add the padding for AES-CBC + * defined in RFC9459 (or so called PKCS7 padding) by default. + */ + expected_output_length = non_aead_byte_count(cose_algorithm_id, + plaintext.len, + &padding_length); + if(expected_output_length == SIZE_MAX) { + return_value = T_COSE_ERR_UNSUPPORTED_CIPHER_ALG; + goto Done3; + } + if(ciphertext_buffer.len < expected_output_length) { + /* Output buffer is too small */ + return_value = T_COSE_ERR_TOO_SMALL; + goto Done3; + } + /* Now it is established that the output buffer is big enough */ + if(ciphertext_buffer.ptr == NULL) { + /* Called in length calculation mode. Return length & exit. */ + ciphertext->len = expected_output_length; + return_value = T_COSE_SUCCESS; + goto Done3; + } + + /* ------- Algorithm and key and IV length checks -------*/ + switch(cose_algorithm_id) { + case T_COSE_ALGORITHM_A128CTR: evp_cipher = EVP_aes_128_ctr();break; + case T_COSE_ALGORITHM_A192CTR: evp_cipher = EVP_aes_192_ctr();break; + case T_COSE_ALGORITHM_A256CTR: evp_cipher = EVP_aes_256_ctr();break; + case T_COSE_ALGORITHM_A128CBC: evp_cipher = EVP_aes_128_cbc();break; + case T_COSE_ALGORITHM_A192CBC: evp_cipher = EVP_aes_192_cbc();break; + case T_COSE_ALGORITHM_A256CBC: evp_cipher = EVP_aes_256_cbc();break; + default: return_value = T_COSE_ERR_UNSUPPORTED_CIPHER_ALG; goto Done3; + } + /* This is a sanity check. OpenSSL doesn't provide this check when + * using a key. It just assume you've provided the right key + * length to EVP_EncryptInit(). A bit unhygenic if you ask me. + * Assuming that EVP_CIPHER_key_length() will always return a + * small positive integer so the cast to size_t is safe. */ + expected_key_length = EVP_CIPHER_key_length(evp_cipher); + if(key.key.buffer.len != (size_t)expected_key_length) { + return_value = T_COSE_ERR_WRONG_TYPE_OF_KEY; + goto Done2; + } + /* Same hygene check for IV/nonce length as for key */ + /* Assume that EVP_CIPHER_iv_length() won't ever return something + * dumb like -1. It would be a bug in OpenSSL or such if it did. + * This make the cast to size_t mostly safe. */ + expected_iv_length = (size_t)EVP_CIPHER_iv_length(evp_cipher); + if(nonce.len < expected_iv_length){ + return_value = T_COSE_ERR_ENCRYPT_FAIL; + goto Done2; + } + + /* -------- Context initialization with key and IV ---------- */ + evp_context = EVP_CIPHER_CTX_new(); + if(evp_context == NULL) { + return_value = T_COSE_ERR_ENCRYPT_FAIL; + goto Done2; + } + ossl_result = EVP_EncryptInit_ex(evp_context, + evp_cipher, + NULL, + key.key.buffer.ptr, + nonce.ptr); + if(ossl_result != 1) { + return_value = T_COSE_ERR_ENCRYPT_FAIL; + goto Done1; + } + + /* ---------- Since this is non AEAD cipher AAD is ignored ---------- */ + + /* ---------- Actual encryption of plaintext to cipher text ---------*/ + if(!is_size_t_to_int_cast_ok(expected_output_length)) { + /* This tells us that it is not safe to track the output + * of the encryption in the integer variables buffer_bytes_used + * and bytes_output. */ + return_value = T_COSE_ERR_ENCRYPT_FAIL; + goto Done1; + } + + buffer_bytes_used = 0; + /* This assumes a stream cipher and no need for handling blocks. + * The section above on lengths makes sure the buffer being written + * to is big enough and that the cast of plaintext.len is safe. + */ + if(!is_size_t_to_int_cast_ok(plaintext.len)) { + /* Cast to integer below would not be safe. */ + return_value = T_COSE_ERR_ENCRYPT_FAIL; + goto Done1; + } + ossl_result = EVP_EncryptUpdate(evp_context, + ciphertext_buffer.ptr, + &bytes_output, + plaintext.ptr, (int)plaintext.len); + if(ossl_result != 1) { + return_value = T_COSE_ERR_ENCRYPT_FAIL; + goto Done1; + } + + buffer_bytes_used += bytes_output; /* Safe becaue of output-length-check */ + + // TODO: Final or Final_ex? + ossl_result = EVP_EncryptFinal_ex(evp_context, + (uint8_t *)ciphertext_buffer.ptr + buffer_bytes_used, + &bytes_output); + if(ossl_result != 1) { + return_value = T_COSE_ERR_ENCRYPT_FAIL; + goto Done1; + } + buffer_bytes_used += bytes_output; /* Safe becaue of output-length-check */ + + /* ---------- Since this is non AEAD do nothing for the tag ----------- */ + + if(!is_int_to_size_t_cast_ok(buffer_bytes_used)) { + return_value = T_COSE_ERR_ENCRYPT_FAIL; + goto Done1; + } + ciphertext->len = (size_t)buffer_bytes_used; + ciphertext->ptr = ciphertext_buffer.ptr; + + return_value = T_COSE_SUCCESS; + +Done1: + /* https://stackoverflow.com/questions/26345175/correct-way-to-free-allocate-the-context-in-the-openssl */ + EVP_CIPHER_CTX_free(evp_context); +Done2: + /* It seems that EVP_aes_128_ctr(), ... returns a const, non-allocated + * EVP_CIPHER and thus doesn't have to be freed. */ +Done3: + return return_value; +} /* * See documentation in t_cose_crypto.h @@ -1737,6 +1977,138 @@ t_cose_crypto_aead_encrypt(const int32_t cose_algorithm_id, return return_value; } +/* + * See documentation in t_cose_crypto.h + */ +enum t_cose_err_t +t_cose_crypto_non_aead_decrypt(const int32_t cose_algorithm_id, + struct t_cose_key key, + struct q_useful_buf_c nonce, + struct q_useful_buf_c ciphertext, + struct q_useful_buf plaintext_buffer, + struct q_useful_buf_c *plaintext) +{ + EVP_CIPHER_CTX *evp_context; + int ossl_result; + const EVP_CIPHER *evp_cipher; + int expected_key_length; + size_t expected_iv_length; + int bytes_output; + int dummy_length; + enum t_cose_err_t return_value; + + /* ------- Identify the algorithm -------*/ + switch(cose_algorithm_id) { + case T_COSE_ALGORITHM_A128CTR: evp_cipher = EVP_aes_128_ctr ();break; + case T_COSE_ALGORITHM_A192CTR: evp_cipher = EVP_aes_192_ctr ();break; + case T_COSE_ALGORITHM_A256CTR: evp_cipher = EVP_aes_256_ctr ();break; + case T_COSE_ALGORITHM_A128CBC: evp_cipher = EVP_aes_128_cbc ();break; + case T_COSE_ALGORITHM_A192CBC: evp_cipher = EVP_aes_192_cbc ();break; + case T_COSE_ALGORITHM_A256CBC: evp_cipher = EVP_aes_256_cbc ();break; + default: return_value = T_COSE_ERR_UNSUPPORTED_CIPHER_ALG; goto Done3; + } + + /* ------- Length checks (since OpenSSL doesn't) -------*/ + if(plaintext_buffer.len < ciphertext.len) { /* plaintext-buffer-check */ + /* The buffer to receive the plaintext is too small. This + * check assumes AEAD. See aead_byte_count(). */ + return_value = T_COSE_ERR_TOO_SMALL; + goto Done2; + } + if(!is_size_t_to_int_cast_ok(plaintext_buffer.len)){ + /* While plaintext_buffer.len is never cast to int, + * the length of bytes that are put in it are + * held by the integer bytes_output. This checks + * affirms that it is OK to hold that counter in an int. */ + return_value = T_COSE_ERR_DECRYPT_FAIL; + goto Done2; + } + + /* ------- Algorithm and key and IV length checks -------*/ + /* This is a sanity check. OpenSSL doesn't provide this check + * when using a key. It just assumes you've provided the right key + * length to EVP_DecryptInit(). A bit unhygenic if you ask me. */ + expected_key_length = EVP_CIPHER_key_length(evp_cipher); + if(key.key.buffer.len != (size_t)expected_key_length) { + return_value = T_COSE_ERR_WRONG_TYPE_OF_KEY; + goto Done2; + } + /* Same hygene check for IV/nonce length as for key */ + /* Assume that EVP_CIPHER_iv_length() won't ever return something + * dumb like -1. It would be a bug in OpenSSL or such if it did. + * This make the cast to size_t mostly safe. */ + expected_iv_length = (size_t)EVP_CIPHER_iv_length(evp_cipher); + if(nonce.len < expected_iv_length){ + return_value = T_COSE_ERR_DECRYPT_FAIL; + goto Done2; + } + + /* -------- Context initialization with key and IV ---------- */ + evp_context = EVP_CIPHER_CTX_new(); + if(evp_context == NULL) { + return_value = T_COSE_ERR_DECRYPT_FAIL; + goto Done2; + } + ossl_result = EVP_DecryptInit(evp_context, + evp_cipher, + key.key.buffer.ptr, + nonce.ptr); + if(ossl_result != 1) { + return_value = T_COSE_ERR_DECRYPT_FAIL; + goto Done1; + } + + /* ---------- AAD ---------- */ + /* Since this is non AEAD cipher, AAD is ignored */ + + /* ---------- Actual encryption of plaintext to cipher text ---------*/ + /* Length subtraction safe because of ciphertext-length-check */ + if(!is_size_t_to_int_cast_ok(ciphertext.len)) { + /* Cast to integer below would not be safe. */ + return_value = T_COSE_ERR_ENCRYPT_FAIL; + goto Done1; + } + ossl_result = EVP_DecryptUpdate(evp_context, + plaintext_buffer.ptr, + &bytes_output, + ciphertext.ptr, + (int)(ciphertext.len)); + if(ossl_result != 1) { + return_value = T_COSE_ERR_ENCRYPT_FAIL; + goto Done1; + } + /* I don't know why but */ + + ossl_result = EVP_DecryptFinal_ex(evp_context, + (uint8_t *)plaintext_buffer.ptr + bytes_output, + &dummy_length); + if(ossl_result != 1) { + /* This is where an authentication failure is detected. */ + return_value = 10; // TODO: proper error code + goto Done1; + } + // This is needed for AES-CBC + bytes_output += dummy_length; + + /* ---------- Return pointer and length of plaintext ---------*/ + if(!is_int_to_size_t_cast_ok(bytes_output)) { + return_value = T_COSE_ERR_ENCRYPT_FAIL; + goto Done1; + } + plaintext->len = (size_t)bytes_output; + plaintext->ptr = plaintext_buffer.ptr; + + return_value = T_COSE_SUCCESS; + +Done1: + /* https://stackoverflow.com/questions/26345175/correct-way-to-free-allocate-the-context-in-the-openssl */ + EVP_CIPHER_CTX_free(evp_context); +Done2: + /* It seems that EVP_aes_128_gcm(), ... returns a const, non-allocated + * EVP_CIPHER and thus doesn't have to be freed. */ +Done3: + return return_value; +} /* * See documentation in t_cose_crypto.h diff --git a/crypto_adapters/t_cose_psa_crypto.c b/crypto_adapters/t_cose_psa_crypto.c index 884c6a46..16151c65 100644 --- a/crypto_adapters/t_cose_psa_crypto.c +++ b/crypto_adapters/t_cose_psa_crypto.c @@ -1082,6 +1082,18 @@ t_cose_crypto_make_symmetric_key_handle(int32_t cose_algorithm_id, psa_key_usage = PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT; key_bitlen = 128; break; + case T_COSE_ALGORITHM_A128CTR: + psa_algorithm = PSA_ALG_CTR; + psa_keytype = PSA_KEY_TYPE_AES; + psa_key_usage = PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT; + key_bitlen = 128; + break; + case T_COSE_ALGORITHM_A128CBC: + psa_algorithm = PSA_ALG_CBC_PKCS7; // RFC9459 requests to use padding + psa_keytype = PSA_KEY_TYPE_AES; + psa_key_usage = PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT; + key_bitlen = 128; + break; case T_COSE_ALGORITHM_A192GCM: case T_COSE_ALGORITHM_A192KW: @@ -1090,6 +1102,18 @@ t_cose_crypto_make_symmetric_key_handle(int32_t cose_algorithm_id, psa_key_usage = PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT; key_bitlen = 192; break; + case T_COSE_ALGORITHM_A192CTR: + psa_algorithm = PSA_ALG_CTR; + psa_keytype = PSA_KEY_TYPE_AES; + psa_key_usage = PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT; + key_bitlen = 192; + break; + case T_COSE_ALGORITHM_A192CBC: + psa_algorithm = PSA_ALG_CBC_PKCS7; // RFC9459 requests to use padding + psa_keytype = PSA_KEY_TYPE_AES; + psa_key_usage = PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT; + key_bitlen = 192; + break; case T_COSE_ALGORITHM_A256GCM: case T_COSE_ALGORITHM_A256KW: @@ -1098,6 +1122,18 @@ t_cose_crypto_make_symmetric_key_handle(int32_t cose_algorithm_id, psa_key_usage = PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT; key_bitlen = 256; break; + case T_COSE_ALGORITHM_A256CTR: + psa_algorithm = PSA_ALG_CTR; + psa_keytype = PSA_KEY_TYPE_AES; + psa_key_usage = PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT; + key_bitlen = 256; + break; + case T_COSE_ALGORITHM_A256CBC: + psa_algorithm = PSA_ALG_CBC_PKCS7; // RFC9459 requests to use padding + psa_keytype = PSA_KEY_TYPE_AES; + psa_key_usage = PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT; + key_bitlen = 256; + break; case T_COSE_ALGORITHM_HMAC256: psa_keytype = PSA_KEY_TYPE_HMAC; @@ -1178,6 +1214,58 @@ aead_byte_count(const int32_t cose_algorithm_id, } +/* Compute size of ciphertext, given size of plaintext. Returns + * SIZE_MAX if the algorithm is unknown. Also returns the tag + * length. */ +static size_t +non_aead_byte_count(const int32_t cose_algorithm_id, + size_t plain_text_len) +{ + /* This works for CTR (counter) and CBC, non AEAD algorithms, + * but can be augmented for others. + * + * For both CTR and CBC as used by COSE and HPKE, the authentication tag is not + * appended. + * + * For CTR the ciphertext length is the same as the plaintext length. + * (This is not true of other ciphers). + * https://crypto.stackexchange.com/questions/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode + * + * For CBC the plaintext will be padded from 1 bytes to the block size. + * The block size is 16 bytes for all A128CBC, A192CBC and A256CBC. + * If the plaintext size is 30 bytes for A128CBC, 2 bytes padding will be inserted, + * and each padding byte has value 0x02. + */ + + size_t padding_length = 0; + switch(cose_algorithm_id) { + case T_COSE_ALGORITHM_A128CTR: + case T_COSE_ALGORITHM_A192CTR: + case T_COSE_ALGORITHM_A256CTR: + return plain_text_len; + case T_COSE_ALGORITHM_A128CBC: + case T_COSE_ALGORITHM_A192CBC: + case T_COSE_ALGORITHM_A256CBC: + padding_length = 16 - plain_text_len % 16; + break; + default: + return SIZE_MAX; + } + + if(plain_text_len > (SIZE_MAX - padding_length)) { + /* The extremely rare case where plain_text_len + * is almost SIZE_MAX in length and the length + * additions below will fail. This error is not + * the right one, but the case is so rare that + * it's not worth the trouble of making up some + * other error. This check is here primarily + * for static analyzers. */ + return SIZE_MAX; + } + return plain_text_len + padding_length; +} + + static enum t_cose_err_t aead_psa_status_to_t_cose_err(psa_status_t status, enum t_cose_err_t deflt) { @@ -1196,6 +1284,8 @@ aead_psa_status_to_t_cose_err(psa_status_t status, enum t_cose_err_t deflt) case PSA_ERROR_INVALID_SIGNATURE: return T_COSE_ERR_DATA_AUTH_FAILED; + case PSA_ERROR_INVALID_PADDING: return T_COSE_ERR_BAD_PADDING; + default: return deflt; } } @@ -1262,6 +1352,91 @@ t_cose_crypto_aead_encrypt(const int32_t cose_algorithm_id, } +/* + * See documentation in t_cose_crypto.h + */ +enum t_cose_err_t +t_cose_crypto_non_aead_encrypt(const int32_t cose_algorithm_id, + struct t_cose_key key, + struct q_useful_buf_c nonce, + struct q_useful_buf_c plaintext, + struct q_useful_buf ciphertext_buffer, + struct q_useful_buf_c *ciphertext) +{ + // based on https://mbed-tls.readthedocs.io/en/latest/getting_started/psa/ + + psa_status_t status; + psa_algorithm_t psa_algorithm_id; + psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; + size_t dummy_length = 0; + + /* Pretty sure the optimizer will do good things with this switch. */ + switch (cose_algorithm_id) { + case T_COSE_ALGORITHM_A128CTR: + psa_algorithm_id = PSA_ALG_CTR; + break; + case T_COSE_ALGORITHM_A192CTR: + psa_algorithm_id = PSA_ALG_CTR; + break; + case T_COSE_ALGORITHM_A256CTR: + psa_algorithm_id = PSA_ALG_CTR; + break; + + // RFC9459 requests to use padding + case T_COSE_ALGORITHM_A128CBC: + psa_algorithm_id = PSA_ALG_CBC_PKCS7; + break; + case T_COSE_ALGORITHM_A192CBC: + psa_algorithm_id = PSA_ALG_CBC_PKCS7; + break; + case T_COSE_ALGORITHM_A256CBC: + psa_algorithm_id = PSA_ALG_CBC_PKCS7; + break; + default: + return T_COSE_ERR_UNSUPPORTED_CIPHER_ALG; + } + + if(ciphertext_buffer.ptr == NULL) { + /* Called in length calculation mode. Return length & exit. */ + ciphertext->len = non_aead_byte_count(cose_algorithm_id, + plaintext.len);; + return T_COSE_SUCCESS; + } + + /* Encrypt the ciphertext */ + status = psa_cipher_encrypt_setup(&operation, (psa_key_handle_t)key.key.handle, psa_algorithm_id); + if(status != PSA_SUCCESS) { + goto Done; + } + status = psa_cipher_set_iv(&operation, nonce.ptr, nonce.len); + if(status != PSA_SUCCESS) { + goto Done; + } + status = psa_cipher_update(&operation, + plaintext.ptr, + plaintext.len, + ciphertext_buffer.ptr, + ciphertext_buffer.len, + &ciphertext->len); + if(status != PSA_SUCCESS) { + goto Done; + } + status = psa_cipher_finish(&operation, + (uint8_t *)ciphertext_buffer.ptr + ciphertext->len, + ciphertext_buffer.len - ciphertext->len, + &dummy_length); + if(status != PSA_SUCCESS) { + goto Done; + } + + ciphertext->len += dummy_length; + ciphertext->ptr = ciphertext_buffer.ptr; + +Done: + return aead_psa_status_to_t_cose_err(status, T_COSE_ERR_ENCRYPT_FAIL); +} + + /* * See documentation in t_cose_crypto.h */ @@ -1305,6 +1480,83 @@ 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_non_aead_decrypt(const int32_t cose_algorithm_id, + struct t_cose_key key, + struct q_useful_buf_c nonce, + struct q_useful_buf_c ciphertext, + struct q_useful_buf plaintext_buffer, + struct q_useful_buf_c *plaintext) +{ + // based on https://mbed-tls.readthedocs.io/en/latest/getting_started/psa/ + + psa_status_t status; + psa_algorithm_t psa_algorithm_id; + psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; + size_t dummy_length = 0; + + /* Pretty sure the optimizer will do good things with this switch. */ + switch (cose_algorithm_id) { + case T_COSE_ALGORITHM_A128CTR: + psa_algorithm_id = PSA_ALG_CTR; + break; + case T_COSE_ALGORITHM_A192CTR: + psa_algorithm_id = PSA_ALG_CTR; + break; + case T_COSE_ALGORITHM_A256CTR: + psa_algorithm_id = PSA_ALG_CTR; + break; + + // RFC9459 requests to use padding + case T_COSE_ALGORITHM_A128CBC: + psa_algorithm_id = PSA_ALG_CBC_PKCS7; + break; + case T_COSE_ALGORITHM_A192CBC: + psa_algorithm_id = PSA_ALG_CBC_PKCS7; + break; + case T_COSE_ALGORITHM_A256CBC: + psa_algorithm_id = PSA_ALG_CBC_PKCS7; + break; + default: + return T_COSE_ERR_UNSUPPORTED_CIPHER_ALG; + } + + /* Decrypt the ciphertext */ + status = psa_cipher_decrypt_setup(&operation, (psa_key_handle_t)key.key.handle, psa_algorithm_id); + if(status != PSA_SUCCESS) { + goto Done; + } + status = psa_cipher_set_iv(&operation, nonce.ptr, nonce.len); + if(status != PSA_SUCCESS) { + goto Done; + } + status = psa_cipher_update(&operation, + ciphertext.ptr, + ciphertext.len, + plaintext_buffer.ptr, + plaintext_buffer.len, + &plaintext->len); + if(status != PSA_SUCCESS) { + goto Done; + } + status = psa_cipher_finish(&operation, + (uint8_t *)plaintext_buffer.ptr + plaintext->len, + plaintext_buffer.len - plaintext->len, + &dummy_length); + if(status != PSA_SUCCESS) { + goto Done; + } + + plaintext->len += dummy_length; + plaintext->ptr = plaintext_buffer.ptr; + +Done: + return aead_psa_status_to_t_cose_err(status, T_COSE_ERR_DECRYPT_FAIL); +} + /* * 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 21f7f61f..ec1e0efe 100644 --- a/crypto_adapters/t_cose_test_crypto.c +++ b/crypto_adapters/t_cose_test_crypto.c @@ -530,6 +530,84 @@ t_cose_crypto_aead_encrypt(const int32_t cose_algorithm_id, } +/* Compute size of ciphertext, given size of plaintext. Returns + * SIZE_MAX if the algorithm is unknown. Also returns the tag + * length. */ +static size_t +non_aead_byte_count(const int32_t cose_algorithm_id, + size_t plain_text_len) +{ + /* This works for CTR (counter) and CBC, non AEAD algorithms, + * but can be augmented for others. + * + * For both CTR and CBC as used by COSE and HPKE, the authentication tag is not + * appended. + * + * For CTR the ciphertext length is the same as the plaintext length. + * (This is not true of other ciphers). + * https://crypto.stackexchange.com/questions/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode + * + * For CBC the plaintext will be padded from 1 bytes to the block size. + * The block size is 16 bytes for all A128CBC, A192CBC and A256CBC. + * If the plaintext size is 30 bytes for A128CBC, 2 bytes padding will be inserted, + * and each padding byte has value 0x02. + */ + + size_t padding_length = 0; + switch(cose_algorithm_id) { + case T_COSE_ALGORITHM_A128CTR: + case T_COSE_ALGORITHM_A192CTR: + case T_COSE_ALGORITHM_A256CTR: + return plain_text_len; + case T_COSE_ALGORITHM_A128CBC: + case T_COSE_ALGORITHM_A192CBC: + case T_COSE_ALGORITHM_A256CBC: + return plain_text_len + (16 - plain_text_len % 16); + } +} + + +/* + * See documentation in t_cose_crypto.h + */ +enum t_cose_err_t +t_cose_crypto_non_aead_encrypt(const int32_t cose_algorithm_id, + struct t_cose_key key, + struct q_useful_buf_c nonce, + struct q_useful_buf_c plaintext, + struct q_useful_buf ciphertext_buffer, + struct q_useful_buf_c *ciphertext) +{ + struct q_useful_buf_c tag = Q_USEFUL_BUF_FROM_SZ_LITERAL(FAKE_TAG); + + (void)nonce; + (void)cose_algorithm_id; + (void)key; + + + if(ciphertext_buffer.ptr == NULL) { + /* Called in length calculation mode. Return length & exit. */ + ciphertext->len = non_aead_byte_count(cose_algorithm_id, + plaintext.len);; + return T_COSE_SUCCESS; + } + + /* Use useful output to copy the plaintext as pretend encryption + * and add "tagtag.." as a pretend tag.*/ + UsefulOutBuf UOB; + UsefulOutBuf_Init(&UOB, ciphertext_buffer); + UsefulOutBuf_AppendUsefulBuf(&UOB, plaintext); + UsefulOutBuf_AppendUsefulBuf(&UOB, tag); + *ciphertext = UsefulOutBuf_OutUBuf(&UOB); + + if(q_useful_buf_c_is_null(*ciphertext)) { + return T_COSE_ERR_TOO_SMALL; + } + + return T_COSE_SUCCESS; +} + + /* * See documentation in t_cose_crypto.h */ @@ -573,6 +651,47 @@ 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_non_aead_decrypt(const int32_t cose_algorithm_id, + struct t_cose_key key, + struct q_useful_buf_c nonce, + struct q_useful_buf_c ciphertext, + struct q_useful_buf plaintext_buffer, + struct q_useful_buf_c *plaintext) +{ + struct q_useful_buf_c expected_tag = Q_USEFUL_BUF_FROM_SZ_LITERAL(FAKE_TAG); + struct q_useful_buf_c received_tag; + struct q_useful_buf_c received_plaintext; + + (void)nonce; + (void)cose_algorithm_id; + (void)key; + + UsefulInputBuf UIB; + UsefulInputBuf_Init(&UIB, ciphertext); + if(ciphertext.len < expected_tag.len) { + return T_COSE_ERR_DECRYPT_FAIL; + } + received_plaintext = UsefulInputBuf_GetUsefulBuf(&UIB, ciphertext.len - expected_tag.len); + received_tag = UsefulInputBuf_GetUsefulBuf(&UIB, expected_tag.len); + + if(q_useful_buf_compare(expected_tag, received_tag)) { + return T_COSE_ERR_DATA_AUTH_FAILED; + } + + *plaintext = q_useful_buf_copy(plaintext_buffer, received_plaintext); + + if(q_useful_buf_c_is_null(*plaintext)) { + return T_COSE_ERR_TOO_SMALL; + } + + return T_COSE_SUCCESS; +} + + static const uint8_t rfc_3394_key_wrap_iv[] = {0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6}; enum t_cose_err_t diff --git a/inc/t_cose/t_cose_common.h b/inc/t_cose/t_cose_common.h index 92bbed43..86a803a5 100644 --- a/inc/t_cose/t_cose_common.h +++ b/inc/t_cose/t_cose_common.h @@ -646,6 +646,11 @@ enum t_cose_err_t { * crypto algorithm is not AEAD and thus can't protect the headers. */ T_COSE_ERR_PROTECTED_NOT_ALLOWED = 90, + /** While decryption, the padding for AES-CBC is invalid. */ + T_COSE_ERR_BAD_PADDING = 90, + + /** External AAD is passed as an argument for non AEAD cipher. */ + T_COSE_ERR_AAD_WITH_NON_AEAD = 91, }; @@ -854,6 +859,7 @@ struct t_cose_sign_inputs { struct t_cose_alg_and_bits { int32_t cose_alg_id; uint32_t bits_in_key; + uint32_t bits_iv; }; diff --git a/inc/t_cose/t_cose_parameters.h b/inc/t_cose/t_cose_parameters.h index 574ad122..9f46566a 100644 --- a/inc/t_cose/t_cose_parameters.h +++ b/inc/t_cose/t_cose_parameters.h @@ -1024,7 +1024,7 @@ t_cose_param_find_alg_id_unprot(const struct t_cose_parameter *parameter_list) int32_t alg_id; bool prot; alg_id = t_cose_param_find_alg_id(parameter_list, &prot); - if(prot != true) { + if(prot == true) { return T_COSE_ALGORITHM_NONE; } diff --git a/inc/t_cose/t_cose_standard_constants.h b/inc/t_cose/t_cose_standard_constants.h index 54ce3ace..65b2f995 100644 --- a/inc/t_cose/t_cose_standard_constants.h +++ b/inc/t_cose/t_cose_standard_constants.h @@ -447,6 +447,65 @@ */ #define T_COSE_ALGORITHM_NONE 0 +/** + * \def COSE_ALGORITHM_A128CTR + * + * \brief AES-CTR mode w/ 128-bit key + * + * As this algorithm is non-AEAD cipher, DO NOT use this + * unless integrity and authentication is provided by another mechanism. + */ +#define T_COSE_ALGORITHM_A128CTR -65534 + +/** + * \def COSE_ALGORITHM_A192CTR + * + * \brief AES-CTR mode w/ 192-bit key + * + * As this algorithm is non-AEAD cipher, DO NOT use this + * unless integrity and authentication is provided by another mechanism. + */ +#define T_COSE_ALGORITHM_A192CTR -65533 + +/** + * \def COSE_ALGORITHM_A256CTR + * + * \brief AES-CTR mode w/ 256-bit key + * + * As this algorithm is non-AEAD cipher, DO NOT use this + * unless integrity and authentication is provided by another mechanism. + */ +#define T_COSE_ALGORITHM_A256CTR -65532 + +/** + * \def COSE_ALGORITHM_A128CBC + * + * \brief AES-CBC mode w/ 128-bit key + * + * As this algorithm is non-AEAD cipher, DO NOT use this + * unless integrity and authentication is provided by another mechanism. + */ +#define T_COSE_ALGORITHM_A128CBC -65531 + +/** + * \def COSE_ALGORITHM_A192CBC + * + * \brief AES-CBC mode w/ 192-bit key + * + * As this algorithm is non-AEAD cipher, DO NOT use this + * unless integrity and authentication is provided by another mechanism. + */ +#define T_COSE_ALGORITHM_A192CBC -65530 + +/** + * \def COSE_ALGORITHM_A256CBC + * + * \brief AES-CBC mode w/ 256-bit key + * + * As this algorithm is non-AEAD cipher, DO NOT use this + * unless integrity and authentication is provided by another mechanism. + */ +#define T_COSE_ALGORITHM_A256CBC -65529 diff --git a/src/t_cose_crypto.h b/src/t_cose_crypto.h index d58d2028..944df741 100644 --- a/src/t_cose_crypto.h +++ b/src/t_cose_crypto.h @@ -1076,6 +1076,55 @@ t_cose_crypto_aead_decrypt(int32_t cose_algorithm_id, struct q_useful_buf plaintext_buffer, struct q_useful_buf_c *plaintext); +/** + * \brief Decrypt a ciphertext using a non AEAD cipher. Part of the + * t_cose crypto adaptation layer. + * + * \param[in] cose_algorithm_id The algorithm to use for decryption. + * The IDs are defined in [COSE (RFC 8152)] + * (https://tools.ietf.org/html/rfc8152) + * or in the [IANA COSE Registry] + * (https://www.iana.org/assignments/cose/cose.xhtml). + * \param[in] key The decryption key to use. + * \param[in] nonce The nonce used as input to the decryption operation. + * \param[in] ciphertext The ciphertext to decrypt. + * \param[in] plaintext_buffer Buffer where the plaintext will be put. + * \param[out] plaintext Place to return the plaintext + * + * The key provided must be a symmetric key of the correct type for + * \c cose_algorithm_id. + * + * A key handle is used even though it could be a buffer with a key in + * order to allow use of keys internal to the crypto library, crypto + * HW and such. See t_cose_crypto_make_symmetric_key_handle(). + * + * This does not need to support a size calculation mode as is + * required of t_cose_crypto_non_aead_encrypt(). + * + * One of the following errors should be returned. Other errors should + * not be returned. + * + * \retval T_COSE_SUCCESS + * The decryption operation was successful. + * \retval T_COSE_ERR_UNSUPPORTED_CIPHER_ALG + * An unsupported cipher algorithm was provided. + * \retval T_COSE_ERR_TOO_SMALL + * The \c plaintext_buffer is too small. + * \retval T_COSE_ERR_WRONG_TYPE_OF_KEY + * The key is not right for the algorithm or is not allowed for decryption. + * \retval T_COSE_ERR_DECRYPT_FAIL + * Decryption failed for a reason other than above. + * \retval T_COSE_ERR_BAD_PADDING + * The padding does not match defined in RFC9459. + */ +enum t_cose_err_t +t_cose_crypto_non_aead_decrypt(int32_t cose_algorithm_id, + struct t_cose_key key, + struct q_useful_buf_c nonce, + struct q_useful_buf_c ciphertext, + struct q_useful_buf plaintext_buffer, + struct q_useful_buf_c *plaintext); + /** * \brief Encrypt plaintext using an AEAD cipher. Part of the * t_cose crypto adaptation layer. @@ -1127,6 +1176,55 @@ t_cose_crypto_aead_encrypt(int32_t cose_algorithm_id, struct q_useful_buf_c *ciphertext); +/** + * \brief Encrypt plaintext using a non AEAD cipher. + * Part of the t_cose crypto adaptation layer. + * + * \param[in] cose_algorithm_id The algorithm to use for encryption. + * The IDs are defined in [COSE (RFC 8152)] + * (https://tools.ietf.org/html/rfc8152) + * or in the [IANA COSE Registry] + * (https://www.iana.org/assignments/cose/cose.xhtml). + * \param[in] key The encryption key to use. + * \param[in] nonce The nonce used as input to the encryption operation. + * \param[in] plaintext The plaintext to encrypt. + * \param[in] ciphertext_buffer Buffer where the ciphertext will be put. + * \param[out] ciphertext Place to put pointer and length to ciphertext. + * + * The key provided must be a symmetric key of the correct type for + * \c cose_algorithm_id. + * + * A key handle is used even though it could be a buffer with a key in + * order to allow use of keys internal to the crypto library, crypto + * HW and such. See t_cose_crypto_make_symmetric_key_handle(). + * + * This must support a size calculation mode which is indicated by + * ciphertext_buffer.ptr == NULL and which fills the size in + * ciphertext->len. + * + * One of the following errors should be returned. Other errors should + * not be returned. + * + * \retval T_COSE_SUCCESS + * The decryption operation was successful. + * \retval T_COSE_ERR_UNSUPPORTED_CIPHER_ALG + * An unsupported cipher algorithm was provided. + * \retval T_COSE_ERR_TOO_SMALL + * \c ciphertext_buffer is too small. + * \retval T_COSE_ERR_WRONG_TYPE_OF_KEY + * The key is not right for the algorithm or is not allowed for encryption. + * \retval T_COSE_ERR_ENCRYPT_FAIL + * Encryption failed for a reason other than above. + */ +enum t_cose_err_t +t_cose_crypto_non_aead_encrypt(int32_t cose_algorithm_id, + struct t_cose_key key, + struct q_useful_buf_c nonce, + struct q_useful_buf_c plaintext, + struct q_useful_buf ciphertext_buffer, + struct q_useful_buf_c *ciphertext); + + /* Free a symmetric key. */ void t_cose_crypto_free_symmetric_key(struct t_cose_key key); diff --git a/src/t_cose_encrypt_dec.c b/src/t_cose_encrypt_dec.c index 1194cdfd..4c5683a7 100644 --- a/src/t_cose_encrypt_dec.c +++ b/src/t_cose_encrypt_dec.c @@ -193,6 +193,16 @@ t_cose_encrypt_dec_detached(struct t_cose_encrypt_dec_ctx* me, nonce_cbor = t_cose_param_find_iv(body_params_list); ce_alg.cose_alg_id = t_cose_param_find_alg_id_prot(body_params_list); + if(ce_alg.cose_alg_id == T_COSE_ALGORITHM_NONE) { + /* Might be non AEAD and located in unprotected header. */ + ce_alg.cose_alg_id = t_cose_param_find_alg_id_unprot(body_params_list); + if(ce_alg.cose_alg_id == T_COSE_ALGORITHM_NONE) { + return T_COSE_ERR_NO_ALG_ID; + } + if(!t_cose_alg_is_non_aead(ce_alg.cose_alg_id)) { + /* TOCO: Should we accept AEAD algorithm ID in unprotected header? */ + } + } all_params_list = body_params_list; ce_alg.bits_in_key = bits_in_crypto_alg(ce_alg.cose_alg_id); @@ -321,41 +331,54 @@ t_cose_encrypt_dec_detached(struct t_cose_encrypt_dec_ctx* me, // TODO: stop here for decode-only mode */ - /* --- Make the Enc_structure ---- */ - /* The Enc_structure from RFC 9052 section 5.3 that is AAD input - * to the AEAD to integrity-protect COSE headers and - * parameters. */ - if(!q_useful_buf_is_null(me->extern_enc_struct_buffer)) { - /* Caller gave us a (bigger) buffer for Enc_structure */ - enc_struct_buffer = me->extern_enc_struct_buffer; - } - msg_type_string = (message_type == T_COSE_OPT_MESSAGE_TYPE_ENCRYPT0 ? - "Encrypt0" : - "Encrypt"); - return_value = - create_enc_structure( - msg_type_string, /* in: message type context string */ - protected_params, /* in: body protected parameters */ - ext_sup_data, /* in: externally supplied data to protect */ - enc_struct_buffer, /* in: buffer for encoded Enc_structure */ - &enc_structure /* out: CBOR encoded Enc_structure */ - ); - if (return_value != T_COSE_SUCCESS) { - goto Done; + /* --- The body/content decryption --- */ + if(t_cose_alg_is_non_aead(ce_alg.cose_alg_id)) { + return_value = + t_cose_crypto_non_aead_decrypt( + ce_alg.cose_alg_id, /* in: cose alg id to decrypt payload */ + cek_key, /* in: content encryption key */ + nonce_cbor, /* in: iv / nonce for decrypt */ + cipher_text, /* in: bytes to decrypt */ + plaintext_buffer, /* in: buffer to output plaintext into */ + plaintext /* out: the decrypted payload */ + ); } + else { + /* --- Make the Enc_structure ---- */ + /* The Enc_structure from RFC 9052 section 5.3 that is AAD input + * to the AEAD to integrity-protect COSE headers and + * parameters. */ + if(!q_useful_buf_is_null(me->extern_enc_struct_buffer)) { + /* Caller gave us a (bigger) buffer for Enc_structure */ + enc_struct_buffer = me->extern_enc_struct_buffer; + } + msg_type_string = (message_type == T_COSE_OPT_MESSAGE_TYPE_ENCRYPT0 ? + "Encrypt0" : + "Encrypt"); + return_value = + create_enc_structure( + msg_type_string, /* in: message type context string */ + protected_params, /* in: body protected parameters */ + ext_sup_data, /* in: AAD from caller to integrity protect */ + enc_struct_buffer, /* in: buffer for encoded Enc_structure */ + &enc_structure /* out: CBOR encoded Enc_structure */ + ); + if (return_value != T_COSE_SUCCESS) { + goto Done; + } - /* --- The body/content decryption --- */ - // TODO: handle AE algorithms - return_value = - t_cose_crypto_aead_decrypt( - ce_alg.cose_alg_id, /* in: cose alg id to decrypt payload */ - cek_key, /* in: content encryption key */ - nonce_cbor, /* in: iv / nonce for decrypt */ - enc_structure, /* in: the AAD for the AEAD */ - cipher_text, /* in: bytes to decrypt */ - plaintext_buffer, /* in: buffer to output plaintext into */ - plaintext /* out: the decrypted payload */ - ); + // TODO: handle AE algorithms + return_value = + t_cose_crypto_aead_decrypt( + ce_alg.cose_alg_id, /* in: cose alg id to decrypt payload */ + cek_key, /* in: content encryption key */ + nonce_cbor, /* in: iv / nonce for decrypt */ + enc_structure, /* in: the AAD for the AEAD */ + cipher_text, /* in: bytes to decrypt */ + plaintext_buffer, /* in: buffer to output plaintext into */ + plaintext /* out: the decrypted payload */ + ); + } if (message_type != T_COSE_OPT_MESSAGE_TYPE_ENCRYPT0) { t_cose_crypto_free_symmetric_key(cek_key); } diff --git a/src/t_cose_encrypt_enc.c b/src/t_cose_encrypt_enc.c index 4438fa48..c713d2d4 100644 --- a/src/t_cose_encrypt_enc.c +++ b/src/t_cose_encrypt_enc.c @@ -39,6 +39,7 @@ t_cose_encrypt_enc_detached(struct t_cose_encrypt_enc *me, unsigned message_type; struct q_useful_buf_c nonce; struct t_cose_parameter params[2]; /* 1 for Alg ID plus 1 for IV */ + struct t_cose_parameter *p_param; struct q_useful_buf_c body_prot_headers; struct q_useful_buf_c enc_structure; struct t_cose_alg_and_bits ce_alg; @@ -51,6 +52,7 @@ t_cose_encrypt_enc_detached(struct t_cose_encrypt_enc *me, struct q_useful_buf encrypt_buffer; struct q_useful_buf_c encrypt_output; bool is_cose_encrypt0; + bool is_none_aead_cipher; struct t_cose_recipient_enc *recipient; @@ -70,11 +72,20 @@ t_cose_encrypt_enc_detached(struct t_cose_encrypt_enc *me, return T_COSE_ERR_BAD_OPT; } - /* ---- Algorithm ID, IV and parameter list ---- */ /* Determine algorithm parameters */ + is_none_aead_cipher = t_cose_alg_is_non_aead(me->payload_cose_algorithm_id); + if(is_none_aead_cipher && !q_useful_buf_c_is_null_or_empty(ext_sup_data)) { + /* Section 6 of RFC9459 says, + * COSE libraries that support either AES-CTR or AES-CBC and + * accept Additional Authenticated Data (AAD) as input MUST return an error + */ + return T_COSE_ERR_AAD_WITH_NON_AEAD; + } + ce_alg.cose_alg_id = me->payload_cose_algorithm_id; ce_alg.bits_in_key = bits_in_crypto_alg(ce_alg.cose_alg_id); + ce_alg.bits_iv = bits_iv_alg(ce_alg.cose_alg_id); if(ce_alg.bits_in_key == UINT32_MAX) { return T_COSE_ERR_UNSUPPORTED_CIPHER_ALG; } @@ -82,7 +93,7 @@ t_cose_encrypt_enc_detached(struct t_cose_encrypt_enc *me, /* Generate random nonce (aka iv) */ return_value = t_cose_crypto_get_random(nonce_buffer, - ce_alg.bits_in_key / 8, + ce_alg.bits_iv / 8, &nonce); params[1] = t_cose_param_make_iv(nonce); @@ -90,6 +101,12 @@ t_cose_encrypt_enc_detached(struct t_cose_encrypt_enc *me, params[1].next = me->added_body_parameters; /* At this point all the header parameters to be encoded are in a * linked list the head of which is params[0]. */ + if(is_none_aead_cipher) { + /* Move all params to unprotected to make protected header be a zero-byte string */ + for(p_param = ¶ms[0]; p_param != NULL; p_param = p_param->next) { + p_param->in_protected = false; + } + } /* ---- Get started with the CBOR encoding ---- */ @@ -108,33 +125,6 @@ t_cose_encrypt_enc_detached(struct t_cose_encrypt_enc *me, goto Done; } - - /* ---- Make the Enc_structure ---- */ - /* Per RFC 9052 section 5.3 the structure that is authenticated - * along with the payload by the AEAD. - * - * Enc_structure = [ - * context : "Encrypt", - * protected : empty_or_serialized_map, - * external_aad : bstr - * ] - */ - if(!q_useful_buf_is_null(me->extern_enc_struct_buffer)) { - /* Caller gave us a (bigger) buffer for Enc_structure */ - enc_struct_buffer = me->extern_enc_struct_buffer; - } - enc_struct_string = is_cose_encrypt0 ? "Encrypt0" : "Encrypt"; - return_value = - create_enc_structure(enc_struct_string, /* in: message context string */ - body_prot_headers, /* in: CBOR encoded prot hdrs */ - ext_sup_data, /* in: external AAD */ - enc_struct_buffer, /* in: output buffer */ - &enc_structure); /* out: encoded Enc_structure */ - if(return_value != T_COSE_SUCCESS) { - goto Done; - } - - /* ---- Figure out the CEK ---- */ if(is_cose_encrypt0) { /* For COSE_Encrypt0, the caller must have set the cek explicitly. */ @@ -160,9 +150,9 @@ t_cose_encrypt_enc_detached(struct t_cose_encrypt_enc *me, /* At this point cek_handle has the encryption key for the AEAD */ - /* ---- Run AEAD to encrypt the payload, detached or not */ + /* ---- Encrypt the payload, detached or not */ if(q_useful_buf_is_null(buffer_for_detached)) { - /* Set up so AEAD writes directly to the output buffer to save lots + /* Set up so encryption writes directly to the output buffer to save lots * of memory since no intermediate buffer is needed! */ QCBOREncode_OpenBytes(&cbor_encoder, &encrypt_buffer); @@ -171,14 +161,49 @@ t_cose_encrypt_enc_detached(struct t_cose_encrypt_enc *me, encrypt_buffer = buffer_for_detached; } - return_value = - t_cose_crypto_aead_encrypt(ce_alg.cose_alg_id, /* in: AEAD alg ID */ - cek_handle, /* in: content encryption key handle */ - nonce, /* in: nonce / IV */ - enc_structure, /* in: AAD to authenticate */ - payload, /* in: payload to encrypt */ - encrypt_buffer, /* in: buffer to write to */ - &encrypt_output /* out: ciphertext */); + if(is_none_aead_cipher) { + return_value = + t_cose_crypto_non_aead_encrypt(ce_alg.cose_alg_id, /* in: non AEAD alg ID */ + cek_handle, /* in: content encryption key handle */ + nonce, /* in: nonce / IV */ + payload, /* in: payload to encrypt */ + encrypt_buffer, /* in: buffer to write to */ + &encrypt_output /* out: ciphertext */); + } else { + /* ---- Make the Enc_structure ---- */ + /* Per RFC 9052 section 5.3 the structure that is authenticated + * along with the payload by the AEAD. + * + * Enc_structure = [ + * context : "Encrypt", + * protected : empty_or_serialized_map, + * external_aad : bstr + * ] + */ + if(!q_useful_buf_is_null(me->extern_enc_struct_buffer)) { + /* Caller gave us a (bigger) buffer for Enc_structure */ + enc_struct_buffer = me->extern_enc_struct_buffer; + } + enc_struct_string = is_cose_encrypt0 ? "Encrypt0" : "Encrypt"; + return_value = + create_enc_structure(enc_struct_string, /* in: message context string */ + body_prot_headers, /* in: CBOR encoded prot hdrs */ + ext_sup_data, /* in: external AAD */ + enc_struct_buffer, /* in: output buffer */ + &enc_structure); /* out: encoded Enc_structure */ + if(return_value != T_COSE_SUCCESS) { + goto Done; + } + + return_value = + t_cose_crypto_aead_encrypt(ce_alg.cose_alg_id, /* in: AEAD alg ID */ + cek_handle, /* in: content encryption key handle */ + nonce, /* in: nonce / IV */ + enc_structure, /* in: AAD to authenticate */ + payload, /* in: payload to encrypt */ + encrypt_buffer, /* in: buffer to write to */ + &encrypt_output /* out: ciphertext */); + } if (return_value != T_COSE_SUCCESS) { goto Done; diff --git a/src/t_cose_util.c b/src/t_cose_util.c index 4ac765a0..47dfd700 100644 --- a/src/t_cose_util.c +++ b/src/t_cose_util.c @@ -226,7 +226,45 @@ bits_in_crypto_alg(int32_t cose_algorithm_id) switch(cose_algorithm_id) { case T_COSE_ALGORITHM_AES128CCM_16_128: case T_COSE_ALGORITHM_A128KW: - case T_COSE_ALGORITHM_A128GCM: return 128; + case T_COSE_ALGORITHM_A128GCM: + case T_COSE_ALGORITHM_A128CTR: + case T_COSE_ALGORITHM_A128CBC: return 128; + case T_COSE_ALGORITHM_A192KW: + case T_COSE_ALGORITHM_A192GCM: + case T_COSE_ALGORITHM_A192CTR: + case T_COSE_ALGORITHM_A192CBC: return 192; + case T_COSE_ALGORITHM_AES256CCM_16_128: + case T_COSE_ALGORITHM_A256KW: + case T_COSE_ALGORITHM_A256GCM: + case T_COSE_ALGORITHM_A256CTR: + case T_COSE_ALGORITHM_A256CBC: return 256; + default: return UINT32_MAX; + } +} + + + +/** + * \brief Returns the IV length (in bits) of a given encryption algo. + * + * @param cose_algorithm_id Crypto algorithm. + * + * Returns the IV length (in bits) or UINT_MAX in case of an + * unknown algorithm id. + */ +uint32_t +bits_iv_alg(int32_t cose_algorithm_id) +{ + switch(cose_algorithm_id) { + case T_COSE_ALGORITHM_AES128CCM_16_128: + case T_COSE_ALGORITHM_A128KW: + case T_COSE_ALGORITHM_A128GCM: + case T_COSE_ALGORITHM_A128CTR: + case T_COSE_ALGORITHM_A128CBC: + case T_COSE_ALGORITHM_A192CTR: + case T_COSE_ALGORITHM_A192CBC: + case T_COSE_ALGORITHM_A256CTR: + case T_COSE_ALGORITHM_A256CBC: return 128; case T_COSE_ALGORITHM_A192KW: case T_COSE_ALGORITHM_A192GCM: return 192; case T_COSE_ALGORITHM_AES256CCM_16_128: @@ -697,3 +735,20 @@ t_cose_link_rs(struct t_cose_rs_obj **list, struct t_cose_rs_obj *new_rs) t->next = new_rs; } } + +/* This returns true if the algorithm id is non AEAD defined in RFC9459 */ +bool +t_cose_alg_is_non_aead(int32_t cose_algorithm_id) +{ + switch(cose_algorithm_id) { + case T_COSE_ALGORITHM_A128CTR: + case T_COSE_ALGORITHM_A192CTR: + case T_COSE_ALGORITHM_A256CTR: + case T_COSE_ALGORITHM_A128CBC: + case T_COSE_ALGORITHM_A192CBC: + case T_COSE_ALGORITHM_A256CBC: + return true; + default: + return false; + } +} diff --git a/src/t_cose_util.h b/src/t_cose_util.h index 80269ad5..c10db5af 100644 --- a/src/t_cose_util.h +++ b/src/t_cose_util.h @@ -149,6 +149,17 @@ uint32_t bits_in_crypto_alg(int32_t cose_algorithm_id); +/** + * \brief Returns the IV length (in bits) of a given encryption algo. + * + * @param cose_algorithm_id Crypto algorithm. + * + * Returns the IV length (in bits) or UINT_MAX in case of an + * unknown algorithm id. + */ +uint32_t +bits_iv_alg(int32_t cose_algorithm_id); + /** * \brief Create the ToBeMaced (TBM) structure bytes for COSE. @@ -362,6 +373,15 @@ t_cose_check_list(int32_t cose_algorithm_id, const int32_t *list); int16_t t_cose_int16_map(const int16_t map[][2], int16_t query); +/** + * \brief Judge whether the algorithm id describes non AEAD cipher. + * + * \param[in] cose_algorithm_id The COSE algorithm id. + * + * \returns true of false. + */ +bool +t_cose_alg_is_non_aead(int32_t cose_algorithm_id); #ifdef __cplusplus } diff --git a/test/data/cose_encrypt_p256_wrap_aescbc.diag b/test/data/cose_encrypt_p256_wrap_aescbc.diag new file mode 100644 index 00000000..069a9fd2 --- /dev/null +++ b/test/data/cose_encrypt_p256_wrap_aescbc.diag @@ -0,0 +1,12 @@ +96([ + h'' / NOTE: The 'protected' header MUST be a zero-length byte string. /, + {1: -65531 / A128CBC /, 5: h'8262A8D72040A5E09A314586395D5750'}, + h'B60EC3C9A166D8EEFDAEA598FAB1CEBB7F0D5DDF2237F13A03A91364594983FE', + [ + [ + h'', + {1: -3}, + h'D21A60F7F67BA866F15BE27C983881AA47DF15AE6925A0D1' + ] + ] +]) \ No newline at end of file diff --git a/test/data/cose_encrypt_p256_wrap_aesctr.diag b/test/data/cose_encrypt_p256_wrap_aesctr.diag new file mode 100644 index 00000000..13488748 --- /dev/null +++ b/test/data/cose_encrypt_p256_wrap_aesctr.diag @@ -0,0 +1,12 @@ +96([ + h'' / NOTE: The 'protected' header MUST be a zero-length byte string. /, + {1: -65534 / A128CTR /, 5: h'77D35242A191E9F9FD26104AB308564E'}, + h'793A616A5617482BE76F17077858B148241591', + [ + [ + h'', + {1: -3}, + h'316ADA13003FE7C61AD4EA503500B285CACADE5107A8E280' + ] + ] +]) \ No newline at end of file diff --git a/test/data/test_messages.c b/test/data/test_messages.c index 4c4f020d..79381ee0 100644 --- a/test/data/test_messages.c +++ b/test/data/test_messages.c @@ -98,6 +98,34 @@ const unsigned char cose_encrypt_p256_wrap_128[] = { 0x85 }; const unsigned int cose_encrypt_p256_wrap_128_len = 225; +const unsigned char cose_encrypt_p256_wrap_aescbc[] = { + 0xd8, 0x60, 0x84, 0x40, 0xa2, 0x01, 0x39, 0xff, + 0xfa, 0x05, 0x50, 0x82, 0x62, 0xa8, 0xd7, 0x20, + 0x40, 0xa5, 0xe0, 0x9a, 0x31, 0x45, 0x86, 0x39, + 0x5d, 0x57, 0x50, 0x58, 0x20, 0xb6, 0x0e, 0xc3, + 0xc9, 0xa1, 0x66, 0xd8, 0xee, 0xfd, 0xae, 0xa5, + 0x98, 0xfa, 0xb1, 0xce, 0xbb, 0x7f, 0x0d, 0x5d, + 0xdf, 0x22, 0x37, 0xf1, 0x3a, 0x03, 0xa9, 0x13, + 0x64, 0x59, 0x49, 0x83, 0xfe, 0x81, 0x83, 0x40, + 0xa1, 0x01, 0x22, 0x58, 0x18, 0xd2, 0x1a, 0x60, + 0xf7, 0xf6, 0x7b, 0xa8, 0x66, 0xf1, 0x5b, 0xe2, + 0x7c, 0x98, 0x38, 0x81, 0xaa, 0x47, 0xdf, 0x15, + 0xae, 0x69, 0x25, 0xa0, 0xd1 +}; +const unsigned int cose_encrypt_p256_wrap_aescbc_len = 93; +const unsigned char cose_encrypt_p256_wrap_aesctr[] = { + 0xd8, 0x60, 0x84, 0x40, 0xa2, 0x01, 0x39, 0xff, + 0xfd, 0x05, 0x50, 0x77, 0xd3, 0x52, 0x42, 0xa1, + 0x91, 0xe9, 0xf9, 0xfd, 0x26, 0x10, 0x4a, 0xb3, + 0x08, 0x56, 0x4e, 0x53, 0x79, 0x3a, 0x61, 0x6a, + 0x56, 0x17, 0x48, 0x2b, 0xe7, 0x6f, 0x17, 0x07, + 0x78, 0x58, 0xb1, 0x48, 0x24, 0x15, 0x91, 0x81, + 0x83, 0x40, 0xa1, 0x01, 0x22, 0x58, 0x18, 0x31, + 0x6a, 0xda, 0x13, 0x00, 0x3f, 0xe7, 0xc6, 0x1a, + 0xd4, 0xea, 0x50, 0x35, 0x00, 0xb2, 0x85, 0xca, + 0xca, 0xde, 0x51, 0x07, 0xa8, 0xe2, 0x80 +}; +const unsigned int cose_encrypt_p256_wrap_aesctr_len = 79; const unsigned char cose_recipients_map_instead_of_array[] = { 0xd8, 0x60, 0x84, 0x43, 0xa1, 0x01, 0x03, 0xa1, 0x05, 0x4c, 0x02, 0xd1, 0xf7, 0xe6, 0xf2, 0x6c, diff --git a/test/data/test_messages.h b/test/data/test_messages.h index b2435bbf..3c8599dc 100644 --- a/test/data/test_messages.h +++ b/test/data/test_messages.h @@ -2,6 +2,8 @@ extern const unsigned char aead_in_error[225]; extern const unsigned char cose_encrypt_junk_recipient[251]; extern const unsigned char cose_encrypt_p256_wrap_128[225]; +extern const unsigned char cose_encrypt_p256_wrap_aescbc[93]; +extern const unsigned char cose_encrypt_p256_wrap_aesctr[79]; extern const unsigned char cose_recipients_map_instead_of_array[231]; extern const unsigned char tstr_ciphertext[223]; extern const unsigned char unknown_symmetric_alg[225]; diff --git a/test/run_tests.c b/test/run_tests.c index afa253d8..17b3b49f 100644 --- a/test/run_tests.c +++ b/test/run_tests.c @@ -47,6 +47,7 @@ static test_entry s_tests[] = { TEST_ENTRY(aead_test), #ifndef T_COSE_DISABLE_KEYWRAP TEST_ENTRY(kw_test), + TEST_ENTRY(decrypt_known_good_aeskw_non_aead_test), #endif TEST_ENTRY(hkdf_test), diff --git a/test/t_cose_encrypt_decrypt_test.c b/test/t_cose_encrypt_decrypt_test.c index dbda6b1a..00568e18 100644 --- a/test/t_cose_encrypt_decrypt_test.c +++ b/test/t_cose_encrypt_decrypt_test.c @@ -14,6 +14,9 @@ #include "t_cose/t_cose_encrypt_enc.h" #include "t_cose/t_cose_recipient_dec_esdh.h" #include "t_cose/t_cose_recipient_enc_esdh.h" +#include "t_cose/t_cose_recipient_dec_keywrap.h" +#include "t_cose/t_cose_recipient_enc_keywrap.h" +#include "t_cose_util.h" #include "data/test_messages.h" @@ -142,12 +145,18 @@ int32_t encrypt0_enc_dec(int32_t cose_algorithm_id) switch(cose_algorithm_id) { case T_COSE_ALGORITHM_A128GCM: + case T_COSE_ALGORITHM_A128CTR: + case T_COSE_ALGORITHM_A128CBC: cek_bytes = Q_USEFUL_BUF_FROM_SZ_LITERAL("128-bit key xxxx"); break; case T_COSE_ALGORITHM_A192GCM: + case T_COSE_ALGORITHM_A192CTR: + case T_COSE_ALGORITHM_A192CBC: cek_bytes = Q_USEFUL_BUF_FROM_SZ_LITERAL("192-bit key xxxxyyyyyyyy"); break; case T_COSE_ALGORITHM_A256GCM: + case T_COSE_ALGORITHM_A256CTR: + case T_COSE_ALGORITHM_A256CBC: cek_bytes = Q_USEFUL_BUF_FROM_SZ_LITERAL("256-bit key xxxxyyyyyyyyzzzzzzzz"); break; case T_COSE_ALGORITHM_AES128CCM_16_128: @@ -194,6 +203,10 @@ int32_t encrypt0_enc_dec(int32_t cose_algorithm_id) Q_USEFUL_BUF_FROM_SZ_LITERAL(AAD), cose_message_buf, &encrypted_cose_message); + if(t_cose_alg_is_non_aead(cose_algorithm_id) && t_cose_err == T_COSE_ERR_AAD_WITH_NON_AEAD) { + return_value = 0; + goto Done; + } if(t_cose_err) { return_value = 2000 + (int32_t)t_cose_err; goto Done; @@ -290,17 +303,47 @@ int32_t base_encrypt_decrypt_test(void) int32_t rv; rv = encrypt0_enc_dec(T_COSE_ALGORITHM_A128GCM); if(rv) { - return rv; + return 10000 + rv; } rv = encrypt0_enc_dec(T_COSE_ALGORITHM_A192GCM); if(rv) { - return rv; + return 20000 + rv; } rv = encrypt0_enc_dec(T_COSE_ALGORITHM_A256GCM); if(rv) { - return rv; + return 30000 + rv; + } + + rv = encrypt0_enc_dec(T_COSE_ALGORITHM_A128CTR); + if(rv) { + return 40000 + rv; + } + + rv = encrypt0_enc_dec(T_COSE_ALGORITHM_A192CTR); + if(rv) { + return 50000 + rv; + } + + rv = encrypt0_enc_dec(T_COSE_ALGORITHM_A256CTR); + if(rv) { + return 60000 + rv; + } + + rv = encrypt0_enc_dec(T_COSE_ALGORITHM_A128CBC); + if(rv) { + return 70000 + rv; + } + + rv = encrypt0_enc_dec(T_COSE_ALGORITHM_A192CBC); + if(rv) { + return 80000 + rv; + } + + rv = encrypt0_enc_dec(T_COSE_ALGORITHM_A256CBC); + if(rv) { + return 90000 + rv; } return 0; @@ -308,6 +351,83 @@ int32_t base_encrypt_decrypt_test(void) } +#ifndef T_COSE_DISABLE_KEYWRAP + +int32_t decrypt_key_wrap(struct q_useful_buf_c cose_encrypt_buffer) +{ + enum t_cose_err_t result; + int32_t return_value = 0; + struct t_cose_recipient_dec_keywrap kw_unwrap_recipient; + struct t_cose_encrypt_dec_ctx decrypt_context; + struct t_cose_key kek; + struct q_useful_buf_c kek_bytes; + Q_USEFUL_BUF_MAKE_STACK_UB( decrypted_buffer, 1024); + struct q_useful_buf_c decrypted_payload; + struct t_cose_parameter *params; + + kek_bytes = Q_USEFUL_BUF_FROM_SZ_LITERAL("128-bit key xxxx"); + result = t_cose_key_init_symmetric(T_COSE_ALGORITHM_A128KW, + kek_bytes, + &kek); + if(result != T_COSE_SUCCESS) { + return_value = 1000 + (int32_t)result; + goto Done2; + } + + t_cose_encrypt_dec_init(&decrypt_context, T_COSE_OPT_MESSAGE_TYPE_ENCRYPT); + t_cose_recipient_dec_keywrap_init(&kw_unwrap_recipient); + t_cose_recipient_dec_keywrap_set_kek(&kw_unwrap_recipient, kek, NULL_Q_USEFUL_BUF_C); + t_cose_encrypt_dec_add_recipient(&decrypt_context, (struct t_cose_recipient_dec *)&kw_unwrap_recipient); + + result = t_cose_encrypt_dec(&decrypt_context, + cose_encrypt_buffer, + NULL_Q_USEFUL_BUF_C, + decrypted_buffer, + &decrypted_payload, + ¶ms); + + if(result != T_COSE_SUCCESS) { + return_value = 2000 + (int32_t)result; + goto Done1; + } + + if(q_useful_buf_compare(decrypted_payload, Q_USEFUL_BUF_FROM_SZ_LITERAL(PAYLOAD))) { + return_value = 3000; + goto Done1; + } + +Done1: + t_cose_key_free_symmetric(kek); +Done2: + return return_value; +} + +int32_t decrypt_known_good_aeskw_non_aead_test(void) +{ + int32_t return_value; + + if(!t_cose_is_algorithm_supported(T_COSE_ALGORITHM_A128KW)) { + /* This is necessary because MbedTLS 2.28 doesn't have + * nist KW enabled by default. The PSA crypto layer deals with + * this dynamically. The below tests will correctly link + * on 2.28, but will fail to run so this exception is needed. + */ + return INT32_MIN; /* Means no testing was actually done */ + } + + return_value = decrypt_key_wrap(UsefulBuf_FROM_BYTE_ARRAY_LITERAL(cose_encrypt_p256_wrap_aesctr)); + if(return_value != 0) { + return return_value + 10000; + } + return_value = decrypt_key_wrap(UsefulBuf_FROM_BYTE_ARRAY_LITERAL(cose_encrypt_p256_wrap_aescbc)); + if(return_value != 0) { + return return_value + 20000; + } + return 0; +} + +#endif /* !T_COSE_DISABLE_KEYWRAP */ + #include "init_keys.h" @@ -317,7 +437,7 @@ int32_t base_encrypt_decrypt_test(void) static int32_t -esdh_enc_dec(int32_t curve) +esdh_enc_dec(int32_t curve, int32_t payload_cose_algorithm_id) { enum t_cose_err_t result; struct t_cose_key privatekey; @@ -356,7 +476,7 @@ esdh_enc_dec(int32_t curve) */ t_cose_encrypt_enc_init(&enc_ctx, T_COSE_OPT_MESSAGE_TYPE_ENCRYPT, - T_COSE_ALGORITHM_A128GCM); + payload_cose_algorithm_id); /* Create the recipient object telling it the algorithm and the public key * for the COSE_Recipient it's going to make. @@ -424,11 +544,32 @@ esdh_enc_dec_test(void) return INT32_MIN; } - result = esdh_enc_dec(T_COSE_ELLIPTIC_CURVE_P_256); + result = esdh_enc_dec(T_COSE_ELLIPTIC_CURVE_P_256, T_COSE_ALGORITHM_A128GCM); if(result) { return result; } - return esdh_enc_dec(T_COSE_ELLIPTIC_CURVE_P_521); + result = esdh_enc_dec(T_COSE_ELLIPTIC_CURVE_P_256, T_COSE_ALGORITHM_A128CTR); + if(result) { + return result; + } + result = esdh_enc_dec(T_COSE_ELLIPTIC_CURVE_P_256, T_COSE_ALGORITHM_A128CBC); + if(result) { + return result; + } + result = esdh_enc_dec(T_COSE_ELLIPTIC_CURVE_P_521, T_COSE_ALGORITHM_A256GCM); + if(result) { + return result; + } + result = esdh_enc_dec(T_COSE_ELLIPTIC_CURVE_P_521, T_COSE_ALGORITHM_A256CTR); + if(result) { + return result; + } + result = esdh_enc_dec(T_COSE_ELLIPTIC_CURVE_P_521, T_COSE_ALGORITHM_A256CBC); + if(result) { + return result; + } + + return 0; } diff --git a/test/t_cose_encrypt_decrypt_test.h b/test/t_cose_encrypt_decrypt_test.h index d281ae01..84df0d22 100644 --- a/test/t_cose_encrypt_decrypt_test.h +++ b/test/t_cose_encrypt_decrypt_test.h @@ -13,6 +13,8 @@ #include +int32_t decrypt_known_good_aeskw_non_aead_test(void); + int32_t base_encrypt_decrypt_test(void); int32_t esdh_enc_dec_test(void);