[PATCH v1 5/8] hw/misc/aspeed_sbc: Support the ECDSA verify command

Jamin Lin <[email protected]>
Newsgroups org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
The AST10x0 secure boot controller register block also hosts an ECDSA
engine. Emulate its secp384r1 "verify" command: on a trigger write to
the command register, read the public key, signature and SHA-384 digest
that the guest staged in the SEC SRAM and defer the verification to the
crypto akcipher backend, reporting the result through the status
register.

The engine reads its operands from the SEC SRAM through a dedicated
address space over the SoC memory, using a 'sram' link and a 'sram-base'
property.

The absolute address is sram_base + the SRAM-relative offset.

Signed-off-by: Jamin Lin <[email protected]>
---
 hw/misc/aspeed_sbc.c         | 154 +++++++++++++++++++++++++++++++++++
 hw/misc/trace-events         |   2 +
 include/hw/misc/aspeed_sbc.h |   5 ++
 3 files changed, 161 insertions(+)

diff --git a/hw/misc/aspeed_sbc.c b/hw/misc/aspeed_sbc.c
index 1dfcf14e5b..724f4cb06c 100644
--- a/hw/misc/aspeed_sbc.c
+++ b/hw/misc/aspeed_sbc.c
@@ -10,11 +10,13 @@
 
 #include "qemu/osdep.h"
 #include "qemu/log.h"
+#include "qemu/cutils.h"
 #include "qemu/error-report.h"
 #include "hw/core/qdev-properties.h"
 #include "hw/misc/aspeed_sbc.h"
 #include "qapi/error.h"
 #include "migration/vmstate.h"
+#include "crypto/akcipher.h"
 #include "trace.h"
 
 #define R_PROT          (0x000 / 4)
@@ -24,8 +26,22 @@
 #define R_CAMP1         (0x020 / 4)
 #define R_CAMP2         (0x024 / 4)
 #define R_QSR           (0x040 / 4)
+#define R_ECDSA_CMD     (0x0bc / 4)
+
+/*
+ * SEC SRAM layout for a secp384r1 ECDSA verify operation. All operands are
+ * 48-byte big-endian integers.
+ */
+#define ECDSA_SRAM_QX   0x2080
+#define ECDSA_SRAM_QY   0x20c0
+#define ECDSA_SRAM_R    0x21c0
+#define ECDSA_SRAM_S    0x2200
+#define ECDSA_SRAM_M    0x2240
+#define ECDSA_P384_COORD_LEN    48
 
 /* R_STATUS */
+#define ECDSA_VERIFY_PASS       BIT(21)
+#define ECDSA_VERIFY_DONE       BIT(20)
 #define ABR_EN                  BIT(14) /* Mirrors SCU510[11] */
 #define ABR_IMAGE_SOURCE        BIT(13)
 #define SPI_ABR_IMAGE_SOURCE    BIT(12)
@@ -42,6 +58,9 @@
 #define OTP_MEM_IDLE            BIT(1)
 #define OTP_COMPARE_STATUS      BIT(0)
 
+/* R_ECDSA_CMD */
+#define ECDSA_CMD_TRIGGER BIT(1)
+
 /* QSR */
 #define QSR_RSA_MASK           (0x3 << 12)
 #define QSR_HASH_MASK          (0x3 << 10)
@@ -220,10 +239,121 @@ static void aspeed_sbc_handle_command(void *opaque, uint32_t cmd)
     s->regs[R_STATUS] |= (OTP_MEM_IDLE | OTP_IDLE);
 }
 
