Merge pull request #1119 from davidhorstmann-arm/psa-buffer-copy-fn

Implement buffer copying functions for PSA crypto
diff --git a/library/psa_crypto.c b/library/psa_crypto.c
index bbd6b24..9e9dfdb 100644
--- a/library/psa_crypto.c
+++ b/library/psa_crypto.c
@@ -8428,4 +8428,148 @@
 }
 #endif /* PSA_WANT_ALG_SOME_PAKE */
 
+
+/** Copy from an input buffer to a local copy.
+ *
+ * \param[in] input             Pointer to input buffer.
+ * \param[in] input_len         Length of the input buffer.
+ * \param[out] input_copy       Pointer to a local copy in which to store the input data.
+ * \param[out] input_copy_len   Length of the local copy buffer.
+ * \return                      #PSA_SUCCESS, if the buffer was successfully
+ *                              copied.
+ * \return                      #PSA_ERROR_CORRUPTION_DETECTED, if the local
+ *                              copy is too small to hold contents of the
+ *                              input buffer.
+ */
+MBEDTLS_STATIC_TESTABLE
+psa_status_t psa_crypto_copy_input(const uint8_t *input, size_t input_len,
+                                   uint8_t *input_copy, size_t input_copy_len)
+{
+    if (input_len > input_copy_len) {
+        return PSA_ERROR_CORRUPTION_DETECTED;
+    }
+
+    if (input_len > 0) {
+        memcpy(input_copy, input, input_len);
+    }
+
+    return PSA_SUCCESS;
+}
+
+/** Copy from a local output buffer into a user-supplied one.
+ *
+ * \param[in] output_copy       Pointer to a local buffer containing the output.
+ * \param[in] output_copy_len   Length of the local buffer.
+ * \param[out] output           Pointer to user-supplied output buffer.
+ * \param[out] output_len       Length of the user-supplied output buffer.
+ * \return                      #PSA_SUCCESS, if the buffer was successfully
+ *                              copied.
+ * \return                      #PSA_ERROR_BUFFER_TOO_SMALL, if the
+ *                              user-supplied output buffer is too small to
+ *                              hold the contents of the local buffer.
+ */
+MBEDTLS_STATIC_TESTABLE
+psa_status_t psa_crypto_copy_output(const uint8_t *output_copy, size_t output_copy_len,
+                                    uint8_t *output, size_t output_len)
+{
+    if (output_len < output_copy_len) {
+        return PSA_ERROR_BUFFER_TOO_SMALL;
+    }
+
+    if (output_copy_len > 0) {
+        memcpy(output, output_copy, output_copy_len);
+    }
+
+    return PSA_SUCCESS;
+}
+
+psa_status_t psa_crypto_local_input_alloc(const uint8_t *input, size_t input_len,
+                                          psa_crypto_local_input_t *local_input)
+{
+    psa_status_t status;
+
+    *local_input = PSA_CRYPTO_LOCAL_INPUT_INIT;
+
+    if (input_len == 0) {
+        return PSA_SUCCESS;
+    }
+
+    local_input->buffer = mbedtls_calloc(input_len, 1);
+    if (local_input->buffer == NULL) {
+        /* Since we dealt with the zero-length case above, we know that
+         * a NULL return value means a failure of allocation. */
+        return PSA_ERROR_INSUFFICIENT_MEMORY;
+    }
+    /* From now on, we must free local_input->buffer on error. */
+
+    local_input->length = input_len;
+
+    status = psa_crypto_copy_input(input, input_len,
+                                   local_input->buffer, local_input->length);
+    if (status != PSA_SUCCESS) {
+        goto error;
+    }
+
+    return PSA_SUCCESS;
+
+error:
+    mbedtls_free(local_input->buffer);
+    local_input->buffer = NULL;
+    local_input->length = 0;
+    return status;
+}
+
+void psa_crypto_local_input_free(psa_crypto_local_input_t *local_input)
+{
+    mbedtls_free(local_input->buffer);
+    local_input->buffer = NULL;
+    local_input->length = 0;
+}
+
+psa_status_t psa_crypto_local_output_alloc(uint8_t *output, size_t output_len,
+                                           psa_crypto_local_output_t *local_output)
+{
+    *local_output = PSA_CRYPTO_LOCAL_OUTPUT_INIT;
+
+    if (output_len == 0) {
+        return PSA_SUCCESS;
+    }
+    local_output->buffer = mbedtls_calloc(output_len, 1);
+    if (local_output->buffer == NULL) {
+        /* Since we dealt with the zero-length case above, we know that
+         * a NULL return value means a failure of allocation. */
+        return PSA_ERROR_INSUFFICIENT_MEMORY;
+    }
+    local_output->length = output_len;
+    local_output->original = output;
+
+    return PSA_SUCCESS;
+}
+
+psa_status_t psa_crypto_local_output_free(psa_crypto_local_output_t *local_output)
+{
+    psa_status_t status;
+
+    if (local_output->buffer == NULL) {
+        local_output->length = 0;
+        return PSA_SUCCESS;
+    }
+    if (local_output->original == NULL) {
+        /* We have an internal copy but nothing to copy back to. */
+        return PSA_ERROR_CORRUPTION_DETECTED;
+    }
+
+    status = psa_crypto_copy_output(local_output->buffer, local_output->length,
+                                    local_output->original, local_output->length);
+    if (status != PSA_SUCCESS) {
+        return status;
+    }
+
+    mbedtls_free(local_output->buffer);
+    local_output->buffer = NULL;
+    local_output->length = 0;
+
+    return PSA_SUCCESS;
+}
+
 #endif /* MBEDTLS_PSA_CRYPTO_C */
