/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/

/***
  This file is part of systemd.

  Copyright 2015 Lennart Poettering

  systemd is free software; you can redistribute it and/or modify it
  under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation; either version 2.1 of the License, or
  (at your option) any later version.

  systemd is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/

#include <gcrypt.h>

#include "alloc-util.h"
#include "dns-domain.h"
#include "hexdecoct.h"
#include "resolved-dns-dnssec.h"
#include "resolved-dns-packet.h"
#include "string-table.h"

/* Open question:
 *
 * How does the DNSSEC canonical form of a hostname with a label
 * containing a dot look like, the way DNS-SD does it?
 *
 * TODO:
 *
 *   - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
 *   - multi-label zone compatibility
 *   - cname/dname compatibility
 *   - nxdomain on qname
 *   - bus calls to override DNSEC setting per interface
 *   - log all DNSSEC downgrades
 *   - enable by default
 *
 *   - RFC 4035, Section 5.3.4 (When receiving a positive wildcard reply, use NSEC to ensure it actually really applies)
 *   - RFC 6840, Section 4.1 (ensure we don't get fed a glue NSEC from the parent zone)
 *   - RFC 6840, Section 4.3 (check for CNAME on NSEC too)
 * */

#define VERIFY_RRS_MAX 256
#define MAX_KEY_SIZE (32*1024)

/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)

/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */
#define NSEC3_ITERATIONS_MAX 2500

/*
 * The DNSSEC Chain of trust:
 *
 *            Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
 *            DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
 *            DS RRs are protected like normal RRs
 *
 * Example chain:
 *            Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
 */

static void initialize_libgcrypt(void) {
        const char *p;

        if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
                return;

        p = gcry_check_version("1.4.5");
        assert(p);

        gcry_control(GCRYCTL_DISABLE_SECMEM);
        gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
}

uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) {
        const uint8_t *p;
        uint32_t sum, f;
        size_t i;

        /* The algorithm from RFC 4034, Appendix B. */

        assert(dnskey);
        assert(dnskey->key->type == DNS_TYPE_DNSKEY);

        f = (uint32_t) dnskey->dnskey.flags;

        if (mask_revoke)
                f &= ~DNSKEY_FLAG_REVOKE;

        sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);

        p = dnskey->dnskey.key;

        for (i = 0; i < dnskey->dnskey.key_size; i++)
                sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];

        sum += (sum >> 16) & UINT32_C(0xFFFF);

        return sum & UINT32_C(0xFFFF);
}

static int rr_compare(const void *a, const void *b) {
        DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
        size_t m;
        int r;

        /* Let's order the RRs according to RFC 4034, Section 6.3 */

        assert(x);
        assert(*x);
        assert((*x)->wire_format);
        assert(y);
        assert(*y);
        assert((*y)->wire_format);

        m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y));

        r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m);
        if (r != 0)
                return r;

        if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
                return -1;
        else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
                return 1;

        return 0;
}

static int dnssec_rsa_verify_raw(
                const char *hash_algorithm,
                const void *signature, size_t signature_size,
                const void *data, size_t data_size,
                const void *exponent, size_t exponent_size,
                const void *modulus, size_t modulus_size) {

        gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
        gcry_mpi_t n = NULL, e = NULL, s = NULL;
        gcry_error_t ge;
        int r;

        assert(hash_algorithm);

        ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
        if (ge != 0) {
                r = -EIO;
                goto finish;
        }

        ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
        if (ge != 0) {
                r = -EIO;
                goto finish;
        }

        ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
        if (ge != 0) {
                r = -EIO;
                goto finish;
        }

        ge = gcry_sexp_build(&signature_sexp,
                             NULL,
                             "(sig-val (rsa (s %m)))",
                             s);

        if (ge != 0) {
                r = -EIO;
                goto finish;
        }

        ge = gcry_sexp_build(&data_sexp,
                             NULL,
                             "(data (flags pkcs1) (hash %s %b))",
                             hash_algorithm,
                             (int) data_size,
                             data);
        if (ge != 0) {
                r = -EIO;
                goto finish;
        }

        ge = gcry_sexp_build(&public_key_sexp,
                             NULL,
                             "(public-key (rsa (n %m) (e %m)))",
                             n,
                             e);
        if (ge != 0) {
                r = -EIO;
                goto finish;
        }

        ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
        if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
                r = 0;
        else if (ge != 0) {
                log_debug("RSA signature check failed: %s", gpg_strerror(ge));
                r = -EIO;
        } else
                r = 1;

finish:
        if (e)
                gcry_mpi_release(e);
        if (n)
                gcry_mpi_release(n);
        if (s)
                gcry_mpi_release(s);

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

static int dnssec_rsa_verify(
                const char *hash_algorithm,
                const void *hash, size_t hash_size,
                DnsResourceRecord *rrsig,
                DnsResourceRecord *dnskey) {

        size_t exponent_size, modulus_size;
        void *exponent, *modulus;

        assert(hash_algorithm);
        assert(hash);
        assert(hash_size > 0);
        assert(rrsig);
        assert(dnskey);

        if (*(uint8_t*) dnskey->dnskey.key == 0) {
                /* exponent is > 255 bytes long */

                exponent = (uint8_t*) dnskey->dnskey.key + 3;
                exponent_size =
                        ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) |
                        ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]);

                if (exponent_size < 256)
                        return -EINVAL;

                if (3 + exponent_size >= dnskey->dnskey.key_size)
                        return -EINVAL;

                modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
                modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;

        } else {
                /* exponent is <= 255 bytes long */

                exponent = (uint8_t*) dnskey->dnskey.key + 1;
                exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];

                if (exponent_size <= 0)
                        return -EINVAL;

                if (1 + exponent_size >= dnskey->dnskey.key_size)
                        return -EINVAL;

                modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
                modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
        }

        return dnssec_rsa_verify_raw(
                        hash_algorithm,
                        rrsig->rrsig.signature, rrsig->rrsig.signature_size,
                        hash, hash_size,
                        exponent, exponent_size,
                        modulus, modulus_size);
}

