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