bootutil/crypto: Add a generic signature validation module for ECDSA
Add a dedicated signature validation module for generic ECDSA signatures,
and a corresponding cryptographic abstraction backend based on PSA Crypto
APIs. This signature verification backend is enabled by defining the
option MCUBOOT_SIGN_ECDSA
Signed-off-by: Antonio de Angelis <Antonio.deAngelis@arm.com>
Change-Id: I47da70629da0a5681ec7c4dcceed875a997b071b
diff --git a/boot/bootutil/CMakeLists.txt b/boot/bootutil/CMakeLists.txt
index 534ca11..3d34359 100644
--- a/boot/bootutil/CMakeLists.txt
+++ b/boot/bootutil/CMakeLists.txt
@@ -26,6 +26,7 @@
src/image_ec256.c
src/image_ed25519.c
src/image_rsa.c
+ src/image_ecdsa.c
src/image_validate.c
src/loader.c
src/swap_misc.c
diff --git a/boot/bootutil/include/bootutil/crypto/ecdsa.h b/boot/bootutil/include/bootutil/crypto/ecdsa.h
new file mode 100644
index 0000000..1868d03
--- /dev/null
+++ b/boot/bootutil/include/bootutil/crypto/ecdsa.h
@@ -0,0 +1,214 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright (c) 2023 Arm Limited
+ */
+
+/*
+ * This module provides a thin abstraction over some of the crypto
+ * primitives to make it easier to swap out the used crypto library.
+ *
+ * At this point, the choice is: MCUBOOT_USE_PSA_CRYPTO
+ * Note that support for MCUBOOT_USE_PSA_CRYPTO is still experimental and it
+ * might not support all the crypto abstractions that MCUBOOT_USE_MBED_TLS
+ * supports. For this reason, it's allowed to have both of them defined, and
+ * for crypto modules that support both abstractions, the
+ * MCUBOOT_USE_PSA_CRYPTO will take precedence.
+ */
+
+#ifndef __BOOTUTIL_CRYPTO_ECDSA_H_
+#define __BOOTUTIL_CRYPTO_ECDSA_H_
+
+#include "mcuboot_config/mcuboot_config.h"
+
+#if defined(MCUBOOT_USE_PSA_CRYPTO)
+#define MCUBOOT_USE_PSA_OR_MBED_TLS
+#endif /* MCUBOOT_USE_PSA_CRYPTO */
+
+#if (defined(MCUBOOT_USE_PSA_OR_MBED_TLS)) != 1
+ #error "One crypto backend must be defined: PSA_CRYPTO"
+#endif
+
+#if defined(MCUBOOT_USE_PSA_CRYPTO)
+ #include <psa/crypto.h>
+ #include <string.h>
+#endif /* MCUBOOT_USE_PSA_CRYPTO */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(MCUBOOT_USE_PSA_CRYPTO)
+// OID id-ecPublicKey 1.2.840.10045.2.1.
+const uint8_t IdEcPublicKey[] = {0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01};
+// OID secp256r1 1.2.840.10045.3.1.7.
+const uint8_t Secp256r1[] = {0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07};
+// OID secp384r1 1.3.132.0.34
+const uint8_t Secp384r1[] = {0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22};
+
+typedef struct {
+ psa_key_id_t key_id;
+ size_t curve_byte_count;
+ psa_algorithm_t required_algorithm;
+} bootutil_ecdsa_context;
+
+static inline void bootutil_ecdsa_init(bootutil_ecdsa_context *ctx)
+{
+ ctx->key_id = PSA_KEY_ID_NULL;
+ ctx->curve_byte_count = 0;
+ ctx->required_algorithm = 0;
+}
+
+static inline void bootutil_ecdsa_drop(bootutil_ecdsa_context *ctx)
+{
+ if (ctx->key_id != PSA_KEY_ID_NULL) {
+ (void)psa_destroy_key(ctx->key_id);
+ }
+}
+
+/* ECDSA public key with format specified in RFC5280 et al. in ASN-1 syntax
+ *
+ * SEQUENCE {
+ * SEQUENCE {
+ * OBJECT idEcPublicKey
+ * OBJECT namedCurve
+ * }
+ * BIT STRING publicKey
+ * }
+ *
+ * ECDSA signature format specified in RFC3279 et al. in ASN-1 syntax
+ *
+ * SEQUENCE {
+ * INTEGER r
+ * INTEGER s
+ * }
+ *
+ */
+
+/* Offset in bytes from the start of the encoding to the length
+ * of the innermost SEQUENCE
+ */
+#define PUB_KEY_LEN_OFF (3)
+/* Offset in bytes from the start of the publicKey encoding of the BIT STRING */
+#define PUB_KEY_VAL_OFF (3)
+/* Computes the pointer to the idEcPublicKey OID from the base of the encoding */
+#define PUB_KEY_OID_OFFSET(p) (*p + PUB_KEY_LEN_OFF+1)
+/* Computes the pointer to the namedCurve OID from the base of the encoding */
+#define CURVE_TYPE_OID_OFFSET(p) PUB_KEY_OID_OFFSET(p) + sizeof(IdEcPublicKey)
+
+/* This helper function gets a pointer to the bitstring associated to the publicKey
+ * as encoded per RFC 5280. This function assumes that the public key encoding is not
+ * bigger than 127 bytes (i.e. usually up until 384 bit curves)
+ *
+ * \param[in,out] p Double pointer to a buffer containing the RFC 5280 of the ECDSA public key.
+ * On output, the pointer is updated to point to the start of the public key
+ * in BIT STRING form.
+ * \param[out] size Pointer to a buffer containing the size of the public key extracted
+ *
+ */
+static inline void get_public_key_from_rfc5280_encoding(uint8_t **p, size_t *size)
+{
+ uint8_t *key_start = (*p) + (PUB_KEY_LEN_OFF + 1 + (*p)[PUB_KEY_LEN_OFF] + PUB_KEY_VAL_OFF);
+ *p = key_start;
+ *size = key_start[-2]-1; /* -2 from PUB_KEY_VAL_OFF to get the length, -1 to remove the ASN.1 padding byte count */
+}
+
+/*
+ * Parse a ECDSA public key with format specified in RFC5280 et al.
+ *
+ * OID for icEcPublicKey is 1.2.840.10045.2.1
+ * OIDs for supported curves are as follows:
+ * secp224r1: 1.3.132.0.33
+ * secp256r1 (prime256v1): 1.2.840.10045.3.1.7
+ * secp384r1: 1.3.132.0.34
+ */
+static int
+bootutil_ecdsa_parse_public_key(bootutil_ecdsa_context *ctx, uint8_t **p, uint8_t *end)
+{
+ psa_key_attributes_t key_attributes = psa_key_attributes_init();
+ size_t key_size;
+ (void)end;
+
+ /* public key oid is valid */
+ if (memcmp(PUB_KEY_OID_OFFSET(p), IdEcPublicKey, sizeof(IdEcPublicKey))) {
+ return (int)PSA_ERROR_INVALID_ARGUMENT;
+ }
+
+ if (!memcmp(CURVE_TYPE_OID_OFFSET(p), Secp256r1, sizeof(Secp256r1))) {
+ ctx->curve_byte_count = 32;
+ ctx->required_algorithm = PSA_ALG_SHA_256;
+ } else if (!memcmp(CURVE_TYPE_OID_OFFSET(p), Secp384r1, sizeof(Secp384r1))) {
+ ctx->curve_byte_count = 48;
+ ctx->required_algorithm = PSA_ALG_SHA_384;
+ } else {
+ return (int)PSA_ERROR_INVALID_ARGUMENT;
+ }
+
+ get_public_key_from_rfc5280_encoding(p, &key_size);
+ /* Set attributes and import key */
+ psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_VERIFY_HASH);
+ psa_set_key_algorithm(&key_attributes, PSA_ALG_ECDSA(ctx->required_algorithm));
+ psa_set_key_type(&key_attributes, PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1));
+
+ return (int)psa_import_key(&key_attributes, *p, key_size, &ctx->key_id);
+}
+
+/* This helper function parses a signature as specified in RFC 5480 into a pair
+ * (r,s) of contiguous bytes
+ *
+ * \param[in] sig Pointer to a buffer containing the encoded signature
+ * \param[in] num_of_curve_bytes The required number of bytes for r and s
+ * \param[out] r_s_pair Buffer containing the (r,s) pair extracted. It's caller
+ * responsibility to ensure the buffer is big enough to
+ * hold the parsed (r,s) pair.
+ */
+static inline void parse_signature_from_rfc5480_encoding(const uint8_t *sig,size_t num_of_curve_bytes,
+ uint8_t *r_s_pair)
+{
+ const uint8_t *sig_ptr = NULL;
+
+ /* Move r in place */
+ size_t r_len = sig[3];
+ sig_ptr = &sig[4];
+ if (r_len >= num_of_curve_bytes) {
+ sig_ptr = sig_ptr + r_len - num_of_curve_bytes;
+ memcpy(&r_s_pair[0], sig_ptr, num_of_curve_bytes);
+ if(r_len % 2) {
+ r_len--;
+ }
+ } else {
+ memcpy(&r_s_pair[num_of_curve_bytes - r_len], sig_ptr, r_len);
+ }
+
+ /* Move s in place */
+ size_t s_len = sig_ptr[r_len+1]; /* + 1 to skip SEQUENCE */
+ sig_ptr = &sig_ptr[r_len+2];
+ if (s_len >= num_of_curve_bytes) {
+ sig_ptr = sig_ptr + s_len - num_of_curve_bytes;
+ memcpy(&r_s_pair[num_of_curve_bytes], sig_ptr, num_of_curve_bytes);
+ } else {
+ memcpy(&r_s_pair[2*num_of_curve_bytes - s_len], sig_ptr, s_len);
+ }
+}
+
+/* PSA Crypto has a dedicated API for ECDSA verification */
+static inline int bootutil_ecdsa_verify(const bootutil_ecdsa_context *ctx,
+ uint8_t *hash, size_t hlen, uint8_t *sig, size_t slen)
+{
+ (void)slen;
+
+ uint8_t reformatted_signature[96] = {0}; /* Up to P-384 signature sizes */
+ parse_signature_from_rfc5480_encoding(sig, ctx->curve_byte_count,reformatted_signature);
+
+ return (int) psa_verify_hash(ctx->key_id, PSA_ALG_ECDSA(ctx->required_algorithm),
+ hash, hlen, reformatted_signature, 2*ctx->curve_byte_count);
+}
+#endif /* MCUBOOT_USE_PSA_CRYPTO */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __BOOTUTIL_CRYPTO_ECDSA_H_ */
diff --git a/boot/bootutil/src/image_ecdsa.c b/boot/bootutil/src/image_ecdsa.c
new file mode 100644
index 0000000..53e5db1
--- /dev/null
+++ b/boot/bootutil/src/image_ecdsa.c
@@ -0,0 +1,64 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright (c) 2023 Arm Limited
+ */
+
+#include <string.h>
+
+#include "mcuboot_config/mcuboot_config.h"
+
+#ifdef MCUBOOT_SIGN_ECDSA
+#include "bootutil_priv.h"
+#include "bootutil/sign_key.h"
+#include "bootutil/fault_injection_hardening.h"
+
+#include "bootutil/crypto/ecdsa.h"
+
+static fih_ret
+bootutil_cmp_ecdsa_sig(bootutil_ecdsa_context *ctx, uint8_t *hash, uint32_t hlen,
+ uint8_t *sig, size_t slen)
+{
+ int rc = -1;
+ FIH_DECLARE(fih_rc, FIH_FAILURE);
+
+ /* PSA Crypto APIs allow the verification in a single call */
+ rc = bootutil_ecdsa_verify(ctx, hash, hlen, sig, slen);
+
+ fih_rc = fih_ret_encode_zero_equality(rc);
+ if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
+ FIH_SET(fih_rc, FIH_FAILURE);
+ }
+
+ FIH_RET(fih_rc);
+}
+
+fih_ret
+bootutil_verify_sig(uint8_t *hash, uint32_t hlen, uint8_t *sig, size_t slen,
+ uint8_t key_id)
+{
+ int rc = -1;
+ FIH_DECLARE(fih_rc, FIH_FAILURE);
+ uint8_t *cp;
+ uint8_t *end;
+ bootutil_ecdsa_context ctx;
+
+ bootutil_ecdsa_init(&ctx);
+
+ cp = (uint8_t *)bootutil_keys[key_id].key;
+ end = cp + *bootutil_keys[key_id].len;
+
+ /* The key used for signature verification is a public ECDSA key */
+ rc = bootutil_ecdsa_parse_public_key(&ctx, &cp, end);
+ if (rc) {
+ goto out;
+ }
+
+ FIH_CALL(bootutil_cmp_ecdsa_sig, fih_rc, &ctx, hash, hlen, sig, slen);
+
+out:
+ bootutil_ecdsa_drop(&ctx);
+
+ FIH_RET(fih_rc);
+}
+#endif /* MCUBOOT_SIGN_ECDSA */