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