1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2024-12-23 17:34:00 +03:00

Merge pull request #2269 from poettering/dnssec11

Eleventh DNSSEC patch set
This commit is contained in:
Tom Gundersen 2016-01-05 15:47:15 +01:00
commit 16b85c51ed
21 changed files with 774 additions and 83 deletions

View File

@ -1990,16 +1990,21 @@ endif
if ENABLE_RESOLVED
MANPAGES += \
man/dnssec-trust-anchors.d.5 \
man/nss-resolve.8 \
man/resolved.conf.5 \
man/systemd-resolved.service.8
MANPAGES_ALIAS += \
man/libnss_resolve.so.2.8 \
man/resolved.conf.d.5 \
man/systemd-resolved.8
man/systemd-resolved.8 \
man/systemd.negative.5 \
man/systemd.positive.5
man/libnss_resolve.so.2.8: man/nss-resolve.8
man/resolved.conf.d.5: man/resolved.conf.5
man/systemd-resolved.8: man/systemd-resolved.service.8
man/systemd.negative.5: man/dnssec-trust-anchors.d.5
man/systemd.positive.5: man/dnssec-trust-anchors.d.5
man/libnss_resolve.so.2.html: man/nss-resolve.html
$(html-alias)
@ -2009,6 +2014,12 @@ man/resolved.conf.d.html: man/resolved.conf.html
man/systemd-resolved.html: man/systemd-resolved.service.html
$(html-alias)
man/systemd.negative.html: man/dnssec-trust-anchors.d.html
$(html-alias)
man/systemd.positive.html: man/dnssec-trust-anchors.d.html
$(html-alias)
endif
if ENABLE_RFKILL
@ -2434,6 +2445,7 @@ EXTRA_DIST += \
man/coredumpctl.xml \
man/crypttab.xml \
man/daemon.xml \
man/dnssec-trust-anchors.d.xml \
man/file-hierarchy.xml \
man/halt.xml \
man/hostname.xml \

View File

