psa_cipher_update with AES-CTR fails when used in-place for a non-block-aligned size #63

Open
opened 2019-02-20 08:03:01 -06:00 by gilles-peskine-arm · 4 comments
gilles-peskine-arm commented 2019-02-20 08:03:01 -06:00 (Migrated from github.com)

Description

Expected behavior: a call to psa_cipher_update on AES-CTR used with the nominal sequence of operations succeeds to encrypt 36 bytes.

Actual behavior: psa_cipher_update returns PSA_ERROR_INVALID_ARGUMENT.

Reported internally by @davidsaada, encountered with mbedcrypto-1.0.0d2 on Mbed OS. I reproduced it with the latest development (72f40c6686) on Linux.

#include <psa/crypto.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int encrypt_test()
{
    const int enc_block_size = 16;

    psa_key_handle_t enc_handle;
    psa_cipher_operation_t operation;
    char *data = "Data value of key 3 is the following";
    size_t data_size = strlen(data);
    unsigned char encrypt_key[enc_block_size];
    unsigned char iv_buf[enc_block_size];
    psa_key_policy_t policy = PSA_KEY_POLICY_INIT;
    size_t out_len;

    memset(encrypt_key, 0xa5, enc_block_size);
    memset(iv_buf, 0, enc_block_size);
    iv_buf[0] = 1;

    psa_status_t psa_ret = psa_crypto_init();
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_allocate_key(&enc_handle);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_key_policy_set_usage(&policy, PSA_KEY_USAGE_ENCRYPT, PSA_ALG_CTR);
    psa_ret = psa_set_key_policy(enc_handle, &policy);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_import_key(enc_handle, PSA_KEY_TYPE_AES, encrypt_key, enc_block_size);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    memset(&operation, 0, sizeof(operation));
    psa_ret = psa_cipher_encrypt_setup(&operation, enc_handle, PSA_ALG_CTR);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_cipher_set_iv(&operation, iv_buf, enc_block_size);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_cipher_update(&operation, (const unsigned char *) data, data_size, (unsigned char *) data, data_size, &out_len);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_cipher_abort(&operation);
    psa_destroy_key(enc_handle);
    return PSA_SUCCESS;

fail:
    printf("Operation failed. err %d\n", psa_ret);
    return psa_ret;
}


int main(void)
{
    return !!encrypt_test();
}

Issue request type

[ ] Question
[ ] Enhancement
[x] Bug
<!-- ************************************** WARNING ************************************** The ciarcom bot parses this header automatically. Any deviation from the template may cause the bot to automatically correct this header or may result in a warning message, requesting updates. Please ensure that nothing follows the Issue request type section, all issue details are within the Description section and no changes are made to the template format (as detailed below). ************************************************************************************* --> ### Description Expected behavior: a call to `psa_cipher_update` on AES-CTR used with the nominal sequence of operations succeeds to encrypt 36 bytes. Actual behavior: `psa_cipher_update` returns `PSA_ERROR_INVALID_ARGUMENT`. Reported internally by @davidsaada, encountered with mbedcrypto-1.0.0d2 on Mbed OS. I reproduced it with the latest development (72f40c6686f4276dd17c0f68e4dc4d374b74b4ca) on Linux. ```c #include <psa/crypto.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int encrypt_test() { const int enc_block_size = 16; psa_key_handle_t enc_handle; psa_cipher_operation_t operation; char *data = "Data value of key 3 is the following"; size_t data_size = strlen(data); unsigned char encrypt_key[enc_block_size]; unsigned char iv_buf[enc_block_size]; psa_key_policy_t policy = PSA_KEY_POLICY_INIT; size_t out_len; memset(encrypt_key, 0xa5, enc_block_size); memset(iv_buf, 0, enc_block_size); iv_buf[0] = 1; psa_status_t psa_ret = psa_crypto_init(); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_allocate_key(&enc_handle); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_key_policy_set_usage(&policy, PSA_KEY_USAGE_ENCRYPT, PSA_ALG_CTR); psa_ret = psa_set_key_policy(enc_handle, &policy); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_import_key(enc_handle, PSA_KEY_TYPE_AES, encrypt_key, enc_block_size); if (psa_ret != PSA_SUCCESS) { goto fail; } memset(&operation, 0, sizeof(operation)); psa_ret = psa_cipher_encrypt_setup(&operation, enc_handle, PSA_ALG_CTR); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_cipher_set_iv(&operation, iv_buf, enc_block_size); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_cipher_update(&operation, (const unsigned char *) data, data_size, (unsigned char *) data, data_size, &out_len); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_cipher_abort(&operation); psa_destroy_key(enc_handle); return PSA_SUCCESS; fail: printf("Operation failed. err %d\n", psa_ret); return psa_ret; } int main(void) { return !!encrypt_test(); } ``` ### Issue request type <!-- Required Please add only one X to one of the following types. Do not fill multiple types (split the issue otherwise.) Please note this is not a GitHub task list, indenting the boxes or changing the format to add a '.' or '*' in front of them would change the meaning incorrectly. The only changes to be made are to add a description text under the description heading and to add a 'x' to the correct box. --> [ ] Question [ ] Enhancement [x] Bug
gilles-peskine-arm commented 2019-02-20 08:15:14 -06:00 (Migrated from github.com)

