resolved-dns-dnssec.c revision d1d1d4b807bc0fdffb64077fe724588ca3af8376
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/>.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering/* Open question:
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * How does the DNSSEC canonical form of a hostname with a label
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * containing a dot look like, the way DNS-SD does it?
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * - multi-label zone compatibility
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * - cname/dname compatibility
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * - nxdomain on qname
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * - workable hack for the .corp, .home, .box case
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * - bus calls to override DNSEC setting per interface
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering/* Maximum number of NSEC3 iterations we'll do. */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * The DNSSEC Chain of trust:
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * DS RRs are protected like normal RRs
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * Example chain:
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
ad867662936a4c7ab2c7116d804c272338801231Lennart Poetteringstatic void initialize_libgcrypt(void) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const char *p;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
assert(p);
const uint8_t *p;
size_t i;
if (mask_revoke)
f &= ~DNSKEY_FLAG_REVOKE;
static int rr_compare(const void *a, const void *b) {
size_t m;
assert(x);
assert(*x);
assert(y);
assert(*y);
static int dnssec_rsa_verify_raw(
const char *hash_algorithm,
if (ge != 0) {
r = -EIO;
goto finish;
if (ge != 0) {
r = -EIO;
goto finish;
if (ge != 0) {
r = -EIO;
goto finish;
NULL,
if (ge != 0) {
r = -EIO;
goto finish;
NULL,
(int) data_size,
data);
if (ge != 0) {
r = -EIO;
goto finish;
NULL,
if (ge != 0) {
r = -EIO;
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);
return -EKEYREJECTED;
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;
if (md_algorithm < 0)
return md_algorithm;
if (n > VERIFY_RRS_MAX)
return -E2BIG;
return -ENODATA;
if (!md)
return -EIO;
goto finish;
const char *suffix;
size_t l;
goto finish;
goto finish;
if (!hash) {
r = -EIO;
goto finish;
case DNSSEC_ALGORITHM_RSASHA1:
r = dnssec_rsa_verify(
dnskey);
r = dnssec_ecdsa_verify(
dnskey);
goto finish;
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:
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)
size_t c = 0;
return -ENOBUFS;
size_t i;
return -ENOBUFS;
return -EINVAL;
for (i = 0; i < (size_t) r; i ++) {
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(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) {
int hashed_size;
if (hashed_size < 0)
return hashed_size;
return -ENOMEM;
if (!hashed_domain)
return -ENOMEM;
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 (!wildcard)
return -ENOMEM;
if (r != hashed_size)
return -EBADMSG;
if (r != hashed_size)
return -EBADMSG;
if (!label)
return -ENOMEM;
if (!next_hashed_domain)
return -ENOMEM;
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_test_nsec(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)