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 int dns_trust_anchor_add_builtin(DnsTrustAnchor
*d
) {
46 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
47 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
;
52 r
= hashmap_ensure_allocated(&d
->positive_by_key
, &dns_resource_key_hash_ops
);
56 if (hashmap_get(d
->positive_by_key
, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN
, DNS_TYPE_DS
, ".")))
59 /* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */
60 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_DS
, "");
64 rr
->ds
.key_tag
= 19036;
65 rr
->ds
.algorithm
= DNSSEC_ALGORITHM_RSASHA256
;
66 rr
->ds
.digest_type
= DNSSEC_DIGEST_SHA256
;
67 rr
->ds
.digest_size
= sizeof(root_digest
);
68 rr
->ds
.digest
= memdup(root_digest
, rr
->ds
.digest_size
);
72 answer
= dns_answer_new(1);
76 r
= dns_answer_add(answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
80 r
= hashmap_put(d
->positive_by_key
, rr
->key
, answer
);
88 static int dns_trust_anchor_load_positive(DnsTrustAnchor
*d
, const char *path
, unsigned line
, const char *s
) {
89 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
90 _cleanup_free_
char *domain
= NULL
, *class = NULL
, *type
= NULL
;
91 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
;
92 DnsAnswer
*old_answer
= NULL
;
99 r
= extract_first_word(&p
, &domain
, NULL
, EXTRACT_QUOTES
);
101 return log_warning_errno(r
, "Unable to parse domain in line %s:%u: %m", path
, line
);
103 if (!dns_name_is_valid(domain
)) {
104 log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain
, path
, line
);
108 r
= extract_many_words(&p
, NULL
, 0, &class, &type
, NULL
);
110 return log_warning_errno(r
, "Unable to parse class and type in line %s:%u: %m", path
, line
);
112 log_warning("Missing class or type in line %s:%u", path
, line
);
116 if (!strcaseeq(class, "IN")) {
117 log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path
, line
);
121 if (strcaseeq(type
, "DS")) {
122 _cleanup_free_
char *key_tag
= NULL
, *algorithm
= NULL
, *digest_type
= NULL
, *digest
= NULL
;
123 _cleanup_free_
void *dd
= NULL
;
128 r
= extract_many_words(&p
, NULL
, 0, &key_tag
, &algorithm
, &digest_type
, &digest
, NULL
);
130 log_warning_errno(r
, "Failed to parse DS parameters on line %s:%u: %m", path
, line
);
134 log_warning("Missing DS parameters on line %s:%u", path
, line
);
138 r
= safe_atou16(key_tag
, &kt
);
140 return log_warning_errno(r
, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag
, path
, line
);
142 a
= dnssec_algorithm_from_string(algorithm
);
144 log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm
, path
, line
);
148 dt
= dnssec_digest_from_string(digest_type
);
150 log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type
, path
, line
);
154 r
= unhexmem(digest
, strlen(digest
), &dd
, &l
);
156 log_warning("Failed to parse DS digest %s on line %s:%u", digest
, path
, line
);
160 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_DS
, domain
);
165 rr
->ds
.algorithm
= a
;
166 rr
->ds
.digest_type
= dt
;
167 rr
->ds
.digest_size
= l
;
171 } else if (strcaseeq(type
, "DNSKEY")) {
172 _cleanup_free_
char *flags
= NULL
, *protocol
= NULL
, *algorithm
= NULL
, *key
= NULL
;
173 _cleanup_free_
void *k
= NULL
;
178 r
= extract_many_words(&p
, NULL
, 0, &flags
, &protocol
, &algorithm
, &key
, NULL
);
180 return log_warning_errno(r
, "Failed to parse DNSKEY parameters on line %s:%u: %m", path
, line
);
182 log_warning("Missing DNSKEY parameters on line %s:%u", path
, line
);
186 if (!streq(protocol
, "3")) {
187 log_warning("DNSKEY Protocol is not 3 on line %s:%u", path
, line
);
191 r
= safe_atou16(flags
, &f
);
193 return log_warning_errno(r
, "Failed to parse DNSKEY flags field %s on line %s:%u", flags
, path
, line
);
194 if ((f
& DNSKEY_FLAG_ZONE_KEY
) == 0) {
195 log_warning("DNSKEY lacks zone key bit set on line %s:%u", path
, line
);
198 if ((f
& DNSKEY_FLAG_REVOKE
)) {
199 log_warning("DNSKEY is already revoked on line %s:%u", path
, line
);
203 a
= dnssec_algorithm_from_string(algorithm
);
205 log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm
, path
, line
);
209 r
= unbase64mem(key
, strlen(key
), &k
, &l
);
211 return log_warning_errno(r
, "Failed to parse DNSKEY key data %s on line %s:%u", key
, path
, line
);
213 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_DNSKEY
, domain
);
217 rr
->dnskey
.flags
= f
;
218 rr
->dnskey
.protocol
= 3;
219 rr
->dnskey
.algorithm
= a
;
220 rr
->dnskey
.key_size
= l
;
225 log_warning("RR type %s is not supported, ignoring line %s:%u.", type
, path
, line
);
230 log_warning("Trailing garbage on line %s:%u, ignoring line.", path
, line
);
234 r
= hashmap_ensure_allocated(&d
->positive_by_key
, &dns_resource_key_hash_ops
);
238 old_answer
= hashmap_get(d
->positive_by_key
, rr
->key
);
239 answer
= dns_answer_ref(old_answer
);
241 r
= dns_answer_add_extend(&answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
243 return log_error_errno(r
, "Failed to add trust anchor RR: %m");
245 r
= hashmap_replace(d
->positive_by_key
, rr
->key
, answer
);
247 return log_error_errno(r
, "Failed to add answer to trust anchor: %m");
249 old_answer
= dns_answer_unref(old_answer
);
255 static int dns_trust_anchor_load_negative(DnsTrustAnchor
*d
, const char *path
, unsigned line
, const char *s
) {
256 _cleanup_free_
char *domain
= NULL
;
263 r
= extract_first_word(&p
, &domain
, NULL
, EXTRACT_QUOTES
);
265 return log_warning_errno(r
, "Unable to parse line %s:%u: %m", path
, line
);
267 if (!dns_name_is_valid(domain
)) {
268 log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain
, path
, line
);
273 log_warning("Trailing garbage at line %s:%u, ignoring line.", path
, line
);
277 r
= set_ensure_allocated(&d
->negative_by_name
, &dns_name_hash_ops
);
281 r
= set_put(d
->negative_by_name
, domain
);
290 static int dns_trust_anchor_load_files(
293 int (*loader
)(DnsTrustAnchor
*d
, const char *path
, unsigned n
, const char *line
)) {
295 _cleanup_strv_free_
char **files
= NULL
;
303 r
= conf_files_list_nulstr(&files
, suffix
, NULL
, trust_anchor_dirs
);
305 return log_error_errno(r
, "Failed to enumerate %s trust anchor files: %m", suffix
);
307 STRV_FOREACH(f
, files
) {
308 _cleanup_fclose_
FILE *g
= NULL
;
317 log_warning_errno(errno
, "Failed to open %s: %m", *f
);
321 FOREACH_LINE(line
, g
, log_warning_errno(errno
, "Failed to read %s, ignoring: %m", *f
)) {
333 (void) loader(d
, *f
, n
, l
);
340 static void dns_trust_anchor_dump(DnsTrustAnchor
*d
) {
346 log_info("Positive Trust Anchors:");
347 HASHMAP_FOREACH(a
, d
->positive_by_key
, i
) {
348 DnsResourceRecord
*rr
;
350 DNS_ANSWER_FOREACH(rr
, a
)
351 log_info("%s", dns_resource_record_to_string(rr
));
354 if (!set_isempty(d
->negative_by_name
)) {
356 log_info("Negative trust anchors:");
358 SET_FOREACH(n
, d
->negative_by_name
, i
)
359 log_info("%s%s", n
, endswith(n
, ".") ? "" : ".");
363 int dns_trust_anchor_load(DnsTrustAnchor
*d
) {
368 /* If loading things from disk fails, we don't consider this fatal */
369 (void) dns_trust_anchor_load_files(d
, ".positive", dns_trust_anchor_load_positive
);
370 (void) dns_trust_anchor_load_files(d
, ".negative", dns_trust_anchor_load_negative
);
372 /* However, if the built-in DS fails, then we have a problem. */
373 r
= dns_trust_anchor_add_builtin(d
);
375 return log_error_errno(r
, "Failed to add trust anchor built-in: %m");
377 dns_trust_anchor_dump(d
);
382 void dns_trust_anchor_flush(DnsTrustAnchor
*d
) {
387 while ((a
= hashmap_steal_first(d
->positive_by_key
)))
390 d
->positive_by_key
= hashmap_free(d
->positive_by_key
);
391 d
->negative_by_name
= set_free_free(d
->negative_by_name
);
394 int dns_trust_anchor_lookup_positive(DnsTrustAnchor
*d
, const DnsResourceKey
*key
, DnsAnswer
**ret
) {
401 /* We only serve DS and DNSKEY RRs. */
402 if (!IN_SET(key
->type
, DNS_TYPE_DS
, DNS_TYPE_DNSKEY
))
405 a
= hashmap_get(d
->positive_by_key
, key
);
409 *ret
= dns_answer_ref(a
);
413 int dns_trust_anchor_lookup_negative(DnsTrustAnchor
*d
, const char *name
) {
417 return set_contains(d
->negative_by_name
, name
);
420 static int dns_trust_anchor_remove_revoked(DnsTrustAnchor
*d
, DnsResourceRecord
*rr
) {
421 _cleanup_(dns_answer_unrefp
) DnsAnswer
*new_answer
= NULL
;
422 DnsAnswer
*old_answer
;
425 old_answer
= hashmap_get(d
->positive_by_key
, rr
->key
);
429 new_answer
= dns_answer_ref(old_answer
);
431 r
= dns_answer_remove_by_rr(&new_answer
, rr
);
435 /* We found the key! Warn the user */
436 log_struct(LOG_WARNING
,
437 LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED
),
438 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
)),
439 "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr
),
442 if (dns_answer_size(new_answer
) <= 0) {
443 assert_se(hashmap_remove(d
->positive_by_key
, rr
->key
) == old_answer
);
444 dns_answer_unref(old_answer
);
448 r
= hashmap_replace(d
->positive_by_key
, new_answer
->items
[0].rr
->key
, new_answer
);
453 dns_answer_unref(old_answer
);
457 static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor
*d
, DnsResourceRecord
*revoked_dnskey
) {
462 assert(revoked_dnskey
);
463 assert(revoked_dnskey
->key
->type
== DNS_TYPE_DNSKEY
);
464 assert(revoked_dnskey
->dnskey
.flags
& DNSKEY_FLAG_REVOKE
);
466 a
= hashmap_get(d
->positive_by_key
, revoked_dnskey
->key
);
468 DnsResourceRecord
*anchor
;
470 /* First, look for the precise DNSKEY in our trust anchor database */
472 DNS_ANSWER_FOREACH(anchor
, a
) {
474 if (anchor
->dnskey
.protocol
!= revoked_dnskey
->dnskey
.protocol
)
477 if (anchor
->dnskey
.algorithm
!= revoked_dnskey
->dnskey
.algorithm
)
480 if (anchor
->dnskey
.key_size
!= revoked_dnskey
->dnskey
.key_size
)
483 if (((anchor
->dnskey
.flags
^ revoked_dnskey
->dnskey
.flags
) | DNSKEY_FLAG_REVOKE
) != DNSKEY_FLAG_REVOKE
)
486 if (memcmp(anchor
->dnskey
.key
, revoked_dnskey
->dnskey
.key
, anchor
->dnskey
.key_size
) != 0)
489 dns_trust_anchor_remove_revoked(d
, anchor
);
494 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
)));
496 DnsResourceRecord
*anchor
;
498 /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */
500 DNS_ANSWER_FOREACH(anchor
, a
) {
502 r
= dnssec_verify_dnskey(revoked_dnskey
, anchor
, true);
508 dns_trust_anchor_remove_revoked(d
, anchor
);
516 static bool dns_trust_anchor_knows_domain(DnsTrustAnchor
*d
, const char *name
) {
519 /* Returns true if there's an entry for the specified domain
520 * name in our trust anchor */
523 hashmap_contains(d
->positive_by_key
, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN
, DNS_TYPE_DNSKEY
, name
)) ||
524 hashmap_contains(d
->positive_by_key
, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN
, DNS_TYPE_DS
, name
));
527 int dns_trust_anchor_check_revoked(DnsTrustAnchor
*d
, DnsAnswer
*rrs
, const DnsResourceKey
*key
) {
528 DnsResourceRecord
*dnskey
;
534 /* Looks for self-signed DNSKEY RRs in "rrs" that have been revoked. */
536 if (key
->type
!= DNS_TYPE_DNSKEY
)
539 DNS_ANSWER_FOREACH(dnskey
, rrs
) {
540 DnsResourceRecord
*rrsig
;
543 r
= dns_resource_key_equal(key
, dnskey
->key
);
549 /* Is this DNSKEY revoked? */
550 if ((dnskey
->dnskey
.flags
& DNSKEY_FLAG_REVOKE
) == 0)
553 /* Could this be interesting to us at all? If not,
554 * there's no point in looking for and verifying a
555 * self-signed RRSIG. */
556 if (!dns_trust_anchor_knows_domain(d
, DNS_RESOURCE_KEY_NAME(dnskey
->key
)))
559 /* Look for a self-signed RRSIG */
560 DNS_ANSWER_FOREACH(rrsig
, rrs
) {
562 if (rrsig
->key
->type
!= DNS_TYPE_RRSIG
)
565 r
= dnssec_rrsig_match_dnskey(rrsig
, dnskey
, true);
571 r
= dnssec_verify_rrset(rrs
, key
, rrsig
, dnskey
, USEC_INFINITY
, &result
);
574 if (result
!= DNSSEC_VALIDATED
)
577 /* Bingo! Now, act! */
578 r
= dns_trust_anchor_check_revoked_one(d
, dnskey
);