Feeding 36 bytes of input to cipher_encrypt in test_suite_psa_crypto passes.

In the failing program, changing data to be a larger buffer doesn't help. But changing the output to be a separate buffer does. Keeping in-place operation but changing the data size to 32 also works.

So the problem is that psa_cipher_update doesn't always work in-place. This is something that the PSA specification guarantees, but Mbed Crypto is known not to support, because Mbed TLS doesn't. We do however expect that in-place operation should work in cases where it does with Mbed TLS, which is the case here.

Feeding 36 bytes of input to `cipher_encrypt` in `test_suite_psa_crypto` passes. In the failing program, changing `data` to be a larger buffer doesn't help. But changing the output to be a separate buffer does. Keeping in-place operation but changing the data size to 32 also works. So the problem is that `psa_cipher_update` doesn't always work in-place. This is something that the PSA specification guarantees, but Mbed Crypto is known not to support, because Mbed TLS doesn't. We do however expect that in-place operation should work in cases where it does with Mbed TLS, which is the case here.
ciarmcom commented 2019-02-20 15:33:51 -06:00 (Migrated from github.com)

Internal Jira reference: https://jira.arm.com/browse/IOTCRYPT-696

*Internal Jira reference: https://jira.arm.com/browse/IOTCRYPT-696*
davidsaada commented 2019-02-21 04:38:30 -06:00 (Migrated from github.com)

@gilles-peskine-arm Thank you for opening this issue. Trying to take this a bit further, it means that non aligned fragments of a streamed encryption/decryption sequence should work (either in place or not).

For that matter, the previous example code can be stretched to show how it should work:

int encrypt_test()
{
    const int enc_block_size = 16;

    psa_key_handle_t enc_handle;
    psa_cipher_operation_t operation;
    char *data = "Data value of key 3 is the following";
    size_t data_size = strlen(data);
    char *enc_data = (char *) malloc(data_size + 1);
    unsigned char encrypt_key[enc_block_size];
    unsigned char iv_buf[enc_block_size];
    psa_key_policy_t policy = PSA_KEY_POLICY_INIT;
    size_t out_len;

    memset(encrypt_key, 0xa5, enc_block_size);
    memset(iv_buf, 0, enc_block_size);
    iv_buf[0] = 1;

    psa_status_t psa_ret = psa_crypto_init();
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    // Encryption part (not in place)

    psa_ret = psa_allocate_key(&enc_handle);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_key_policy_set_usage(&policy, PSA_KEY_USAGE_ENCRYPT, PSA_ALG_CTR);
    psa_ret = psa_set_key_policy(enc_handle, &policy);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_import_key(enc_handle, PSA_KEY_TYPE_AES, encrypt_key, enc_block_size);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    memset(&operation, 0, sizeof(operation));
    psa_ret = psa_cipher_encrypt_setup(&operation, enc_handle, PSA_ALG_CTR);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_cipher_set_iv(&operation, iv_buf, enc_block_size);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_cipher_update(&operation, (const unsigned char *) data, 13, (unsigned char *) enc_data, 13, &out_len);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_cipher_update(&operation, (const unsigned char *) data + 13, data_size - 13, (unsigned char *) enc_data + 13, data_size - 13, &out_len);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_cipher_abort(&operation);
    psa_destroy_key(enc_handle);



    // Decryption part (in place)

    psa_ret = psa_allocate_key(&enc_handle);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_key_policy_set_usage(&policy, PSA_KEY_USAGE_DECRYPT, PSA_ALG_CTR);
    psa_ret = psa_set_key_policy(enc_handle, &policy);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_import_key(enc_handle, PSA_KEY_TYPE_AES, encrypt_key, enc_block_size);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    memset(&operation, 0, sizeof(operation));
    psa_ret = psa_cipher_decrypt_setup(&operation, enc_handle, PSA_ALG_CTR);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_cipher_set_iv(&operation, iv_buf, enc_block_size);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_cipher_update(&operation, (const unsigned char *) enc_data, 25, (unsigned char *) enc_data, 25, &out_len);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }

    psa_ret = psa_cipher_update(&operation, (const unsigned char *) enc_data + 25, data_size - 25, (unsigned char *) enc_data + 25, data_size - 25, &out_len);
    if (psa_ret != PSA_SUCCESS) {
        goto fail;
    }


    psa_cipher_abort(&operation);
    psa_destroy_key(enc_handle);

    if (memcmp(data, enc_data, data_size)) {
        printf("Buffers after encryption and decryption don't match.\n");
        psa_ret = PSA_ERROR_UNKNOWN_ERROR;
    }

    free(enc_data);
    return PSA_SUCCESS;