diff --git a/library/psa_crypto_core.h b/library/psa_crypto_core.h
index d406ce4..687e640 100644
--- a/library/psa_crypto_core.h
+++ b/library/psa_crypto_core.h
@@ -842,4 +842,74 @@
 psa_status_t mbedtls_psa_verify_hash_abort(
     mbedtls_psa_verify_hash_interruptible_operation_t *operation);
 
+typedef struct psa_crypto_local_input_s {
+    uint8_t *buffer;
+    size_t length;
+} psa_crypto_local_input_t;
+
+#define PSA_CRYPTO_LOCAL_INPUT_INIT ((psa_crypto_local_input_t) { NULL, 0 })
+
+/** Allocate a local copy of an input buffer and copy the contents into it.
+ *
+ * \param[in] input             Pointer to input buffer.
+ * \param[in] input_len         Length of the input buffer.
+ * \param[out] local_input      Pointer to a psa_crypto_local_input_t struct
+ *                              containing a local input copy.
+ * \return                      #PSA_SUCCESS, if the buffer was successfully
+ *                              copied.
+ * \return                      #PSA_ERROR_INSUFFICIENT_MEMORY, if a copy of
+ *                              the buffer cannot be allocated.
+ */
+psa_status_t psa_crypto_local_input_alloc(const uint8_t *input, size_t input_len,
+                                          psa_crypto_local_input_t *local_input);
+
+/** Free a local copy of an input buffer.
+ *
+ * \param[in] local_input       Pointer to a psa_crypto_local_input_t struct
+ *                              populated by a previous call to
+ *                              psa_crypto_local_input_alloc().
+ */
+void psa_crypto_local_input_free(psa_crypto_local_input_t *local_input);
+
+typedef struct psa_crypto_local_output_s {
+    uint8_t *original;
+    uint8_t *buffer;
+    size_t length;
+} psa_crypto_local_output_t;
+
+#define PSA_CRYPTO_LOCAL_OUTPUT_INIT ((psa_crypto_local_output_t) { NULL, NULL, 0 })
+
+/** Allocate a local copy of an output buffer.
+ *
+ * \note                        This does not copy any data from the original
+ *                              output buffer but only allocates a buffer
+ *                              whose contents will be copied back to the
+ *                              original in a future call to
+ *                              psa_crypto_local_output_free().
+ *
+ * \param[in] output            Pointer to output buffer.
+ * \param[in] output_len        Length of the output buffer.
+ * \param[out] local_output     Pointer to a psa_crypto_local_output_t struct to
+ *                              populate with the local output copy.
+ * \return                      #PSA_SUCCESS, if the buffer was successfully
+ *                              copied.
+ * \return                      #PSA_ERROR_INSUFFICIENT_MEMORY, if a copy of
+ *                              the buffer cannot be allocated.
+ */
+psa_status_t psa_crypto_local_output_alloc(uint8_t *output, size_t output_len,
+                                           psa_crypto_local_output_t *local_output);
+
+/** Copy from a local copy of an output buffer back to the original, then
+ *  free the local copy.
+ *
+ * \param[in] local_output      Pointer to a psa_crypto_local_output_t struct
+ *                              populated by a previous call to
+ *                              psa_crypto_local_output_alloc().
+ * \return                      #PSA_SUCCESS, if the local output was
+ *                              successfully copied back to the original.
+ * \return                      #PSA_ERROR_CORRUPTION_DETECTED, if the output
+ *                              could not be copied back to the original.
+ */
+psa_status_t psa_crypto_local_output_free(psa_crypto_local_output_t *local_output);
+
 #endif /* PSA_CRYPTO_CORE_H */
