]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolve: add support for RFC 8080 (#7600)
authorott <ott@users.noreply.github.com>
Tue, 12 Dec 2017 15:30:12 +0000 (16:30 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 12 Dec 2017 15:30:12 +0000 (16:30 +0100)
RFC 8080 describes how to use EdDSA keys and signatures in DNSSEC. It
uses the curves Ed25519 and Ed448. Libgcrypt 1.8.1 does not support
Ed448, so only the Ed25519 is supported at the moment. Once Libgcrypt
supports Ed448, support for it can be trivially added to resolve.

src/resolve/RFCs
src/resolve/resolved-dns-dnssec.c
src/resolve/resolved-dns-rr.c
src/resolve/resolved-dns-rr.h
src/resolve/test-dnssec.c

index 09c85f951820eaae3c6f106693c406b10dbb9f3d..7190c16faaad6e01222734e4ec687216da086c26 100644 (file)
@@ -53,6 +53,7 @@ Y https://tools.ietf.org/html/rfc6975 → Signaling Cryptographic Algorithm Unde
 Y https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS
 Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors
 ~ https://tools.ietf.org/html/rfc7719 → DNS Terminology
+Y https://tools.ietf.org/html/rfc8080 → Edwards-Curve Digital Security Algorithm (EdDSA) for DNSSEC
 
 Also relevant:
 
index f04b246a3d6a9a3c0a64f7ee1f0f30424e4075ce..e3eca7e62cc859ffc1d26ae0f8f92c5609299b3f 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <stdio_ext.h>
+
 #if HAVE_GCRYPT
 #include <gcrypt.h>
 #endif
 
 #include "alloc-util.h"
 #include "dns-domain.h"
+#include "fd-util.h"
+#include "fileio.h"
 #include "gcrypt-util.h"
 #include "hexdecoct.h"
 #include "resolved-dns-dnssec.h"
@@ -436,6 +440,99 @@ static int dnssec_ecdsa_verify(
                         q, key_size*2+1);
 }
 
+#if GCRYPT_VERSION_NUMBER >= 0x010600
+static int dnssec_eddsa_verify_raw(
+                const char *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) {
+
+        gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
+        gcry_error_t ge;
+        int k;
+
+        ge = gcry_sexp_build(&signature_sexp,
+                             NULL,
+                             "(sig-val (eddsa (r %b) (s %b)))",
+                             (int) signature_r_size,
+                             signature_r,
+                             (int) signature_s_size,
+                             signature_s);
+        if (ge != 0) {
+                k = -EIO;
+                goto finish;
+        }
+
+        ge = gcry_sexp_build(&data_sexp,
+                             NULL,
+                             "(data (flags eddsa) (hash-algo sha512) (value %b))",
+                             (int) data_size,
+                             data);
+        if (ge != 0) {
+                k = -EIO;
+                goto finish;
+        }
+
+        ge = gcry_sexp_build(&public_key_sexp,
+                             NULL,
+                             "(public-key (ecc (curve %s) (flags eddsa) (q %b)))",
+                             curve,
+                             (int) key_size,
+                             key);
+        if (ge != 0) {
+                k = -EIO;
+                goto finish;
+        }
+
+        ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
+        if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
+                k = 0;
+        else if (ge != 0) {
+                log_debug("EdDSA signature check failed: %s", gpg_strerror(ge));
+                k = -EIO;
+        } else
+                k = 1;
+finish:
+        if (public_key_sexp)
+                gcry_sexp_release(public_key_sexp);
+        if (signature_sexp)
+                gcry_sexp_release(signature_sexp);
+        if (data_sexp)
+                gcry_sexp_release(data_sexp);
+
+        return k;
+}
+
+static int dnssec_eddsa_verify(
+                int algorithm,
+                const void *data, size_t data_size,
+                DnsResourceRecord *rrsig,
+                DnsResourceRecord *dnskey) {
+        const char *curve;
+        size_t key_size;
+
+        if (algorithm == DNSSEC_ALGORITHM_ED25519) {
+                curve = "Ed25519";
+                key_size = 32;
+        } else
+                return -EOPNOTSUPP;
+
+        if (dnskey->dnskey.key_size != key_size)
+                return -EINVAL;
+
+        if (rrsig->rrsig.signature_size != key_size * 2)
+                return -EINVAL;
+
+        return dnssec_eddsa_verify_raw(
+                        curve,
+                        rrsig->rrsig.signature, key_size,
+                        (uint8_t*) rrsig->rrsig.signature + key_size, key_size,
+                        data, data_size,
+                        dnskey->dnskey.key, key_size);
+}
+#endif
+
 static void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
         gcry_md_write(md, &v, sizeof(v));
 }
