From bba5d7c439a35a4aa4244a1b69750e1c14dc9bed Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 27 Jul 2025 18:10:01 +0200 Subject: [PATCH] Add constant-time AES-CBC encrypt and decrypt tests through PSA The main goal is to validate that unpadding is constant-time, including error reporting. Use a separate test function, not annotations in the existing function, so that the functional tests can run on any platform, and we know from test outcomes where we have run the constant-time tests. The tests can only be actually constant-time if AES is constant time, since AES computations are part of what is checked. Thus this requires hardware-accelerated AES. We can't run our AESNI (or AESCE?) code under Msan (it doesn't detect when memory is written from assembly code), so these tests can only be run with Valgrind. Signed-off-by: Gilles Peskine --- .../test_suite_psa_crypto_constant_time.data | 39 +++++ ...st_suite_psa_crypto_constant_time.function | 161 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 tests/suites/test_suite_psa_crypto_constant_time.data create mode 100644 tests/suites/test_suite_psa_crypto_constant_time.function 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..fb8368ad31 --- /dev/null +++ b/tests/suites/test_suite_psa_crypto_constant_time.function @@ -0,0 +1,161 @@ +/* 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 + +/* 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; + 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; + unsigned char *output = NULL; + size_t output_size = PSA_CIPHER_ENCRYPT_OUTPUT_MAX_SIZE(plaintext->len); + size_t update_length = SIZE_MAX; + size_t finish_length = SIZE_MAX; + + PSA_INIT(); + TEST_CALLOC(output, output_size); + 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)); + + PSA_ASSERT(psa_cipher_encrypt_setup(&operation, key, alg)); + PSA_ASSERT(psa_cipher_set_iv(&operation, iv->x, iv->len)); + PSA_ASSERT(psa_cipher_update(&operation, + plaintext->x, plaintext->len, + output, output_size, &update_length)); + TEST_LE_U(update_length, output_size); + PSA_ASSERT(psa_cipher_finish(&operation, + output + update_length, + output_size - update_length, + &finish_length)); + + TEST_CF_PUBLIC(output, output_size); + TEST_MEMORY_COMPARE(expected_ciphertext->x, expected_ciphertext->len, + output, update_length + finish_length); + +exit: + mbedtls_free(output); + 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; + 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; + unsigned char *input = NULL; + unsigned char *output = NULL; + size_t output_size = PSA_CIPHER_DECRYPT_OUTPUT_MAX_SIZE(ciphertext->len); + size_t update_length = SIZE_MAX; + size_t finish_length = SIZE_MAX; + size_t output_length = SIZE_MAX; + psa_status_t status; + + PSA_INIT(); + TEST_CALLOC(output, output_size); + 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 + + 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)); + + PSA_ASSERT(psa_cipher_decrypt_setup(&operation, key, alg)); + PSA_ASSERT(psa_cipher_set_iv(&operation, iv->x, iv->len)); + PSA_ASSERT(psa_cipher_update(&operation, + ciphertext->x, ciphertext->len, + output, output_size, &update_length)); + TEST_LE_U(update_length, output_size); + status = psa_cipher_finish(&operation, + output + update_length, + output_size - update_length, + &finish_length); + TEST_CF_PUBLIC(output, output_size); + + if (expect_invalid_padding) { + TEST_EQUAL(status, PSA_ERROR_INVALID_PADDING); + } else { + TEST_EQUAL(status, PSA_SUCCESS); + TEST_MEMORY_COMPARE(expected_plaintext->x, expected_plaintext->len, + output, update_length + finish_length); + } + + memset(output, 0, output_size); + TEST_CALLOC(input, iv->len + ciphertext->len); + memcpy(input, iv->x, iv->len); + memcpy(input + iv->len, ciphertext->x, ciphertext->len); + status = psa_cipher_decrypt(key, alg, + input, iv->len + ciphertext->len, + output, output_size, &output_length); + TEST_CF_PUBLIC(output, output_size); + + if (expect_invalid_padding) { + TEST_EQUAL(status, PSA_ERROR_INVALID_PADDING); + } else { + TEST_EQUAL(status, PSA_SUCCESS); + TEST_MEMORY_COMPARE(expected_plaintext->x, expected_plaintext->len, + output, output_length); + } + +exit: + mbedtls_free(input); + mbedtls_free(output); + psa_cipher_abort(&operation); + psa_destroy_key(key); + PSA_DONE(); +} +/* END_CASE */