static int dnssec_ecdsa_verify_raw(
                const char *hash_algorithm,
                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_mpi_t q = NULL, r = NULL, s = NULL;
        gcry_error_t ge;
        int k;

        assert(hash_algorithm);

        ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL);
        if (ge != 0) {
                k = -EIO;
                goto finish;
        }

        ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL);
        if (ge != 0) {
                k = -EIO;
                goto finish;
        }

        ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL);
        if (ge != 0) {
                k = -EIO;
                goto finish;
        }

        ge = gcry_sexp_build(&signature_sexp,
                             NULL,
                             "(sig-val (ecdsa (r %m) (s %m)))",
                             r,
                             s);
        if (ge != 0) {
                k = -EIO;
                goto finish;
        }

        ge = gcry_sexp_build(&data_sexp,
                             NULL,
                             "(data (flags rfc6979) (hash %s %b))",
                             hash_algorithm,
                             (int) data_size,
                             data);
        if (ge != 0) {
                k = -EIO;
                goto finish;
        }

        ge = gcry_sexp_build(&public_key_sexp,
                             NULL,
                             "(public-key (ecc (curve %s) (q %m)))",
                             curve,
                             q);
        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("ECDSA signature check failed: %s", gpg_strerror(ge));
                k = -EIO;
        } else
                k = 1;
finish:
        if (r)
                gcry_mpi_release(r);
        if (s)
                gcry_mpi_release(s);
        if (q)
                gcry_mpi_release(q);

        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_ecdsa_verify(
                const char *hash_algorithm,
                int algorithm,
                const void *hash, size_t hash_size,
                DnsResourceRecord *rrsig,
                DnsResourceRecord *dnskey) {

        const char *curve;
        size_t key_size;
        uint8_t *q;

        assert(hash);
        assert(hash_size);
        assert(rrsig);
        assert(dnskey);

        if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) {
                key_size = 32;
                curve = "NIST P-256";
        } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) {
                key_size = 48;
                curve = "NIST P-384";
        } else
                return -EOPNOTSUPP;

        if (dnskey->dnskey.key_size != key_size * 2)
                return -EINVAL;

        if (rrsig->rrsig.signature_size != key_size * 2)
                return -EINVAL;

        q = alloca(key_size*2 + 1);
        q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */
        memcpy(q+1, dnskey->dnskey.key, key_size*2);

        return dnssec_ecdsa_verify_raw(
                        hash_algorithm,
                        curve,
                        rrsig->rrsig.signature, key_size,
                        (uint8_t*) rrsig->rrsig.signature + key_size, key_size,
                        hash, hash_size,
                        q, key_size*2+1);
}

static void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
        gcry_md_write(md, &v, sizeof(v));
}

static void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
        v = htobe16(v);
        gcry_md_write(md, &v, sizeof(v));
}

static void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
        v = htobe32(v);
        gcry_md_write(md, &v, sizeof(v));
}

static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
        usec_t expiration, inception, skew;

        assert(rrsig);
        assert(rrsig->key->type == DNS_TYPE_RRSIG);

        if (realtime == USEC_INFINITY)
                realtime = now(CLOCK_REALTIME);

        expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
        inception = rrsig->rrsig.inception * USEC_PER_SEC;

        if (inception > expiration)
                return -EKEYREJECTED;

        /* Permit a certain amount of clock skew of 10% of the valid
         * time range. This takes inspiration from unbound's
         * resolver. */
        skew = (expiration - inception) / 10;
        if (skew > SKEW_MAX)
                skew = SKEW_MAX;

        if (inception < skew)
                inception = 0;
        else
                inception -= skew;

        if (expiration + skew < expiration)
                expiration = USEC_INFINITY;
        else
                expiration += skew;

        return realtime < inception || realtime > expiration;
}