diff --git a/library/psa_crypto_invasive.h b/library/psa_crypto_invasive.h
index 8b445a1..6a1181f 100644
--- a/library/psa_crypto_invasive.h
+++ b/library/psa_crypto_invasive.h
@@ -72,6 +72,13 @@
 psa_status_t psa_mac_key_can_do(
     psa_algorithm_t algorithm,
     psa_key_type_t key_type);
+
+psa_status_t psa_crypto_copy_input(const uint8_t *input, size_t input_len,
+                                   uint8_t *input_copy, size_t input_copy_len);
+
+psa_status_t psa_crypto_copy_output(const uint8_t *output_copy, size_t output_copy_len,
+                                    uint8_t *output, size_t output_len);
+
 #endif /* MBEDTLS_TEST_HOOKS && MBEDTLS_PSA_CRYPTO_C */
 
 #endif /* PSA_CRYPTO_INVASIVE_H */
diff --git a/tests/suites/test_suite_psa_crypto.function b/tests/suites/test_suite_psa_crypto.function
index a510f8e..1962f7b 100644
--- a/tests/suites/test_suite_psa_crypto.function
+++ b/tests/suites/test_suite_psa_crypto.function
@@ -13,7 +13,6 @@
 #include "psa/crypto.h"
 #include "psa_crypto_slot_management.h"
 
-/* For psa_can_do_hash() */
 #include "psa_crypto_core.h"
 
 #include "test/asn1_helpers.h"
