[PATCH v2 3/9] crypto/akcipher: Support ECDSA sign/verify with nettle

Jamin Lin <[email protected]>
Newsgroups org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
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]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.