static int algorithm_to_gcrypt_md(uint8_t algorithm) {

        /* Translates a DNSSEC signature algorithm into a gcrypt
         * digest identifier.
         *
         * Note that we implement all algorithms listed as "Must
         * implement" and "Recommended to Implement" in RFC6944. We
         * don't implement any algorithms that are listed as
         * "Optional" or "Must Not Implement". Specifically, we do not
         * implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and
         * GOST-ECC. */

        switch (algorithm) {

        case DNSSEC_ALGORITHM_RSASHA1:
        case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
                return GCRY_MD_SHA1;

        case DNSSEC_ALGORITHM_RSASHA256:
        case DNSSEC_ALGORITHM_ECDSAP256SHA256:
                return GCRY_MD_SHA256;

        case DNSSEC_ALGORITHM_ECDSAP384SHA384:
                return GCRY_MD_SHA384;

        case DNSSEC_ALGORITHM_RSASHA512:
                return GCRY_MD_SHA512;

        default:
                return -EOPNOTSUPP;
        }
}

int dnssec_verify_rrset(
                DnsAnswer *a,
                const DnsResourceKey *key,
                DnsResourceRecord *rrsig,
                DnsResourceRecord *dnskey,
                usec_t realtime,
                DnssecResult *result) {

        uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
        size_t hash_size;
        void *hash;
        DnsResourceRecord **list, *rr;
        gcry_md_hd_t md = NULL;
        int r, md_algorithm;
        bool wildcard = false;
        size_t k, n = 0;

        assert(key);
        assert(rrsig);
        assert(dnskey);
        assert(result);
        assert(rrsig->key->type == DNS_TYPE_RRSIG);
        assert(dnskey->key->type == DNS_TYPE_DNSKEY);

        /* Verifies the the RRSet matching the specified "key" in "a",
         * using the signature "rrsig" and the key "dnskey". It's
         * assumed the 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_expired(rrsig, realtime);
        if (r < 0)
                return r;
        if (r > 0) {
                *result = DNSSEC_SIGNATURE_EXPIRED;
                return 0;
        }

        /* Collect all relevant RRs in a single array, so that we can look at the RRset */
        list = newa(DnsResourceRecord *, dns_answer_size(a));

        DNS_ANSWER_FOREACH(rr, a) {
                r = dns_resource_key_equal(key, rr->key);
                if (r < 0)
                        return r;
                if (r == 0)
                        continue;

                /* We need the wire format for ordering, and digest calculation */
                r = dns_resource_record_to_wire_format(rr, true);
                if (r < 0)
                        return r;

                list[n++] = rr;

                if (n > VERIFY_RRS_MAX)
                        return -E2BIG;
        }

        if (n <= 0)
                return -ENODATA;

        /* 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();

        hash_size = gcry_md_get_algo_dlen(md_algorithm);
        assert(hash_size > 0);

        gcry_md_open(&md, md_algorithm, 0);
        if (!md)
                return -EIO;

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

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

        for (k = 0; k < n; k++) {
                const char *suffix;
                size_t l;
                rr = list[k];

                r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
                if (r < 0)
                        goto finish;
                if (r > 0) /* This is a wildcard! */ {
                        gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
                        wildcard = true;
                }

                r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true);
                if (r < 0)
                        goto finish;
                gcry_md_write(md, wire_format_name, r);

                md_add_uint16(md, rr->key->type);
                md_add_uint16(md, rr->key->class);
                md_add_uint32(md, 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);
        }

        hash = gcry_md_read(md, 0);
        if (!hash) {
                r = -EIO;
                goto finish;
        }

        switch (rrsig->rrsig.algorithm) {

        case DNSSEC_ALGORITHM_RSASHA1:
        case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
        case DNSSEC_ALGORITHM_RSASHA256:
        case DNSSEC_ALGORITHM_RSASHA512:
                r = dnssec_rsa_verify(
                                gcry_md_algo_name(md_algorithm),
                                hash, hash_size,
                                rrsig,
                                dnskey);
                break;

        case DNSSEC_ALGORITHM_ECDSAP256SHA256:
        case DNSSEC_ALGORITHM_ECDSAP384SHA384:
                r = dnssec_ecdsa_verify(
                                gcry_md_algo_name(md_algorithm),
                                rrsig->rrsig.algorithm,
                                hash, hash_size,
                                rrsig,
                                dnskey);
                break;
        }

        if (r < 0)
                goto finish;

        if (!r)
                *result = DNSSEC_INVALID;
        else if (wildcard)
                *result = DNSSEC_VALIDATED_WILDCARD;
        else
                *result = DNSSEC_VALIDATED;
        r = 0;

finish:
        gcry_md_close(md);
        return r;
}

