resolved-dns-dnssec.c revision d12bf2bdff8d616b7e59fc480c7e610003b494df
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/>.
3ffd4af22052963e7a29431721ee204e634bea75Lennart Poettering/* Open question:
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering * How does the DNSSEC canonical form of a hostname with a label
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * containing a dot look like, the way DNS-SD does it?
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek * - Iterative validation
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek * - NSEC proof of non-existance
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * - NSEC3 proof of non-existance
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * - Make trust anchor store read additional DS+DNSKEY data from disk
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek * - wildcard zones compatibility
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering * - multi-label zone compatibility
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * - DNSSEC cname/dname compatibility
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * - per-interface DNSSEC setting
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering * - DSA support
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering * - EC support?
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * The DNSSEC Chain of trust:
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * DS RRs are protected like normal RRs
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering * Example chain:
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poetteringstatic bool dnssec_algorithm_supported(int algorithm) {
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poetteringstatic bool dnssec_digest_supported(int digest) {
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poetteringuint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
6c03089c32c251d823173bda4d809a9e643219f0Lennart Poettering /* The algorithm from RFC 4034, Appendix B. */
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
d7bd3de0654669e65b9642c248c5fa6d1d9a9f61Lennart Poettering for (i = 0; i < dnskey->dnskey.key_size; i++)
cfeaa44a09756a93a881f786678973d9b1e382dbLennart Poettering sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
d4fffc4b8beb86e77fd710c1f43913a490ed083aZbigniew Jędrzejewski-Szmek sum += (sum >> 16) & UINT32_C(0xFFFF);
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poetteringstatic int rr_compare(const void *a, const void *b) {
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek /* Let's order the RRs according to RFC 4034, Section 6.3 */
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
7027ff61a34a12487712b382a061c654acc3a679Lennart Poettering r = memcmp((*x)->wire_format, (*y)->wire_format, m);
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering if ((*x)->wire_format_size < (*y)->wire_format_size)
374ec6abf31ada6ca554cc8ea99b282373fac010Lennart Poettering else if ((*x)->wire_format_size > (*y)->wire_format_size)
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering const void *signature, size_t signature_size,
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering const void *exponent, size_t exponent_size,
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering const void *modulus, size_t modulus_size) {
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering "(sig-val (rsa (s %m)))",
9444b1f20e311f073864d81e913bd4f32fe95cfdLennart Poettering "(data (flags pkcs1) (hash %s %b))",
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering "(public-key (rsa (n %m) (e %m)))",
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering else if (ge != 0) {
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poettering log_debug("RSA signature check failed: %s", gpg_strerror(ge));
de0671ee7fe465e108f62dcbbbe9366f81dd9e9aZbigniew Jędrzejewski-Szmek gcry_sexp_release(public_key_sexp);
aff38e74bd776471f15ba54b305a24b0251eb865Lennart Poetteringstatic void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poetteringstatic void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poetteringstatic void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poetteringstatic int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
ae018d9bc900d6355dea4af05119b49c67945184Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
a0ab566574303be1ca12cdb334f284cfd407caa5Lennart Poettering expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
a0ab566574303be1ca12cdb334f284cfd407caa5Lennart Poettering inception = rrsig->rrsig.inception * USEC_PER_SEC;
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov /* Permit a certain amount of clock skew of 10% of the valid
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov * time range. This takes inspiration from unbound's
185a08745957cbd32e8293daf8c51ab9c995a71eDimitri John Ledkov * resolver. */
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering return realtime < inception || realtime > expiration;
a016b9228f338cb9b380ce7e00826ef462767d98Lennart Poettering uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
c96cc5822c165e86be78ed96dac6573986032fabLennart Poettering size_t exponent_size, modulus_size, hash_size;
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering assert(rrsig->key->type == DNS_TYPE_RRSIG);
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering assert(dnskey->key->type == DNS_TYPE_DNSKEY);
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering /* Verifies the the RRSet matching the specified "key" in "a",
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering * using the signature "rrsig" and the key "dnskey". It's
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering * assumed the RRSIG and DNSKEY match. */
751bc6ac79320bc16e63e8c1bbb713c30a3b7bc9Lennart Poettering if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm))
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering r = dnssec_rrsig_expired(rrsig, realtime);
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering /* Collect all relevant RRs in a single array, so that we can look at the RRset */
5f4c5fef66581383ee852b301db67f687663004cLennart Poettering list = newa(DnsResourceRecord *, a->n_rrs);
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek r = dns_resource_key_equal(key, rr->key);
8b0849e9710d721c5d0b775aaf0fd662eefa1449Lennart Poettering /* We need the wire format for ordering, and digest calculation */
329ac4bc5429cd86c4ac76b13e7e2784f3982760Lennart Poettering r = dns_resource_record_to_wire_format(rr, true);
96cde13ace6406582688028f3df5668a172ba628Zbigniew Jędrzejewski-Szmek /* Bring the RRs into canonical order */
case DNSSEC_ALGORITHM_RSASHA1:
if (!md)
return -EIO;
goto finish;
size_t l;
r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true);
goto finish;
if (!hash) {
r = -EIO;
goto finish;
r = -EINVAL;
goto finish;
r = -EINVAL;
goto finish;
if (exponent_size <= 0) {
r = -EINVAL;
goto finish;
r = -EINVAL;
goto finish;
r = dnssec_rsa_verify(
goto finish;
return -EINVAL;
DnsAnswer *a,
if (!a || a->n_rrs <= 0)
return -ENODATA;
found_rrsig = true;
found_dnskey = true;
if (r < 0 && r != EOPNOTSUPP)
if (r == DNSSEC_VERIFIED)
return DNSSEC_VERIFIED;
if (found_dnskey)
return DNSSEC_INVALID;
if (found_rrsig)
return DNSSEC_MISSING_KEY;
return DNSSEC_NO_SIGNATURE;
size_t c = 0;
return -ENOBUFS;
size_t i;
return -ENOBUFS;
return -EINVAL;
for (i = 0; i < (size_t) r; i ++) {
void *result;
return -EINVAL;
return -EINVAL;
return -EKEYREJECTED;
return -EKEYREJECTED;
return -EOPNOTSUPP;
return -EOPNOTSUPP;
case DNSSEC_DIGEST_SHA1:
case DNSSEC_DIGEST_SHA256:
if (!md)
return -EIO;
goto finish;
if (!result) {
r = -EIO;
goto finish;