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