int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) {

        assert(rrsig);
        assert(dnskey);

        /* Checks if the specified DNSKEY RR matches the key used for
         * the signature in the specified RRSIG RR */

        if (rrsig->key->type != DNS_TYPE_RRSIG)
                return -EINVAL;

        if (dnskey->key->type != DNS_TYPE_DNSKEY)
                return 0;
        if (dnskey->key->class != rrsig->key->class)
                return 0;
        if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
                return 0;
        if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE))
                return 0;
        if (dnskey->dnskey.protocol != 3)
                return 0;
        if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
                return 0;

        if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag)
                return 0;

        return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
}

int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
        int r;

        assert(key);
        assert(rrsig);

        /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */

        if (rrsig->key->type != DNS_TYPE_RRSIG)
                return 0;
        if (rrsig->key->class != key->class)
                return 0;
        if (rrsig->rrsig.type_covered != key->type)
                return 0;

        /* Make sure signer is a parent of the RRset */
        r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer);
        if (r <= 0)
                return r;

        /* Make sure the owner name has at least as many labels as the "label" fields indicates. */
        r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key));
        if (r < 0)
                return r;
        if (r < rrsig->rrsig.labels)
                return 0;

        return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
}

static int dnssec_fix_rrset_ttl(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord *rrsig, usec_t realtime) {
        DnsResourceRecord *rr;
        int r;

        assert(key);
        assert(rrsig);

        DNS_ANSWER_FOREACH(rr, a) {
                r = dns_resource_key_equal(key, rr->key);
                if (r < 0)
                        return r;
                if (r == 0)
                        continue;

                /* Pick the TTL as the minimum of the RR's TTL, the
                 * RR's original TTL according to the RRSIG and the
                 * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */
                rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl);
                rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC;
        }

        return 0;
}

int dnssec_verify_rrset_search(
                DnsAnswer *a,
                const DnsResourceKey *key,
                DnsAnswer *validated_dnskeys,
                usec_t realtime,
                DnssecResult *result,
                DnsResourceRecord **ret_rrsig) {

        bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
        DnsResourceRecord *rrsig;
        int r;

        assert(key);
        assert(result);

        /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */

        if (!a || a->n_rrs <= 0)
                return -ENODATA;

        /* Iterate through each RRSIG RR. */
        DNS_ANSWER_FOREACH(rrsig, a) {
                DnsResourceRecord *dnskey;
                DnsAnswerFlags flags;

                /* Is this an RRSIG RR that applies to RRs matching our key? */
                r = dnssec_key_match_rrsig(key, rrsig);
                if (r < 0)
                        return r;
                if (r == 0)
                        continue;

                found_rrsig = true;

                /* Look for a matching key */
                DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) {
                        DnssecResult one_result;

                        if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
                                continue;

                        /* Is this a DNSKEY RR that matches they key of our RRSIG? */
                        r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false);
                        if (r < 0)
                                return r;
                        if (r == 0)
                                continue;

                        /* Take the time here, if it isn't set yet, so
                         * that we do all validations with the same
                         * time. */
                        if (realtime == USEC_INFINITY)
                                realtime = now(CLOCK_REALTIME);

                        /* Yay, we found a matching RRSIG with a matching
                         * DNSKEY, awesome. Now let's verify all entries of
                         * the RRSet against the RRSIG and DNSKEY
                         * combination. */

                        r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result);
                        if (r < 0)
                                return r;

                        switch (one_result) {

                        case DNSSEC_VALIDATED:
                        case DNSSEC_VALIDATED_WILDCARD:
                                /* Yay, the RR has been validated,
                                 * return immediately, but fix up the expiry */
                                r = dnssec_fix_rrset_ttl(a, key, rrsig, realtime);
                                if (r < 0)
                                        return r;

                                if (ret_rrsig)
                                        *ret_rrsig = rrsig;

                                *result = one_result;
                                return 0;

                        case DNSSEC_INVALID:
                                /* If the signature is invalid, let's try another
                                   key and/or signature. After all they
                                   key_tags and stuff are not unique, and
                                   might be shared by multiple keys. */
                                found_invalid = true;
                                continue;

                        case DNSSEC_UNSUPPORTED_ALGORITHM:
                                /* If the key algorithm is
                                   unsupported, try another
                                   RRSIG/DNSKEY pair, but remember we
                                   encountered this, so that we can
                                   return a proper error when we
                                   encounter nothing better. */
                                found_unsupported_algorithm = true;
                                continue;

                        case DNSSEC_SIGNATURE_EXPIRED:
                                /* If the signature is expired, try
                                   another one, but remember it, so
                                   that we can return this */
                                found_expired_rrsig = true;
                                continue;

                        default:
                                assert_not_reached("Unexpected DNSSEC validation result");
                        }
                }
        }

        if (found_expired_rrsig)
                *result = DNSSEC_SIGNATURE_EXPIRED;
        else if (found_unsupported_algorithm)
                *result = DNSSEC_UNSUPPORTED_ALGORITHM;
        else if (found_invalid)
                *result = DNSSEC_INVALID;
        else if (found_rrsig)
                *result = DNSSEC_MISSING_KEY;
        else
                *result = DNSSEC_NO_SIGNATURE;

        if (ret_rrsig)
                *ret_rrsig = NULL;

        return 0;
}

