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 if (hashmap_get(d
->positive_by_key
, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN
, DNS_TYPE_DNSKEY
, ".")))
62 /* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */
63 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_DS
, "");
67 rr
->ds
.key_tag
= 19036;
68 rr
->ds
.algorithm
= DNSSEC_ALGORITHM_RSASHA256
;
69 rr
->ds
.digest_type
= DNSSEC_DIGEST_SHA256
;
70 rr
->ds
.digest_size
= sizeof(root_digest
);
71 rr
->ds
.digest
= memdup(root_digest
, rr
->ds
.digest_size
);
75 answer
= dns_answer_new(1);
79 r
= dns_answer_add(answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
83 r
= hashmap_put(d
->positive_by_key
, rr
->key
, answer
);
91 static int dns_trust_anchor_load_positive(DnsTrustAnchor
*d
, const char *path
, unsigned line
, const char *s
) {
92 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
93 _cleanup_free_
char *domain
= NULL
, *class = NULL
, *type
= NULL
;
94 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
;
95 DnsAnswer
*old_answer
= NULL
;
102 r
= extract_first_word(&p
, &domain
, NULL
, EXTRACT_QUOTES
);
104 return log_warning_errno(r
, "Unable to parse domain in line %s:%u: %m", path
, line
);
106 if (!dns_name_is_valid(domain
)) {
107 log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain
, path
, line
);
111 r
= extract_many_words(&p
, NULL
, 0, &class, &type
, NULL
);
113 return log_warning_errno(r
, "Unable to parse class and type in line %s:%u: %m", path
, line
);
115 log_warning("Missing class or type in line %s:%u", path
, line
);
119 if (!strcaseeq(class, "IN")) {
120 log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path
, line
);
124 if (strcaseeq(type
, "DS")) {
125 _cleanup_free_
char *key_tag
= NULL
, *algorithm
= NULL
, *digest_type
= NULL
, *digest
= NULL
;
126 _cleanup_free_
void *dd
= NULL
;
131 r
= extract_many_words(&p
, NULL
, 0, &key_tag
, &algorithm
, &digest_type
, &digest
, NULL
);
133 log_warning_errno(r
, "Failed to parse DS parameters on line %s:%u: %m", path
, line
);
137 log_warning("Missing DS parameters on line %s:%u", path
, line
);
141 r
= safe_atou16(key_tag
, &kt
);
143 return log_warning_errno(r
, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag
, path
, line
);
145 a
= dnssec_algorithm_from_string(algorithm
);
147 log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm
, path
, line
);
151 dt
= dnssec_digest_from_string(digest_type
);
153 log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type
, path
, line
);
157 r
= unhexmem(digest
, strlen(digest
), &dd
, &l
);
159 log_warning("Failed to parse DS digest %s on line %s:%u", digest
, path
, line
);
163 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_DS
, domain
);
168 rr
->ds
.algorithm
= a
;
169 rr
->ds
.digest_type
= dt
;
170 rr
->ds
.digest_size
= l
;
174 } else if (strcaseeq(type
, "DNSKEY")) {
175 _cleanup_free_
char *flags
= NULL
, *protocol
= NULL
, *algorithm
= NULL
, *key
= NULL
;
176 _cleanup_free_
void *k
= NULL
;
181 r
= extract_many_words(&p
, NULL
, 0, &flags
, &protocol
, &algorithm
, &key
, NULL
);
183 return log_warning_errno(r
, "Failed to parse DNSKEY parameters on line %s:%u: %m", path
, line
);
185 log_warning("Missing DNSKEY parameters on line %s:%u", path
, line
);
189 if (!streq(protocol
, "3")) {
190 log_warning("DNSKEY Protocol is not 3 on line %s:%u", path
, line
);
194 r
= safe_atou16(flags
, &f
);
196 return log_warning_errno(r
, "Failed to parse DNSKEY flags field %s on line %s:%u", flags
, path
, line
);
197 if ((f
& DNSKEY_FLAG_ZONE_KEY
) == 0) {
198 log_warning("DNSKEY lacks zone key bit set on line %s:%u", path
, line
);
201 if ((f
& DNSKEY_FLAG_REVOKE
)) {
202 log_warning("DNSKEY is already revoked on line %s:%u", path
, line
);
206 a
= dnssec_algorithm_from_string(algorithm
);
208 log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm
, path
, line
);
212 r
= unbase64mem(key
, strlen(key
), &k
, &l
);
214 return log_warning_errno(r
, "Failed to parse DNSKEY key data %s on line %s:%u", key
, path
, line
);
216 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_DNSKEY
, domain
);
220 rr
->dnskey
.flags
= f
;
221 rr
->dnskey
.protocol
= 3;
222 rr
->dnskey
.algorithm
= a
;
223 rr
->dnskey
.key_size
= l
;
228 log_warning("RR type %s is not supported, ignoring line %s:%u.", type
, path
, line
);
233 log_warning("Trailing garbage on line %s:%u, ignoring line.", path
, line
);
237 r
= hashmap_ensure_allocated(&d
->positive_by_key
, &dns_resource_key_hash_ops
);
241 old_answer
= hashmap_get(d
->positive_by_key
, rr
->key
);
242 answer
= dns_answer_ref(old_answer
);
244 r
= dns_answer_add_extend(&answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
246 return log_error_errno(r
, "Failed to add trust anchor RR: %m");
248 r
= hashmap_replace(d
->positive_by_key
, rr
->key
, answer
);
250 return log_error_errno(r
, "Failed to add answer to trust anchor: %m");
252 old_answer
= dns_answer_unref(old_answer
);
258 static int dns_trust_anchor_load_negative(DnsTrustAnchor
*d
, const char *path
, unsigned line
, const char *s
) {
259 _cleanup_free_
char *domain
= NULL
;
266 r
= extract_first_word(&p
, &domain
, NULL
, EXTRACT_QUOTES
);
268 return log_warning_errno(r
, "Unable to parse line %s:%u: %m", path
, line
);
270 if (!dns_name_is_valid(domain
)) {
271 log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain
, path
, line
);
276 log_warning("Trailing garbage at line %s:%u, ignoring line.", path
, line
);
280 r
= set_ensure_allocated(&d
->negative_by_name
, &dns_name_hash_ops
);
284 r
= set_put(d
->negative_by_name
, domain
);
293 static int dns_trust_anchor_load_files(
296 int (*loader
)(DnsTrustAnchor
*d
, const char *path
, unsigned n
, const char *line
)) {
298 _cleanup_strv_free_
char **files
= NULL
;
306 r
= conf_files_list_nulstr(&files
, suffix
, NULL
, trust_anchor_dirs
);
308 return log_error_errno(r
, "Failed to enumerate %s trust anchor files: %m", suffix
);
310 STRV_FOREACH(f
, files
) {
311 _cleanup_fclose_
FILE *g
= NULL
;
320 log_warning_errno(errno
, "Failed to open %s: %m", *f
);
324 FOREACH_LINE(line
, g
, log_warning_errno(errno
, "Failed to read %s, ignoring: %m", *f
)) {
336 (void) loader(d
, *f
, n
, l
);
343 static void dns_trust_anchor_dump(DnsTrustAnchor
*d
) {
349 log_info("Positive Trust Anchors:");
350 HASHMAP_FOREACH(a
, d
->positive_by_key
, i
) {
351 DnsResourceRecord
*rr
;
353 DNS_ANSWER_FOREACH(rr
, a
)
354 log_info("%s", dns_resource_record_to_string(rr
));
357 if (!set_isempty(d
->negative_by_name
)) {
359 log_info("Negative trust anchors:");
361 SET_FOREACH(n
, d
->negative_by_name
, i
)
362 log_info("%s%s", n
, endswith(n
, ".") ? "" : ".");
366 int dns_trust_anchor_load(DnsTrustAnchor
*d
) {
371 /* If loading things from disk fails, we don't consider this fatal */
372 (void) dns_trust_anchor_load_files(d
, ".positive", dns_trust_anchor_load_positive
);
373 (void) dns_trust_anchor_load_files(d
, ".negative", dns_trust_anchor_load_negative
);
375 /* However, if the built-in DS fails, then we have a problem. */
376 r
= dns_trust_anchor_add_builtin(d
);
378 return log_error_errno(r
, "Failed to add trust anchor built-in: %m");
380 dns_trust_anchor_dump(d
);
385 void dns_trust_anchor_flush(DnsTrustAnchor
*d
) {
390 while ((a
= hashmap_steal_first(d
->positive_by_key
)))
393 d
->positive_by_key
= hashmap_free(d
->positive_by_key
);
394 d
->negative_by_name
= set_free_free(d
->negative_by_name
);
397 int dns_trust_anchor_lookup_positive(DnsTrustAnchor
*d
, const DnsResourceKey
*key
, DnsAnswer
**ret
) {
404 /* We only serve DS and DNSKEY RRs. */
405 if (!IN_SET(key
->type
, DNS_TYPE_DS
, DNS_TYPE_DNSKEY
))
408 a
= hashmap_get(d
->positive_by_key
, key
);
412 *ret
= dns_answer_ref(a
);
416 int dns_trust_anchor_lookup_negative(DnsTrustAnchor
*d
, const char *name
) {
420 return set_contains(d
->negative_by_name
, name
);
423 static int dns_trust_anchor_remove_revoked(DnsTrustAnchor
*d
, DnsResourceRecord
*rr
) {
424 _cleanup_(dns_answer_unrefp
) DnsAnswer
*new_answer
= NULL
;
425 DnsAnswer
*old_answer
;
428 old_answer
= hashmap_get(d
->positive_by_key
, rr
->key
);
432 new_answer
= dns_answer_ref(old_answer
);
434 r
= dns_answer_remove_by_rr(&new_answer
, rr
);
438 /* We found the key! Warn the user */
439 log_struct(LOG_WARNING
,
440 LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED
),
441 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
)),
442 "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr
),
445 if (dns_answer_size(new_answer
) <= 0) {
446 assert_se(hashmap_remove(d
->positive_by_key
, rr
->key
) == old_answer
);
447 dns_answer_unref(old_answer
);
451 r
= hashmap_replace(d
->positive_by_key
, new_answer
->items
[0].rr
->key
, new_answer
);
456 dns_answer_unref(old_answer
);
460 static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor
*d
, DnsResourceRecord
*revoked_dnskey
) {
465 assert(revoked_dnskey
);
466 assert(revoked_dnskey
->key
->type
== DNS_TYPE_DNSKEY
);
467 assert(revoked_dnskey
->dnskey
.flags
& DNSKEY_FLAG_REVOKE
);
469 a
= hashmap_get(d
->positive_by_key
, revoked_dnskey
->key
);
471 DnsResourceRecord
*anchor
;
473 /* First, look for the precise DNSKEY in our trust anchor database */
475 DNS_ANSWER_FOREACH(anchor
, a
) {
477 if (anchor
->dnskey
.protocol
!= revoked_dnskey
->dnskey
.protocol
)
480 if (anchor
->dnskey
.algorithm
!= revoked_dnskey
->dnskey
.algorithm
)
483 if (anchor
->dnskey
.key_size
!= revoked_dnskey
->dnskey
.key_size
)
486 if (((anchor
->dnskey
.flags
^ revoked_dnskey
->dnskey
.flags
) | DNSKEY_FLAG_REVOKE
) != DNSKEY_FLAG_REVOKE
)
489 if (memcmp(anchor
->dnskey
.key
, revoked_dnskey
->dnskey
.key
, anchor
->dnskey
.key_size
) != 0)
492 dns_trust_anchor_remove_revoked(d
, anchor
);
497 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
)));
499 DnsResourceRecord
*anchor
;
501 /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */
503 DNS_ANSWER_FOREACH(anchor
, a
) {
505 r
= dnssec_verify_dnskey(revoked_dnskey
, anchor
, true);
511 dns_trust_anchor_remove_revoked(d
, anchor
);
519 static bool dns_trust_anchor_knows_domain(DnsTrustAnchor
*d
, const char *name
) {
522 /* Returns true if there's an entry for the specified domain
523 * name in our trust anchor */
526 hashmap_contains(d
->positive_by_key
, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN
, DNS_TYPE_DNSKEY
, name
)) ||
527 hashmap_contains(d
->positive_by_key
, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN
, DNS_TYPE_DS
, name
));
530 int dns_trust_anchor_check_revoked(DnsTrustAnchor
*d
, DnsAnswer
*rrs
, const DnsResourceKey
*key
) {
531 DnsResourceRecord
*dnskey
;
537 /* Looks for self-signed DNSKEY RRs in "rrs" that have been revoked. */
539 if (key
->type
!= DNS_TYPE_DNSKEY
)
542 DNS_ANSWER_FOREACH(dnskey
, rrs
) {
543 DnsResourceRecord
*rrsig
;
546 r
= dns_resource_key_equal(key
, dnskey
->key
);
552 /* Is this DNSKEY revoked? */
553 if ((dnskey
->dnskey
.flags
& DNSKEY_FLAG_REVOKE
) == 0)
556 /* Could this be interesting to us at all? If not,
557 * there's no point in looking for and verifying a
558 * self-signed RRSIG. */
559 if (!dns_trust_anchor_knows_domain(d
, DNS_RESOURCE_KEY_NAME(dnskey
->key
)))
562 /* Look for a self-signed RRSIG */
563 DNS_ANSWER_FOREACH(rrsig
, rrs
) {
565 if (rrsig
->key
->type
!= DNS_TYPE_RRSIG
)
568 r
= dnssec_rrsig_match_dnskey(rrsig
, dnskey
, true);
574 r
= dnssec_verify_rrset(rrs
, key
, rrsig
, dnskey
, USEC_INFINITY
, &result
);
577 if (result
!= DNSSEC_VALIDATED
)
580 /* Bingo! Now, act! */
581 r
= dns_trust_anchor_check_revoked_one(d
, dnskey
);