Hi Cédric
> Subject: Re: [PATCH v3 3/9] crypto/akcipher: Support ECDSA sign/verify with
> nettle
>
> On 8/31/26 05:41, Jamin Lin wrote:
> > 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 <[email protected]>
> > Reviewed-by: Daniel P. Berrangé <[email protected]>
> > ---
> > 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;
>
> Should we have a guard here, like in qcrypto_nettle_ecdsa_sign() ?
>
> if (ecdsa->type != QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC) {
> error_setg(errp, "ECDSA verify requires a public key");
> return -1;
> }
>
> Just asking.
>
Thanks for the review and suggestion.
Will add.
Jamin
> Thanks,
>
> C.
>
> > + /* 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;
> > }