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 */