@ -0,0 +1,189 @@
<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
This file is part of systemd.
Copyright 2016 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/>.
-->
<refentry id="dnssec-trust-anchors.d" conditional='ENABLE_RESOLVED'
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>dnssec-trust-anchors.d</title>
<productname>systemd</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Lennart</firstname>
<surname>Poettering</surname>
<email>lennart@poettering.net</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>dnssec-trust-anchors.d</refentrytitle>
<manvolnum>5</manvolnum>
</refmeta>
<refnamediv>
<refname>dnssec-trust-anchors.d</refname>
<refname>systemd.positive</refname>
<refname>systemd.negative</refname>
<refpurpose>DNSSEC trust anchor configuration files</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>/etc/dnssec-trust-anchors.d/*.positive</filename></para>
<para><filename>/run/dnssec-trust-anchors.d/*.positive</filename></para>
<para><filename>/usr/lib/dnssec-trust-anchors.d/*.positive</filename></para>
<para><filename>/etc/dnssec-trust-anchors.d/*.negative</filename></para>
<para><filename>/run/dnssec-trust-anchors.d/*.negative</filename></para>
<para><filename>/usr/lib/dnssec-trust-anchors.d/*.negative</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>The DNSSEC trust anchor configuration files define positive
and negative trust anchors
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
bases DNSSEC integrity proofs on.</para>
</refsect1>
<refsect1>
<title>Positive Trust Anchors</title>
<para>Positive trust anchor configuration files contain DNSKEY and
DS resource record definitions to use as base for DNSSEC integrity
proofs. See <ulink
url="https://tools.ietf.org/html/rfc4035#section-4.4">RFC 4035,
Section 4.4</ulink> for more information about DNSSEC trust
anchors.</para>
<para>Positive trust anchors are read from files with the suffix
<filename>.positive</filename> located in
<filename>/etc/dnssec-trust-anchors.d/</filename>,
<filename>/run/dnssec-trust-anchors.d/</filename> and
<filename>/usr/lib/dnssec-trust-anchors.d/</filename>. These
directories are searched in the specified order, and a trust
anchor file of the same name in an earlier path overrides a trust
anchor files in a later path. To disable a trust anchor file
shipped in <filename>/usr/lib/dnssec-trust-anchors.d/</filename>
it is sufficient to provide an identically-named file in
<filename>/etc/dnssec-trust-anchors.d/</filename> or
<filename>/run/dnssec-trust-anchors.d/</filename> that is either
empty or a symlink to <filename>/dev/null</filename> ("masked").</para>
<para>Positive trust anchor files are simple text files resembling
DNS zone files, as documented in <ulink
url="https://tools.ietf.org/html/rfc1035#section-5">RFC 1035, Section
5</ulink>. One DS or DNSKEY resource record may be listed per
line. Empty lines and lines starting with a semicolon
(<literal>;</literal>) are ignored and considered comments. A DS
resource record is specified like in the following example:</para>
<programlisting>. IN DS 19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5</programlisting>
<para>The first word specifies the domain, use
<literal>.</literal> for the root domain. The domain may be
specified with or without trailing dot, which is considered
equivalent. The second word must be <literal>IN</literal> the
third word <literal>DS</literal>. The following words specify the
key tag, signature algorithm, digest algorithm, followed by the
hex-encoded key fingerprint. See <ulink
url="https://tools.ietf.org/html/rfc4034#section-5">RFC 4034,
Section 5</ulink> for details about the precise syntax and meaning
of these fields.</para>
<para>Alternatively, DNSKEY resource records may be used to define
trust anchors, like in the following example:</para>
<programlisting>. IN DNSKEY 257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaDX6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpzW5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relSQageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0=</programlisting>
<para>The first word specifies the domain again, the second word
must be <literal>IN</literal>, followed by
<literal>DNSKEY</literal>. The subsequent words encode the DNSKEY
flags, protocol and algorithm fields, followed by the key data
encoded in Base64. See See <ulink
url="https://tools.ietf.org/html/rfc4034#section-2">RFC 4034,
Section 2</ulink> for details about the precise syntax and meaning
of these fields.</para>
<para>If multiple DS or DNSKEY records are defined for the same
domain (possibly even in different trust anchor files), all keys
are used and are considered equivalent as base for DNSSEC
proofs.</para>
<para>Note that <filename>systemd-resolved</filename> will
automatically use a built-in trust anchor key for the Internet
root domain if no positive trust anchors are defined for the root
domain. In most cases it is hence unnecessary to define an
explicit key with trust anchor files. The built-in key is disabled
as soon as at least one trust anchor key for the root domain is
defined in trust anchor files.</para>
<para>It is generally recommended to encode trust anchors in DS
resource records, rather than DNSKEY resource records.</para>
<para>If a trust anchor specified via a DS record is found revoked
it is automatically removed from the trust anchor database for the
runtime. See <ulink url="https://tools.ietf.org/html/rfc5011">RFC
5011</ulink> for details about revoked trust anchors. Note that
<filename>systemd-resolved</filename> will not update its trust
anchor database from DNS servers automatically. Instead, it is
recommended to update the resolver software or update the new
trust anchor via adding in new trust anchor files.</para>
<para>The current DNSSEC trust anchor for the Internet's root
domain is available a the <ulink
url="https://data.iana.org/root-anchors/root-anchors.xml">IANA
Trust Anchor and Keys</ulink> page.</para>
</refsect1>
<refsect1>
<title>Negative Trust Anchors</title>
<para>Negative trust anchors define domains where DNSSEC
validation shall be turned off. Negative trust anchor files are
found at the same location as positive trust anchor files, and
follow the same overriding rules. They are text files with the
<filename>.negative</filename> suffix. Empty lines and lines whose
first character is <literal>;</literal> are ignored. Each line
specifies one domain name where DNSSEC validation shall be
disabled on.</para>
<para>Negative trust anchors are useful to support private DNS
subtrees that are not referenced from the Internet DNS hierarchy,
and not signed.</para>
<para><ulink url="https://tools.ietf.org/html/rfc7646">RFC
7646</ulink> for details on negative trust anchors.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -124,6 +124,63 @@
global setting is on.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>DNSSEC=</varname></term>
<listitem><para>Takes a boolean argument or
<literal>downgrade-ok</literal>. If true all DNS lookups are
DNSSEC-validated locally. If a response for a lookup request
is detected invalid this is returned as lookup failure to
applications. Note that this mode requires a DNS server that
supports DNSSEC. If the DNS server does not properly support
DNSSEC all validations will fail. If set to
<literal>downgrade-ok</literal> DNSSEC validation is
attempted, but if the server does not support DNSSEC properly,
DNSSEC mode is automatically disabled. Note that this mode
makes DNSSEC validation vulnerable to "downgrade" attacks,
where an attacker might be able to trigger a downgrade to
non-DNSSEC mode by synthesizing a DNS response that suggests
DNSSEC was not supported. If set to false, DNS lookups are not
DNSSEC validated.</para>
<para>Note that DNSSEC validation requires retrieval of
additional DNS data, and thus results in a small DNS look-up
time penalty.</para>
<para>DNSSEC requires knowledge of "trust anchors" to prove
data integrity. The trust anchor for the Internet root domain
is built into the resolver, additional trust anchors may be
defined with
<citerefentry><refentrytitle>dnssec-trust-anchors.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
Trust anchors may change in regular intervals, and old trust
anchors may be revoked. In such a case DNSSEC validation is
not possible until new trust anchors are configured locally or
the resolver software package is updated with the new root
trust anchor. In effect, when the built-in trust anchor is
revoked and <varname>DNSSEC=</varname> is true, all further
lookups will fail, as it cannot be proved anymore whether
lookups are correctly signed, or validly unsigned. If
<varname>DNSSEC=</varname> is set to
<literal>downgrade-ok</literal> the resolver will
automatically turn of DNSSEC validation in such a case.</para>
<para>Client programs looking up DNS data will be informed
whether lookups could be verified using DNSSEC, or whether the
returned data could not be verified (either because the data
was found unsigned in the DNS, or the DNS server did not
support DNSSEC or no appropriate trust anchors were known). In
the latter case it is assumed that client programs employ a
secondary scheme to validate the returned DNS data, should
this be required.</para>
<para>It is recommended to set <varname>DNSSEC=</varname> to
true on systems where it is kown that the DNS server supports
DNSSEC correctly, and where software or trust anchor updates
happen regularly. On other systems it is recommended to set
<varname>DNSSEC=</varname> to
<literal>missing-ok</literal>.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -133,6 +190,7 @@
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>dnssec-trust-anchors.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>resolv.conf</refentrytitle><manvolnum>4</manvolnum></citerefentry>
</para>
</refsect1>

View File

@ -144,7 +144,9 @@
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>dnssec-trust-anchors.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>

View File

@ -74,6 +74,8 @@
#define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted"
#define BUS_ERROR_CONNECTION_FAILURE "org.freedesktop.resolve1.ConnectionFailure"
#define BUS_ERROR_NO_SUCH_SERVICE "org.freedesktop.resolve1.NoSuchService"
#define BUS_ERROR_DNSSEC_FAILED "org.freedesktop.resolve1.DnssecFailed"
#define BUS_ERROR_NO_TRUST_ANCHOR "org.freedesktop.resolve1.NoTrustAnchor"
#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError."
#define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer"

View File

@ -9,7 +9,7 @@ Y https://tools.ietf.org/html/rfc1034 → DOMAIN NAMES - CONCEPTS AND FACILITIES
Y https://tools.ietf.org/html/rfc1035 → DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION
? https://tools.ietf.org/html/rfc1101 → DNS Encoding of Network Names and Other Types
Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts -- Application and Support
https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes
~ https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes
Y https://tools.ietf.org/html/rfc1536 → Common DNS Implementation Errors and Suggested Fixes
Y https://tools.ietf.org/html/rfc1876 → A Means for Expressing Location Information in the Domain Name System
Y https://tools.ietf.org/html/rfc2181 → Clarifications to the DNS Specification
@ -30,9 +30,9 @@ Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Si
~ https://tools.ietf.org/html/rfc4592 → The Role of Wildcards in the Domain Name System
~ https://tools.ietf.org/html/rfc4697 → Observed DNS Resolution Misbehavior
Y https://tools.ietf.org/html/rfc4795 → Link-Local Multicast Name Resolution (LLMNR)
! https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors
Y https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors
https://tools.ietf.org/html/rfc5155 → DNS Security (DNSSEC) Hashed Authenticated Denial of Existence
https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers
Y https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers
Y https://tools.ietf.org/html/rfc5702 → Use of SHA-2 Algorithms with RSA in DNSKEY and RRSIG Resource Records for DNSSEC
Y https://tools.ietf.org/html/rfc5890 → Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework
Y https://tools.ietf.org/html/rfc5891 → Internationalized Domain Names in Applications (IDNA): Protocol
@ -41,7 +41,7 @@ Y https://tools.ietf.org/html/rfc6303 → Locally Served DNS Zones
https://tools.ietf.org/html/rfc6604 → xNAME RCODE and Status Bits Clarification
Y https://tools.ietf.org/html/rfc6605 → Elliptic Curve Digital Signature Algorithm (DSA) for DNSSEC
https://tools.ietf.org/html/rfc6672 → DNAME Redirection in the DNS
https://tools.ietf.org/html/rfc6731 → Improved Recursive DNS Server Selection for Multi-Interfaced Nodes
! https://tools.ietf.org/html/rfc6731 → Improved Recursive DNS Server Selection for Multi-Interfaced Nodes
Y https://tools.ietf.org/html/rfc6761 → Special-Use Domain Names
https://tools.ietf.org/html/rfc6762 → Multicast DNS
https://tools.ietf.org/html/rfc6763 → DNS-Based Service Discovery
@ -51,7 +51,7 @@ Y https://tools.ietf.org/html/rfc6891 → Extension Mechanisms for DNS (EDNS(0))
Y https://tools.ietf.org/html/rfc6944 → Applicability Statement: DNS Security (DNSSEC) DNSKEY Algorithm Implementation Status
Y https://tools.ietf.org/html/rfc6975 → Signaling Cryptographic Algorithm Understanding in DNS Security Extensions (DNSSEC)
https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS
! https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors
Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors
~ https://tools.ietf.org/html/rfc7719 → DNS Terminology
Also relevant:

View File

@ -64,9 +64,12 @@ static int reply_query_state(DnsQuery *q) {
return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted");
case DNS_TRANSACTION_DNSSEC_FAILED:
return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "DNSSEC validation failed: %s",
return sd_bus_reply_method_errorf(q->request, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s",
dnssec_result_to_string(q->answer_dnssec_result));
case DNS_TRANSACTION_NO_TRUST_ANCHOR:
return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known");
case DNS_TRANSACTION_RCODE_FAILURE: {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;

View File

@ -524,6 +524,92 @@ int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) {
return 1;
}
int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) {
bool found = false, other = false;
DnsResourceRecord *rr;
unsigned i;
int r;
assert(a);
assert(rm);
/* Remove all entries matching the specified RR from *a */
DNS_ANSWER_FOREACH(rr, *a) {
r = dns_resource_record_equal(rr, rm);
if (r < 0)
return r;
if (r > 0)
found = true;
else
other = true;
if (found && other)
break;
}
if (!found)
return 0;
if (!other) {
*a = dns_answer_unref(*a); /* Return NULL for the empty answer */
return 1;
}
if ((*a)->n_ref > 1) {
_cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL;
DnsAnswerFlags flags;
int ifindex;
copy = dns_answer_new((*a)->n_rrs);
if (!copy)
return -ENOMEM;
DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) {
r = dns_resource_record_equal(rr, rm);
if (r < 0)
return r;
if (r > 0)
continue;
r = dns_answer_add_raw(copy, rr, ifindex, flags);
if (r < 0)
return r;
}
dns_answer_unref(*a);
*a = copy;
copy = NULL;
return 1;
}
/* Only a single reference, edit in-place */
i = 0;
for (;;) {
if (i >= (*a)->n_rrs)
break;
r = dns_resource_record_equal((*a)->items[i].rr, rm);
if (r < 0)
return r;
if (r > 0) {
/* Kill this entry */
dns_resource_record_unref((*a)->items[i].rr);
memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1));
(*a)->n_rrs --;
continue;
} else
/* Keep this entry */
i++;
}
return 1;
}
int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) {
DnsResourceRecord *rr_source;
int ifindex_source, r;

View File

@ -77,6 +77,8 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free);
int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free);
int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key);
int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr);
int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags);
int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags);

