resolved-dns-dnssec.c revision a8f158b9296c7bc1d0f627715e626cc50a331efc
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering This file is part of systemd.
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering Copyright 2015 Lennart Poettering
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering systemd is free software; you can redistribute it and/or modify it
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering under the terms of the GNU Lesser General Public License as published by
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering (at your option) any later version.
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering systemd is distributed in the hope that it will be useful, but
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering Lesser General Public License for more details.
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering You should have received a copy of the GNU Lesser General Public License
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek/* Open question:
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek * How does the DNSSEC canonical form of a hostname with a label
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek * containing a dot look like, the way DNS-SD does it?
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering * - Make trust anchor store read additional DS+DNSKEY data from disk
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * - multi-label zone compatibility
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * - cname/dname compatibility
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering * - per-interface DNSSEC setting
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering * - nxdomain on qname
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering * - retry on failed validation?
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering * - DNSSEC key revocation support? https://tools.ietf.org/html/rfc5011
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * - when doing negative caching, use NSEC/NSEC3 RR instead of SOA for TTL
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek/* Maximum number of NSEC3 iterations we'll do. */
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering * The DNSSEC Chain of trust:
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * DS RRs are protected like normal RRs
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering * Example chain:
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmekstatic void initialize_libgcrypt(void) {
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek p = gcry_check_version("1.4.5");
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek gcry_control(GCRYCTL_DISABLE_SECMEM);
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poetteringuint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering /* The algorithm from RFC 4034, Appendix B. */
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering for (i = 0; i < dnskey->dnskey.key_size; i++)
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poetteringstatic int rr_compare(const void *a, const void *b) {
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering /* Let's order the RRs according to RFC 4034, Section 6.3 */
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering r = memcmp((*x)->wire_format, (*y)->wire_format, m);
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering if ((*x)->wire_format_size < (*y)->wire_format_size)
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering else if ((*x)->wire_format_size > (*y)->wire_format_size)
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering const void *signature, size_t signature_size,
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering const void *exponent, size_t exponent_size,
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering const void *modulus, size_t modulus_size) {
e9174f29c7e3ee45137537b126458718913a3ec5Lennart Poettering gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
de0671ee7fe465e108f62dcbbbe9366f81dd9e9aZbigniew Jędrzejewski-Szmek ge = gcry_sexp_build(&signature_sexp,
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering "(sig-val (rsa (s %m)))",
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering "(data (flags pkcs1) (hash %s %b))",
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering "(public-key (rsa (n %m) (e %m)))",
a0ab566574303be1ca12cdb334f284cfd407caa5Lennart Poettering ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
a0ab566574303be1ca12cdb334f284cfd407caa5Lennart Poettering if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering else if (ge != 0) {
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering log_debug("RSA signature check failed: %s", gpg_strerror(ge));
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering if (*(uint8_t*) dnskey->dnskey.key == 0) {
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering /* exponent is > 255 bytes long */
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 3;
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering if (3 + exponent_size >= dnskey->dnskey.key_size)
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
143bfdaf0b890fa7acadf02d1eafacaef1b696bdHolger Hans Peter Freyther /* exponent is <= 255 bytes long */
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 1;
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
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;
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;
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;
if (md_algorithm < 0)
return md_algorithm;
if (!md)
return -EIO;
if (!result) {
r = -EIO;
goto finish;
switch (algorithm) {
case NSEC3_ALGORITHM_SHA1:
return GCRY_MD_SHA1;
return -EOPNOTSUPP;
int algorithm;
void *result;
return -EINVAL;
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 dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
int hashed_size, r;
goto found_suffix;
if (hashed_size < 0)
return hashed_size;
if (!label)
return -ENOMEM;
if (!hashed_domain)
return -ENOMEM;
goto found_closest_encloser;
pp = p;
r = dns_name_parent(&p);
return -EBADMSG;
return -EBADMSG;
if (!pp) {
*authenticated = a;
if (r != hashed_size)
return -EBADMSG;
return -ENOMEM;
if (!next_closer_domain)
return -ENOMEM;
if (!label)
return -ENOMEM;
if (!next_hashed_domain)
return -ENOMEM;
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
bool have_nsec3 = false;
case DNS_TYPE_NSEC:
r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name);
case DNS_TYPE_NSEC3:
have_nsec3 = true;
if (have_nsec3)