From: Yu Watanabe Date: Tue, 30 Jun 2026 11:15:31 +0000 (+0900) Subject: resolve: add unit test for dnssec_ecdsa_verify_raw() X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=59fa4dd142fbc8a8c3165a17faca633e4e83bad8;p=thirdparty%2Fsystemd.git resolve: add unit test for dnssec_ecdsa_verify_raw() --- diff --git a/src/resolve/resolved-dns-dnssec-crypto.h b/src/resolve/resolved-dns-dnssec-crypto.h index c0b00e6ae2a..568e511d189 100644 --- a/src/resolve/resolved-dns-dnssec-crypto.h +++ b/src/resolve/resolved-dns-dnssec-crypto.h @@ -13,4 +13,12 @@ int dnssec_rsa_verify_raw( const struct iovec *exponent, const struct iovec *modulus); +int dnssec_ecdsa_verify_raw( + const EVP_MD *hash_algorithm, + int curve, + const void *signature_r, size_t signature_r_size, + const void *signature_s, size_t signature_s_size, + const void *data, size_t data_size, + const void *key, size_t key_size); + #endif diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index b9afdac9ffd..b7748272f33 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -204,7 +204,7 @@ static int dnssec_rsa_verify( &IOVEC_MAKE(modulus, modulus_size)); } -static int dnssec_ecdsa_verify_raw( +int dnssec_ecdsa_verify_raw( const EVP_MD *hash_algorithm, int curve, const void *signature_r, size_t signature_r_size, diff --git a/src/resolve/test-dnssec-crypto.c b/src/resolve/test-dnssec-crypto.c index 034320b348d..ff22f5ddd9d 100644 --- a/src/resolve/test-dnssec-crypto.c +++ b/src/resolve/test-dnssec-crypto.c @@ -246,6 +246,162 @@ TEST(dnssec_rsa_verify_raw) { TEST_RSA_VERIFY(test_signature, test_digest, test_exponent, bad_modulus, -EINVAL); } +static const uint8_t test_ecdsa_r_buf[] = { + 0x0b, 0x81, 0x39, 0x45, 0xa8, 0xcd, 0x1b, 0x12, + 0xd6, 0x9f, 0xa9, 0x88, 0xf2, 0x28, 0x39, 0x13, + 0x59, 0x1d, 0xe7, 0xee, 0xea, 0x92, 0x87, 0x0a, + 0x2c, 0x7a, 0xf2, 0x3b, 0xfb, 0x3e, 0xcb, 0x3b, +}; + +static const uint8_t test_ecdsa_s_buf[] = { + 0x74, 0xcd, 0x7c, 0x0b, 0x52, 0xa5, 0x7b, 0xf5, + 0x63, 0x84, 0xd5, 0xd9, 0xd2, 0xcb, 0xe3, 0xce, + 0x55, 0x05, 0x11, 0x7c, 0xe1, 0xfe, 0x55, 0xb4, + 0x54, 0x2e, 0x6e, 0x1c, 0xff, 0x50, 0xe5, 0x92, +}; + +static const uint8_t test_ecdsa_key_buf[] = { + 0x04, 0x23, 0x1f, 0x7d, 0x08, 0xec, 0x20, 0xcd, + 0xde, 0x03, 0x93, 0xa7, 0xb2, 0x47, 0x73, 0xeb, + 0xde, 0xd3, 0x5a, 0xbe, 0x35, 0x01, 0xda, 0x31, + 0x2b, 0x7b, 0x61, 0xcf, 0xd2, 0x30, 0xbf, 0xbf, + 0xb7, 0x6f, 0x0d, 0x86, 0x0a, 0x32, 0x46, 0xb6, + 0xdc, 0xb0, 0xd0, 0xa7, 0x3f, 0x5d, 0xdd, 0xdd, + 0xb9, 0xbb, 0x6b, 0x01, 0x61, 0x93, 0x2c, 0x1a, + 0x26, 0x65, 0x5f, 0x46, 0xb1, 0xe1, 0xc7, 0x1d, + 0x21, +}; + +static const struct iovec test_ecdsa_r = IOVEC_MAKE(test_ecdsa_r_buf, sizeof(test_ecdsa_r_buf)); +static const struct iovec test_ecdsa_s = IOVEC_MAKE(test_ecdsa_s_buf, sizeof(test_ecdsa_s_buf)); +static const struct iovec test_ecdsa_key = IOVEC_MAKE(test_ecdsa_key_buf, sizeof(test_ecdsa_key_buf)); + +TEST(generate_ecdsa_test_vectors) { + /* This does not test anything but generates test vectors for dnssec_ecdsa_verify_raw(). + * This is skipped when we are running on valgrind or sanitizers, as it is extremely slow. */ +#if HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) + return (void) log_tests_skipped("Running on valgrind"); +#endif +#if HAS_FEATURE_ADDRESS_SANITIZER + return (void) log_tests_skipped("Running on sanitizers"); +#endif + + /* Generate a P-256 (prime256v1) EC key pair. */ + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *kctx = + ASSERT_NOT_NULL(sym_EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_keygen_init(kctx)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_CTX_set_ec_paramgen_curve_nid(kctx, NID_X9_62_prime256v1)); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + ASSERT_OK_POSITIVE(sym_EVP_PKEY_generate(kctx, &pkey)); + + /* Export uncompressed public key point (0x04 || X || Y) */ + size_t key_sz = 0; + ASSERT_OK_POSITIVE(sym_EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, NULL, 0, &key_sz)); + struct iovec pubkey = IOVEC_ALLOCA(key_sz); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, pubkey.iov_base, key_sz, &key_sz)); + pubkey.iov_len = key_sz; + + /* Sign the existing test_digest with ECDSA */ + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *sctx = ASSERT_NOT_NULL(sym_EVP_PKEY_CTX_new(pkey, NULL)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_sign_init(sctx)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_CTX_set_signature_md(sctx, sym_EVP_sha256())); + size_t sig_sz; + ASSERT_OK_POSITIVE(sym_EVP_PKEY_sign(sctx, NULL, &sig_sz, test_digest.iov_base, test_digest.iov_len)); + struct iovec signature_der = IOVEC_ALLOCA(sig_sz); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_sign(sctx, signature_der.iov_base, &signature_der.iov_len, test_digest.iov_base, test_digest.iov_len)); + + /* ECDSA signature is DER encoded. Extract r and s components. */ + const uint8_t *p = signature_der.iov_base; + _cleanup_(ECDSA_SIG_freep) ECDSA_SIG *esig = ASSERT_NOT_NULL(sym_d2i_ECDSA_SIG(NULL, &p, signature_der.iov_len)); + + const BIGNUM *r_bn = sym_ECDSA_SIG_get0_r(esig); + const BIGNUM *s_bn = sym_ECDSA_SIG_get0_s(esig); + + _cleanup_(iovec_done) struct iovec r_iov = {}, s_iov = {}; + export_bn(r_bn, &r_iov); + export_bn(s_bn, &s_iov); + + iovec_dump_c("test_ecdsa_r_buf", &r_iov); + iovec_dump_c("test_ecdsa_s_buf", &s_iov); + iovec_dump_c("test_ecdsa_key_buf", &pubkey); +} + +#define TEST_ECDSA_VERIFY(r, s, digest, key, expected) \ + if (expected >= 0) \ + ASSERT_OK_EQ(dnssec_ecdsa_verify_raw( \ + sym_EVP_sha256(), \ + NID_X9_62_prime256v1, \ + (r).iov_base, (r).iov_len, \ + (s).iov_base, (s).iov_len, \ + (digest).iov_base, (digest).iov_len, \ + (key).iov_base, (key).iov_len), \ + expected); \ + else \ + ASSERT_ERROR(dnssec_ecdsa_verify_raw( \ + sym_EVP_sha256(), \ + NID_X9_62_prime256v1, \ + (r).iov_base, (r).iov_len, \ + (s).iov_base, (s).iov_len, \ + (digest).iov_base, (digest).iov_len, \ + (key).iov_base, (key).iov_len), \ + -expected); + +TEST(dnssec_ecdsa_verify_raw) { +#if !defined(OPENSSL_NO_DEPRECATED_3_0) + uint8_t *p; + + /* Normal verification */ + TEST_ECDSA_VERIFY(test_ecdsa_r, test_ecdsa_s, test_digest, test_ecdsa_key, 1); + + /* Fuzzing R component */ + _cleanup_(iovec_done) struct iovec bad_r = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_ecdsa_r, &bad_r)); + p = bad_r.iov_base; + p[0] ^= 0x01; + TEST_ECDSA_VERIFY(bad_r, test_ecdsa_s, test_digest, test_ecdsa_key, 0); + + p[0] ^= 0x01; + bad_r.iov_len -= 1; + TEST_ECDSA_VERIFY(bad_r, test_ecdsa_s, test_digest, test_ecdsa_key, 0); + + /* Fuzzing S component */ + _cleanup_(iovec_done) struct iovec bad_s = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_ecdsa_s, &bad_s)); + p = bad_s.iov_base; + p[0] ^= 0x01; + TEST_ECDSA_VERIFY(test_ecdsa_r, bad_s, test_digest, test_ecdsa_key, 0); + + p[0] ^= 0x01; + bad_s.iov_len -= 1; + TEST_ECDSA_VERIFY(test_ecdsa_r, bad_s, test_digest, test_ecdsa_key, 0); + + /* Fuzzing Digest */ + _cleanup_(iovec_done) struct iovec bad_digest = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_digest, &bad_digest)); + p = bad_digest.iov_base; + p[0] ^= 0x01; + TEST_ECDSA_VERIFY(test_ecdsa_r, test_ecdsa_s, bad_digest, test_ecdsa_key, 0); + + p[0] ^= 0x01; + bad_digest.iov_len -= 1; + TEST_ECDSA_VERIFY(test_ecdsa_r, test_ecdsa_s, bad_digest, test_ecdsa_key, 0); + + /* Fuzzing Public Key Point */ + _cleanup_(iovec_done) struct iovec bad_key = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_ecdsa_key, &bad_key)); + p = bad_key.iov_base; + p[bad_key.iov_len - 1] ^= 0x01; + TEST_ECDSA_VERIFY(test_ecdsa_r, test_ecdsa_s, test_digest, bad_key, -ENOTRECOVERABLE); + + p[bad_key.iov_len - 1] ^= 0x01; + bad_key.iov_len -= 1; + TEST_ECDSA_VERIFY(test_ecdsa_r, test_ecdsa_s, test_digest, bad_key, -ENOTRECOVERABLE); +#else + TEST_ECDSA_VERIFY(test_ecdsa_r, test_ecdsa_s, test_digest, test_ecdsa_key, -EOPNOTSUPP); +#endif +} + static int intro(void) { if (DLOPEN_LIBCRYPTO(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0) return EXIT_TEST_SKIP;