resolved-dns-dnssec.c revision 7715f91dca0ce4622daf8beab582d57ec49d005e
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek This file is part of systemd.
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek Copyright 2015 Lennart Poettering
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek systemd is free software; you can redistribute it and/or modify it
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek under the terms of the GNU Lesser General Public License as published by
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek the Free Software Foundation; either version 2.1 of the License, or
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek (at your option) any later version.
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek systemd is distributed in the hope that it will be useful, but
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek WITHOUT ANY WARRANTY; without even the implied warranty of
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek Lesser General Public License for more details.
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek You should have received a copy of the GNU Lesser General Public License
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek along with systemd; If not, see <http://www.gnu.org/licenses/>.
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers/* Open question:
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * How does the DNSSEC canonical form of a hostname with a label
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * containing a dot look like, the way DNS-SD does it?
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - multi-label zone compatibility
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - cname/dname compatibility
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - nxdomain on qname
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - bus calls to override DNSEC setting per interface
0b6b6787e3f0ae8906ce0212bd629edbe931b73dKay Sievers * - log all DNSSEC downgrades
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * - enable by default
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * - RFC 4035, Section 5.3.4 (When receiving a positive wildcard reply, use NSEC to ensure it actually really applies)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * - RFC 6840, Section 4.1 (ensure we don't get fed a glue NSEC from the parent zone)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * - RFC 6840, Section 4.3 (check for CNAME on NSEC too)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek#define NSEC3_ITERATIONS_MAX 2500
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * The DNSSEC Chain of trust:
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * DS RRs are protected like normal RRs
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * Example chain:
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmekstatic void initialize_libgcrypt(void) {
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek p = gcry_check_version("1.4.5");
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek gcry_control(GCRYCTL_DISABLE_SECMEM);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmekuint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) {
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek /* The algorithm from RFC 4034, Appendix B. */
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek assert(dnskey->key->type == DNS_TYPE_DNSKEY);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek f = (uint32_t) dnskey->dnskey.flags;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek for (i = 0; i < dnskey->dnskey.key_size; i++)
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek sum += (sum >> 16) & UINT32_C(0xFFFF);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmekstatic int rr_compare(const void *a, const void *b) {
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek /* Let's order the RRs according to RFC 4034, Section 6.3 */
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y));
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m);
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmekstatic int dnssec_rsa_verify_raw(
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek const void *signature, size_t signature_size,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek const void *data, size_t data_size,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek const void *exponent, size_t exponent_size,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek const void *modulus, size_t modulus_size) {
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak if (ge != 0) {
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak if (ge != 0) {
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak if (ge != 0) {
3519d230c8bafe834b2dac26ace49fcfba139823Karel Zak "(sig-val (rsa (s %m)))",
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek ge = gcry_sexp_build(&data_sexp,
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek "(data (flags pkcs1) (hash %s %b))",
d15d0333be6a1ca7fdd99a1881d967b6be8f387aZbigniew Jędrzejewski-Szmek ge = gcry_sexp_build(&public_key_sexp,
c5e04d51277994cca29234b33a6b8fc90a183cf3Zbigniew Jędrzejewski-Szmek "(public-key (rsa (n %m) (e %m)))",
goto finish;
else if (ge != 0) {
r = -EIO;
gcry_mpi_release(e);
gcry_mpi_release(n);
gcry_mpi_release(s);
if (public_key_sexp)
if (signature_sexp)
if (data_sexp)
static int dnssec_rsa_verify(
const char *hash_algorithm,
return -EINVAL;
return -EINVAL;
if (exponent_size <= 0)
return -EINVAL;
return -EINVAL;
return dnssec_rsa_verify_raw(
static int dnssec_ecdsa_verify_raw(
const char *hash_algorithm,
const char *curve,
if (ge != 0) {
k = -EIO;
goto finish;
if (ge != 0) {
k = -EIO;
goto finish;
if (ge != 0) {
k = -EIO;
goto finish;
NULL,
if (ge != 0) {
k = -EIO;
goto finish;
NULL,
(int) data_size,
data);
if (ge != 0) {
k = -EIO;
goto finish;
NULL,
if (ge != 0) {
k = -EIO;
goto finish;
else if (ge != 0) {
k = -EIO;
gcry_mpi_release(r);
gcry_mpi_release(s);
gcry_mpi_release(q);
if (public_key_sexp)
if (signature_sexp)
if (data_sexp)
static int dnssec_ecdsa_verify(
const char *hash_algorithm,
int algorithm,
const char *curve;
uint8_t *q;
return -EOPNOTSUPP;
return -EINVAL;
return -EINVAL;
return dnssec_ecdsa_verify_raw(
v = htobe16(v);
v = htobe32(v);
inception = 0;
switch (algorithm) {
case DNSSEC_ALGORITHM_RSASHA1:
return GCRY_MD_SHA1;
return GCRY_MD_SHA256;
return GCRY_MD_SHA384;
return GCRY_MD_SHA512;
return -EOPNOTSUPP;
int dnssec_verify_rrset(
DnsAnswer *a,
void *hash;
int r, md_algorithm;
size_t k, n = 0;
bool wildcard;
const char *source;
if (md_algorithm < 0)
return md_algorithm;
wildcard = r > 0;
if (n > VERIFY_RRS_MAX)
return -E2BIG;
return -ENODATA;
if (!md)
return -EIO;
goto finish;
goto finish;
size_t l;
if (wildcard)
if (!hash) {
r = -EIO;
goto finish;
case DNSSEC_ALGORITHM_RSASHA1:
r = dnssec_rsa_verify(
dnskey);
r = dnssec_ecdsa_verify(
dnskey);
goto finish;
else if (wildcard)
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) {
return -EINVAL;
static int dnssec_fix_rrset_ttl(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord *rrsig, usec_t realtime) {
DnsAnswer *a,
bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
if (!a || a->n_rrs <= 0)
return -ENODATA;
found_rrsig = true;
switch (one_result) {
case DNSSEC_VALIDATED:
if (ret_rrsig)
case DNSSEC_INVALID:
found_invalid = true;
found_unsupported_algorithm = true;
case DNSSEC_SIGNATURE_EXPIRED:
found_expired_rrsig = true;
if (found_expired_rrsig)
else if (found_unsupported_algorithm)
else if (found_invalid)
else if (found_rrsig)
if (ret_rrsig)
size_t c = 0;
return -ENOBUFS;
return -ENOBUFS;
return -EINVAL;
switch (algorithm) {
case DNSSEC_DIGEST_SHA1:
return GCRY_MD_SHA1;
case DNSSEC_DIGEST_SHA256:
return GCRY_MD_SHA256;
case DNSSEC_DIGEST_SHA384:
return GCRY_MD_SHA384;
return -EOPNOTSUPP;
int md_algorithm, r;
void *result;
return -EINVAL;
return -EINVAL;
return -EKEYREJECTED;
return -EKEYREJECTED;
return -EKEYREJECTED;
if (md_algorithm < 0)
return md_algorithm;
if (!md)
return -EIO;
if (mask_revoke)
if (!result) {
r = -EIO;
goto finish;
if (r == -EKEYREJECTED)
switch (algorithm) {
case NSEC3_ALGORITHM_SHA1:
return GCRY_MD_SHA1;
return -EOPNOTSUPP;
int algorithm;
void *result;
return -EINVAL;
log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3));
return -EOPNOTSUPP;
if (algorithm < 0)
return algorithm;
return -EINVAL;
if (!md)
return -EIO;
if (!result) {
r = -EIO;
goto finish;
if (!result) {
r = -EIO;
goto finish;
r = (int) hash_size;
if (!nsec3)
return dns_name_equal(a, b);
static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) {
return -ENOMEM;
return -ENOMEM;
*ret = j;
return (int) hashed_size;
static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) {
int hashed_size;
if (hashed_size < 0)
return hashed_size;
* First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest
* is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that
* 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
static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
int hashed_size, r;
goto found_zone;
if (hashed_size < 0)
return hashed_size;
goto found_closest_encloser;
pp = p;
r = dns_name_parent(&p);
return -EBADMSG;
return -EBADMSG;
if (!pp) {
if (authenticated)
*authenticated = a;
if (ttl)
if (r != hashed_size)
return -EBADMSG;
if (r != hashed_size)
return -EBADMSG;
r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain);
optout = true;
no_closer = true;
optout = true;
no_wildcard = true;
return -EBADMSG;
if (!no_closer) {
if (wildcard_rr) {
if (optout)
if (optout)
else if (no_wildcard)
if (authenticated)
*authenticated = a;
if (ttl)
int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
bool have_nsec3 = false;
case DNS_TYPE_NSEC:
if (authenticated)
if (ttl)
r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name);
if (authenticated)
if (ttl)
case DNS_TYPE_NSEC3:
have_nsec3 = true;
if (have_nsec3)
int dnssec_nsec_test_between(DnsAnswer *answer, const char *name, const char *zone, bool *authenticated) {
/* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified
bool found = false;
case DNS_TYPE_NSEC:
found = r > 0;
case DNS_TYPE_NSEC3: {
rr,
name,
zone,
zone,
found = r > 0;
if (found) {
if (authenticated)