resolved-dns-dnssec.c revision 13b78323bad1e41e0474b833da2a0b72aab56f09
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering/***
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering This file is part of systemd.
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering Copyright 2015 Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering systemd is free software; you can redistribute it and/or modify it
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering under the terms of the GNU Lesser General Public License as published by
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering (at your option) any later version.
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering systemd is distributed in the hope that it will be useful, but
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering Lesser General Public License for more details.
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering You should have received a copy of the GNU Lesser General Public License
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering***/
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering#include <gcrypt.h>
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering#include "alloc-util.h"
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering#include "dns-domain.h"
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering#include "hexdecoct.h"
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering#include "resolved-dns-dnssec.h"
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering#include "resolved-dns-packet.h"
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering#include "string-table.h"
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering/* Open question:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering *
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * How does the DNSSEC canonical form of a hostname with a label
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * containing a dot look like, the way DNS-SD does it?
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering *
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * TODO:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering *
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - Make trust anchor store read additional DS+DNSKEY data from disk
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - wildcard zones compatibility
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - multi-label zone compatibility
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - cname/dname compatibility
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - per-interface DNSSEC setting
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - fix TTL for cache entries to match RRSIG TTL
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - retry on failed validation?
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - DSA support
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - EC support?
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering *
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering#define VERIFY_RRS_MAX 256
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering#define MAX_KEY_SIZE (32*1024)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering/*
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * The DNSSEC Chain of trust:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering *
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * DS RRs are protected like normal RRs
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering *
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * Example chain:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic void initialize_libgcrypt(void) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const char *p;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering p = gcry_check_version("1.4.5");
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(p);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_control(GCRYCTL_DISABLE_SECMEM);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering}
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic bool dnssec_algorithm_supported(int algorithm) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return IN_SET(algorithm,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DNSSEC_ALGORITHM_RSASHA1,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DNSSEC_ALGORITHM_RSASHA256,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DNSSEC_ALGORITHM_RSASHA512);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering}
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringuint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const uint8_t *p;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering uint32_t sum;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering size_t i;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* The algorithm from RFC 4034, Appendix B. */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(dnskey);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering sum = (uint32_t) dnskey->dnskey.flags +
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering p = dnskey->dnskey.key;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering for (i = 0; i < dnskey->dnskey.key_size; i++)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering sum += (sum >> 16) & UINT32_C(0xFFFF);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return sum & UINT32_C(0xFFFF);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering}
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poetteringstatic int rr_compare(const void *a, const void *b) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering size_t m;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering int r;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Let's order the RRs according to RFC 4034, Section 6.3 */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(x);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(*x);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert((*x)->wire_format);
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering assert(y);
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering assert(*y);
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering assert((*y)->wire_format);
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = memcmp((*x)->wire_format, (*y)->wire_format, m);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r != 0)
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering return r;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if ((*x)->wire_format_size < (*y)->wire_format_size)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return -1;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering else if ((*x)->wire_format_size > (*y)->wire_format_size)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return 1;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return 0;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering}
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic int dnssec_rsa_verify(
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const char *hash_algorithm,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const void *signature, size_t signature_size,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const void *data, size_t data_size,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const void *exponent, size_t exponent_size,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const void *modulus, size_t modulus_size) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_mpi_t n = NULL, e = NULL, s = NULL;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_error_t ge;
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering int r;
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering assert(hash_algorithm);
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering if (ge != 0) {
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering r = -EIO;
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (ge != 0) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = -EIO;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (ge != 0) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = -EIO;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ge = gcry_sexp_build(&signature_sexp,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering NULL,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering "(sig-val (rsa (s %m)))",
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering s);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (ge != 0) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = -EIO;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ge = gcry_sexp_build(&data_sexp,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering NULL,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering "(data (flags pkcs1) (hash %s %b))",
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering hash_algorithm,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering (int) data_size,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering data);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (ge != 0) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = -EIO;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ge = gcry_sexp_build(&public_key_sexp,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering NULL,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering "(public-key (rsa (n %m) (e %m)))",
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering n,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering e);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (ge != 0) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = -EIO;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = 0;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering else if (ge != 0) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering log_debug("RSA signature check failed: %s", gpg_strerror(ge));
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = -EIO;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering } else
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = 1;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringfinish:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (e)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_mpi_release(e);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (n)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_mpi_release(n);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (s)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_mpi_release(s);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (public_key_sexp)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_sexp_release(public_key_sexp);
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering if (signature_sexp)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_sexp_release(signature_sexp);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (data_sexp)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_sexp_release(data_sexp);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return r;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering}
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_write(md, &v, sizeof(v));
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering}
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering v = htobe16(v);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_write(md, &v, sizeof(v));
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering}
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering v = htobe32(v);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_write(md, &v, sizeof(v));
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering}
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering usec_t expiration, inception, skew;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(rrsig);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (realtime == USEC_INFINITY)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering realtime = now(CLOCK_REALTIME);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering inception = rrsig->rrsig.inception * USEC_PER_SEC;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (inception > expiration)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return -EKEYREJECTED;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Permit a certain amount of clock skew of 10% of the valid
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * time range. This takes inspiration from unbound's
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * resolver. */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering skew = (expiration - inception) / 10;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (skew > SKEW_MAX)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering skew = SKEW_MAX;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (inception < skew)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering inception = 0;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering else
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering inception -= skew;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (expiration + skew < expiration)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering expiration = USEC_INFINITY;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering else
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering expiration += skew;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return realtime < inception || realtime > expiration;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering}
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringint dnssec_verify_rrset(
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DnsAnswer *a,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DnsResourceKey *key,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DnsResourceRecord *rrsig,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DnsResourceRecord *dnskey,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering usec_t realtime,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DnssecResult *result) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering size_t exponent_size, modulus_size, hash_size;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering void *exponent, *modulus, *hash;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DnsResourceRecord **list, *rr;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_hd_t md = NULL;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering size_t k, n = 0;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering int r;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(key);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(rrsig);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(dnskey);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(result);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Verifies the the RRSet matching the specified "key" in "a",
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * using the signature "rrsig" and the key "dnskey". It's
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * assumed the RRSIG and DNSKEY match. */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm)) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering *result = DNSSEC_UNSUPPORTED_ALGORITHM;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return 0;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (a->n_rrs > VERIFY_RRS_MAX)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return -E2BIG;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dnssec_rrsig_expired(rrsig, realtime);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r < 0)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return r;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r > 0) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering *result = DNSSEC_SIGNATURE_EXPIRED;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return 0;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Collect all relevant RRs in a single array, so that we can look at the RRset */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering list = newa(DnsResourceRecord *, a->n_rrs);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DNS_ANSWER_FOREACH(rr, a) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_resource_key_equal(key, rr->key);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r < 0)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return r;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r == 0)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering continue;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* We need the wire format for ordering, and digest calculation */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_resource_record_to_wire_format(rr, true);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r < 0)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return r;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering list[n++] = rr;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (n <= 0)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return -ENODATA;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Bring the RRs into canonical order */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering initialize_libgcrypt();
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* OK, the RRs are now in canonical order. Let's calculate the digest */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering switch (rrsig->rrsig.algorithm) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering case DNSSEC_ALGORITHM_RSASHA1:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_open(&md, GCRY_MD_SHA1, 0);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering hash_size = 20;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering break;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering case DNSSEC_ALGORITHM_RSASHA256:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_open(&md, GCRY_MD_SHA256, 0);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering hash_size = 32;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering break;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering case DNSSEC_ALGORITHM_RSASHA512:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_open(&md, GCRY_MD_SHA512, 0);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering hash_size = 64;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering break;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering default:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert_not_reached("Unknown digest");
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (!md)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return -EIO;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint16(md, rrsig->rrsig.type_covered);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint8(md, rrsig->rrsig.algorithm);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint8(md, rrsig->rrsig.labels);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint32(md, rrsig->rrsig.original_ttl);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint32(md, rrsig->rrsig.expiration);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint32(md, rrsig->rrsig.inception);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint16(md, rrsig->rrsig.key_tag);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r < 0)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_write(md, wire_format_name, r);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering for (k = 0; k < n; k++) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const char *suffix;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering size_t l;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering rr = list[k];
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r < 0)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r > 0) /* This is a wildcard! */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r < 0)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_write(md, wire_format_name, r);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint16(md, rr->key->type);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint16(md, rr->key->class);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint32(md, rrsig->rrsig.original_ttl);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering l = rr->wire_format_size - rr->wire_format_rdata_offset;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(l <= 0xFFFF);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint16(md, (uint16_t) l);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering hash = gcry_md_read(md, 0);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (!hash) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = -EIO;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (*(uint8_t*) dnskey->dnskey.key == 0) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* exponent is > 255 bytes long */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 3;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering exponent_size =
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (exponent_size < 256) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = -EINVAL;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (3 + exponent_size >= dnskey->dnskey.key_size) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = -EINVAL;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering } else {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* exponent is <= 255 bytes long */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 1;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (exponent_size <= 0) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = -EINVAL;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering }
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (1 + exponent_size >= dnskey->dnskey.key_size) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = -EINVAL;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering goto finish;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering }
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering }
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering r = dnssec_rsa_verify(
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering gcry_md_algo_name(gcry_md_get_algo(md)),
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering rrsig->rrsig.signature, rrsig->rrsig.signature_size,
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering hash, hash_size,
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering exponent, exponent_size,
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering modulus, modulus_size);
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering if (r < 0)
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering goto finish;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering *result = r ? DNSSEC_VALIDATED : DNSSEC_INVALID;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering r = 0;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poetteringfinish:
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering gcry_md_close(md);
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering return r;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering}
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poetteringint dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering assert(rrsig);
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering assert(dnskey);
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering /* Checks if the specified DNSKEY RR matches the key used for
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering * the signature in the specified RRSIG RR */
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering if (rrsig->key->type != DNS_TYPE_RRSIG)
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering return -EINVAL;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering if (dnskey->key->type != DNS_TYPE_DNSKEY)
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering return 0;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering if (dnskey->key->class != rrsig->key->class)
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering return 0;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering return 0;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering if (dnskey->dnskey.protocol != 3)
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering return 0;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering return 0;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return 0;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering}
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringint dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering int r;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(key);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(rrsig);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (rrsig->key->type != DNS_TYPE_RRSIG)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return 0;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (rrsig->key->class != key->class)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return 0;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (rrsig->rrsig.type_covered != key->type)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return 0;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Make sure signer is a parent of the RRset */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r <= 0)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return r;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Make sure the owner name has at least as many labels as the "label" fields indicates. */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key));
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r < 0)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return r;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r < rrsig->rrsig.labels)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return 0;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering}
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringint dnssec_verify_rrset_search(
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DnsAnswer *a,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DnsResourceKey *key,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DnsAnswer *validated_dnskeys,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering usec_t realtime,
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering DnssecResult *result) {
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering DnsResourceRecord *rrsig;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering int r;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering 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);
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:
/* Yay, the RR has been validated,
* return immediately. */
*result = DNSSEC_VALIDATED;
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;
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 (;;) {
size_t i;
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;
for (i = 0; i < (size_t) r; i ++) {
if (buffer[i] >= 'A' && buffer[i] <= 'Z')
buffer[i] = buffer[i] - 'A' + 'a';
}
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(uint8_t algorithm) {
/* Translates a DNSSEC digest algorithm into a gcrypt digest iedntifier */
switch (algorithm) {
case DNSSEC_DIGEST_SHA1:
return GCRY_MD_SHA1;
case DNSSEC_DIGEST_SHA256:
return GCRY_MD_SHA256;
default:
return -EOPNOTSUPP;
}
}
int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
gcry_md_hd_t md = NULL;
size_t hash_size;
int algorithm;
void *result;
int r;
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 (dnskey->dnskey.protocol != 3)
return -EKEYREJECTED;
if (dnskey->dnskey.algorithm != ds->ds.algorithm)
return 0;
if (dnssec_keytag(dnskey) != ds->ds.key_tag)
return 0;
initialize_libgcrypt();
algorithm = digest_to_gcrypt(ds->ds.digest_type);
if (algorithm < 0)
return algorithm;
hash_size = gcry_md_get_algo_dlen(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, algorithm, 0);
if (!md)
return -EIO;
gcry_md_write(md, owner_name, r);
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;
r = dnssec_verify_dnskey(dnskey, ds);
if (r < 0)
return r;
if (r > 0)
return 1;
}
return 0;
}
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;
algorithm = digest_to_gcrypt(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 dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) {
_cleanup_free_ char *next_closer_domain = NULL, *l = NULL;
uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
const char *suffix, *p, *pp = NULL;
DnsResourceRecord *rr;
DnsAnswerFlags flags;
int hashed_size, r;
assert(key);
assert(result);
/* First step, look for the longest common suffix we find with any NSEC3 RR in the response. */
suffix = DNS_RESOURCE_KEY_NAME(key);
for (;;) {
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
_cleanup_free_ char *hashed_domain = NULL, *label = NULL;
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
continue;
if (rr->key->type != DNS_TYPE_NSEC3)
continue;
/* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
if (!IN_SET(rr->nsec3.flags, 0, 1))
continue;
r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), suffix);
if (r < 0)
return r;
if (r > 0)
goto found_suffix;
}
/* Strip one label from the front */
r = dns_name_parent(&suffix);
if (r < 0)
return r;
if (r == 0)
break;
}
*result = DNSSEC_NSEC_NO_RR;
return 0;
found_suffix:
/* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */
p = DNS_RESOURCE_KEY_NAME(key);
for (;;) {
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
_cleanup_free_ char *hashed_domain = NULL, *label = NULL;
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
continue;
if (rr->key->type != DNS_TYPE_NSEC3)
continue;
/* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
if (!IN_SET(rr->nsec3.flags, 0, 1))
continue;
r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), suffix);
if (r < 0)
return r;
if (r == 0)
continue;
hashed_size = dnssec_nsec3_hash(rr, p, hashed);
if (hashed_size == -EOPNOTSUPP) {
*result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
return 0;
}
if (hashed_size < 0)
return hashed_size;
if (rr->nsec3.next_hashed_name_size != (size_t) hashed_size)
return -EBADMSG;
label = base32hexmem(hashed, hashed_size, false);
if (!label)
return -ENOMEM;
hashed_domain = strjoin(label, ".", suffix, NULL);
if (!hashed_domain)
return -ENOMEM;
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain);
if (r < 0)
return r;
if (r > 0)
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(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(rr->nsec3.types, DNS_TYPE_NS) &&
!bitmap_isset(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. */
*result = bitmap_isset(rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
return 0;
}
r = dnssec_nsec3_hash(rr, pp, hashed);
if (r < 0)
return r;
if (r != hashed_size)
return -EBADMSG;
l = base32hexmem(hashed, hashed_size, false);
if (!l)
return -ENOMEM;
next_closer_domain = strjoin(l, ".", p, NULL);
if (!next_closer_domain)
return -ENOMEM;
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
_cleanup_free_ char *label = NULL, *next_hashed_domain = NULL;
const char *nsec3_parent;
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
continue;
if (rr->key->type != DNS_TYPE_NSEC3)
continue;
/* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
if (!IN_SET(rr->nsec3.flags, 0, 1))
continue;
nsec3_parent = DNS_RESOURCE_KEY_NAME(rr->key);
r = dns_name_parent(&nsec3_parent);
if (r < 0)
return r;
if (r == 0)
continue;
r = dns_name_equal(p, nsec3_parent);
if (r < 0)
return r;
if (r == 0)
continue;
label = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false);
if (!label)
return -ENOMEM;
next_hashed_domain = strjoin(label, ".", p, NULL);
if (!next_hashed_domain)
return -ENOMEM;
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)
*result = DNSSEC_NSEC_OPTOUT;
else
*result = DNSSEC_NSEC_NXDOMAIN;
return 1;
}
}
*result = DNSSEC_NSEC_NO_RR;
return 0;
}
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) {
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;
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
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) {
*result = bitmap_isset(rr->nsec.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
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;
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);
/* No approproate NSEC RR found, report this. */
*result = DNSSEC_NSEC_NO_RR;
return 0;
}
static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
[DNSSEC_NO] = "no",
[DNSSEC_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_VALIDATED] = "validated",
[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",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);