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(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_load_positive(DnsTrustAnchor
*d
, const char *path
, unsigned line
, const char *s
) {
104 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
105 _cleanup_free_
char *domain
= NULL
, *class = NULL
, *type
= NULL
;
106 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
;
107 DnsAnswer
*old_answer
= NULL
;
114 r
= extract_first_word(&p
, &domain
, NULL
, EXTRACT_QUOTES
);
116 return log_warning_errno(r
, "Unable to parse domain in line %s:%u: %m", path
, line
);
118 if (!dns_name_is_valid(domain
)) {
119 log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain
, path
, line
);
123 r
= extract_many_words(&p
, NULL
, 0, &class, &type
, NULL
);
125 return log_warning_errno(r
, "Unable to parse class and type in line %s:%u: %m", path
, line
);
127 log_warning("Missing class or type in line %s:%u", path
, line
);
131 if (!strcaseeq(class, "IN")) {
132 log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path
, line
);
136 if (strcaseeq(type
, "DS")) {
137 _cleanup_free_
char *key_tag
= NULL
, *algorithm
= NULL
, *digest_type
= NULL
, *digest
= NULL
;
138 _cleanup_free_
void *dd
= NULL
;
143 r
= extract_many_words(&p
, NULL
, 0, &key_tag
, &algorithm
, &digest_type
, &digest
, NULL
);
145 log_warning_errno(r
, "Failed to parse DS parameters on line %s:%u: %m", path
, line
);
149 log_warning("Missing DS parameters on line %s:%u", path
, line
);
153 r
= safe_atou16(key_tag
, &kt
);
155 return log_warning_errno(r
, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag
, path
, line
);
157 a
= dnssec_algorithm_from_string(algorithm
);
159 log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm
, path
, line
);
163 dt
= dnssec_digest_from_string(digest_type
);
165 log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type
, path
, line
);
169 r
= unhexmem(digest
, strlen(digest
), &dd
, &l
);
171 log_warning("Failed to parse DS digest %s on line %s:%u", digest
, path
, line
);
175 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_DS
, domain
);
180 rr
->ds
.algorithm
= a
;
181 rr
->ds
.digest_type
= dt
;
182 rr
->ds
.digest_size
= l
;
186 } else if (strcaseeq(type
, "DNSKEY")) {
187 _cleanup_free_
char *flags
= NULL
, *protocol
= NULL
, *algorithm
= NULL
, *key
= NULL
;
188 _cleanup_free_
void *k
= NULL
;
193 r
= extract_many_words(&p
, NULL
, 0, &flags
, &protocol
, &algorithm
, &key
, NULL
);
195 return log_warning_errno(r
, "Failed to parse DNSKEY parameters on line %s:%u: %m", path
, line
);
197 log_warning("Missing DNSKEY parameters on line %s:%u", path
, line
);
201 if (!streq(protocol
, "3")) {
202 log_warning("DNSKEY Protocol is not 3 on line %s:%u", path
, line
);
206 r
= safe_atou16(flags
, &f
);
208 return log_warning_errno(r
, "Failed to parse DNSKEY flags field %s on line %s:%u", flags
, path
, line
);
209 if ((f
& DNSKEY_FLAG_ZONE_KEY
) == 0) {
210 log_warning("DNSKEY lacks zone key bit set on line %s:%u", path
, line
);
213 if ((f
& DNSKEY_FLAG_REVOKE
)) {
214 log_warning("DNSKEY is already revoked on line %s:%u", path
, line
);
218 a
= dnssec_algorithm_from_string(algorithm
);
220 log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm
, path
, line
);
224 r
= unbase64mem(key
, strlen(key
), &k
, &l
);
226 return log_warning_errno(r
, "Failed to parse DNSKEY key data %s on line %s:%u", key
, path
, line
);
228 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_DNSKEY
, domain
);
232 rr
->dnskey
.flags
= f
;
233 rr
->dnskey
.protocol
= 3;
234 rr
->dnskey
.algorithm
= a
;
235 rr
->dnskey
.key_size
= l
;
240 log_warning("RR type %s is not supported, ignoring line %s:%u.", type
, path
, line
);
245 log_warning("Trailing garbage on line %s:%u, ignoring line.", path
, line
);
249 r
= hashmap_ensure_allocated(&d
->positive_by_key
, &dns_resource_key_hash_ops
);
253 old_answer
= hashmap_get(d
->positive_by_key
, rr
->key
);
254 answer
= dns_answer_ref(old_answer
);
256 r
= dns_answer_add_extend(&answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
258 return log_error_errno(r
, "Failed to add trust anchor RR: %m");
260 r
= hashmap_replace(d
->positive_by_key
, rr
->key
, answer
);
262 return log_error_errno(r
, "Failed to add answer to trust anchor: %m");
264 old_answer
= dns_answer_unref(old_answer
);
270 static int dns_trust_anchor_load_negative(DnsTrustAnchor
*d
, const char *path
, unsigned line
, const char *s
) {
271 _cleanup_free_
char *domain
= NULL
;
278 r
= extract_first_word(&p
, &domain
, NULL
, EXTRACT_QUOTES
);
280 return log_warning_errno(r
, "Unable to parse line %s:%u: %m", path
, line
);
282 if (!dns_name_is_valid(domain
)) {
283 log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain
, path
, line
);
288 log_warning("Trailing garbage at line %s:%u, ignoring line.", path
, line
);
292 r
= set_ensure_allocated(&d
->negative_by_name
, &dns_name_hash_ops
);
296 r
= set_put(d
->negative_by_name
, domain
);
305 static int dns_trust_anchor_load_files(
308 int (*loader
)(DnsTrustAnchor
*d
, const char *path
, unsigned n
, const char *line
)) {
310 _cleanup_strv_free_
char **files
= NULL
;
318 r
= conf_files_list_nulstr(&files
, suffix
, NULL
, trust_anchor_dirs
);
320 return log_error_errno(r
, "Failed to enumerate %s trust anchor files: %m", suffix
);
322 STRV_FOREACH(f
, files
) {
323 _cleanup_fclose_
FILE *g
= NULL
;
332 log_warning_errno(errno
, "Failed to open %s: %m", *f
);
336 FOREACH_LINE(line
, g
, log_warning_errno(errno
, "Failed to read %s, ignoring: %m", *f
)) {
348 (void) loader(d
, *f
, n
, l
);
355 static void dns_trust_anchor_dump(DnsTrustAnchor
*d
) {
361 if (hashmap_isempty(d
->positive_by_key
))
362 log_info("No positive trust anchors defined.");
364 log_info("Positive Trust Anchors:");
365 HASHMAP_FOREACH(a
, d
->positive_by_key
, i
) {
366 DnsResourceRecord
*rr
;
368 DNS_ANSWER_FOREACH(rr
, a
)
369 log_info("%s", dns_resource_record_to_string(rr
));
373 if (set_isempty(d
->negative_by_name
))
374 log_info("No negative trust anchors defined.");
377 log_info("Negative trust anchors:");
379 SET_FOREACH(n
, d
->negative_by_name
, i
)
380 log_info("%s%s", n
, endswith(n
, ".") ? "" : ".");
384 int dns_trust_anchor_load(DnsTrustAnchor
*d
) {
389 /* If loading things from disk fails, we don't consider this fatal */
390 (void) dns_trust_anchor_load_files(d
, ".positive", dns_trust_anchor_load_positive
);
391 (void) dns_trust_anchor_load_files(d
, ".negative", dns_trust_anchor_load_negative
);
393 /* However, if the built-in DS fails, then we have a problem. */
394 r
= dns_trust_anchor_add_builtin(d
);
396 return log_error_errno(r
, "Failed to add trust anchor built-in: %m");
398 dns_trust_anchor_dump(d
);
403 void dns_trust_anchor_flush(DnsTrustAnchor
*d
) {
408 while ((a
= hashmap_steal_first(d
->positive_by_key
)))
411 d
->positive_by_key
= hashmap_free(d
->positive_by_key
);
412 d
->negative_by_name
= set_free_free(d
->negative_by_name
);
415 int dns_trust_anchor_lookup_positive(DnsTrustAnchor
*d
, const DnsResourceKey
*key
, DnsAnswer
**ret
) {
422 /* We only serve DS and DNSKEY RRs. */
423 if (!IN_SET(key
->type
, DNS_TYPE_DS
, DNS_TYPE_DNSKEY
))
426 a
= hashmap_get(d
->positive_by_key
, key
);
430 *ret
= dns_answer_ref(a
);
434 int dns_trust_anchor_lookup_negative(DnsTrustAnchor
*d
, const char *name
) {
438 return set_contains(d
->negative_by_name
, name
);
441 static int dns_trust_anchor_remove_revoked(DnsTrustAnchor
*d
, DnsResourceRecord
*rr
) {
442 _cleanup_(dns_answer_unrefp
) DnsAnswer
*new_answer
= NULL
;
443 DnsAnswer
*old_answer
;
446 old_answer
= hashmap_get(d
->positive_by_key
, rr
->key
);
450 new_answer
= dns_answer_ref(old_answer
);
452 r
= dns_answer_remove_by_rr(&new_answer
, rr
);
456 /* We found the key! Warn the user */
457 log_struct(LOG_WARNING
,
458 LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED
),
459 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
)),
460 "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr
),
463 if (dns_answer_size(new_answer
) <= 0) {
464 assert_se(hashmap_remove(d
->positive_by_key
, rr
->key
) == old_answer
);
465 dns_answer_unref(old_answer
);
469 r
= hashmap_replace(d
->positive_by_key
, new_answer
->items
[0].rr
->key
, new_answer
);
474 dns_answer_unref(old_answer
);
478 static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor
*d
, DnsResourceRecord
*revoked_dnskey
) {
483 assert(revoked_dnskey
);
484 assert(revoked_dnskey
->key
->type
== DNS_TYPE_DNSKEY
);
485 assert(revoked_dnskey
->dnskey
.flags
& DNSKEY_FLAG_REVOKE
);
487 a
= hashmap_get(d
->positive_by_key
, revoked_dnskey
->key
);
489 DnsResourceRecord
*anchor
;
491 /* First, look for the precise DNSKEY in our trust anchor database */
493 DNS_ANSWER_FOREACH(anchor
, a
) {
495 if (anchor
->dnskey
.protocol
!= revoked_dnskey
->dnskey
.protocol
)
498 if (anchor
->dnskey
.algorithm
!= revoked_dnskey
->dnskey
.algorithm
)
501 if (anchor
->dnskey
.key_size
!= revoked_dnskey
->dnskey
.key_size
)
504 if (((anchor
->dnskey
.flags
^ revoked_dnskey
->dnskey
.flags
) | DNSKEY_FLAG_REVOKE
) != DNSKEY_FLAG_REVOKE
)
507 if (memcmp(anchor
->dnskey
.key
, revoked_dnskey
->dnskey
.key
, anchor
->dnskey
.key_size
) != 0)
510 dns_trust_anchor_remove_revoked(d
, anchor
);
515 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
)));
517 DnsResourceRecord
*anchor
;
519 /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */
521 DNS_ANSWER_FOREACH(anchor
, a
) {
523 r
= dnssec_verify_dnskey(revoked_dnskey
, anchor
, true);
529 dns_trust_anchor_remove_revoked(d
, anchor
);
537 int dns_trust_anchor_check_revoked(DnsTrustAnchor
*d
, DnsAnswer
*rrs
, const DnsResourceKey
*key
) {
538 DnsResourceRecord
*dnskey
;
544 /* Looks for self-signed DNSKEY RRs in "rrs" that have been revoked. */
546 if (key
->type
!= DNS_TYPE_DNSKEY
)
549 DNS_ANSWER_FOREACH(dnskey
, rrs
) {
550 DnsResourceRecord
*rrsig
;
553 r
= dns_resource_key_equal(key
, dnskey
->key
);
559 /* Is this DNSKEY revoked? */
560 if ((dnskey
->dnskey
.flags
& DNSKEY_FLAG_REVOKE
) == 0)
563 /* Could this be interesting to us at all? If not,
564 * there's no point in looking for and verifying a
565 * self-signed RRSIG. */
566 if (!dns_trust_anchor_knows_domain_positive(d
, DNS_RESOURCE_KEY_NAME(dnskey
->key
)))
569 /* Look for a self-signed RRSIG */
570 DNS_ANSWER_FOREACH(rrsig
, rrs
) {
572 if (rrsig
->key
->type
!= DNS_TYPE_RRSIG
)
575 r
= dnssec_rrsig_match_dnskey(rrsig
, dnskey
, true);
581 r
= dnssec_verify_rrset(rrs
, key
, rrsig
, dnskey
, USEC_INFINITY
, &result
);
584 if (result
!= DNSSEC_VALIDATED
)
587 /* Bingo! Now, act! */
588 r
= dns_trust_anchor_check_revoked_one(d
, dnskey
);