psa_util: add raw<->DER ECDSA conversion functions

Signed-off-by: Valerio Setti <valerio.setti@nordicsemi.no>
This commit is contained in:
Valerio Setti 2024-01-08 16:49:17 +01:00
parent aa3fa98bc4
commit 75501f5ede
2 changed files with 241 additions and 0 deletions

View File

@ -176,6 +176,42 @@ static inline mbedtls_md_type_t mbedtls_md_type_from_psa_alg(psa_algorithm_t psa
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 */

View File

@ -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 @@ mbedtls_ecp_group_id mbedtls_ecc_group_from_psa(psa_ecc_family_t family,
}
#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 */