@@ -445,9 +542,18 @@ static void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
         gcry_md_write(md, &v, sizeof(v));
 }
 
-static void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
+static void fwrite_uint8(FILE *fp, uint8_t v) {
+        fwrite(&v, sizeof(v), 1, fp);
+}
+
+static void fwrite_uint16(FILE *fp, uint16_t v) {
+        v = htobe16(v);
+        fwrite(&v, sizeof(v), 1, fp);
+}
+
+static void fwrite_uint32(FILE *fp, uint32_t v) {
         v = htobe32(v);
-        gcry_md_write(md, &v, sizeof(v));
+        fwrite(&v, sizeof(v), 1, fp);
 }
 
 static int dnssec_rrsig_prepare(DnsResourceRecord *rrsig) {
@@ -613,6 +719,9 @@ int dnssec_verify_rrset(
         gcry_md_hd_t md = NULL;
         int r, md_algorithm;
         size_t k, n = 0;
+        size_t sig_size = 0;
+        _cleanup_free_ char *sig_data = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
         size_t hash_size;
         void *hash;
         bool wildcard;
@@ -628,14 +737,6 @@ int dnssec_verify_rrset(
          * using the signature "rrsig" and the key "dnskey". It's
          * assumed that RRSIG and DNSKEY match. */
 
-        md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm);
-        if (md_algorithm == -EOPNOTSUPP) {
-                *result = DNSSEC_UNSUPPORTED_ALGORITHM;
-                return 0;
-        }
-        if (md_algorithm < 0)
-                return md_algorithm;
-
         r = dnssec_rrsig_prepare(rrsig);
         if (r == -EINVAL) {
                 *result = DNSSEC_INVALID;
@@ -725,28 +826,23 @@ int dnssec_verify_rrset(
         /* Bring the RRs into canonical order */
         qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
 
-        /* OK, the RRs are now in canonical order. Let's calculate the digest */
-        initialize_libgcrypt(false);
-
-        hash_size = gcry_md_get_algo_dlen(md_algorithm);
-        assert(hash_size > 0);
-
-        gcry_md_open(&md, md_algorithm, 0);
-        if (!md)
-                return -EIO;
+        f = open_memstream(&sig_data, &sig_size);
+        if (!f)
+                return -ENOMEM;
+        __fsetlocking(f, FSETLOCKING_BYCALLER);
 
-        md_add_uint16(md, rrsig->rrsig.type_covered);
-        md_add_uint8(md, rrsig->rrsig.algorithm);
-        md_add_uint8(md, rrsig->rrsig.labels);
-        md_add_uint32(md, rrsig->rrsig.original_ttl);
-        md_add_uint32(md, rrsig->rrsig.expiration);
-        md_add_uint32(md, rrsig->rrsig.inception);
-        md_add_uint16(md, rrsig->rrsig.key_tag);
+        fwrite_uint16(f, rrsig->rrsig.type_covered);
+        fwrite_uint8(f, rrsig->rrsig.algorithm);
+        fwrite_uint8(f, rrsig->rrsig.labels);
+        fwrite_uint32(f, rrsig->rrsig.original_ttl);
+        fwrite_uint32(f, rrsig->rrsig.expiration);
+        fwrite_uint32(f, rrsig->rrsig.inception);
+        fwrite_uint16(f, rrsig->rrsig.key_tag);
 
         r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
         if (r < 0)
                 goto finish;
-        gcry_md_write(md, wire_format_name, r);
+        fwrite(wire_format_name, 1, r, f);
 
         /* Convert the source of synthesis into wire format */
         r = dns_name_to_wire_format(source, wire_format_name, sizeof(wire_format_name), true);
@@ -760,24 +856,66 @@ int dnssec_verify_rrset(
 
                 /* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */
                 if (wildcard)
-                        gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
-                gcry_md_write(md, wire_format_name, r);
+                        fwrite((uint8_t[]) { 1, '*'}, sizeof(uint8_t), 2, f);
+                fwrite(wire_format_name, 1, r, f);
 
-                md_add_uint16(md, rr->key->type);
-                md_add_uint16(md, rr->key->class);
-                md_add_uint32(md, rrsig->rrsig.original_ttl);
+                fwrite_uint16(f, rr->key->type);
+                fwrite_uint16(f, rr->key->class);
+                fwrite_uint32(f, rrsig->rrsig.original_ttl);
 
                 l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr);
                 assert(l <= 0xFFFF);
 
-                md_add_uint16(md, (uint16_t) l);
-                gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l);
+                fwrite_uint16(f, (uint16_t) l);
+                fwrite(DNS_RESOURCE_RECORD_RDATA(rr), 1, l, f);
         }
 
-        hash = gcry_md_read(md, 0);
-        if (!hash) {
-                r = -EIO;
+        r = fflush_and_check(f);
+        if (r < 0)
+                return r;
+
+        initialize_libgcrypt(false);
+
+        switch (rrsig->rrsig.algorithm) {
+#if GCRYPT_VERSION_NUMBER >= 0x010600
+        case DNSSEC_ALGORITHM_ED25519:
+                break;
+#else
+        case DNSSEC_ALGORITHM_ED25519:
+#endif
+        case DNSSEC_ALGORITHM_ED448:
+                *result = DNSSEC_UNSUPPORTED_ALGORITHM;
+                r = 0;
                 goto finish;
+        default:
+                /* OK, the RRs are now in canonical order. Let's calculate the digest */
+                md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm);
+                if (md_algorithm == -EOPNOTSUPP) {
+                        *result = DNSSEC_UNSUPPORTED_ALGORITHM;
+                        r = 0;
+                        goto finish;
+                }
+                if (md_algorithm < 0) {
+                        r = md_algorithm;
+                        goto finish;
+                }
+
+                gcry_md_open(&md, md_algorithm, 0);
+                if (!md) {
+                        r = -EIO;
+                        goto finish;
+                }
+
+                hash_size = gcry_md_get_algo_dlen(md_algorithm);
+                assert(hash_size > 0);
+
+                gcry_md_write(md, sig_data, sig_size);
+
+                hash = gcry_md_read(md, 0);
+                if (!hash) {
+                        r = -EIO;
+                        goto finish;
+                }
         }
 
         switch (rrsig->rrsig.algorithm) {
@@ -802,6 +940,15 @@ int dnssec_verify_rrset(
                                 rrsig,
                                 dnskey);
                 break;
+#if GCRYPT_VERSION_NUMBER >= 0x010600
+        case DNSSEC_ALGORITHM_ED25519:
+                r = dnssec_eddsa_verify(
+                                rrsig->rrsig.algorithm,
+                                sig_data, sig_size,
+                                rrsig,
+                                dnskey);
+                break;
+#endif
         }
 
         if (r < 0)
@@ -821,7 +968,9 @@ int dnssec_verify_rrset(
         r = 0;
 
 finish:
-        gcry_md_close(md);
+        if (md)
+                gcry_md_close(md);
+
         return r;
 }
 
index 4a0c1d6a3543351b6edea6c528dfcfae914f46d8..26d2cbcc200af37c26af07bea3cf27c8e78a86a4 100644 (file)
@@ -1849,6 +1849,8 @@ static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] =
         [DNSSEC_ALGORITHM_ECC_GOST]           = "ECC-GOST",
         [DNSSEC_ALGORITHM_ECDSAP256SHA256]    = "ECDSAP256SHA256",
         [DNSSEC_ALGORITHM_ECDSAP384SHA384]    = "ECDSAP384SHA384",
+        [DNSSEC_ALGORITHM_ED25519]            = "ED25519",
+        [DNSSEC_ALGORITHM_ED448]              = "ED448",
         [DNSSEC_ALGORITHM_INDIRECT]           = "INDIRECT",
         [DNSSEC_ALGORITHM_PRIVATEDNS]         = "PRIVATEDNS",
         [DNSSEC_ALGORITHM_PRIVATEOID]         = "PRIVATEOID",
index 3f3d46e6b3e99034261c582511a63db25b35305a..8e1a6bb2dc4905e7b690d7a7e2cd893a6300d1b9 100644 (file)
@@ -57,6 +57,8 @@ enum {
         DNSSEC_ALGORITHM_ECC_GOST = 12,        /* RFC 5933 */
         DNSSEC_ALGORITHM_ECDSAP256SHA256 = 13, /* RFC 6605 */
         DNSSEC_ALGORITHM_ECDSAP384SHA384 = 14, /* RFC 6605 */
+        DNSSEC_ALGORITHM_ED25519 = 15,         /* RFC 8080 */
+        DNSSEC_ALGORITHM_ED448 = 16,           /* RFC 8080 */
         DNSSEC_ALGORITHM_INDIRECT = 252,
         DNSSEC_ALGORITHM_PRIVATEDNS,
         DNSSEC_ALGORITHM_PRIVATEOID,
index aea7fff1d6d62efcd55eb7a7aacbd8f1a9d3abfc..2d2b5e31bf807b2b15c1847e8ed88fda37fb9b14 100644 (file)
@@ -19,6 +19,7 @@
 ***/
 
 #include <arpa/inet.h>
+#include <gcrypt.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 
@@ -124,6 +125,189 @@ static void test_dnssec_verify_dns_key(void) {
         assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds2, false) > 0);
 }
 
