resolved-dns-dnssec.c revision b9282bc12840aff500a334836226f6b8df24926d
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering This file is part of systemd.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering Copyright 2015 Lennart Poettering
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering systemd is free software; you can redistribute it and/or modify it
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering under the terms of the GNU Lesser General Public License as published by
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering (at your option) any later version.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering systemd is distributed in the hope that it will be useful, but
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering Lesser General Public License for more details.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering You should have received a copy of the GNU Lesser General Public License
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
71d35b6b5563817dfbe757ab9e3b9f018b2db491Thomas Hindoe Paaboel Andersen#include "resolved-dns-dnssec.h"
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering/* Open question:
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * How does the DNSSEC canonical form of a hostname with a label
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering * containing a dot look like, the way DNS-SD does it?
28b8191e2f391f043d380d47eb79ed9ff66f14bdLennart Poettering * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
28b8191e2f391f043d380d47eb79ed9ff66f14bdLennart Poettering * - multi-label zone compatibility
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * - cname/dname compatibility
23502de3b0891455c8ce499a9eb61b69d060a829Daniel Mack * - nxdomain on qname
23502de3b0891455c8ce499a9eb61b69d060a829Daniel Mack * - bus calls to override DNSEC setting per interface
23502de3b0891455c8ce499a9eb61b69d060a829Daniel Mack * - log all DNSSEC downgrades
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * - enable by default
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * - RFC 4035, Section 5.3.4 (When receiving a positive wildcard reply, use NSEC to ensure it actually really applies)
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * - RFC 6840, Section 4.1 (ensure we don't get fed a glue NSEC from the parent zone)
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * - RFC 6840, Section 4.3 (check for CNAME on NSEC too)
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
6f717d0817573a76c3e586eae02793d8b23a0581Lennart Poettering#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
6f717d0817573a76c3e586eae02793d8b23a0581Lennart Poettering/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * The DNSSEC Chain of trust:
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * DS RRs are protected like normal RRs
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * Example chain:
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
6f717d0817573a76c3e586eae02793d8b23a0581Lennart Poetteringstatic void initialize_libgcrypt(void) {
6f717d0817573a76c3e586eae02793d8b23a0581Lennart Poettering const char *p;
8730bccfc59fe507bd3e0a3abcf411b497ac4f0eLennart Poettering if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
d15ad74251454d55b715958d8e6f50f45195904aLennart Poettering gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poetteringuint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) {
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering /* The algorithm from RFC 4034, Appendix B. */
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
1b4f6e79ec51a57003896a0b605fba427b4a98d2Lennart Poettering sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering for (i = 0; i < dnskey->dnskey.key_size; i++)
2001c80560e3dae69e14fd994d3978c187af48b8Lennart Poettering sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poetteringstatic int rr_compare(const void *a, const void *b) {
7b50eb2efa122200e39646c19a29abab302f7d24Lennart Poettering DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
97c67192eadaffe67b803ec5b991a92bb1137d0bLennart Poettering /* Let's order the RRs according to RFC 4034, Section 6.3 */
a8812dd7f161a3e459c1730ac92ff2bbc9986ff1Lennart Poettering m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y));
a8812dd7f161a3e459c1730ac92ff2bbc9986ff1Lennart Poettering r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
d75acfb059ece4512278b8820a9103664996f1e5Lennart Poettering else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const void *signature, size_t signature_size,
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const void *exponent, size_t exponent_size,
8ac4e9e1e54397f6d1745c2a7a806132418c7da2Lennart Poettering const void *modulus, size_t modulus_size) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
c0eb11cfd016381fe02875a4ef29c1ade00c94e7Lennart Poettering ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
7e8e0422aeb16f2a09a40546c61df753d10029b6Lennart Poettering ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
946c70944ebdf428ffeb9991a7449edbd4011461Zbigniew Jędrzejewski-Szmek "(sig-val (rsa (s %m)))",
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek ge = gcry_sexp_build(&data_sexp,
0dae31d468b1a0e22d98921f7b0dbd92fd217167Zbigniew Jędrzejewski-Szmek "(data (flags pkcs1) (hash %s %b))",
549c1a2564b56f2bb38f1203d59c747ea15817f3Tom Gundersen "(public-key (rsa (n %m) (e %m)))",
8db0d2f5c37e7e8f5bfce016cfdad7947a3ea939Zbigniew Jędrzejewski-Szmek ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
8db0d2f5c37e7e8f5bfce016cfdad7947a3ea939Zbigniew Jędrzejewski-Szmek if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek else if (ge != 0) {
151226ab4bf276d60d51864330a99f886b923697Zbigniew Jędrzejewski-Szmek log_debug("RSA signature check failed: %s", gpg_strerror(ge));
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* exponent is > 255 bytes long */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 3;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) |
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]);
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering if (3 + exponent_size >= dnskey->dnskey.key_size)
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering /* exponent is <= 255 bytes long */
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 1;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering if (1 + exponent_size >= dnskey->dnskey.key_size)
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
85aeaccc10b111e8d16d3879b7c30a219ee6e10aLennart Poettering modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering rrsig->rrsig.signature, rrsig->rrsig.signature_size,
5d27351f8546530cf779847b0b04b0172c09f9d0Tom Gundersen const void *signature_r, size_t signature_r_size,
547973dea7abd6c124ff6c79fe2bbe322a7314aeLennart Poettering const void *signature_s, size_t signature_s_size,
7778dffff3d8bd7438fe19a248c16203668324c9Daniel Mack gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL);
322345fdb9865ef2477fba8e4bdde0e1183ef505Lennart Poettering ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL);
ab481675f98d3d3f12e7e48ba6d2159123b9c7bfLennart Poettering ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL);
8e54f5d90a6b9dd1ff672fb97ea98de66c49e332Lennart Poettering "(sig-val (ecdsa (r %m) (s %m)))",
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);
const char *name;
/* Checks whether the specified RRSIG RR is somewhat valid, and initializes the .n_skip_labels_source and
return -EINVAL;
if (n_key_labels < 0)
return n_key_labels;
return -EINVAL;
if (n_signer_labels < 0)
return n_signer_labels;
return -EINVAL;
return -EINVAL;
return -EINVAL;
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;
static void dnssec_fix_rrset_ttl(
assert(n > 0);
int dnssec_verify_rrset(
DnsAnswer *a,
int r, md_algorithm;
size_t k, n = 0;
void *hash;
bool wildcard;
if (md_algorithm < 0)
return md_algorithm;
if (r == -EINVAL) {
/* Some keys may only appear signed in the zone apex, and are invalid anywhere else. (SOA, NS...) */
wildcard = r == 0;
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;
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 dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
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;
return 0; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */
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)
/* If the name we just determined is not equal or child of the name we are interested in, then we can't say
/* If the name we we are interested in is not a prefix of the common suffix of the NSEC RR's owner and next domain names, then we can't say anything either. */
r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix);
int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
bool have_nsec3 = false;
const char *name;
case DNS_TYPE_NSEC:
if (authenticated)
if (ttl)
if (authenticated)
if (ttl)
if (authenticated)
if (ttl)
case DNS_TYPE_NSEC3:
have_nsec3 = true;
if (have_nsec3)
int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, 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)
static int dnssec_test_positive_wildcard_nsec3(
const char *name,
const char *source,
const char *zone,
bool *authenticated) {
* Note a key difference between the NSEC3 and NSEC versions of the proof. NSEC RRs don't have to exist for
* empty non-transients. NSEC3 RRs however have to. This means it's sufficient to check if the next closer name
* To prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f all we have to check is that
static int dnssec_test_positive_wildcard_nsec(
const char *name,
const char *source,
const char *zone,
bool *_authenticated) {
bool authenticated = true;
* A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and
* Note that if we want to prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f, then we
* 1) a.b.c.d.e.f
* 2) *.b.c.d.e.f
* 3) b.c.d.e.f
/* Check if there's an NSEC or NSEC3 RR that proves that the mame we determined is really non-existing,
return -EBADMSG;
if (!wc)
return -ENOMEM;
const char *name,
const char *source,
const char *zone,
bool *authenticated) {