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 2/9] crypto/akcipher: Support ECDSA sign/verify with gcrypt
Date: Fri, 21 Aug 2026 03:20:39 +0000 [thread overview]
Message-ID: <20260821032036.1829294-3-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20260821032036.1829294-1-jamin_lin@aspeedtech.com>
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
next prev parent reply other threads:[~2026-08-21 3:21 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 ` Jamin Lin [this message]
2026-08-21 3:20 ` [PATCH v2 3/9] crypto/akcipher: Support ECDSA sign/verify with nettle Jamin Lin
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-3-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