* [PATCH v1 0/8] Add ECDSA akcipher support and the ASPEED SBC ECDSA engine for AST10x0
@ 2026-08-18 9:22 Jamin Lin
2026-08-18 9:22 ` [PATCH v1 1/8] qapi/crypto: Add ECDSA algorithm and curve id Jamin Lin
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Jamin Lin @ 2026-08-18 9:22 UTC (permalink / raw)
To: Daniel P. Berrangé, Cédric Le Goater, Peter Maydell,
Steven Lee, Troy Lee, Kane Chen, Andrew Jeffery, Joel Stanley,
Eric Blake, Markus Armbruster, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, open list:All patches CC here,
open list:ASPEED BMCs
Cc: Jamin Lin, Troy Lee
This series adds ECDSA to the QEMU crypto akcipher framework and models the
ASPEED AST10x0 secure boot controller (SBC) ECDSA engine on top of it.
The crypto side adds ECDSA sign/verify for prime256v1 (NIST P-256) and
secp384r1 (NIST P-384) in both the gcrypt and nettle backends, using the raw
big-endian form (public key Qx || Qy, private key scalar d, signature r || s)
with a pre-computed digest as input.
Both the AST2600 and the AST1030/AST1060 have a Secure Boot Controller (SBC)
that supports RSA and ECDSA verification, but only the AST1030/AST1060 have an
ECDSA verify engine, so this series models ECDSA only. RSA verification is used
solely to verify the AST2600 SPL in ROM CODE, which ASPEED does not release,
so it is very unlikely to be used by end users.
v1:
1. Add ECDSA akcipher support with gcrypt backend
2. Add ECDSA akcipher support with nettle backend
3. Add ECDSA sign/verify unit tests
4. Support the ECDSA verify command for AST10x0
5. Add ASPEED SBC ECDSA engine qtest
Jamin Lin (8):
qapi/crypto: Add ECDSA algorithm and curve id
crypto/akcipher: Support ECDSA sign/verify with gcrypt
crypto/akcipher: Support ECDSA sign/verify with nettle
tests/crypto: Add ECDSA sign/verify tests
hw/misc/aspeed_sbc: Support the ECDSA verify command
hw/misc/aspeed_sbc: Increase register space to 0x1000
hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model
tests/qtest: Add ASPEED SBC ECDSA engine test
crypto/akcipher-gcrypt.c.inc | 328 +++++++++++++++++++++++++++++-
crypto/akcipher-nettle.c.inc | 267 ++++++++++++++++++++++++
hw/arm/aspeed_ast10x0.c | 4 +
hw/misc/aspeed_sbc.c | 158 +++++++++++++-
hw/misc/trace-events | 2 +
include/hw/misc/aspeed_sbc.h | 7 +-
qapi/crypto.json | 33 ++-
tests/qtest/aspeed-sbc-test.c | 190 +++++++++++++++++
tests/qtest/meson.build | 2 +
tests/unit/test-crypto-akcipher.c | 236 +++++++++++++++++++++
10 files changed, 1221 insertions(+), 6 deletions(-)
create mode 100644 tests/qtest/aspeed-sbc-test.c
--
2.53.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1 1/8] qapi/crypto: Add ECDSA algorithm and curve id
2026-08-18 9:22 [PATCH v1 0/8] Add ECDSA akcipher support and the ASPEED SBC ECDSA engine for AST10x0 Jamin Lin
@ 2026-08-18 9:22 ` Jamin Lin
2026-08-18 9:22 ` [PATCH v1 2/8] crypto/akcipher: Support ECDSA sign/verify with gcrypt Jamin Lin
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jamin Lin @ 2026-08-18 9:22 UTC (permalink / raw)
To: Daniel P. Berrangé, Cédric Le Goater, Peter Maydell,
Steven Lee, Troy Lee, Kane Chen, Andrew Jeffery, Joel Stanley,
Eric Blake, Markus Armbruster, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, open list:All patches CC here,
open list:ASPEED BMCs
Cc: Jamin Lin, Troy Lee
Introduce a new asymmetric cipher algorithm, ECDSA, a curve identifier
enum (prime256v1 / NIST P-256 and secp384r1 / NIST P-384) and the
per-algorithm ECDSA options. This is the QAPI groundwork consumed by the
crypto akcipher backends in the following patches.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
qapi/crypto.json | 33 +++++++++++++++++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
diff --git a/qapi/crypto.json b/qapi/crypto.json
index 6e3a98ff68..016d7c65bf 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -557,10 +557,26 @@
#
# @rsa: RSA algorithm
#
+# @ecdsa: ECDSA algorithm
+#
# Since: 7.1
##
{ 'enum': 'QCryptoAkCipherAlgo',
- 'data': ['rsa']}
+ 'data': ['rsa', 'ecdsa']}
+
+##
+# @QCryptoCurveID:
+#
+# The supported elliptic curves.
+#
+# @prime256v1: NIST P-256 curve (prime256v1)
+#
+# @secp384r1: NIST P-384 curve (secp384r1)
+#
+# Since: 11.2
+##
+{ 'enum': 'QCryptoCurveID',
+ 'data': ['prime256v1', 'secp384r1']}
##
# @QCryptoAkCipherKeyType:
@@ -605,6 +621,18 @@
'data': { 'hash-alg':'QCryptoHashAlgo',
'padding-alg': 'QCryptoRSAPaddingAlgo'}}
+##
+# @QCryptoAkCipherOptionsECDSA:
+#
+# Specific parameters for the ECDSA algorithm.
+#
+# @curve-id: the elliptic curve to use
+#
+# Since: 11.2
+##
+{ 'struct': 'QCryptoAkCipherOptionsECDSA',
+ 'data': { 'curve-id': 'QCryptoCurveID' }}
+
##
# @QCryptoAkCipherOptions:
#
@@ -618,4 +646,5 @@
{ 'union': 'QCryptoAkCipherOptions',
'base': { 'alg': 'QCryptoAkCipherAlgo' },
'discriminator': 'alg',
- 'data': { 'rsa': 'QCryptoAkCipherOptionsRSA' }}
+ 'data': { 'rsa': 'QCryptoAkCipherOptionsRSA',
+ 'ecdsa': 'QCryptoAkCipherOptionsECDSA' }}
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v1 2/8] crypto/akcipher: Support ECDSA sign/verify with gcrypt
2026-08-18 9:22 [PATCH v1 0/8] Add ECDSA akcipher support and the ASPEED SBC ECDSA engine for AST10x0 Jamin Lin
2026-08-18 9:22 ` [PATCH v1 1/8] qapi/crypto: Add ECDSA algorithm and curve id Jamin Lin
@ 2026-08-18 9:22 ` Jamin Lin
2026-08-18 9:22 ` [PATCH v1 3/8] crypto/akcipher: Support ECDSA sign/verify with nettle Jamin Lin
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jamin Lin @ 2026-08-18 9:22 UTC (permalink / raw)
To: Daniel P. Berrangé, Cédric Le Goater, Peter Maydell,
Steven Lee, Troy Lee, Kane Chen, Andrew Jeffery, Joel Stanley,
Eric Blake, Markus Armbruster, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, open list:All patches CC here,
open list:ASPEED BMCs
Cc: Jamin Lin, Troy Lee
Implement ECDSA signing and verification for the gcrypt backend, for the
prime256v1 (NIST P-256) and secp384r1 (NIST P-384) curves. The
encrypt/decrypt driver ops return an error, as ECDSA is a signature
algorithm. The public key is provided as the raw affine coordinates
Qx || Qy, the private key as the raw scalar d, and the signature as the
raw pair r || s (each half the curve size, big-endian); the input to
sign/verify is a pre-computed message digest.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
crypto/akcipher-gcrypt.c.inc | 328 ++++++++++++++++++++++++++++++++++-
1 file changed, 327 insertions(+), 1 deletion(-)
diff --git a/crypto/akcipher-gcrypt.c.inc b/crypto/akcipher-gcrypt.c.inc
index bcf030fdec..ad8aeb7e74 100644
--- a/crypto/akcipher-gcrypt.c.inc
+++ b/crypto/akcipher-gcrypt.c.inc
@@ -36,6 +36,12 @@ typedef struct QCryptoGcryptRSA {
QCryptoHashAlgo hash_alg;
} QCryptoGcryptRSA;
+typedef struct QCryptoGcryptECDSA {
+ QCryptoAkCipher akcipher;
+ gcry_sexp_t key;
+ QCryptoCurveID curve_id;
+} QCryptoGcryptECDSA;
+
static void qcrypto_gcrypt_rsa_free(QCryptoAkCipher *akcipher)
{
QCryptoGcryptRSA *rsa = (QCryptoGcryptRSA *)akcipher;
@@ -53,6 +59,12 @@ static QCryptoGcryptRSA *qcrypto_gcrypt_rsa_new(
const uint8_t *key, size_t keylen,
Error **errp);
+static QCryptoGcryptECDSA *qcrypto_gcrypt_ecdsa_new(
+ const QCryptoAkCipherOptionsECDSA *opt,
+ QCryptoAkCipherKeyType type,
+ const uint8_t *key, size_t keylen,
+ Error **errp);
+
QCryptoAkCipher *qcrypto_akcipher_new(const QCryptoAkCipherOptions *opts,
QCryptoAkCipherKeyType type,
const uint8_t *key, size_t keylen,
@@ -63,6 +75,10 @@ QCryptoAkCipher *qcrypto_akcipher_new(const QCryptoAkCipherOptions *opts,
return (QCryptoAkCipher *)qcrypto_gcrypt_rsa_new(
&opts->u.rsa, type, key, keylen, errp);
+ case QCRYPTO_AK_CIPHER_ALGO_ECDSA:
+ return (QCryptoAkCipher *)qcrypto_gcrypt_ecdsa_new(
+ &opts->u.ecdsa, type, key, keylen, errp);
+
default:
error_setg(errp, "Unsupported algorithm: %u", opts->alg);
return NULL;
@@ -565,6 +581,306 @@ error:
}
+/*
+ * ECDSA support (sign and verify)
+ *
+ * Keys and signatures use raw big-endian formats:
+ * - public key: Qx || Qy, each 'coord_len' bytes
+ * - private key: the scalar d, 'coord_len' bytes
+ * - signature: r || s, each 'coord_len' bytes
+ * - the input to sign/verify is a raw message digest
+ */
+static const char *qcrypto_gcrypt_ecdsa_curve_name(QCryptoCurveID curve_id)
+{
+ switch (curve_id) {
+ case QCRYPTO_CURVE_ID_PRIME256V1:
+ return "NIST P-256";
+
+ case QCRYPTO_CURVE_ID_SECP384R1:
+ return "NIST P-384";
+
+ default:
+ return NULL;
+ }
+}
+
+static size_t qcrypto_gcrypt_ecdsa_coord_len(QCryptoCurveID curve_id)
+{
+ switch (curve_id) {
+ case QCRYPTO_CURVE_ID_PRIME256V1:
+ return 32;
+
+ case QCRYPTO_CURVE_ID_SECP384R1:
+ return 48;
+
+ default:
+ return 0;
+ }
+}
+
+static void qcrypto_gcrypt_ecdsa_free(QCryptoAkCipher *akcipher)
+{
+ QCryptoGcryptECDSA *ecdsa = (QCryptoGcryptECDSA *)akcipher;
+ if (!ecdsa) {
+ return;
+ }
+
+ gcry_sexp_release(ecdsa->key);
+ g_free(ecdsa);
+}
+
+static int qcrypto_gcrypt_ecdsa_encrypt(QCryptoAkCipher *akcipher,
+ const void *in, size_t in_len,
+ void *out, size_t out_len,
+ Error **errp)
+{
+ error_setg(errp, "ECDSA does not support encryption");
+ return -1;
+}
+
+static int qcrypto_gcrypt_ecdsa_decrypt(QCryptoAkCipher *akcipher,
+ const void *in, size_t in_len,
+ void *out, size_t out_len,
+ Error **errp)
+{
+ error_setg(errp, "ECDSA does not support decryption");
+ return -1;
+}
+
+/*
+ * Write an MPI into a fixed-length, big-endian, left-zero-padded buffer.
+ *
+ * gcry_mpi_print(GCRYMPI_FMT_USG) emits only the minimal number of bytes (it
+ * drops leading zeros), but an ECDSA r/s component must occupy exactly the
+ * curve size. Zero-fill the leading bytes and right-align the value, so a
+ * component whose most significant byte is zero still lands at the correct
+ * offset in the r || s output.
+ *
+ * Returns -1 if the value does not fit in 'len' bytes.
+ */
+static int qcrypto_gcrypt_mpi_to_buf(gcry_mpi_t mpi, uint8_t *buf, size_t len)
+{
+ size_t nbytes = (gcry_mpi_get_nbits(mpi) + 7) / 8;
+
+ if (nbytes > len) {
+ return -1;
+ }
+ memset(buf, 0, len - nbytes);
+ gcry_mpi_print(GCRYMPI_FMT_USG, buf + (len - nbytes), nbytes, NULL, mpi);
+ return 0;
+}
+
+static int qcrypto_gcrypt_ecdsa_sign(QCryptoAkCipher *akcipher,
+ const void *in, size_t in_len,
+ void *out, size_t out_len,
+ Error **errp)
+{
+ QCryptoGcryptECDSA *ecdsa = (QCryptoGcryptECDSA *)akcipher;
+ size_t coord_len = qcrypto_gcrypt_ecdsa_coord_len(ecdsa->curve_id);
+ gcry_sexp_t dgst_sexp = NULL;
+ gcry_sexp_t sig_sexp = NULL;
+ gcry_sexp_t r_sexp = NULL;
+ gcry_sexp_t s_sexp = NULL;
+ gcry_mpi_t r_mpi = NULL;
+ gcry_mpi_t s_mpi = NULL;
+ gcry_error_t err;
+ int ret = -1;
+
+ if (in_len == 0 || in_len > akcipher->max_dgst_len) {
+ error_setg(errp, "Invalid digest length %zu", in_len);
+ return ret;
+ }
+
+ if (out_len < coord_len * 2) {
+ error_setg(errp, "Signature buffer length %zu is less than %zu",
+ out_len, coord_len * 2);
+ return ret;
+ }
+
+ err = gcry_sexp_build(&dgst_sexp, NULL,
+ "(data (flags raw) (value %b))",
+ (int)in_len, in);
+ if (gcry_err_code(err) != 0) {
+ error_setg(errp, "Failed to build digest: %s/%s",
+ gcry_strsource(err), gcry_strerror(err));
+ goto cleanup;
+ }
+
+ err = gcry_pk_sign(&sig_sexp, dgst_sexp, ecdsa->key);
+ if (gcry_err_code(err) != 0) {
+ error_setg(errp, "Failed to make signature: %s/%s",
+ gcry_strsource(err), gcry_strerror(err));
+ goto cleanup;
+ }
+
+ /* S-expression of signature: (sig-val (ecdsa (r r-mpi) (s s-mpi))) */
+ r_sexp = gcry_sexp_find_token(sig_sexp, "r", 0);
+ s_sexp = gcry_sexp_find_token(sig_sexp, "s", 0);
+ if (!r_sexp || !s_sexp) {
+ error_setg(errp, "Invalid signature result");
+ goto cleanup;
+ }
+ r_mpi = gcry_sexp_nth_mpi(r_sexp, 1, GCRYMPI_FMT_USG);
+ s_mpi = gcry_sexp_nth_mpi(s_sexp, 1, GCRYMPI_FMT_USG);
+ if (!r_mpi || !s_mpi) {
+ error_setg(errp, "Invalid signature result");
+ goto cleanup;
+ }
+
+ /* output is r || s, each zero-padded to the curve size */
+ if (qcrypto_gcrypt_mpi_to_buf(r_mpi, out, coord_len) < 0 ||
+ qcrypto_gcrypt_mpi_to_buf(s_mpi, (uint8_t *)out + coord_len,
+ coord_len) < 0) {
+ error_setg(errp, "Signature component is too large");
+ goto cleanup;
+ }
+ ret = coord_len * 2;
+
+cleanup:
+ gcry_sexp_release(dgst_sexp);
+ gcry_sexp_release(sig_sexp);
+ gcry_sexp_release(r_sexp);
+ gcry_sexp_release(s_sexp);
+ gcry_mpi_release(r_mpi);
+ gcry_mpi_release(s_mpi);
+ return ret;
+}
+
+static int qcrypto_gcrypt_ecdsa_verify(QCryptoAkCipher *akcipher,
+ const void *in, size_t in_len,
+ const void *in2, size_t in2_len,
+ Error **errp)
+{
+ QCryptoGcryptECDSA *ecdsa = (QCryptoGcryptECDSA *)akcipher;
+ size_t coord_len = qcrypto_gcrypt_ecdsa_coord_len(ecdsa->curve_id);
+ gcry_sexp_t sig_sexp = NULL;
+ gcry_sexp_t dgst_sexp = NULL;
+ gcry_error_t err;
+ int ret = -1;
+
+ /* signature is r || s */
+ if (in_len != coord_len * 2) {
+ error_setg(errp, "Signature length %zu is not %zu",
+ in_len, coord_len * 2);
+ return ret;
+ }
+
+ if (in2_len == 0 || in2_len > akcipher->max_dgst_len) {
+ error_setg(errp, "Invalid digest length %zu", in2_len);
+ return ret;
+ }
+
+ err = gcry_sexp_build(&sig_sexp, NULL,
+ "(sig-val (ecdsa (r %b) (s %b)))",
+ (int)coord_len, in,
+ (int)coord_len, (const uint8_t *)in + coord_len);
+ if (gcry_err_code(err) != 0) {
+ error_setg(errp, "Failed to build signature: %s/%s",
+ gcry_strsource(err), gcry_strerror(err));
+ goto cleanup;
+ }
+
+ err = gcry_sexp_build(&dgst_sexp, NULL,
+ "(data (flags raw) (value %b))",
+ (int)in2_len, in2);
+ if (gcry_err_code(err) != 0) {
+ error_setg(errp, "Failed to build digest: %s/%s",
+ gcry_strsource(err), gcry_strerror(err));
+ goto cleanup;
+ }
+
+ err = gcry_pk_verify(sig_sexp, dgst_sexp, ecdsa->key);
+ if (gcry_err_code(err) != 0) {
+ error_setg(errp, "Failed to verify signature: %s/%s",
+ gcry_strsource(err), gcry_strerror(err));
+ goto cleanup;
+ }
+ ret = 0;
+
+cleanup:
+ gcry_sexp_release(sig_sexp);
+ gcry_sexp_release(dgst_sexp);
+ return ret;
+}
+
+QCryptoAkCipherDriver gcrypt_ecdsa = {
+ .encrypt = qcrypto_gcrypt_ecdsa_encrypt,
+ .decrypt = qcrypto_gcrypt_ecdsa_decrypt,
+ .sign = qcrypto_gcrypt_ecdsa_sign,
+ .verify = qcrypto_gcrypt_ecdsa_verify,
+ .free = qcrypto_gcrypt_ecdsa_free,
+};
+
+static QCryptoGcryptECDSA *qcrypto_gcrypt_ecdsa_new(
+ const QCryptoAkCipherOptionsECDSA *opt,
+ QCryptoAkCipherKeyType type,
+ const uint8_t *key, size_t keylen,
+ Error **errp)
+{
+ QCryptoGcryptECDSA *ecdsa;
+ const char *curve_name = qcrypto_gcrypt_ecdsa_curve_name(opt->curve_id);
+ size_t coord_len = qcrypto_gcrypt_ecdsa_coord_len(opt->curve_id);
+ g_autofree uint8_t *point = NULL;
+ gcry_error_t err;
+
+ if (!curve_name || coord_len == 0) {
+ error_setg(errp, "Unsupported curve id: %u", opt->curve_id);
+ return NULL;
+ }
+
+ ecdsa = g_new0(QCryptoGcryptECDSA, 1);
+ ecdsa->akcipher.driver = &gcrypt_ecdsa;
+ ecdsa->curve_id = opt->curve_id;
+ ecdsa->akcipher.max_dgst_len = coord_len;
+ ecdsa->akcipher.max_signature_len = coord_len * 2;
+
+ switch (type) {
+ case QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC:
+ /* public key: Qx || Qy */
+ if (keylen != coord_len * 2) {
+ error_setg(errp, "Public key length %zu is not %zu",
+ keylen, coord_len * 2);
+ goto error;
+ }
+ /* build uncompressed EC point: 0x04 || Qx || Qy */
+ point = g_malloc(1 + keylen);
+ point[0] = 0x04;
+ memcpy(point + 1, key, keylen);
+ err = gcry_sexp_build(&ecdsa->key, NULL,
+ "(public-key (ecc (curve %s) (q %b)))",
+ curve_name, (int)(1 + keylen), point);
+ break;
+
+ case QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE:
+ /* private key: the scalar d */
+ if (keylen != coord_len) {
+ error_setg(errp, "Private key length %zu is not %zu",
+ keylen, coord_len);
+ goto error;
+ }
+ err = gcry_sexp_build(&ecdsa->key, NULL,
+ "(private-key (ecc (curve %s) (d %b)))",
+ curve_name, (int)keylen, key);
+ break;
+
+ default:
+ error_setg(errp, "Unknown akcipher key type %d", type);
+ goto error;
+ }
+
+ if (gcry_err_code(err) != 0) {
+ error_setg(errp, "Failed to build ECDSA key: %s/%s",
+ gcry_strsource(err), gcry_strerror(err));
+ goto error;
+ }
+
+ return ecdsa;
+
+error:
+ qcrypto_gcrypt_ecdsa_free((QCryptoAkCipher *)ecdsa);
+ return NULL;
+}
+
bool qcrypto_akcipher_supports(QCryptoAkCipherOptions *opts)
{
switch (opts->alg) {
@@ -589,7 +905,17 @@ bool qcrypto_akcipher_supports(QCryptoAkCipherOptions *opts)
return false;
}
+ case QCRYPTO_AK_CIPHER_ALGO_ECDSA:
+ switch (opts->u.ecdsa.curve_id) {
+ case QCRYPTO_CURVE_ID_PRIME256V1:
+ case QCRYPTO_CURVE_ID_SECP384R1:
+ return true;
+
+ default:
+ return false;
+ }
+
default:
- return true;
+ return false;
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v1 3/8] crypto/akcipher: Support ECDSA sign/verify with nettle
2026-08-18 9:22 [PATCH v1 0/8] Add ECDSA akcipher support and the ASPEED SBC ECDSA engine for AST10x0 Jamin Lin
2026-08-18 9:22 ` [PATCH v1 1/8] qapi/crypto: Add ECDSA algorithm and curve id Jamin Lin
2026-08-18 9:22 ` [PATCH v1 2/8] crypto/akcipher: Support ECDSA sign/verify with gcrypt Jamin Lin
@ 2026-08-18 9:22 ` Jamin Lin
2026-08-18 9:22 ` [PATCH v1 4/8] tests/crypto: Add ECDSA sign/verify tests Jamin Lin
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jamin Lin @ 2026-08-18 9:22 UTC (permalink / raw)
To: Daniel P. Berrangé, Cédric Le Goater, Peter Maydell,
Steven Lee, Troy Lee, Kane Chen, Andrew Jeffery, Joel Stanley,
Eric Blake, Markus Armbruster, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, open list:All patches CC here,
open list:ASPEED BMCs
Cc: Jamin Lin, Troy Lee
Implement ECDSA signing and verification for the nettle/hogweed backend,
for the prime256v1 (NIST P-256) and secp384r1 (NIST P-384) curves.
A public key (Qx || Qy) is loaded into an ecc_point for verify;
a private key (the scalar d) into an ecc_scalar for sign,
using nettle's ecdsa_sign() / ecdsa_verify().
The encrypt/decrypt driver ops return an error.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
crypto/akcipher-nettle.c.inc | 267 +++++++++++++++++++++++++++++++++++
1 file changed, 267 insertions(+)
diff --git a/crypto/akcipher-nettle.c.inc b/crypto/akcipher-nettle.c.inc
index 1d4bd6960e..34bdc3de27 100644
--- a/crypto/akcipher-nettle.c.inc
+++ b/crypto/akcipher-nettle.c.inc
@@ -20,6 +20,10 @@
*/
#include <nettle/rsa.h>
+#include <nettle/ecdsa.h>
+#include <nettle/ecc-curve.h>
+#include <nettle/ecc.h>
+#include <nettle/bignum.h>
#include "qemu/osdep.h"
#include "qemu/host-utils.h"
@@ -55,6 +59,12 @@ static QCryptoAkCipher *qcrypto_nettle_rsa_new(
const uint8_t *key, size_t keylen,
Error **errp);
+static QCryptoAkCipher *qcrypto_nettle_ecdsa_new(
+ const QCryptoAkCipherOptionsECDSA *opt,
+ QCryptoAkCipherKeyType type,
+ const uint8_t *key, size_t keylen,
+ Error **errp);
+
QCryptoAkCipher *qcrypto_akcipher_new(const QCryptoAkCipherOptions *opts,
QCryptoAkCipherKeyType type,
const uint8_t *key, size_t keylen,
@@ -64,6 +74,10 @@ QCryptoAkCipher *qcrypto_akcipher_new(const QCryptoAkCipherOptions *opts,
case QCRYPTO_AK_CIPHER_ALGO_RSA:
return qcrypto_nettle_rsa_new(&opts->u.rsa, type, key, keylen, errp);
+ case QCRYPTO_AK_CIPHER_ALGO_ECDSA:
+ return qcrypto_nettle_ecdsa_new(&opts->u.ecdsa, type, key, keylen,
+ errp);
+
default:
error_setg(errp, "Unsupported algorithm: %u", opts->alg);
return NULL;
@@ -422,6 +436,249 @@ error:
}
+/*
+ * ECDSA support (sign and verify)
+ *
+ * Keys and signatures use raw big-endian formats:
+ * - public key: Qx || Qy, each 'coord_len' bytes
+ * - private key: the scalar d, 'coord_len' bytes
+ * - signature: r || s, each 'coord_len' bytes
+ * - the input to sign/verify is a raw message digest
+ */
+typedef struct QCryptoNettleECDSA {
+ QCryptoAkCipher akcipher;
+ QCryptoAkCipherKeyType type;
+ struct ecc_point pub;
+ struct ecc_scalar priv;
+ QCryptoCurveID curve_id;
+} QCryptoNettleECDSA;
+
+static const struct ecc_curve *qcrypto_nettle_ecdsa_curve(
+ QCryptoCurveID curve_id)
+{
+ switch (curve_id) {
+ case QCRYPTO_CURVE_ID_PRIME256V1:
+ return nettle_get_secp_256r1();
+
+ case QCRYPTO_CURVE_ID_SECP384R1:
+ return nettle_get_secp_384r1();
+
+ default:
+ return NULL;
+ }
+}
+
+static size_t qcrypto_nettle_ecdsa_coord_len(QCryptoCurveID curve_id)
+{
+ switch (curve_id) {
+ case QCRYPTO_CURVE_ID_PRIME256V1:
+ return 32;
+
+ case QCRYPTO_CURVE_ID_SECP384R1:
+ return 48;
+
+ default:
+ return 0;
+ }
+}
+
+static void qcrypto_nettle_ecdsa_free(QCryptoAkCipher *akcipher)
+{
+ QCryptoNettleECDSA *ecdsa = (QCryptoNettleECDSA *)akcipher;
+ if (!ecdsa) {
+ return;
+ }
+
+ if (ecdsa->type == QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE) {
+ ecc_scalar_clear(&ecdsa->priv);
+ } else {
+ ecc_point_clear(&ecdsa->pub);
+ }
+ g_free(ecdsa);
+}
+
+static int qcrypto_nettle_ecdsa_encrypt(QCryptoAkCipher *akcipher,
+ const void *in, size_t in_len,
+ void *out, size_t out_len,
+ Error **errp)
+{
+ error_setg(errp, "ECDSA does not support encryption");
+ return -1;
+}
+
+static int qcrypto_nettle_ecdsa_decrypt(QCryptoAkCipher *akcipher,
+ const void *in, size_t in_len,
+ void *out, size_t out_len,
+ Error **errp)
+{
+ error_setg(errp, "ECDSA does not support decryption");
+ return -1;
+}
+
+static int qcrypto_nettle_ecdsa_sign(QCryptoAkCipher *akcipher,
+ const void *in, size_t in_len,
+ void *out, size_t out_len,
+ Error **errp)
+{
+ QCryptoNettleECDSA *ecdsa = (QCryptoNettleECDSA *)akcipher;
+ size_t coord_len = qcrypto_nettle_ecdsa_coord_len(ecdsa->curve_id);
+ struct dsa_signature sig;
+
+ if (ecdsa->type != QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE) {
+ error_setg(errp, "ECDSA sign requires a private key");
+ return -1;
+ }
+
+ if (in_len == 0 || in_len > akcipher->max_dgst_len) {
+ error_setg(errp, "Invalid digest length %zu", in_len);
+ return -1;
+ }
+
+ if (out_len < coord_len * 2) {
+ error_setg(errp, "Signature buffer length %zu is less than %zu",
+ out_len, coord_len * 2);
+ return -1;
+ }
+
+ dsa_signature_init(&sig);
+ ecdsa_sign(&ecdsa->priv, NULL, wrap_nettle_random_func, in_len, in, &sig);
+
+ /* output is r || s, each zero-padded to the curve size */
+ nettle_mpz_get_str_256(coord_len, out, sig.r);
+ nettle_mpz_get_str_256(coord_len, (uint8_t *)out + coord_len, sig.s);
+
+ dsa_signature_clear(&sig);
+ return coord_len * 2;
+}
+
+static int qcrypto_nettle_ecdsa_verify(QCryptoAkCipher *akcipher,
+ const void *in, size_t in_len,
+ const void *in2, size_t in2_len,
+ Error **errp)
+{
+ QCryptoNettleECDSA *ecdsa = (QCryptoNettleECDSA *)akcipher;
+ size_t coord_len = qcrypto_nettle_ecdsa_coord_len(ecdsa->curve_id);
+ struct dsa_signature sig;
+ int ret = -1;
+
+ /* signature is r || s */
+ if (in_len != coord_len * 2) {
+ error_setg(errp, "Signature length %zu is not %zu",
+ in_len, coord_len * 2);
+ return ret;
+ }
+
+ if (in2_len == 0 || in2_len > akcipher->max_dgst_len) {
+ error_setg(errp, "Invalid digest length %zu", in2_len);
+ return ret;
+ }
+
+ dsa_signature_init(&sig);
+ nettle_mpz_set_str_256_u(sig.r, coord_len, in);
+ nettle_mpz_set_str_256_u(sig.s, coord_len, (const uint8_t *)in + coord_len);
+
+ if (ecdsa_verify(&ecdsa->pub, in2_len, in2, &sig) == 1) {
+ ret = 0;
+ } else {
+ error_setg(errp, "Failed to verify signature");
+ }
+
+ dsa_signature_clear(&sig);
+ return ret;
+}
+
+QCryptoAkCipherDriver nettle_ecdsa = {
+ .encrypt = qcrypto_nettle_ecdsa_encrypt,
+ .decrypt = qcrypto_nettle_ecdsa_decrypt,
+ .sign = qcrypto_nettle_ecdsa_sign,
+ .verify = qcrypto_nettle_ecdsa_verify,
+ .free = qcrypto_nettle_ecdsa_free,
+};
+
+static QCryptoAkCipher *qcrypto_nettle_ecdsa_new(
+ const QCryptoAkCipherOptionsECDSA *opt,
+ QCryptoAkCipherKeyType type,
+ const uint8_t *key, size_t keylen,
+ Error **errp)
+{
+ QCryptoNettleECDSA *ecdsa;
+ const struct ecc_curve *curve = qcrypto_nettle_ecdsa_curve(opt->curve_id);
+ size_t coord_len = qcrypto_nettle_ecdsa_coord_len(opt->curve_id);
+ mpz_t x;
+ mpz_t y;
+
+ if (!curve || coord_len == 0) {
+ error_setg(errp, "Unsupported curve id: %u", opt->curve_id);
+ return NULL;
+ }
+
+ switch (type) {
+ case QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC:
+ if (keylen != coord_len * 2) {
+ error_setg(errp, "Public key length %zu is not %zu",
+ keylen, coord_len * 2);
+ return NULL;
+ }
+ break;
+
+ case QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE:
+ if (keylen != coord_len) {
+ error_setg(errp, "Private key length %zu is not %zu",
+ keylen, coord_len);
+ return NULL;
+ }
+ break;
+
+ default:
+ error_setg(errp, "Unknown akcipher key type %d", type);
+ return NULL;
+ }
+
+ ecdsa = g_new0(QCryptoNettleECDSA, 1);
+ ecdsa->akcipher.driver = &nettle_ecdsa;
+ ecdsa->type = type;
+ ecdsa->curve_id = opt->curve_id;
+ ecdsa->akcipher.max_dgst_len = coord_len;
+ ecdsa->akcipher.max_signature_len = coord_len * 2;
+
+ if (type == QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC) {
+ /* public key: Qx || Qy */
+ ecc_point_init(&ecdsa->pub, curve);
+ mpz_init(x);
+ mpz_init(y);
+ nettle_mpz_set_str_256_u(x, coord_len, key);
+ nettle_mpz_set_str_256_u(y, coord_len,
+ (const uint8_t *)key + coord_len);
+
+ if (!ecc_point_set(&ecdsa->pub, x, y)) {
+ error_setg(errp, "Invalid ECDSA public key (not on curve)");
+ mpz_clear(x);
+ mpz_clear(y);
+ qcrypto_nettle_ecdsa_free((QCryptoAkCipher *)ecdsa);
+ return NULL;
+ }
+
+ mpz_clear(x);
+ mpz_clear(y);
+ } else {
+ /* private key: the scalar d */
+ ecc_scalar_init(&ecdsa->priv, curve);
+ mpz_init(x);
+ nettle_mpz_set_str_256_u(x, coord_len, key);
+
+ if (!ecc_scalar_set(&ecdsa->priv, x)) {
+ error_setg(errp, "Invalid ECDSA private key");
+ mpz_clear(x);
+ qcrypto_nettle_ecdsa_free((QCryptoAkCipher *)ecdsa);
+ return NULL;
+ }
+
+ mpz_clear(x);
+ }
+
+ return (QCryptoAkCipher *)ecdsa;
+}
+
bool qcrypto_akcipher_supports(QCryptoAkCipherOptions *opts)
{
switch (opts->alg) {
@@ -445,6 +702,16 @@ bool qcrypto_akcipher_supports(QCryptoAkCipherOptions *opts)
}
break;
+ case QCRYPTO_AK_CIPHER_ALGO_ECDSA:
+ switch (opts->u.ecdsa.curve_id) {
+ case QCRYPTO_CURVE_ID_PRIME256V1:
+ case QCRYPTO_CURVE_ID_SECP384R1:
+ return true;
+
+ default:
+ return false;
+ }
+
default:
return false;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v1 4/8] tests/crypto: Add ECDSA sign/verify tests
2026-08-18 9:22 [PATCH v1 0/8] Add ECDSA akcipher support and the ASPEED SBC ECDSA engine for AST10x0 Jamin Lin
` (2 preceding siblings ...)
2026-08-18 9:22 ` [PATCH v1 3/8] crypto/akcipher: Support ECDSA sign/verify with nettle Jamin Lin
@ 2026-08-18 9:22 ` Jamin Lin
2026-08-18 9:22 ` [PATCH v1 5/8] hw/misc/aspeed_sbc: Support the ECDSA verify command Jamin Lin
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jamin Lin @ 2026-08-18 9:22 UTC (permalink / raw)
To: Daniel P. Berrangé, Cédric Le Goater, Peter Maydell,
Steven Lee, Troy Lee, Kane Chen, Andrew Jeffery, Joel Stanley,
Eric Blake, Markus Armbruster, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, open list:All patches CC here,
open list:ASPEED BMCs
Cc: Jamin Lin, Troy Lee
Add unit tests for ECDSA prime256v1 and secp384r1, using the
RFC 6979 (Deterministic ECDSA) "sample" known-answer vectors. The same
key pair drives the verify test (against the RFC's known signature) and
the sign test, which signs the digest with the private key and checks
that the result verifies with the public key. Each test skips
(g_test_skip) when the crypto backend does not support ECDSA, via
qcrypto_akcipher_supports().
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
tests/unit/test-crypto-akcipher.c | 236 ++++++++++++++++++++++++++++++
1 file changed, 236 insertions(+)
diff --git a/tests/unit/test-crypto-akcipher.c b/tests/unit/test-crypto-akcipher.c
index 53c2211ba8..fb1a6163e9 100644
--- a/tests/unit/test-crypto-akcipher.c
+++ b/tests/unit/test-crypto-akcipher.c
@@ -658,6 +658,117 @@ static const uint8_t rsa_public_key_extra_elem[] = {
0x02, 0x06, 0xe1, 0x22, 0xdb, 0xe1, 0x22, 0xdb,
};
+/*
+ * ECDSA known-answer vectors from RFC 6979 (Deterministic ECDSA), message
+ * "sample", decoded into raw big-endian form. The same key pair drives both
+ * the verify test (against the RFC's known signature) and the sign round-trip
+ * test:
+ * - private key: the scalar d (curve size)
+ * - public key: Qx || Qy (2 * curve size)
+ * - dgst: the message digest (curve size)
+ * - signature: r || s (2 * curve size)
+ *
+ * prime256v1 / SHA-256 (RFC 6979 A.2.5):
+ */
+static const uint8_t ecdsa_p256_privkey[32] = {
+ 0xc9, 0xaf, 0xa9, 0xd8, 0x45, 0xba, 0x75, 0x16,
+ 0x6b, 0x5c, 0x21, 0x57, 0x67, 0xb1, 0xd6, 0x93,
+ 0x4e, 0x50, 0xc3, 0xdb, 0x36, 0xe8, 0x9b, 0x12,
+ 0x7b, 0x8a, 0x62, 0x2b, 0x12, 0x0f, 0x67, 0x21,
+};
+
+static const uint8_t ecdsa_p256_pubkey[64] = {
+ 0x60, 0xfe, 0xd4, 0xba, 0x25, 0x5a, 0x9d, 0x31,
+ 0xc9, 0x61, 0xeb, 0x74, 0xc6, 0x35, 0x6d, 0x68,
+ 0xc0, 0x49, 0xb8, 0x92, 0x3b, 0x61, 0xfa, 0x6c,
+ 0xe6, 0x69, 0x62, 0x2e, 0x60, 0xf2, 0x9f, 0xb6,
+ 0x79, 0x03, 0xfe, 0x10, 0x08, 0xb8, 0xbc, 0x99,
+ 0xa4, 0x1a, 0xe9, 0xe9, 0x56, 0x28, 0xbc, 0x64,
+ 0xf2, 0xf1, 0xb2, 0x0c, 0x2d, 0x7e, 0x9f, 0x51,
+ 0x77, 0xa3, 0xc2, 0x94, 0xd4, 0x46, 0x22, 0x99,
+};
+
+static const uint8_t ecdsa_p256_dgst[32] = {
+ 0xaf, 0x2b, 0xdb, 0xe1, 0xaa, 0x9b, 0x6e, 0xc1,
+ 0xe2, 0xad, 0xe1, 0xd6, 0x94, 0xf4, 0x1f, 0xc7,
+ 0x1a, 0x83, 0x1d, 0x02, 0x68, 0xe9, 0x89, 0x15,
+ 0x62, 0x11, 0x3d, 0x8a, 0x62, 0xad, 0xd1, 0xbf,
+};
+
+static const uint8_t ecdsa_p256_signature[64] = {
+ 0xef, 0xd4, 0x8b, 0x2a, 0xac, 0xb6, 0xa8, 0xfd,
+ 0x11, 0x40, 0xdd, 0x9c, 0xd4, 0x5e, 0x81, 0xd6,
+ 0x9d, 0x2c, 0x87, 0x7b, 0x56, 0xaa, 0xf9, 0x91,
+ 0xc3, 0x4d, 0x0e, 0xa8, 0x4e, 0xaf, 0x37, 0x16,
+ 0xf7, 0xcb, 0x1c, 0x94, 0x2d, 0x65, 0x7c, 0x41,
+ 0xd4, 0x36, 0xc7, 0xa1, 0xb6, 0xe2, 0x9f, 0x65,
+ 0xf3, 0xe9, 0x00, 0xdb, 0xb9, 0xaf, 0xf4, 0x06,
+ 0x4d, 0xc4, 0xab, 0x2f, 0x84, 0x3a, 0xcd, 0xa8,
+};
+
+/* secp384r1 / SHA-384 (RFC 6979 A.2.6): */
+static const uint8_t ecdsa_p384_privkey[48] = {
+ 0x6b, 0x9d, 0x3d, 0xad, 0x2e, 0x1b, 0x8c, 0x1c,
+ 0x05, 0xb1, 0x98, 0x75, 0xb6, 0x65, 0x9f, 0x4d,
+ 0xe2, 0x3c, 0x3b, 0x66, 0x7b, 0xf2, 0x97, 0xba,
+ 0x9a, 0xa4, 0x77, 0x40, 0x78, 0x71, 0x37, 0xd8,
+ 0x96, 0xd5, 0x72, 0x4e, 0x4c, 0x70, 0xa8, 0x25,
+ 0xf8, 0x72, 0xc9, 0xea, 0x60, 0xd2, 0xed, 0xf5,
+};
+
+static const uint8_t ecdsa_p384_pubkey[96] = {
+ 0xec, 0x3a, 0x4e, 0x41, 0x5b, 0x4e, 0x19, 0xa4,
+ 0x56, 0x86, 0x18, 0x02, 0x9f, 0x42, 0x7f, 0xa5,
+ 0xda, 0x9a, 0x8b, 0xc4, 0xae, 0x92, 0xe0, 0x2e,
+ 0x06, 0xaa, 0xe5, 0x28, 0x6b, 0x30, 0x0c, 0x64,
+ 0xde, 0xf8, 0xf0, 0xea, 0x90, 0x55, 0x86, 0x60,
+ 0x64, 0xa2, 0x54, 0x51, 0x54, 0x80, 0xbc, 0x13,
+ 0x80, 0x15, 0xd9, 0xb7, 0x2d, 0x7d, 0x57, 0x24,
+ 0x4e, 0xa8, 0xef, 0x9a, 0xc0, 0xc6, 0x21, 0x89,
+ 0x67, 0x08, 0xa5, 0x93, 0x67, 0xf9, 0xdf, 0xb9,
+ 0xf5, 0x4c, 0xa8, 0x4b, 0x3f, 0x1c, 0x9d, 0xb1,
+ 0x28, 0x8b, 0x23, 0x1c, 0x3a, 0xe0, 0xd4, 0xfe,
+ 0x73, 0x44, 0xfd, 0x25, 0x33, 0x26, 0x47, 0x20,
+};
+
+static const uint8_t ecdsa_p384_dgst[48] = {
+ 0x9a, 0x90, 0x83, 0x50, 0x5b, 0xc9, 0x22, 0x76,
+ 0xae, 0xc4, 0xbe, 0x31, 0x26, 0x96, 0xef, 0x7b,
+ 0xf3, 0xbf, 0x60, 0x3f, 0x4b, 0xbd, 0x38, 0x11,
+ 0x96, 0xa0, 0x29, 0xf3, 0x40, 0x58, 0x53, 0x12,
+ 0x31, 0x3b, 0xca, 0x4a, 0x9b, 0x5b, 0x89, 0x0e,
+ 0xfe, 0xe4, 0x2c, 0x77, 0xb1, 0xee, 0x25, 0xfe,
+};
+
+static const uint8_t ecdsa_p384_signature[96] = {
+ 0x94, 0xed, 0xbb, 0x92, 0xa5, 0xec, 0xb8, 0xaa,
+ 0xd4, 0x73, 0x6e, 0x56, 0xc6, 0x91, 0x91, 0x6b,
+ 0x3f, 0x88, 0x14, 0x06, 0x66, 0xce, 0x9f, 0xa7,
+ 0x3d, 0x64, 0xc4, 0xea, 0x95, 0xad, 0x13, 0x3c,
+ 0x81, 0xa6, 0x48, 0x15, 0x2e, 0x44, 0xac, 0xf9,
+ 0x6e, 0x36, 0xdd, 0x1e, 0x80, 0xfa, 0xbe, 0x46,
+ 0x99, 0xef, 0x4a, 0xeb, 0x15, 0xf1, 0x78, 0xce,
+ 0xa1, 0xfe, 0x40, 0xdb, 0x26, 0x03, 0x13, 0x8f,
+ 0x13, 0x0e, 0x74, 0x0a, 0x19, 0x62, 0x45, 0x26,
+ 0x20, 0x3b, 0x63, 0x51, 0xd0, 0xa3, 0xa9, 0x4f,
+ 0xa3, 0x29, 0xc1, 0x45, 0x78, 0x6e, 0x67, 0x9e,
+ 0x7b, 0x82, 0xc7, 0x1a, 0x38, 0x62, 0x8a, 0xc8,
+};
+
+typedef struct QCryptoECDSATestData QCryptoECDSATestData;
+struct QCryptoECDSATestData {
+ const char *path;
+ QCryptoCurveID curve_id;
+ const uint8_t *priv_key;
+ size_t priv_key_len;
+ const uint8_t *pub_key;
+ size_t pub_key_len;
+ const uint8_t *dgst;
+ size_t dlen;
+ const uint8_t *signature;
+ size_t slen;
+};
+
typedef struct QCryptoRSAKeyTestData QCryptoRSAKeyTestData;
struct QCryptoRSAKeyTestData {
const char *path;
@@ -969,6 +1080,120 @@ static void test_rsakey(const void *opaque)
g_assert(qcrypto_akcipher_max_dgst_len(key) == data->exp_key_len);
}
+static QCryptoECDSATestData ecdsa_test_data[] = {
+ {
+ .path = "/crypto/akcipher/ecdsa-prime256v1",
+ .curve_id = QCRYPTO_CURVE_ID_PRIME256V1,
+ .priv_key = ecdsa_p256_privkey,
+ .priv_key_len = sizeof(ecdsa_p256_privkey),
+ .pub_key = ecdsa_p256_pubkey,
+ .pub_key_len = sizeof(ecdsa_p256_pubkey),
+ .dgst = ecdsa_p256_dgst,
+ .dlen = sizeof(ecdsa_p256_dgst),
+ .signature = ecdsa_p256_signature,
+ .slen = sizeof(ecdsa_p256_signature),
+ },
+ {
+ .path = "/crypto/akcipher/ecdsa-secp384r1",
+ .curve_id = QCRYPTO_CURVE_ID_SECP384R1,
+ .priv_key = ecdsa_p384_privkey,
+ .priv_key_len = sizeof(ecdsa_p384_privkey),
+ .pub_key = ecdsa_p384_pubkey,
+ .pub_key_len = sizeof(ecdsa_p384_pubkey),
+ .dgst = ecdsa_p384_dgst,
+ .dlen = sizeof(ecdsa_p384_dgst),
+ .signature = ecdsa_p384_signature,
+ .slen = sizeof(ecdsa_p384_signature),
+ },
+ /* Add more curves here as they gain backend support. */
+};
+
+static void test_ecdsa_verify(const void *opaque)
+{
+ const QCryptoECDSATestData *data = opaque;
+ QCryptoAkCipherOptions opt = {
+ .alg = QCRYPTO_AK_CIPHER_ALGO_ECDSA,
+ .u.ecdsa = {
+ .curve_id = data->curve_id,
+ },
+ };
+ g_autoptr(QCryptoAkCipher) pub_key = NULL;
+ g_autofree uint8_t *signature = NULL;
+ g_autofree uint8_t *dgst = NULL;
+
+ if (!qcrypto_akcipher_supports(&opt)) {
+ g_test_skip("ECDSA is not supported by the crypto backend");
+ return;
+ }
+
+ pub_key = qcrypto_akcipher_new(&opt, QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC,
+ data->pub_key, data->pub_key_len,
+ &error_abort);
+ g_assert(pub_key != NULL);
+ g_assert(qcrypto_akcipher_max_signature_len(pub_key) == data->slen);
+ g_assert(qcrypto_akcipher_max_dgst_len(pub_key) == data->dlen);
+
+ /* A valid signature must verify. */
+ g_assert(qcrypto_akcipher_verify(pub_key, data->signature, data->slen,
+ data->dgst, data->dlen,
+ &error_abort) == 0);
+
+ /* A tampered signature must fail (error is expected, so ignore it). */
+ signature = g_memdup2(data->signature, data->slen);
+ signature[0]++;
+ g_assert(qcrypto_akcipher_verify(pub_key, signature, data->slen,
+ data->dgst, data->dlen, NULL) != 0);
+
+ /* A tampered digest must also fail. */
+ dgst = g_memdup2(data->dgst, data->dlen);
+ dgst[0]++;
+ g_assert(qcrypto_akcipher_verify(pub_key, data->signature, data->slen,
+ dgst, data->dlen, NULL) != 0);
+}
+
+static void test_ecdsa_sign(const void *opaque)
+{
+ const QCryptoECDSATestData *data = opaque;
+ QCryptoAkCipherOptions opt = {
+ .alg = QCRYPTO_AK_CIPHER_ALGO_ECDSA,
+ .u.ecdsa = {
+ .curve_id = data->curve_id,
+ },
+ };
+ g_autoptr(QCryptoAkCipher) priv_key = NULL;
+ g_autoptr(QCryptoAkCipher) pub_key = NULL;
+ g_autofree uint8_t *signature = NULL;
+ int slen;
+
+ if (!qcrypto_akcipher_supports(&opt)) {
+ g_test_skip("ECDSA is not supported by the crypto backend");
+ return;
+ }
+
+ priv_key = qcrypto_akcipher_new(&opt, QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE,
+ data->priv_key, data->priv_key_len,
+ &error_abort);
+ g_assert(priv_key != NULL);
+
+ /*
+ * Sign the digest.
+ * ECDSA signatures are randomized so only length is fixed.
+ */
+ slen = qcrypto_akcipher_max_signature_len(priv_key);
+ signature = g_new0(uint8_t, slen);
+ g_assert(qcrypto_akcipher_sign(priv_key, data->dgst, data->dlen,
+ signature, slen, &error_abort) == slen);
+
+ /* The freshly produced signature must verify with the public key. */
+ pub_key = qcrypto_akcipher_new(&opt, QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC,
+ data->pub_key, data->pub_key_len,
+ &error_abort);
+ g_assert(pub_key != NULL);
+ g_assert(qcrypto_akcipher_verify(pub_key, signature, slen,
+ data->dgst, data->dlen,
+ &error_abort) == 0);
+}
+
int main(int argc, char **argv)
{
size_t i;
@@ -985,6 +1210,17 @@ int main(int argc, char **argv)
&rsakey_test_data[i],
test_rsakey);
}
+ for (i = 0; i < G_N_ELEMENTS(ecdsa_test_data); i++) {
+ g_autofree char *verify_path =
+ g_strdup_printf("%s-verify", ecdsa_test_data[i].path);
+ g_autofree char *sign_path =
+ g_strdup_printf("%s-sign", ecdsa_test_data[i].path);
+
+ g_test_add_data_func(verify_path, &ecdsa_test_data[i],
+ test_ecdsa_verify);
+ g_test_add_data_func(sign_path, &ecdsa_test_data[i],
+ test_ecdsa_sign);
+ }
return g_test_run();
}
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v1 5/8] hw/misc/aspeed_sbc: Support the ECDSA verify command
2026-08-18 9:22 [PATCH v1 0/8] Add ECDSA akcipher support and the ASPEED SBC ECDSA engine for AST10x0 Jamin Lin
` (3 preceding siblings ...)
2026-08-18 9:22 ` [PATCH v1 4/8] tests/crypto: Add ECDSA sign/verify tests Jamin Lin
@ 2026-08-18 9:22 ` Jamin Lin
2026-08-18 9:23 ` [PATCH v1 6/8] hw/misc/aspeed_sbc: Increase register space to 0x1000 Jamin Lin
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jamin Lin @ 2026-08-18 9:22 UTC (permalink / raw)
To: Daniel P. Berrangé, Cédric Le Goater, Peter Maydell,
Steven Lee, Troy Lee, Kane Chen, Andrew Jeffery, Joel Stanley,
Eric Blake, Markus Armbruster, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, open list:All patches CC here,
open list:ASPEED BMCs
Cc: Jamin Lin, Troy Lee
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 <jamin_lin@aspeedtech.com>
---
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
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v1 6/8] hw/misc/aspeed_sbc: Increase register space to 0x1000
2026-08-18 9:22 [PATCH v1 0/8] Add ECDSA akcipher support and the ASPEED SBC ECDSA engine for AST10x0 Jamin Lin
` (4 preceding siblings ...)
2026-08-18 9:22 ` [PATCH v1 5/8] hw/misc/aspeed_sbc: Support the ECDSA verify command Jamin Lin
@ 2026-08-18 9:23 ` Jamin Lin
2026-08-18 9:23 ` [PATCH v1 7/8] hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model Jamin Lin
2026-08-18 9:23 ` [PATCH v1 8/8] tests/qtest: Add ASPEED SBC ECDSA engine test Jamin Lin
7 siblings, 0 replies; 9+ messages in thread
From: Jamin Lin @ 2026-08-18 9:23 UTC (permalink / raw)
To: Daniel P. Berrangé, Cédric Le Goater, Peter Maydell,
Steven Lee, Troy Lee, Kane Chen, Andrew Jeffery, Joel Stanley,
Eric Blake, Markus Armbruster, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, open list:All patches CC here,
open list:ASPEED BMCs
Cc: Jamin Lin, Troy Lee
The SBC controller register space is 0x1000.
Increase ASPEED_SBC_NR_REGS accordingly to cover the
full register space.
Bump the VMState version to 2.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
hw/misc/aspeed_sbc.c | 4 ++--
include/hw/misc/aspeed_sbc.h | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/hw/misc/aspeed_sbc.c b/hw/misc/aspeed_sbc.c
index 724f4cb06c..951dde5126 100644
--- a/hw/misc/aspeed_sbc.c
+++ b/hw/misc/aspeed_sbc.c
@@ -464,8 +464,8 @@ static void aspeed_sbc_realize(DeviceState *dev, Error **errp)
static const VMStateDescription vmstate_aspeed_sbc = {
.name = TYPE_ASPEED_SBC,
- .version_id = 1,
- .minimum_version_id = 1,
+ .version_id = 2,
+ .minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_UINT32_ARRAY(regs, AspeedSBCState, ASPEED_SBC_NR_REGS),
VMSTATE_END_OF_LIST(),
diff --git a/include/hw/misc/aspeed_sbc.h b/include/hw/misc/aspeed_sbc.h
index 9b04a10aed..e37c3a12dd 100644
--- a/include/hw/misc/aspeed_sbc.h
+++ b/include/hw/misc/aspeed_sbc.h
@@ -17,7 +17,7 @@
#define TYPE_ASPEED_AST10X0_SBC TYPE_ASPEED_SBC "-ast10x0"
OBJECT_DECLARE_TYPE(AspeedSBCState, AspeedSBCClass, ASPEED_SBC)
-#define ASPEED_SBC_NR_REGS (0x93c >> 2)
+#define ASPEED_SBC_NR_REGS (0x1000 >> 2)
#define QSR_AES BIT(27)
#define QSR_RSA1024 (0x0 << 12)
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v1 7/8] hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model
2026-08-18 9:22 [PATCH v1 0/8] Add ECDSA akcipher support and the ASPEED SBC ECDSA engine for AST10x0 Jamin Lin
` (5 preceding siblings ...)
2026-08-18 9:23 ` [PATCH v1 6/8] hw/misc/aspeed_sbc: Increase register space to 0x1000 Jamin Lin
@ 2026-08-18 9:23 ` Jamin Lin
2026-08-18 9:23 ` [PATCH v1 8/8] tests/qtest: Add ASPEED SBC ECDSA engine test Jamin Lin
7 siblings, 0 replies; 9+ messages in thread
From: Jamin Lin @ 2026-08-18 9:23 UTC (permalink / raw)
To: Daniel P. Berrangé, Cédric Le Goater, Peter Maydell,
Steven Lee, Troy Lee, Kane Chen, Andrew Jeffery, Joel Stanley,
Eric Blake, Markus Armbruster, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, open list:All patches CC here,
open list:ASPEED BMCs
Cc: Jamin Lin, Troy Lee
Pass the internal SEC SRAM region and its base address to the secure
boot controller (via the 'sram' link and 'sram-base' property) so its
ECDSA engine can read the public key, signature and digest that the
guest firmware stages there before triggering a verify.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
hw/arm/aspeed_ast10x0.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/hw/arm/aspeed_ast10x0.c b/hw/arm/aspeed_ast10x0.c
index 93c81195b5..8e34870856 100644
--- a/hw/arm/aspeed_ast10x0.c
+++ b/hw/arm/aspeed_ast10x0.c
@@ -357,6 +357,10 @@ static bool aspeed_soc_ast10x0_realize(Aspeed10x0SoCState *a, Error **errp)
}
/* Secure Boot Controller */
+ object_property_set_link(OBJECT(&s->sbc), "sram", OBJECT(&s->sram[1]),
+ &error_abort);
+ qdev_prop_set_uint64(DEVICE(&s->sbc), "sram-base",
+ sc->memmap[ASPEED_DEV_SRAM1]);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->sbc), errp)) {
return false;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v1 8/8] tests/qtest: Add ASPEED SBC ECDSA engine test
2026-08-18 9:22 [PATCH v1 0/8] Add ECDSA akcipher support and the ASPEED SBC ECDSA engine for AST10x0 Jamin Lin
` (6 preceding siblings ...)
2026-08-18 9:23 ` [PATCH v1 7/8] hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model Jamin Lin
@ 2026-08-18 9:23 ` Jamin Lin
7 siblings, 0 replies; 9+ messages in thread
From: Jamin Lin @ 2026-08-18 9:23 UTC (permalink / raw)
To: Daniel P. Berrangé, Cédric Le Goater, Peter Maydell,
Steven Lee, Troy Lee, Kane Chen, Andrew Jeffery, Joel Stanley,
Eric Blake, Markus Armbruster, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, open list:All patches CC here,
open list:ASPEED BMCs
Cc: Jamin Lin, Troy Lee
Add a qtest for the ASPEED secure boot controller ECDSA engine. It stages
the public key, signature and SHA-384 digest of a secp384r1 known-answer
vector (from the Linux kernel crypto self-test manager,
ecdsa_nist_p384_tv_template) into the SEC SRAM, triggers the engine's
verify command and checks the status register: a valid signature reports
done + pass, and a tampered signature reports done without pass.
The test is skipped when the crypto backend does not support ECDSA, via
qcrypto_akcipher_supports().
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
tests/qtest/aspeed-sbc-test.c | 190 ++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 2 +
2 files changed, 192 insertions(+)
create mode 100644 tests/qtest/aspeed-sbc-test.c
diff --git a/tests/qtest/aspeed-sbc-test.c b/tests/qtest/aspeed-sbc-test.c
new file mode 100644
index 0000000000..927a54dffb
--- /dev/null
+++ b/tests/qtest/aspeed-sbc-test.c
@@ -0,0 +1,190 @@
+/*
+ * QTest testcase for the ASPEED Secure Boot Controller (SBC)
+ *
+ * Copyright (C) 2026 ASPEED Technology Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+#include "crypto/akcipher.h"
+
+/* SBC register block */
+#define SBC_STATUS 0x014
+#define SBC_ECDSA_VERIFY_PASS BIT(21)
+#define SBC_ECDSA_VERIFY_DONE BIT(20)
+#define SBC_ECDSA_CMD 0x0bc
+#define SBC_ECDSA_CMD_TRIGGER BIT(1)
+
+/*
+ * SEC SRAM operand offsets for a secp384r1 ECDSA verify. Every operand is a
+ * 48-byte big-endian integer.
+ */
+#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
+
+/*
+ * ECDSA secp384r1 / SHA-384 known-answer vector from the Linux kernel crypto
+ * self-test manager (ecdsa_nist_p384_tv_template, sha384 entry in
+ * crypto/testmgr.h, v6.18), decoded into raw big-endian form: public key
+ * Qx || Qy, signature r || s and the SHA-384 message digest.
+ *
+ * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/crypto/testmgr.h?h=v6.18
+ */
+static const uint8_t ecdsa_p384_pubkey[96] = {
+ /* Qx */
+ 0x3a, 0x2f, 0x62, 0xe7, 0x1a, 0xcf, 0x24, 0xd0,
+ 0x0b, 0x7c, 0xe0, 0xed, 0x46, 0x0a, 0x4f, 0x74,
+ 0x16, 0x43, 0xe9, 0x1a, 0x25, 0x7c, 0x55, 0xff,
+ 0xf0, 0x29, 0x68, 0x66, 0x20, 0x91, 0xf9, 0xdb,
+ 0x2b, 0xf6, 0xb3, 0x6c, 0x54, 0x01, 0xca, 0xc7,
+ 0x6a, 0x5c, 0x0d, 0xeb, 0x68, 0xd9, 0x3c, 0xf1,
+ /* Qy */
+ 0x01, 0x74, 0x1f, 0xf9, 0x6c, 0xe5, 0x5b, 0x60,
+ 0xe9, 0x7f, 0x5d, 0xb3, 0x12, 0x80, 0x2a, 0xd8,
+ 0x67, 0x92, 0xc9, 0x0e, 0x4c, 0x4c, 0x6b, 0xa1,
+ 0xb2, 0xa8, 0x1e, 0xac, 0x1c, 0x97, 0xd9, 0x21,
+ 0x67, 0xe5, 0x1b, 0x5a, 0x52, 0x31, 0x68, 0xd6,
+ 0xee, 0xf0, 0x19, 0xb0, 0x55, 0xed, 0x89, 0x9e,
+};
+
+static const uint8_t ecdsa_p384_signature[96] = {
+ /* r */
+ 0x9b, 0x28, 0x68, 0xc0, 0xa1, 0xea, 0x8c, 0x50,
+ 0xee, 0x2e, 0x62, 0x35, 0x46, 0xfa, 0x00, 0xd8,
+ 0x2d, 0x7a, 0x91, 0x5f, 0x49, 0x2d, 0x22, 0x08,
+ 0x29, 0xe6, 0xfb, 0xca, 0x8c, 0xd6, 0xb6, 0xb4,
+ 0x3b, 0x1f, 0x07, 0x8f, 0x15, 0x02, 0xfe, 0x1d,
+ 0xa2, 0xa4, 0xc8, 0xf2, 0xea, 0x9d, 0x11, 0x1f,
+ /* s */
+ 0xfc, 0x50, 0xf6, 0x43, 0xbd, 0x50, 0x82, 0x0e,
+ 0xbf, 0xe3, 0x75, 0x24, 0x49, 0xac, 0xfb, 0xc8,
+ 0x71, 0xcd, 0x8f, 0x18, 0x99, 0xf0, 0x0f, 0x13,
+ 0x44, 0x92, 0x8c, 0x86, 0x99, 0x65, 0xb3, 0x97,
+ 0x96, 0x17, 0x04, 0xc9, 0x05, 0x77, 0xf1, 0x8e,
+ 0xab, 0x8d, 0x4e, 0xde, 0xe6, 0x6d, 0x9b, 0x66,
+};
+
+static const uint8_t ecdsa_p384_dgst[48] = {
+ 0x8d, 0xf2, 0xc0, 0xe9, 0xa8, 0xf3, 0x8e, 0x44,
+ 0xc4, 0x8c, 0x1a, 0xa0, 0xb8, 0xd7, 0x17, 0xdf,
+ 0xf2, 0x37, 0x1b, 0xc6, 0xe3, 0xf5, 0x62, 0xcc,
+ 0x68, 0xf5, 0xd5, 0x0b, 0xbf, 0x73, 0x2b, 0xb1,
+ 0xb0, 0x4c, 0x04, 0x00, 0x31, 0xab, 0xfe, 0xc8,
+ 0xd6, 0x09, 0xc8, 0xf2, 0xea, 0xd3, 0x28, 0xff,
+};
+
+typedef struct AspeedSBCECDSA {
+ const char *name;
+ QCryptoCurveID curve_id;
+ const uint8_t *pubkey;
+ const uint8_t *signature;
+ const uint8_t *dgst;
+ size_t coord_len;
+} AspeedSBCECDSA;
+
+static const AspeedSBCECDSA sbc_ecdsa_tests[] = {
+ {
+ .name = "secp384r1",
+ .curve_id = QCRYPTO_CURVE_ID_SECP384R1,
+ .pubkey = ecdsa_p384_pubkey,
+ .signature = ecdsa_p384_signature,
+ .dgst = ecdsa_p384_dgst,
+ .coord_len = 48,
+ },
+};
+
+typedef struct AspeedSBCTest {
+ const char *machine;
+ uint32_t base;
+ uint64_t sram_base;
+ int index;
+} AspeedSBCTest;
+
+static void sbc_ecdsa_stage(QTestState *qts, const AspeedSBCTest *c,
+ const AspeedSBCECDSA *t)
+{
+ size_t len = t->coord_len;
+
+ qtest_memwrite(qts, c->sram_base + ECDSA_SRAM_QX, t->pubkey, len);
+ qtest_memwrite(qts, c->sram_base + ECDSA_SRAM_QY, t->pubkey + len, len);
+ qtest_memwrite(qts, c->sram_base + ECDSA_SRAM_R, t->signature, len);
+ qtest_memwrite(qts, c->sram_base + ECDSA_SRAM_S, t->signature + len, len);
+ qtest_memwrite(qts, c->sram_base + ECDSA_SRAM_M, t->dgst, len);
+}
+
+/*
+ * Drive one ECDSA verify through the engine the way the guest firmware does:
+ * stage the operands in the SEC SRAM, trigger the command register and read
+ * back the done/pass bits in the status register.
+ */
+static void test_ecdsa_verify(const void *opaque)
+{
+ const AspeedSBCTest *c = opaque;
+ const AspeedSBCECDSA *t = &sbc_ecdsa_tests[c->index];
+ QTestState *qts = qtest_init(c->machine);
+ uint32_t status;
+ uint8_t bad;
+
+ /* A valid signature verifies */
+ sbc_ecdsa_stage(qts, c, t);
+ qtest_writel(qts, c->base + SBC_ECDSA_CMD, SBC_ECDSA_CMD_TRIGGER);
+ status = qtest_readl(qts, c->base + SBC_STATUS);
+ g_assert_cmphex(status & (SBC_ECDSA_VERIFY_DONE | SBC_ECDSA_VERIFY_PASS),
+ ==, SBC_ECDSA_VERIFY_DONE | SBC_ECDSA_VERIFY_PASS);
+
+ /* A tampered signature must fail */
+ bad = t->signature[0] ^ 0xff;
+ qtest_memwrite(qts, c->sram_base + ECDSA_SRAM_R, &bad, 1);
+ qtest_writel(qts, c->base + SBC_ECDSA_CMD, SBC_ECDSA_CMD_TRIGGER);
+ status = qtest_readl(qts, c->base + SBC_STATUS);
+ g_assert_cmphex(status & SBC_ECDSA_VERIFY_DONE, ==, SBC_ECDSA_VERIFY_DONE);
+ g_assert_cmphex(status & SBC_ECDSA_VERIFY_PASS, ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void aspeed_add_sbc_ecdsa_tests(const char *prefix, const char *machine,
+ uint32_t base, uint64_t sram_base)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sbc_ecdsa_tests); i++) {
+ QCryptoAkCipherOptions opts = {
+ .alg = QCRYPTO_AK_CIPHER_ALGO_ECDSA,
+ .u.ecdsa.curve_id = sbc_ecdsa_tests[i].curve_id,
+ };
+ g_autofree char *path = NULL;
+ AspeedSBCTest *t;
+
+ if (!qcrypto_akcipher_supports(&opts)) {
+ g_printerr("# skip SBC ECDSA %s test: not supported by the crypto "
+ "backend\n", sbc_ecdsa_tests[i].name);
+ continue;
+ }
+
+ path = g_strdup_printf("%s/sbc/ecdsa/%s", prefix,
+ sbc_ecdsa_tests[i].name);
+ t = g_new0(AspeedSBCTest, 1);
+ t->machine = machine;
+ t->base = base;
+ t->sram_base = sram_base;
+ t->index = i;
+ qtest_add_data_func_full(path, t, test_ecdsa_verify, g_free);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ aspeed_add_sbc_ecdsa_tests("ast1030", "-machine ast1030-evb",
+ 0x7e6f2000, 0x79000000);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index f7c7d06620..af7a460e08 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -222,6 +222,7 @@ qtests_npcm8xx = \
qtests_aspeed = \
['aspeed_gpio-test',
'aspeed_hace-test',
+ 'aspeed-sbc-test',
'aspeed_scu-test',
'aspeed_smc-test']
qtests_aspeed64 = \
@@ -394,6 +395,7 @@ endif
qtests = {
'aspeed_hace-test': [files('aspeed-hace-utils.c', 'aspeed_hace-test.c'),
crypto],
+ 'aspeed-sbc-test': [files('aspeed-sbc-test.c'), crypto],
'aspeed_smc-test': files('aspeed-smc-utils.c', 'aspeed_smc-test.c'),
'ast2700-hace-test': [files('aspeed-hace-utils.c', 'ast2700-hace-test.c'),
crypto],
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-08-18 9:25 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18 9:22 [PATCH v1 0/8] Add ECDSA akcipher support and the ASPEED SBC ECDSA engine for AST10x0 Jamin Lin
2026-08-18 9:22 ` [PATCH v1 1/8] qapi/crypto: Add ECDSA algorithm and curve id Jamin Lin
2026-08-18 9:22 ` [PATCH v1 2/8] crypto/akcipher: Support ECDSA sign/verify with gcrypt Jamin Lin
2026-08-18 9:22 ` [PATCH v1 3/8] crypto/akcipher: Support ECDSA sign/verify with nettle Jamin Lin
2026-08-18 9:22 ` [PATCH v1 4/8] tests/crypto: Add ECDSA sign/verify tests Jamin Lin
2026-08-18 9:22 ` [PATCH v1 5/8] hw/misc/aspeed_sbc: Support the ECDSA verify command Jamin Lin
2026-08-18 9:23 ` [PATCH v1 6/8] hw/misc/aspeed_sbc: Increase register space to 0x1000 Jamin Lin
2026-08-18 9:23 ` [PATCH v1 7/8] hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model Jamin Lin
2026-08-18 9:23 ` [PATCH v1 8/8] tests/qtest: Add ASPEED SBC ECDSA engine test Jamin Lin
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.