+static void sbc_ecdsa_hexdump(const char *desc, const char *buf, size_t size)
+{
+    g_autoptr(GString) str = g_string_sized_new(64);
+    size_t len;
+    size_t i;
+
+    for (i = 0; i < size; i += len) {
+        len = MIN(16, size - i);
+        g_string_truncate(str, 0);
+        qemu_hexdump_line(str, buf + i, len, 1, 4);
+        trace_aspeed_sbc_ecdsa_hexdump(desc, i, str->str);
+    }
+}
+
+/*
+ * The hardware only supports ECDSA secp384r1 (NIST P-384).
+ * The guest has already staged the public key,
+ * signature and digest in the SEC SRAM; read them out and defer the actual
+ * verification to the crypto backend.
+ *
+ * Returns true if the signature verifies. The caller is responsible for
+ * updating the status register.
+ */
+static bool aspeed_sbc_ecdsa_verify(AspeedSBCState *s)
+{
+    /* Only ECDSA secp384r1 is supported by this engine. */
+    QCryptoAkCipherOptions opts = {
+        .alg = QCRYPTO_AK_CIPHER_ALGO_ECDSA,
+        .u.ecdsa.curve_id = QCRYPTO_CURVE_ID_SECP384R1,
+    };
+    g_autoptr(QCryptoAkCipher) akcipher = NULL;
+    uint8_t pubkey[ECDSA_P384_COORD_LEN * 2];
+    uint8_t sig[ECDSA_P384_COORD_LEN * 2];
+    uint8_t dgst[ECDSA_P384_COORD_LEN];
+    Error *err = NULL;
+
+    if (!qcrypto_akcipher_supports(&opts)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: ECDSA secp384r1 is not supported by the crypto "
+                      "backend\n", __func__);
+        return false;
+    }
+
+    /*
+     * ECDSA_SRAM_* are SRAM-relative offsets, but sram_as is an AddressSpace
+     * over the SoC memory, where the SEC SRAM is mapped at sram_base - so the
+     * absolute address is sram_base + ECDSA_SRAM_*.
+     */
+    if (address_space_read(&s->sram_as, s->sram_base + ECDSA_SRAM_QX,
+                           MEMTXATTRS_UNSPECIFIED, pubkey,
+                           ECDSA_P384_COORD_LEN) != MEMTX_OK) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: failed to read ECDSA Qx from SEC SRAM\n", __func__);
+        return false;
+    }
+    if (address_space_read(&s->sram_as, s->sram_base + ECDSA_SRAM_QY,
+                           MEMTXATTRS_UNSPECIFIED,
+                           pubkey + ECDSA_P384_COORD_LEN,
+                           ECDSA_P384_COORD_LEN) != MEMTX_OK) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: failed to read ECDSA Qy from SEC SRAM\n", __func__);
+        return false;
+    }
+    if (address_space_read(&s->sram_as, s->sram_base + ECDSA_SRAM_R,
+                           MEMTXATTRS_UNSPECIFIED, sig,
+                           ECDSA_P384_COORD_LEN) != MEMTX_OK) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: failed to read ECDSA r from SEC SRAM\n", __func__);
+        return false;
+    }
+    if (address_space_read(&s->sram_as, s->sram_base + ECDSA_SRAM_S,
+                           MEMTXATTRS_UNSPECIFIED, sig + ECDSA_P384_COORD_LEN,
+                           ECDSA_P384_COORD_LEN) != MEMTX_OK) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: failed to read ECDSA s from SEC SRAM\n", __func__);
+        return false;
+    }
+    if (address_space_read(&s->sram_as, s->sram_base + ECDSA_SRAM_M,
+                           MEMTXATTRS_UNSPECIFIED, dgst,
+                           ECDSA_P384_COORD_LEN) != MEMTX_OK) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: failed to read ECDSA digest from SEC SRAM\n",
+                      __func__);
+        return false;
+    }
+
+    if (trace_event_get_state_backends(TRACE_ASPEED_SBC_ECDSA_HEXDUMP)) {
+        sbc_ecdsa_hexdump("pubkey", (char *)pubkey, sizeof(pubkey));
+        sbc_ecdsa_hexdump("signature", (char *)sig, sizeof(sig));
+        sbc_ecdsa_hexdump("digest", (char *)dgst, sizeof(dgst));
+    }
+
+    akcipher = qcrypto_akcipher_new(&opts, QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC,
+                                    pubkey, sizeof(pubkey), &err);
+    if (!akcipher) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: %s\n", __func__,
+                      error_get_pretty(err));
+        error_free(err);
+        return false;
+    }
+
+    if (qcrypto_akcipher_verify(akcipher, sig, sizeof(sig),
+                                dgst, sizeof(dgst), &err) != 0) {
+        error_free(err);
+        return false;
+    }
+
+    return true;
+}
+
 static void aspeed_sbc_write(void *opaque, hwaddr addr, uint64_t data,
                               unsigned int size)
 {
     AspeedSBCState *s = ASPEED_SBC(opaque);
+    AspeedSBCClass *sc = ASPEED_SBC_GET_CLASS(s);
 
     addr >>= 2;
 
@@ -244,6 +374,18 @@ static void aspeed_sbc_write(void *opaque, hwaddr addr, uint64_t data,
     case R_CMD:
         aspeed_sbc_handle_command(opaque, data);
         return;
+    case R_ECDSA_CMD:
+        if (sc->has_ecdsa && data == ECDSA_CMD_TRIGGER) {
+            s->regs[R_STATUS] &= ~(ECDSA_VERIFY_DONE | ECDSA_VERIFY_PASS);
+            if (aspeed_sbc_ecdsa_verify(s)) {
+                s->regs[R_STATUS] |= ECDSA_VERIFY_PASS;
+            }
+            s->regs[R_STATUS] |= ECDSA_VERIFY_DONE;
+            trace_aspeed_sbc_ecdsa_verify(
+                (s->regs[R_STATUS] & ECDSA_VERIFY_PASS) ? "pass" : "fail");
+        }
+        s->regs[addr] = data;
+        return;
     default:
         break;
     }
@@ -306,6 +448,14 @@ static void aspeed_sbc_realize(DeviceState *dev, Error **errp)
         }
     }
 
