psa_util: add raw<->DER ECDSA conversion functions

Signed-off-by: Valerio Setti <valerio.setti@nordicsemi.no>
diff --git a/include/mbedtls/psa_util.h b/include/mbedtls/psa_util.h
index 47724c6..912179b 100644
--- a/include/mbedtls/psa_util.h
+++ b/include/mbedtls/psa_util.h
@@ -176,6 +176,42 @@
     return (mbedtls_md_type_t) (psa_alg & PSA_ALG_HASH_MASK);
 }
 
+#if defined(MBEDTLS_ASN1_WRITE_C)
+/** Convert an ECDSA signature from raw format to DER ASN.1 one.
+ *
+ * \param       raw         Buffer that contains the signature in raw format.
+ * \param       raw_len     Length of raw buffer in bytes
+ * \param[out]  der         Buffer that will be filled with the converted DER
+ *                          output. It can overlap with raw buffer.
+ * \param       der_size    Size of the output der buffer in bytes.
+ * \param[out]  der_len     On success it contains the amount of valid data
+ *                          (in bytes) written to der buffer. It's undefined
+ *                          in case of failure.
+ * \param       bits        Size of each raw coordinate in bits.
+ */
+int mbedtls_ecdsa_raw_to_der(const unsigned char *raw, size_t raw_len,
+                             unsigned char *der, size_t der_size, size_t *der_len,
+                             size_t bits);
+#endif /* MBEDTLS_ASN1_WRITE_C */
+
+#if defined(MBEDTLS_ASN1_PARSE_C)
+/** Convert an ECDSA signature from DER ASN.1 format to raw.
+ *
+ * \param       der         Buffer that contains the signature in DER format.
+ * \param       der_len     Size of the der buffer in bytes.
+ * \param[out]  raw         Buffer that will be filled with the converted raw
+ *                          signature. It can overlap with der buffer.
+ * \param       raw_size    Size of the raw buffer in bytes.
+ * \param[out]  raw_len     On success it is updated with the amount of valid
+ *                          data (in bytes) written to raw buffer. It's undefined
+ *                          in case of failure.
+ * \param       bits        Size of each raw coordinate in bits.
+ */
+int mbedtls_ecdsa_der_to_raw(const unsigned char *der, size_t der_len,
+                             unsigned char *raw, size_t raw_size, size_t *raw_len,
+                             size_t bits);
+#endif /* MBEDTLS_ASN1_PARSE_C */
+
 /**@}*/
 
 #endif /* MBEDTLS_PSA_CRYPTO_C */
diff --git a/library/psa_util.c b/library/psa_util.c
index 41586e2..2c35db0 100644
--- a/library/psa_util.c
+++ b/library/psa_util.c
@@ -40,6 +40,10 @@
 #if defined(MBEDTLS_BLOCK_CIPHER_SOME_PSA)
 #include <mbedtls/cipher.h>
 #endif
+#if defined(MBEDTLS_ASN1_WRITE_C)
+#include <mbedtls/asn1write.h>
+#include <psa/crypto_sizes.h>
+#endif
 
 /* PSA_SUCCESS is kept at the top of each error table since
  * it's the most common status when everything functions properly. */
@@ -330,4 +334,205 @@
 }
 #endif /* PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY */
 
