resolved-dns-dnssec.c revision 72667f0890372a952a7c5b8cc498ec3cf9440973
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <gcrypt.h>
#include "alloc-util.h"
#include "dns-domain.h"
#include "hexdecoct.h"
#include "resolved-dns-dnssec.h"
#include "resolved-dns-packet.h"
#include "string-table.h"
/* Open question:
*
* How does the DNSSEC canonical form of a hostname with a label
* containing a dot look like, the way DNS-SD does it?
*
* TODO:
*
* - Iterative validation
* - NSEC proof of non-existance
* - NSEC3 proof of non-existance
* - Make trust anchor store read additional DS+DNSKEY data from disk
* - wildcard zones compatibility
* - multi-label zone compatibility
* - per-interface DNSSEC setting
* - DSA support
* - EC support?
*
* */
#define VERIFY_RRS_MAX 256
/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
/*
* The DNSSEC Chain of trust:
*
* Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
* DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
* DS RRs are protected like normal RRs
*
* Example chain:
*/
static void initialize_libgcrypt(void) {
const char *p;
return;
p = gcry_check_version("1.4.5");
assert(p);
}
static bool dnssec_algorithm_supported(int algorithm) {
}
const uint8_t *p;
size_t i;
/* The algorithm from RFC 4034, Appendix B. */
}
static int rr_compare(const void *a, const void *b) {
size_t m;
int r;
/* Let's order the RRs according to RFC 4034, Section 6.3 */
assert(x);
assert(*x);
assert((*x)->wire_format);
assert(y);
assert(*y);
assert((*y)->wire_format);
if (r != 0)
return r;
if ((*x)->wire_format_size < (*y)->wire_format_size)
return -1;
else if ((*x)->wire_format_size > (*y)->wire_format_size)
return 1;
return 0;
}
static int dnssec_rsa_verify(
const char *hash_algorithm,
int r;
if (ge != 0) {
r = -EIO;
goto finish;
}
if (ge != 0) {
r = -EIO;
goto finish;
}
if (ge != 0) {
r = -EIO;
goto finish;
}
NULL,
"(sig-val (rsa (s %m)))",
s);
if (ge != 0) {
r = -EIO;
goto finish;
}
NULL,
"(data (flags pkcs1) (hash %s %b))",
(int) data_size,
data);
if (ge != 0) {
r = -EIO;
goto finish;
}
NULL,
"(public-key (rsa (n %m) (e %m)))",
n,
e);
if (ge != 0) {
r = -EIO;
goto finish;
}
r = 0;
else if (ge != 0) {
r = -EIO;
} else
r = 1;
if (e)
gcry_mpi_release(e);
if (n)
gcry_mpi_release(n);
if (s)
gcry_mpi_release(s);
if (public_key_sexp)
if (signature_sexp)
if (data_sexp)
return r;
}
gcry_md_write(md, &v, sizeof(v));
}
v = htobe16(v);
gcry_md_write(md, &v, sizeof(v));
}
v = htobe32(v);
gcry_md_write(md, &v, sizeof(v));
}
if (realtime == USEC_INFINITY)
if (inception > expiration)
return -EKEYREJECTED;
/* Permit a certain amount of clock skew of 10% of the valid
* time range. This takes inspiration from unbound's
* resolver. */
inception = 0;
else
else
expiration += skew;
}
int dnssec_verify_rrset(
DnsAnswer *a,
DnssecResult *result) {
size_t k, n = 0;
int r;
/* Verifies the the RRSet matching the specified "key" in "a",
* using the signature "rrsig" and the key "dnskey". It's
* assumed the RRSIG and DNSKEY match. */
return 0;
}
if (a->n_rrs > VERIFY_RRS_MAX)
return -E2BIG;
if (r < 0)
return r;
if (r > 0) {
return 0;
}
/* Collect all relevant RRs in a single array, so that we can look at the RRset */
DNS_ANSWER_FOREACH(rr, a) {
if (r < 0)
return r;
if (r == 0)
continue;
/* We need the wire format for ordering, and digest calculation */
r = dns_resource_record_to_wire_format(rr, true);
if (r < 0)
return r;
}
if (n <= 0)
return -ENODATA;
/* Bring the RRs into canonical order */
/* OK, the RRs are now in canonical order. Let's calculate the digest */
case DNSSEC_ALGORITHM_RSASHA1:
hash_size = 20;
break;
hash_size = 32;
break;
hash_size = 64;
break;
default:
assert_not_reached("Unknown digest");
}
if (!md)
return -EIO;
if (r < 0)
goto finish;
for (k = 0; k < n; k++) {
size_t l;
r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true);
if (r < 0)
goto finish;
assert(l <= 0xFFFF);
}
if (!hash) {
r = -EIO;
goto finish;
}
/* exponent is > 255 bytes long */
if (exponent_size < 256) {
r = -EINVAL;
goto finish;
}
r = -EINVAL;
goto finish;
}
} else {
/* exponent is <= 255 bytes long */
if (exponent_size <= 0) {
r = -EINVAL;
goto finish;
}
r = -EINVAL;
goto finish;
}
}
r = dnssec_rsa_verify(
if (r < 0)
goto finish;
r = 0;
return r;
}
/* Checks if the specified DNSKEY RR matches the key used for
* the signature in the specified RRSIG RR */
return -EINVAL;
return 0;
return 0;
return 0;
return 0;
return 0;
return 0;
}
/* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
return 0;
return 0;
return 0;
}
DnsAnswer *a,
DnssecResult *result) {
bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
int r;
/* Verifies all RRs from "a" that match the key "key", against DNSKEY and DS RRs in "validated_dnskeys" */
if (!a || a->n_rrs <= 0)
return -ENODATA;
/* Iterate through each RRSIG RR. */
DNS_ANSWER_FOREACH(rrsig, a) {
/* Is this an RRSIG RR that applies to RRs matching our key? */
if (r < 0)
return r;
if (r == 0)
continue;
found_rrsig = true;
/* Look for a matching key */
/* Is this a DNSKEY RR that matches they key of our RRSIG? */
if (r < 0)
return r;
if (r == 0)
continue;
/* Take the time here, if it isn't set yet, so
* that we do all validations with the same
* time. */
if (realtime == USEC_INFINITY)
/* Yay, we found a matching RRSIG with a matching
* DNSKEY, awesome. Now let's verify all entries of
* the RRSet against the RRSIG and DNSKEY
* combination. */
if (r < 0)
return r;
switch (one_result) {
case DNSSEC_VALIDATED:
/* Yay, the RR has been validated,
* return immediately. */
return 0;
case DNSSEC_INVALID:
/* If the signature is invalid, let's try another
key_tags and stuff are not unique, and
might be shared by multiple keys. */
found_invalid = true;
continue;
/* If the key algorithm is
unsupported, try another
encountered this, so that we can
return a proper error when we
encounter nothing better. */
found_unsupported_algorithm = true;
continue;
case DNSSEC_SIGNATURE_EXPIRED:
/* If the signature is expired, try
another one, but remember it, so
that we can return this */
found_expired_rrsig = true;
continue;
default:
assert_not_reached("Unexpected DNSSEC validation result");
}
}
}
if (found_expired_rrsig)
else if (found_unsupported_algorithm)
else if (found_invalid)
*result = DNSSEC_INVALID;
else if (found_rrsig)
else
return 0;
}
size_t c = 0;
int r;
/* Converts the specified hostname into DNSSEC canonicalized
* form. */
if (buffer_max < 2)
return -ENOBUFS;
for (;;) {
size_t i;
if (r < 0)
return r;
if (r == 0)
break;
if (r > 0) {
int k;
/* DNSSEC validation is always done on the ASCII version of the label */
if (k < 0)
return k;
if (k > 0)
r = k;
}
return -ENOBUFS;
/* The DNSSEC canonical form is not clear on what to
* do with dots appearing in labels, the way DNS-SD
* does it. Refuse it for now. */
return -EINVAL;
for (i = 0; i < (size_t) r; i ++) {
}
buffer[r] = '.';
buffer += r + 1;
c += r + 1;
buffer_max -= r + 1;
}
if (c <= 0) {
/* Not even a single label: this is the root domain name */
buffer[0] = '.';
buffer[1] = 0;
return 1;
}
return (int) c;
}
/* Translates a DNSSEC digest algorithm into a gcrypt digest iedntifier */
switch (algorithm) {
case DNSSEC_DIGEST_SHA1:
return GCRY_MD_SHA1;
case DNSSEC_DIGEST_SHA256:
return GCRY_MD_SHA256;
default:
return -EOPNOTSUPP;
}
}
int algorithm;
void *result;
int r;
/* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
return -EINVAL;
return -EINVAL;
return -EKEYREJECTED;
return -EKEYREJECTED;
return 0;
return 0;
if (algorithm < 0)
return algorithm;
return 0;
if (r < 0)
return r;
if (!md)
return -EIO;
if (!result) {
r = -EIO;
goto finish;
}
return r;
}
int r;
return 0;
continue;
if (r < 0)
return r;
if (r > 0)
return 1;
}
return 0;
}
int algorithm;
void *result;
unsigned k;
int r;
return -EINVAL;
if (algorithm < 0)
return algorithm;
return -EINVAL;
if (r < 0)
return r;
if (!md)
return -EIO;
if (!result) {
r = -EIO;
goto finish;
}
if (!result) {
r = -EIO;
goto finish;
}
}
r = (int) hash_size;
return r;
}
int r;
continue;
case DNS_TYPE_NSEC:
if (r < 0)
return r;
if (r > 0) {
return 0;
}
r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name);
if (r < 0)
return r;
if (r > 0) {
return 0;
}
break;
case DNS_TYPE_NSEC3: {
char label[DNS_LABEL_MAX];
int label_length, c, q;
const char *p;
bool covered;
/* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
continue;
if (label_length < 0)
return label_length;
if (label_length == 0)
continue;
if (r < 0)
return r;
if (r == 0)
continue;
if (r == -EINVAL)
continue;
if (r < 0)
return r;
continue;
if (c == 0)
continue;
/* RFC 5155, Section 8.1 says we MUST ignore NSEC3 RRs with unknown algorithms */
if (r == -EOPNOTSUPP)
continue;
if (r < 0)
return r;
if ((size_t) r != decoded_size)
continue;
if (r == 0) {
return 0;
}
covered = c < 0 ?
r < 0 && q < 0 :
q < 0 || r < 0;
if (covered) {
return 0;
}
break;
}
default:
break;
}
}
/* No approproate NSEC RR found, report this. */
return 0;
}
static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
[DNSSEC_NO] = "no",
[DNSSEC_TRUST] = "trust",
[DNSSEC_YES] = "yes",
};
static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_VALIDATED] = "validated",
[DNSSEC_INVALID] = "invalid",
[DNSSEC_SIGNATURE_EXPIRED] = "signature-expired",
[DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm",
[DNSSEC_NO_SIGNATURE] = "no-signature",
[DNSSEC_MISSING_KEY] = "missing-key",
[DNSSEC_UNSIGNED] = "unsigned",
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
[DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
};