]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-trust-anchor.c
resolved: move trust anchor files to /etc/dnssec-trust-anchors.d/
[thirdparty/systemd.git] / src / resolve / resolved-dns-trust-anchor.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2015 Lennart Poettering
7
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.
12
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.
17
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/>.
20 ***/
21
22 #include <sd-messages.h>
23
24 #include "alloc-util.h"
25 #include "conf-files.h"
26 #include "def.h"
27 #include "dns-domain.h"
28 #include "fd-util.h"
29 #include "fileio.h"
30 #include "hexdecoct.h"
31 #include "parse-util.h"
32 #include "resolved-dns-trust-anchor.h"
33 #include "resolved-dns-dnssec.h"
34 #include "set.h"
35 #include "string-util.h"
36 #include "strv.h"
37
38 static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d");
39
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 };
44
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;
48 int r;
49
50 assert(d);
51
52 r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops);
53 if (r < 0)
54 return r;
55
56 if (hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, ".")))
57 return 0;
58
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, "");
61 if (!rr)
62 return -ENOMEM;
63
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);
69 if (!rr->ds.digest)
70 return -ENOMEM;
71
72 answer = dns_answer_new(1);
73 if (!answer)
74 return -ENOMEM;
75
76 r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED);
77 if (r < 0)
78 return r;
79
80 r = hashmap_put(d->positive_by_key, rr->key, answer);
81 if (r < 0)
82 return r;
83
84 answer = NULL;
85 return 0;
86 }
87
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;
93 const char *p = s;
94 int r;
95
96 assert(d);
97 assert(line);
98
99 r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES);
100 if (r < 0)
101 return log_warning_errno(r, "Unable to parse domain in line %s:%u: %m", path, line);
102
103 if (!dns_name_is_valid(domain)) {
104 log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line);
105 return -EINVAL;
106 }
107
108 r = extract_many_words(&p, NULL, 0, &class, &type, NULL);
109 if (r < 0)
110 return log_warning_errno(r, "Unable to parse class and type in line %s:%u: %m", path, line);
111 if (r != 2) {
112 log_warning("Missing class or type in line %s:%u", path, line);
113 return -EINVAL;
114 }
115
116 if (!strcaseeq(class, "IN")) {
117 log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path, line);
118 return -EINVAL;
119 }
120
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;
124 uint16_t kt;
125 int a, dt;
126 size_t l;
127
128 r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type, &digest, NULL);
129 if (r < 0) {
130 log_warning_errno(r, "Failed to parse DS parameters on line %s:%u: %m", path, line);
131 return -EINVAL;
132 }
133 if (r != 4) {
134 log_warning("Missing DS parameters on line %s:%u", path, line);
135 return -EINVAL;
136 }
137
138 r = safe_atou16(key_tag, &kt);
139 if (r < 0)
140 return log_warning_errno(r, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag, path, line);
141
142 a = dnssec_algorithm_from_string(algorithm);
143 if (a < 0) {
144 log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm, path, line);
145 return -EINVAL;
146 }
147
148 dt = dnssec_digest_from_string(digest_type);
149 if (dt < 0) {
150 log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type, path, line);
151 return -EINVAL;
152 }
153
154 r = unhexmem(digest, strlen(digest), &dd, &l);
155 if (r < 0) {
156 log_warning("Failed to parse DS digest %s on line %s:%u", digest, path, line);
157 return -EINVAL;
158 }
159
160 rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, domain);
161 if (!rr)
162 return log_oom();
163
164 rr->ds.key_tag = kt;
165 rr->ds.algorithm = a;
166 rr->ds.digest_type = dt;
167 rr->ds.digest_size = l;
168 rr->ds.digest = dd;
169 dd = NULL;
170
171 } else if (strcaseeq(type, "DNSKEY")) {
172 _cleanup_free_ char *flags = NULL, *protocol = NULL, *algorithm = NULL, *key = NULL;
173 _cleanup_free_ void *k = NULL;
174 uint16_t f;
175 size_t l;
176 int a;
177
178 r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm, &key, NULL);
179 if (r < 0)
180 return log_warning_errno(r, "Failed to parse DNSKEY parameters on line %s:%u: %m", path, line);
181 if (r != 4) {
182 log_warning("Missing DNSKEY parameters on line %s:%u", path, line);
183 return -EINVAL;
184 }
185
186 if (!streq(protocol, "3")) {
187 log_warning("DNSKEY Protocol is not 3 on line %s:%u", path, line);
188 return -EINVAL;
189 }
190
191 r = safe_atou16(flags, &f);
192 if (r < 0)
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);
196 return -EINVAL;
197 }
198 if ((f & DNSKEY_FLAG_REVOKE)) {
199 log_warning("DNSKEY is already revoked on line %s:%u", path, line);
200 return -EINVAL;
201 }
202
203 a = dnssec_algorithm_from_string(algorithm);
204 if (a < 0) {
205 log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm, path, line);
206 return -EINVAL;
207 }
208
209 r = unbase64mem(key, strlen(key), &k, &l);
210 if (r < 0)
211 return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", key, path, line);
212
213 rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, domain);
214 if (!rr)
215 return log_oom();
216
217 rr->dnskey.flags = f;
218 rr->dnskey.protocol = 3;
219 rr->dnskey.algorithm = a;
220 rr->dnskey.key_size = l;
221 rr->dnskey.key = k;
222 k = NULL;
223
224 } else {
225 log_warning("RR type %s is not supported, ignoring line %s:%u.", type, path, line);
226 return -EINVAL;
227 }
228
229 if (!isempty(p)) {
230 log_warning("Trailing garbage on line %s:%u, ignoring line.", path, line);
231 return -EINVAL;
232 }
233
234 r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops);
235 if (r < 0)
236 return r;
237
238 old_answer = hashmap_get(d->positive_by_key, rr->key);
239 answer = dns_answer_ref(old_answer);
240
241 r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED);
242 if (r < 0)
243 return log_error_errno(r, "Failed to add trust anchor RR: %m");
244
245 r = hashmap_replace(d->positive_by_key, rr->key, answer);
246 if (r < 0)
247 return log_error_errno(r, "Failed to add answer to trust anchor: %m");
248
249 old_answer = dns_answer_unref(old_answer);
250 answer = NULL;
251
252 return 0;
253 }
254
255 static int dns_trust_anchor_load_negative(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) {
256 _cleanup_free_ char *domain = NULL;
257 const char *p = s;
258 int r;
259
260 assert(d);
261 assert(line);
262
263 r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES);
264 if (r < 0)
265 return log_warning_errno(r, "Unable to parse line %s:%u: %m", path, line);
266
267 if (!dns_name_is_valid(domain)) {
268 log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line);
269 return -EINVAL;
270 }
271
272 if (!isempty(p)) {
273 log_warning("Trailing garbage at line %s:%u, ignoring line.", path, line);
274 return -EINVAL;
275 }
276
277 r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops);
278 if (r < 0)
279 return r;
280
281 r = set_put(d->negative_by_name, domain);
282 if (r < 0)
283 return log_oom();
284 if (r > 0)
285 domain = NULL;
286
287 return 0;
288 }
289
290 static int dns_trust_anchor_load_files(
291 DnsTrustAnchor *d,
292 const char *suffix,
293 int (*loader)(DnsTrustAnchor *d, const char *path, unsigned n, const char *line)) {
294
295 _cleanup_strv_free_ char **files = NULL;
296 char **f;
297 int r;
298
299 assert(d);
300 assert(suffix);
301 assert(loader);
302
303 r = conf_files_list_nulstr(&files, suffix, NULL, trust_anchor_dirs);
304 if (r < 0)
305 return log_error_errno(r, "Failed to enumerate %s trust anchor files: %m", suffix);
306
307 STRV_FOREACH(f, files) {
308 _cleanup_fclose_ FILE *g = NULL;
309 char line[LINE_MAX];
310 unsigned n = 0;
311
312 g = fopen(*f, "r");
313 if (!g) {
314 if (errno == ENOENT)
315 continue;
316
317 log_warning_errno(errno, "Failed to open %s: %m", *f);
318 continue;
319 }
320
321 FOREACH_LINE(line, g, log_warning_errno(errno, "Failed to read %s, ignoring: %m", *f)) {
322 char *l;
323
324 n++;
325
326 l = strstrip(line);
327 if (isempty(l))
328 continue;
329
330 if (*l == ';')
331 continue;
332
333 (void) loader(d, *f, n, l);
334 }
335 }
336
337 return 0;
338 }
339
340 static void dns_trust_anchor_dump(DnsTrustAnchor *d) {
341 DnsAnswer *a;
342 Iterator i;
343
344 assert(d);
345
346 log_info("Positive Trust Anchors:");
347 HASHMAP_FOREACH(a, d->positive_by_key, i) {
348 DnsResourceRecord *rr;
349
350 DNS_ANSWER_FOREACH(rr, a)
351 log_info("%s", dns_resource_record_to_string(rr));
352 }
353
354 if (!set_isempty(d->negative_by_name)) {
355 char *n;
356 log_info("Negative trust anchors:");
357
358 SET_FOREACH(n, d->negative_by_name, i)
359 log_info("%s%s", n, endswith(n, ".") ? "" : ".");
360 }
361 }
362
363 int dns_trust_anchor_load(DnsTrustAnchor *d) {
364 int r;
365
366 assert(d);
367
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);
371
372 /* However, if the built-in DS fails, then we have a problem. */
373 r = dns_trust_anchor_add_builtin(d);
374 if (r < 0)
375 return log_error_errno(r, "Failed to add trust anchor built-in: %m");
376
377 dns_trust_anchor_dump(d);
378
379 return 0;
380 }
381
382 void dns_trust_anchor_flush(DnsTrustAnchor *d) {
383 DnsAnswer *a;
384
385 assert(d);
386
387 while ((a = hashmap_steal_first(d->positive_by_key)))
388 dns_answer_unref(a);
389
390 d->positive_by_key = hashmap_free(d->positive_by_key);
391 d->negative_by_name = set_free_free(d->negative_by_name);
392 }
393
394 int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *key, DnsAnswer **ret) {
395 DnsAnswer *a;
396
397 assert(d);
398 assert(key);
399 assert(ret);
400
401 /* We only serve DS and DNSKEY RRs. */
402 if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY))
403 return 0;
404
405 a = hashmap_get(d->positive_by_key, key);
406 if (!a)
407 return 0;
408
409 *ret = dns_answer_ref(a);
410 return 1;
411 }
412
413 int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) {
414 assert(d);
415 assert(name);
416
417 return set_contains(d->negative_by_name, name);
418 }
419
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;
423 int r;
424
425 old_answer = hashmap_get(d->positive_by_key, rr->key);
426 if (!old_answer)
427 return 0;
428
429 new_answer = dns_answer_ref(old_answer);
430
431 r = dns_answer_remove_by_rr(&new_answer, rr);
432 if (r <= 0)
433 return r;
434
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),
440 NULL);
441
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);
445 return 1;
446 }
447
448 r = hashmap_replace(d->positive_by_key, new_answer->items[0].rr->key, new_answer);
449 if (r < 0)
450 return r;
451
452 new_answer = NULL;
453 dns_answer_unref(old_answer);
454 return 1;
455 }
456
457 static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceRecord *revoked_dnskey) {
458 DnsAnswer *a;
459 int r;
460
461 assert(d);
462 assert(revoked_dnskey);
463 assert(revoked_dnskey->key->type == DNS_TYPE_DNSKEY);
464 assert(revoked_dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE);
465
466 a = hashmap_get(d->positive_by_key, revoked_dnskey->key);
467 if (a) {
468 DnsResourceRecord *anchor;
469
470 /* First, look for the precise DNSKEY in our trust anchor database */
471
472 DNS_ANSWER_FOREACH(anchor, a) {
473
474 if (anchor->dnskey.protocol != revoked_dnskey->dnskey.protocol)
475 continue;
476
477 if (anchor->dnskey.algorithm != revoked_dnskey->dnskey.algorithm)
478 continue;
479
480 if (anchor->dnskey.key_size != revoked_dnskey->dnskey.key_size)
481 continue;
482
483 if (((anchor->dnskey.flags ^ revoked_dnskey->dnskey.flags) | DNSKEY_FLAG_REVOKE) != DNSKEY_FLAG_REVOKE)
484 continue;
485
486 if (memcmp(anchor->dnskey.key, revoked_dnskey->dnskey.key, anchor->dnskey.key_size) != 0)
487 continue;
488
489 dns_trust_anchor_remove_revoked(d, anchor);
490 break;
491 }
492 }
493
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)));
495 if (a) {
496 DnsResourceRecord *anchor;
497
498 /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */
499
500 DNS_ANSWER_FOREACH(anchor, a) {
501
502 r = dnssec_verify_dnskey(revoked_dnskey, anchor, true);
503 if (r < 0)
504 return r;
505 if (r == 0)
506 continue;
507
508 dns_trust_anchor_remove_revoked(d, anchor);
509 break;
510 }
511 }
512
513 return 0;
514 }
515
516 static bool dns_trust_anchor_knows_domain(DnsTrustAnchor *d, const char *name) {
517 assert(d);
518
519 /* Returns true if there's an entry for the specified domain
520 * name in our trust anchor */
521
522 return
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));
525 }
526
527 int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsAnswer *rrs, const DnsResourceKey *key) {
528 DnsResourceRecord *dnskey;
529 int r;
530
531 assert(d);
532 assert(key);
533
534 /* Looks for self-signed DNSKEY RRs in "rrs" that have been revoked. */
535
536 if (key->type != DNS_TYPE_DNSKEY)
537 return 0;
538
539 DNS_ANSWER_FOREACH(dnskey, rrs) {
540 DnsResourceRecord *rrsig;
541 DnssecResult result;
542
543 r = dns_resource_key_equal(key, dnskey->key);
544 if (r < 0)
545 return r;
546 if (r == 0)
547 continue;
548
549 /* Is this DNSKEY revoked? */
550 if ((dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE) == 0)
551 continue;
552
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)))
557 continue;
558
559 /* Look for a self-signed RRSIG */
560 DNS_ANSWER_FOREACH(rrsig, rrs) {
561
562 if (rrsig->key->type != DNS_TYPE_RRSIG)
563 continue;
564
565 r = dnssec_rrsig_match_dnskey(rrsig, dnskey, true);
566 if (r < 0)
567 return r;
568 if (r == 0)
569 continue;
570
571 r = dnssec_verify_rrset(rrs, key, rrsig, dnskey, USEC_INFINITY, &result);
572 if (r < 0)
573 return r;
574 if (result != DNSSEC_VALIDATED)
575 continue;
576
577 /* Bingo! Now, act! */
578 r = dns_trust_anchor_check_revoked_one(d, dnskey);
579 if (r < 0)
580 return r;
581 }
582 }
583
584 return 0;
585 }