1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2015 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <sd-messages.h>
24 #include "alloc-util.h"
25 #include "conf-files.h"
27 #include "dns-domain.h"
30 #include "hexdecoct.h"
31 #include "parse-util.h"
32 #include "resolved-dns-trust-anchor.h"
33 #include "resolved-dns-dnssec.h"
35 #include "string-util.h"
38 static const char trust_anchor_dirs
[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d");
40 /* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */
41 static const uint8_t root_digest
[] =
42 { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60,
43 0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 };
45 static bool dns_trust_anchor_knows_domain_positive(DnsTrustAnchor
*d
, const char *name
) {
48 /* Returns true if there's an entry for the specified domain
49 * name in our trust anchor */
52 hashmap_contains(d
->positive_by_key
, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN
, DNS_TYPE_DNSKEY
, name
)) ||
53 hashmap_contains(d
->positive_by_key
, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN
, DNS_TYPE_DS
, name
));
56 static int dns_trust_anchor_add_builtin_positive(DnsTrustAnchor
*d
) {
57 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
58 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
;
63 r
= hashmap_ensure_allocated(&d
->positive_by_key
, &dns_resource_key_hash_ops
);
67 /* Only add the built-in trust anchor if there's neither a DS
68 * nor a DNSKEY defined for the root domain. That way users
69 * have an easy way to override the root domain DS/DNSKEY
71 if (dns_trust_anchor_knows_domain_positive(d
, "."))
74 /* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */
75 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_DS
, "");
79 rr
->ds
.key_tag
= 19036;
80 rr
->ds
.algorithm
= DNSSEC_ALGORITHM_RSASHA256
;
81 rr
->ds
.digest_type
= DNSSEC_DIGEST_SHA256
;
82 rr
->ds
.digest_size
= sizeof(root_digest
);
83 rr
->ds
.digest
= memdup(root_digest
, rr
->ds
.digest_size
);
87 answer
= dns_answer_new(1);
91 r
= dns_answer_add(answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
95 r
= hashmap_put(d
->positive_by_key
, rr
->key
, answer
);
103 static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor
*d
) {
105 static const char private_domains
[] =
106 /* RFC 6761 says that .test is a special domain for
107 * testing and not to be installed in the root zone */
110 /* RFC 6761 says that these reverse IP lookup ranges
111 * are for private addresses, and hence should not
112 * show up in the root zone */
114 "16.172.in-addr.arpa\0"
115 "17.172.in-addr.arpa\0"
116 "18.172.in-addr.arpa\0"
117 "19.172.in-addr.arpa\0"
118 "20.172.in-addr.arpa\0"
119 "21.172.in-addr.arpa\0"
120 "22.172.in-addr.arpa\0"
121 "23.172.in-addr.arpa\0"
122 "24.172.in-addr.arpa\0"
123 "25.172.in-addr.arpa\0"
124 "26.172.in-addr.arpa\0"
125 "27.172.in-addr.arpa\0"
126 "28.172.in-addr.arpa\0"
127 "29.172.in-addr.arpa\0"
128 "30.172.in-addr.arpa\0"
129 "31.172.in-addr.arpa\0"
130 "168.192.in-addr.arpa\0"
132 /* RFC 6762 reserves the .local domain for Multicast
133 * DNS, it hence cannot appear in the root zone. (Note
134 * that we by default do not route .local traffic to
135 * DNS anyway, except when a configured search domain
139 /* These two are well known, popular private zone
140 * TLDs, that are blocked from delegation, according
142 * http://icannwiki.com/Name_Collision#NGPC_Resolution
144 * There's also ongoing work on making this official
146 * https://www.ietf.org/archive/id/draft-chapin-additional-reserved-tlds-02.txt */
150 /* The following four TLDs are suggested for private
151 * zones in RFC 6762, Appendix G, and are hence very
152 * unlikely to be made official TLDs any day soon */
163 /* Only add the built-in trust anchor if there's no negative
164 * trust anchor defined at all. This enables easy overriding
165 * of negative trust anchors. */
167 if (set_size(d
->negative_by_name
) > 0)
170 r
= set_ensure_allocated(&d
->negative_by_name
, &dns_name_hash_ops
);
174 /* We add a couple of domains as default negative trust
175 * anchors, where it's very unlikely they will be installed in
176 * the root zone. If they exist they must be private, and thus
179 NULSTR_FOREACH(name
, private_domains
) {
181 if (dns_trust_anchor_knows_domain_positive(d
, name
))
184 r
= set_put_strdup(d
->negative_by_name
, name
);
192 static int dns_trust_anchor_load_positive(DnsTrustAnchor
*d
, const char *path
, unsigned line
, const char *s
) {
193 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
194 _cleanup_free_
char *domain
= NULL
, *class = NULL
, *type
= NULL
;
195 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
;
196 DnsAnswer
*old_answer
= NULL
;
203 r
= extract_first_word(&p
, &domain
, NULL
, EXTRACT_QUOTES
);
205 return log_warning_errno(r
, "Unable to parse domain in line %s:%u: %m", path
, line
);
207 if (!dns_name_is_valid(domain
)) {
208 log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain
, path
, line
);
212 r
= extract_many_words(&p
, NULL
, 0, &class, &type
, NULL
);
214 return log_warning_errno(r
, "Unable to parse class and type in line %s:%u: %m", path
, line
);
216 log_warning("Missing class or type in line %s:%u", path
, line
);
220 if (!strcaseeq(class, "IN")) {
221 log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path
, line
);
225 if (strcaseeq(type
, "DS")) {
226 _cleanup_free_
char *key_tag
= NULL
, *algorithm
= NULL
, *digest_type
= NULL
, *digest
= NULL
;
227 _cleanup_free_
void *dd
= NULL
;
232 r
= extract_many_words(&p
, NULL
, 0, &key_tag
, &algorithm
, &digest_type
, &digest
, NULL
);
234 log_warning_errno(r
, "Failed to parse DS parameters on line %s:%u: %m", path
, line
);
238 log_warning("Missing DS parameters on line %s:%u", path
, line
);
242 r
= safe_atou16(key_tag
, &kt
);
244 return log_warning_errno(r
, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag
, path
, line
);
246 a
= dnssec_algorithm_from_string(algorithm
);
248 log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm
, path
, line
);
252 dt
= dnssec_digest_from_string(digest_type
);
254 log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type
, path
, line
);
258 r
= unhexmem(digest
, strlen(digest
), &dd
, &l
);
260 log_warning("Failed to parse DS digest %s on line %s:%u", digest
, path
, line
);
264 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_DS
, domain
);
269 rr
->ds
.algorithm
= a
;
270 rr
->ds
.digest_type
= dt
;
271 rr
->ds
.digest_size
= l
;
275 } else if (strcaseeq(type
, "DNSKEY")) {
276 _cleanup_free_
char *flags
= NULL
, *protocol
= NULL
, *algorithm
= NULL
, *key
= NULL
;
277 _cleanup_free_
void *k
= NULL
;
282 r
= extract_many_words(&p
, NULL
, 0, &flags
, &protocol
, &algorithm
, &key
, NULL
);
284 return log_warning_errno(r
, "Failed to parse DNSKEY parameters on line %s:%u: %m", path
, line
);
286 log_warning("Missing DNSKEY parameters on line %s:%u", path
, line
);
290 if (!streq(protocol
, "3")) {
291 log_warning("DNSKEY Protocol is not 3 on line %s:%u", path
, line
);
295 r
= safe_atou16(flags
, &f
);
297 return log_warning_errno(r
, "Failed to parse DNSKEY flags field %s on line %s:%u", flags
, path
, line
);
298 if ((f
& DNSKEY_FLAG_ZONE_KEY
) == 0) {
299 log_warning("DNSKEY lacks zone key bit set on line %s:%u", path
, line
);
302 if ((f
& DNSKEY_FLAG_REVOKE
)) {
303 log_warning("DNSKEY is already revoked on line %s:%u", path
, line
);
307 a
= dnssec_algorithm_from_string(algorithm
);
309 log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm
, path
, line
);
313 r
= unbase64mem(key
, strlen(key
), &k
, &l
);
315 return log_warning_errno(r
, "Failed to parse DNSKEY key data %s on line %s:%u", key
, path
, line
);
317 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_DNSKEY
, domain
);
321 rr
->dnskey
.flags
= f
;
322 rr
->dnskey
.protocol
= 3;
323 rr
->dnskey
.algorithm
= a
;
324 rr
->dnskey
.key_size
= l
;
329 log_warning("RR type %s is not supported, ignoring line %s:%u.", type
, path
, line
);
334 log_warning("Trailing garbage on line %s:%u, ignoring line.", path
, line
);
338 r
= hashmap_ensure_allocated(&d
->positive_by_key
, &dns_resource_key_hash_ops
);
342 old_answer
= hashmap_get(d
->positive_by_key
, rr
->key
);
343 answer
= dns_answer_ref(old_answer
);
345 r
= dns_answer_add_extend(&answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
347 return log_error_errno(r
, "Failed to add trust anchor RR: %m");
349 r
= hashmap_replace(d
->positive_by_key
, rr
->key
, answer
);
351 return log_error_errno(r
, "Failed to add answer to trust anchor: %m");
353 old_answer
= dns_answer_unref(old_answer
);
359 static int dns_trust_anchor_load_negative(DnsTrustAnchor
*d
, const char *path
, unsigned line
, const char *s
) {
360 _cleanup_free_
char *domain
= NULL
;
367 r
= extract_first_word(&p
, &domain
, NULL
, EXTRACT_QUOTES
);
369 return log_warning_errno(r
, "Unable to parse line %s:%u: %m", path
, line
);
371 if (!dns_name_is_valid(domain
)) {
372 log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain
, path
, line
);
377 log_warning("Trailing garbage at line %s:%u, ignoring line.", path
, line
);
381 r
= set_ensure_allocated(&d
->negative_by_name
, &dns_name_hash_ops
);
385 r
= set_put(d
->negative_by_name
, domain
);
394 static int dns_trust_anchor_load_files(
397 int (*loader
)(DnsTrustAnchor
*d
, const char *path
, unsigned n
, const char *line
)) {
399 _cleanup_strv_free_
char **files
= NULL
;
407 r
= conf_files_list_nulstr(&files
, suffix
, NULL
, trust_anchor_dirs
);
409 return log_error_errno(r
, "Failed to enumerate %s trust anchor files: %m", suffix
);
411 STRV_FOREACH(f
, files
) {
412 _cleanup_fclose_
FILE *g
= NULL
;
421 log_warning_errno(errno
, "Failed to open %s: %m", *f
);
425 FOREACH_LINE(line
, g
, log_warning_errno(errno
, "Failed to read %s, ignoring: %m", *f
)) {
437 (void) loader(d
, *f
, n
, l
);
444 static int domain_name_cmp(const void *a
, const void *b
) {
445 char **x
= (char**) a
, **y
= (char**) b
;
447 return dns_name_compare_func(*x
, *y
);
450 static int dns_trust_anchor_dump(DnsTrustAnchor
*d
) {
456 if (hashmap_isempty(d
->positive_by_key
))
457 log_info("No positive trust anchors defined.");
459 log_info("Positive Trust Anchors:");
460 HASHMAP_FOREACH(a
, d
->positive_by_key
, i
) {
461 DnsResourceRecord
*rr
;
463 DNS_ANSWER_FOREACH(rr
, a
)
464 log_info("%s", dns_resource_record_to_string(rr
));
468 if (set_isempty(d
->negative_by_name
))
469 log_info("No negative trust anchors defined.");
471 _cleanup_free_
char **l
= NULL
, *j
= NULL
;
473 l
= set_get_strv(d
->negative_by_name
);
477 qsort_safe(l
, set_size(d
->negative_by_name
), sizeof(char*), domain_name_cmp
);
479 j
= strv_join(l
, " ");
483 log_info("Negative trust anchors: %s", j
);
489 int dns_trust_anchor_load(DnsTrustAnchor
*d
) {
494 /* If loading things from disk fails, we don't consider this fatal */
495 (void) dns_trust_anchor_load_files(d
, ".positive", dns_trust_anchor_load_positive
);
496 (void) dns_trust_anchor_load_files(d
, ".negative", dns_trust_anchor_load_negative
);
498 /* However, if the built-in DS fails, then we have a problem. */
499 r
= dns_trust_anchor_add_builtin_positive(d
);
501 return log_error_errno(r
, "Failed to add built-in positive trust anchor: %m");
503 r
= dns_trust_anchor_add_builtin_negative(d
);
505 return log_error_errno(r
, "Failed to add built-in negative trust anchor: %m");
507 dns_trust_anchor_dump(d
);
512 void dns_trust_anchor_flush(DnsTrustAnchor
*d
) {
517 while ((a
= hashmap_steal_first(d
->positive_by_key
)))
520 d
->positive_by_key
= hashmap_free(d
->positive_by_key
);
521 d
->negative_by_name
= set_free_free(d
->negative_by_name
);
524 int dns_trust_anchor_lookup_positive(DnsTrustAnchor
*d
, const DnsResourceKey
*key
, DnsAnswer
**ret
) {
531 /* We only serve DS and DNSKEY RRs. */
532 if (!IN_SET(key
->type
, DNS_TYPE_DS
, DNS_TYPE_DNSKEY
))
535 a
= hashmap_get(d
->positive_by_key
, key
);
539 *ret
= dns_answer_ref(a
);
543 int dns_trust_anchor_lookup_negative(DnsTrustAnchor
*d
, const char *name
) {
547 return set_contains(d
->negative_by_name
, name
);
550 static int dns_trust_anchor_remove_revoked(DnsTrustAnchor
*d
, DnsResourceRecord
*rr
) {
551 _cleanup_(dns_answer_unrefp
) DnsAnswer
*new_answer
= NULL
;
552 DnsAnswer
*old_answer
;
555 old_answer
= hashmap_get(d
->positive_by_key
, rr
->key
);
559 new_answer
= dns_answer_ref(old_answer
);
561 r
= dns_answer_remove_by_rr(&new_answer
, rr
);
565 /* We found the key! Warn the user */
566 log_struct(LOG_WARNING
,
567 LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED
),
568 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
)),
569 "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr
),
572 if (dns_answer_size(new_answer
) <= 0) {
573 assert_se(hashmap_remove(d
->positive_by_key
, rr
->key
) == old_answer
);
574 dns_answer_unref(old_answer
);
578 r
= hashmap_replace(d
->positive_by_key
, new_answer
->items
[0].rr
->key
, new_answer
);
583 dns_answer_unref(old_answer
);
587 static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor
*d
, DnsResourceRecord
*revoked_dnskey
) {
592 assert(revoked_dnskey
);
593 assert(revoked_dnskey
->key
->type
== DNS_TYPE_DNSKEY
);
594 assert(revoked_dnskey
->dnskey
.flags
& DNSKEY_FLAG_REVOKE
);
596 a
= hashmap_get(d
->positive_by_key
, revoked_dnskey
->key
);
598 DnsResourceRecord
*anchor
;
600 /* First, look for the precise DNSKEY in our trust anchor database */
602 DNS_ANSWER_FOREACH(anchor
, a
) {
604 if (anchor
->dnskey
.protocol
!= revoked_dnskey
->dnskey
.protocol
)
607 if (anchor
->dnskey
.algorithm
!= revoked_dnskey
->dnskey
.algorithm
)
610 if (anchor
->dnskey
.key_size
!= revoked_dnskey
->dnskey
.key_size
)
613 if (((anchor
->dnskey
.flags
^ revoked_dnskey
->dnskey
.flags
) | DNSKEY_FLAG_REVOKE
) != DNSKEY_FLAG_REVOKE
)
616 if (memcmp(anchor
->dnskey
.key
, revoked_dnskey
->dnskey
.key
, anchor
->dnskey
.key_size
) != 0)
619 dns_trust_anchor_remove_revoked(d
, anchor
);
624 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
)));
626 DnsResourceRecord
*anchor
;
628 /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */
630 DNS_ANSWER_FOREACH(anchor
, a
) {
632 r
= dnssec_verify_dnskey(revoked_dnskey
, anchor
, true);
638 dns_trust_anchor_remove_revoked(d
, anchor
);
646 int dns_trust_anchor_check_revoked(DnsTrustAnchor
*d
, DnsAnswer
*rrs
, const DnsResourceKey
*key
) {
647 DnsResourceRecord
*dnskey
;
653 /* Looks for self-signed DNSKEY RRs in "rrs" that have been revoked. */
655 if (key
->type
!= DNS_TYPE_DNSKEY
)
658 DNS_ANSWER_FOREACH(dnskey
, rrs
) {
659 DnsResourceRecord
*rrsig
;
662 r
= dns_resource_key_equal(key
, dnskey
->key
);
668 /* Is this DNSKEY revoked? */
669 if ((dnskey
->dnskey
.flags
& DNSKEY_FLAG_REVOKE
) == 0)
672 /* Could this be interesting to us at all? If not,
673 * there's no point in looking for and verifying a
674 * self-signed RRSIG. */
675 if (!dns_trust_anchor_knows_domain_positive(d
, DNS_RESOURCE_KEY_NAME(dnskey
->key
)))
678 /* Look for a self-signed RRSIG */
679 DNS_ANSWER_FOREACH(rrsig
, rrs
) {
681 if (rrsig
->key
->type
!= DNS_TYPE_RRSIG
)
684 r
= dnssec_rrsig_match_dnskey(rrsig
, dnskey
, true);
690 r
= dnssec_verify_rrset(rrs
, key
, rrsig
, dnskey
, USEC_INFINITY
, &result
);
693 if (result
!= DNSSEC_VALIDATED
)
696 /* Bingo! Now, act! */
697 r
= dns_trust_anchor_check_revoked_one(d
, dnskey
);