resolved-dns-dnssec.c revision ca994e853c5408fcdeee66580e8b5056ad9e2c15
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen This file is part of systemd.
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen Copyright 2015 Lennart Poettering
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen systemd is free software; you can redistribute it and/or modify it
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen under the terms of the GNU Lesser General Public License as published by
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen the Free Software Foundation; either version 2.1 of the License, or
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen (at your option) any later version.
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen systemd is distributed in the hope that it will be useful, but
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen WITHOUT ANY WARRANTY; without even the implied warranty of
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen Lesser General Public License for more details.
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen You should have received a copy of the GNU Lesser General Public License
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen along with systemd; If not, see <http://www.gnu.org/licenses/>.
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen/* Open question:
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen * 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?
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering * - Make trust anchor store read additional DS+DNSKEY data from disk
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
39d8db043b599a7382f94bfc904d5e108af438bdLennart Poettering * - multi-label zone compatibility
39d8db043b599a7382f94bfc904d5e108af438bdLennart Poettering * - cname/dname compatibility
39d8db043b599a7382f94bfc904d5e108af438bdLennart Poettering * - per-interface DNSSEC setting
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * - fix TTL for cache entries to match RRSIG TTL
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * - retry on failed validation?
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * - DSA support?
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 * 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
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poetteringstatic void initialize_libgcrypt(void) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const char *p;
a2a416f768e2aa7db5b975cd50eb19237cac9cceLennart Poettering if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poetteringuint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
21d73c87b09ec2b8642424bc714ce9af3da4fc40Lennart Poettering /* The algorithm from RFC 4034, Appendix B. */
a2a416f768e2aa7db5b975cd50eb19237cac9cceLennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering for (i = 0; i < dnskey->dnskey.key_size; i++)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poetteringstatic int rr_compare(const void *a, const void *b) {
da927ba997d68401563b927f92e6e40e021a8e5cMichal Schmidt DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* Let's order the RRs according to RFC 4034, Section 6.3 */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = memcmp((*x)->wire_format, (*y)->wire_format, m);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if ((*x)->wire_format_size < (*y)->wire_format_size)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering else if ((*x)->wire_format_size > (*y)->wire_format_size)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const void *signature, size_t signature_size,
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const void *exponent, size_t exponent_size,
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const void *modulus, size_t modulus_size) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart 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);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering "(sig-val (rsa (s %m)))",
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering "(data (flags pkcs1) (hash %s %b))",
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering "(public-key (rsa (n %m) (e %m)))",
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering else if (ge != 0) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering log_debug("RSA signature check failed: %s", gpg_strerror(ge));
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (*(uint8_t*) dnskey->dnskey.key == 0) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* exponent is > 255 bytes long */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 3;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (3 + exponent_size >= dnskey->dnskey.key_size)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* exponent is <= 255 bytes long */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering exponent = (uint8_t*) dnskey->dnskey.key + 1;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (1 + exponent_size >= dnskey->dnskey.key_size)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering rrsig->rrsig.signature, rrsig->rrsig.signature_size,
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const void *signature_r, size_t signature_r_size,
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const void *signature_s, size_t signature_s_size,
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering 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);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering "(sig-val (ecdsa (r %m) (s %m)))",
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering "(data (flags rfc6979) (hash %s %b))",
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering "(public-key (ecc (curve %s) (q %m)))",
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering else if (ge != 0) {
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering log_debug("ECDSA signature check failed: %s", gpg_strerror(ge));
8d3d7072e609ef0e0fb37e1d19a29307d58146c3Michal Schmidt if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) {
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) {
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering if (dnskey->dnskey.key_size != key_size * 2)
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering if (rrsig->rrsig.signature_size != key_size * 2)
edc501d4674dadc304d45a7e1c5b69e207eb8cd4Lennart Poettering q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */
edc501d4674dadc304d45a7e1c5b69e207eb8cd4Lennart Poettering memcpy(q+1, dnskey->dnskey.key, key_size*2);
edc501d4674dadc304d45a7e1c5b69e207eb8cd4Lennart Poettering (uint8_t*) rrsig->rrsig.signature + key_size, key_size,
edc501d4674dadc304d45a7e1c5b69e207eb8cd4Lennart Poetteringstatic void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
edc501d4674dadc304d45a7e1c5b69e207eb8cd4Lennart Poetteringstatic void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
edc501d4674dadc304d45a7e1c5b69e207eb8cd4Lennart Poetteringstatic void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
edc501d4674dadc304d45a7e1c5b69e207eb8cd4Lennart Poetteringstatic int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
edc501d4674dadc304d45a7e1c5b69e207eb8cd4Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
edc501d4674dadc304d45a7e1c5b69e207eb8cd4Lennart Poettering expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
90ab504273a7f186ebb76e6acfb778b4e0d7c91bLennart Poettering inception = rrsig->rrsig.inception * USEC_PER_SEC;
90ab504273a7f186ebb76e6acfb778b4e0d7c91bLennart Poettering /* Permit a certain amount of clock skew of 10% of the valid
90ab504273a7f186ebb76e6acfb778b4e0d7c91bLennart Poettering * time range. This takes inspiration from unbound's
90ab504273a7f186ebb76e6acfb778b4e0d7c91bLennart Poettering * resolver. */
edc501d4674dadc304d45a7e1c5b69e207eb8cd4Lennart Poettering return realtime < inception || realtime > expiration;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poetteringstatic int algorithm_to_gcrypt_md(uint8_t algorithm) {
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen /* Translates a DNSSEC signature algorithm into a gcrypt digest identifier */
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* Verifies the the RRSet matching the specified "key" in "a",
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * using the signature "rrsig" and the key "dnskey". It's
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen * assumed the RRSIG and DNSKEY match. */
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm);
edc501d4674dadc304d45a7e1c5b69e207eb8cd4Lennart Poettering r = dnssec_rrsig_expired(rrsig, realtime);
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen /* Collect all relevant RRs in a single array, so that we can look at the RRset */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering list = newa(DnsResourceRecord *, a->n_rrs);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = dns_resource_key_equal(key, rr->key);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* We need the wire format for ordering, and digest calculation */
3e684349c2cead2e6fd2f816c34eb17daba23a49Lennart Poettering r = dns_resource_record_to_wire_format(rr, true);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* Bring the RRs into canonical order */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* OK, the RRs are now in canonical order. Let's calculate the digest */
902bb5d8abb2a7d258741828d212ca549ab16950Lennart Poettering hash_size = gcry_md_get_algo_dlen(md_algorithm);
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering md_add_uint16(md, rrsig->rrsig.type_covered);
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering md_add_uint8(md, rrsig->rrsig.algorithm);
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering md_add_uint32(md, rrsig->rrsig.original_ttl);
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering md_add_uint32(md, rrsig->rrsig.expiration);
eb60f9cd4e93ff5016dc1b5486fd1b7e1565fd92Lennart Poettering md_add_uint32(md, rrsig->rrsig.inception);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering for (k = 0; k < n; k++) {
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering if (r > 0) /* This is a wildcard! */
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true);
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering md_add_uint32(md, rrsig->rrsig.original_ttl);
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering l = rr->wire_format_size - rr->wire_format_rdata_offset;
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering *result = r ? DNSSEC_VALIDATED : DNSSEC_INVALID;
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poetteringint dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering /* Checks if the specified DNSKEY RR matches the key used for
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering * the signature in the specified RRSIG RR */
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering if (dnskey->key->type != DNS_TYPE_DNSKEY)
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering if (dnskey->key->class != rrsig->key->class)
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag)
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poetteringint dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
da927ba997d68401563b927f92e6e40e021a8e5cMichal Schmidt /* Make sure signer is a parent of the RRset */
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer);
87f5a19343acf8ba697acc5a62bdb1a2b8c9eda3Lennart Poettering /* Make sure the owner name has at least as many labels as the "label" fields indicates. */
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key));
a9feff3d774eaa1cc1b59189e8f344c01e69f888Tom Gundersen return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
bda2c408f8a739c19161818bcc842107f60652a2Tom Gundersen bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
bda2c408f8a739c19161818bcc842107f60652a2Tom Gundersen /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */
bda2c408f8a739c19161818bcc842107f60652a2Tom Gundersen if (!a || a->n_rrs <= 0)
4713135eae4f7b6b670a98476fe413edfa1d9f41Zbigniew Jędrzejewski-Szmek /* Iterate through each RRSIG RR. */
4713135eae4f7b6b670a98476fe413edfa1d9f41Zbigniew Jędrzejewski-Szmek /* Is this an RRSIG RR that applies to RRs matching our key? */
4713135eae4f7b6b670a98476fe413edfa1d9f41Zbigniew Jędrzejewski-Szmek r = dnssec_key_match_rrsig(key, rrsig);
4713135eae4f7b6b670a98476fe413edfa1d9f41Zbigniew Jędrzejewski-Szmek /* Look for a matching key */
4713135eae4f7b6b670a98476fe413edfa1d9f41Zbigniew Jędrzejewski-Szmek DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) {
4713135eae4f7b6b670a98476fe413edfa1d9f41Zbigniew Jędrzejewski-Szmek if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
4713135eae4f7b6b670a98476fe413edfa1d9f41Zbigniew Jędrzejewski-Szmek /* Is this a DNSKEY RR that matches they key of our RRSIG? */
4713135eae4f7b6b670a98476fe413edfa1d9f41Zbigniew Jędrzejewski-Szmek r = dnssec_rrsig_match_dnskey(rrsig, dnskey);
4713135eae4f7b6b670a98476fe413edfa1d9f41Zbigniew Jędrzejewski-Szmek /* Take the time here, if it isn't set yet, so
4713135eae4f7b6b670a98476fe413edfa1d9f41Zbigniew Jędrzejewski-Szmek * that we do all validations with the same
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen /* Yay, we found a matching RRSIG with a matching
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen * DNSKEY, awesome. Now let's verify all entries of
bda2c408f8a739c19161818bcc842107f60652a2Tom Gundersen * the RRSet against the RRSIG and DNSKEY
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * combination. */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result);
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering /* Yay, the RR has been validated,
87f5a19343acf8ba697acc5a62bdb1a2b8c9eda3Lennart Poettering * return immediately. */
d5099efc47d4e6ac60816b5381a5f607ab03f06eMichal Schmidt /* If the signature is invalid, let's try another
bda2c408f8a739c19161818bcc842107f60652a2Tom Gundersen key and/or signature. After all they
bda2c408f8a739c19161818bcc842107f60652a2Tom Gundersen key_tags and stuff are not unique, and
bda2c408f8a739c19161818bcc842107f60652a2Tom Gundersen might be shared by multiple keys. */
87f5a19343acf8ba697acc5a62bdb1a2b8c9eda3Lennart Poettering /* If the key algorithm is
87f5a19343acf8ba697acc5a62bdb1a2b8c9eda3Lennart Poettering unsupported, try another
87f5a19343acf8ba697acc5a62bdb1a2b8c9eda3Lennart Poettering RRSIG/DNSKEY pair, but remember we
87f5a19343acf8ba697acc5a62bdb1a2b8c9eda3Lennart Poettering encountered this, so that we can
87f5a19343acf8ba697acc5a62bdb1a2b8c9eda3Lennart Poettering return a proper error when we
bda2c408f8a739c19161818bcc842107f60652a2Tom Gundersen encounter nothing better. */
87f5a19343acf8ba697acc5a62bdb1a2b8c9eda3Lennart Poettering /* If the signature is expired, try
87f5a19343acf8ba697acc5a62bdb1a2b8c9eda3Lennart Poettering another one, but remember it, so
87f5a19343acf8ba697acc5a62bdb1a2b8c9eda3Lennart Poettering that we can return this */
bda2c408f8a739c19161818bcc842107f60652a2Tom Gundersen assert_not_reached("Unexpected DNSSEC validation result");
87f5a19343acf8ba697acc5a62bdb1a2b8c9eda3Lennart Poetteringint dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
a5a807e63a50314e190e9166d8a453cd8dd258e3Zbigniew Jędrzejewski-Szmek /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poetteringint dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* Converts the specified hostname into DNSSEC canonicalized
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering r = dns_label_unescape(&n, buffer, buffer_max);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* DNSSEC validation is always done on the ASCII version of the label */
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* The DNSSEC canonical form is not clear on what to
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * do with dots appearing in labels, the way DNS-SD
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering * does it. Refuse it for now. */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering for (i = 0; i < (size_t) r; i ++) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (buffer[i] >= 'A' && buffer[i] <= 'Z')
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* Not even a single label: this is the root domain name */
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering return (int) c;
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poetteringstatic int digest_to_gcrypt_md(uint8_t algorithm) {
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poetteringint dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (dnskey->key->type != DNS_TYPE_DNSKEY)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen if (dnskey->dnskey.algorithm != ds->ds.algorithm)
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering if (dnssec_keytag(dnskey) != ds->ds.key_tag)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type);
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering hash_size = gcry_md_get_algo_dlen(md_algorithm);
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering md_add_uint8(md, dnskey->dnskey.protocol);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering md_add_uint8(md, dnskey->dnskey.algorithm);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0;
ec2c5e4398f9d65e5dfe61530f2556224733d1e6Lennart Poetteringint dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering if (dnskey->key->type != DNS_TYPE_DNSKEY)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (ds->key->class != dnskey->key->class)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key));
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poetteringint dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX];
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering algorithm = digest_to_gcrypt_md(nsec3->nsec3.algorithm);
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering hash_size = gcry_md_get_algo_dlen(algorithm);
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen if (nsec3->nsec3.next_hashed_name_size != hash_size)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering for (k = 0; k < nsec3->nsec3.iterations; k++) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poetteringstatic int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourceRecord *nsec3) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering const char *a, *b;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* If a second NSEC3 RR is specified, also check if they are from the same zone. */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (rr->nsec3.algorithm != nsec3->nsec3.algorithm)
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering if (rr->nsec3.iterations != nsec3->nsec3.iterations)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (rr->nsec3.salt_size != nsec3->nsec3.salt_size)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = dns_name_parent(&a); /* strip off hash */
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering r = dns_name_parent(&b); /* strip off hash */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poetteringstatic int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering _cleanup_free_ char *next_closer_domain = NULL, *l = NULL;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* First step, look for the longest common suffix we find with any NSEC3 RR in the response. */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering DNS_ANSWER_FOREACH_FLAGS(suffix_rr, flags, answer) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering _cleanup_free_ char *hashed_domain = NULL, *label = NULL;
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = nsec3_is_good(suffix_rr, flags, NULL);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_rr->key), 1, suffix);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* Strip one label from the front */
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering _cleanup_free_ char *hashed_domain = NULL, *label = NULL;
a2a416f768e2aa7db5b975cd50eb19237cac9cceLennart Poettering hashed_size = dnssec_nsec3_hash(suffix_rr, p, hashed);
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering label = base32hexmem(hashed, hashed_size, false);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering hashed_domain = strjoin(label, ".", suffix, NULL);
5cb36f41f01cf4b1f4395abfffd1b33116591e58Lennart Poettering DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering if (rr->nsec3.next_hashed_name_size != (size_t) hashed_size)
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain);
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering /* We didn't find the closest encloser with this name,
2c27fbca2d88214bd305272308a370a962818f1eLennart Poettering * but let's remember this domain name, it might be
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering * the next closer name */
2c27fbca2d88214bd305272308a370a962818f1eLennart Poettering /* Strip one label from the front */
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering /* We found a closest encloser in 'p'; next closer is 'pp' */
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering if (bitmap_isset(rr->nsec3.types, DNS_TYPE_DNAME))
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering /* Ensure that this data is from the delegated domain
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering * (i.e. originates from the "lower" DNS server), and isn't
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering * just glue records (i.e. doesn't originate from the "upper"
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering * DNS server). */
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering if (bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) &&
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering !bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA))
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering *result = bitmap_isset(rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
4e945a6f7971fd7d1f6b2c62ee3afdaff3c95ce4Lennart Poettering l = base32hexmem(hashed, hashed_size, false);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering next_closer_domain = strjoin(l, ".", p, NULL);
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering _cleanup_free_ char *label = NULL, *next_hashed_domain = NULL;
091a364c802e34a58f3260c9cb5db9b75c62215cTom Gundersen label = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false);
e1c959948c0e31d6997bcdfbabfbd077784b2baeLennart Poettering next_hashed_domain = strjoin(label, ".", p, NULL);
e1c959948c0e31d6997bcdfbabfbd077784b2baeLennart Poettering r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain);
e1c959948c0e31d6997bcdfbabfbd077784b2baeLennart Poettering *authenticated = a && (flags & DNS_ANSWER_AUTHENTICATED);
a407657425a3e47fd2b559cd3bc800f791303f63Lennart Poetteringint dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
a407657425a3e47fd2b559cd3bc800f791303f63Lennart Poettering /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
a407657425a3e47fd2b559cd3bc800f791303f63Lennart Poettering DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key));
a407657425a3e47fd2b559cd3bc800f791303f63Lennart Poettering *result = bitmap_isset(rr->nsec.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
623a4c97b9175f95c4b1c6fc34e36c56f1e4ddbfLennart Poettering *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name);
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering /* OK, this was not sufficient. Let's see if NSEC3 can help. */
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering return dnssec_test_nsec3(answer, key, result, authenticated);
bf3f1271e2cc0c22b11c8a805a997578dabe9191Lennart Poettering /* No approproate NSEC RR found, report this. */
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poetteringstatic const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart PoetteringDEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poetteringstatic const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired",
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm",
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
1716f6dcf54d4c181c2e2558e3d5414f54c8d9caLennart Poettering [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",