diff --git a/tests/suites/test_suite_psa_crypto_memory.data b/tests/suites/test_suite_psa_crypto_memory.data
new file mode 100644
index 0000000..2a828f5
--- /dev/null
+++ b/tests/suites/test_suite_psa_crypto_memory.data
@@ -0,0 +1,62 @@
+PSA input buffer copy: straightforward copy
+copy_input:20:20:PSA_SUCCESS
+
+PSA input buffer copy: copy buffer larger than required
+copy_input:10:20:PSA_SUCCESS
+
+PSA input buffer copy: copy buffer too small
+copy_input:20:10:PSA_ERROR_CORRUPTION_DETECTED
+
+PSA input buffer copy: zero-length source buffer
+copy_input:0:10:PSA_SUCCESS
+
+PSA input buffer copy: zero-length both buffers
+copy_input:0:0:PSA_SUCCESS
+
+PSA output buffer copy: straightforward copy
+copy_output:20:20:PSA_SUCCESS
+
+PSA output buffer copy: output buffer larger than required
+copy_output:10:20:PSA_SUCCESS
+
+PSA output buffer copy: output buffer too small
+copy_output:20:10:PSA_ERROR_BUFFER_TOO_SMALL
+
+PSA output buffer copy: zero-length source buffer
+copy_output:0:10:PSA_SUCCESS
+
+PSA output buffer copy: zero-length both buffers
+copy_output:0:0:PSA_SUCCESS
+
+PSA crypto local input alloc
+local_input_alloc:200:PSA_SUCCESS
+
+PSA crypto local input alloc, NULL buffer
+local_input_alloc:0:PSA_SUCCESS
+
+PSA crypto local input free
+local_input_free:200
+
+PSA crypto local input free, NULL buffer
+local_input_free:0
+
+PSA crypto local input round-trip
+local_input_round_trip
+
+PSA crypto local output alloc
+local_output_alloc:200:PSA_SUCCESS
+
+PSA crypto local output alloc, NULL buffer
+local_output_alloc:0:PSA_SUCCESS
+
+PSA crypto local output free
+local_output_free:200:0:PSA_SUCCESS
+
+PSA crypto local output free, NULL buffer
+local_output_free:0:0:PSA_SUCCESS
+
+PSA crypto local output free, NULL original buffer
+local_output_free:200:1:PSA_ERROR_CORRUPTION_DETECTED
+
+PSA crypto local output round-trip
+local_output_round_trip
diff --git a/tests/suites/test_suite_psa_crypto_memory.function b/tests/suites/test_suite_psa_crypto_memory.function
new file mode 100644
index 0000000..2bb0f0d
--- /dev/null
+++ b/tests/suites/test_suite_psa_crypto_memory.function
@@ -0,0 +1,250 @@
+/* BEGIN_HEADER */
+#include <stdint.h>
+
+#include "common.h"
+
+#include "psa/crypto.h"
+
+#include "psa_crypto_core.h"
+#include "psa_crypto_invasive.h"
+
+#include "test/psa_crypto_helpers.h"
+
+/* Helper to fill a buffer with a data pattern. The pattern is not
+ * important, it just allows a basic check that the correct thing has
+ * been written, in a way that will detect an error in offset. */
+static void fill_buffer_pattern(uint8_t *buffer, size_t len)
+{
+    for (size_t i = 0; i < len; i++) {
+        buffer[i] = (uint8_t) (i % 256);
+    }
+}
+/* END_HEADER */
+
+/* BEGIN_DEPENDENCIES
+ * depends_on:MBEDTLS_PSA_CRYPTO_C:MBEDTLS_TEST_HOOKS
+ * END_DEPENDENCIES
+ */
+
+/* BEGIN_CASE */
+void copy_input(int src_len, int dst_len, psa_status_t exp_status)
+{
+    uint8_t *src_buffer = NULL;
+    uint8_t *dst_buffer = NULL;
+    psa_status_t status;
+
+    TEST_CALLOC(src_buffer, src_len);
+    TEST_CALLOC(dst_buffer, dst_len);
+
+    fill_buffer_pattern(src_buffer, src_len);
+
+    status = psa_crypto_copy_input(src_buffer, src_len, dst_buffer, dst_len);
+    TEST_EQUAL(status, exp_status);
+
+    if (exp_status == PSA_SUCCESS) {
+        /* Note: We compare the first src_len bytes of each buffer, as this is what was copied. */
+        TEST_MEMORY_COMPARE(src_buffer, src_len, dst_buffer, src_len);
+    }
+
+exit:
+    mbedtls_free(src_buffer);
+    mbedtls_free(dst_buffer);
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void copy_output(int src_len, int dst_len, psa_status_t exp_status)
+{
+    uint8_t *src_buffer = NULL;
+    uint8_t *dst_buffer = NULL;
+    psa_status_t status;
+
+    TEST_CALLOC(src_buffer, src_len);
+    TEST_CALLOC(dst_buffer, dst_len);
+
+    fill_buffer_pattern(src_buffer, src_len);
+
+    status = psa_crypto_copy_output(src_buffer, src_len, dst_buffer, dst_len);
+    TEST_EQUAL(status, exp_status);
+
+    if (exp_status == PSA_SUCCESS) {
+        /* Note: We compare the first src_len bytes of each buffer, as this is what was copied. */
+        TEST_MEMORY_COMPARE(src_buffer, src_len, dst_buffer, src_len);
+    }
+
+exit:
+    mbedtls_free(src_buffer);
+    mbedtls_free(dst_buffer);
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void local_input_alloc(int input_len, psa_status_t exp_status)
+{
+    uint8_t *input = NULL;
+    psa_crypto_local_input_t local_input;
+    psa_status_t status;
+
+    local_input.buffer = NULL;
+
+    TEST_CALLOC(input, input_len);
+    fill_buffer_pattern(input, input_len);
+
+    status = psa_crypto_local_input_alloc(input, input_len, &local_input);
+    TEST_EQUAL(status, exp_status);
+
+    if (exp_status == PSA_SUCCESS) {
+        if (input_len != 0) {
+            TEST_ASSERT(local_input.buffer != input);
+        }
+        TEST_MEMORY_COMPARE(input, input_len,
+                            local_input.buffer, local_input.length);
+    }
+
+exit:
+    mbedtls_free(local_input.buffer);
+    mbedtls_free(input);
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void local_input_free(int input_len)
+{
+    psa_crypto_local_input_t local_input;
+
+    local_input.buffer = NULL;
+    local_input.length = input_len;
+    TEST_CALLOC(local_input.buffer, local_input.length);
+
+    psa_crypto_local_input_free(&local_input);
+
+    TEST_ASSERT(local_input.buffer == NULL);
+    TEST_EQUAL(local_input.length, 0);
+
+exit:
+    mbedtls_free(local_input.buffer);
+    local_input.buffer = NULL;
+    local_input.length = 0;
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void local_input_round_trip()
+{
+    psa_crypto_local_input_t local_input;
+    uint8_t input[200];
+    psa_status_t status;
+
+    fill_buffer_pattern(input, sizeof(input));
+
+    status = psa_crypto_local_input_alloc(input, sizeof(input), &local_input);
+    TEST_EQUAL(status, PSA_SUCCESS);
+    TEST_MEMORY_COMPARE(local_input.buffer, local_input.length,
+                        input, sizeof(input));
+    TEST_ASSERT(local_input.buffer != input);
+
+    psa_crypto_local_input_free(&local_input);
+    TEST_ASSERT(local_input.buffer == NULL);
+    TEST_EQUAL(local_input.length, 0);
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void local_output_alloc(int output_len, psa_status_t exp_status)
+{
+    uint8_t *output = NULL;
+    psa_crypto_local_output_t local_output;
+    psa_status_t status;
+
+    local_output.buffer = NULL;
+
+    TEST_CALLOC(output, output_len);
+
+    status = psa_crypto_local_output_alloc(output, output_len, &local_output);
+    TEST_EQUAL(status, exp_status);
+
+    if (exp_status == PSA_SUCCESS) {
+        TEST_ASSERT(local_output.original == output);
+        TEST_EQUAL(local_output.length, output_len);
+    }
+
+exit:
+    mbedtls_free(local_output.buffer);
+    local_output.original = NULL;
+    local_output.buffer = NULL;
+    local_output.length = 0;
+    mbedtls_free(output);
+    output = NULL;
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void local_output_free(int output_len, int original_is_null,
+                       psa_status_t exp_status)
+{
+    uint8_t *output = NULL;
+    uint8_t *buffer_copy_for_comparison = NULL;
+    psa_crypto_local_output_t local_output = PSA_CRYPTO_LOCAL_OUTPUT_INIT;
+    psa_status_t status;
+
+    if (!original_is_null) {
+        TEST_CALLOC(output, output_len);
+    }
+    TEST_CALLOC(buffer_copy_for_comparison, output_len);
+    TEST_CALLOC(local_output.buffer, output_len);
+    local_output.length = output_len;
+    local_output.original = output;
+
+    if (local_output.length != 0) {
+        fill_buffer_pattern(local_output.buffer, local_output.length);
+        memcpy(buffer_copy_for_comparison, local_output.buffer, local_output.length);
+    }
+
+    status = psa_crypto_local_output_free(&local_output);
+    TEST_EQUAL(status, exp_status);
+
+    if (exp_status == PSA_SUCCESS) {
+        TEST_ASSERT(local_output.buffer == NULL);
+        TEST_EQUAL(local_output.length, 0);
+        TEST_MEMORY_COMPARE(buffer_copy_for_comparison, output_len,
+                            output, output_len);
+    }
+
+exit:
+    mbedtls_free(output);
+    mbedtls_free(buffer_copy_for_comparison);
+    mbedtls_free(local_output.buffer);
+    local_output.length = 0;
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void local_output_round_trip()
+{
+    psa_crypto_local_output_t local_output;
+    uint8_t output[200];
+    uint8_t *buffer_copy_for_comparison = NULL;
+    psa_status_t status;
+
+    status = psa_crypto_local_output_alloc(output, sizeof(output), &local_output);
+    TEST_EQUAL(status, PSA_SUCCESS);
+    TEST_ASSERT(local_output.buffer != output);
+
+    /* Simulate the function generating output */
+    fill_buffer_pattern(local_output.buffer, local_output.length);
+
+    TEST_CALLOC(buffer_copy_for_comparison, local_output.length);
+    memcpy(buffer_copy_for_comparison, local_output.buffer, local_output.length);
+
+    psa_crypto_local_output_free(&local_output);
+    TEST_ASSERT(local_output.buffer == NULL);
+    TEST_EQUAL(local_output.length, 0);
+
+    /* Check that the buffer was correctly copied back */
+    TEST_MEMORY_COMPARE(output, sizeof(output),
+                        buffer_copy_for_comparison, sizeof(output));
+
+exit:
+    mbedtls_free(buffer_copy_for_comparison);
+}
+/* END_CASE */