int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
        DnsResourceRecord *rr;
        int r;

        /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */

        DNS_ANSWER_FOREACH(rr, a) {
                r = dnssec_key_match_rrsig(key, rr);
                if (r < 0)
                        return r;
                if (r > 0)
                        return 1;
        }

        return 0;
}

int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
        size_t c = 0;
        int r;

        /* Converts the specified hostname into DNSSEC canonicalized
         * form. */

        if (buffer_max < 2)
                return -ENOBUFS;

        for (;;) {
                r = dns_label_unescape(&n, buffer, buffer_max);
                if (r < 0)
                        return r;
                if (r == 0)
                        break;
                if (r > 0) {
                        int k;

                        /* DNSSEC validation is always done on the ASCII version of the label */
                        k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
                        if (k < 0)
                                return k;
                        if (k > 0)
                                r = k;
                }

                if (buffer_max < (size_t) r + 2)
                        return -ENOBUFS;

                /* The DNSSEC canonical form is not clear on what to
                 * do with dots appearing in labels, the way DNS-SD
                 * does it. Refuse it for now. */

                if (memchr(buffer, '.', r))
                        return -EINVAL;

                ascii_strlower_n(buffer, (size_t) r);
                buffer[r] = '.';

                buffer += r + 1;
                c += r + 1;

                buffer_max -= r + 1;
        }

        if (c <= 0) {
                /* Not even a single label: this is the root domain name */

                assert(buffer_max > 2);
                buffer[0] = '.';
                buffer[1] = 0;

                return 1;
        }

        return (int) c;
}

static int digest_to_gcrypt_md(uint8_t algorithm) {

        /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */

        switch (algorithm) {

        case DNSSEC_DIGEST_SHA1:
                return GCRY_MD_SHA1;

        case DNSSEC_DIGEST_SHA256:
                return GCRY_MD_SHA256;

        case DNSSEC_DIGEST_SHA384:
                return GCRY_MD_SHA384;

        default:
                return -EOPNOTSUPP;
        }
}

int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
        char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
        gcry_md_hd_t md = NULL;
        size_t hash_size;
        int md_algorithm, r;
        void *result;

        assert(dnskey);
        assert(ds);

        /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */

        if (dnskey->key->type != DNS_TYPE_DNSKEY)
                return -EINVAL;
        if (ds->key->type != DNS_TYPE_DS)
                return -EINVAL;
        if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
                return -EKEYREJECTED;
        if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE))
                return -EKEYREJECTED;
        if (dnskey->dnskey.protocol != 3)
                return -EKEYREJECTED;

        if (dnskey->dnskey.algorithm != ds->ds.algorithm)
                return 0;
        if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag)
                return 0;

        initialize_libgcrypt();

        md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type);
        if (md_algorithm < 0)
                return md_algorithm;

        hash_size = gcry_md_get_algo_dlen(md_algorithm);
        assert(hash_size > 0);

        if (ds->ds.digest_size != hash_size)
                return 0;

        r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
        if (r < 0)
                return r;

        gcry_md_open(&md, md_algorithm, 0);
        if (!md)
                return -EIO;

        gcry_md_write(md, owner_name, r);
        if (mask_revoke)
                md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE);
        else
                md_add_uint16(md, dnskey->dnskey.flags);
        md_add_uint8(md, dnskey->dnskey.protocol);
        md_add_uint8(md, dnskey->dnskey.algorithm);
        gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);

        result = gcry_md_read(md, 0);
        if (!result) {
                r = -EIO;
                goto finish;
        }

        r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0;

finish:
        gcry_md_close(md);
        return r;
}

int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
        DnsResourceRecord *ds;
        DnsAnswerFlags flags;
        int r;

        assert(dnskey);

        if (dnskey->key->type != DNS_TYPE_DNSKEY)
                return 0;

        DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) {

                if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
                        continue;

                if (ds->key->type != DNS_TYPE_DS)
                        continue;

                if (ds->key->class != dnskey->key->class)
                        continue;

                r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key));
                if (r < 0)
                        return r;
                if (r == 0)
                        continue;

                r = dnssec_verify_dnskey(dnskey, ds, false);
                if (r == -EKEYREJECTED)
                        return 0; /* The DNSKEY is revoked or otherwise invalid, we won't bless it */
                if (r < 0)
                        return r;
                if (r > 0)
                        return 1;
        }

        return 0;
}

static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) {

        /* Translates a DNSSEC NSEC3 hash algorithm into a gcrypt digest identifier */

        switch (algorithm) {

        case NSEC3_ALGORITHM_SHA1:
                return GCRY_MD_SHA1;

        default:
                return -EOPNOTSUPP;
        }
}