+#if defined(MBEDTLS_ASN1_WRITE_C)
+/*
+ * Convert a single raw coordinate to DER ASN.1 format.
+ * Note: this function is similar to mbedtls_asn1_write_mpi(), but it doesn't
+ * depend on BIGNUM_C.
+ * Note: this function fills der_buf backward.
+ */
+static int convert_raw_to_der_single_int(const unsigned char *raw_buf, size_t raw_len,
+                                         unsigned char *der_buf_start,
+                                         unsigned char *der_buf_end)
+{
+    unsigned char *p = der_buf_end;
+    int len = raw_len;
+    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+
+    /* Copy the raw coordinate to the end of der_buf. */
+    if ((p - der_buf_start) < len) {
+        return MBEDTLS_ERR_ASN1_BUF_TOO_SMALL;
+    }
+    p -= len;
+    memcpy(p, raw_buf, len);
+
+    /* ASN.1 DER encoding requires minimal length, so skip leading 0s.
+     * Provided input MPIs should not be 0, but as a failsafe measure, still
+     * detect that and return error in case. */
+    while (*p == 0x00) {
+        ++p;
+        --len;
+        if (len == 0) {
+            return MBEDTLS_ERR_ASN1_INVALID_DATA;
+        }
+    }
+
+    /* If MSb is 1, ASN.1 requires that we prepend a 0. */
+    if (*p & 0x80) {
+        if ((p - der_buf_start) < 1) {
+            return MBEDTLS_ERR_ASN1_BUF_TOO_SMALL;
+        }
+        --p;
+        *p = 0x00;
+        ++len;
+    }
+
+    MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&p, der_buf_start, len));
+    MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_tag(&p, der_buf_start, MBEDTLS_ASN1_INTEGER));
+
+    return len;
+}
+
+int mbedtls_ecdsa_raw_to_der(const unsigned char *raw, size_t raw_len,
+                             unsigned char *der, size_t der_size, size_t *der_len,
+                             size_t bits)
+{
+    unsigned char r[PSA_BITS_TO_BYTES(PSA_VENDOR_ECC_MAX_CURVE_BITS)];
+    unsigned char s[PSA_BITS_TO_BYTES(PSA_VENDOR_ECC_MAX_CURVE_BITS)];
+    const size_t coordinate_len = PSA_BITS_TO_BYTES(bits);
+    size_t len = 0;
+    unsigned char *p = der + der_size;
+    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+
+    if (raw_len < 2 * coordinate_len) {
+        return MBEDTLS_ERR_ASN1_INVALID_DATA;
+    }
+
+    /* Since raw and der buffers might overlap, dump r and s before starting
+     * the conversion. */
+    memset(r, 0, sizeof(r));
+    memcpy(r, raw, coordinate_len);
+    memset(s, 0, sizeof(s));
+    memcpy(s, raw + coordinate_len, coordinate_len);
+
+    /* der buffer will initially be written starting from its end so we pick s
+     * first and then r. */
+    ret = convert_raw_to_der_single_int(s, coordinate_len, der, p);
+    if (ret < 0) {
+        return ret;
+    }
+    p -= ret;
+    len += ret;
+
+    ret = convert_raw_to_der_single_int(r, coordinate_len, der, p);
+    if (ret < 0) {
+        return ret;
+    }
+    p -= ret;
+    len += ret;
+
+    /* Add ASN.1 header (len + tag). */
+    MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&p, der, len));
+    MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_tag(&p, der,
+                                                     MBEDTLS_ASN1_CONSTRUCTED |
+                                                     MBEDTLS_ASN1_SEQUENCE));
+
+    /* memmove the content of der buffer to its beginnig. */
+    memmove(der, p, len);
+    *der_len = len;
+
+    return 0;
+}
+#endif /* MBEDTLS_ASN1_WRITE_C */
+
+#if defined(MBEDTLS_ASN1_PARSE_C)
+/*
+ * Convert a single integer from ASN.1 DER format to raw.
+ * Note: der and raw buffers are not overlapping here.
+ */
+static int convert_der_to_raw_single_int(unsigned char *der, size_t der_len,
+                                         unsigned char *raw, size_t raw_len,
+                                         size_t coordinate_size)
+{
+    unsigned char *p = der;
+    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+    size_t unpadded_len, padding_len = 0;
+
+    /* Get the length of ASN.1 element (i.e. the integer we need to parse). */
+    ret = mbedtls_asn1_get_tag(&p, p + der_len, &unpadded_len,
+                               MBEDTLS_ASN1_INTEGER);
+    if (ret != 0) {
+        return ret;
+    }
+
+    /* Skip leading zeros */
+    while (*p == 0x00) {
+        p++;
+        unpadded_len--;
+        /* It should never happen that the input number is all zeros. */
+        if (unpadded_len == 0) {
+            return MBEDTLS_ERR_ASN1_LENGTH_MISMATCH;
+        }
+    }
+
+    if (raw_len < coordinate_size) {
+        return MBEDTLS_ERR_ASN1_LENGTH_MISMATCH;
+    }
+
+    if (unpadded_len < coordinate_size) {
+        padding_len = coordinate_size - unpadded_len;
+        memset(raw, 0x00, padding_len);
+    }
+    memcpy(raw + padding_len, p, unpadded_len);
+    p += unpadded_len;
+
+    return (int) (p - der);
+}
+
+int mbedtls_ecdsa_der_to_raw(const unsigned char *der, size_t der_len,
+                             unsigned char *raw, size_t raw_size, size_t *raw_len,
+                             size_t bits)
+{
+    unsigned char raw_tmp[PSA_VENDOR_ECDSA_SIGNATURE_MAX_SIZE];
+    unsigned char *p = (unsigned char *) der;
+    size_t data_len;
+    size_t coordinate_size = PSA_BITS_TO_BYTES(bits);
+    int ret;
+
+    /* The output raw buffer should be at least twice the size of a raw
+     * coordinate in order to store r and s. */
+    if (raw_size < coordinate_size * 2) {
+        return MBEDTLS_ERR_ASN1_BUF_TOO_SMALL;
+    }
+
+    /* Check that the provided input DER buffer has the right header. */
+    ret = mbedtls_asn1_get_tag(&p, der + der_len, &data_len,
+                               MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE);
+    if (ret != 0) {
+        return ret;
+    }
+
+    memset(raw_tmp, 0, sizeof(raw_tmp));
+
+    /* Extract r */
+    ret = convert_der_to_raw_single_int(p, data_len, raw_tmp, sizeof(raw_tmp),
+                                        coordinate_size);
+    if (ret < 0) {
+        return ret;
+    }
+    p += ret;
+    data_len -= ret;
+
+    /* Extract s */
+    ret = convert_der_to_raw_single_int(p, data_len, raw_tmp + coordinate_size,
+                                        sizeof(raw_tmp) - coordinate_size,
+                                        coordinate_size);
+    if (ret < 0) {
+        return ret;
+    }
+    p += ret;
+    data_len -= ret;
+
+    /* Check that we consumed all the input der data. */
+    if ((p - der) != (int) der_len) {
+        return MBEDTLS_ERR_ASN1_LENGTH_MISMATCH;
+    }
+
+    memcpy(raw, raw_tmp, 2 * coordinate_size);
+    *raw_len = 2 * coordinate_size;
+
+    return 0;
+}
+#endif /* MBEDTLS_ASN1_PARSE_C */
+
 #endif /* MBEDTLS_PSA_CRYPTO_C */