View File

@ -273,13 +273,13 @@ static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
return NULL;
}
static usec_t calculate_until(DnsResourceRecord *rr, usec_t timestamp, bool use_soa_minimum) {
static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) {
uint32_t ttl;
usec_t u;
assert(rr);
ttl = rr->ttl;
ttl = MIN(rr->ttl, nsec_ttl);
if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) {
/* If this is a SOA RR, and it is requested, clamp to
* the SOA's minimum field. This is used when we do
@ -339,7 +339,7 @@ static void dns_cache_item_update_positive(
dns_resource_key_unref(i->key);
i->key = dns_resource_key_ref(rr->key);
i->until = calculate_until(rr, timestamp, false);
i->until = calculate_until(rr, (uint32_t) -1, timestamp, false);
i->authenticated = authenticated;
i->shared_owner = shared_owner;
@ -420,7 +420,7 @@ static int dns_cache_put_positive(
i->type = DNS_CACHE_POSITIVE;
i->key = dns_resource_key_ref(rr->key);
i->rr = dns_resource_record_ref(rr);
i->until = calculate_until(rr, timestamp, false);
i->until = calculate_until(rr, (uint32_t) -1, timestamp, false);
i->authenticated = authenticated;
i->shared_owner = shared_owner;
i->owner_family = owner_family;
@ -448,6 +448,7 @@ static int dns_cache_put_negative(
DnsResourceKey *key,
int rcode,
bool authenticated,
uint32_t nsec_ttl,
usec_t timestamp,
DnsResourceRecord *soa,
int owner_family,
@ -470,13 +471,13 @@ static int dns_cache_put_negative(
if (dns_type_is_pseudo(key->type))
return 0;
if (soa->soa.minimum <= 0 || soa->ttl <= 0) {
if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) {
if (log_get_max_level() >= LOG_DEBUG) {
r = dns_resource_key_to_string(key, &key_str);
if (r < 0)
return r;
log_debug("Not caching negative entry with zero SOA TTL: %s", key_str);
log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", key_str);
}
return 0;
@ -496,7 +497,7 @@ static int dns_cache_put_negative(
return -ENOMEM;
i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN;
i->until = calculate_until(soa, timestamp, true);
i->until = calculate_until(soa, nsec_ttl, timestamp, true);
i->authenticated = authenticated;
i->owner_family = owner_family;
i->owner_address = *owner_address;
@ -571,6 +572,7 @@ int dns_cache_put(
int rcode,
DnsAnswer *answer,
bool authenticated,
uint32_t nsec_ttl,
usec_t timestamp,
int owner_family,
const union in_addr_union *owner_address) {
@ -669,6 +671,7 @@ int dns_cache_put(
key,
rcode,
authenticated,
nsec_ttl,
timestamp,
soa,
owner_family, owner_address);

View File

@ -41,7 +41,7 @@ typedef struct DnsCache {
void dns_cache_flush(DnsCache *c);
void dns_cache_prune(DnsCache *c);
int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, usec_t timestamp, int owner_family, const union in_addr_union *owner_address);
int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address);
int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer, bool *authenticated);
int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address);

View File

@ -38,11 +38,8 @@
* - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
* - multi-label zone compatibility
* - cname/dname compatibility
* - per-interface DNSSEC setting
* - nxdomain on qname
* - retry on failed validation?
* - DNSSEC key revocation support? https://tools.ietf.org/html/rfc5011
* - when doing negative caching, use NSEC/NSEC3 RR instead of SOA for TTL
* - per-interface DNSSEC setting
*
* */
@ -79,9 +76,9 @@ static void initialize_libgcrypt(void) {
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
}
uint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) {
const uint8_t *p;
uint32_t sum;
uint32_t sum, f;
size_t i;
/* The algorithm from RFC 4034, Appendix B. */
@ -89,8 +86,12 @@ uint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
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);
f = (uint32_t) dnskey->dnskey.flags;
if (mask_revoke)
f &= ~DNSKEY_FLAG_REVOKE;
sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
p = dnskey->dnskey.key;
@ -116,15 +117,15 @@ static int rr_compare(const void *a, const void *b) {
assert(*y);
assert((*y)->wire_format);
m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y));
r = memcmp((*x)->wire_format, (*y)->wire_format, m);
r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m);
if (r != 0)
return r;
if ((*x)->wire_format_size < (*y)->wire_format_size)
if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
return -1;
else if ((*x)->wire_format_size > (*y)->wire_format_size)
else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
return 1;
return 0;
@ -494,7 +495,7 @@ static int algorithm_to_gcrypt_md(uint8_t algorithm) {
int dnssec_verify_rrset(
DnsAnswer *a,
DnsResourceKey *key,
const DnsResourceKey *key,
DnsResourceRecord *rrsig,
DnsResourceRecord *dnskey,
usec_t realtime,
@ -605,12 +606,11 @@ int dnssec_verify_rrset(
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;
l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr);
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);
gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l);
}
hash = gcry_md_read(md, 0);
@ -654,7 +654,7 @@ finish:
return r;
}
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) {
assert(rrsig);
assert(dnskey);
@ -671,12 +671,14 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske
return 0;
if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
return 0;
if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE))
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)
if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag)
return 0;
return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
@ -738,7 +740,7 @@ static int dnssec_fix_rrset_ttl(DnsAnswer *a, const DnsResourceKey *key, DnsReso
int dnssec_verify_rrset_search(
DnsAnswer *a,
DnsResourceKey *key,
const DnsResourceKey *key,
DnsAnswer *validated_dnskeys,
usec_t realtime,
DnssecResult *result) {
@ -777,7 +779,7 @@ int dnssec_verify_rrset_search(
continue;
/* Is this a DNSKEY RR that matches they key of our RRSIG? */
r = dnssec_rrsig_match_dnskey(rrsig, dnskey);
r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false);
if (r < 0)
return r;
if (r == 0)
@ -957,7 +959,7 @@ static int digest_to_gcrypt_md(uint8_t algorithm) {
}
}
int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
gcry_md_hd_t md = NULL;
size_t hash_size;
@ -975,12 +977,14 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
return -EINVAL;
if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
return -EKEYREJECTED;
if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE))
return -EKEYREJECTED;
if (dnskey->dnskey.protocol != 3)
return -EKEYREJECTED;
if (dnskey->dnskey.algorithm != ds->ds.algorithm)
return 0;
if (dnssec_keytag(dnskey) != ds->ds.key_tag)
if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag)
return 0;
initialize_libgcrypt();
@ -1004,7 +1008,10 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
return -EIO;
gcry_md_write(md, owner_name, r);
md_add_uint16(md, dnskey->dnskey.flags);
if (mask_revoke)
md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE);
else
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);
@ -1049,7 +1056,9 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_
if (r == 0)
continue;
r = dnssec_verify_dnskey(dnskey, ds);
r = dnssec_verify_dnskey(dnskey, ds, false);
if (r == -EKEYREJECTED)
return 0; /* The DNSKEY is revoked or otherwise invalid, we won't bless it */
if (r < 0)
return r;
if (r > 0)
@ -1073,7 +1082,7 @@ static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) {
}
}
int dnssec_nsec3_hash(const DnsResourceRecord *nsec3, const char *name, void *ret) {
int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX];
gcry_md_hd_t md = NULL;
size_t hash_size;
@ -1089,8 +1098,10 @@ int dnssec_nsec3_hash(const DnsResourceRecord *nsec3, const char *name, void *re
if (nsec3->key->type != DNS_TYPE_NSEC3)
return -EINVAL;
if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX)
if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) {
log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3));
return -EOPNOTSUPP;
}
algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm);
if (algorithm < 0)
@ -1200,7 +1211,7 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourc
return dns_name_equal(a, b);
}
static int nsec3_hashed_domain(const DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) {
static int nsec3_hashed_domain(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) {
_cleanup_free_ char *l = NULL, *hashed_domain = NULL;
uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
int hashed_size;
@ -1238,7 +1249,7 @@ static int nsec3_hashed_domain(const DnsResourceRecord *nsec3, const char *domai
* that there is no proof either way. The latter is the case if a the proof of non-existence of a given
* name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records
* to conclude anything we indicate this by returning NO_RR. */
static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
_cleanup_free_ char *next_closer_domain = NULL, *wildcard = NULL, *wildcard_domain = NULL;
const char *zone, *p, *pp = NULL;
DnsResourceRecord *rr, *enclosure_rr, *suffix_rr, *wildcard_rr = NULL;
@ -1248,7 +1259,6 @@ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecR
assert(key);
assert(result);
assert(authenticated);
/* First step, find the zone name and the NSEC3 parameters of the zone.
* it is sufficient to look for the longest common suffix we find with
@ -1357,7 +1367,10 @@ found_closest_encloser:
else
*result = DNSSEC_NSEC_NODATA;
*authenticated = a;
if (authenticated)
*authenticated = a;
if (ttl)
*ttl = enclosure_rr->ttl;
return 0;
}
@ -1440,7 +1453,6 @@ found_closest_encloser:
if (!no_closer) {
*result = DNSSEC_NSEC_NO_RR;
return 0;
}
@ -1476,12 +1488,16 @@ found_closest_encloser:
}
}
*authenticated = a;
if (authenticated)
*authenticated = a;
if (ttl)
*ttl = enclosure_rr->ttl;
return 0;
}
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
DnsResourceRecord *rr;
bool have_nsec3 = false;
DnsAnswerFlags flags;
@ -1489,7 +1505,6 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
assert(key);
assert(result);
assert(authenticated);
/* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
@ -1512,7 +1527,12 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
*result = DNSSEC_NSEC_CNAME;
else
*result = DNSSEC_NSEC_NODATA;
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
if (authenticated)
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
if (ttl)
*ttl = rr->ttl;
return 0;
}
@ -1521,7 +1541,12 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
return r;
if (r > 0) {
*result = DNSSEC_NSEC_NXDOMAIN;
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
if (authenticated)
*authenticated = flags & DNS_ANSWER_AUTHENTICATED;
if (ttl)
*ttl = rr->ttl;
return 0;
}
break;
@ -1534,7 +1559,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
/* OK, this was not sufficient. Let's see if NSEC3 can help. */
if (have_nsec3)
return dnssec_test_nsec3(answer, key, result, authenticated);
return dnssec_test_nsec3(answer, key, result, authenticated, ttl);
/* No approproate NSEC RR found, report this. */
*result = DNSSEC_NSEC_NO_RR;

View File

@ -72,22 +72,22 @@ enum DnssecResult {
/* The longest digest we'll ever generate, of all digest algorithms we support */
#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32))
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey);
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok);
int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig);
int dnssec_verify_rrset(DnsAnswer *answer, DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result);
int dnssec_verify_rrset_search(DnsAnswer *answer, DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result);
int dnssec_verify_rrset(DnsAnswer *answer, const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result);
int dnssec_verify_rrset_search(DnsAnswer *answer, const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result);
int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds);
int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke);
int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds);
int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key);
uint16_t dnssec_keytag(DnsResourceRecord *dnskey);
uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke);
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max);
int dnssec_nsec3_hash(const DnsResourceRecord *nsec3, const char *name, void *ret);
int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret);
typedef enum DnssecNsecResult {
DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */
@ -99,7 +99,7 @@ typedef enum DnssecNsecResult {
DNSSEC_NSEC_OPTOUT,
} DnssecNsecResult;
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated);
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl);
const char* dnssec_mode_to_string(DnssecMode m) _const_;
DnssecMode dnssec_mode_from_string(const char *s) _pure_;

