[PATCH v1 4/8] tests/crypto: Add ECDSA sign/verify tests

Jamin Lin <[email protected]>
Newsgroups org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
Add unit tests for ECDSA prime256v1 and secp384r1, using the
RFC 6979 (Deterministic ECDSA) "sample" known-answer vectors. The same
key pair drives the verify test (against the RFC's known signature) and
the sign test, which signs the digest with the private key and checks
that the result verifies with the public key. Each test skips
(g_test_skip) when the crypto backend does not support ECDSA, via
qcrypto_akcipher_supports().

Signed-off-by: Jamin Lin <[email protected]>
---
 tests/unit/test-crypto-akcipher.c | 236 ++++++++++++++++++++++++++++++
 1 file changed, 236 insertions(+)

diff --git a/tests/unit/test-crypto-akcipher.c b/tests/unit/test-crypto-akcipher.c
index 53c2211ba8..fb1a6163e9 100644
--- a/tests/unit/test-crypto-akcipher.c
+++ b/tests/unit/test-crypto-akcipher.c
@@ -658,6 +658,117 @@ static const uint8_t rsa_public_key_extra_elem[] = {
     0x02, 0x06, 0xe1, 0x22, 0xdb, 0xe1, 0x22, 0xdb,
 };
 
+/*
+ * ECDSA known-answer vectors from RFC 6979 (Deterministic ECDSA), message
+ * "sample", decoded into raw big-endian form. The same key pair drives both
+ * the verify test (against the RFC's known signature) and the sign round-trip
+ * test:
+ *   - private key: the scalar d (curve size)
+ *   - public key: Qx || Qy (2 * curve size)
+ *   - dgst: the message digest (curve size)
+ *   - signature: r || s (2 * curve size)
+ *
+ * prime256v1 / SHA-256 (RFC 6979 A.2.5):
+ */
+static const uint8_t ecdsa_p256_privkey[32] = {
+    0xc9, 0xaf, 0xa9, 0xd8, 0x45, 0xba, 0x75, 0x16,
+    0x6b, 0x5c, 0x21, 0x57, 0x67, 0xb1, 0xd6, 0x93,
+    0x4e, 0x50, 0xc3, 0xdb, 0x36, 0xe8, 0x9b, 0x12,
+    0x7b, 0x8a, 0x62, 0x2b, 0x12, 0x0f, 0x67, 0x21,
+};
+
+static const uint8_t ecdsa_p256_pubkey[64] = {
+    0x60, 0xfe, 0xd4, 0xba, 0x25, 0x5a, 0x9d, 0x31,
+    0xc9, 0x61, 0xeb, 0x74, 0xc6, 0x35, 0x6d, 0x68,
+    0xc0, 0x49, 0xb8, 0x92, 0x3b, 0x61, 0xfa, 0x6c,
+    0xe6, 0x69, 0x62, 0x2e, 0x60, 0xf2, 0x9f, 0xb6,
+    0x79, 0x03, 0xfe, 0x10, 0x08, 0xb8, 0xbc, 0x99,
+    0xa4, 0x1a, 0xe9, 0xe9, 0x56, 0x28, 0xbc, 0x64,
+    0xf2, 0xf1, 0xb2, 0x0c, 0x2d, 0x7e, 0x9f, 0x51,
+    0x77, 0xa3, 0xc2, 0x94, 0xd4, 0x46, 0x22, 0x99,
+};
+
+static const uint8_t ecdsa_p256_dgst[32] = {
+    0xaf, 0x2b, 0xdb, 0xe1, 0xaa, 0x9b, 0x6e, 0xc1,
+    0xe2, 0xad, 0xe1, 0xd6, 0x94, 0xf4, 0x1f, 0xc7,
+    0x1a, 0x83, 0x1d, 0x02, 0x68, 0xe9, 0x89, 0x15,
+    0x62, 0x11, 0x3d, 0x8a, 0x62, 0xad, 0xd1, 0xbf,
+};
+
+static const uint8_t ecdsa_p256_signature[64] = {
+    0xef, 0xd4, 0x8b, 0x2a, 0xac, 0xb6, 0xa8, 0xfd,
+    0x11, 0x40, 0xdd, 0x9c, 0xd4, 0x5e, 0x81, 0xd6,
+    0x9d, 0x2c, 0x87, 0x7b, 0x56, 0xaa, 0xf9, 0x91,
+    0xc3, 0x4d, 0x0e, 0xa8, 0x4e, 0xaf, 0x37, 0x16,
+    0xf7, 0xcb, 0x1c, 0x94, 0x2d, 0x65, 0x7c, 0x41,
+    0xd4, 0x36, 0xc7, 0xa1, 0xb6, 0xe2, 0x9f, 0x65,
+    0xf3, 0xe9, 0x00, 0xdb, 0xb9, 0xaf, 0xf4, 0x06,
+    0x4d, 0xc4, 0xab, 0x2f, 0x84, 0x3a, 0xcd, 0xa8,
+};
+
+/* secp384r1 / SHA-384 (RFC 6979 A.2.6): */
+static const uint8_t ecdsa_p384_privkey[48] = {
+    0x6b, 0x9d, 0x3d, 0xad, 0x2e, 0x1b, 0x8c, 0x1c,
+    0x05, 0xb1, 0x98, 0x75, 0xb6, 0x65, 0x9f, 0x4d,
+    0xe2, 0x3c, 0x3b, 0x66, 0x7b, 0xf2, 0x97, 0xba,
+    0x9a, 0xa4, 0x77, 0x40, 0x78, 0x71, 0x37, 0xd8,
+    0x96, 0xd5, 0x72, 0x4e, 0x4c, 0x70, 0xa8, 0x25,
+    0xf8, 0x72, 0xc9, 0xea, 0x60, 0xd2, 0xed, 0xf5,
+};
+
+static const uint8_t ecdsa_p384_pubkey[96] = {
+    0xec, 0x3a, 0x4e, 0x41, 0x5b, 0x4e, 0x19, 0xa4,
+    0x56, 0x86, 0x18, 0x02, 0x9f, 0x42, 0x7f, 0xa5,
+    0xda, 0x9a, 0x8b, 0xc4, 0xae, 0x92, 0xe0, 0x2e,
+    0x06, 0xaa, 0xe5, 0x28, 0x6b, 0x30, 0x0c, 0x64,
+    0xde, 0xf8, 0xf0, 0xea, 0x90, 0x55, 0x86, 0x60,
+    0x64, 0xa2, 0x54, 0x51, 0x54, 0x80, 0xbc, 0x13,
+    0x80, 0x15, 0xd9, 0xb7, 0x2d, 0x7d, 0x57, 0x24,
+    0x4e, 0xa8, 0xef, 0x9a, 0xc0, 0xc6, 0x21, 0x89,
+    0x67, 0x08, 0xa5, 0x93, 0x67, 0xf9, 0xdf, 0xb9,
+    0xf5, 0x4c, 0xa8, 0x4b, 0x3f, 0x1c, 0x9d, 0xb1,
+    0x28, 0x8b, 0x23, 0x1c, 0x3a, 0xe0, 0xd4, 0xfe,
+    0x73, 0x44, 0xfd, 0x25, 0x33, 0x26, 0x47, 0x20,
+};
+
+static const uint8_t ecdsa_p384_dgst[48] = {
+    0x9a, 0x90, 0x83, 0x50, 0x5b, 0xc9, 0x22, 0x76,
+    0xae, 0xc4, 0xbe, 0x31, 0x26, 0x96, 0xef, 0x7b,
+    0xf3, 0xbf, 0x60, 0x3f, 0x4b, 0xbd, 0x38, 0x11,
+    0x96, 0xa0, 0x29, 0xf3, 0x40, 0x58, 0x53, 0x12,
+    0x31, 0x3b, 0xca, 0x4a, 0x9b, 0x5b, 0x89, 0x0e,
+    0xfe, 0xe4, 0x2c, 0x77, 0xb1, 0xee, 0x25, 0xfe,
+};
+
+static const uint8_t ecdsa_p384_signature[96] = {
+    0x94, 0xed, 0xbb, 0x92, 0xa5, 0xec, 0xb8, 0xaa,
+    0xd4, 0x73, 0x6e, 0x56, 0xc6, 0x91, 0x91, 0x6b,
+    0x3f, 0x88, 0x14, 0x06, 0x66, 0xce, 0x9f, 0xa7,
+    0x3d, 0x64, 0xc4, 0xea, 0x95, 0xad, 0x13, 0x3c,
+    0x81, 0xa6, 0x48, 0x15, 0x2e, 0x44, 0xac, 0xf9,
+    0x6e, 0x36, 0xdd, 0x1e, 0x80, 0xfa, 0xbe, 0x46,
+    0x99, 0xef, 0x4a, 0xeb, 0x15, 0xf1, 0x78, 0xce,
+    0xa1, 0xfe, 0x40, 0xdb, 0x26, 0x03, 0x13, 0x8f,
+    0x13, 0x0e, 0x74, 0x0a, 0x19, 0x62, 0x45, 0x26,
+    0x20, 0x3b, 0x63, 0x51, 0xd0, 0xa3, 0xa9, 0x4f,
+    0xa3, 0x29, 0xc1, 0x45, 0x78, 0x6e, 0x67, 0x9e,
+    0x7b, 0x82, 0xc7, 0x1a, 0x38, 0x62, 0x8a, 0xc8,
+};
+
+typedef struct QCryptoECDSATestData QCryptoECDSATestData;
+struct QCryptoECDSATestData {
+    const char *path;
+    QCryptoCurveID curve_id;
+    const uint8_t *priv_key;
+    size_t priv_key_len;
+    const uint8_t *pub_key;
+    size_t pub_key_len;
+    const uint8_t *dgst;
+    size_t dlen;
+    const uint8_t *signature;
+    size_t slen;
+};
+
 typedef struct QCryptoRSAKeyTestData QCryptoRSAKeyTestData;
 struct QCryptoRSAKeyTestData {
     const char *path;
@@ -969,6 +1080,120 @@ static void test_rsakey(const void *opaque)
     g_assert(qcrypto_akcipher_max_dgst_len(key) == data->exp_key_len);
 }
 
+static QCryptoECDSATestData ecdsa_test_data[] = {
+    {
+        .path = "/crypto/akcipher/ecdsa-prime256v1",
+        .curve_id = QCRYPTO_CURVE_ID_PRIME256V1,
+        .priv_key = ecdsa_p256_privkey,
+        .priv_key_len = sizeof(ecdsa_p256_privkey),
+        .pub_key = ecdsa_p256_pubkey,
+        .pub_key_len = sizeof(ecdsa_p256_pubkey),
+        .dgst = ecdsa_p256_dgst,
+        .dlen = sizeof(ecdsa_p256_dgst),
+        .signature = ecdsa_p256_signature,
+        .slen = sizeof(ecdsa_p256_signature),
+    },
+    {
+        .path = "/crypto/akcipher/ecdsa-secp384r1",
+        .curve_id = QCRYPTO_CURVE_ID_SECP384R1,
+        .priv_key = ecdsa_p384_privkey,
+        .priv_key_len = sizeof(ecdsa_p384_privkey),
+        .pub_key = ecdsa_p384_pubkey,
+        .pub_key_len = sizeof(ecdsa_p384_pubkey),
+        .dgst = ecdsa_p384_dgst,
+        .dlen = sizeof(ecdsa_p384_dgst),
+        .signature = ecdsa_p384_signature,
+        .slen = sizeof(ecdsa_p384_signature),
+    },
+    /* Add more curves here as they gain backend support. */
+};
+
+static void test_ecdsa_verify(const void *opaque)
+{
+    const QCryptoECDSATestData *data = opaque;
+    QCryptoAkCipherOptions opt = {
+        .alg = QCRYPTO_AK_CIPHER_ALGO_ECDSA,
+        .u.ecdsa = {
+            .curve_id = data->curve_id,
+        },
+    };
+    g_autoptr(QCryptoAkCipher) pub_key = NULL;
+    g_autofree uint8_t *signature = NULL;
+    g_autofree uint8_t *dgst = NULL;
+
+    if (!qcrypto_akcipher_supports(&opt)) {
+        g_test_skip("ECDSA is not supported by the crypto backend");
+        return;
+    }
+
+    pub_key = qcrypto_akcipher_new(&opt, QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC,
+                                   data->pub_key, data->pub_key_len,
+                                   &error_abort);
+    g_assert(pub_key != NULL);
+    g_assert(qcrypto_akcipher_max_signature_len(pub_key) == data->slen);
+    g_assert(qcrypto_akcipher_max_dgst_len(pub_key) == data->dlen);
+
+    /* A valid signature must verify. */
+    g_assert(qcrypto_akcipher_verify(pub_key, data->signature, data->slen,
+                                     data->dgst, data->dlen,
+                                     &error_abort) == 0);
+
+    /* A tampered signature must fail (error is expected, so ignore it). */
+    signature = g_memdup2(data->signature, data->slen);
+    signature[0]++;
+    g_assert(qcrypto_akcipher_verify(pub_key, signature, data->slen,
+                                     data->dgst, data->dlen, NULL) != 0);
+
+    /* A tampered digest must also fail. */
+    dgst = g_memdup2(data->dgst, data->dlen);
+    dgst[0]++;
+    g_assert(qcrypto_akcipher_verify(pub_key, data->signature, data->slen,
+                                     dgst, data->dlen, NULL) != 0);
+}
+
+static void test_ecdsa_sign(const void *opaque)
+{
+    const QCryptoECDSATestData *data = opaque;
+    QCryptoAkCipherOptions opt = {
+        .alg = QCRYPTO_AK_CIPHER_ALGO_ECDSA,
+        .u.ecdsa = {
+            .curve_id = data->curve_id,
+        },
+    };
+    g_autoptr(QCryptoAkCipher) priv_key = NULL;
+    g_autoptr(QCryptoAkCipher) pub_key = NULL;
+    g_autofree uint8_t *signature = NULL;
+    int slen;
+
+    if (!qcrypto_akcipher_supports(&opt)) {
+        g_test_skip("ECDSA is not supported by the crypto backend");
+        return;
+    }
+
+    priv_key = qcrypto_akcipher_new(&opt, QCRYPTO_AK_CIPHER_KEY_TYPE_PRIVATE,
+                                    data->priv_key, data->priv_key_len,
+                                    &error_abort);
+    g_assert(priv_key != NULL);
+
+    /*
+     * Sign the digest.
+     * ECDSA signatures are randomized so only length is fixed.
+     */
+    slen = qcrypto_akcipher_max_signature_len(priv_key);
+    signature = g_new0(uint8_t, slen);
+    g_assert(qcrypto_akcipher_sign(priv_key, data->dgst, data->dlen,
+                                   signature, slen, &error_abort) == slen);
+
+    /* The freshly produced signature must verify with the public key. */
+    pub_key = qcrypto_akcipher_new(&opt, QCRYPTO_AK_CIPHER_KEY_TYPE_PUBLIC,
+                                   data->pub_key, data->pub_key_len,
+                                   &error_abort);
+    g_assert(pub_key != NULL);
+    g_assert(qcrypto_akcipher_verify(pub_key, signature, slen,
+                                     data->dgst, data->dlen,
+                                     &error_abort) == 0);
+}
+
 int main(int argc, char **argv)
 {
     size_t i;
@@ -985,6 +1210,17 @@ int main(int argc, char **argv)
                              &rsakey_test_data[i],
                              test_rsakey);
     }
+    for (i = 0; i < G_N_ELEMENTS(ecdsa_test_data); i++) {
+        g_autofree char *verify_path =
+            g_strdup_printf("%s-verify", ecdsa_test_data[i].path);
+        g_autofree char *sign_path =
+            g_strdup_printf("%s-sign", ecdsa_test_data[i].path);
+
+        g_test_add_data_func(verify_path, &ecdsa_test_data[i],
+                             test_ecdsa_verify);
+        g_test_add_data_func(sign_path, &ecdsa_test_data[i],
+                             test_ecdsa_sign);
+    }
 
     return g_test_run();
 }
-- 
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.