mbedtls_ecp_write_key: document and test smaller output buffer

Document and test the current behavior, even if it is weird:

* For Weierstrass keys, the error is MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL,
  not MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL.
* For Weierstrass keys, a smaller output buffer is ok if the output fits.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
diff --git a/include/mbedtls/ecp.h b/include/mbedtls/ecp.h
index 231832b..047e494 100644
--- a/include/mbedtls/ecp.h
+++ b/include/mbedtls/ecp.h
@@ -1287,8 +1287,8 @@
  * \brief           This function exports an elliptic curve private key.
  *
  * \note            Note that although this function accepts an output
- *                  buffer that is larger than the key, most key import
- *                  interfaces require the output to be trimmed to the
+ *                  buffer that is smaller or larger than the key, most key
+ *                  import interfaces require the output to have exactly
  *                  key's nominal length. It is generally simplest to
  *                  pass the key's nominal length as \c buflen, after
  *                  checking that the output buffer is large enough.
@@ -1305,13 +1305,18 @@
  *                  representation (which is little-endian), padded with
  *                  null bytes at the end to reach \p buflen bytes.
  * \param buflen    The total length of the buffer in bytes.
- *                  The length of the output is always
+ *                  The length of the output is
  *                  (`grp->nbits` + 7) / 8 bytes
  *                  where `grp->nbits` is the private key size in bits.
+ *                  For Weierstrass keys, if the output buffer is smaller,
+ *                  leading zeros are trimmed to fit if possible. For
+ *                  Montgomery keys, the output buffer must always be large
+ *                  enough for the nominal length.
  *
  * \return          \c 0 on success.
