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:
commit
16b85c51ed
@ -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 \
|
||||
|
189
man/dnssec-trust-anchors.d.xml
Normal file
189
man/dnssec-trust-anchors.d.xml
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user