int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
        uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX];
        gcry_md_hd_t md = NULL;
        size_t hash_size;
        int algorithm;
        void *result;
        unsigned k;
        int r;

        assert(nsec3);
        assert(name);
        assert(ret);

        if (nsec3->key->type != DNS_TYPE_NSEC3)
                return -EINVAL;

        if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) {
                log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3));
                return -EOPNOTSUPP;
        }

        algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm);
        if (algorithm < 0)
                return algorithm;

        initialize_libgcrypt();

        hash_size = gcry_md_get_algo_dlen(algorithm);
        assert(hash_size > 0);

        if (nsec3->nsec3.next_hashed_name_size != hash_size)
                return -EINVAL;

        r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true);
        if (r < 0)
                return r;

        gcry_md_open(&md, algorithm, 0);
        if (!md)
                return -EIO;

        gcry_md_write(md, wire_format, r);
        gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);

        result = gcry_md_read(md, 0);
        if (!result) {
                r = -EIO;
                goto finish;
        }

        for (k = 0; k < nsec3->nsec3.iterations; k++) {
                uint8_t tmp[hash_size];
                memcpy(tmp, result, hash_size);

                gcry_md_reset(md);
                gcry_md_write(md, tmp, hash_size);
                gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);

                result = gcry_md_read(md, 0);
                if (!result) {
                        r = -EIO;
                        goto finish;
                }
        }

        memcpy(ret, result, hash_size);
        r = (int) hash_size;

finish:
        gcry_md_close(md);
        return r;
}

static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) {
        const char *a, *b;
        int r;

        assert(rr);

        if (rr->key->type != DNS_TYPE_NSEC3)
                return 0;

        /* RFC  5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
        if (!IN_SET(rr->nsec3.flags, 0, 1))
                return 0;

        /* Ignore NSEC3 RRs whose algorithm we don't know */
        if (nsec3_hash_to_gcrypt_md(rr->nsec3.algorithm) < 0)
                return 0;
        /* Ignore NSEC3 RRs with an excessive number of required iterations */
        if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX)
                return 0;

        if (!nsec3)
                return 1;

        /* If a second NSEC3 RR is specified, also check if they are from the same zone. */

        if (nsec3 == rr) /* Shortcut */
                return 1;

        if (rr->key->class != nsec3->key->class)
                return 0;
        if (rr->nsec3.algorithm != nsec3->nsec3.algorithm)
                return 0;
        if (rr->nsec3.iterations != nsec3->nsec3.iterations)
                return 0;
        if (rr->nsec3.salt_size != nsec3->nsec3.salt_size)
                return 0;
        if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0)
                return 0;

        a = DNS_RESOURCE_KEY_NAME(rr->key);
        r = dns_name_parent(&a); /* strip off hash */
        if (r < 0)
                return r;
        if (r == 0)
                return 0;

        b = DNS_RESOURCE_KEY_NAME(nsec3->key);
        r = dns_name_parent(&b); /* strip off hash */
        if (r < 0)
                return r;
        if (r == 0)
                return 0;

        return dns_name_equal(a, b);
}

static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) {
        _cleanup_free_ char *l = NULL;
        char *j;

        assert(hashed);
        assert(hashed_size > 0);
        assert(zone);
        assert(ret);

        l = base32hexmem(hashed, hashed_size, false);
        if (!l)
                return -ENOMEM;

        j = strjoin(l, ".", zone, NULL);
        if (!j)
                return -ENOMEM;

        *ret = j;
        return (int) hashed_size;
}

static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) {
        uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
        int hashed_size;

        assert(nsec3);
        assert(domain);
        assert(zone);
        assert(ret);

        hashed_size = dnssec_nsec3_hash(nsec3, domain, hashed);
        if (hashed_size < 0)
                return hashed_size;

        return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret);
}

/* See RFC 5155, Section 8
 * First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest
 * enclosure. Secondly, find a proof that there is no closer enclosure and either a proof that there
 * is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that
 * matches the wildcard domain.
 *
 * Based on this we can prove either the existence of the record in @key, or NXDOMAIN or NODATA, or
 * that there is no proof either way. The latter is the case if a the proof of non-existence of a given
 * name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records
 * to conclude anything we indicate this by returning NO_RR. */
static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
        _cleanup_free_ char *next_closer_domain = NULL, *wildcard = NULL, *wildcard_domain = NULL;
        const char *zone, *p, *pp = NULL;
        DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL;
        DnsAnswerFlags flags;
        int hashed_size, r;
        bool a, no_closer = false, no_wildcard = false, optout = false;

        assert(key);
        assert(result);

        /* First step, find the zone name and the NSEC3 parameters of the zone.
         * it is sufficient to look for the longest common suffix we find with
         * any NSEC3 RR in the response. Any NSEC3 record will do as all NSEC3
         * records from a given zone in a response must use the same
         * parameters. */
        zone = DNS_RESOURCE_KEY_NAME(key);
        for (;;) {
                DNS_ANSWER_FOREACH_FLAGS(zone_rr, flags, answer) {
                        r = nsec3_is_good(zone_rr, NULL);
                        if (r < 0)
                                return r;
                        if (r == 0)
                                continue;

                        r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(zone_rr->key), 1, zone);
                        if (r < 0)
                                return r;
                        if (r > 0)
                                goto found_zone;
                }

                /* Strip one label from the front */
                r = dns_name_parent(&zone);
                if (r < 0)
                        return r;
                if (r == 0)
                        break;
        }

        *result = DNSSEC_NSEC_NO_RR;
        return 0;

