Merge pull request #2087 from poettering/dnssec
Basic DNSSEC support, and unrelated fixes
This commit is contained in:
+30
-6
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "list.h"
|
||||
#include "prioq.h"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user