resolved-dns-dnssec.c revision 22ebb9e4a9d8802b194eccc050552d0e188af489
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering This file is part of systemd.
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering Copyright 2015 Lennart Poettering
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering systemd is free software; you can redistribute it and/or modify it
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering under the terms of the GNU Lesser General Public License as published by
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering (at your option) any later version.
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering systemd is distributed in the hope that it will be useful, but
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering Lesser General Public License for more details.
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering You should have received a copy of the GNU Lesser General Public License
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering/* Open question:
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering * How does the DNSSEC canonical form of a hostname with a label
1ca208fb4f93e5869704af1812cbff7130a2fc03Zbigniew Jędrzejewski-Szmek * containing a dot look like, the way DNS-SD does it?
e48fdd84432bbf9c2ecc339183258c7c33116032Lennart Poettering * - Iterative validation
e48fdd84432bbf9c2ecc339183258c7c33116032Lennart Poettering * - NSEC proof of non-existance
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering * - NSEC3 proof of non-existance
73b80ec2d999c45ce13f3e034704249d80829f7eLennart Poettering * - Make trust anchor store read additional DS+DNSKEY data from disk
1b9e5b126359a2a2ec37de1f94f046093abc74b8Lennart Poettering * - wildcard zones compatibility
f9ac15442e4132f00eca5495d53c17062aae13e0Lennart Poettering * - multi-label zone compatibility
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering * - DMSSEC cname/dname compatibility
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering * - per-interface DNSSEC setting
73b80ec2d999c45ce13f3e034704249d80829f7eLennart Poettering * - DSA support
73b80ec2d999c45ce13f3e034704249d80829f7eLennart Poettering * - EC support?
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering * The DNSSEC Chain of trust:
7410616cd9dbbec97cf98d75324da5cda2b2f7a2Lennart Poettering * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
7410616cd9dbbec97cf98d75324da5cda2b2f7a2Lennart Poettering * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
7410616cd9dbbec97cf98d75324da5cda2b2f7a2Lennart Poettering * DS RRs are protected like normal RRs
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering * Example chain:
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
7410616cd9dbbec97cf98d75324da5cda2b2f7a2Lennart Poetteringstatic bool dnssec_algorithm_supported(int algorithm) {
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poetteringstatic bool dnssec_digest_supported(int digest) {
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poetteringuint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering /* The algorithm from RFC 4034, Appendix B. */
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering for (i = 0; i < dnskey->dnskey.key_size; i++)
cca1dfddd4ce4357113663532696488427cc54e4Lennart Poettering sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
63c372cb9df3bee01e3bf8cd7f96f336bddda846Lennart Poetteringstatic int rr_compare(const void *a, const void *b) {
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering /* Let's order the RRs according to RFC 4034, Section 6.3 */
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
4a62c710b62a5a3c7a8a278b810b9d5b5a0c8f4fMichal Schmidt r = memcmp((*x)->wire_format, (*y)->wire_format, m);
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering if ((*x)->wire_format_size < (*y)->wire_format_size)
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering else if ((*x)->wire_format_size > (*y)->wire_format_size)
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering const void *signature, size_t signature_size,
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering const void *exponent, size_t exponent_size,
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering const void *modulus, size_t modulus_size) {
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
73b80ec2d999c45ce13f3e034704249d80829f7eLennart Poettering ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
e48fdd84432bbf9c2ecc339183258c7c33116032Lennart Poettering "(sig-val (rsa (s %m)))",
1af7211984a8dba3c5ba40fae794c4c55f5e6bd3Lennart Poettering "(data (flags pkcs1) (hash %s %b))",
7410616cd9dbbec97cf98d75324da5cda2b2f7a2Lennart Poettering "(public-key (rsa (n %m) (e %m)))",
4a62c710b62a5a3c7a8a278b810b9d5b5a0c8f4fMichal Schmidt ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering else if (ge != 0)
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sieversstatic void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poetteringstatic void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
73b80ec2d999c45ce13f3e034704249d80829f7eLennart Poetteringstatic void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
73b80ec2d999c45ce13f3e034704249d80829f7eLennart Poetteringstatic int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers inception = rrsig->rrsig.inception * USEC_PER_SEC;
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers /* Permit a certain amount of clock skew of 10% of the valid
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers * time range. This takes inspiration from unbound's
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers * resolver. */
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering return realtime < inception || realtime > expiration;
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering size_t exponent_size, modulus_size, hash_size;
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering /* Verifies the the RRSet matching the specified "key" in "a",
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering * using the signature "rrsig" and the key "dnskey". It's
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering * assumed the RRSIG and DNSKEY match. */
56f64d95763a799ba4475daf44d8e9f72a1bd474Michal Schmidt if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm))
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering r = dnssec_rrsig_expired(rrsig, realtime);
a0b1209c4a59754f428894e0485413542da50014Zbigniew Jędrzejewski-Szmek /* Collect all relevant RRs in a single array, so that we can look at the RRset */
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering list = newa(DnsResourceRecord *, a->n_rrs);
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering r = dns_resource_key_equal(key, rr->key);
cca1dfddd4ce4357113663532696488427cc54e4Lennart Poettering /* We need the wire format for ordering, and digest calculation */
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers r = dns_resource_record_to_wire_format(rr, true);
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers /* Bring the RRs into canonical order */
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers qsort_safe(list, n, sizeof(DnsResourceRecord), rr_compare);
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers /* OK, the RRs are now in canonical order. Let's calculate the digest */
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen for (k = 0; k < n; k++) {
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true);
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen l = rr->wire_format_size - rr->wire_format_rdata_offset;
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen /* exponent is > 255 bytes long */
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen if (3 + exponent_size >= dnskey->dnskey.key_size) {
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen /* exponent is <= 255 bytes long */
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen if (1 + exponent_size >= dnskey->dnskey.key_size) {
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen rrsig->rrsig.signature, rrsig->rrsig.signature_size,
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sieversint dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers /* Checks if the specified DNSKEY RR matches the key used for
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers * the signature in the specified RRSIG RR */
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag)
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(rrsig->key));
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sieversint dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) {
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers /* Verifies all RRs from "a" that match the key "key", against DNSKEY RRs in "validated_dnskeys" */
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers if (!a || a->n_rrs <= 0)
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers /* Iterate through each RRSIG RR. */
7a1494aa4e4a131d73e866cf1e7eb7b6e47dbab8Tom Gundersen /* Take the time here, if it isn't set yet, so
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers * that we do all validations with the same
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering /* Yay, we found a matching RRSIG with a matching
1ca208fb4f93e5869704af1812cbff7130a2fc03Zbigniew Jędrzejewski-Szmek * DNSKEY, awesome. Now let's verify all entries of
1ca208fb4f93e5869704af1812cbff7130a2fc03Zbigniew Jędrzejewski-Szmek * the RRSet against the RRSIG and DNSKEY
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering * combination. */
59512f21d77d984cf1363fb0d1770218c5e17020Kay Sievers r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime);
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering if (r < 0 && r != EOPNOTSUPP)
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering /* If the signature is invalid, or done using
73b80ec2d999c45ce13f3e034704249d80829f7eLennart Poettering an unsupported algorithm, let's try another
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering key and/or signature. After all they
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering key_tags and stuff are not unique, and
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering might be shared by multiple keys. */
d2a623823f8d83c97c35fcd28f90e8cd59066f8aZbigniew Jędrzejewski-Szmekint dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering /* Converts the specified hostname into DNSSEC canonicalized
d2a623823f8d83c97c35fcd28f90e8cd59066f8aZbigniew Jędrzejewski-Szmek r = dns_label_unescape(&n, buffer, buffer_max);
fa041593fe04b12ffd7e81d8b3598a7a6f313fb3Lennart Poettering /* DNSSEC validation is always done on the ASCII version of the label */
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering /* The DNSSEC canonical form is not clear on what to
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering * do with dots appearing in labels, the way DNS-SD
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering * does it. Refuse it for now. */
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering for (i = 0; i < (size_t) r; i ++) {
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering if (buffer[i] >= 'A' && buffer[i] <= 'Z')
458a2f85e8ae08c534bf8d030fbeeb791893422bTom Gundersen if (c <= 0) {
458a2f85e8ae08c534bf8d030fbeeb791893422bTom Gundersen /* Not even a single label: this is the root domain name */
fa041593fe04b12ffd7e81d8b3598a7a6f313fb3Lennart Poetteringint dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
d2a623823f8d83c97c35fcd28f90e8cd59066f8aZbigniew Jędrzejewski-Szmek if (dnskey->key->type != DNS_TYPE_DNSKEY)
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
1ca208fb4f93e5869704af1812cbff7130a2fc03Zbigniew Jędrzejewski-Szmek if (!dnssec_algorithm_supported(dnskey->dnskey.algorithm))
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering if (!dnssec_digest_supported(ds->ds.digest_type))
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering if (dnskey->dnskey.algorithm != ds->ds.algorithm)
1ca208fb4f93e5869704af1812cbff7130a2fc03Zbigniew Jędrzejewski-Szmek gcry_md_open(&md, GCRY_MD_SHA256, 0);
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering md_add_uint8(md, dnskey->dnskey.protocol);
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering md_add_uint8(md, dnskey->dnskey.algorithm);
61331eab0a53cd9b8446eab6d1ebf1a046d8efc1Lennart Poettering gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);
1a14a53cfded6e78c6e8dfb73fdff0039971d642Lennart Poettering r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0;
73b80ec2d999c45ce13f3e034704249d80829f7eLennart Poetteringstatic const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {