resolved-dns-dnssec.c revision 6f8a2c6817e35ca3e76130b31624f7f30e596433
199767f8919635c4928607450d9e0abb932109ceToomas Soome/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome/***
199767f8919635c4928607450d9e0abb932109ceToomas Soome This file is part of systemd.
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome Copyright 2015 Lennart Poettering
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome systemd is free software; you can redistribute it and/or modify it
199767f8919635c4928607450d9e0abb932109ceToomas Soome under the terms of the GNU Lesser General Public License as published by
199767f8919635c4928607450d9e0abb932109ceToomas Soome the Free Software Foundation; either version 2.1 of the License, or
199767f8919635c4928607450d9e0abb932109ceToomas Soome (at your option) any later version.
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome systemd is distributed in the hope that it will be useful, but
199767f8919635c4928607450d9e0abb932109ceToomas Soome WITHOUT ANY WARRANTY; without even the implied warranty of
199767f8919635c4928607450d9e0abb932109ceToomas Soome MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
199767f8919635c4928607450d9e0abb932109ceToomas Soome Lesser General Public License for more details.
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome You should have received a copy of the GNU Lesser General Public License
199767f8919635c4928607450d9e0abb932109ceToomas Soome along with systemd; If not, see <http://www.gnu.org/licenses/>.
199767f8919635c4928607450d9e0abb932109ceToomas Soome***/
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include <gcrypt.h>
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include "alloc-util.h"
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include "dns-domain.h"
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include "hexdecoct.h"
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include "resolved-dns-dnssec.h"
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include "resolved-dns-packet.h"
199767f8919635c4928607450d9e0abb932109ceToomas Soome#include "string-table.h"
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome/* Open question:
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * How does the DNSSEC canonical form of a hostname with a label
199767f8919635c4928607450d9e0abb932109ceToomas Soome * containing a dot look like, the way DNS-SD does it?
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * TODO:
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
199767f8919635c4928607450d9e0abb932109ceToomas Soome * - multi-label zone compatibility
199767f8919635c4928607450d9e0abb932109ceToomas Soome * - cname/dname compatibility
199767f8919635c4928607450d9e0abb932109ceToomas Soome * - nxdomain on qname
199767f8919635c4928607450d9e0abb932109ceToomas Soome * - per-interface DNSSEC setting
199767f8919635c4928607450d9e0abb932109ceToomas Soome * - when doing negative caching, use NSEC/NSEC3 RR instead of SOA for TTL
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome#define VERIFY_RRS_MAX 256
199767f8919635c4928607450d9e0abb932109ceToomas Soome#define MAX_KEY_SIZE (32*1024)
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
199767f8919635c4928607450d9e0abb932109ceToomas Soome#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome/* Maximum number of NSEC3 iterations we'll do. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome#define NSEC3_ITERATIONS_MAX 2048
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome/*
199767f8919635c4928607450d9e0abb932109ceToomas Soome * The DNSSEC Chain of trust:
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
199767f8919635c4928607450d9e0abb932109ceToomas Soome * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
199767f8919635c4928607450d9e0abb932109ceToomas Soome * DS RRs are protected like normal RRs
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * Example chain:
199767f8919635c4928607450d9e0abb932109ceToomas Soome * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
199767f8919635c4928607450d9e0abb932109ceToomas Soome */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic void initialize_libgcrypt(void) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome const char *p;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
199767f8919635c4928607450d9e0abb932109ceToomas Soome return;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome p = gcry_check_version("1.4.5");
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(p);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_control(GCRYCTL_DISABLE_SECMEM);
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomeuint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome const uint8_t *p;
199767f8919635c4928607450d9e0abb932109ceToomas Soome uint32_t sum, f;
199767f8919635c4928607450d9e0abb932109ceToomas Soome size_t i;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* The algorithm from RFC 4034, Appendix B. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(dnskey);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(dnskey->key->type == DNS_TYPE_DNSKEY);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome f = (uint32_t) dnskey->dnskey.flags;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (mask_revoke)
199767f8919635c4928607450d9e0abb932109ceToomas Soome f &= ~DNSKEY_FLAG_REVOKE;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome p = dnskey->dnskey.key;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome for (i = 0; i < dnskey->dnskey.key_size; i++)
199767f8919635c4928607450d9e0abb932109ceToomas Soome sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome sum += (sum >> 16) & UINT32_C(0xFFFF);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return sum & UINT32_C(0xFFFF);
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic int rr_compare(const void *a, const void *b) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
199767f8919635c4928607450d9e0abb932109ceToomas Soome size_t m;
199767f8919635c4928607450d9e0abb932109ceToomas Soome int r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Let's order the RRs according to RFC 4034, Section 6.3 */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(x);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(*x);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert((*x)->wire_format);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(y);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(*y);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert((*y)->wire_format);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y));
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r != 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -1;
199767f8919635c4928607450d9e0abb932109ceToomas Soome else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 1;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic int dnssec_rsa_verify_raw(
199767f8919635c4928607450d9e0abb932109ceToomas Soome const char *hash_algorithm,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const void *signature, size_t signature_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const void *data, size_t data_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const void *exponent, size_t exponent_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const void *modulus, size_t modulus_size) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_mpi_t n = NULL, e = NULL, s = NULL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_error_t ge;
199767f8919635c4928607450d9e0abb932109ceToomas Soome int r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(hash_algorithm);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_sexp_build(&signature_sexp,
199767f8919635c4928607450d9e0abb932109ceToomas Soome NULL,
199767f8919635c4928607450d9e0abb932109ceToomas Soome "(sig-val (rsa (s %m)))",
199767f8919635c4928607450d9e0abb932109ceToomas Soome s);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_sexp_build(&data_sexp,
199767f8919635c4928607450d9e0abb932109ceToomas Soome NULL,
199767f8919635c4928607450d9e0abb932109ceToomas Soome "(data (flags pkcs1) (hash %s %b))",
199767f8919635c4928607450d9e0abb932109ceToomas Soome hash_algorithm,
199767f8919635c4928607450d9e0abb932109ceToomas Soome (int) data_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome data);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_sexp_build(&public_key_sexp,
199767f8919635c4928607450d9e0abb932109ceToomas Soome NULL,
199767f8919635c4928607450d9e0abb932109ceToomas Soome "(public-key (rsa (n %m) (e %m)))",
199767f8919635c4928607450d9e0abb932109ceToomas Soome n,
199767f8919635c4928607450d9e0abb932109ceToomas Soome e);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome else if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome log_debug("RSA signature check failed: %s", gpg_strerror(ge));
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome } else
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = 1;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomefinish:
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (e)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_mpi_release(e);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (n)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_mpi_release(n);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (s)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_mpi_release(s);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (public_key_sexp)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_sexp_release(public_key_sexp);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (signature_sexp)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_sexp_release(signature_sexp);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (data_sexp)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_sexp_release(data_sexp);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic int dnssec_rsa_verify(
199767f8919635c4928607450d9e0abb932109ceToomas Soome const char *hash_algorithm,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const void *hash, size_t hash_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord *rrsig,
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord *dnskey) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome size_t exponent_size, modulus_size;
199767f8919635c4928607450d9e0abb932109ceToomas Soome void *exponent, *modulus;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(hash_algorithm);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(hash);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(hash_size > 0);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(rrsig);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(dnskey);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (*(uint8_t*) dnskey->dnskey.key == 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* exponent is > 255 bytes long */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome exponent = (uint8_t*) dnskey->dnskey.key + 3;
199767f8919635c4928607450d9e0abb932109ceToomas Soome exponent_size =
199767f8919635c4928607450d9e0abb932109ceToomas Soome ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) |
199767f8919635c4928607450d9e0abb932109ceToomas Soome ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (exponent_size < 256)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EINVAL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (3 + exponent_size >= dnskey->dnskey.key_size)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EINVAL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
199767f8919635c4928607450d9e0abb932109ceToomas Soome modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome } else {
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* exponent is <= 255 bytes long */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome exponent = (uint8_t*) dnskey->dnskey.key + 1;
199767f8919635c4928607450d9e0abb932109ceToomas Soome exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (exponent_size <= 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EINVAL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (1 + exponent_size >= dnskey->dnskey.key_size)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EINVAL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
199767f8919635c4928607450d9e0abb932109ceToomas Soome modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return dnssec_rsa_verify_raw(
199767f8919635c4928607450d9e0abb932109ceToomas Soome hash_algorithm,
199767f8919635c4928607450d9e0abb932109ceToomas Soome rrsig->rrsig.signature, rrsig->rrsig.signature_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome hash, hash_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome exponent, exponent_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome modulus, modulus_size);
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic int dnssec_ecdsa_verify_raw(
199767f8919635c4928607450d9e0abb932109ceToomas Soome const char *hash_algorithm,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const char *curve,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const void *signature_r, size_t signature_r_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const void *signature_s, size_t signature_s_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const void *data, size_t data_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const void *key, size_t key_size) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_mpi_t q = NULL, r = NULL, s = NULL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_error_t ge;
199767f8919635c4928607450d9e0abb932109ceToomas Soome int k;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(hash_algorithm);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome k = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome k = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome k = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_sexp_build(&signature_sexp,
199767f8919635c4928607450d9e0abb932109ceToomas Soome NULL,
199767f8919635c4928607450d9e0abb932109ceToomas Soome "(sig-val (ecdsa (r %m) (s %m)))",
199767f8919635c4928607450d9e0abb932109ceToomas Soome r,
199767f8919635c4928607450d9e0abb932109ceToomas Soome s);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome k = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_sexp_build(&data_sexp,
199767f8919635c4928607450d9e0abb932109ceToomas Soome NULL,
199767f8919635c4928607450d9e0abb932109ceToomas Soome "(data (flags rfc6979) (hash %s %b))",
199767f8919635c4928607450d9e0abb932109ceToomas Soome hash_algorithm,
199767f8919635c4928607450d9e0abb932109ceToomas Soome (int) data_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome data);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome k = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_sexp_build(&public_key_sexp,
199767f8919635c4928607450d9e0abb932109ceToomas Soome NULL,
199767f8919635c4928607450d9e0abb932109ceToomas Soome "(public-key (ecc (curve %s) (q %m)))",
199767f8919635c4928607450d9e0abb932109ceToomas Soome curve,
199767f8919635c4928607450d9e0abb932109ceToomas Soome q);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome k = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
199767f8919635c4928607450d9e0abb932109ceToomas Soome k = 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome else if (ge != 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome log_debug("ECDSA signature check failed: %s", gpg_strerror(ge));
199767f8919635c4928607450d9e0abb932109ceToomas Soome k = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome } else
199767f8919635c4928607450d9e0abb932109ceToomas Soome k = 1;
199767f8919635c4928607450d9e0abb932109ceToomas Soomefinish:
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_mpi_release(r);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (s)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_mpi_release(s);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (q)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_mpi_release(q);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (public_key_sexp)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_sexp_release(public_key_sexp);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (signature_sexp)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_sexp_release(signature_sexp);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (data_sexp)
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_sexp_release(data_sexp);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return k;
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic int dnssec_ecdsa_verify(
199767f8919635c4928607450d9e0abb932109ceToomas Soome const char *hash_algorithm,
199767f8919635c4928607450d9e0abb932109ceToomas Soome int algorithm,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const void *hash, size_t hash_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord *rrsig,
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord *dnskey) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome const char *curve;
199767f8919635c4928607450d9e0abb932109ceToomas Soome size_t key_size;
199767f8919635c4928607450d9e0abb932109ceToomas Soome uint8_t *q;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(hash);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(hash_size);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(rrsig);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(dnskey);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome key_size = 32;
199767f8919635c4928607450d9e0abb932109ceToomas Soome curve = "NIST P-256";
199767f8919635c4928607450d9e0abb932109ceToomas Soome } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome key_size = 48;
199767f8919635c4928607450d9e0abb932109ceToomas Soome curve = "NIST P-384";
199767f8919635c4928607450d9e0abb932109ceToomas Soome } else
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EOPNOTSUPP;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (dnskey->dnskey.key_size != key_size * 2)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EINVAL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (rrsig->rrsig.signature_size != key_size * 2)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EINVAL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome q = alloca(key_size*2 + 1);
199767f8919635c4928607450d9e0abb932109ceToomas Soome q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */
199767f8919635c4928607450d9e0abb932109ceToomas Soome memcpy(q+1, dnskey->dnskey.key, key_size*2);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return dnssec_ecdsa_verify_raw(
199767f8919635c4928607450d9e0abb932109ceToomas Soome hash_algorithm,
199767f8919635c4928607450d9e0abb932109ceToomas Soome curve,
199767f8919635c4928607450d9e0abb932109ceToomas Soome rrsig->rrsig.signature, key_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome (uint8_t*) rrsig->rrsig.signature + key_size, key_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome hash, hash_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome q, key_size*2+1);
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_write(md, &v, sizeof(v));
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome v = htobe16(v);
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_write(md, &v, sizeof(v));
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome v = htobe32(v);
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_write(md, &v, sizeof(v));
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome usec_t expiration, inception, skew;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(rrsig);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(rrsig->key->type == DNS_TYPE_RRSIG);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (realtime == USEC_INFINITY)
199767f8919635c4928607450d9e0abb932109ceToomas Soome realtime = now(CLOCK_REALTIME);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
199767f8919635c4928607450d9e0abb932109ceToomas Soome inception = rrsig->rrsig.inception * USEC_PER_SEC;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (inception > expiration)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EKEYREJECTED;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Permit a certain amount of clock skew of 10% of the valid
199767f8919635c4928607450d9e0abb932109ceToomas Soome * time range. This takes inspiration from unbound's
199767f8919635c4928607450d9e0abb932109ceToomas Soome * resolver. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome skew = (expiration - inception) / 10;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (skew > SKEW_MAX)
199767f8919635c4928607450d9e0abb932109ceToomas Soome skew = SKEW_MAX;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (inception < skew)
199767f8919635c4928607450d9e0abb932109ceToomas Soome inception = 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome else
199767f8919635c4928607450d9e0abb932109ceToomas Soome inception -= skew;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (expiration + skew < expiration)
199767f8919635c4928607450d9e0abb932109ceToomas Soome expiration = USEC_INFINITY;
199767f8919635c4928607450d9e0abb932109ceToomas Soome else
199767f8919635c4928607450d9e0abb932109ceToomas Soome expiration += skew;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return realtime < inception || realtime > expiration;
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic int algorithm_to_gcrypt_md(uint8_t algorithm) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Translates a DNSSEC signature algorithm into a gcrypt
199767f8919635c4928607450d9e0abb932109ceToomas Soome * digest identifier.
199767f8919635c4928607450d9e0abb932109ceToomas Soome *
199767f8919635c4928607450d9e0abb932109ceToomas Soome * Note that we implement all algorithms listed as "Must
199767f8919635c4928607450d9e0abb932109ceToomas Soome * implement" and "Recommended to Implement" in RFC6944. We
199767f8919635c4928607450d9e0abb932109ceToomas Soome * don't implement any algorithms that are listed as
199767f8919635c4928607450d9e0abb932109ceToomas Soome * "Optional" or "Must Not Implement". Specifically, we do not
199767f8919635c4928607450d9e0abb932109ceToomas Soome * implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and
199767f8919635c4928607450d9e0abb932109ceToomas Soome * GOST-ECC. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome switch (algorithm) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_RSASHA1:
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
199767f8919635c4928607450d9e0abb932109ceToomas Soome return GCRY_MD_SHA1;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_RSASHA256:
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_ECDSAP256SHA256:
199767f8919635c4928607450d9e0abb932109ceToomas Soome return GCRY_MD_SHA256;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_ECDSAP384SHA384:
199767f8919635c4928607450d9e0abb932109ceToomas Soome return GCRY_MD_SHA384;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_RSASHA512:
199767f8919635c4928607450d9e0abb932109ceToomas Soome return GCRY_MD_SHA512;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome default:
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EOPNOTSUPP;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomeint dnssec_verify_rrset(
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsAnswer *a,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const DnsResourceKey *key,
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord *rrsig,
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord *dnskey,
199767f8919635c4928607450d9e0abb932109ceToomas Soome usec_t realtime,
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnssecResult *result) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
199767f8919635c4928607450d9e0abb932109ceToomas Soome size_t hash_size;
199767f8919635c4928607450d9e0abb932109ceToomas Soome void *hash;
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord **list, *rr;
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_hd_t md = NULL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome int r, md_algorithm;
199767f8919635c4928607450d9e0abb932109ceToomas Soome size_t k, n = 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(key);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(rrsig);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(dnskey);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(result);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(rrsig->key->type == DNS_TYPE_RRSIG);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(dnskey->key->type == DNS_TYPE_DNSKEY);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Verifies the the RRSet matching the specified "key" in "a",
199767f8919635c4928607450d9e0abb932109ceToomas Soome * using the signature "rrsig" and the key "dnskey". It's
199767f8919635c4928607450d9e0abb932109ceToomas Soome * assumed the RRSIG and DNSKEY match. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (md_algorithm == -EOPNOTSUPP) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome *result = DNSSEC_UNSUPPORTED_ALGORITHM;
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (md_algorithm < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return md_algorithm;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dnssec_rrsig_expired(rrsig, realtime);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r > 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome *result = DNSSEC_SIGNATURE_EXPIRED;
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Collect all relevant RRs in a single array, so that we can look at the RRset */
199767f8919635c4928607450d9e0abb932109ceToomas Soome list = newa(DnsResourceRecord *, a->n_rrs);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome DNS_ANSWER_FOREACH(rr, a) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dns_resource_key_equal(key, rr->key);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r == 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* We need the wire format for ordering, and digest calculation */
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dns_resource_record_to_wire_format(rr, true);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome list[n++] = rr;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (n > VERIFY_RRS_MAX)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -E2BIG;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (n <= 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -ENODATA;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Bring the RRs into canonical order */
199767f8919635c4928607450d9e0abb932109ceToomas Soome qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* OK, the RRs are now in canonical order. Let's calculate the digest */
199767f8919635c4928607450d9e0abb932109ceToomas Soome initialize_libgcrypt();
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome hash_size = gcry_md_get_algo_dlen(md_algorithm);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(hash_size > 0);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_open(&md, md_algorithm, 0);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (!md)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint16(md, rrsig->rrsig.type_covered);
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint8(md, rrsig->rrsig.algorithm);
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint8(md, rrsig->rrsig.labels);
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint32(md, rrsig->rrsig.original_ttl);
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint32(md, rrsig->rrsig.expiration);
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint32(md, rrsig->rrsig.inception);
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint16(md, rrsig->rrsig.key_tag);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_write(md, wire_format_name, r);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome for (k = 0; k < n; k++) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome const char *suffix;
199767f8919635c4928607450d9e0abb932109ceToomas Soome size_t l;
199767f8919635c4928607450d9e0abb932109ceToomas Soome rr = list[k];
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r > 0) /* This is a wildcard! */
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_write(md, wire_format_name, r);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint16(md, rr->key->type);
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint16(md, rr->key->class);
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint32(md, rrsig->rrsig.original_ttl);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(l <= 0xFFFF);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint16(md, (uint16_t) l);
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l);
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome hash = gcry_md_read(md, 0);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (!hash) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome switch (rrsig->rrsig.algorithm) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_RSASHA1:
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_RSASHA256:
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_RSASHA512:
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dnssec_rsa_verify(
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_algo_name(md_algorithm),
199767f8919635c4928607450d9e0abb932109ceToomas Soome hash, hash_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome rrsig,
199767f8919635c4928607450d9e0abb932109ceToomas Soome dnskey);
199767f8919635c4928607450d9e0abb932109ceToomas Soome break;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_ECDSAP256SHA256:
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_ALGORITHM_ECDSAP384SHA384:
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dnssec_ecdsa_verify(
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_algo_name(md_algorithm),
199767f8919635c4928607450d9e0abb932109ceToomas Soome rrsig->rrsig.algorithm,
199767f8919635c4928607450d9e0abb932109ceToomas Soome hash, hash_size,
199767f8919635c4928607450d9e0abb932109ceToomas Soome rrsig,
199767f8919635c4928607450d9e0abb932109ceToomas Soome dnskey);
199767f8919635c4928607450d9e0abb932109ceToomas Soome break;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome *result = r ? DNSSEC_VALIDATED : DNSSEC_INVALID;
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomefinish:
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_close(md);
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomeint dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(rrsig);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(dnskey);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Checks if the specified DNSKEY RR matches the key used for
199767f8919635c4928607450d9e0abb932109ceToomas Soome * the signature in the specified RRSIG RR */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (rrsig->key->type != DNS_TYPE_RRSIG)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EINVAL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (dnskey->key->type != DNS_TYPE_DNSKEY)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (dnskey->key->class != rrsig->key->class)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE))
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (dnskey->dnskey.protocol != 3)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomeint dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome int r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(key);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(rrsig);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (rrsig->key->type != DNS_TYPE_RRSIG)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (rrsig->key->class != key->class)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (rrsig->rrsig.type_covered != key->type)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Make sure signer is a parent of the RRset */
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r <= 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Make sure the owner name has at least as many labels as the "label" fields indicates. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key));
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < rrsig->rrsig.labels)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic int dnssec_fix_rrset_ttl(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord *rrsig, usec_t realtime) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord *rr;
199767f8919635c4928607450d9e0abb932109ceToomas Soome int r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(key);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(rrsig);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome DNS_ANSWER_FOREACH(rr, a) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dns_resource_key_equal(key, rr->key);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r == 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Pick the TTL as the minimum of the RR's TTL, the
199767f8919635c4928607450d9e0abb932109ceToomas Soome * RR's original TTL according to the RRSIG and the
199767f8919635c4928607450d9e0abb932109ceToomas Soome * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */
199767f8919635c4928607450d9e0abb932109ceToomas Soome rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl);
199767f8919635c4928607450d9e0abb932109ceToomas Soome rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomeint dnssec_verify_rrset_search(
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsAnswer *a,
199767f8919635c4928607450d9e0abb932109ceToomas Soome const DnsResourceKey *key,
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsAnswer *validated_dnskeys,
199767f8919635c4928607450d9e0abb932109ceToomas Soome usec_t realtime,
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnssecResult *result) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord *rrsig;
199767f8919635c4928607450d9e0abb932109ceToomas Soome int r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(key);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(result);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (!a || a->n_rrs <= 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -ENODATA;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Iterate through each RRSIG RR. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome DNS_ANSWER_FOREACH(rrsig, a) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord *dnskey;
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsAnswerFlags flags;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Is this an RRSIG RR that applies to RRs matching our key? */
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dnssec_key_match_rrsig(key, rrsig);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r == 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome found_rrsig = true;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Look for a matching key */
199767f8919635c4928607450d9e0abb932109ceToomas Soome DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnssecResult one_result;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Is this a DNSKEY RR that matches they key of our RRSIG? */
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r == 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Take the time here, if it isn't set yet, so
199767f8919635c4928607450d9e0abb932109ceToomas Soome * that we do all validations with the same
199767f8919635c4928607450d9e0abb932109ceToomas Soome * time. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (realtime == USEC_INFINITY)
199767f8919635c4928607450d9e0abb932109ceToomas Soome realtime = now(CLOCK_REALTIME);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Yay, we found a matching RRSIG with a matching
199767f8919635c4928607450d9e0abb932109ceToomas Soome * DNSKEY, awesome. Now let's verify all entries of
199767f8919635c4928607450d9e0abb932109ceToomas Soome * the RRSet against the RRSIG and DNSKEY
199767f8919635c4928607450d9e0abb932109ceToomas Soome * combination. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome switch (one_result) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_VALIDATED:
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Yay, the RR has been validated,
199767f8919635c4928607450d9e0abb932109ceToomas Soome * return immediately, but fix up the expiry */
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dnssec_fix_rrset_ttl(a, key, rrsig, realtime);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome *result = DNSSEC_VALIDATED;
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_INVALID:
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* If the signature is invalid, let's try another
199767f8919635c4928607450d9e0abb932109ceToomas Soome key and/or signature. After all they
199767f8919635c4928607450d9e0abb932109ceToomas Soome key_tags and stuff are not unique, and
199767f8919635c4928607450d9e0abb932109ceToomas Soome might be shared by multiple keys. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome found_invalid = true;
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_UNSUPPORTED_ALGORITHM:
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* If the key algorithm is
199767f8919635c4928607450d9e0abb932109ceToomas Soome unsupported, try another
199767f8919635c4928607450d9e0abb932109ceToomas Soome RRSIG/DNSKEY pair, but remember we
199767f8919635c4928607450d9e0abb932109ceToomas Soome encountered this, so that we can
199767f8919635c4928607450d9e0abb932109ceToomas Soome return a proper error when we
199767f8919635c4928607450d9e0abb932109ceToomas Soome encounter nothing better. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome found_unsupported_algorithm = true;
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_SIGNATURE_EXPIRED:
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* If the signature is expired, try
199767f8919635c4928607450d9e0abb932109ceToomas Soome another one, but remember it, so
199767f8919635c4928607450d9e0abb932109ceToomas Soome that we can return this */
199767f8919635c4928607450d9e0abb932109ceToomas Soome found_expired_rrsig = true;
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome default:
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert_not_reached("Unexpected DNSSEC validation result");
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (found_expired_rrsig)
199767f8919635c4928607450d9e0abb932109ceToomas Soome *result = DNSSEC_SIGNATURE_EXPIRED;
199767f8919635c4928607450d9e0abb932109ceToomas Soome else if (found_unsupported_algorithm)
199767f8919635c4928607450d9e0abb932109ceToomas Soome *result = DNSSEC_UNSUPPORTED_ALGORITHM;
199767f8919635c4928607450d9e0abb932109ceToomas Soome else if (found_invalid)
199767f8919635c4928607450d9e0abb932109ceToomas Soome *result = DNSSEC_INVALID;
199767f8919635c4928607450d9e0abb932109ceToomas Soome else if (found_rrsig)
199767f8919635c4928607450d9e0abb932109ceToomas Soome *result = DNSSEC_MISSING_KEY;
199767f8919635c4928607450d9e0abb932109ceToomas Soome else
199767f8919635c4928607450d9e0abb932109ceToomas Soome *result = DNSSEC_NO_SIGNATURE;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomeint dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord *rr;
199767f8919635c4928607450d9e0abb932109ceToomas Soome int r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome DNS_ANSWER_FOREACH(rr, a) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dnssec_key_match_rrsig(key, rr);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r > 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 1;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomeint dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome size_t c = 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome int r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Converts the specified hostname into DNSSEC canonicalized
199767f8919635c4928607450d9e0abb932109ceToomas Soome * form. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (buffer_max < 2)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -ENOBUFS;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome for (;;) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome size_t i;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dns_label_unescape(&n, buffer, buffer_max);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r == 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome break;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r > 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome int k;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* DNSSEC validation is always done on the ASCII version of the label */
199767f8919635c4928607450d9e0abb932109ceToomas Soome k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (k < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return k;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (k > 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = k;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (buffer_max < (size_t) r + 2)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -ENOBUFS;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* The DNSSEC canonical form is not clear on what to
199767f8919635c4928607450d9e0abb932109ceToomas Soome * do with dots appearing in labels, the way DNS-SD
199767f8919635c4928607450d9e0abb932109ceToomas Soome * does it. Refuse it for now. */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (memchr(buffer, '.', r))
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EINVAL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome for (i = 0; i < (size_t) r; i ++) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (buffer[i] >= 'A' && buffer[i] <= 'Z')
199767f8919635c4928607450d9e0abb932109ceToomas Soome buffer[i] = buffer[i] - 'A' + 'a';
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome buffer[r] = '.';
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome buffer += r + 1;
199767f8919635c4928607450d9e0abb932109ceToomas Soome c += r + 1;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome buffer_max -= r + 1;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (c <= 0) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Not even a single label: this is the root domain name */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(buffer_max > 2);
199767f8919635c4928607450d9e0abb932109ceToomas Soome buffer[0] = '.';
199767f8919635c4928607450d9e0abb932109ceToomas Soome buffer[1] = 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 1;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome return (int) c;
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomestatic int digest_to_gcrypt_md(uint8_t algorithm) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome switch (algorithm) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_DIGEST_SHA1:
199767f8919635c4928607450d9e0abb932109ceToomas Soome return GCRY_MD_SHA1;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_DIGEST_SHA256:
199767f8919635c4928607450d9e0abb932109ceToomas Soome return GCRY_MD_SHA256;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome case DNSSEC_DIGEST_SHA384:
199767f8919635c4928607450d9e0abb932109ceToomas Soome return GCRY_MD_SHA384;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome default:
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EOPNOTSUPP;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomeint dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_hd_t md = NULL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome size_t hash_size;
199767f8919635c4928607450d9e0abb932109ceToomas Soome int md_algorithm, r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome void *result;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(dnskey);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(ds);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (dnskey->key->type != DNS_TYPE_DNSKEY)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EINVAL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ds->key->type != DNS_TYPE_DS)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EINVAL;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EKEYREJECTED;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE))
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EKEYREJECTED;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (dnskey->dnskey.protocol != 3)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EKEYREJECTED;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (dnskey->dnskey.algorithm != ds->ds.algorithm)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome initialize_libgcrypt();
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (md_algorithm < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return md_algorithm;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome hash_size = gcry_md_get_algo_dlen(md_algorithm);
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(hash_size > 0);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ds->ds.digest_size != hash_size)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_open(&md, md_algorithm, 0);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (!md)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_write(md, owner_name, r);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (mask_revoke)
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE);
199767f8919635c4928607450d9e0abb932109ceToomas Soome else
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint16(md, dnskey->dnskey.flags);
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint8(md, dnskey->dnskey.protocol);
199767f8919635c4928607450d9e0abb932109ceToomas Soome md_add_uint8(md, dnskey->dnskey.algorithm);
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome result = gcry_md_read(md, 0);
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (!result) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = -EIO;
199767f8919635c4928607450d9e0abb932109ceToomas Soome goto finish;
199767f8919635c4928607450d9e0abb932109ceToomas Soome }
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomefinish:
199767f8919635c4928607450d9e0abb932109ceToomas Soome gcry_md_close(md);
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome}
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soomeint dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsResourceRecord *ds;
199767f8919635c4928607450d9e0abb932109ceToomas Soome DnsAnswerFlags flags;
199767f8919635c4928607450d9e0abb932109ceToomas Soome int r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome assert(dnskey);
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (dnskey->key->type != DNS_TYPE_DNSKEY)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return 0;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) {
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ds->key->type != DNS_TYPE_DS)
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (ds->key->class != dnskey->key->class)
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key));
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r < 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome return r;
199767f8919635c4928607450d9e0abb932109ceToomas Soome if (r == 0)
199767f8919635c4928607450d9e0abb932109ceToomas Soome continue;
199767f8919635c4928607450d9e0abb932109ceToomas Soome
199767f8919635c4928607450d9e0abb932109ceToomas Soome r = dnssec_verify_dnskey(dnskey, ds, false);
199767f8919635c4928607450d9e0abb932109ceToomas Soome 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, DnsAnswerFlags flags, 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(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) {
_cleanup_free_ char *l = NULL, *hashed_domain = NULL;
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;
l = base32hexmem(hashed, hashed_size, false);
if (!l)
return -ENOMEM;
hashed_domain = strjoin(l, ".", zone, NULL);
if (!hashed_domain)
return -ENOMEM;
*ret = hashed_domain;
hashed_domain = NULL;
return hashed_size;
}
/* 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) {
_cleanup_free_ char *next_closer_domain = NULL, *wildcard = NULL, *wildcard_domain = NULL;
const char *zone, *p, *pp = NULL;
DnsResourceRecord *rr, *enclosure_rr, *suffix_rr, *wildcard_rr = NULL;
DnsAnswerFlags flags;
int hashed_size, r;
bool a, no_closer = false, no_wildcard = false, optout = false;
assert(key);
assert(result);
assert(authenticated);
/* 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(suffix_rr, flags, answer) {
r = nsec3_is_good(suffix_rr, flags, NULL);
if (r < 0)
return r;
if (r == 0)
continue;
r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_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(suffix_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, flags, suffix_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;
*authenticated = a;
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(enclosure_rr, wildcard, zone, &wildcard_domain);
if (r < 0)
return r;
if (r != hashed_size)
return -EBADMSG;
r = nsec3_hashed_domain(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 *label = NULL, *next_hashed_domain = NULL;
r = nsec3_is_good(rr, flags, suffix_rr);
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, ".", zone, 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)
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;
}
}
*authenticated = a;
return 0;
}
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
DnsResourceRecord *rr;
bool have_nsec3 = false;
DnsAnswerFlags flags;
int r;
assert(key);
assert(result);
assert(authenticated);
/* 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;
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
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;
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
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);
/* 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_DOWNGRADE_OK] = "downgrade-ok",
[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",
[DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);