+    if (sc->has_ecdsa) {
+        if (!s->sram) {
+            error_setg(errp, TYPE_ASPEED_SBC ": 'sram' link not set");
+            return;
+        }
+        address_space_init(&s->sram_as, s->sram, TYPE_ASPEED_SBC ".sram");
+    }
+
     memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_sbc_ops, s,
             TYPE_ASPEED_SBC, 0x1000);
 
@@ -325,6 +475,9 @@ static const VMStateDescription vmstate_aspeed_sbc = {
 static const Property aspeed_sbc_properties[] = {
     DEFINE_PROP_BOOL("emmc-abr", AspeedSBCState, emmc_abr, 0),
     DEFINE_PROP_UINT32("signing-settings", AspeedSBCState, signing_settings, 0),
+    DEFINE_PROP_LINK("sram", AspeedSBCState, sram,
+                     TYPE_MEMORY_REGION, MemoryRegion *),
+    DEFINE_PROP_UINT64("sram-base", AspeedSBCState, sram_base, 0),
 };
 
 static void aspeed_sbc_class_init(ObjectClass *klass, const void *data)
@@ -355,6 +508,7 @@ static void aspeed_ast10x0_sbc_class_init(ObjectClass *klass, const void *data)
 
     dc->desc = "AST10X0 Secure Boot Controller";
     sc->has_otp = true;
+    sc->has_ecdsa = true;
 }
 
 static const TypeInfo aspeed_sbc_types[] = {
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index c9a868b3ef..1cd2ef8acc 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -95,6 +95,8 @@ aspeed_sbc_ignore_cmd(uint32_t cmd) "Ignoring command 0x%" PRIx32
 aspeed_sbc_handle_cmd(uint32_t cmd, uint32_t addr, bool ret) "Handling command 0x%" PRIx32 " for OTP addr 0x%" PRIx32 " Result: %d"
 aspeed_sbc_otp_read(uint32_t addr, uint32_t value) "OTP Memory read: addr 0x%" PRIx32 " value 0x%" PRIx32
 aspeed_sbc_otp_prog(uint32_t addr, uint32_t value) "OTP Memory write: addr 0x%" PRIx32 " value 0x%" PRIx32
+aspeed_sbc_ecdsa_verify(const char *result) "ECDSA verify done: %s"
+aspeed_sbc_ecdsa_hexdump(const char *desc, uint32_t offset, const char *s) "%s: 0x%08x: %s"
 
 # aspeed_scu.c
 aspeed_scu_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32
diff --git a/include/hw/misc/aspeed_sbc.h b/include/hw/misc/aspeed_sbc.h
index 07c7c22a86..9b04a10aed 100644
--- a/include/hw/misc/aspeed_sbc.h
+++ b/include/hw/misc/aspeed_sbc.h
@@ -40,12 +40,17 @@ struct AspeedSBCState {
     uint32_t regs[ASPEED_SBC_NR_REGS];
 
     AspeedOTPState otp;
+
+    MemoryRegion *sram;
+    AddressSpace sram_as;
+    uint64_t sram_base;
 };
 
 struct AspeedSBCClass {
     SysBusDeviceClass parent_class;
 
     bool has_otp;
+    bool has_ecdsa;
 };
 
 #endif /* ASPEED_SBC_H */
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.