fail:
    free(enc_data);
    printf("Operation failed. err %d\n", psa_ret);
    return psa_ret;
}
@gilles-peskine-arm Thank you for opening this issue. Trying to take this a bit further, it means that non aligned fragments of a streamed encryption/decryption sequence should work (either in place or not). For that matter, the previous example code can be stretched to show how it should work: ``` int encrypt_test() { const int enc_block_size = 16; psa_key_handle_t enc_handle; psa_cipher_operation_t operation; char *data = "Data value of key 3 is the following"; size_t data_size = strlen(data); char *enc_data = (char *) malloc(data_size + 1); unsigned char encrypt_key[enc_block_size]; unsigned char iv_buf[enc_block_size]; psa_key_policy_t policy = PSA_KEY_POLICY_INIT; size_t out_len; memset(encrypt_key, 0xa5, enc_block_size); memset(iv_buf, 0, enc_block_size); iv_buf[0] = 1; psa_status_t psa_ret = psa_crypto_init(); if (psa_ret != PSA_SUCCESS) { goto fail; } // Encryption part (not in place) psa_ret = psa_allocate_key(&enc_handle); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_key_policy_set_usage(&policy, PSA_KEY_USAGE_ENCRYPT, PSA_ALG_CTR); psa_ret = psa_set_key_policy(enc_handle, &policy); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_import_key(enc_handle, PSA_KEY_TYPE_AES, encrypt_key, enc_block_size); if (psa_ret != PSA_SUCCESS) { goto fail; } memset(&operation, 0, sizeof(operation)); psa_ret = psa_cipher_encrypt_setup(&operation, enc_handle, PSA_ALG_CTR); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_cipher_set_iv(&operation, iv_buf, enc_block_size); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_cipher_update(&operation, (const unsigned char *) data, 13, (unsigned char *) enc_data, 13, &out_len); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_cipher_update(&operation, (const unsigned char *) data + 13, data_size - 13, (unsigned char *) enc_data + 13, data_size - 13, &out_len); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_cipher_abort(&operation); psa_destroy_key(enc_handle); // Decryption part (in place) psa_ret = psa_allocate_key(&enc_handle); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_key_policy_set_usage(&policy, PSA_KEY_USAGE_DECRYPT, PSA_ALG_CTR); psa_ret = psa_set_key_policy(enc_handle, &policy); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_import_key(enc_handle, PSA_KEY_TYPE_AES, encrypt_key, enc_block_size); if (psa_ret != PSA_SUCCESS) { goto fail; } memset(&operation, 0, sizeof(operation)); psa_ret = psa_cipher_decrypt_setup(&operation, enc_handle, PSA_ALG_CTR); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_cipher_set_iv(&operation, iv_buf, enc_block_size); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_cipher_update(&operation, (const unsigned char *) enc_data, 25, (unsigned char *) enc_data, 25, &out_len); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_ret = psa_cipher_update(&operation, (const unsigned char *) enc_data + 25, data_size - 25, (unsigned char *) enc_data + 25, data_size - 25, &out_len); if (psa_ret != PSA_SUCCESS) { goto fail; } psa_cipher_abort(&operation); psa_destroy_key(enc_handle); if (memcmp(data, enc_data, data_size)) { printf("Buffers after encryption and decryption don't match.\n"); psa_ret = PSA_ERROR_UNKNOWN_ERROR; } free(enc_data); return PSA_SUCCESS; fail: free(enc_data); printf("Operation failed. err %d\n", psa_ret); return psa_ret; } ```
davidsaada commented 2019-02-21 09:38:30 -06:00 (Migrated from github.com)

Sorry - please ignore my last message here. Figure that it was wrong, unless it's a streaming algorithm.

Sorry - please ignore my last message here. Figure that it was wrong, unless it's a streaming algorithm.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-ARMmbed/mbed-crypto#63
No description provided.