View File

@ -34,8 +34,9 @@ typedef struct DnsResourceRecord DnsResourceRecord;
typedef struct DnsTxtItem DnsTxtItem;
/* DNSKEY RR flags */
#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8)
#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0)
#define DNSKEY_FLAG_REVOKE (UINT16_C(1) << 7)
#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8)
/* mDNS RR flags */
#define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15)
@ -235,7 +236,7 @@ struct DnsResourceRecord {
};
static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) {
if (_unlikely_(!key))
if (!key)
return NULL;
if (key->_name)
@ -244,6 +245,27 @@ static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) {
return (char*) key + sizeof(DnsResourceKey);
}
static inline const void* DNS_RESOURCE_RECORD_RDATA(DnsResourceRecord *rr) {
if (!rr)
return NULL;
if (!rr->wire_format)
return NULL;
assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
return (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset;
}
static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) {
if (!rr)
return 0;
if (!rr->wire_format)
return 0;
assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
return rr->wire_format_size - rr->wire_format_rdata_offset;
}
DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name);
DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname);
int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name);

View File

@ -19,6 +19,8 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sd-messages.h>
#include "af-list.h"
#include "alloc-util.h"
#include "dns-domain.h"
@ -38,6 +40,7 @@ static void dns_transaction_reset_answer(DnsTransaction *t) {
t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
t->answer_authenticated = false;
t->answer_nsec_ttl = (uint32_t) -1;
}
static void dns_transaction_close_connection(DnsTransaction *t) {
@ -62,6 +65,8 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
if (!t)
return NULL;
log_debug("Freeing transaction %" PRIu16 ".", t->id);
dns_transaction_close_connection(t);
dns_transaction_stop_timeout(t);
@ -106,16 +111,20 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free);
void dns_transaction_gc(DnsTransaction *t) {
bool dns_transaction_gc(DnsTransaction *t) {
assert(t);
if (t->block_gc > 0)
return;
return true;
if (set_isempty(t->notify_query_candidates) &&
set_isempty(t->notify_zone_items) &&
set_isempty(t->notify_transactions))
set_isempty(t->notify_transactions)) {
dns_transaction_free(t);
return false;
}
return true;
}
int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) {
@ -149,6 +158,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
t->dns_udp_fd = -1;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
t->answer_nsec_ttl = (uint32_t) -1;
t->key = dns_resource_key_ref(key);
/* Find a fresh, unused transaction id */
@ -237,6 +247,7 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
if (state == DNS_TRANSACTION_DNSSEC_FAILED)
log_struct(LOG_NOTICE,
LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE),
LOG_MESSAGE("DNSSEC validation failed for question %s: %s", dns_transaction_key_string(t), dnssec_result_to_string(t->answer_dnssec_result)),
"DNS_TRANSACTION=%" PRIu16, t->id,
"DNS_QUESTION=%s", dns_transaction_key_string(t),
@ -473,6 +484,7 @@ static void dns_transaction_cache_answer(DnsTransaction *t) {
t->answer_rcode,
t->answer,
t->answer_authenticated,
t->answer_nsec_ttl,
0,
t->received->family,
&t->received->sender);
@ -718,7 +730,22 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
t->answer_authenticated = false;
/* Block GC while starting requests for additional DNSSEC RRs */
t->block_gc++;
r = dns_transaction_request_dnssec_keys(t);
t->block_gc--;
/* Maybe the transaction is ready for GC'ing now? If so, free it and return. */
if (!dns_transaction_gc(t))
return;
/* Requesting additional keys might have resulted in
* this transaction to fail, since the auxiliary
* request failed for some reason. If so, we are not
* in pending state anymore, and we should exit
* quickly. */
if (t->state != DNS_TRANSACTION_PENDING)
return;
if (r < 0) {
dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
return;
@ -902,6 +929,41 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
return 0;
}
if (dns_name_is_root(DNS_RESOURCE_KEY_NAME(t->key)) &&
t->key->type == DNS_TYPE_DS) {
/* Hmm, this is a request for the root DS? A
* DS RR doesn't exist in the root zone, and
* if our trust anchor didn't know it either,
* this means we cannot do any DNSSEC logic
* anymore. */
if (t->scope->dnssec_mode == DNSSEC_DOWNGRADE_OK) {
/* We are in downgrade mode. In this
* case, synthesize an unsigned empty
* response, so that the any lookup
* depending on this one can continue
* assuming there was no DS, and hence
* the root zone was unsigned. */
t->answer_rcode = DNS_RCODE_SUCCESS;
t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR;
t->answer_authenticated = false;
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
} else
/* If we are not in downgrade mode,
* then fail the lookup, because we
* cannot reasonably answer it. There
* might be DS RRs, but we don't know
* them, and the DNS server won't tell
* them to us (and even if it would,
* we couldn't validate it and trust
* it). */
dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR);
return 0;
}
}
/* Check the zone, but only if this transaction is not used
@ -1209,6 +1271,28 @@ int dns_transaction_go(DnsTransaction *t) {
return 1;
}
static int dns_transaction_find_cyclic(DnsTransaction *t, DnsTransaction *aux) {
DnsTransaction *n;
Iterator i;
int r;
assert(t);
assert(aux);
/* Try to find cyclic dependencies between transaction objects */
if (t == aux)
return 1;
SET_FOREACH(n, aux->notify_transactions, i) {
r = dns_transaction_find_cyclic(t, n);
if (r != 0)
return r;
}
return r;
}
static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) {
DnsTransaction *aux;
int r;
@ -1227,6 +1311,18 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource
*ret = aux;
return 0;
}
r = dns_transaction_find_cyclic(t, aux);
if (r < 0)
return r;
if (r > 0) {
log_debug("Detected potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).",
aux->id,
strna(dns_transaction_key_string(aux)),
t->id,
strna(dns_transaction_key_string(t)));
return -ELOOP;
}
}
r = set_ensure_allocated(&t->dnssec_transactions, NULL);
@ -1263,12 +1359,6 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *
assert(t);
assert(key);
r = dns_resource_key_equal(t->key, key);
if (r < 0)
return r;
if (r > 0) /* Don't go in circles */
return 0;
/* Try to get the data from the trust anchor */
r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a);
if (r < 0)
@ -1283,6 +1373,8 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *
/* This didn't work, ask for it via the network/cache then. */
r = dns_transaction_add_dnssec_transaction(t, key, &aux);
if (r == -ELOOP) /* This would result in a cyclic dependency */
return 0;
if (r < 0)
return r;
@ -1292,7 +1384,7 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *
return r;
}
return 0;
return 1;
}
static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags *flags) {
@ -1494,7 +1586,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
if (!ds)
return -ENOMEM;
log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dnssec_keytag(rr));
log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dnssec_keytag(rr, false));
r = dns_transaction_request_dnssec_rr(t, ds);
if (r < 0)
return r;
@ -2116,6 +2208,14 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
/* Maybe warn the user that we
* encountered a revoked
* DNSKEY for a key from our
* trust anchor */
r = dns_trust_anchor_check_revoked(&t->scope->manager->trust_anchor, t->answer, rr->key);
if (r < 0)
return r;
}
/* Add the validated RRset to the new
@ -2288,7 +2388,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
bool authenticated = false;
/* Bummer! Let's check NSEC/NSEC3 */
r = dnssec_test_nsec(t->answer, t->key, &nr, &authenticated);
r = dnssec_test_nsec(t->answer, t->key, &nr, &authenticated, &t->answer_nsec_ttl);
if (r < 0)
return r;
@ -2376,6 +2476,7 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX]
[DNS_TRANSACTION_CONNECTION_FAILURE] = "connection-failure",
[DNS_TRANSACTION_ABORTED] = "aborted",
[DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed",
[DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor",
};
DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState);

