diff --git a/ChangeLog.d/pkcs7-padding-error-leak.txt b/ChangeLog.d/pkcs7-padding-error-leak.txt new file mode 100644 index 0000000000..5d204d5bef --- /dev/null +++ b/ChangeLog.d/pkcs7-padding-error-leak.txt @@ -0,0 +1,5 @@ +Security + * Fix a timing side channel in CBC-PKCS7 decryption that could + allow an attacker who can submit chosen ciphertexts to recover + some plaintexts through a timing-based padding oracle attack. + Credits to Beat Heeb from Oberon microsystems AG. CVE-TODO diff --git a/library/psa_crypto.c b/library/psa_crypto.c index 9c28609d7e..5f2cad42b7 100644 --- a/library/psa_crypto.c +++ b/library/psa_crypto.c @@ -73,6 +73,8 @@ #include "mbedtls/psa_util.h" #include "mbedtls/threading.h" +#include "constant_time_internal.h" + #if defined(MBEDTLS_PSA_BUILTIN_ALG_HKDF) || \ defined(MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXTRACT) || \ defined(MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXPAND) @@ -4692,13 +4694,27 @@ psa_status_t psa_cipher_finish(psa_cipher_operation_t *operation, output_length); exit: - if (status == PSA_SUCCESS) { - status = psa_cipher_abort(operation); - } else { - *output_length = 0; - (void) psa_cipher_abort(operation); + /* C99 doesn't allow a declaration to follow a label */; + psa_status_t abort_status = psa_cipher_abort(operation); + /* Normally abort shouldn't fail unless the operation is in a bad + * state, in which case we'd expect finish to fail with the same error. + * So it doesn't matter much which call's error code we pick when both + * fail. However, in unauthenticated decryption specifically, the + * distinction between PSA_SUCCESS and PSA_ERROR_INVALID_PADDING is + * security-sensitive (risk of a padding oracle attack), so here we + * must not have a code path that depends on the value of status. */ + if (abort_status != PSA_SUCCESS) { + status = abort_status; } + /* Set *output_length to 0 if status != PSA_SUCCESS, without + * leaking the value of status through a timing side channel + * (status == PSA_ERROR_INVALID_PADDING is sensitive when doing + * unpadded decryption, due to the risk of padding oracle attack). */ + mbedtls_ct_condition_t success = + mbedtls_ct_bool_not(mbedtls_ct_bool(status)); + *output_length = mbedtls_ct_size_if_else_0(success, *output_length); + LOCAL_OUTPUT_FREE(output_external, output); return status; @@ -4841,13 +4857,17 @@ psa_status_t psa_cipher_decrypt(mbedtls_svc_key_id_t key, exit: unlock_status = psa_unregister_read_under_mutex(slot); - if (status == PSA_SUCCESS) { + if (unlock_status != PSA_SUCCESS) { status = unlock_status; } - if (status != PSA_SUCCESS) { - *output_length = 0; - } + /* Set *output_length to 0 if status != PSA_SUCCESS, without + * leaking the value of status through a timing side channel + * (status == PSA_ERROR_INVALID_PADDING is sensitive when doing + * unpadded decryption, due to the risk of padding oracle attack). */ + mbedtls_ct_condition_t success = + mbedtls_ct_bool_not(mbedtls_ct_bool(status)); + *output_length = mbedtls_ct_size_if_else_0(success, *output_length); LOCAL_INPUT_FREE(input_external, input); LOCAL_OUTPUT_FREE(output_external, output); diff --git a/library/psa_crypto_cipher.c b/library/psa_crypto_cipher.c index efc5813ff0..7f691c1d95 100644 --- a/library/psa_crypto_cipher.c +++ b/library/psa_crypto_cipher.c @@ -13,6 +13,7 @@ #include "psa_crypto_cipher.h" #include "psa_crypto_core.h" #include "psa_crypto_random_impl.h" +#include "constant_time_internal.h" #include "mbedtls/cipher.h" #include "mbedtls/error.h" @@ -551,7 +552,19 @@ psa_status_t mbedtls_psa_cipher_finish( uint8_t *output, size_t output_size, size_t *output_length) { psa_status_t status = PSA_ERROR_GENERIC_ERROR; - uint8_t temp_output_buffer[MBEDTLS_MAX_BLOCK_LENGTH]; + size_t invalid_padding = 0; + + /* We will copy output_size bytes from temp_output_buffer to the + * output buffer. We can't use *output_length to determine how + * much to copy because we must not leak that value through timing + * when doing decryption with unpadding. But the underlying function + * is not guaranteed to write beyond *output_length. To ensure we don't + * leak the former content of the stack to the caller, wipe that + * former content. */ + uint8_t temp_output_buffer[MBEDTLS_MAX_BLOCK_LENGTH] = { 0 }; + if (output_size > sizeof(temp_output_buffer)) { + output_size = sizeof(temp_output_buffer); + } if (operation->ctx.cipher.unprocessed_len != 0) { if (operation->alg == PSA_ALG_ECB_NO_PADDING || @@ -562,25 +575,34 @@ psa_status_t mbedtls_psa_cipher_finish( } status = mbedtls_to_psa_error( - mbedtls_cipher_finish(&operation->ctx.cipher, - temp_output_buffer, - output_length)); + mbedtls_cipher_finish_padded(&operation->ctx.cipher, + temp_output_buffer, + output_length, + &invalid_padding)); if (status != PSA_SUCCESS) { goto exit; } - if (*output_length == 0) { + if (output_size == 0) { ; /* Nothing to copy. Note that output may be NULL in this case. */ - } else if (output_size >= *output_length) { - memcpy(output, temp_output_buffer, *output_length); } else { - status = PSA_ERROR_BUFFER_TOO_SMALL; + /* Do not use the value of *output_length to determine how much + * to copy. When decrypting a padded cipher, the output length is + * sensitive, and leaking it could allow a padding oracle attack. */ + memcpy(output, temp_output_buffer, output_size); } + status = mbedtls_ct_error_if_else_0(invalid_padding, + PSA_ERROR_INVALID_PADDING); + mbedtls_ct_condition_t buffer_too_small = + mbedtls_ct_uint_lt(output_size, *output_length); + status = mbedtls_ct_error_if(buffer_too_small, + PSA_ERROR_BUFFER_TOO_SMALL, + status); + exit: mbedtls_platform_zeroize(temp_output_buffer, sizeof(temp_output_buffer)); - return status; } @@ -701,17 +723,21 @@ psa_status_t mbedtls_psa_cipher_decrypt( &operation, mbedtls_buffer_offset(output, accumulated_length), output_size - accumulated_length, &olength); - if (status != PSA_SUCCESS) { - goto exit; - } *output_length = accumulated_length + olength; exit: - if (status == PSA_SUCCESS) { - status = mbedtls_psa_cipher_abort(&operation); - } else { - mbedtls_psa_cipher_abort(&operation); + /* C99 doesn't allow a declaration to follow a label */; + psa_status_t abort_status = mbedtls_psa_cipher_abort(&operation); + /* Normally abort shouldn't fail unless the operation is in a bad + * state, in which case we'd expect finish to fail with the same error. + * So it doesn't matter much which call's error code we pick when both + * fail. However, in unauthenticated decryption specifically, the + * distinction between PSA_SUCCESS and PSA_ERROR_INVALID_PADDING is + * security-sensitive (risk of a padding oracle attack), so here we + * must not have a code path that depends on the value of status. */ + if (abort_status != PSA_SUCCESS) { + status = abort_status; } return status; diff --git a/tests/suites/test_suite_psa_crypto_constant_time.data b/tests/suites/test_suite_psa_crypto_constant_time.data new file mode 100644 index 0000000000..9b4c64e6c9 --- /dev/null +++ b/tests/suites/test_suite_psa_crypto_constant_time.data @@ -0,0 +1,39 @@ +CT encrypt CHACHA20 +depends_on:PSA_WANT_ALG_STREAM_CIPHER:PSA_WANT_KEY_TYPE_CHACHA20 +ct_cipher_encrypt:PSA_ALG_STREAM_CIPHER:PSA_KEY_TYPE_CHACHA20:"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f":"000000000000004a00000000":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000":"af051e40bba0354981329a806a140eafd258a22a6dcb4bb9f6569cb3efe2deaf837bd87ca20b5ba12081a306af0eb35c41a239d20dfc74c81771560d9c9c1e4b224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf788a3b0aa372600a92b57974cded2b9334794cba40c63e34cdea212c4cf07d41b769a6749f3f630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53ac40c5945398b6eda1a832c89c167eacd901d7e2bf363" + +CT encrypt AES-CTR +depends_on:PSA_WANT_ALG_CTR:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_encrypt:PSA_ALG_CTR:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"dd3b5e5319b7591daab1e1a92687feb2":"396ee84fb75fdbb5c2b13c7fe5a654aa" + +CT encrypt AES-CBC-nopad +depends_on:PSA_WANT_ALG_CBC_NO_PADDING:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_encrypt:PSA_ALG_CBC_NO_PADDING:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"49e4e66c89a86b67758df89db9ad6955":"396ee84fb75fdbb5c2b13c7fe5a654aa" + +CT encrypt AES-CBC-PKCS7 +depends_on:PSA_WANT_ALG_CBC_PKCS7:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_encrypt:PSA_ALG_CBC_PKCS7:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"6bc1bee22e409f96e93d7e117393172a":"a076ec9dfbe47d52afc357336f20743bca7e8a15dc3c776436314293031cd4f3" + +CT decrypt CHACHA20 +depends_on:PSA_WANT_ALG_STREAM_CIPHER:PSA_WANT_KEY_TYPE_CHACHA20 +ct_cipher_decrypt:PSA_ALG_STREAM_CIPHER:PSA_KEY_TYPE_CHACHA20:"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f":"000000000000004a00000000":"af051e40bba0354981329a806a140eafd258a22a6dcb4bb9f6569cb3efe2deaf837bd87ca20b5ba12081a306af0eb35c41a239d20dfc74c81771560d9c9c1e4b224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf788a3b0aa372600a92b57974cded2b9334794cba40c63e34cdea212c4cf07d41b769a6749f3f630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53ac40c5945398b6eda1a832c89c167eacd901d7e2bf363":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000":0 + +CT decrypt AES-CTR +depends_on:PSA_WANT_ALG_CTR:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_decrypt:PSA_ALG_CTR:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"396ee84fb75fdbb5c2b13c7fe5a654aa":"dd3b5e5319b7591daab1e1a92687feb2":0 + +CT decrypt AES-CBC-nopad +depends_on:PSA_WANT_ALG_CBC_NO_PADDING:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_decrypt:PSA_ALG_CBC_NO_PADDING:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"396ee84fb75fdbb5c2b13c7fe5a654aa":"49e4e66c89a86b67758df89db9ad6955":0 + +CT decrypt AES-CBC-PKCS7 good +depends_on:PSA_WANT_ALG_CBC_PKCS7:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_decrypt:PSA_ALG_CBC_PKCS7:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"a076ec9dfbe47d52afc357336f20743bca7e8a15dc3c776436314293031cd4f3":"6bc1bee22e409f96e93d7e117393172a":0 + +CT decrypt AES-CBC-PKCS7 invalid padding @0 +depends_on:PSA_WANT_ALG_CBC_PKCS7:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_decrypt:PSA_ALG_CBC_PKCS7:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"a076ec9dfbe47d52afc357336f20743bf42ddf64c420325affb343d5d5f5d5dc":"6bc1bee22e409f96e93d7e117393172a":1 + +CT decrypt AES-CBC-PKCS7 invalid padding @16 +depends_on:PSA_WANT_ALG_CBC_PKCS7:PSA_WANT_KEY_TYPE_AES:HAVE_CONSTANT_TIME_AES +ct_cipher_decrypt:PSA_ALG_CBC_PKCS7:PSA_KEY_TYPE_AES:"2b7e151628aed2a6abf7158809cf4f3c":"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a":"a076ec9dfbe47d52afc357336f20743ba3d6a86d0a9d172eeb1b754512d04416":"6bc1bee22e409f96e93d7e117393172a":1 diff --git a/tests/suites/test_suite_psa_crypto_constant_time.function b/tests/suites/test_suite_psa_crypto_constant_time.function new file mode 100644 index 0000000000..740b8d7d18 --- /dev/null +++ b/tests/suites/test_suite_psa_crypto_constant_time.function @@ -0,0 +1,310 @@ +/* BEGIN_HEADER */ +/* Positive test cases for PSA crypto APIs that assert constant-time + * (more accurately constant-flow) behavior. */ + +#include +#include + +/* Our software AES implementation is not constant-time. For constant-time + * testing involving AES, require a hardware-assisted AES that is + * constant-time. + * + * We assume that if the hardware-assisted version is available in the build, + * it will be available at runtime. The AES tests will fail if run on a + * processor without AESNI/AESCE. + */ +#include "aesce.h" +#include "aesni.h" +#if defined(MBEDTLS_AESCE_HAVE_CODE) || defined(MBEDTLS_AESNI_HAVE_CODE) +#define HAVE_CONSTANT_TIME_AES +#endif + +static int ct_cipher_multipart(psa_cipher_operation_t *operation, + const data_t *iv, + const data_t *input, + size_t output_size, + const data_t *expected_output, + psa_status_t expected_finish_status) +{ + unsigned char *output = NULL; + size_t update_length = SIZE_MAX; + size_t finish_length = SIZE_MAX; + psa_status_t status; + int ok = 0; + + TEST_CALLOC(output, output_size); + + PSA_ASSERT(psa_cipher_set_iv(operation, iv->x, iv->len)); + status = psa_cipher_update(operation, + input->x, input->len, + output, output_size, &update_length); + if (expected_finish_status == PSA_ERROR_BUFFER_TOO_SMALL && + status == PSA_ERROR_BUFFER_TOO_SMALL) { + /* The output buffer is already too small for update. That's ok. */ + ok = 1; + goto exit; + } else { + PSA_ASSERT(status); + } + TEST_LE_U(update_length, output_size); + TEST_EQUAL(psa_cipher_finish(operation, + output + update_length, + output_size - update_length, + &finish_length), + expected_finish_status); + + TEST_CF_PUBLIC(output, output_size); + if (expected_finish_status == PSA_SUCCESS) { + TEST_MEMORY_COMPARE(expected_output->x, expected_output->len, + output, update_length + finish_length); + } + ok = 1; + +exit: + mbedtls_free(output); + psa_cipher_abort(operation); + return ok; +} + +static int ct_cipher_decrypt_oneshot(mbedtls_svc_key_id_t key, + psa_algorithm_t alg, + const data_t *input, + size_t output_size, + const data_t *expected_output, + psa_status_t expected_status) +{ + unsigned char *output = NULL; + size_t output_length = SIZE_MAX; + int ok = 0; + + TEST_CALLOC(output, output_size); + + TEST_EQUAL(psa_cipher_decrypt(key, alg, + input->x, input->len, + output, output_size, &output_length), + expected_status); + + TEST_CF_PUBLIC(output, output_size); + if (expected_status == PSA_SUCCESS) { + TEST_MEMORY_COMPARE(expected_output->x, expected_output->len, + output, output_length); + } + ok = 1; + +exit: + mbedtls_free(output); + return ok; +} + +/* END_HEADER */ + +/* BEGIN_DEPENDENCIES + * depends_on:MBEDTLS_PSA_CRYPTO_C + * END_DEPENDENCIES + */ + +/* BEGIN_CASE */ +/* Known answer test for cipher multipart encryption. + * There is no known answer test for one-shot encryption because that + * uses a random IV. */ +void ct_cipher_encrypt(int alg_arg, + int key_type_arg, const data_t *key_data, + const data_t *iv, + const data_t *plaintext, + const data_t *expected_ciphertext) +{ + psa_key_type_t key_type = key_type_arg; + psa_algorithm_t alg = alg_arg; + size_t sufficient_output_size = + PSA_CIPHER_ENCRYPT_OUTPUT_SIZE(key_type, alg, plaintext->len); + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + mbedtls_svc_key_id_t key = MBEDTLS_SVC_KEY_ID_INIT; + psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; + + PSA_INIT(); + TEST_CF_SECRET(key_data->x, key_data->len); + TEST_CF_SECRET(plaintext->x, plaintext->len); + //TEST_ASSERT(key_data->x[0] != 42); // uncomment to trip constant-flow test + + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&attributes, alg); + psa_set_key_type(&attributes, key_type); + PSA_ASSERT(psa_import_key(&attributes, key_data->x, key_data->len, &key)); + + /* Output buffer too small for the actual output */ + mbedtls_test_set_step(1); + PSA_ASSERT(psa_cipher_encrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, plaintext, + expected_ciphertext->len - 1, + expected_ciphertext, + PSA_ERROR_BUFFER_TOO_SMALL)) { + goto exit; + } + + if (expected_ciphertext->len < sufficient_output_size) { + /* For a buffer of intermediate size (between the actual output length + * and the guaranteed sufficient size), either PSA_SUCCESS or + * PSA_ERROR_BUFFER_TOO_SMALL is acceptable. Require what the our + * built-in implementation currently does. */ + psa_status_t intermediate_size_status = PSA_SUCCESS; + + /* Output buffer size just large enough for the actual output + * but less than the guaranteed sufficient size */ + mbedtls_test_set_step(2); + PSA_ASSERT(psa_cipher_encrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, plaintext, + expected_ciphertext->len, + expected_ciphertext, + intermediate_size_status)) { + goto exit; + } + + /* Output buffer size large enough for the actual output + * but one less than the guaranteed sufficient size */ + mbedtls_test_set_step(3); + PSA_ASSERT(psa_cipher_encrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, plaintext, + sufficient_output_size - 1, + expected_ciphertext, + intermediate_size_status)) { + goto exit; + } + } + + /* Guaranteed sufficient output buffer size */ + mbedtls_test_set_step(4); + PSA_ASSERT(psa_cipher_encrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, plaintext, + sufficient_output_size, + expected_ciphertext, + PSA_SUCCESS)) { + goto exit; + } + +exit: + psa_cipher_abort(&operation); + psa_destroy_key(key); + PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE */ +/* Known answer for cipher decryption (one-shot and multipart). + * Supports good cases and invalid padding cases. */ +void ct_cipher_decrypt(int alg_arg, + int key_type_arg, const data_t *key_data, + const data_t *iv, + const data_t *ciphertext, + const data_t *expected_plaintext, + int expect_invalid_padding) +{ + psa_key_type_t key_type = key_type_arg; + psa_algorithm_t alg = alg_arg; + size_t sufficient_output_size = + PSA_CIPHER_DECRYPT_OUTPUT_SIZE(key_type, alg, ciphertext->len); + psa_status_t expected_status = + expect_invalid_padding ? PSA_ERROR_INVALID_PADDING : PSA_SUCCESS; + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + mbedtls_svc_key_id_t key = MBEDTLS_SVC_KEY_ID_INIT; + psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; + data_t input = { NULL, iv->len + ciphertext->len }; + + PSA_INIT(); + TEST_CF_SECRET(key_data->x, key_data->len); + TEST_CF_SECRET(ciphertext->x, ciphertext->len); + //TEST_ASSERT(key_data->x[0] != 42); // uncomment to trip constant-flow test + + TEST_CALLOC(input.x, input.len); + memcpy(input.x, iv->x, iv->len); + memcpy(input.x + iv->len, ciphertext->x, ciphertext->len); + + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attributes, alg); + psa_set_key_type(&attributes, key_type); + PSA_ASSERT(psa_import_key(&attributes, key_data->x, key_data->len, &key)); + + /* Output buffer too small for the actual output */ + mbedtls_test_set_step(1); + PSA_ASSERT(psa_cipher_decrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, ciphertext, + expected_plaintext->len - 1, + expected_plaintext, + PSA_ERROR_BUFFER_TOO_SMALL)) { + goto exit; + } + if (!ct_cipher_decrypt_oneshot(key, alg, &input, + expected_plaintext->len - 1, + expected_plaintext, + PSA_ERROR_BUFFER_TOO_SMALL)) { + goto exit; + } + + if (expected_plaintext->len < sufficient_output_size) { + /* For a buffer of intermediate size (between the actual output length + * and the guaranteed sufficient size), either PSA_SUCCESS (or + * PSA_ERROR_INVALID_PADDING if the padding is invalid) or + * PSA_ERROR_BUFFER_TOO_SMALL is acceptable. Require what the our + * built-in implementation currently does. */ + psa_status_t intermediate_size_status = expected_status; + if (alg == PSA_ALG_CBC_PKCS7) { + intermediate_size_status = PSA_ERROR_BUFFER_TOO_SMALL; + } + + /* Output buffer size just large enough for the actual output + * but less than the guaranteed sufficient size */ + mbedtls_test_set_step(2); + PSA_ASSERT(psa_cipher_decrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, ciphertext, + expected_plaintext->len, + expected_plaintext, + intermediate_size_status)) { + goto exit; + } + if (!ct_cipher_decrypt_oneshot(key, alg, &input, + expected_plaintext->len - 1, + expected_plaintext, + intermediate_size_status)) { + goto exit; + } + + /* Output buffer size large enough for the actual output + * but one less than the guaranteed sufficient size */ + mbedtls_test_set_step(3); + PSA_ASSERT(psa_cipher_decrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, ciphertext, + sufficient_output_size - 1, + expected_plaintext, + intermediate_size_status)) { + goto exit; + } + if (!ct_cipher_decrypt_oneshot(key, alg, &input, + sufficient_output_size - 1, + expected_plaintext, + intermediate_size_status)) { + goto exit; + } + } + + /* Guaranteed sufficient output buffer size */ + mbedtls_test_set_step(4); + PSA_ASSERT(psa_cipher_decrypt_setup(&operation, key, alg)); + if (!ct_cipher_multipart(&operation, iv, ciphertext, + sufficient_output_size, + expected_plaintext, + expected_status)) { + goto exit; + } + if (!ct_cipher_decrypt_oneshot(key, alg, &input, + sufficient_output_size, + expected_plaintext, + expected_status)) { + goto exit; + } + +exit: + mbedtls_free(input.x); + psa_cipher_abort(&operation); + psa_destroy_key(key); + PSA_DONE(); +} +/* END_CASE */