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;
> >       }

Reply via email to