found_zone:
        /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */
        p = DNS_RESOURCE_KEY_NAME(key);
        for (;;) {
                _cleanup_free_ char *hashed_domain = NULL;

                hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain);
                if (hashed_size == -EOPNOTSUPP) {
                        *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
                        return 0;
                }
                if (hashed_size < 0)
                        return hashed_size;

                DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) {

                        r = nsec3_is_good(enclosure_rr, zone_rr);
                        if (r < 0)
                                return r;
                        if (r == 0)
                                continue;

                        if (enclosure_rr->nsec3.next_hashed_name_size != (size_t) hashed_size)
                                continue;

                        r = dns_name_equal(DNS_RESOURCE_KEY_NAME(enclosure_rr->key), hashed_domain);
                        if (r < 0)
                                return r;
                        if (r > 0) {
                                a = flags & DNS_ANSWER_AUTHENTICATED;
                                goto found_closest_encloser;
                        }
                }

                /* We didn't find the closest encloser with this name,
                 * but let's remember this domain name, it might be
                 * the next closer name */

                pp = p;

                /* Strip one label from the front */
                r = dns_name_parent(&p);
                if (r < 0)
                        return r;
                if (r == 0)
                        break;
        }

        *result = DNSSEC_NSEC_NO_RR;
        return 0;

found_closest_encloser:
        /* We found a closest encloser in 'p'; next closer is 'pp' */

        /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */
        if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME))
                return -EBADMSG;

        /* Ensure that this data is from the delegated domain
         * (i.e. originates from the "lower" DNS server), and isn't
         * just glue records (i.e. doesn't originate from the "upper"
         * DNS server). */
        if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) &&
            !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA))
                return -EBADMSG;

        if (!pp) {
                /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */
                if (bitmap_isset(enclosure_rr->nsec3.types, key->type))
                        *result = DNSSEC_NSEC_FOUND;
                else if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_CNAME))
                        *result = DNSSEC_NSEC_CNAME;
                else
                        *result = DNSSEC_NSEC_NODATA;

                if (authenticated)
                        *authenticated = a;
                if (ttl)
                        *ttl = enclosure_rr->ttl;

                return 0;
        }

        /* Prove that there is no next closer and whether or not there is a wildcard domain. */

        wildcard = strappend("*.", p);
        if (!wildcard)
                return -ENOMEM;

        r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain);
        if (r < 0)
                return r;
        if (r != hashed_size)
                return -EBADMSG;

        r = nsec3_hashed_domain_make(enclosure_rr, pp, zone, &next_closer_domain);
        if (r < 0)
                return r;
        if (r != hashed_size)
                return -EBADMSG;

        DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
                _cleanup_free_ char *next_hashed_domain = NULL;

                r = nsec3_is_good(rr, zone_rr);
                if (r < 0)
                        return r;
                if (r == 0)
                        continue;

                r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain);
                if (r < 0)
                        return r;

                r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain);
                if (r < 0)
                        return r;
                if (r > 0) {
                        if (rr->nsec3.flags & 1)
                                optout = true;

                        a = a && (flags & DNS_ANSWER_AUTHENTICATED);

                        no_closer = true;
                }

                r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain);
                if (r < 0)
                        return r;
                if (r > 0) {
                        a = a && (flags & DNS_ANSWER_AUTHENTICATED);

                        wildcard_rr = rr;
                }

                r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain, next_hashed_domain);
                if (r < 0)
                        return r;
                if (r > 0) {
                        if (rr->nsec3.flags & 1)
                                /* This only makes sense if we have a wildcard delegation, which is
                                 * very unlikely, see RFC 4592, Section 4.2, but we cannot rely on
                                 * this not happening, so hence cannot simply conclude NXDOMAIN as
                                 * we would wish */
                                optout = true;

                        a = a && (flags & DNS_ANSWER_AUTHENTICATED);

                        no_wildcard = true;
                }
        }

        if (wildcard_rr && no_wildcard)
                return -EBADMSG;

        if (!no_closer) {
                *result = DNSSEC_NSEC_NO_RR;
                return 0;
        }

        if (wildcard_rr) {
                /* A wildcard exists that matches our query. */
                if (optout)
                        /* This is not specified in any RFC to the best of my knowledge, but
                         * if the next closer enclosure is covered by an opt-out NSEC3 RR
                         * it means that we cannot prove that the source of synthesis is
                         * correct, as there may be a closer match. */
                        *result = DNSSEC_NSEC_OPTOUT;
                else if (bitmap_isset(wildcard_rr->nsec3.types, key->type))
                        *result = DNSSEC_NSEC_FOUND;
                else if (bitmap_isset(wildcard_rr->nsec3.types, DNS_TYPE_CNAME))
                        *result = DNSSEC_NSEC_CNAME;
                else
                        *result = DNSSEC_NSEC_NODATA;
        } else {
                if (optout)
                        /* The RFC only specifies that we have to care for optout for NODATA for
                         * DS records. However, children of an insecure opt-out delegation should
                         * also be considered opt-out, rather than verified NXDOMAIN.
                         * Note that we do not require a proof of wildcard non-existence if the
                         * next closer domain is covered by an opt-out, as that would not provide
                         * any additional information. */
                        *result = DNSSEC_NSEC_OPTOUT;
                else if (no_wildcard)
                        *result = DNSSEC_NSEC_NXDOMAIN;
                else {
                        *result = DNSSEC_NSEC_NO_RR;

                        return 0;
                }
        }

        if (authenticated)
                *authenticated = a;

        if (ttl)
                *ttl = enclosure_rr->ttl;

        return 0;
}