View File

@ -39,6 +39,7 @@ enum DnsTransactionState {
DNS_TRANSACTION_CONNECTION_FAILURE,
DNS_TRANSACTION_ABORTED,
DNS_TRANSACTION_DNSSEC_FAILED,
DNS_TRANSACTION_NO_TRUST_ANCHOR,
_DNS_TRANSACTION_STATE_MAX,
_DNS_TRANSACTION_STATE_INVALID = -1
};
@ -80,6 +81,7 @@ struct DnsTransaction {
int answer_rcode;
DnssecResult answer_dnssec_result;
DnsTransactionSource answer_source;
uint32_t answer_nsec_ttl;
/* Indicates whether the primary answer is authenticated,
* i.e. whether the RRs from answer which directly match the
@ -140,7 +142,7 @@ struct DnsTransaction {
int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key);
DnsTransaction* dns_transaction_free(DnsTransaction *t);
void dns_transaction_gc(DnsTransaction *t);
bool dns_transaction_gc(DnsTransaction *t);
int dns_transaction_go(DnsTransaction *t);
void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p);

View File

@ -19,6 +19,8 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sd-messages.h>
#include "alloc-util.h"
#include "conf-files.h"
#include "def.h"
@ -28,11 +30,12 @@
#include "hexdecoct.h"
#include "parse-util.h"
#include "resolved-dns-trust-anchor.h"
#include "resolved-dns-dnssec.h"
#include "set.h"
#include "string-util.h"
#include "strv.h"
static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("systemd/dnssec-trust-anchors.d");
static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d");
/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */
static const uint8_t root_digest[] =
@ -53,6 +56,9 @@ static int dns_trust_anchor_add_builtin(DnsTrustAnchor *d) {
if (hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, ".")))
return 0;
if (hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, ".")))
return 0;
/* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */
rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "");
if (!rr)
@ -188,6 +194,14 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u
r = safe_atou16(flags, &f);
if (r < 0)
return log_warning_errno(r, "Failed to parse DNSKEY flags field %s on line %s:%u", flags, path, line);
if ((f & DNSKEY_FLAG_ZONE_KEY) == 0) {
log_warning("DNSKEY lacks zone key bit set on line %s:%u", path, line);
return -EINVAL;
}
if ((f & DNSKEY_FLAG_REVOKE)) {
log_warning("DNSKEY is already revoked on line %s:%u", path, line);
return -EINVAL;
}
a = dnssec_algorithm_from_string(algorithm);
if (a < 0) {
@ -405,3 +419,170 @@ int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) {
return set_contains(d->negative_by_name, name);
}
static int dns_trust_anchor_remove_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) {
_cleanup_(dns_answer_unrefp) DnsAnswer *new_answer = NULL;
DnsAnswer *old_answer;
int r;
old_answer = hashmap_get(d->positive_by_key, rr->key);
if (!old_answer)
return 0;
new_answer = dns_answer_ref(old_answer);
r = dns_answer_remove_by_rr(&new_answer, rr);
if (r <= 0)
return r;
/* We found the key! Warn the user */
log_struct(LOG_WARNING,
LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED),
LOG_MESSAGE("DNSSEC Trust anchor %s has been revoked. Please update the trust anchor, or upgrade your operating system."), strna(dns_resource_record_to_string(rr)),
"TRUST_ANCHOR=%s", dns_resource_record_to_string(rr),
NULL);
if (dns_answer_size(new_answer) <= 0) {
assert_se(hashmap_remove(d->positive_by_key, rr->key) == old_answer);
dns_answer_unref(old_answer);
return 1;
}
r = hashmap_replace(d->positive_by_key, new_answer->items[0].rr->key, new_answer);
if (r < 0)
return r;
new_answer = NULL;
dns_answer_unref(old_answer);
return 1;
}
static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceRecord *revoked_dnskey) {
DnsAnswer *a;
int r;
assert(d);
assert(revoked_dnskey);
assert(revoked_dnskey->key->type == DNS_TYPE_DNSKEY);
assert(revoked_dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE);
a = hashmap_get(d->positive_by_key, revoked_dnskey->key);
if (a) {
DnsResourceRecord *anchor;
/* First, look for the precise DNSKEY in our trust anchor database */
DNS_ANSWER_FOREACH(anchor, a) {
if (anchor->dnskey.protocol != revoked_dnskey->dnskey.protocol)
continue;
if (anchor->dnskey.algorithm != revoked_dnskey->dnskey.algorithm)
continue;
if (anchor->dnskey.key_size != revoked_dnskey->dnskey.key_size)
continue;
if (((anchor->dnskey.flags ^ revoked_dnskey->dnskey.flags) | DNSKEY_FLAG_REVOKE) != DNSKEY_FLAG_REVOKE)
continue;
if (memcmp(anchor->dnskey.key, revoked_dnskey->dnskey.key, anchor->dnskey.key_size) != 0)
continue;
dns_trust_anchor_remove_revoked(d, anchor);
break;
}
}
a = hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(revoked_dnskey->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(revoked_dnskey->key)));
if (a) {
DnsResourceRecord *anchor;
/* Second, look for DS RRs matching this DNSKEY in our trust anchor database */
DNS_ANSWER_FOREACH(anchor, a) {
r = dnssec_verify_dnskey(revoked_dnskey, anchor, true);
if (r < 0)
return r;
if (r == 0)
continue;
dns_trust_anchor_remove_revoked(d, anchor);
break;
}
}
return 0;
}
static bool dns_trust_anchor_knows_domain(DnsTrustAnchor *d, const char *name) {
assert(d);
/* Returns true if there's an entry for the specified domain
* name in our trust anchor */
return
hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, name)) ||
hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name));
}
int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsAnswer *rrs, const DnsResourceKey *key) {
DnsResourceRecord *dnskey;
int r;
assert(d);
assert(key);
/* Looks for self-signed DNSKEY RRs in "rrs" that have been revoked. */
if (key->type != DNS_TYPE_DNSKEY)
return 0;
DNS_ANSWER_FOREACH(dnskey, rrs) {
DnsResourceRecord *rrsig;
DnssecResult result;
r = dns_resource_key_equal(key, dnskey->key);
if (r < 0)
return r;
if (r == 0)
continue;
/* Is this DNSKEY revoked? */
if ((dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE) == 0)
continue;
/* Could this be interesting to us at all? If not,
* there's no point in looking for and verifying a
* self-signed RRSIG. */
if (!dns_trust_anchor_knows_domain(d, DNS_RESOURCE_KEY_NAME(dnskey->key)))
continue;
/* Look for a self-signed RRSIG */
DNS_ANSWER_FOREACH(rrsig, rrs) {
if (rrsig->key->type != DNS_TYPE_RRSIG)
continue;
r = dnssec_rrsig_match_dnskey(rrsig, dnskey, true);
if (r < 0)
return r;
if (r == 0)
continue;
r = dnssec_verify_rrset(rrs, key, rrsig, dnskey, USEC_INFINITY, &result);
if (r < 0)
return r;
if (result != DNSSEC_VALIDATED)
continue;
/* Bingo! Now, act! */
r = dns_trust_anchor_check_revoked_one(d, dnskey);
if (r < 0)
return r;
}
}
return 0;
}

