All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Cédric Le Goater" <clg@redhat.com>
To: qemu-arm@nongnu.org, qemu-devel@nongnu.org
Cc: "Jamin Lin" <jamin_lin@aspeedtech.com>,
	"Daniel P. Berrangé" <berrange@redhat.com>,
	"Cédric Le Goater" <clg@redhat.com>
Subject: [PULL 13/40] crypto/akcipher: Support ECDSA sign/verify with gcrypt
Date: Sun,  6 Sep 2026 19:09:54 +0200	[thread overview]
Message-ID: <20260906171021.26568-14-clg@redhat.com> (raw)
In-Reply-To: <20260906171021.26568-1-clg@redhat.com>

From: Jamin Lin <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>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260901085238.995968-3-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
---
 crypto/akcipher-gcrypt.c.inc | 340 ++++++++++++++++++++++++++++++++++-
 1 file changed, 339 insertions(+), 1 deletion(-)

diff --git a/crypto/akcipher-gcrypt.c.inc b/crypto/akcipher-gcrypt.c.inc
index bcf030fdec45..3e49cc150849 100644
--- a/crypto/akcipher-gcrypt.c.inc
+++ b/crypto/akcipher-gcrypt.c.inc
@@ -36,6 +36,13 @@ typedef struct QCryptoGcryptRSA {
     QCryptoHashAlgo hash_alg;
 } QCryptoGcryptRSA;
 
