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