int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
        DnsResourceRecord *rr;
        bool have_nsec3 = false;
        DnsAnswerFlags flags;
        int r;

        assert(key);
        assert(result);

        /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */

        DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {

                if (rr->key->class != key->class)
                        continue;

                switch (rr->key->type) {

                case DNS_TYPE_NSEC:

                        r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key));
                        if (r < 0)
                                return r;
                        if (r > 0) {
                                if (bitmap_isset(rr->nsec.types, key->type))
                                        *result = DNSSEC_NSEC_FOUND;
                                else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME))
                                        *result = DNSSEC_NSEC_CNAME;
                                else
                                        *result = DNSSEC_NSEC_NODATA;

                                if (authenticated)
                                        *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
                                if (ttl)
                                        *ttl = rr->ttl;

                                return 0;
                        }

                        r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name);
                        if (r < 0)
                                return r;
                        if (r > 0) {
                                *result = DNSSEC_NSEC_NXDOMAIN;

                                if (authenticated)
                                        *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
                                if (ttl)
                                        *ttl = rr->ttl;

                                return 0;
                        }
                        break;

                case DNS_TYPE_NSEC3:
                        have_nsec3 = true;
                        break;
                }
        }

        /* OK, this was not sufficient. Let's see if NSEC3 can help. */
        if (have_nsec3)
                return dnssec_test_nsec3(answer, key, result, authenticated, ttl);

        /* No approproate NSEC RR found, report this. */
        *result = DNSSEC_NSEC_NO_RR;
        return 0;
}

int dnssec_nsec_test_between(DnsAnswer *answer, const char *name, const char *zone, bool *authenticated) {
        DnsResourceRecord *rr;
        DnsAnswerFlags flags;
        int r;

        assert(name);
        assert(zone);

        /* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified
         * 'zone'. The 'zone' must be a suffix of the 'name'. */

        DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
                bool found = false;

                r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), zone);
                if (r < 0)
                        return r;
                if (r == 0)
                        continue;

                switch (rr->key->type) {

                case DNS_TYPE_NSEC:
                        r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name);
                        if (r < 0)
                                return r;

                        found = r > 0;
                        break;

                case DNS_TYPE_NSEC3: {
                        _cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL;

                        r = nsec3_is_good(rr, NULL);
                        if (r < 0)
                                return r;
                        if (r == 0)
                                break;

                        /* Format the domain we are testing with the NSEC3 RR's hash function */
                        r = nsec3_hashed_domain_make(
                                        rr,
                                        name,
                                        zone,
                                        &hashed_domain);
                        if (r < 0)
                                return r;
                        if ((size_t) r != rr->nsec3.next_hashed_name_size)
                                break;

                        /* Format the NSEC3's next hashed name as proper domain name */
                        r = nsec3_hashed_domain_format(
                                        rr->nsec3.next_hashed_name,
                                        rr->nsec3.next_hashed_name_size,
                                        zone,
                                        &next_hashed_domain);
                        if (r < 0)
                                return r;

                        r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain, next_hashed_domain);
                        if (r < 0)
                                return r;

                        found = r > 0;
                        break;
                }

                default:
                        continue;
                }

                if (found) {
                        if (authenticated)
                                *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
                        return 1;
                }
        }

        return 0;
}

static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
        [DNSSEC_VALIDATED] = "validated",
        [DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard",
        [DNSSEC_INVALID] = "invalid",
        [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired",
        [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm",
        [DNSSEC_NO_SIGNATURE] = "no-signature",
        [DNSSEC_MISSING_KEY] = "missing-key",
        [DNSSEC_UNSIGNED] = "unsigned",
        [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
        [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
        [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);