resolved-dns-dnssec.c revision 13b78323bad1e41e0474b833da2a0b72aab56f09
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering This file is part of systemd.
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering Copyright 2015 Lennart Poettering
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering systemd is free software; you can redistribute it and/or modify it
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering under the terms of the GNU Lesser General Public License as published by
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering (at your option) any later version.
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering systemd is distributed in the hope that it will be useful, but
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering Lesser General Public License for more details.
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering You should have received a copy of the GNU Lesser General Public License
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering/* Open question:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * How does the DNSSEC canonical form of a hostname with a label
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * containing a dot look like, the way DNS-SD does it?
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - Make trust anchor store read additional DS+DNSKEY data from disk
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - wildcard zones compatibility
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - multi-label zone compatibility
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - cname/dname compatibility
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - per-interface DNSSEC setting
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - fix TTL for cache entries to match RRSIG TTL
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - retry on failed validation?
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - DSA support
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * - EC support?
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * The DNSSEC Chain of trust:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * DS RRs are protected like normal RRs
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * Example chain:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic void initialize_libgcrypt(void) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const char *p;
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic bool dnssec_algorithm_supported(int algorithm) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringuint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* The algorithm from RFC 4034, Appendix B. */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering for (i = 0; i < dnskey->dnskey.key_size; i++)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poetteringstatic int rr_compare(const void *a, const void *b) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Let's order the RRs according to RFC 4034, Section 6.3 */
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = memcmp((*x)->wire_format, (*y)->wire_format, m);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if ((*x)->wire_format_size < (*y)->wire_format_size)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering else if ((*x)->wire_format_size > (*y)->wire_format_size)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const void *signature, size_t signature_size,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const void *exponent, size_t exponent_size,
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering const void *modulus, size_t modulus_size) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
78fe420ff0bb4cd94de3c4d3f15a3021cc3e2878Lennart Poettering ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering "(sig-val (rsa (s %m)))",
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering "(data (flags pkcs1) (hash %s %b))",
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering "(public-key (rsa (n %m) (e %m)))",
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering else if (ge != 0) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering log_debug("RSA signature check failed: %s", gpg_strerror(ge));
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringstatic int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering inception = rrsig->rrsig.inception * USEC_PER_SEC;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Permit a certain amount of clock skew of 10% of the valid
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * time range. This takes inspiration from unbound's
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * resolver. */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return realtime < inception || realtime > expiration;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering size_t exponent_size, modulus_size, hash_size;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Verifies the the RRSet matching the specified "key" in "a",
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * using the signature "rrsig" and the key "dnskey". It's
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering * assumed the RRSIG and DNSKEY match. */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm)) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dnssec_rrsig_expired(rrsig, realtime);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Collect all relevant RRs in a single array, so that we can look at the RRset */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering list = newa(DnsResourceRecord *, a->n_rrs);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_resource_key_equal(key, rr->key);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* We need the wire format for ordering, and digest calculation */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_resource_record_to_wire_format(rr, true);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Bring the RRs into canonical order */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* OK, the RRs are now in canonical order. Let's calculate the digest */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint16(md, rrsig->rrsig.type_covered);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint8(md, rrsig->rrsig.algorithm);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint32(md, rrsig->rrsig.original_ttl);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint32(md, rrsig->rrsig.expiration);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint32(md, rrsig->rrsig.inception);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering for (k = 0; k < n; k++) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (r > 0) /* This is a wildcard! */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering md_add_uint32(md, rrsig->rrsig.original_ttl);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering l = rr->wire_format_size - rr->wire_format_rdata_offset;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (*(uint8_t*) dnskey->dnskey.key == 0) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* exponent is > 255 bytes long */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 3;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (3 + exponent_size >= dnskey->dnskey.key_size) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* exponent is <= 255 bytes long */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 1;
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (1 + exponent_size >= dnskey->dnskey.key_size) {
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering rrsig->rrsig.signature, rrsig->rrsig.signature_size,
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering *result = r ? DNSSEC_VALIDATED : DNSSEC_INVALID;
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poetteringint dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering /* Checks if the specified DNSKEY RR matches the key used for
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering * the signature in the specified RRSIG RR */
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering if (dnskey->key->type != DNS_TYPE_DNSKEY)
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering if (dnskey->key->class != rrsig->key->class)
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
875c2e220e2611165e09051c4747971811f1de58Lennart Poetteringint dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering if (rrsig->rrsig.type_covered != key->type)
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Make sure signer is a parent of the RRset */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer);
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering /* Make sure the owner name has at least as many labels as the "label" fields indicates. */
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key));
875c2e220e2611165e09051c4747971811f1de58Lennart Poettering return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
4d9ced9956755901238fede6fc5a3d7e4e816aa6Lennart Poettering 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;
return -EOPNOTSUPP;
int algorithm;
void *result;
return -EINVAL;
return -EINVAL;
return -EKEYREJECTED;
return -EKEYREJECTED;
if (algorithm < 0)
return algorithm;
if (!md)
return -EIO;
if (!result) {
r = -EIO;
goto finish;
int algorithm;
void *result;
return -EINVAL;
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;
int hashed_size, r;
goto found_suffix;
if (hashed_size < 0)
return hashed_size;
return -EBADMSG;
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) {
if (r != hashed_size)
return -EBADMSG;
return -ENOMEM;
if (!next_closer_domain)
return -ENOMEM;
const char *nsec3_parent;
if (!label)
return -ENOMEM;
if (!next_hashed_domain)
return -ENOMEM;
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)