+static void test_dnssec_verify_rfc8080_ed25519_example1(void) {
+        static const uint8_t dnskey_blob[] = {
+                0x97, 0x4d, 0x96, 0xa2, 0x2d, 0x22, 0x4b, 0xc0, 0x1a, 0xdb, 0x91, 0x50, 0x91, 0x47, 0x7d,
+                0x44, 0xcc, 0xd9, 0x1c, 0x9a, 0x41, 0xa1, 0x14, 0x30, 0x01, 0x01, 0x17, 0xd5, 0x2c, 0x59,
+                0x24, 0xe
+        };
+        static const uint8_t ds_fprint[] = {
+                0xdd, 0xa6, 0xb9, 0x69, 0xbd, 0xfb, 0x79, 0xf7, 0x1e, 0xe7, 0xb7, 0xfb, 0xdf, 0xb7, 0xdc,
+                0xd7, 0xad, 0xbb, 0xd3, 0x5d, 0xdf, 0x79, 0xed, 0x3b, 0x6d, 0xd7, 0xf6, 0xe3, 0x56, 0xdd,
+                0xd7, 0x47, 0xf7, 0x6f, 0x5f, 0x7a, 0xe1, 0xa6, 0xf9, 0xe5, 0xce, 0xfc, 0x7b, 0xbf, 0x5a,
+                0xdf, 0x4e, 0x1b
+        };
+        static const uint8_t signature_blob[] = {
+                0xa0, 0xbf, 0x64, 0xac, 0x9b, 0xa7, 0xef, 0x17, 0xc1, 0x38, 0x85, 0x9c, 0x18, 0x78, 0xbb,
+                0x99, 0xa8, 0x39, 0xfe, 0x17, 0x59, 0xac, 0xa5, 0xb0, 0xd7, 0x98, 0xcf, 0x1a, 0xb1, 0xe9,
+                0x8d, 0x07, 0x91, 0x02, 0xf4, 0xdd, 0xb3, 0x36, 0x8f, 0x0f, 0xe4, 0x0b, 0xb3, 0x77, 0xf1,
+                0xf0, 0x0e, 0x0c, 0xdd, 0xed, 0xb7, 0x99, 0x16, 0x7d, 0x56, 0xb6, 0xe9, 0x32, 0x78, 0x30,
+                0x72, 0xba, 0x8d, 0x02
+        };
+
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds = NULL, *mx = NULL,
+                *rrsig = NULL;
+        _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+        DnssecResult result;
+
+        dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "example.com.");
+        assert_se(dnskey);
+
+        dnskey->dnskey.flags = 257;
+        dnskey->dnskey.protocol = 3;
+        dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_ED25519;
+        dnskey->dnskey.key_size = sizeof(dnskey_blob);
+        dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
+        assert_se(dnskey->dnskey.key);
+
+        log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
+
+        ds = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "example.com.");
+        assert_se(ds);
+
+        ds->ds.key_tag = 3613;
+        ds->ds.algorithm = DNSSEC_ALGORITHM_ED25519;
+        ds->ds.digest_type = DNSSEC_DIGEST_SHA256;
+        ds->ds.digest_size = sizeof(ds_fprint);
+        ds->ds.digest = memdup(ds_fprint, ds->ds.digest_size);
+        assert_se(ds->ds.digest);
+
+        log_info("DS: %s", strna(dns_resource_record_to_string(ds)));
+
+        mx = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_MX, "example.com.");
+        assert_se(mx);
+
+        mx->mx.priority = 10;
+        mx->mx.exchange = strdup("mail.example.com.");
+        assert_se(mx->mx.exchange);
+
+        log_info("MX: %s", strna(dns_resource_record_to_string(mx)));
+
+        rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "example.com.");
+        assert_se(rrsig);
+
+        rrsig->rrsig.type_covered = DNS_TYPE_MX;
+        rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_ED25519;
+        rrsig->rrsig.labels = 2;
+        rrsig->rrsig.original_ttl = 3600;
+        rrsig->rrsig.expiration = 1440021600;
+        rrsig->rrsig.inception = 1438207200;
+        rrsig->rrsig.key_tag = 3613;
+        rrsig->rrsig.signer = strdup("example.com.");
+        assert_se(rrsig->rrsig.signer);
+        rrsig->rrsig.signature_size = sizeof(signature_blob);
+        rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size);
+        assert_se(rrsig->rrsig.signature);
+
+        log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig)));
+
+        assert_se(dnssec_key_match_rrsig(mx->key, rrsig) > 0);
+        assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0);
+
+        answer = dns_answer_new(1);
+        assert_se(answer);
+        assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED) >= 0);
+
+        assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey,
+                                rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0);
+#if GCRYPT_VERSION_NUMBER >= 0x010600
+        assert_se(result == DNSSEC_VALIDATED);
+#else
+        assert_se(result == DNSSEC_UNSUPPORTED_ALGORITHM);
+#endif
+}
+
+static void test_dnssec_verify_rfc8080_ed25519_example2(void) {
+        static const uint8_t dnskey_blob[] = {
+                0xcc, 0xf9, 0xd9, 0xfd, 0x0c, 0x04, 0x7b, 0xb4, 0xbc, 0x0b, 0x94, 0x8f, 0xcf, 0x63, 0x9f,
+                0x4b, 0x94, 0x51, 0xe3, 0x40, 0x13, 0x93, 0x6f, 0xeb, 0x62, 0x71, 0x3d, 0xc4, 0x72, 0x4,
+                0x8a, 0x3b
+        };
+        static const uint8_t ds_fprint[] = {
+                0xe3, 0x4d, 0x7b, 0xf3, 0x56, 0xfd, 0xdf, 0x87, 0xb7, 0xf7, 0x67, 0x5e, 0xe3, 0xdd, 0x9e,
+                0x73, 0xbe, 0xda, 0x7b, 0x67, 0xb5, 0xe5, 0xde, 0xf4, 0x7f, 0xae, 0x7b, 0xe5, 0xad, 0x5c,
+                0xd1, 0xb7, 0x39, 0xf5, 0xce, 0x76, 0xef, 0x97, 0x34, 0xe1, 0xe6, 0xde, 0xf3, 0x47, 0x3a,
+                0xeb, 0x5e, 0x1c
+        };
+        static const uint8_t signature_blob[] = {
+                0xcd, 0x74, 0x34, 0x6e, 0x46, 0x20, 0x41, 0x31, 0x05, 0xc9, 0xf2, 0xf2, 0x8b, 0xd4, 0x28,
+                0x89, 0x8e, 0x83, 0xf1, 0x97, 0x58, 0xa3, 0x8c, 0x32, 0x52, 0x15, 0x62, 0xa1, 0x86, 0x57,
+                0x15, 0xd4, 0xf8, 0xd7, 0x44, 0x0f, 0x44, 0x84, 0xd0, 0x4a, 0xa2, 0x52, 0x9f, 0x34, 0x28,
+                0x4a, 0x6e, 0x69, 0xa0, 0x9e, 0xe0, 0x0f, 0xb0, 0x10, 0x47, 0x43, 0xbb, 0x2a, 0xe2, 0x39,
+                0x93, 0x6a, 0x5c, 0x06
+        };
+
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds = NULL, *mx = NULL,
+                *rrsig = NULL;
+        _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+        DnssecResult result;
+
+        dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "example.com.");
+        assert_se(dnskey);
+
+        dnskey->dnskey.flags = 257;
+        dnskey->dnskey.protocol = 3;
+        dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_ED25519;
+        dnskey->dnskey.key_size = sizeof(dnskey_blob);
+        dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
+        assert_se(dnskey->dnskey.key);
+
+        log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
+
+        ds = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "example.com.");
+        assert_se(ds);
+
+        ds->ds.key_tag = 35217;
+        ds->ds.algorithm = DNSSEC_ALGORITHM_ED25519;
+        ds->ds.digest_type = DNSSEC_DIGEST_SHA256;
+        ds->ds.digest_size = sizeof(ds_fprint);
+        ds->ds.digest = memdup(ds_fprint, ds->ds.digest_size);
+        assert_se(ds->ds.digest);
+
+        log_info("DS: %s", strna(dns_resource_record_to_string(ds)));
+
+        mx = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_MX, "example.com.");
+        assert_se(mx);
+
+        mx->mx.priority = 10;
+        mx->mx.exchange = strdup("mail.example.com.");
+        assert_se(mx->mx.exchange);
+
+        log_info("MX: %s", strna(dns_resource_record_to_string(mx)));
+
+        rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "example.com.");
+        assert_se(rrsig);
+
+        rrsig->rrsig.type_covered = DNS_TYPE_MX;
+        rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_ED25519;
+        rrsig->rrsig.labels = 2;
+        rrsig->rrsig.original_ttl = 3600;
+        rrsig->rrsig.expiration = 1440021600;
+        rrsig->rrsig.inception = 1438207200;
+        rrsig->rrsig.key_tag = 35217;
+        rrsig->rrsig.signer = strdup("example.com.");
+        assert_se(rrsig->rrsig.signer);
+        rrsig->rrsig.signature_size = sizeof(signature_blob);
+        rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size);
+        assert_se(rrsig->rrsig.signature);
+
+        log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig)));
+
+        assert_se(dnssec_key_match_rrsig(mx->key, rrsig) > 0);
+        assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0);
+
+        answer = dns_answer_new(1);
+        assert_se(answer);
+        assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED) >= 0);
+
+        assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey,
+                                rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0);
+#if GCRYPT_VERSION_NUMBER >= 0x010600
+        assert_se(result == DNSSEC_VALIDATED);
+#else
+        assert_se(result == DNSSEC_UNSUPPORTED_ALGORITHM);
+#endif
+}
 static void test_dnssec_verify_rrset(void) {
 
         static const uint8_t signature_blob[] = {
@@ -335,6 +519,8 @@ int main(int argc, char*argv[]) {
 
 #if HAVE_GCRYPT
         test_dnssec_verify_dns_key();
+        test_dnssec_verify_rfc8080_ed25519_example1();
+        test_dnssec_verify_rfc8080_ed25519_example2();
         test_dnssec_verify_rrset();
         test_dnssec_verify_rrset2();
         test_dnssec_nsec3_hash();