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