]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-dnssec.c
resolved: add SHA384 digest 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"
72667f08 26#include "hexdecoct.h"
2b442ac8
LP
27#include "resolved-dns-dnssec.h"
28#include "resolved-dns-packet.h"
24710c48 29#include "string-table.h"
2b442ac8
LP
30
31/* Open question:
32 *
33 * How does the DNSSEC canonical form of a hostname with a label
34 * containing a dot look like, the way DNS-SD does it?
35 *
2cd87277
LP
36 * TODO:
37 *
bb1fa242 38 * - Make trust anchor store read additional DS+DNSKEY data from disk
3ecc3df8 39 * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
bb1fa242 40 * - multi-label zone compatibility
3e92a719 41 * - cname/dname compatibility
bb1fa242 42 * - per-interface DNSSEC setting
73b8d8e9 43 * - fix TTL for cache entries to match RRSIG TTL
3e92a719 44 * - retry on failed validation?
2cd87277
LP
45 * - DSA support
46 * - EC support?
47 *
2b442ac8
LP
48 * */
49
50#define VERIFY_RRS_MAX 256
51#define MAX_KEY_SIZE (32*1024)
52
896c5672
LP
53/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
54#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
55
2b442ac8
LP
56/*
57 * The DNSSEC Chain of trust:
58 *
59 * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
60 * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
61 * DS RRs are protected like normal RRs
62 *
63 * Example chain:
64 * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
65 */
66
0638401a
LP
67static void initialize_libgcrypt(void) {
68 const char *p;
69
70 if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
71 return;
72
73 p = gcry_check_version("1.4.5");
74 assert(p);
75
76 gcry_control(GCRYCTL_DISABLE_SECMEM);
77 gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
78}
79
2b442ac8 80static bool dnssec_algorithm_supported(int algorithm) {
964ef14c
LP
81 return IN_SET(algorithm,
82 DNSSEC_ALGORITHM_RSASHA1,
83 DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
84 DNSSEC_ALGORITHM_RSASHA256,
85 DNSSEC_ALGORITHM_RSASHA512);
2b442ac8
LP
86}
87
2b442ac8
LP
88uint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
89 const uint8_t *p;
90 uint32_t sum;
91 size_t i;
92
93 /* The algorithm from RFC 4034, Appendix B. */
94
95 assert(dnskey);
96 assert(dnskey->key->type == DNS_TYPE_DNSKEY);
97
98 sum = (uint32_t) dnskey->dnskey.flags +
99 ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
100
101 p = dnskey->dnskey.key;
102
103 for (i = 0; i < dnskey->dnskey.key_size; i++)
104 sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
105
106 sum += (sum >> 16) & UINT32_C(0xFFFF);
107
108 return sum & UINT32_C(0xFFFF);
109}
110
111static int rr_compare(const void *a, const void *b) {
112 DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
113 size_t m;
114 int r;
115
116 /* Let's order the RRs according to RFC 4034, Section 6.3 */
117
118 assert(x);
119 assert(*x);
120 assert((*x)->wire_format);
121 assert(y);
122 assert(*y);
123 assert((*y)->wire_format);
124
125 m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
126
127 r = memcmp((*x)->wire_format, (*y)->wire_format, m);
128 if (r != 0)
129 return r;
130
131 if ((*x)->wire_format_size < (*y)->wire_format_size)
132 return -1;
133 else if ((*x)->wire_format_size > (*y)->wire_format_size)
134 return 1;
135
136 return 0;
137}
138
139static int dnssec_rsa_verify(
140 const char *hash_algorithm,
141 const void *signature, size_t signature_size,
142 const void *data, size_t data_size,
143 const void *exponent, size_t exponent_size,
144 const void *modulus, size_t modulus_size) {
145
146 gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
147 gcry_mpi_t n = NULL, e = NULL, s = NULL;
148 gcry_error_t ge;
149 int r;
150
151 assert(hash_algorithm);
152
153 ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
154 if (ge != 0) {
155 r = -EIO;
156 goto finish;
157 }
158
159 ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
160 if (ge != 0) {
161 r = -EIO;
162 goto finish;
163 }
164
165 ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
166 if (ge != 0) {
167 r = -EIO;
168 goto finish;
169 }
170
171 ge = gcry_sexp_build(&signature_sexp,
172 NULL,
173 "(sig-val (rsa (s %m)))",
174 s);
175
176 if (ge != 0) {
177 r = -EIO;
178 goto finish;
179 }
180
181 ge = gcry_sexp_build(&data_sexp,
182 NULL,
183 "(data (flags pkcs1) (hash %s %b))",
184 hash_algorithm,
185 (int) data_size,
186 data);
187 if (ge != 0) {
188 r = -EIO;
189 goto finish;
190 }
191
192 ge = gcry_sexp_build(&public_key_sexp,
193 NULL,
194 "(public-key (rsa (n %m) (e %m)))",
195 n,
196 e);
197 if (ge != 0) {
198 r = -EIO;
199 goto finish;
200 }
201
202 ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
d12bf2bd 203 if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
2b442ac8 204 r = 0;
d12bf2bd
LP
205 else if (ge != 0) {
206 log_debug("RSA signature check failed: %s", gpg_strerror(ge));
2b442ac8 207 r = -EIO;
d12bf2bd 208 } else
2b442ac8
LP
209 r = 1;
210
211finish:
212 if (e)
213 gcry_mpi_release(e);
214 if (n)
215 gcry_mpi_release(n);
216 if (s)
217 gcry_mpi_release(s);
218
219 if (public_key_sexp)
220 gcry_sexp_release(public_key_sexp);
221 if (signature_sexp)
222 gcry_sexp_release(signature_sexp);
223 if (data_sexp)
224 gcry_sexp_release(data_sexp);
225
226 return r;
227}
228
229static void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
230 gcry_md_write(md, &v, sizeof(v));
231}
232
233static void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
234 v = htobe16(v);
235 gcry_md_write(md, &v, sizeof(v));
236}
237
238static void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
239 v = htobe32(v);
240 gcry_md_write(md, &v, sizeof(v));
241}
242
2a326321
LP
243static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
244 usec_t expiration, inception, skew;
245
246 assert(rrsig);
247 assert(rrsig->key->type == DNS_TYPE_RRSIG);
248
249 if (realtime == USEC_INFINITY)
250 realtime = now(CLOCK_REALTIME);
251
252 expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
253 inception = rrsig->rrsig.inception * USEC_PER_SEC;
254
255 if (inception > expiration)
2a44bec4 256 return -EKEYREJECTED;
2a326321 257
896c5672
LP
258 /* Permit a certain amount of clock skew of 10% of the valid
259 * time range. This takes inspiration from unbound's
260 * resolver. */
2a326321 261 skew = (expiration - inception) / 10;
896c5672
LP
262 if (skew > SKEW_MAX)
263 skew = SKEW_MAX;
2a326321
LP
264
265 if (inception < skew)
266 inception = 0;
267 else
268 inception -= skew;
269
270 if (expiration + skew < expiration)
271 expiration = USEC_INFINITY;
272 else
273 expiration += skew;
274
275 return realtime < inception || realtime > expiration;
276}
277
278int dnssec_verify_rrset(
279 DnsAnswer *a,
280 DnsResourceKey *key,
281 DnsResourceRecord *rrsig,
282 DnsResourceRecord *dnskey,
547973de
LP
283 usec_t realtime,
284 DnssecResult *result) {
2a326321 285
2b442ac8
LP
286 uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
287 size_t exponent_size, modulus_size, hash_size;
288 void *exponent, *modulus, *hash;
289 DnsResourceRecord **list, *rr;
290 gcry_md_hd_t md = NULL;
291 size_t k, n = 0;
292 int r;
293
294 assert(key);
295 assert(rrsig);
296 assert(dnskey);
547973de 297 assert(result);
2a326321
LP
298 assert(rrsig->key->type == DNS_TYPE_RRSIG);
299 assert(dnskey->key->type == DNS_TYPE_DNSKEY);
2b442ac8
LP
300
301 /* Verifies the the RRSet matching the specified "key" in "a",
302 * using the signature "rrsig" and the key "dnskey". It's
303 * assumed the RRSIG and DNSKEY match. */
304
203f1b35
LP
305 if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm)) {
306 *result = DNSSEC_UNSUPPORTED_ALGORITHM;
307 return 0;
308 }
2b442ac8
LP
309
310 if (a->n_rrs > VERIFY_RRS_MAX)
311 return -E2BIG;
312
2a326321
LP
313 r = dnssec_rrsig_expired(rrsig, realtime);
314 if (r < 0)
315 return r;
547973de
LP
316 if (r > 0) {
317 *result = DNSSEC_SIGNATURE_EXPIRED;
318 return 0;
319 }
2a326321 320
2b442ac8
LP
321 /* Collect all relevant RRs in a single array, so that we can look at the RRset */
322 list = newa(DnsResourceRecord *, a->n_rrs);
323
324 DNS_ANSWER_FOREACH(rr, a) {
325 r = dns_resource_key_equal(key, rr->key);
326 if (r < 0)
327 return r;
328 if (r == 0)
329 continue;
330
331 /* We need the wire format for ordering, and digest calculation */
332 r = dns_resource_record_to_wire_format(rr, true);
333 if (r < 0)
334 return r;
335
336 list[n++] = rr;
337 }
338
339 if (n <= 0)
340 return -ENODATA;
341
342 /* Bring the RRs into canonical order */
6c5e8fbf 343 qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
2b442ac8 344
0638401a
LP
345 initialize_libgcrypt();
346
2b442ac8
LP
347 /* OK, the RRs are now in canonical order. Let's calculate the digest */
348 switch (rrsig->rrsig.algorithm) {
349
350 case DNSSEC_ALGORITHM_RSASHA1:
964ef14c 351 case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
2b442ac8
LP
352 gcry_md_open(&md, GCRY_MD_SHA1, 0);
353 hash_size = 20;
354 break;
355
356 case DNSSEC_ALGORITHM_RSASHA256:
357 gcry_md_open(&md, GCRY_MD_SHA256, 0);
358 hash_size = 32;
359 break;
360
361 case DNSSEC_ALGORITHM_RSASHA512:
362 gcry_md_open(&md, GCRY_MD_SHA512, 0);
363 hash_size = 64;
364 break;
365
366 default:
367 assert_not_reached("Unknown digest");
368 }
369
370 if (!md)
371 return -EIO;
372
373 md_add_uint16(md, rrsig->rrsig.type_covered);
374 md_add_uint8(md, rrsig->rrsig.algorithm);
375 md_add_uint8(md, rrsig->rrsig.labels);
376 md_add_uint32(md, rrsig->rrsig.original_ttl);
377 md_add_uint32(md, rrsig->rrsig.expiration);
378 md_add_uint32(md, rrsig->rrsig.inception);
379 md_add_uint16(md, rrsig->rrsig.key_tag);
380
381 r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
382 if (r < 0)
383 goto finish;
384 gcry_md_write(md, wire_format_name, r);
385
386 for (k = 0; k < n; k++) {
e7ff0e0b 387 const char *suffix;
2b442ac8
LP
388 size_t l;
389 rr = list[k];
390
e7ff0e0b
LP
391 r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
392 if (r < 0)
393 goto finish;
394 if (r > 0) /* This is a wildcard! */
395 gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
396
397 r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true);
2b442ac8
LP
398 if (r < 0)
399 goto finish;
400 gcry_md_write(md, wire_format_name, r);
401
402 md_add_uint16(md, rr->key->type);
403 md_add_uint16(md, rr->key->class);
404 md_add_uint32(md, rrsig->rrsig.original_ttl);
405
406 assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
407 l = rr->wire_format_size - rr->wire_format_rdata_offset;
408 assert(l <= 0xFFFF);
409
410 md_add_uint16(md, (uint16_t) l);
411 gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
412 }
413
414 hash = gcry_md_read(md, 0);
415 if (!hash) {
416 r = -EIO;
417 goto finish;
418 }
419
420 if (*(uint8_t*) dnskey->dnskey.key == 0) {
421 /* exponent is > 255 bytes long */
422
423 exponent = (uint8_t*) dnskey->dnskey.key + 3;
424 exponent_size =
425 ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
426 ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
427
428 if (exponent_size < 256) {
429 r = -EINVAL;
430 goto finish;
431 }
432
433 if (3 + exponent_size >= dnskey->dnskey.key_size) {
434 r = -EINVAL;
435 goto finish;
436 }
437
438 modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
439 modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
440
441 } else {
442 /* exponent is <= 255 bytes long */
443
444 exponent = (uint8_t*) dnskey->dnskey.key + 1;
445 exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
446
447 if (exponent_size <= 0) {
448 r = -EINVAL;
449 goto finish;
450 }
451
452 if (1 + exponent_size >= dnskey->dnskey.key_size) {
453 r = -EINVAL;
454 goto finish;
455 }
456
457 modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
458 modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
459 }
460
461 r = dnssec_rsa_verify(
462 gcry_md_algo_name(gcry_md_get_algo(md)),
463 rrsig->rrsig.signature, rrsig->rrsig.signature_size,
464 hash, hash_size,
465 exponent, exponent_size,
466 modulus, modulus_size);
467 if (r < 0)
468 goto finish;
469
547973de
LP
470 *result = r ? DNSSEC_VALIDATED : DNSSEC_INVALID;
471 r = 0;
2b442ac8
LP
472
473finish:
474 gcry_md_close(md);
475 return r;
476}
477
478int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
479
480 assert(rrsig);
481 assert(dnskey);
482
483 /* Checks if the specified DNSKEY RR matches the key used for
484 * the signature in the specified RRSIG RR */
485
486 if (rrsig->key->type != DNS_TYPE_RRSIG)
487 return -EINVAL;
488
489 if (dnskey->key->type != DNS_TYPE_DNSKEY)
490 return 0;
491 if (dnskey->key->class != rrsig->key->class)
492 return 0;
493 if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
494 return 0;
495 if (dnskey->dnskey.protocol != 3)
496 return 0;
497 if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
498 return 0;
499
500 if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag)
501 return 0;
502
15accc27 503 return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
2b442ac8
LP
504}
505
105e1512 506int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
e7ff0e0b
LP
507 int r;
508
2b442ac8
LP
509 assert(key);
510 assert(rrsig);
511
512 /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
513
514 if (rrsig->key->type != DNS_TYPE_RRSIG)
515 return 0;
516 if (rrsig->key->class != key->class)
517 return 0;
518 if (rrsig->rrsig.type_covered != key->type)
519 return 0;
520
e7ff0e0b
LP
521 /* Make sure signer is a parent of the RRset */
522 r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer);
523 if (r <= 0)
524 return r;
525
526 /* Make sure the owner name has at least as many labels as the "label" fields indicates. */
527 r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key));
528 if (r < 0)
529 return r;
530 if (r < rrsig->rrsig.labels)
531 return 0;
532
2b442ac8
LP
533 return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
534}
535
2a326321
LP
536int dnssec_verify_rrset_search(
537 DnsAnswer *a,
538 DnsResourceKey *key,
539 DnsAnswer *validated_dnskeys,
547973de
LP
540 usec_t realtime,
541 DnssecResult *result) {
2a326321 542
203f1b35 543 bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
2b442ac8
LP
544 DnsResourceRecord *rrsig;
545 int r;
546
547 assert(key);
547973de 548 assert(result);
2b442ac8 549
105e1512 550 /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */
2b442ac8
LP
551
552 if (!a || a->n_rrs <= 0)
553 return -ENODATA;
554
555 /* Iterate through each RRSIG RR. */
556 DNS_ANSWER_FOREACH(rrsig, a) {
557 DnsResourceRecord *dnskey;
105e1512 558 DnsAnswerFlags flags;
2b442ac8 559
203f1b35 560 /* Is this an RRSIG RR that applies to RRs matching our key? */
2b442ac8
LP
561 r = dnssec_key_match_rrsig(key, rrsig);
562 if (r < 0)
563 return r;
564 if (r == 0)
565 continue;
566
567 found_rrsig = true;
568
547973de 569 /* Look for a matching key */
105e1512 570 DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) {
547973de 571 DnssecResult one_result;
2b442ac8 572
105e1512
LP
573 if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
574 continue;
575
203f1b35 576 /* Is this a DNSKEY RR that matches they key of our RRSIG? */
2b442ac8
LP
577 r = dnssec_rrsig_match_dnskey(rrsig, dnskey);
578 if (r < 0)
579 return r;
580 if (r == 0)
581 continue;
582
2a326321
LP
583 /* Take the time here, if it isn't set yet, so
584 * that we do all validations with the same
585 * time. */
586 if (realtime == USEC_INFINITY)
587 realtime = now(CLOCK_REALTIME);
588
2b442ac8
LP
589 /* Yay, we found a matching RRSIG with a matching
590 * DNSKEY, awesome. Now let's verify all entries of
591 * the RRSet against the RRSIG and DNSKEY
592 * combination. */
593
547973de 594 r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result);
203f1b35 595 if (r < 0)
2b442ac8 596 return r;
203f1b35
LP
597
598 switch (one_result) {
599
600 case DNSSEC_VALIDATED:
601 /* Yay, the RR has been validated,
602 * return immediately. */
547973de
LP
603 *result = DNSSEC_VALIDATED;
604 return 0;
2b442ac8 605
203f1b35
LP
606 case DNSSEC_INVALID:
607 /* If the signature is invalid, let's try another
608 key and/or signature. After all they
609 key_tags and stuff are not unique, and
610 might be shared by multiple keys. */
611 found_invalid = true;
612 continue;
613
614 case DNSSEC_UNSUPPORTED_ALGORITHM:
615 /* If the key algorithm is
616 unsupported, try another
617 RRSIG/DNSKEY pair, but remember we
618 encountered this, so that we can
619 return a proper error when we
620 encounter nothing better. */
621 found_unsupported_algorithm = true;
622 continue;
623
624 case DNSSEC_SIGNATURE_EXPIRED:
625 /* If the signature is expired, try
626 another one, but remember it, so
627 that we can return this */
628 found_expired_rrsig = true;
629 continue;
630
631 default:
632 assert_not_reached("Unexpected DNSSEC validation result");
633 }
2b442ac8
LP
634 }
635 }
636
203f1b35
LP
637 if (found_expired_rrsig)
638 *result = DNSSEC_SIGNATURE_EXPIRED;
639 else if (found_unsupported_algorithm)
640 *result = DNSSEC_UNSUPPORTED_ALGORITHM;
641 else if (found_invalid)
547973de
LP
642 *result = DNSSEC_INVALID;
643 else if (found_rrsig)
644 *result = DNSSEC_MISSING_KEY;
645 else
646 *result = DNSSEC_NO_SIGNATURE;
2b442ac8 647
547973de 648 return 0;
2b442ac8
LP
649}
650
105e1512
LP
651int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
652 DnsResourceRecord *rr;
653 int r;
654
655 /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */
656
657 DNS_ANSWER_FOREACH(rr, a) {
658 r = dnssec_key_match_rrsig(key, rr);
659 if (r < 0)
660 return r;
661 if (r > 0)
662 return 1;
663 }
664
665 return 0;
666}
667
2b442ac8 668int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
2b442ac8
LP
669 size_t c = 0;
670 int r;
671
672 /* Converts the specified hostname into DNSSEC canonicalized
673 * form. */
674
675 if (buffer_max < 2)
676 return -ENOBUFS;
677
678 for (;;) {
679 size_t i;
680
681 r = dns_label_unescape(&n, buffer, buffer_max);
682 if (r < 0)
683 return r;
684 if (r == 0)
685 break;
686 if (r > 0) {
687 int k;
688
689 /* DNSSEC validation is always done on the ASCII version of the label */
690 k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
691 if (k < 0)
692 return k;
693 if (k > 0)
694 r = k;
695 }
696
697 if (buffer_max < (size_t) r + 2)
698 return -ENOBUFS;
699
700 /* The DNSSEC canonical form is not clear on what to
701 * do with dots appearing in labels, the way DNS-SD
702 * does it. Refuse it for now. */
703
704 if (memchr(buffer, '.', r))
705 return -EINVAL;
706
707 for (i = 0; i < (size_t) r; i ++) {
708 if (buffer[i] >= 'A' && buffer[i] <= 'Z')
709 buffer[i] = buffer[i] - 'A' + 'a';
710 }
711
712 buffer[r] = '.';
713
714 buffer += r + 1;
715 c += r + 1;
716
717 buffer_max -= r + 1;
718 }
719
720 if (c <= 0) {
721 /* Not even a single label: this is the root domain name */
722
723 assert(buffer_max > 2);
724 buffer[0] = '.';
725 buffer[1] = 0;
726
727 return 1;
728 }
729
730 return (int) c;
731}
732
a1972a91
LP
733static int digest_to_gcrypt(uint8_t algorithm) {
734
735 /* Translates a DNSSEC digest algorithm into a gcrypt digest iedntifier */
736
737 switch (algorithm) {
738
739 case DNSSEC_DIGEST_SHA1:
740 return GCRY_MD_SHA1;
741
742 case DNSSEC_DIGEST_SHA256:
743 return GCRY_MD_SHA256;
744
af22c65b
LP
745 case DNSSEC_DIGEST_SHA384:
746 return GCRY_MD_SHA384;
747
a1972a91
LP
748 default:
749 return -EOPNOTSUPP;
750 }
751}
752
2b442ac8 753int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
2b442ac8 754 char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
a1972a91
LP
755 gcry_md_hd_t md = NULL;
756 size_t hash_size;
757 int algorithm;
2b442ac8
LP
758 void *result;
759 int r;
760
761 assert(dnskey);
762 assert(ds);
763
764 /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
765
766 if (dnskey->key->type != DNS_TYPE_DNSKEY)
767 return -EINVAL;
768 if (ds->key->type != DNS_TYPE_DS)
769 return -EINVAL;
770 if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
771 return -EKEYREJECTED;
772 if (dnskey->dnskey.protocol != 3)
773 return -EKEYREJECTED;
774
2b442ac8
LP
775 if (dnskey->dnskey.algorithm != ds->ds.algorithm)
776 return 0;
777 if (dnssec_keytag(dnskey) != ds->ds.key_tag)
778 return 0;
779
0638401a
LP
780 initialize_libgcrypt();
781
a1972a91
LP
782 algorithm = digest_to_gcrypt(ds->ds.digest_type);
783 if (algorithm < 0)
784 return algorithm;
2b442ac8 785
a1972a91
LP
786 hash_size = gcry_md_get_algo_dlen(algorithm);
787 assert(hash_size > 0);
2b442ac8 788
a1972a91
LP
789 if (ds->ds.digest_size != hash_size)
790 return 0;
2b442ac8 791
a1972a91
LP
792 r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
793 if (r < 0)
794 return r;
2b442ac8 795
a1972a91 796 gcry_md_open(&md, algorithm, 0);
2b442ac8
LP
797 if (!md)
798 return -EIO;
799
2b442ac8
LP
800 gcry_md_write(md, owner_name, r);
801 md_add_uint16(md, dnskey->dnskey.flags);
802 md_add_uint8(md, dnskey->dnskey.protocol);
803 md_add_uint8(md, dnskey->dnskey.algorithm);
804 gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);
805
806 result = gcry_md_read(md, 0);
807 if (!result) {
808 r = -EIO;
809 goto finish;
810 }
811
812 r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0;
813
814finish:
815 gcry_md_close(md);
816 return r;
817}
24710c48 818
547973de
LP
819int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
820 DnsResourceRecord *ds;
105e1512 821 DnsAnswerFlags flags;
547973de
LP
822 int r;
823
824 assert(dnskey);
825
826 if (dnskey->key->type != DNS_TYPE_DNSKEY)
827 return 0;
828
105e1512
LP
829 DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) {
830
831 if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
832 continue;
547973de
LP
833
834 if (ds->key->type != DNS_TYPE_DS)
835 continue;
836
d1c4ee32
LP
837 if (ds->key->class != dnskey->key->class)
838 continue;
839
840 r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key));
841 if (r < 0)
842 return r;
843 if (r == 0)
844 continue;
845
547973de
LP
846 r = dnssec_verify_dnskey(dnskey, ds);
847 if (r < 0)
848 return r;
849 if (r > 0)
850 return 1;
851 }
852
853 return 0;
854}
855
72667f08
LP
856int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
857 uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX];
858 gcry_md_hd_t md = NULL;
859 size_t hash_size;
860 int algorithm;
861 void *result;
862 unsigned k;
863 int r;
864
865 assert(nsec3);
866 assert(name);
867 assert(ret);
868
869 if (nsec3->key->type != DNS_TYPE_NSEC3)
870 return -EINVAL;
871
872 algorithm = digest_to_gcrypt(nsec3->nsec3.algorithm);
873 if (algorithm < 0)
874 return algorithm;
875
876 initialize_libgcrypt();
877
878 hash_size = gcry_md_get_algo_dlen(algorithm);
879 assert(hash_size > 0);
880
881 if (nsec3->nsec3.next_hashed_name_size != hash_size)
882 return -EINVAL;
883
884 r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true);
885 if (r < 0)
886 return r;
887
888 gcry_md_open(&md, algorithm, 0);
889 if (!md)
890 return -EIO;
891
892 gcry_md_write(md, wire_format, r);
893 gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
894
895 result = gcry_md_read(md, 0);
896 if (!result) {
897 r = -EIO;
898 goto finish;
899 }
900
901 for (k = 0; k < nsec3->nsec3.iterations; k++) {
902 uint8_t tmp[hash_size];
903 memcpy(tmp, result, hash_size);
904
905 gcry_md_reset(md);
906 gcry_md_write(md, tmp, hash_size);
907 gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
908
909 result = gcry_md_read(md, 0);
910 if (!result) {
911 r = -EIO;
912 goto finish;
913 }
914 }
915
916 memcpy(ret, result, hash_size);
917 r = (int) hash_size;
918
919finish:
920 gcry_md_close(md);
921 return r;
922}
923
db5b0e92
LP
924static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourceRecord *nsec3) {
925 const char *a, *b;
926 int r;
927
928 assert(rr);
929
db5b0e92
LP
930 if (rr->key->type != DNS_TYPE_NSEC3)
931 return 0;
932
933 /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
934 if (!IN_SET(rr->nsec3.flags, 0, 1))
935 return 0;
936
937 if (!nsec3)
938 return 1;
939
940 /* If a second NSEC3 RR is specified, also check if they are from the same zone. */
941
942 if (nsec3 == rr) /* Shortcut */
943 return 1;
944
945 if (rr->key->class != nsec3->key->class)
946 return 0;
947 if (rr->nsec3.algorithm != nsec3->nsec3.algorithm)
948 return 0;
949 if (rr->nsec3.iterations != nsec3->nsec3.iterations)
950 return 0;
951 if (rr->nsec3.salt_size != nsec3->nsec3.salt_size)
952 return 0;
953 if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0)
954 return 0;
955
956 a = DNS_RESOURCE_KEY_NAME(rr->key);
957 r = dns_name_parent(&a); /* strip off hash */
958 if (r < 0)
959 return r;
960 if (r == 0)
961 return 0;
962
963 b = DNS_RESOURCE_KEY_NAME(nsec3->key);
964 r = dns_name_parent(&b); /* strip off hash */
965 if (r < 0)
966 return r;
967 if (r == 0)
968 return 0;
969
970 return dns_name_equal(a, b);
971}
972
ed29bfdc 973static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
105e1512
LP
974 _cleanup_free_ char *next_closer_domain = NULL, *l = NULL;
975 uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
13b78323 976 const char *suffix, *p, *pp = NULL;
db5b0e92 977 DnsResourceRecord *rr, *suffix_rr;
105e1512
LP
978 DnsAnswerFlags flags;
979 int hashed_size, r;
ed29bfdc 980 bool a;
72667f08
LP
981
982 assert(key);
983 assert(result);
ed29bfdc 984 assert(authenticated);
72667f08 985
13b78323
LP
986 /* First step, look for the longest common suffix we find with any NSEC3 RR in the response. */
987 suffix = DNS_RESOURCE_KEY_NAME(key);
988 for (;;) {
db5b0e92 989 DNS_ANSWER_FOREACH_FLAGS(suffix_rr, flags, answer) {
13b78323
LP
990 _cleanup_free_ char *hashed_domain = NULL, *label = NULL;
991
db5b0e92
LP
992 r = nsec3_is_good(suffix_rr, flags, NULL);
993 if (r < 0)
994 return r;
995 if (r == 0)
13b78323
LP
996 continue;
997
db5b0e92 998 r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_rr->key), 1, suffix);
13b78323
LP
999 if (r < 0)
1000 return r;
1001 if (r > 0)
1002 goto found_suffix;
1003 }
1004
1005 /* Strip one label from the front */
1006 r = dns_name_parent(&suffix);
1007 if (r < 0)
1008 return r;
1009 if (r == 0)
1010 break;
1011 }
1012
1013 *result = DNSSEC_NSEC_NO_RR;
1014 return 0;
1015
1016found_suffix:
1017 /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */
105e1512
LP
1018 p = DNS_RESOURCE_KEY_NAME(key);
1019 for (;;) {
db5b0e92 1020 _cleanup_free_ char *hashed_domain = NULL, *label = NULL;
72667f08 1021
db5b0e92
LP
1022 hashed_size = dnssec_nsec3_hash(suffix_rr, p, hashed);
1023 if (hashed_size == -EOPNOTSUPP) {
1024 *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
1025 return 0;
1026 }
1027 if (hashed_size < 0)
1028 return hashed_size;
72667f08 1029
db5b0e92
LP
1030 label = base32hexmem(hashed, hashed_size, false);
1031 if (!label)
1032 return -ENOMEM;
72667f08 1033
db5b0e92
LP
1034 hashed_domain = strjoin(label, ".", suffix, NULL);
1035 if (!hashed_domain)
1036 return -ENOMEM;
1037
1038 DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
72667f08 1039
db5b0e92 1040 r = nsec3_is_good(rr, flags, suffix_rr);
72667f08
LP
1041 if (r < 0)
1042 return r;
105e1512
LP
1043 if (r == 0)
1044 continue;
1045
105e1512 1046 if (rr->nsec3.next_hashed_name_size != (size_t) hashed_size)
db5b0e92 1047 continue;
105e1512
LP
1048
1049 r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain);
72667f08
LP
1050 if (r < 0)
1051 return r;
ed29bfdc
LP
1052 if (r > 0) {
1053 a = flags & DNS_ANSWER_AUTHENTICATED;
13b78323 1054 goto found_closest_encloser;
ed29bfdc 1055 }
105e1512
LP
1056 }
1057
1058 /* We didn't find the closest encloser with this name,
1059 * but let's remember this domain name, it might be
1060 * the next closer name */
1061
1062 pp = p;
1063
1064 /* Strip one label from the front */
1065 r = dns_name_parent(&p);
1066 if (r < 0)
1067 return r;
1068 if (r == 0)
72667f08 1069 break;
105e1512 1070 }
72667f08 1071
105e1512
LP
1072 *result = DNSSEC_NSEC_NO_RR;
1073 return 0;
72667f08 1074
13b78323 1075found_closest_encloser:
105e1512 1076 /* We found a closest encloser in 'p'; next closer is 'pp' */
72667f08 1077
105e1512
LP
1078 /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */
1079 if (bitmap_isset(rr->nsec3.types, DNS_TYPE_DNAME))
1080 return -EBADMSG;
72667f08 1081
105e1512
LP
1082 /* Ensure that this data is from the delegated domain
1083 * (i.e. originates from the "lower" DNS server), and isn't
1084 * just glue records (i.e. doesn't originate from the "upper"
1085 * DNS server). */
1086 if (bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) &&
1087 !bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA))
1088 return -EBADMSG;
72667f08 1089
105e1512
LP
1090 if (!pp) {
1091 /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */
1092 *result = bitmap_isset(rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
ed29bfdc 1093 *authenticated = a;
105e1512
LP
1094 return 0;
1095 }
72667f08 1096
105e1512
LP
1097 r = dnssec_nsec3_hash(rr, pp, hashed);
1098 if (r < 0)
1099 return r;
1100 if (r != hashed_size)
1101 return -EBADMSG;
72667f08 1102
105e1512
LP
1103 l = base32hexmem(hashed, hashed_size, false);
1104 if (!l)
1105 return -ENOMEM;
72667f08 1106
105e1512
LP
1107 next_closer_domain = strjoin(l, ".", p, NULL);
1108 if (!next_closer_domain)
1109 return -ENOMEM;
72667f08 1110
105e1512
LP
1111 DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
1112 _cleanup_free_ char *label = NULL, *next_hashed_domain = NULL;
105e1512 1113
db5b0e92 1114 r = nsec3_is_good(rr, flags, suffix_rr);
105e1512
LP
1115 if (r < 0)
1116 return r;
1117 if (r == 0)
1118 continue;
1119
1120 label = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false);
1121 if (!label)
1122 return -ENOMEM;
1123
1124 next_hashed_domain = strjoin(label, ".", p, NULL);
1125 if (!next_hashed_domain)
1126 return -ENOMEM;
1127
1128 r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain);
1129 if (r < 0)
1130 return r;
1131 if (r > 0) {
1132 if (rr->nsec3.flags & 1)
1133 *result = DNSSEC_NSEC_OPTOUT;
1134 else
72667f08 1135 *result = DNSSEC_NSEC_NXDOMAIN;
105e1512 1136
ed29bfdc 1137 *authenticated = a && (flags & DNS_ANSWER_AUTHENTICATED);
105e1512
LP
1138 return 1;
1139 }
1140 }
1141
1142 *result = DNSSEC_NSEC_NO_RR;
1143 return 0;
1144}
1145
ed29bfdc 1146int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) {
105e1512
LP
1147 DnsResourceRecord *rr;
1148 bool have_nsec3 = false;
1149 DnsAnswerFlags flags;
1150 int r;
1151
1152 assert(key);
1153 assert(result);
ed29bfdc 1154 assert(authenticated);
105e1512
LP
1155
1156 /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
1157
1158 DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
1159
1160 if (rr->key->class != key->class)
1161 continue;
1162
105e1512
LP
1163 switch (rr->key->type) {
1164
1165 case DNS_TYPE_NSEC:
1166
1167 r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key));
1168 if (r < 0)
1169 return r;
1170 if (r > 0) {
1171 *result = bitmap_isset(rr->nsec.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA;
ed29bfdc 1172 *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
72667f08
LP
1173 return 0;
1174 }
1175
105e1512
LP
1176 r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name);
1177 if (r < 0)
1178 return r;
1179 if (r > 0) {
1180 *result = DNSSEC_NSEC_NXDOMAIN;
ed29bfdc 1181 *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
105e1512
LP
1182 return 0;
1183 }
72667f08 1184 break;
72667f08 1185
105e1512
LP
1186 case DNS_TYPE_NSEC3:
1187 have_nsec3 = true;
72667f08
LP
1188 break;
1189 }
1190 }
1191
105e1512
LP
1192 /* OK, this was not sufficient. Let's see if NSEC3 can help. */
1193 if (have_nsec3)
ed29bfdc 1194 return dnssec_test_nsec3(answer, key, result, authenticated);
105e1512 1195
72667f08
LP
1196 /* No approproate NSEC RR found, report this. */
1197 *result = DNSSEC_NSEC_NO_RR;
1198 return 0;
1199}
1200
24710c48
LP
1201static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
1202 [DNSSEC_NO] = "no",
b652d4a2 1203 [DNSSEC_DOWNGRADE_OK] = "downgrade-ok",
24710c48
LP
1204 [DNSSEC_YES] = "yes",
1205};
1206DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
547973de
LP
1207
1208static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
1209 [DNSSEC_VALIDATED] = "validated",
1210 [DNSSEC_INVALID] = "invalid",
203f1b35
LP
1211 [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired",
1212 [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm",
547973de
LP
1213 [DNSSEC_NO_SIGNATURE] = "no-signature",
1214 [DNSSEC_MISSING_KEY] = "missing-key",
203f1b35 1215 [DNSSEC_UNSIGNED] = "unsigned",
547973de 1216 [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
72667f08 1217 [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
b652d4a2 1218 [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
547973de
LP
1219};
1220DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);