From: Jamin Lin <jamin_lin@aspeedtech.com>
To: "Daniel P. Berrangé" <berrange@redhat.com>,
"Cédric Le Goater" <clg@kaod.org>,
"Peter Maydell" <peter.maydell@linaro.org>,
"Steven Lee" <steven_lee@aspeedtech.com>,
"Troy Lee" <leetroy@gmail.com>,
"Kane Chen" <kane_chen@aspeedtech.com>,
"Andrew Jeffery" <andrew@codeconstruct.com.au>,
"Joel Stanley" <joel@jms.id.au>, "Eric Blake" <eblake@redhat.com>,
"Markus Armbruster" <armbru@redhat.com>,
"Fabiano Rosas" <farosas@suse.de>,
"Laurent Vivier" <lvivier@redhat.com>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"open list:All patches CC here" <qemu-devel@nongnu.org>,
"open list:ASPEED BMCs" <qemu-arm@nongnu.org>
Cc: Jamin Lin <jamin_lin@aspeedtech.com>, Troy Lee <troy_lee@aspeedtech.com>
Subject: [PATCH v2 3/9] crypto/akcipher: Support ECDSA sign/verify with nettle
Date: Fri, 21 Aug 2026 03:20:41 +0000 [thread overview]
Message-ID: <20260821032036.1829294-4-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20260821032036.1829294-1-jamin_lin@aspeedtech.com>
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
next prev parent reply other threads:[~2026-08-21 3:22 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-21 3:20 [PATCH v2 0/9] Add ECDSA akcipher support and the ASPEED SBC ECDSA engine for AST10x0 Jamin Lin
2026-08-21 3:20 ` [PATCH v2 1/9] qapi/crypto: Add ECDSA algorithm and curve id Jamin Lin
2026-08-21 3:20 ` [PATCH v2 2/9] crypto/akcipher: Support ECDSA sign/verify with gcrypt Jamin Lin
2026-08-21 3:20 ` Jamin Lin [this message]
2026-08-21 3:20 ` [PATCH v2 4/9] hw/arm/aspeed_ast10x0: Remove obsolete unimplemented SBC mapping Jamin Lin
2026-08-21 3:20 ` [PATCH v2 5/9] tests/crypto: Add ECDSA sign/verify tests Jamin Lin
2026-08-21 3:20 ` [PATCH v2 6/9] hw/misc/aspeed_sbc: Support the ECDSA verify command Jamin Lin
2026-08-21 3:20 ` [PATCH v2 7/9] hw/misc/aspeed_sbc: Increase register space to 0x1000 Jamin Lin
2026-08-21 3:20 ` [PATCH v2 8/9] hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model Jamin Lin
2026-08-21 3:20 ` [PATCH v2 9/9] tests/qtest: Add ASPEED SBC ECDSA engine test Jamin Lin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260821032036.1829294-4-jamin_lin@aspeedtech.com \
--to=jamin_lin@aspeedtech.com \
--cc=andrew@codeconstruct.com.au \
--cc=armbru@redhat.com \
--cc=berrange@redhat.com \
--cc=clg@kaod.org \
--cc=eblake@redhat.com \
--cc=farosas@suse.de \
--cc=joel@jms.id.au \
--cc=kane_chen@aspeedtech.com \
--cc=leetroy@gmail.com \
--cc=lvivier@redhat.com \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=steven_lee@aspeedtech.com \
--cc=troy_lee@aspeedtech.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox