]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-dnssec.c
resolved: add a limit on the max DNSSEC RRSIG expiry skew we allow
[thirdparty/systemd.git] / src / resolve / resolved-dns-dnssec.c
CommitLineData
2b442ac8
LP
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 <gcrypt.h>
23
24#include "alloc-util.h"
25#include "dns-domain.h"
26#include "resolved-dns-dnssec.h"
27#include "resolved-dns-packet.h"
28
29/* Open question:
30 *
31 * How does the DNSSEC canonical form of a hostname with a label
32 * containing a dot look like, the way DNS-SD does it?
33 *
34 * */
35
36#define VERIFY_RRS_MAX 256
37#define MAX_KEY_SIZE (32*1024)
38
896c5672
LP
39/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
40#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
41
2b442ac8
LP
42/*
43 * The DNSSEC Chain of trust:
44 *
45 * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
46 * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
47 * DS RRs are protected like normal RRs
48 *
49 * Example chain:
50 * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
51 */
52
53static bool dnssec_algorithm_supported(int algorithm) {
964ef14c
LP
54 return IN_SET(algorithm,
55 DNSSEC_ALGORITHM_RSASHA1,
56 DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
57 DNSSEC_ALGORITHM_RSASHA256,
58 DNSSEC_ALGORITHM_RSASHA512);
2b442ac8
LP
59}
60
61static bool dnssec_digest_supported(int digest) {
964ef14c
LP
62 return IN_SET(digest,
63 DNSSEC_DIGEST_SHA1,
64 DNSSEC_DIGEST_SHA256);
2b442ac8
LP
65}
66
67uint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
68 const uint8_t *p;
69 uint32_t sum;
70 size_t i;
71
72 /* The algorithm from RFC 4034, Appendix B. */
73
74 assert(dnskey);
75 assert(dnskey->key->type == DNS_TYPE_DNSKEY);
76
77 sum = (uint32_t) dnskey->dnskey.flags +
78 ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
79
80 p = dnskey->dnskey.key;
81
82 for (i = 0; i < dnskey->dnskey.key_size; i++)
83 sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
84
85 sum += (sum >> 16) & UINT32_C(0xFFFF);
86
87 return sum & UINT32_C(0xFFFF);
88}
89
90static int rr_compare(const void *a, const void *b) {
91 DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
92 size_t m;
93 int r;
94
95 /* Let's order the RRs according to RFC 4034, Section 6.3 */
96
97 assert(x);
98 assert(*x);
99 assert((*x)->wire_format);
100 assert(y);
101 assert(*y);
102 assert((*y)->wire_format);
103
104 m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
105
106 r = memcmp((*x)->wire_format, (*y)->wire_format, m);
107 if (r != 0)
108 return r;
109
110 if ((*x)->wire_format_size < (*y)->wire_format_size)
111 return -1;
112 else if ((*x)->wire_format_size > (*y)->wire_format_size)
113 return 1;
114
115 return 0;
116}
117
118static int dnssec_rsa_verify(
119 const char *hash_algorithm,
120 const void *signature, size_t signature_size,
121 const void *data, size_t data_size,
122 const void *exponent, size_t exponent_size,
123 const void *modulus, size_t modulus_size) {
124
125 gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
126 gcry_mpi_t n = NULL, e = NULL, s = NULL;
127 gcry_error_t ge;
128 int r;
129
130 assert(hash_algorithm);
131
132 ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
133 if (ge != 0) {
134 r = -EIO;
135 goto finish;
136 }
137
138 ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
139 if (ge != 0) {
140 r = -EIO;
141 goto finish;
142 }
143
144 ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
145 if (ge != 0) {
146 r = -EIO;
147 goto finish;
148 }
149
150 ge = gcry_sexp_build(&signature_sexp,
151 NULL,
152 "(sig-val (rsa (s %m)))",
153 s);
154
155 if (ge != 0) {
156 r = -EIO;
157 goto finish;
158 }
159
160 ge = gcry_sexp_build(&data_sexp,
161 NULL,
162 "(data (flags pkcs1) (hash %s %b))",
163 hash_algorithm,
164 (int) data_size,
165 data);
166 if (ge != 0) {
167 r = -EIO;
168 goto finish;
169 }
170
171 ge = gcry_sexp_build(&public_key_sexp,
172 NULL,
173 "(public-key (rsa (n %m) (e %m)))",
174 n,
175 e);
176 if (ge != 0) {
177 r = -EIO;
178 goto finish;
179 }
180
181 ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
182 if (ge == GPG_ERR_BAD_SIGNATURE)
183 r = 0;
184 else if (ge != 0)
185 r = -EIO;
186 else
187 r = 1;
188
189finish:
190 if (e)
191 gcry_mpi_release(e);
192 if (n)
193 gcry_mpi_release(n);
194 if (s)
195 gcry_mpi_release(s);
196
197 if (public_key_sexp)
198 gcry_sexp_release(public_key_sexp);
199 if (signature_sexp)
200 gcry_sexp_release(signature_sexp);
201 if (data_sexp)
202 gcry_sexp_release(data_sexp);
203
204 return r;
205}
206
207static void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
208 gcry_md_write(md, &v, sizeof(v));
209}
210
211static void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
212 v = htobe16(v);
213 gcry_md_write(md, &v, sizeof(v));
214}
215
216static void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
217 v = htobe32(v);
218 gcry_md_write(md, &v, sizeof(v));
219}
220
2a326321
LP
221static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
222 usec_t expiration, inception, skew;
223
224 assert(rrsig);
225 assert(rrsig->key->type == DNS_TYPE_RRSIG);
226
227 if (realtime == USEC_INFINITY)
228 realtime = now(CLOCK_REALTIME);
229
230 expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
231 inception = rrsig->rrsig.inception * USEC_PER_SEC;
232
233 if (inception > expiration)
2a44bec4 234 return -EKEYREJECTED;
2a326321 235
896c5672
LP
236 /* Permit a certain amount of clock skew of 10% of the valid
237 * time range. This takes inspiration from unbound's
238 * resolver. */
2a326321 239 skew = (expiration - inception) / 10;
896c5672
LP
240 if (skew > SKEW_MAX)
241 skew = SKEW_MAX;
2a326321
LP
242
243 if (inception < skew)
244 inception = 0;
245 else
246 inception -= skew;
247
248 if (expiration + skew < expiration)
249 expiration = USEC_INFINITY;
250 else
251 expiration += skew;
252
253 return realtime < inception || realtime > expiration;
254}
255
256int dnssec_verify_rrset(
257 DnsAnswer *a,
258 DnsResourceKey *key,
259 DnsResourceRecord *rrsig,
260 DnsResourceRecord *dnskey,
261 usec_t realtime) {
262
2b442ac8
LP
263 uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
264 size_t exponent_size, modulus_size, hash_size;
265 void *exponent, *modulus, *hash;
266 DnsResourceRecord **list, *rr;
267 gcry_md_hd_t md = NULL;
268 size_t k, n = 0;
269 int r;
270
271 assert(key);
272 assert(rrsig);
273 assert(dnskey);
2a326321
LP
274 assert(rrsig->key->type == DNS_TYPE_RRSIG);
275 assert(dnskey->key->type == DNS_TYPE_DNSKEY);
2b442ac8
LP
276
277 /* Verifies the the RRSet matching the specified "key" in "a",
278 * using the signature "rrsig" and the key "dnskey". It's
279 * assumed the RRSIG and DNSKEY match. */
280
281 if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm))
282 return -EOPNOTSUPP;
283
284 if (a->n_rrs > VERIFY_RRS_MAX)
285 return -E2BIG;
286
2a326321
LP
287 r = dnssec_rrsig_expired(rrsig, realtime);
288 if (r < 0)
289 return r;
290 if (r > 0)
291 return DNSSEC_SIGNATURE_EXPIRED;
292
2b442ac8
LP
293 /* Collect all relevant RRs in a single array, so that we can look at the RRset */
294 list = newa(DnsResourceRecord *, a->n_rrs);
295
296 DNS_ANSWER_FOREACH(rr, a) {
297 r = dns_resource_key_equal(key, rr->key);
298 if (r < 0)
299 return r;
300 if (r == 0)
301 continue;
302
303 /* We need the wire format for ordering, and digest calculation */
304 r = dns_resource_record_to_wire_format(rr, true);
305 if (r < 0)
306 return r;
307
308 list[n++] = rr;
309 }
310
311 if (n <= 0)
312 return -ENODATA;
313
314 /* Bring the RRs into canonical order */
315 qsort_safe(list, n, sizeof(DnsResourceRecord), rr_compare);
316
317 /* OK, the RRs are now in canonical order. Let's calculate the digest */
318 switch (rrsig->rrsig.algorithm) {
319
320 case DNSSEC_ALGORITHM_RSASHA1:
964ef14c 321 case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
2b442ac8
LP
322 gcry_md_open(&md, GCRY_MD_SHA1, 0);
323 hash_size = 20;
324 break;
325
326 case DNSSEC_ALGORITHM_RSASHA256:
327 gcry_md_open(&md, GCRY_MD_SHA256, 0);
328 hash_size = 32;
329 break;
330
331 case DNSSEC_ALGORITHM_RSASHA512:
332 gcry_md_open(&md, GCRY_MD_SHA512, 0);
333 hash_size = 64;
334 break;
335
336 default:
337 assert_not_reached("Unknown digest");
338 }
339
340 if (!md)
341 return -EIO;
342
343 md_add_uint16(md, rrsig->rrsig.type_covered);
344 md_add_uint8(md, rrsig->rrsig.algorithm);
345 md_add_uint8(md, rrsig->rrsig.labels);
346 md_add_uint32(md, rrsig->rrsig.original_ttl);
347 md_add_uint32(md, rrsig->rrsig.expiration);
348 md_add_uint32(md, rrsig->rrsig.inception);
349 md_add_uint16(md, rrsig->rrsig.key_tag);
350
351 r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
352 if (r < 0)
353 goto finish;
354 gcry_md_write(md, wire_format_name, r);
355
356 for (k = 0; k < n; k++) {
357 size_t l;
358 rr = list[k];
359
360 r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true);
361 if (r < 0)
362 goto finish;
363 gcry_md_write(md, wire_format_name, r);
364
365 md_add_uint16(md, rr->key->type);
366 md_add_uint16(md, rr->key->class);
367 md_add_uint32(md, rrsig->rrsig.original_ttl);
368
369 assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
370 l = rr->wire_format_size - rr->wire_format_rdata_offset;
371 assert(l <= 0xFFFF);
372
373 md_add_uint16(md, (uint16_t) l);
374 gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
375 }
376
377 hash = gcry_md_read(md, 0);
378 if (!hash) {
379 r = -EIO;
380 goto finish;
381 }
382
383 if (*(uint8_t*) dnskey->dnskey.key == 0) {
384 /* exponent is > 255 bytes long */
385
386 exponent = (uint8_t*) dnskey->dnskey.key + 3;
387 exponent_size =
388 ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
389 ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
390
391 if (exponent_size < 256) {
392 r = -EINVAL;
393 goto finish;
394 }
395
396 if (3 + exponent_size >= dnskey->dnskey.key_size) {
397 r = -EINVAL;
398 goto finish;
399 }
400
401 modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
402 modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
403
404 } else {
405 /* exponent is <= 255 bytes long */
406
407 exponent = (uint8_t*) dnskey->dnskey.key + 1;
408 exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
409
410 if (exponent_size <= 0) {
411 r = -EINVAL;
412 goto finish;
413 }
414
415 if (1 + exponent_size >= dnskey->dnskey.key_size) {
416 r = -EINVAL;
417 goto finish;
418 }
419
420 modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
421 modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
422 }
423
424 r = dnssec_rsa_verify(
425 gcry_md_algo_name(gcry_md_get_algo(md)),
426 rrsig->rrsig.signature, rrsig->rrsig.signature_size,
427 hash, hash_size,
428 exponent, exponent_size,
429 modulus, modulus_size);
430 if (r < 0)
431 goto finish;
432
433 r = r ? DNSSEC_VERIFIED : DNSSEC_INVALID;
434
435finish:
436 gcry_md_close(md);
437 return r;
438}
439
440int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
441
442 assert(rrsig);
443 assert(dnskey);
444
445 /* Checks if the specified DNSKEY RR matches the key used for
446 * the signature in the specified RRSIG RR */
447
448 if (rrsig->key->type != DNS_TYPE_RRSIG)
449 return -EINVAL;
450
451 if (dnskey->key->type != DNS_TYPE_DNSKEY)
452 return 0;
453 if (dnskey->key->class != rrsig->key->class)
454 return 0;
455 if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
456 return 0;
457 if (dnskey->dnskey.protocol != 3)
458 return 0;
459 if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
460 return 0;
461
462 if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag)
463 return 0;
464
465 return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(rrsig->key));
466}
467
468int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) {
469 assert(key);
470 assert(rrsig);
471
472 /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
473
474 if (rrsig->key->type != DNS_TYPE_RRSIG)
475 return 0;
476 if (rrsig->key->class != key->class)
477 return 0;
478 if (rrsig->rrsig.type_covered != key->type)
479 return 0;
480
481 return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
482}
483
2a326321
LP
484int dnssec_verify_rrset_search(
485 DnsAnswer *a,
486 DnsResourceKey *key,
487 DnsAnswer *validated_dnskeys,
488 usec_t realtime) {
489
2b442ac8
LP
490 bool found_rrsig = false, found_dnskey = false;
491 DnsResourceRecord *rrsig;
492 int r;
493
494 assert(key);
495
496 /* Verifies all RRs from "a" that match the key "key", against DNSKEY RRs in "validated_dnskeys" */
497
498 if (!a || a->n_rrs <= 0)
499 return -ENODATA;
500
501 /* Iterate through each RRSIG RR. */
502 DNS_ANSWER_FOREACH(rrsig, a) {
503 DnsResourceRecord *dnskey;
504
505 r = dnssec_key_match_rrsig(key, rrsig);
506 if (r < 0)
507 return r;
508 if (r == 0)
509 continue;
510
511 found_rrsig = true;
512
513 DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) {
514
515 r = dnssec_rrsig_match_dnskey(rrsig, dnskey);
516 if (r < 0)
517 return r;
518 if (r == 0)
519 continue;
520
521 found_dnskey = true;
522
2a326321
LP
523 /* Take the time here, if it isn't set yet, so
524 * that we do all validations with the same
525 * time. */
526 if (realtime == USEC_INFINITY)
527 realtime = now(CLOCK_REALTIME);
528
2b442ac8
LP
529 /* Yay, we found a matching RRSIG with a matching
530 * DNSKEY, awesome. Now let's verify all entries of
531 * the RRSet against the RRSIG and DNSKEY
532 * combination. */
533
2a326321 534 r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime);
2b442ac8
LP
535 if (r < 0 && r != EOPNOTSUPP)
536 return r;
537 if (r == DNSSEC_VERIFIED)
538 return DNSSEC_VERIFIED;
539
540 /* If the signature is invalid, or done using
541 an unsupported algorithm, let's try another
542 key and/or signature. After all they
543 key_tags and stuff are not unique, and
544 might be shared by multiple keys. */
545 }
546 }
547
548 if (found_dnskey)
549 return DNSSEC_INVALID;
550
551 if (found_rrsig)
552 return DNSSEC_MISSING_KEY;
553
554 return DNSSEC_NO_SIGNATURE;
555}
556
557int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
558 _cleanup_free_ char *s = NULL;
559 size_t c = 0;
560 int r;
561
562 /* Converts the specified hostname into DNSSEC canonicalized
563 * form. */
564
565 if (buffer_max < 2)
566 return -ENOBUFS;
567
568 for (;;) {
569 size_t i;
570
571 r = dns_label_unescape(&n, buffer, buffer_max);
572 if (r < 0)
573 return r;
574 if (r == 0)
575 break;
576 if (r > 0) {
577 int k;
578
579 /* DNSSEC validation is always done on the ASCII version of the label */
580 k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
581 if (k < 0)
582 return k;
583 if (k > 0)
584 r = k;
585 }
586
587 if (buffer_max < (size_t) r + 2)
588 return -ENOBUFS;
589
590 /* The DNSSEC canonical form is not clear on what to
591 * do with dots appearing in labels, the way DNS-SD
592 * does it. Refuse it for now. */
593
594 if (memchr(buffer, '.', r))
595 return -EINVAL;
596
597 for (i = 0; i < (size_t) r; i ++) {
598 if (buffer[i] >= 'A' && buffer[i] <= 'Z')
599 buffer[i] = buffer[i] - 'A' + 'a';
600 }
601
602 buffer[r] = '.';
603
604 buffer += r + 1;
605 c += r + 1;
606
607 buffer_max -= r + 1;
608 }
609
610 if (c <= 0) {
611 /* Not even a single label: this is the root domain name */
612
613 assert(buffer_max > 2);
614 buffer[0] = '.';
615 buffer[1] = 0;
616
617 return 1;
618 }
619
620 return (int) c;
621}
622
623int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
624 gcry_md_hd_t md = NULL;
625 char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
626 void *result;
627 int r;
628
629 assert(dnskey);
630 assert(ds);
631
632 /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
633
634 if (dnskey->key->type != DNS_TYPE_DNSKEY)
635 return -EINVAL;
636 if (ds->key->type != DNS_TYPE_DS)
637 return -EINVAL;
638 if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
639 return -EKEYREJECTED;
640 if (dnskey->dnskey.protocol != 3)
641 return -EKEYREJECTED;
642
643 if (!dnssec_algorithm_supported(dnskey->dnskey.algorithm))
644 return -EOPNOTSUPP;
645 if (!dnssec_digest_supported(ds->ds.digest_type))
646 return -EOPNOTSUPP;
647
648 if (dnskey->dnskey.algorithm != ds->ds.algorithm)
649 return 0;
650 if (dnssec_keytag(dnskey) != ds->ds.key_tag)
651 return 0;
652
653 switch (ds->ds.digest_type) {
654
655 case DNSSEC_DIGEST_SHA1:
656
657 if (ds->ds.digest_size != 20)
658 return 0;
659
660 gcry_md_open(&md, GCRY_MD_SHA1, 0);
661 break;
662
663 case DNSSEC_DIGEST_SHA256:
664
665 if (ds->ds.digest_size != 32)
666 return 0;
667
668 gcry_md_open(&md, GCRY_MD_SHA256, 0);
669 break;
670
671 default:
672 assert_not_reached("Unknown digest");
673 }
674
675 if (!md)
676 return -EIO;
677
678 r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
679 if (r < 0)
680 goto finish;
681
682 gcry_md_write(md, owner_name, r);
683 md_add_uint16(md, dnskey->dnskey.flags);
684 md_add_uint8(md, dnskey->dnskey.protocol);
685 md_add_uint8(md, dnskey->dnskey.algorithm);
686 gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);
687
688 result = gcry_md_read(md, 0);
689 if (!result) {
690 r = -EIO;
691 goto finish;
692 }
693
694 r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0;
695
696finish:
697 gcry_md_close(md);
698 return r;
699}