Merge pull request #2087 from poettering/dnssec

Basic DNSSEC support, and unrelated fixes
This commit is contained in:
Tom Gundersen
2015-12-03 01:52:01 +01:00
19 changed files with 1242 additions and 154 deletions
+30 -6
View File
@@ -5136,12 +5136,13 @@ polkitpolicy_in_files += \
src/import/org.freedesktop.import1.policy.in
EXTRA_DIST += \
units/systemd-importd.service.in \
src/resolve/resolved.conf.in
units/systemd-importd.service.in
# ------------------------------------------------------------------------------
if ENABLE_RESOLVED
if HAVE_GCRYPT
systemd_resolved_SOURCES = \
src/resolve/resolved.c \
src/resolve/resolved-manager.c \
@@ -5181,6 +5182,8 @@ systemd_resolved_SOURCES = \
src/resolve/resolved-dns-zone.c \
src/resolve/resolved-dns-stream.h \
src/resolve/resolved-dns-stream.c \
src/resolve/resolved-dns-dnssec.h \
src/resolve/resolved-dns-dnssec.c \
src/resolve/dns-type.c \
src/resolve/dns-type.h
@@ -5226,9 +5229,6 @@ GENERAL_ALIASES += \
nodist_pkgsysconf_DATA += \
src/resolve/resolved.conf
tests += \
test-dns-domain
libnss_resolve_la_SOURCES = \
src/nss-resolve/nss-resolve.sym \
src/nss-resolve/nss-resolve.c
@@ -5272,10 +5272,34 @@ systemd_resolve_host_LDADD = \
rootlibexec_PROGRAMS += \
systemd-resolve-host
tests += \
test-dns-domain \
test-dnssec
test_dnssec_SOURCES = \
src/resolve/test-dnssec.c \
src/resolve/resolved-dns-packet.c \
src/resolve/resolved-dns-packet.h \
src/resolve/resolved-dns-rr.c \
src/resolve/resolved-dns-rr.h \
src/resolve/resolved-dns-answer.c \
src/resolve/resolved-dns-answer.h \
src/resolve/resolved-dns-question.c \
src/resolve/resolved-dns-question.h \
src/resolve/resolved-dns-dnssec.c \
src/resolve/resolved-dns-dnssec.h \
src/resolve/dns-type.c \
src/resolve/dns-type.h
test_dnssec_LDADD = \
libshared.la
endif
endif
EXTRA_DIST += \
units/systemd-resolved.service.m4.in
units/systemd-resolved.service.m4.in \
src/resolve/resolved.conf.in
# ------------------------------------------------------------------------------
if ENABLE_NETWORKD
+6 -2
View File
@@ -212,10 +212,10 @@ try_dmi:
unreliable enough, so let's not do any additional guesswork
on top of that.
See the SMBIOS Specification 2.7.1 section 7.4.1 for
See the SMBIOS Specification 4.0 section 7.4.1 for
details about the values listed here:
http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
*/
switch (t) {
@@ -237,7 +237,11 @@ try_dmi:
case 0x11:
case 0x1C:
case 0x1D:
return "server";
case 0x1E:
return "tablet";
}
return NULL;
+1 -1
View File
@@ -554,7 +554,7 @@ static int client_append_fqdn_option(DHCPMessage *message, size_t optlen, size_t
buffer[1] = 0; /* RCODE1 (deprecated) */
buffer[2] = 0; /* RCODE2 (deprecated) */
r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3);
r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false);
if (r > 0)
r = dhcp_option_append(message, optlen, optoffset, 0,
DHCP_OPTION_FQDN, 3 + r, buffer);
+2 -10
View File
@@ -425,8 +425,6 @@ fail:
}
static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
size_t start;
int r;
assert(m);
@@ -443,17 +441,11 @@ static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int i
if (r < 0)
return r;
r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
r = dns_resource_record_to_wire_format(rr, false);
if (r < 0)
return r;
p->refuse_compression = true;
r = dns_packet_append_rr(p, rr, &start);
if (r < 0)
return r;
r = sd_bus_message_append_array(m, 'y', DNS_PACKET_DATA(p) + start, p->size - start);
r = sd_bus_message_append_array(m, 'y', rr->wire_format, rr->wire_format_size);
if (r < 0)
return r;
+15 -10
View File
@@ -24,6 +24,7 @@
typedef struct DnsAnswer DnsAnswer;
typedef struct DnsAnswerItem DnsAnswerItem;
#include "macro.h"
#include "resolved-dns-rr.h"
/* A simple array of resource records. We keep track of the
@@ -59,19 +60,23 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref);
#define DNS_ANSWER_FOREACH(kk, a) \
for (unsigned _i = ({ \
#define _DNS_ANSWER_FOREACH(q, kk, a) \
for (unsigned UNIQ_T(i, q) = ({ \
(kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \
0; \
}); \
(a) && ((_i) < (a)->n_rrs); \
_i++, (kk) = (_i < (a)->n_rrs ? (a)->items[_i].rr : NULL))
}); \
(a) && (UNIQ_T(i, q) < (a)->n_rrs); \
UNIQ_T(i, q)++, (kk) = (UNIQ_T(i, q) < (a)->n_rrs ? (a)->items[UNIQ_T(i, q)].rr : NULL))
#define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) \
for (unsigned _i = ({ \
#define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a)
#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifindex, a) \
for (unsigned UNIQ_T(i, q) = ({ \
(kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \
(ifindex) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \
0; \
}); \
(a) && ((_i) < (a)->n_rrs); \
_i++, (kk) = ((_i < (a)->n_rrs) ? (a)->items[_i].rr : NULL), (ifindex) = ((_i < (a)->n_rrs) ? (a)->items[_i].ifindex : 0))
}); \
(a) && (UNIQ_T(i, q) < (a)->n_rrs); \
UNIQ_T(i, q)++, (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), (ifindex) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0))
#define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) _DNS_ANSWER_FOREACH_IFINDEX(UNIQ, kk, ifindex, a)
+35 -10
View File
@@ -521,8 +521,8 @@ fail:
return r;
}
static DnsCacheItem *dns_cache_get_by_key_follow_cname(DnsCache *c, DnsResourceKey *k) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *cname_key = NULL;
static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *nsec_key = NULL, *cname_key = NULL;
DnsCacheItem *i;
const char *n;
int r;
@@ -534,20 +534,29 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname(DnsCache *c, DnsResourceK
* much, after all this is just a cache */
i = hashmap_get(c->by_key, k);
if (i || k->type == DNS_TYPE_CNAME || k->type == DNS_TYPE_DNAME)
if (i || IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME, DNS_TYPE_NSEC))
return i;
n = DNS_RESOURCE_KEY_NAME(k);
/* Check if we have an NSEC record instead for the name. */
nsec_key = dns_resource_key_new(k->class, DNS_TYPE_NSEC, n);
if (!nsec_key)
return NULL;
i = hashmap_get(c->by_key, nsec_key);
if (i)
return i;
/* Check if we have a CNAME record instead */
cname_key = dns_resource_key_new_cname(k);
if (!cname_key)
return NULL;
i = hashmap_get(c->by_key, cname_key);
if (i)
return i;
/* OK, let's look for cached DNAME records. */
n = DNS_RESOURCE_KEY_NAME(k);
for (;;) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *dname_key = NULL;
char label[DNS_LABEL_MAX];
@@ -578,6 +587,7 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
int r;
bool nxdomain = false;
_cleanup_free_ char *key_str = NULL;
DnsResourceRecord *nsec = NULL;
DnsCacheItem *j, *first;
assert(c);
@@ -601,7 +611,7 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
return 0;
}
first = dns_cache_get_by_key_follow_cname(c, key);
first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key);
if (!first) {
/* If one question cannot be answered we need to refresh */
@@ -617,9 +627,11 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
}
LIST_FOREACH(by_key, j, first) {
if (j->rr)
if (j->rr) {
if (j->rr->key->type == DNS_TYPE_NSEC)
nsec = j->rr;
n++;
else if (j->type == DNS_CACHE_NXDOMAIN)
} else if (j->type == DNS_CACHE_NXDOMAIN)
nxdomain = true;
}
@@ -627,9 +639,22 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
if (r < 0)
return r;
if (nsec && key->type != DNS_TYPE_NSEC) {
log_debug("NSEC NODATA cache hit for %s", key_str);
/* We only found an NSEC record that matches our name.
* If it says the type doesn't exit report
* NODATA. Otherwise report a cache miss. */
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
return !bitmap_isset(nsec->nsec.types, key->type);
}
log_debug("%s cache hit for %s",
nxdomain ? "NXDOMAIN" :
n > 0 ? "Positive" : "NODATA",
n > 0 ? "Positive" :
nxdomain ? "NXDOMAIN" : "NODATA",
key_str);
if (n <= 0) {
-1
View File
@@ -21,7 +21,6 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "hashmap.h"
#include "list.h"
#include "prioq.h"
+692
View File
@@ -0,0 +1,692 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
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 "resolved-dns-dnssec.h"
#include "resolved-dns-packet.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?
*
* */
#define VERIFY_RRS_MAX 256
#define MAX_KEY_SIZE (32*1024)
/*
* 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:
* Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
*/
static bool dnssec_algorithm_supported(int algorithm) {
return IN_SET(algorithm,
DNSSEC_ALGORITHM_RSASHA1,
DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
DNSSEC_ALGORITHM_RSASHA256,
DNSSEC_ALGORITHM_RSASHA512);
}
static bool dnssec_digest_supported(int digest) {
return IN_SET(digest,
DNSSEC_DIGEST_SHA1,
DNSSEC_DIGEST_SHA256);
}
uint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
const uint8_t *p;
uint32_t sum;
size_t i;
/* The algorithm from RFC 4034, Appendix B. */
assert(dnskey);
assert(dnskey->key->type == DNS_TYPE_DNSKEY);
sum = (uint32_t) dnskey->dnskey.flags +
((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
p = dnskey->dnskey.key;
for (i = 0; i < dnskey->dnskey.key_size; i++)
sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
sum += (sum >> 16) & UINT32_C(0xFFFF);
return sum & UINT32_C(0xFFFF);
}
static int rr_compare(const void *a, const void *b) {
DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) 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);
m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
r = memcmp((*x)->wire_format, (*y)->wire_format, m);
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,
const void *signature, size_t signature_size,
const void *data, size_t data_size,
const void *exponent, size_t exponent_size,
const void *modulus, size_t modulus_size) {
gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
gcry_mpi_t n = NULL, e = NULL, s = NULL;
gcry_error_t ge;
int r;
assert(hash_algorithm);
ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
if (ge != 0) {
r = -EIO;
goto finish;
}
ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
if (ge != 0) {
r = -EIO;
goto finish;
}
ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
if (ge != 0) {
r = -EIO;
goto finish;
}
ge = gcry_sexp_build(&signature_sexp,
NULL,
"(sig-val (rsa (s %m)))",
s);
if (ge != 0) {
r = -EIO;
goto finish;
}
ge = gcry_sexp_build(&data_sexp,
NULL,
"(data (flags pkcs1) (hash %s %b))",
hash_algorithm,
(int) data_size,
data);
if (ge != 0) {
r = -EIO;
goto finish;
}
ge = gcry_sexp_build(&public_key_sexp,
NULL,
"(public-key (rsa (n %m) (e %m)))",
n,
e);
if (ge != 0) {
r = -EIO;
goto finish;
}
ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
if (ge == GPG_ERR_BAD_SIGNATURE)
r = 0;
else if (ge != 0)
r = -EIO;
else
r = 1;
finish:
if (e)
gcry_mpi_release(e);
if (n)
gcry_mpi_release(n);
if (s)
gcry_mpi_release(s);
if (public_key_sexp)
gcry_sexp_release(public_key_sexp);
if (signature_sexp)
gcry_sexp_release(signature_sexp);
if (data_sexp)
gcry_sexp_release(data_sexp);
return r;
}
static void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
gcry_md_write(md, &v, sizeof(v));
}
static void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
v = htobe16(v);
gcry_md_write(md, &v, sizeof(v));
}
static void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
v = htobe32(v);
gcry_md_write(md, &v, sizeof(v));
}
static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
usec_t expiration, inception, skew;
assert(rrsig);
assert(rrsig->key->type == DNS_TYPE_RRSIG);
if (realtime == USEC_INFINITY)
realtime = now(CLOCK_REALTIME);
expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
inception = rrsig->rrsig.inception * USEC_PER_SEC;
if (inception > expiration)
return -EINVAL;
/* Permit a certain amount of clock skew of 10% of the valid time range */
skew = (expiration - inception) / 10;
if (inception < skew)
inception = 0;
else
inception -= skew;
if (expiration + skew < expiration)
expiration = USEC_INFINITY;
else
expiration += skew;
return realtime < inception || realtime > expiration;
}
int dnssec_verify_rrset(
DnsAnswer *a,
DnsResourceKey *key,
DnsResourceRecord *rrsig,
DnsResourceRecord *dnskey,
usec_t realtime) {
uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
size_t exponent_size, modulus_size, hash_size;
void *exponent, *modulus, *hash;
DnsResourceRecord **list, *rr;
gcry_md_hd_t md = NULL;
size_t k, n = 0;
int r;
assert(key);
assert(rrsig);
assert(dnskey);
assert(rrsig->key->type == DNS_TYPE_RRSIG);
assert(dnskey->key->type == DNS_TYPE_DNSKEY);
/* 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. */
if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm))
return -EOPNOTSUPP;
if (a->n_rrs > VERIFY_RRS_MAX)
return -E2BIG;
r = dnssec_rrsig_expired(rrsig, realtime);
if (r < 0)
return r;
if (r > 0)
return DNSSEC_SIGNATURE_EXPIRED;
/* Collect all relevant RRs in a single array, so that we can look at the RRset */
list = newa(DnsResourceRecord *, a->n_rrs);
DNS_ANSWER_FOREACH(rr, a) {
r = dns_resource_key_equal(key, rr->key);
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;
list[n++] = rr;
}
if (n <= 0)
return -ENODATA;
/* Bring the RRs into canonical order */
qsort_safe(list, n, sizeof(DnsResourceRecord), rr_compare);
/* OK, the RRs are now in canonical order. Let's calculate the digest */
switch (rrsig->rrsig.algorithm) {
case DNSSEC_ALGORITHM_RSASHA1:
case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
gcry_md_open(&md, GCRY_MD_SHA1, 0);
hash_size = 20;
break;
case DNSSEC_ALGORITHM_RSASHA256:
gcry_md_open(&md, GCRY_MD_SHA256, 0);
hash_size = 32;
break;
case DNSSEC_ALGORITHM_RSASHA512:
gcry_md_open(&md, GCRY_MD_SHA512, 0);
hash_size = 64;
break;
default:
assert_not_reached("Unknown digest");
}
if (!md)
return -EIO;
md_add_uint16(md, rrsig->rrsig.type_covered);
md_add_uint8(md, rrsig->rrsig.algorithm);
md_add_uint8(md, rrsig->rrsig.labels);
md_add_uint32(md, rrsig->rrsig.original_ttl);
md_add_uint32(md, rrsig->rrsig.expiration);
md_add_uint32(md, rrsig->rrsig.inception);
md_add_uint16(md, rrsig->rrsig.key_tag);
r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
if (r < 0)
goto finish;
gcry_md_write(md, wire_format_name, r);
for (k = 0; k < n; k++) {
size_t l;
rr = list[k];
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;
gcry_md_write(md, wire_format_name, r);
md_add_uint16(md, rr->key->type);
md_add_uint16(md, rr->key->class);
md_add_uint32(md, rrsig->rrsig.original_ttl);
assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
l = rr->wire_format_size - rr->wire_format_rdata_offset;
assert(l <= 0xFFFF);
md_add_uint16(md, (uint16_t) l);
gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
}
hash = gcry_md_read(md, 0);
if (!hash) {
r = -EIO;
goto finish;
}
if (*(uint8_t*) dnskey->dnskey.key == 0) {
/* exponent is > 255 bytes long */
exponent = (uint8_t*) dnskey->dnskey.key + 3;
exponent_size =
((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
if (exponent_size < 256) {
r = -EINVAL;
goto finish;
}
if (3 + exponent_size >= dnskey->dnskey.key_size) {
r = -EINVAL;
goto finish;
}
modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
} else {
/* exponent is <= 255 bytes long */
exponent = (uint8_t*) dnskey->dnskey.key + 1;
exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
if (exponent_size <= 0) {
r = -EINVAL;
goto finish;
}
if (1 + exponent_size >= dnskey->dnskey.key_size) {
r = -EINVAL;
goto finish;
}
modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
}
r = dnssec_rsa_verify(
gcry_md_algo_name(gcry_md_get_algo(md)),
rrsig->rrsig.signature, rrsig->rrsig.signature_size,
hash, hash_size,
exponent, exponent_size,
modulus, modulus_size);
if (r < 0)
goto finish;
r = r ? DNSSEC_VERIFIED : DNSSEC_INVALID;
finish:
gcry_md_close(md);
return r;
}
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
assert(rrsig);
assert(dnskey);
/* Checks if the specified DNSKEY RR matches the key used for
* the signature in the specified RRSIG RR */
if (rrsig->key->type != DNS_TYPE_RRSIG)
return -EINVAL;
if (dnskey->key->type != DNS_TYPE_DNSKEY)
return 0;
if (dnskey->key->class != rrsig->key->class)
return 0;
if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
return 0;
if (dnskey->dnskey.protocol != 3)
return 0;
if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
return 0;
if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag)
return 0;
return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(rrsig->key));
}
int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) {
assert(key);
assert(rrsig);
/* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
if (rrsig->key->type != DNS_TYPE_RRSIG)
return 0;
if (rrsig->key->class != key->class)
return 0;
if (rrsig->rrsig.type_covered != key->type)
return 0;
return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
}
int dnssec_verify_rrset_search(
DnsAnswer *a,
DnsResourceKey *key,
DnsAnswer *validated_dnskeys,
usec_t realtime) {
bool found_rrsig = false, found_dnskey = false;
DnsResourceRecord *rrsig;
int r;
assert(key);
/* Verifies all RRs from "a" that match the key "key", against DNSKEY RRs in "validated_dnskeys" */
if (!a || a->n_rrs <= 0)
return -ENODATA;
/* Iterate through each RRSIG RR. */
DNS_ANSWER_FOREACH(rrsig, a) {
DnsResourceRecord *dnskey;
r = dnssec_key_match_rrsig(key, rrsig);
if (r < 0)
return r;
if (r == 0)
continue;
found_rrsig = true;
DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) {
r = dnssec_rrsig_match_dnskey(rrsig, dnskey);
if (r < 0)
return r;
if (r == 0)
continue;
found_dnskey = true;
/* Take the time here, if it isn't set yet, so
* that we do all validations with the same
* time. */
if (realtime == USEC_INFINITY)
realtime = now(CLOCK_REALTIME);
/* 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. */
r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime);
if (r < 0 && r != EOPNOTSUPP)
return r;
if (r == DNSSEC_VERIFIED)
return DNSSEC_VERIFIED;
/* If the signature is invalid, or done using
an unsupported algorithm, let's try another
key and/or signature. After all they
key_tags and stuff are not unique, and
might be shared by multiple keys. */
}
}
if (found_dnskey)
return DNSSEC_INVALID;
if (found_rrsig)
return DNSSEC_MISSING_KEY;
return DNSSEC_NO_SIGNATURE;
}
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
_cleanup_free_ char *s = NULL;
size_t c = 0;
int r;
/* Converts the specified hostname into DNSSEC canonicalized
* form. */
if (buffer_max < 2)
return -ENOBUFS;
for (;;) {
size_t i;
r = dns_label_unescape(&n, buffer, buffer_max);
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 */
k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
if (k < 0)
return k;
if (k > 0)
r = k;
}
if (buffer_max < (size_t) r + 2)
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. */
if (memchr(buffer, '.', r))
return -EINVAL;
for (i = 0; i < (size_t) r; i ++) {
if (buffer[i] >= 'A' && buffer[i] <= 'Z')
buffer[i] = buffer[i] - 'A' + 'a';
}
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 */
assert(buffer_max > 2);
buffer[0] = '.';
buffer[1] = 0;
return 1;
}
return (int) c;
}
int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
gcry_md_hd_t md = NULL;
char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
void *result;
int r;
assert(dnskey);
assert(ds);
/* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
if (dnskey->key->type != DNS_TYPE_DNSKEY)
return -EINVAL;
if (ds->key->type != DNS_TYPE_DS)
return -EINVAL;
if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
return -EKEYREJECTED;
if (dnskey->dnskey.protocol != 3)
return -EKEYREJECTED;
if (!dnssec_algorithm_supported(dnskey->dnskey.algorithm))
return -EOPNOTSUPP;
if (!dnssec_digest_supported(ds->ds.digest_type))
return -EOPNOTSUPP;
if (dnskey->dnskey.algorithm != ds->ds.algorithm)
return 0;
if (dnssec_keytag(dnskey) != ds->ds.key_tag)
return 0;
switch (ds->ds.digest_type) {
case DNSSEC_DIGEST_SHA1:
if (ds->ds.digest_size != 20)
return 0;
gcry_md_open(&md, GCRY_MD_SHA1, 0);
break;
case DNSSEC_DIGEST_SHA256:
if (ds->ds.digest_size != 32)
return 0;
gcry_md_open(&md, GCRY_MD_SHA256, 0);
break;
default:
assert_not_reached("Unknown digest");
}
if (!md)
return -EIO;
r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
if (r < 0)
goto finish;
gcry_md_write(md, owner_name, r);
md_add_uint16(md, dnskey->dnskey.flags);
md_add_uint8(md, dnskey->dnskey.protocol);
md_add_uint8(md, dnskey->dnskey.algorithm);
gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);
result = gcry_md_read(md, 0);
if (!result) {
r = -EIO;
goto finish;
}
r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0;
finish:
gcry_md_close(md);
return r;
}
+49
View File
@@ -0,0 +1,49 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
#pragma once
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
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 "dns-domain.h"
#include "resolved-dns-answer.h"
#include "resolved-dns-rr.h"
enum {
DNSSEC_VERIFIED,
DNSSEC_INVALID,
DNSSEC_NO_SIGNATURE,
DNSSEC_MISSING_KEY,
DNSSEC_SIGNATURE_EXPIRED,
};
#define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2)
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey);
int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig);
int dnssec_verify_rrset(DnsAnswer *answer, DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime);
int dnssec_verify_rrset_search(DnsAnswer *a, DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime);
int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds);
uint16_t dnssec_keytag(DnsResourceRecord *dnskey);
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max);
+39 -67
View File
@@ -108,6 +108,8 @@ DnsPacket *dns_packet_ref(DnsPacket *p) {
if (!p)
return NULL;
assert(!p->on_stack);
assert(p->n_ref > 0);
p->n_ref++;
return p;
@@ -126,7 +128,9 @@ static void dns_packet_free(DnsPacket *p) {
hashmap_free(p->names);
free(p->_data);
free(p);
if (!p->on_stack)
free(p);
}
DnsPacket *dns_packet_unref(DnsPacket *p) {
@@ -351,25 +355,10 @@ int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) {
}
int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) {
void *d;
size_t l;
int r;
assert(p);
assert(s);
l = strlen(s);
if (l > 255)
return -E2BIG;
r = dns_packet_extend(p, 1 + l, &d, start);
if (r < 0)
return r;
((uint8_t*) d)[0] = (uint8_t) l;
memcpy(((uint8_t*) d) + 1, s, l);
return 0;
return dns_packet_append_raw_string(p, s, strlen(s), start);
}
int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) {
@@ -395,7 +384,7 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_
}
int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) {
void *w;
uint8_t *w;
int r;
assert(p);
@@ -404,12 +393,29 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start
if (l > DNS_LABEL_MAX)
return -E2BIG;
r = dns_packet_extend(p, 1 + l, &w, start);
r = dns_packet_extend(p, 1 + l, (void**) &w, start);
if (r < 0)
return r;
((uint8_t*) w)[0] = (uint8_t) l;
memcpy(((uint8_t*) w) + 1, d, l);
*(w++) = (uint8_t) l;
if (p->canonical_form) {
size_t i;
/* Generate in canonical form, as defined by DNSSEC
* RFC 4034, Section 6.2, i.e. all lower-case. */
for (i = 0; i < l; i++) {
if (d[i] >= 'A' && d[i] <= 'Z')
w[i] = (uint8_t) (d[i] - 'A' + 'a');
else
w[i] = (uint8_t) d[i];
}
} else
/* Otherwise, just copy the string unaltered. This is
* essential for DNS-SD, where the casing of labels
* matters and needs to be retained. */
memcpy(w, d, l);
return 0;
}
@@ -662,8 +668,8 @@ fail:
return r;
}
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start) {
size_t saved_size, rdlength_offset, end, rdlength;
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) {
size_t saved_size, rdlength_offset, end, rdlength, rds;
int r;
assert(p);
@@ -684,6 +690,8 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
if (r < 0)
goto fail;
rds = p->size - saved_size;
switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) {
case DNS_TYPE_SRV:
@@ -841,11 +849,11 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
break;
case DNS_TYPE_DNSKEY:
r = dns_packet_append_uint16(p, dnskey_to_flags(rr), NULL);
r = dns_packet_append_uint16(p, rr->dnskey.flags, NULL);
if (r < 0)
goto fail;
r = dns_packet_append_uint8(p, 3u, NULL);
r = dns_packet_append_uint8(p, rr->dnskey.protocol, NULL);
if (r < 0)
goto fail;
@@ -962,6 +970,9 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
if (start)
*start = saved_size;
if (rdata_start)
*rdata_start = rds;
return 0;
fail:
@@ -1427,17 +1438,6 @@ static bool loc_size_ok(uint8_t size) {
return m <= 9 && e <= 9 && (m > 0 || e == 0);
}
static int dnskey_parse_flags(DnsResourceRecord *rr, uint16_t flags) {
assert(rr);
if (flags & ~(DNSKEY_FLAG_SEP | DNSKEY_FLAG_ZONE_KEY))
return -EBADMSG;
rr->dnskey.zone_key_flag = flags & DNSKEY_FLAG_ZONE_KEY;
rr->dnskey.sep_flag = flags & DNSKEY_FLAG_SEP;
return 0;
}
int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
@@ -1706,28 +1706,15 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
break;
case DNS_TYPE_DNSKEY: {
uint16_t flags;
uint8_t proto;
r = dns_packet_read_uint16(p, &flags, NULL);
case DNS_TYPE_DNSKEY:
r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL);
if (r < 0)
goto fail;
r = dnskey_parse_flags(rr, flags);
r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL);
if (r < 0)
goto fail;
r = dns_packet_read_uint8(p, &proto, NULL);
if (r < 0)
goto fail;
/* protocol is required to be always 3 */
if (proto != 3) {
r = -EBADMSG;
goto fail;
}
r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL);
if (r < 0)
goto fail;
@@ -1744,7 +1731,6 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
}
break;
}
case DNS_TYPE_RRSIG:
r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL);
@@ -1976,17 +1962,3 @@ static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = {
[DNS_PROTOCOL_LLMNR] = "llmnr",
};
DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol);
static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = {
[DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5",
[DNSSEC_ALGORITHM_DH] = "DH",
[DNSSEC_ALGORITHM_DSA] = "DSA",
[DNSSEC_ALGORITHM_ECC] = "ECC",
[DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1",
[DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1",
[DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1",
[DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT",
[DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS",
[DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int);
+5 -29
View File
@@ -88,8 +88,10 @@ struct DnsPacket {
uint16_t sender_port, destination_port;
uint32_t ttl;
bool extracted;
bool refuse_compression;
bool on_stack:1;
bool extracted:1;
bool refuse_compression:1;
bool canonical_form:1;
};
static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) {
@@ -162,7 +164,7 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_
int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, size_t *start);
int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, size_t *start);
int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start);
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start);
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start);
int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start);
void dns_packet_truncate(DnsPacket *p, size_t sz);
@@ -223,32 +225,6 @@ DnsProtocol dns_protocol_from_string(const char *s) _pure_;
#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) })
#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } })
#define DNSKEY_FLAG_ZONE_KEY (1u << 8)
#define DNSKEY_FLAG_SEP (1u << 0)
static inline uint16_t dnskey_to_flags(const DnsResourceRecord *rr) {
return (rr->dnskey.zone_key_flag * DNSKEY_FLAG_ZONE_KEY |
rr->dnskey.sep_flag * DNSKEY_FLAG_SEP);
}
/* http://tools.ietf.org/html/rfc4034#appendix-A.1 */
enum {
DNSSEC_ALGORITHM_RSAMD5 = 1,
DNSSEC_ALGORITHM_DH,
DNSSEC_ALGORITHM_DSA,
DNSSEC_ALGORITHM_ECC,
DNSSEC_ALGORITHM_RSASHA1,
DNSSEC_ALGORITHM_DSA_NSEC3_SHA1,
DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
DNSSEC_ALGORITHM_INDIRECT = 252,
DNSSEC_ALGORITHM_PRIVATEDNS,
DNSSEC_ALGORITHM_PRIVATEOID,
_DNSSEC_ALGORITHM_MAX_DEFINED
};
const char* dnssec_algorithm_to_string(int i) _const_;
int dnssec_algorithm_from_string(const char *s) _pure_;
static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family) {
/* Converts a protocol + family into a flags field as used in queries */
+8 -5
View File
@@ -23,9 +23,10 @@
typedef struct DnsQuestion DnsQuestion;
#include "macro.h"
#include "resolved-dns-rr.h"
/* A simple array of resources keys */
/* A simple array of resource keys */
struct DnsQuestion {
unsigned n_ref;
@@ -55,10 +56,12 @@ const char *dns_question_first_name(DnsQuestion *q);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref);
#define DNS_QUESTION_FOREACH(key, q) \
for (unsigned _i = ({ \
#define _DNS_QUESTION_FOREACH(u, key, q) \
for (unsigned UNIQ_T(i, u) = ({ \
(key) = ((q) && (q)->n_keys > 0) ? (q)->keys[0] : NULL; \
0; \
}); \
(q) && ((_i) < (q)->n_keys); \
_i++, (key) = (_i < (q)->n_keys ? (q)->keys[_i] : NULL))
(q) && (UNIQ_T(i, u) < (q)->n_keys); \
UNIQ_T(i, u)++, (key) = (UNIQ_T(i, u) < (q)->n_keys ? (q)->keys[UNIQ_T(i, u)] : NULL))
#define DNS_QUESTION_FOREACH(key, q) _DNS_QUESTION_FOREACH(UNIQ, key, q)
+74 -4
View File
@@ -27,6 +27,7 @@
#include "hexdecoct.h"
#include "resolved-dns-packet.h"
#include "resolved-dns-rr.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
@@ -416,6 +417,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) {
free(rr->generic.data);
}
free(rr->wire_format);
dns_resource_key_unref(rr->key);
}
@@ -576,8 +578,8 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor
memcmp(a->sshfp.fingerprint, b->sshfp.fingerprint, a->sshfp.fingerprint_size) == 0;
case DNS_TYPE_DNSKEY:
return a->dnskey.zone_key_flag == b->dnskey.zone_key_flag &&
a->dnskey.sep_flag == b->dnskey.sep_flag &&
return a->dnskey.flags == b->dnskey.flags &&
a->dnskey.protocol == b->dnskey.protocol &&
a->dnskey.algorithm == b->dnskey.algorithm &&
a->dnskey.key_size == b->dnskey.key_size &&
memcmp(a->dnskey.key, b->dnskey.key, a->dnskey.key_size) == 0;
@@ -883,9 +885,10 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
if (!t)
return -ENOMEM;
r = asprintf(&s, "%s %u 3 %.*s%.*u %s",
r = asprintf(&s, "%s %u %u %.*s%.*u %s",
k,
dnskey_to_flags(rr),
rr->dnskey.flags,
rr->dnskey.protocol,
alg ? -1 : 0, alg,
alg ? 0 : 1, alg ? 0u : (unsigned) rr->dnskey.algorithm,
t);
@@ -993,6 +996,51 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
return 0;
}
int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) {
DnsPacket packet = {
.n_ref = 1,
.protocol = DNS_PROTOCOL_DNS,
.on_stack = true,
.refuse_compression = true,
.canonical_form = canonical,
};
size_t start, rds;
int r;
assert(rr);
/* Generates the RR in wire-format, optionally in the
* canonical form as discussed in the DNSSEC RFC 4034, Section
* 6.2. We allocate a throw-away DnsPacket object on the stack
* here, because we need some book-keeping for memory
* management, and can reuse the DnsPacket serializer, that
* can generate the canonical form, too, but also knows label
* compression and suchlike. */
if (rr->wire_format && rr->wire_format_canonical == canonical)
return 0;
r = dns_packet_append_rr(&packet, rr, &start, &rds);
if (r < 0)
return r;
assert(start == 0);
assert(packet._data);
free(rr->wire_format);
rr->wire_format = packet._data;
rr->wire_format_size = packet.size;
rr->wire_format_rdata_offset = rds;
rr->wire_format_canonical = canonical;
packet._data = NULL;
dns_packet_unref(&packet);
return 0;
}
const char *dns_class_to_string(uint16_t class) {
switch (class) {
@@ -1049,3 +1097,25 @@ bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) {
return dns_txt_item_equal(a->items_next, b->items_next);
}
static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = {
[DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5",
[DNSSEC_ALGORITHM_DH] = "DH",
[DNSSEC_ALGORITHM_DSA] = "DSA",
[DNSSEC_ALGORITHM_ECC] = "ECC",
[DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1",
[DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1",
[DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1",
[DNSSEC_ALGORITHM_RSASHA256] = "RSASHA256",
[DNSSEC_ALGORITHM_RSASHA512] = "RSASHA512",
[DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT",
[DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS",
[DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int);
static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = {
[DNSSEC_DIGEST_SHA1] = "SHA1",
[DNSSEC_DIGEST_SHA256] = "SHA256",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_digest, int);
+46 -3
View File
@@ -41,6 +41,37 @@ enum {
_DNS_CLASS_INVALID = -1
};
/* DNSKEY RR flags */
#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8)
#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0)
/* DNSSEC algorithm identifiers, see
* http://tools.ietf.org/html/rfc4034#appendix-A.1 and
* https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */
enum {
DNSSEC_ALGORITHM_RSAMD5 = 1,
DNSSEC_ALGORITHM_DH,
DNSSEC_ALGORITHM_DSA,
DNSSEC_ALGORITHM_ECC,
DNSSEC_ALGORITHM_RSASHA1,
DNSSEC_ALGORITHM_DSA_NSEC3_SHA1,
DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */
DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */
DNSSEC_ALGORITHM_INDIRECT = 252,
DNSSEC_ALGORITHM_PRIVATEDNS,
DNSSEC_ALGORITHM_PRIVATEOID,
_DNSSEC_ALGORITHM_MAX_DEFINED
};
/* DNSSEC digest identifiers, see
* https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */
enum {
DNSSEC_DIGEST_SHA1 = 1,
DNSSEC_DIGEST_SHA256 = 2,
_DNSSEC_DIGEST_MAX_DEFINED
};
struct DnsResourceKey {
unsigned n_ref;
uint16_t class, type;
@@ -57,7 +88,11 @@ struct DnsResourceRecord {
unsigned n_ref;
DnsResourceKey *key;
uint32_t ttl;
bool unparseable;
bool unparseable:1;
bool wire_format_canonical:1;
void *wire_format;
size_t wire_format_size;
size_t wire_format_rdata_offset;
union {
struct {
void *data;
@@ -135,8 +170,8 @@ struct DnsResourceRecord {
/* http://tools.ietf.org/html/rfc4034#section-2.1 */
struct {
bool zone_key_flag:1;
bool sep_flag:1;
uint16_t flags;
uint8_t protocol;
uint8_t algorithm;
void* key;
size_t key_size;
@@ -209,6 +244,8 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor
int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref);
int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical);
DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i);
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);
@@ -216,3 +253,9 @@ const char *dns_class_to_string(uint16_t type);
int dns_class_from_string(const char *name, uint16_t *class);
extern const struct hash_ops dns_resource_key_hash_ops;
const char* dnssec_algorithm_to_string(int i) _const_;
int dnssec_algorithm_from_string(const char *s) _pure_;
const char *dnssec_digest_to_string(int i) _const_;
int dnssec_digest_from_string(const char *s) _pure_;
+3 -3
View File
@@ -549,7 +549,7 @@ static int dns_scope_make_reply_packet(
if (answer) {
for (i = 0; i < answer->n_rrs; i++) {
r = dns_packet_append_rr(p, answer->items[i].rr, NULL);
r = dns_packet_append_rr(p, answer->items[i].rr, NULL, NULL);
if (r < 0)
return r;
}
@@ -559,7 +559,7 @@ static int dns_scope_make_reply_packet(
if (soa) {
for (i = 0; i < soa->n_rrs; i++) {
r = dns_packet_append_rr(p, soa->items[i].rr, NULL);
r = dns_packet_append_rr(p, soa->items[i].rr, NULL, NULL);
if (r < 0)
return r;
}
@@ -733,7 +733,7 @@ static int dns_scope_make_conflict_packet(
if (r < 0)
return r;
r = dns_packet_append_rr(p, rr, NULL);
r = dns_packet_append_rr(p, rr, NULL, NULL);
if (r < 0)
return r;
+217
View File
@@ -0,0 +1,217 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
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 <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include "alloc-util.h"
#include "resolved-dns-dnssec.h"
#include "resolved-dns-rr.h"
#include "string-util.h"
static void test_dnssec_verify_rrset(void) {
static const uint8_t signature_blob[] = {
0x7f, 0x79, 0xdd, 0x5e, 0x89, 0x79, 0x18, 0xd0, 0x34, 0x86, 0x8c, 0x72, 0x77, 0x75, 0x48, 0x4d,
0xc3, 0x7d, 0x38, 0x04, 0xab, 0xcd, 0x9e, 0x4c, 0x82, 0xb0, 0x92, 0xca, 0xe9, 0x66, 0xe9, 0x6e,
0x47, 0xc7, 0x68, 0x8c, 0x94, 0xf6, 0x69, 0xcb, 0x75, 0x94, 0xe6, 0x30, 0xa6, 0xfb, 0x68, 0x64,
0x96, 0x1a, 0x84, 0xe1, 0xdc, 0x16, 0x4c, 0x83, 0x6c, 0x44, 0xf2, 0x74, 0x4d, 0x74, 0x79, 0x8f,
0xf3, 0xf4, 0x63, 0x0d, 0xef, 0x5a, 0xe7, 0xe2, 0xfd, 0xf2, 0x2b, 0x38, 0x7c, 0x28, 0x96, 0x9d,
0xb6, 0xcd, 0x5c, 0x3b, 0x57, 0xe2, 0x24, 0x78, 0x65, 0xd0, 0x9e, 0x77, 0x83, 0x09, 0x6c, 0xff,
0x3d, 0x52, 0x3f, 0x6e, 0xd1, 0xed, 0x2e, 0xf9, 0xee, 0x8e, 0xa6, 0xbe, 0x9a, 0xa8, 0x87, 0x76,
0xd8, 0x77, 0xcc, 0x96, 0xa0, 0x98, 0xa1, 0xd1, 0x68, 0x09, 0x43, 0xcf, 0x56, 0xd9, 0xd1, 0x66,
};
static const uint8_t dnskey_blob[] = {
0x03, 0x01, 0x00, 0x01, 0x9b, 0x49, 0x9b, 0xc1, 0xf9, 0x9a, 0xe0, 0x4e, 0xcf, 0xcb, 0x14, 0x45,
0x2e, 0xc9, 0xf9, 0x74, 0xa7, 0x18, 0xb5, 0xf3, 0xde, 0x39, 0x49, 0xdf, 0x63, 0x33, 0x97, 0x52,
0xe0, 0x8e, 0xac, 0x50, 0x30, 0x8e, 0x09, 0xd5, 0x24, 0x3d, 0x26, 0xa4, 0x49, 0x37, 0x2b, 0xb0,
0x6b, 0x1b, 0xdf, 0xde, 0x85, 0x83, 0xcb, 0x22, 0x4e, 0x60, 0x0a, 0x91, 0x1a, 0x1f, 0xc5, 0x40,
0xb1, 0xc3, 0x15, 0xc1, 0x54, 0x77, 0x86, 0x65, 0x53, 0xec, 0x10, 0x90, 0x0c, 0x91, 0x00, 0x5e,
0x15, 0xdc, 0x08, 0x02, 0x4c, 0x8c, 0x0d, 0xc0, 0xac, 0x6e, 0xc4, 0x3e, 0x1b, 0x80, 0x19, 0xe4,
0xf7, 0x5f, 0x77, 0x51, 0x06, 0x87, 0x61, 0xde, 0xa2, 0x18, 0x0f, 0x40, 0x8b, 0x79, 0x72, 0xfa,
0x8d, 0x1a, 0x44, 0x47, 0x0d, 0x8e, 0x3a, 0x2d, 0xc7, 0x39, 0xbf, 0x56, 0x28, 0x97, 0xd9, 0x20,
0x4f, 0x00, 0x51, 0x3b,
};
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
_cleanup_free_ char *x = NULL, *y = NULL, *z = NULL;
a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov");
assert_se(a);
a->a.in_addr.s_addr = inet_addr("52.0.14.116");
assert_se(dns_resource_record_to_string(a, &x) >= 0);
log_info("A: %s", x);
rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV.");
assert_se(rrsig);
rrsig->rrsig.type_covered = DNS_TYPE_A;
rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256;
rrsig->rrsig.labels = 2;
rrsig->rrsig.original_ttl = 600;
rrsig->rrsig.expiration = 0x5683135c;
rrsig->rrsig.inception = 0x565b7da8;
rrsig->rrsig.key_tag = 63876;
rrsig->rrsig.signer = strdup("Nasa.Gov.");
assert_se(rrsig->rrsig.signer);
rrsig->rrsig.signature_size = sizeof(signature_blob);
rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size);
assert_se(rrsig->rrsig.signature);
assert_se(dns_resource_record_to_string(rrsig, &y) >= 0);
log_info("RRSIG: %s", y);
dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV");
assert_se(dnskey);
dnskey->dnskey.flags = 256;
dnskey->dnskey.protocol = 3;
dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256;
dnskey->dnskey.key_size = sizeof(dnskey_blob);
dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
assert_se(dnskey->dnskey.key);
assert_se(dns_resource_record_to_string(dnskey, &z) >= 0);
log_info("DNSKEY: %s", z);
log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey));
assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0);
assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey) > 0);
answer = dns_answer_new(1);
assert_se(answer);
assert_se(dns_answer_add(answer, a, 0) >= 0);
/* Validate the RR as it if was 2015-12-2 today */
assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC) == DNSSEC_VERIFIED);
}
static void test_dnssec_verify_dns_key(void) {
static const uint8_t ds1_fprint[] = {
0x46, 0x8B, 0xC8, 0xDD, 0xC7, 0xE8, 0x27, 0x03, 0x40, 0xBB, 0x8A, 0x1F, 0x3B, 0x2E, 0x45, 0x9D,
0x80, 0x67, 0x14, 0x01,
};
static const uint8_t ds2_fprint[] = {
0x8A, 0xEE, 0x80, 0x47, 0x05, 0x5F, 0x83, 0xD1, 0x48, 0xBA, 0x8F, 0xF6, 0xDD, 0xA7, 0x60, 0xCE,
0x94, 0xF7, 0xC7, 0x5E, 0x52, 0x4C, 0xF2, 0xE9, 0x50, 0xB9, 0x2E, 0xCB, 0xEF, 0x96, 0xB9, 0x98,
};
static const uint8_t dnskey_blob[] = {
0x03, 0x01, 0x00, 0x01, 0xa8, 0x12, 0xda, 0x4f, 0xd2, 0x7d, 0x54, 0x14, 0x0e, 0xcc, 0x5b, 0x5e,
0x45, 0x9c, 0x96, 0x98, 0xc0, 0xc0, 0x85, 0x81, 0xb1, 0x47, 0x8c, 0x7d, 0xe8, 0x39, 0x50, 0xcc,
0xc5, 0xd0, 0xf2, 0x00, 0x81, 0x67, 0x79, 0xf6, 0xcc, 0x9d, 0xad, 0x6c, 0xbb, 0x7b, 0x6f, 0x48,
0x97, 0x15, 0x1c, 0xfd, 0x0b, 0xfe, 0xd3, 0xd7, 0x7d, 0x9f, 0x81, 0x26, 0xd3, 0xc5, 0x65, 0x49,
0xcf, 0x46, 0x62, 0xb0, 0x55, 0x6e, 0x47, 0xc7, 0x30, 0xef, 0x51, 0xfb, 0x3e, 0xc6, 0xef, 0xde,
0x27, 0x3f, 0xfa, 0x57, 0x2d, 0xa7, 0x1d, 0x80, 0x46, 0x9a, 0x5f, 0x14, 0xb3, 0xb0, 0x2c, 0xbe,
0x72, 0xca, 0xdf, 0xb2, 0xff, 0x36, 0x5b, 0x4f, 0xec, 0x58, 0x8e, 0x8d, 0x01, 0xe9, 0xa9, 0xdf,
0xb5, 0x60, 0xad, 0x52, 0x4d, 0xfc, 0xa9, 0x3e, 0x8d, 0x35, 0x95, 0xb3, 0x4e, 0x0f, 0xca, 0x45,
0x1b, 0xf7, 0xef, 0x3a, 0x88, 0x25, 0x08, 0xc7, 0x4e, 0x06, 0xc1, 0x62, 0x1a, 0xce, 0xd8, 0x77,
0xbd, 0x02, 0x65, 0xf8, 0x49, 0xfb, 0xce, 0xf6, 0xa8, 0x09, 0xfc, 0xde, 0xb2, 0x09, 0x9d, 0x39,
0xf8, 0x63, 0x9c, 0x32, 0x42, 0x7c, 0xa0, 0x30, 0x86, 0x72, 0x7a, 0x4a, 0xc6, 0xd4, 0xb3, 0x2d,
0x24, 0xef, 0x96, 0x3f, 0xc2, 0xda, 0xd3, 0xf2, 0x15, 0x6f, 0xda, 0x65, 0x4b, 0x81, 0x28, 0x68,
0xf4, 0xfe, 0x3e, 0x71, 0x4f, 0x50, 0x96, 0x72, 0x58, 0xa1, 0x89, 0xdd, 0x01, 0x61, 0x39, 0x39,
0xc6, 0x76, 0xa4, 0xda, 0x02, 0x70, 0x3d, 0xc0, 0xdc, 0x8d, 0x70, 0x72, 0x04, 0x90, 0x79, 0xd4,
0xec, 0x65, 0xcf, 0x49, 0x35, 0x25, 0x3a, 0x14, 0x1a, 0x45, 0x20, 0xeb, 0x31, 0xaf, 0x92, 0xba,
0x20, 0xd3, 0xcd, 0xa7, 0x13, 0x44, 0xdc, 0xcf, 0xf0, 0x27, 0x34, 0xb9, 0xe7, 0x24, 0x6f, 0x73,
0xe7, 0xea, 0x77, 0x03,
};
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = NULL;
_cleanup_free_ char *a = NULL, *b = NULL, *c = NULL;
/* The two DS RRs in effect for nasa.gov on 2015-12-01. */
ds1 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "nasa.gov");
assert_se(ds1);
ds1->ds.key_tag = 47857;
ds1->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256;
ds1->ds.digest_type = DNSSEC_DIGEST_SHA1;
ds1->ds.digest_size = sizeof(ds1_fprint);
ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size);
assert_se(ds1->ds.digest);
assert_se(dns_resource_record_to_string(ds1, &a) >= 0);
log_info("DS1: %s", a);
ds2 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "NASA.GOV");
assert_se(ds2);
ds2->ds.key_tag = 47857;
ds2->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256;
ds2->ds.digest_type = DNSSEC_DIGEST_SHA256;
ds2->ds.digest_size = sizeof(ds2_fprint);
ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size);
assert_se(ds2->ds.digest);
assert_se(dns_resource_record_to_string(ds2, &b) >= 0);
log_info("DS2: %s", b);
dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nasa.GOV");
assert_se(dnskey);
dnskey->dnskey.flags = 257;
dnskey->dnskey.protocol = 3;
dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256;
dnskey->dnskey.key_size = sizeof(dnskey_blob);
dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
assert_se(dnskey->dnskey.key);
assert_se(dns_resource_record_to_string(dnskey, &c) >= 0);
log_info("DNSKEY: %s", c);
log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey));
assert_se(dnssec_verify_dnskey(dnskey, ds1) > 0);
assert_se(dnssec_verify_dnskey(dnskey, ds2) > 0);
}
static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) {
char canonicalized[DNSSEC_CANONICAL_HOSTNAME_MAX];
assert_se(dnssec_canonicalize(original, canonicalized, sizeof(canonicalized)) == r);
if (r < 0)
return;
assert_se(streq(canonicalized, canonical));
}
static void test_dnssec_canonicalize(void) {
test_dnssec_canonicalize_one("", ".", 1);
test_dnssec_canonicalize_one(".", ".", 1);
test_dnssec_canonicalize_one("foo", "foo.", 4);
test_dnssec_canonicalize_one("foo.", "foo.", 4);
test_dnssec_canonicalize_one("FOO.", "foo.", 4);
test_dnssec_canonicalize_one("FOO.bar.", "foo.bar.", 8);
test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL);
}
int main(int argc, char*argv[]) {
test_dnssec_canonicalize();
test_dnssec_verify_dns_key();
test_dnssec_verify_rrset();
return 0;
}
+15 -1
View File
@@ -865,7 +865,7 @@ bool dns_name_is_single_label(const char *name) {
}
/* Encode a domain name according to RFC 1035 Section 3.1, without compression */
int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len) {
int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) {
uint8_t *label_length, *out;
int r;
@@ -890,6 +890,20 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len) {
if (r < 0)
return r;
if (canonical) {
size_t i;
/* Optionally, output the name in DNSSEC
* canonical format, as described in RFC 4034,
* section 6.2. Or in other words: in
* lower-case. */
for (i = 0; i < (size_t) r; i++) {
if (out[i] >= 'A' && out[i] <= 'Z')
out[i] = out[i] - 'A' + 'a';
}
}
/* Fill label length, move forward */
*label_length = r;
out += r;
+4 -1
View File
@@ -34,6 +34,9 @@
/* Maximum length of a full hostname, consisting of a series of unescaped labels, and no trailing dot or NUL byte */
#define DNS_HOSTNAME_MAX 253
/* Maximum length of a full hostname, on the wire, including the final NUL byte */
#define DNS_WIRE_FOMAT_HOSTNAME_MAX 255
int dns_label_unescape(const char **name, char *dest, size_t sz);
int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz);
int dns_label_escape(const char *p, size_t l, char *dest, size_t sz);
@@ -77,7 +80,7 @@ int dns_name_address(const char *p, int *family, union in_addr_union *a);
bool dns_name_is_root(const char *name);
bool dns_name_is_single_label(const char *name);
int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len);
int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical);
bool dns_srv_type_is_valid(const char *name);
bool dns_service_name_is_valid(const char *name);
+1 -1
View File
@@ -56,7 +56,7 @@ static void test_dns_name_to_wire_format_one(const char *what, const char *expec
uint8_t buffer[buffer_sz];
int r;
r = dns_name_to_wire_format(what, buffer, buffer_sz);
r = dns_name_to_wire_format(what, buffer, buffer_sz, false);
assert_se(r == ret);
if (r < 0)