- * \return          #MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL if the \p key
-                    representation is larger than the available space in \p buf.
+ * \return          #MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL or
+ *                  #MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL if the \p key
+ *                  representation is larger than the available space in \p buf.
  * \return          Another negative error code on different kinds of failure.
  */
 int mbedtls_ecp_write_key(mbedtls_ecp_keypair *key,
diff --git a/tests/suites/test_suite_ecp.data b/tests/suites/test_suite_ecp.data
index 7d3dd1a..a244bc4 100644
--- a/tests/suites/test_suite_ecp.data
+++ b/tests/suites/test_suite_ecp.data
@@ -566,6 +566,42 @@
 depends_on:MBEDTLS_ECP_DP_SECP256R1_ENABLED
 ecp_write_key:MBEDTLS_ECP_DP_SECP256R1:"f12a1320760270a83cbffd53f6031ef76a5d86c8a204f2c30ca9ebf51f0f0ea7":65:0
 
+ECP write key: secp256r1, output short by 1
+depends_on:MBEDTLS_ECP_DP_SECP256R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP256R1:"f12a1320760270a83cbffd53f6031ef76a5d86c8a204f2c30ca9ebf51f0f0ea7":31:MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL
+
+ECP write key: secp256r1, output_size=1
+depends_on:MBEDTLS_ECP_DP_SECP256R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP256R1:"f12a1320760270a83cbffd53f6031ef76a5d86c8a204f2c30ca9ebf51f0f0ea7":1:MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL
+
+ECP write key: secp256r1, output_size=0
+depends_on:MBEDTLS_ECP_DP_SECP256R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP256R1:"f12a1320760270a83cbffd53f6031ef76a5d86c8a204f2c30ca9ebf51f0f0ea7":0:MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL
+
+ECP write key: secp256r1, top byte = 0, output_size=32
+depends_on:MBEDTLS_ECP_DP_SECP256R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP256R1:"00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff":32:0
+
+ECP write key: secp256r1, top byte = 0, output_size=31 (fits)
+depends_on:MBEDTLS_ECP_DP_SECP256R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP256R1:"00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff":31:0
+
+ECP write key: secp256r1, top byte = 0, output_size=30 (too small)
+depends_on:MBEDTLS_ECP_DP_SECP256R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP256R1:"00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff":30:MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL
+
+ECP write key: secp256r1, mostly-0 key, output_size=32
+depends_on:MBEDTLS_ECP_DP_SECP256R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP256R1:"0000000000000000000000000000000000000000000000000000000000000001":32:0
+
+ECP write key: secp256r1, mostly-0 key, output_size=31 (fits)
+depends_on:MBEDTLS_ECP_DP_SECP256R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP256R1:"0000000000000000000000000000000000000000000000000000000000000001":31:0
+
+ECP write key: secp256r1, mostly-0 key, output_size=1 (fits)
+depends_on:MBEDTLS_ECP_DP_SECP256R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP256R1:"0000000000000000000000000000000000000000000000000000000000000001":1:0
+
 ECP write key: secp384r1, nominal
 depends_on:MBEDTLS_ECP_DP_SECP384R1_ENABLED
 ecp_write_key:MBEDTLS_ECP_DP_SECP384R1:"d27335ea71664af244dd14e9fd1260715dfd8a7965571c48d709ee7a7962a156d706a90cbcb5df2986f05feadb9376f1":48:0
@@ -582,6 +618,18 @@
 depends_on:MBEDTLS_ECP_DP_SECP384R1_ENABLED
 ecp_write_key:MBEDTLS_ECP_DP_SECP384R1:"d27335ea71664af244dd14e9fd1260715dfd8a7965571c48d709ee7a7962a156d706a90cbcb5df2986f05feadb9376f1":97:0
 
+ECP write key: secp384r1, output short by 1
+depends_on:MBEDTLS_ECP_DP_SECP384R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP384R1:"d27335ea71664af244dd14e9fd1260715dfd8a7965571c48d709ee7a7962a156d706a90cbcb5df2986f05feadb9376f1":47:MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL
+
+ECP write key: secp384r1, output_size=1
+depends_on:MBEDTLS_ECP_DP_SECP384R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP384R1:"d27335ea71664af244dd14e9fd1260715dfd8a7965571c48d709ee7a7962a156d706a90cbcb5df2986f05feadb9376f1":1:MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL
+
+ECP write key: secp384r1, output_size=0
+depends_on:MBEDTLS_ECP_DP_SECP384R1_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_SECP384R1:"d27335ea71664af244dd14e9fd1260715dfd8a7965571c48d709ee7a7962a156d706a90cbcb5df2986f05feadb9376f1":0:MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL
+
 ECP write key: Curve25519, nominal
 depends_on:MBEDTLS_ECP_DP_CURVE25519_ENABLED
 ecp_write_key:MBEDTLS_ECP_DP_CURVE25519:"a046e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449a44":32:0
@@ -598,6 +646,26 @@
 depends_on:MBEDTLS_ECP_DP_CURVE25519_ENABLED
 ecp_write_key:MBEDTLS_ECP_DP_CURVE25519:"a046e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449a44":65:0
 
+ECP write key: Curve25519, output short by 1
+depends_on:MBEDTLS_ECP_DP_CURVE25519_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_CURVE25519:"a046e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449a44":31:MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL
+
+ECP write key: Curve25519, output_size=1
+depends_on:MBEDTLS_ECP_DP_CURVE25519_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_CURVE25519:"a046e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449a44":1:MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL
+
+ECP write key: Curve25519, output_size=0
+depends_on:MBEDTLS_ECP_DP_CURVE25519_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_CURVE25519:"a046e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449a44":0:MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL
+
+ECP write key: Curve25519, mostly-0 key, output_size=32
+depends_on:MBEDTLS_ECP_DP_CURVE25519_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_CURVE25519:"0000000000000000000000000000000000000000000000000000000000000040":32:0
+
+ECP write key: Curve25519, mostly-0 key, output_size=31
+depends_on:MBEDTLS_ECP_DP_CURVE25519_ENABLED
+ecp_write_key:MBEDTLS_ECP_DP_CURVE25519:"0000000000000000000000000000000000000000000000000000000000000040":31:MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL
+
 ECP mod p192 small (more than 192 bits, less limbs than 2 * 192 bits)
 depends_on:MBEDTLS_ECP_DP_SECP192R1_ENABLED
 ecp_fast_mod:MBEDTLS_ECP_DP_SECP192R1:"0100000000000103010000000000010201000000000001010100000000000100"
diff --git a/tests/suites/test_suite_ecp.function b/tests/suites/test_suite_ecp.function
index aff5ba4..da13720 100644
--- a/tests/suites/test_suite_ecp.function
+++ b/tests/suites/test_suite_ecp.function
@@ -1399,16 +1399,18 @@
 
     if (expected_ret == 0) {
         size_t length = (key.grp.nbits + 7) / 8;
-        TEST_LE_U(length, exported_size);
-
         const unsigned char *key_start = NULL;
         const unsigned char *zeros_start = NULL;
         switch (mbedtls_ecp_get_type(&key.grp)) {
             case MBEDTLS_ECP_TYPE_SHORT_WEIERSTRASS:
+                if ((size_t) exported_size < length) {
+                    length = exported_size;
+                }
                 key_start = exported + exported_size - length;
                 zeros_start = exported;
                 break;
             case MBEDTLS_ECP_TYPE_MONTGOMERY:
+                TEST_LE_U(length, exported_size);
                 key_start = exported;
                 zeros_start = exported + length;
                 break;
@@ -1416,11 +1418,22 @@
                 TEST_FAIL("Unknown ECP curve type");
                 break;
         }
-        TEST_MEMORY_COMPARE(in_key->x, in_key->len,
-                            key_start, length);
-        for (size_t i = 0; i < exported_size - length; i++) {
-            mbedtls_test_set_step(i);
-            TEST_EQUAL(zeros_start[i], 0);
+
+        if (length < in_key->len) {
+            /* Shorter output (only possible with Weierstrass keys) */
+            for (size_t i = 0; i < in_key->len - length; i++) {
+                mbedtls_test_set_step(i);
+                TEST_EQUAL(in_key->x[i], 0);
+            }
+            TEST_MEMORY_COMPARE(in_key->x + in_key->len - length, length,
+                                key_start, length);
+        } else {
+            TEST_MEMORY_COMPARE(in_key->x, in_key->len,
+                                key_start, length);
+            for (size_t i = 0; i < exported_size - length; i++) {
+                mbedtls_test_set_step(i);
+                TEST_EQUAL(zeros_start[i], 0);
+            }
         }
     }