View File

@ -39,3 +39,5 @@ void dns_trust_anchor_flush(DnsTrustAnchor *d);
int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey* key, DnsAnswer **answer);
int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name);
int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsAnswer *rrs, const DnsResourceKey *key);

View File

@ -122,7 +122,7 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us
dns_transaction_process_reply(t, p);
}
dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, 0, p->family, &p->sender);
dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender);
} else if (dns_packet_validate_query(p) > 0) {
log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p));

View File

@ -107,10 +107,10 @@ static void test_dnssec_verify_rrset2(void) {
assert_se(dnskey->dnskey.key);
log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey));
log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false));
assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0);
assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey) > 0);
assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0);
answer = dns_answer_new(1);
assert_se(answer);
@ -186,10 +186,10 @@ static void test_dnssec_verify_rrset(void) {
assert_se(dnskey->dnskey.key);
log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey));
log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false));
assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0);
assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey) > 0);
assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0);
answer = dns_answer_new(1);
assert_se(answer);
@ -268,10 +268,10 @@ static void test_dnssec_verify_dns_key(void) {
assert_se(dnskey->dnskey.key);
log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey));
log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false));
assert_se(dnssec_verify_dnskey(dnskey, ds1) > 0);
assert_se(dnssec_verify_dnskey(dnskey, ds2) > 0);
assert_se(dnssec_verify_dnskey(dnskey, ds1, false) > 0);
assert_se(dnssec_verify_dnskey(dnskey, ds2, false) > 0);
}
static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) {

View File

@ -87,6 +87,7 @@ _SD_BEGIN_DECLARATIONS;
#define SD_MESSAGE_BOOTCHART SD_ID128_MAKE(9f,26,aa,56,2c,f4,40,c2,b1,6c,77,3d,04,79,b5,18)
#define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d)
#define SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED SD_ID128_MAKE(4d,44,08,cf,d0,d1,44,85,91,84,d1,e6,5d,7c,8a,65)
_SD_END_DECLARATIONS;