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