+typedef struct QCryptoGcryptECDSA {
+    QCryptoAkCipher akcipher;
+    QCryptoAkCipherKeyType type;
+    gcry_sexp_t key;
+    QCryptoCurveID curve_id;
+} QCryptoGcryptECDSA;
+
 static void qcrypto_gcrypt_rsa_free(QCryptoAkCipher *akcipher)
 {
     QCryptoGcryptRSA *rsa = (QCryptoGcryptRSA *)akcipher;
@@ -53,6 +60,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 +76,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 +582,317 @@ 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 (ecdsa->type != QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE) {
+        error_setg(errp, "ECDSA sign requires a private key");
+        return ret;
+    }
+
+    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;
+
+    if (ecdsa->type != QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC) {
+        error_setg(errp, "ECDSA verify requires a public key");
+        return ret;
+    }
+
+    /* 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->type = type;
+    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 +917,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.55.0



  parent reply	other threads:[~2026-09-06 17:13 UTC|newest]

Thread overview: 42+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-06 17:09 [PULL 00/40] aspeed queue Cédric Le Goater
2026-09-06 17:09 ` [PULL 01/40] hw/misc/aspeed_scu: Set both AST2600 protection key registers on reset Cédric Le Goater
2026-09-06 17:09 ` [PULL 02/40] hw/i2c/aspeed_i2c: Latch received bytes for SMBus block reads Cédric Le Goater
2026-09-06 17:09 ` [PULL 03/40] hw/usb/hcd-ehci: Check for DMA errors in get_dwords()/put_dwords() Cédric Le Goater
2026-09-06 17:09 ` [PULL 04/40] hw/usb/hcd-ehci: Make get_dwords() return bool Cédric Le Goater
2026-09-06 17:09 ` [PULL 05/40] hw/usb/hcd-ehci: Handle get_dwords() failures in async writeback Cédric Le Goater
2026-09-06 17:09 ` [PULL 06/40] hw/misc/aspeed_scu: Support the second random number generator Cédric Le Goater
2026-09-06 17:09 ` [PULL 07/40] tests/qtest/aspeed-hace-utils: Replace g_printerr() with g_test_skip() Cédric Le Goater
2026-09-06 17:09 ` [PULL 08/40] hw/misc/aspeed_acry: Add ASPEED ACRY model Cédric Le Goater
2026-09-06 17:09 ` [PULL 09/40] hw/arm/aspeed_ast2600: Introduce the ACRY SRAM Cédric Le Goater
2026-09-06 17:09 ` [PULL 10/40] hw/arm/aspeed_ast2600: Wire up the ACRY model Cédric Le Goater
2026-09-06 17:09 ` [PULL 11/40] tests/qtest/aspeed-acry-test: Add RSA ModExp tests Cédric Le Goater
2026-09-06 17:09 ` [PULL 12/40] qapi/crypto: Add ECDSA algorithm and curve id Cédric Le Goater
2026-09-06 17:09 ` Cédric Le Goater [this message]
2026-09-06 17:09 ` [PULL 14/40] crypto/akcipher: Support ECDSA sign/verify with nettle Cédric Le Goater
2026-09-06 17:09 ` [PULL 15/40] tests/crypto: Add ECDSA sign/verify tests Cédric Le Goater
2026-09-06 17:09 ` [PULL 16/40] hw/arm/aspeed_ast10x0: Remove obsolete unimplemented SBC mapping Cédric Le Goater
2026-09-06 17:09 ` [PULL 17/40] hw/misc/aspeed_sbc: Increase register space to 0x1000 Cédric Le Goater
2026-09-06 17:09 ` [PULL 18/40] hw/arm/aspeed_ast10x0: Wire SEC SRAM to the SBC model Cédric Le Goater
2026-09-06 17:10 ` [PULL 19/40] hw/misc/aspeed_sbc: Support the ECDSA verify command Cédric Le Goater
2026-09-06 17:10 ` [PULL 20/40] tests/qtest: Add ASPEED SBC ECDSA engine test Cédric Le Goater
2026-09-06 17:10 ` [PULL 21/40] hw/usb/aspeed-udc: Add ASPEED UDC device controller Cédric Le Goater
2026-09-06 17:10 ` [PULL 22/40] hw/usb/aspeed-udc: Add ASPEED UDC gadget USB device Cédric Le Goater
2026-09-06 17:10 ` [PULL 23/40] hw/usb/aspeed-udc: Add programmable endpoint DMA transfers Cédric Le Goater
2026-09-06 17:10 ` [PULL 24/40] hw/arm/aspeed_ast2600: Wire up the UDC Cédric Le Goater
2026-09-06 17:10 ` [PULL 25/40] hw/sensor: tmp105: make device state private to the implementation Cédric Le Goater
2026-09-06 17:10 ` [PULL 26/40] hw/sensor: tmp105: name the parent object field parent_obj Cédric Le Goater
2026-09-06 17:10 ` [PULL 27/40] hw/sensor: tmp105: implement Resettable reset Cédric Le Goater
2026-09-06 17:10 ` [PULL 28/40] hw/sensor: tmp105: enforce the configurable fault queue Cédric Le Goater
2026-09-06 17:10 ` [PULL 29/40] hw/sensor: tmp105: describe the temperature property Cédric Le Goater
2026-09-06 17:10 ` [PULL 30/40] hw/arm: aspeed: guard board-local temperature-sensor aliases Cédric Le Goater
2026-09-06 17:10 ` [PULL 31/40] hw/sensor: tmp105: add TMP75, TMP175 and LM75B variants Cédric Le Goater
2026-09-06 17:10 ` [PULL 32/40] tests/qtest: tmp105: cover the ALERT fault queue Cédric Le Goater
2026-09-06 17:10 ` [PULL 33/40] tests/qtest: tmp105: cover the TMP75, TMP175 and LM75B variants Cédric Le Goater
2026-09-06 17:10 ` [PULL 34/40] tests/qtest: tmp105: cover one-shot and fault-queue write immunity Cédric Le Goater
2026-09-06 17:10 ` [PULL 35/40] tests/qtest: tmp105: cover shutdown clearing the ALERT across variants Cédric Le Goater
2026-09-06 17:10 ` [PULL 36/40] hw/arm: sanmiguel: add Facebook SanMiguel BMC machine Cédric Le Goater
2026-09-06 17:10 ` [PULL 37/40] hw/arm: sanmiguel: populate EEPROM data Cédric Le Goater
2026-09-06 17:10 ` [PULL 38/40] hw/arm: catalina: use the real TMP75 model Cédric Le Goater
2026-09-06 17:10 ` [PULL 39/40] hw/arm: fuji: use the real TMP75 and LM75B temperature sensors Cédric Le Goater
2026-09-06 17:10 ` [PULL 40/40] tests/functional: aspeed: optionally check the device tree model on boot Cédric Le Goater
2026-09-07 16:35 ` [PULL 00/40] aspeed queue Peter Maydell

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=20260906171021.26568-14-clg@redhat.com \
    --to=clg@redhat.com \
    --cc=berrange@redhat.com \
    --cc=jamin_lin@aspeedtech.com \
    --cc=qemu-arm@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    /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 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.