Update repub branch u/fanf2/patch to rebasing branch u/fanf2/rebasing revision v9_15_...
[ipreg/bind9.git] / bin / dnssec / dnssec-cds.c
CommitLineData
ba37674d 1/*
843d3896 2 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
ba37674d
EH
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
843d3896
OS
7 *
8 * See the COPYRIGHT file distributed with this work for additional
9 * information regarding copyright ownership.
ba37674d
EH
10 */
11
12/*
13 * Written by Tony Finch <dot@dotat.at> <fanf2@cam.ac.uk>
14 * at Cambridge University Information Services
15 */
16
17/*! \file */
18
ba37674d 19#include <errno.h>
cb6a185c 20#include <inttypes.h>
994e6569 21#include <stdbool.h>
ba37674d
EH
22#include <stdlib.h>
23
24#include <isc/buffer.h>
25#include <isc/commandline.h>
ba37674d
EH
26#include <isc/file.h>
27#include <isc/hash.h>
28#include <isc/mem.h>
29#include <isc/print.h>
30#include <isc/serial.h>
31#include <isc/string.h>
32#include <isc/time.h>
33#include <isc/util.h>
34
35#include <dns/callbacks.h>
36#include <dns/db.h>
37#include <dns/dbiterator.h>
38#include <dns/dnssec.h>
39#include <dns/ds.h>
40#include <dns/fixedname.h>
41#include <dns/keyvalues.h>
42#include <dns/log.h>
43#include <dns/master.h>
44#include <dns/name.h>
45#include <dns/rdata.h>
46#include <dns/rdataclass.h>
47#include <dns/rdatalist.h>
48#include <dns/rdataset.h>
49#include <dns/rdatasetiter.h>
50#include <dns/rdatatype.h>
51#include <dns/result.h>
52#include <dns/time.h>
53
54#include <dst/dst.h>
55
c3b8130f 56#if USE_PKCS11
ba37674d
EH
57#include <pk11/result.h>
58#endif
59
60#include "dnssectool.h"
61
ba37674d 62const char *program = "dnssec-cds";
ba37674d
EH
63
64/*
65 * Infrastructure
66 */
67static isc_log_t *lctx = NULL;
68static isc_mem_t *mctx = NULL;
ba37674d
EH
69
70/*
71 * The domain we are working on
72 */
73static const char *namestr = NULL;
74static dns_fixedname_t fixed;
75static dns_name_t *name = NULL;
76static dns_rdataclass_t rdclass = dns_rdataclass_in;
77
d36b7f86 78static const char *startstr = NULL; /* from which we derive notbefore */
ba37674d
EH
79static isc_stdtime_t notbefore = 0; /* restrict sig inception times */
80static dns_rdata_rrsig_t oldestsig; /* for recording inception time */
81
82static int nkey; /* number of child zone DNSKEY records */
83
84/*
85 * The validation strategy of this program is top-down.
86 *
87 * We start with an implicitly trusted authoritative dsset.
88 *
89 * The child DNSKEY RRset is scanned to find out which keys are
90 * authenticated by DS records, and the result is recorded in a key
91 * table as described later in this comment.
92 *
93 * The key table is used up to three times to verify the signatures on
94 * the child DNSKEY, CDNSKEY, and CDS RRsets. In this program, only keys
95 * that have matching DS records are used for validating signatures.
96 *
97 * For replay attack protection, signatures are ignored if their inception
98 * time is before the previously recorded inception time. We use the earliest
99 * signature so that another run of dnssec-cds with the same records will
100 * still accept all the signatures.
101 *
102 * A key table is an array of nkey keyinfo structures, like
103 *
104 * keyinfo_t key_tbl[nkey];
105 *
106 * Each key is decoded into more useful representations, held in
107 * keyinfo->rdata
108 * keyinfo->dst
109 *
110 * If a key has no matching DS record then keyinfo->dst is NULL.
111 *
112 * The key algorithm and ID are saved in keyinfo->algo and
113 * keyinfo->tag for quicky skipping DS and RRSIG records that can't
114 * match.
115 */
116typedef struct keyinfo {
117 dns_rdata_t rdata;
118 dst_key_t *dst;
0f219714 119 dns_secalg_t algo;
ba37674d
EH
120 dns_keytag_t tag;
121} keyinfo_t;
122
123/* A replaceable function that can generate a DS RRset from some input */
124typedef isc_result_t
125ds_maker_func_t(dns_rdatalist_t *dslist, isc_buffer_t *buf, dns_rdata_t *rdata);
126
127static dns_rdataset_t cdnskey_set, cdnskey_sig;
128static dns_rdataset_t cds_set, cds_sig;
129static dns_rdataset_t dnskey_set, dnskey_sig;
130static dns_rdataset_t old_ds_set, new_ds_set;
131
132static keyinfo_t *old_key_tbl, *new_key_tbl;
133
134isc_buffer_t *new_ds_buf = NULL; /* backing store for new_ds_set */
135
136static void
137verbose_time(int level, const char *msg, isc_stdtime_t time) {
138 isc_result_t result;
139 isc_buffer_t timebuf;
140 char timestr[32];
141
142 if (verbose < level) {
143 return;
144 }
145
146 isc_buffer_init(&timebuf, timestr, sizeof(timestr));
147 result = dns_time64_totext(time, &timebuf);
148 check_result(result, "dns_time64_totext()");
149 isc_buffer_putuint8(&timebuf, 0);
150 if (verbose < 3) {
151 vbprintf(level, "%s %s\n", msg, timestr);
152 } else {
c355e1f3
OS
153 vbprintf(level, "%s %s (%" PRIu32 ")\n",
154 msg, timestr, time);
ba37674d
EH
155 }
156}
157
158static void
159initname(char *setname) {
160 isc_result_t result;
161 isc_buffer_t buf;
162
4df4a8e7 163 name = dns_fixedname_initname(&fixed);
ba37674d
EH
164 namestr = setname;
165
166 isc_buffer_init(&buf, setname, strlen(setname));
167 isc_buffer_add(&buf, strlen(setname));
168 result = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL);
169 if (result != ISC_R_SUCCESS) {
170 fatal("could not initialize name %s", setname);
171 }
172}
173
174static void
175findset(dns_db_t *db, dns_dbnode_t *node, dns_rdatatype_t type,
176 dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset)
177{
178 isc_result_t result;
179
180 dns_rdataset_init(rdataset);
181 if (sigrdataset != NULL) {
182 dns_rdataset_init(sigrdataset);
183 }
184 result = dns_db_findrdataset(db, node, NULL, type, 0, 0,
185 rdataset, sigrdataset);
186 if (result != ISC_R_NOTFOUND) {
187 check_result(result, "dns_db_findrdataset()");
188 }
189}
190
191static void
192freeset(dns_rdataset_t *rdataset) {
193 if (dns_rdataset_isassociated(rdataset)) {
194 dns_rdataset_disassociate(rdataset);
195 }
196}
197
198static void
199freelist(dns_rdataset_t *rdataset) {
200 dns_rdatalist_t *rdlist;
201 dns_rdata_t *rdata;
202
203 if (!dns_rdataset_isassociated(rdataset)) {
204 return;
205 }
206
207 dns_rdatalist_fromrdataset(rdataset, &rdlist);
208
209 for (rdata = ISC_LIST_HEAD(rdlist->rdata);
210 rdata != NULL;
211 rdata = ISC_LIST_HEAD(rdlist->rdata))
212 {
213 ISC_LIST_UNLINK(rdlist->rdata, rdata, link);
214 isc_mem_put(mctx, rdata, sizeof(*rdata));
215 }
216 isc_mem_put(mctx, rdlist, sizeof(*rdlist));
217 dns_rdataset_disassociate(rdataset);
218}
219
220static void
221free_all_sets(void) {
222 freeset(&cdnskey_set);
223 freeset(&cdnskey_sig);
224 freeset(&cds_set);
225 freeset(&cds_sig);
226 freeset(&dnskey_set);
227 freeset(&dnskey_sig);
228 freeset(&old_ds_set);
229 freelist(&new_ds_set);
230 if (new_ds_buf != NULL) {
231 isc_buffer_free(&new_ds_buf);
232 }
233}
234
235static void
236load_db(const char *filename, dns_db_t **dbp, dns_dbnode_t **nodep) {
237 isc_result_t result;
238
239 result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone,
240 rdclass, 0, NULL, dbp);
241 check_result(result, "dns_db_create()");
242
275a6a3b
WK
243 result = dns_db_load(*dbp, filename,
244 dns_masterformat_text, DNS_MASTER_HINT);
ba37674d
EH
245 if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) {
246 fatal("can't load %s: %s", filename,
247 isc_result_totext(result));
248 }
249
994e6569 250 result = dns_db_findnode(*dbp, name, false, nodep);
ba37674d
EH
251 if (result != ISC_R_SUCCESS) {
252 fatal("can't find %s node in %s", namestr, filename);
253 }
254}
255
256static void
257free_db(dns_db_t **dbp, dns_dbnode_t **nodep) {
258 dns_db_detachnode(*dbp, nodep);
259 dns_db_detach(dbp);
260}
261
262static void
263load_child_sets(const char *file) {
264 dns_db_t *db = NULL;
265 dns_dbnode_t *node = NULL;
266
267 load_db(file, &db, &node);
268 findset(db, node, dns_rdatatype_dnskey, &dnskey_set, &dnskey_sig);
269 findset(db, node, dns_rdatatype_cdnskey, &cdnskey_set, &cdnskey_sig);
270 findset(db, node, dns_rdatatype_cds, &cds_set, &cds_sig);
271 free_db(&db, &node);
272}
273
274static void
275get_dsset_name(char *filename, size_t size,
276 const char *path, const char *suffix)
277{
278 isc_result_t result;
279 isc_buffer_t buf;
280 size_t len;
281
282 isc_buffer_init(&buf, filename, size);
283
284 len = strlen(path);
285
286 /* allow room for a trailing slash */
287 if (isc_buffer_availablelength(&buf) <= len) {
288 fatal("%s: pathname too long", path);
289 }
290 isc_buffer_putstr(&buf, path);
291
292 if (isc_file_isdirectory(path) == ISC_R_SUCCESS) {
293 const char *prefix = "dsset-";
294
295 if (path[len - 1] != '/') {
296 isc_buffer_putstr(&buf, "/");
297 }
298
299 if (isc_buffer_availablelength(&buf) < strlen(prefix)) {
300 fatal("%s: pathname too long", path);
301 }
302 isc_buffer_putstr(&buf, prefix);
303
994e6569 304 result = dns_name_tofilenametext(name, false, &buf);
ba37674d
EH
305 check_result(result, "dns_name_tofilenametext()");
306 if (isc_buffer_availablelength(&buf) == 0) {
307 fatal("%s: pathname too long", path);
308 }
309 }
310 /* allow room for a trailing nul */
311 if (isc_buffer_availablelength(&buf) <= strlen(suffix)) {
312 fatal("%s: pathname too long", path);
313 }
314 isc_buffer_putstr(&buf, suffix);
315 isc_buffer_putuint8(&buf, 0);
316}
317
318static void
319load_parent_set(const char *path) {
320 isc_result_t result;
321 dns_db_t *db = NULL;
322 dns_dbnode_t *node = NULL;
323 isc_time_t modtime;
324 char filename[PATH_MAX + 1];
325
326 get_dsset_name(filename, sizeof(filename), path, "");
327
328 result = isc_file_getmodtime(filename, &modtime);
329 if (result != ISC_R_SUCCESS) {
330 fatal("could not get modification time of %s: %s",
331 filename, isc_result_totext(result));
332 }
333 notbefore = isc_time_seconds(&modtime);
334 if (startstr != NULL) {
335 isc_stdtime_t now;
336 isc_stdtime_get(&now);
337 notbefore = strtotime(startstr, now, notbefore, NULL);
338 }
339 verbose_time(1, "child records must not be signed before", notbefore);
340
341 load_db(filename, &db, &node);
342 findset(db, node, dns_rdatatype_ds, &old_ds_set, NULL);
343
344 if (!dns_rdataset_isassociated(&old_ds_set)) {
345 fatal("could not find DS records for %s in %s",
346 namestr, filename);
347 }
348
349 free_db(&db, &node);
350}
351
5df3f839
MA
352#define MAX_CDS_RDATA_TEXT_SIZE DNS_RDATA_MAXLENGTH * 2
353
ba37674d
EH
354static isc_buffer_t *
355formatset(dns_rdataset_t *rdataset) {
356 isc_result_t result;
357 isc_buffer_t *buf = NULL;
358 dns_master_style_t *style = NULL;
359 unsigned int styleflags;
ba37674d
EH
360
361 styleflags = (rdataset->ttl == 0) ? DNS_STYLEFLAG_NO_TTL : 0;
362
363 /*
364 * This style is for consistency with the output of dnssec-dsfromkey
365 * which just separates fields with spaces. The huge tab stop width
366 * eliminates any tab characters.
367 */
e2a06db7
WK
368 result = dns_master_stylecreate(&style, styleflags,
369 0, 0, 0, 0, 0, 1000000, 0,
370 mctx);
5df3f839 371 check_result(result, "dns_master_stylecreate2 failed");
ba37674d 372
5df3f839
MA
373 result = isc_buffer_allocate(mctx, &buf, MAX_CDS_RDATA_TEXT_SIZE);
374 check_result(result, "printing DS records");
375 result = dns_master_rdatasettotext(name, rdataset, style, buf);
376
377 if ((result == ISC_R_SUCCESS) && isc_buffer_availablelength(buf) < 1) {
378 result = ISC_R_NOSPACE;
379 }
380
381 check_result(result, "dns_rdataset_totext()");
ba37674d
EH
382
383 isc_buffer_putuint8(buf, 0);
384
385 dns_master_styledestroy(&style, mctx);
386
387 return (buf);
388}
389
390static void
391write_parent_set(const char *path, const char *inplace,
994e6569 392 bool nsupdate, dns_rdataset_t *rdataset)
ba37674d
EH
393{
394 isc_result_t result;
395 isc_buffer_t *buf = NULL;
396 isc_region_t r;
397 isc_time_t filetime;
398 char backname[PATH_MAX + 1];
399 char filename[PATH_MAX + 1];
400 char tmpname[PATH_MAX + 1];
401 FILE *fp = NULL;
402
403 if (nsupdate && inplace == NULL) {
404 return;
405 }
406
407 buf = formatset(rdataset);
408 isc_buffer_usedregion(buf, &r);
409
410 /*
411 * Try to ensure a write error doesn't make a zone go insecure!
412 */
413 if (inplace == NULL) {
414 printf("%s", (char *)r.base);
415 isc_buffer_free(&buf);
416 if (fflush(stdout) == EOF) {
417 fatal("error writing to stdout: %s", strerror(errno));
418 }
419 return;
420 }
421
422 if (inplace[0] != '\0') {
423 get_dsset_name(backname, sizeof(backname), path, inplace);
424 }
425 get_dsset_name(filename, sizeof(filename), path, "");
426 get_dsset_name(tmpname, sizeof(tmpname), path, "-XXXXXXXXXX");
427
428 result = isc_file_openunique(tmpname, &fp);
429 if (result != ISC_R_SUCCESS) {
430 fatal("open %s: %s", tmpname, isc_result_totext(result));
431 }
432 fprintf(fp, "%s", (char *)r.base);
433 isc_buffer_free(&buf);
434 if (fclose(fp) == EOF) {
3f3b51e7 435 int err = errno;
ba37674d 436 isc_file_remove(tmpname);
3f3b51e7 437 fatal("error writing to %s: %s", tmpname, strerror(err));
ba37674d
EH
438 }
439
440 isc_time_set(&filetime, oldestsig.timesigned, 0);
441 result = isc_file_settime(tmpname, &filetime);
442 if (result != ISC_R_SUCCESS) {
443 isc_file_remove(tmpname);
444 fatal("can't set modification time of %s: %s",
445 tmpname, isc_result_totext(result));
446 }
447
448 if (inplace[0] != '\0') {
449 isc_file_rename(filename, backname);
450 }
451 isc_file_rename(tmpname, filename);
452}
453
454typedef enum { LOOSE, TIGHT } strictness_t;
455
456/*
457 * Find out if any (C)DS record matches a particular (C)DNSKEY.
458 */
994e6569 459static bool
ba37674d
EH
460match_key_dsset(keyinfo_t *ki, dns_rdataset_t *dsset, strictness_t strictness)
461{
462 isc_result_t result;
463 unsigned char dsbuf[DNS_DS_BUFFERSIZE];
464
465 for (result = dns_rdataset_first(dsset);
466 result == ISC_R_SUCCESS;
467 result = dns_rdataset_next(dsset))
468 {
469 dns_rdata_ds_t ds;
470 dns_rdata_t dsrdata = DNS_RDATA_INIT;
471 dns_rdata_t newdsrdata = DNS_RDATA_INIT;
994e6569 472 bool c;
ba37674d
EH
473
474 dns_rdataset_current(dsset, &dsrdata);
475 result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
476 check_result(result, "dns_rdata_tostruct(DS)");
477
478 if (ki->tag != ds.key_tag || ki->algo != ds.algorithm) {
479 continue;
480 }
481
ba37674d
EH
482 result = dns_ds_buildrdata(name, &ki->rdata, ds.digest_type,
483 dsbuf, &newdsrdata);
ba37674d
EH
484 if (result != ISC_R_SUCCESS) {
485 vbprintf(3, "dns_ds_buildrdata("
486 "keytag=%d, algo=%d, digest=%d): %s\n",
487 ds.key_tag, ds.algorithm, ds.digest_type,
488 dns_result_totext(result));
489 continue;
490 }
491 /* allow for both DS and CDS */
492 c = dsrdata.type != dns_rdatatype_ds;
493 dsrdata.type = dns_rdatatype_ds;
494 if (dns_rdata_compare(&dsrdata, &newdsrdata) == 0) {
495 vbprintf(1, "found matching %s %d %d %d\n",
496 c ? "CDS" : "DS",
497 ds.key_tag, ds.algorithm, ds.digest_type);
994e6569 498 return (true);
ba37674d
EH
499 } else if (strictness == TIGHT) {
500 vbprintf(0, "key does not match %s %d %d %d "
501 "when it looks like it should\n",
502 c ? "CDS" : "DS",
503 ds.key_tag, ds.algorithm, ds.digest_type);
994e6569 504 return (false);
ba37674d
EH
505 }
506 }
507
d36b7f86
EH
508 vbprintf(1, "no matching %s for %s %d %d\n",
509 dsset->type == dns_rdatatype_cds
510 ? "CDS" : "DS",
511 ki->rdata.type == dns_rdatatype_cdnskey
512 ? "CDNSKEY" : "DNSKEY",
513 ki->tag, ki->algo);
514
994e6569 515 return (false);
ba37674d
EH
516}
517
518/*
519 * Find which (C)DNSKEY records match a (C)DS RRset.
520 * This creates a keyinfo_t key_tbl[nkey] array.
521 */
522static keyinfo_t *
523match_keyset_dsset(dns_rdataset_t *keyset, dns_rdataset_t *dsset,
524 strictness_t strictness)
525{
526 isc_result_t result;
527 keyinfo_t *keytable;
528 int i;
529
530 nkey = dns_rdataset_count(keyset);
531
532 keytable = isc_mem_get(mctx, sizeof(keyinfo_t) * nkey);
533 if (keytable == NULL) {
534 fatal("out of memory");
535 }
536
537 for (result = dns_rdataset_first(keyset), i = 0;
538 result == ISC_R_SUCCESS;
539 result = dns_rdataset_next(keyset), i++)
540 {
541 keyinfo_t *ki;
542 dns_rdata_dnskey_t dnskey;
543 dns_rdata_t *keyrdata;
544 isc_region_t r;
545
546 INSIST(i < nkey);
547 ki = &keytable[i];
548 keyrdata = &ki->rdata;
549
550 dns_rdata_init(keyrdata);
551 dns_rdataset_current(keyset, keyrdata);
552
553 result = dns_rdata_tostruct(keyrdata, &dnskey, NULL);
554 check_result(result, "dns_rdata_tostruct(DNSKEY)");
555 ki->algo = dnskey.algorithm;
556
557 dns_rdata_toregion(keyrdata, &r);
e69dc0db 558 ki->tag = dst_region_computeid(&r);
ba37674d
EH
559
560 ki->dst = NULL;
561 if (!match_key_dsset(ki, dsset, strictness)) {
562 continue;
563 }
564
565 result = dns_dnssec_keyfromrdata(name, keyrdata,
566 mctx, &ki->dst);
567 if (result != ISC_R_SUCCESS) {
568 vbprintf(3, "dns_dnssec_keyfromrdata("
569 "keytag=%d, algo=%d): %s\n",
570 ki->tag, ki->algo,
571 dns_result_totext(result));
572 }
573 }
574
575 return (keytable);
576}
577
578static void
579free_keytable(keyinfo_t **keytable_p) {
580 keyinfo_t *keytable = *keytable_p;
581 keyinfo_t *ki;
582 int i;
583
584 for (i = 0; i < nkey; i++) {
585 ki = &keytable[i];
586 if (ki->dst != NULL) {
587 dst_key_free(&ki->dst);
588 }
589 }
590
591 isc_mem_put(mctx, keytable, sizeof(keyinfo_t) * nkey);
592 *keytable_p = NULL;
593}
594
595/*
596 * Find out which keys have signed an RRset. Keys that do not match a
597 * DS record are skipped.
598 *
599 * The return value is an array with nkey elements, one for each key,
600 * either zero if the key was skipped or did not sign the RRset, or
601 * otherwise the key algorithm. This is used by the signature coverage
602 * check functions below.
603 */
0f219714 604static dns_secalg_t *
ba37674d
EH
605matching_sigs(keyinfo_t *keytbl, dns_rdataset_t *rdataset,
606 dns_rdataset_t *sigset)
607{
608 isc_result_t result;
0f219714 609 dns_secalg_t *algo;
ba37674d
EH
610 int i;
611
612 algo = isc_mem_get(mctx, nkey);
613 if (algo == NULL) {
614 fatal("allocating RRSIG/DNSKEY match list: %s",
615 isc_result_totext(ISC_R_NOMEMORY));
616 }
617 memset(algo, 0, nkey);
618
619 for (result = dns_rdataset_first(sigset);
620 result == ISC_R_SUCCESS;
621 result = dns_rdataset_next(sigset))
622 {
623 dns_rdata_t sigrdata = DNS_RDATA_INIT;
624 dns_rdata_rrsig_t sig;
625
626 dns_rdataset_current(sigset, &sigrdata);
627 result = dns_rdata_tostruct(&sigrdata, &sig, NULL);
628 check_result(result, "dns_rdata_tostruct(RRSIG)");
629
630 /*
631 * Replay attack protection: check against current age limit
632 */
633 if (isc_serial_lt(sig.timesigned, notbefore)) {
634 vbprintf(1, "skip RRSIG by key %d: too old\n",
635 sig.keyid);
636 continue;
637 }
638
639 for (i = 0; i < nkey; i++) {
640 keyinfo_t *ki = &keytbl[i];
d36b7f86 641 if (sig.keyid != ki->tag ||
ba37674d
EH
642 sig.algorithm != ki->algo ||
643 !dns_name_equal(&sig.signer, name))
644 {
645 continue;
646 }
d36b7f86
EH
647 if (ki->dst == NULL) {
648 vbprintf(1, "skip RRSIG by key %d:"
649 " no matching (C)DS\n",
650 sig.keyid);
651 continue;
652 }
ba37674d
EH
653
654 result = dns_dnssec_verify(name, rdataset, ki->dst,
994e6569 655 false, 0, mctx,
25cd3168
WK
656 &sigrdata, NULL);
657
658 if (result != ISC_R_SUCCESS &&
659 result != DNS_R_FROMWILDCARD) {
d36b7f86
EH
660 vbprintf(1, "skip RRSIG by key %d:"
661 " verification failed: %s\n",
662 sig.keyid, isc_result_totext(result));
ba37674d
EH
663 continue;
664 }
665
666 vbprintf(1, "found RRSIG by key %d\n", ki->tag);
667 algo[i] = sig.algorithm;
668
669 /*
670 * Replay attack protection: work out next age limit,
671 * only after the signature has been verified
672 */
673 if (oldestsig.timesigned == 0 ||
674 isc_serial_lt(sig.timesigned,
675 oldestsig.timesigned))
676 {
677 verbose_time(2, "this is the oldest so far",
678 sig.timesigned);
679 oldestsig = sig;
680 }
681 }
682 }
683
684 return (algo);
685}
686
687/*
688 * Consume the result of matching_sigs(). When checking records
689 * fetched from the child zone, any working signature is enough.
690 */
994e6569 691static bool
0f219714 692signed_loose(dns_secalg_t *algo) {
994e6569 693 bool ok = false;
ba37674d
EH
694 int i;
695 for (i = 0; i < nkey; i++) {
696 if (algo[i] != 0) {
994e6569 697 ok = true;
ba37674d
EH
698 }
699 }
700 isc_mem_put(mctx, algo, nkey);
701 return (ok);
702}
703
704/*
705 * Consume the result of matching_sigs(). To ensure that the new DS
706 * RRset does not break the chain of trust to the DNSKEY RRset, every
707 * key algorithm in the DS RRset must have a signature in the DNSKEY
708 * RRset.
709 */
994e6569 710static bool
0f219714 711signed_strict(dns_rdataset_t *dsset, dns_secalg_t *algo) {
ba37674d 712 isc_result_t result;
994e6569 713 bool all_ok = true;
ba37674d
EH
714
715 for (result = dns_rdataset_first(dsset);
716 result == ISC_R_SUCCESS;
717 result = dns_rdataset_next(dsset))
718 {
719 dns_rdata_t dsrdata = DNS_RDATA_INIT;
720 dns_rdata_ds_t ds;
994e6569 721 bool ds_ok;
ba37674d
EH
722 int i;
723
724 dns_rdataset_current(dsset, &dsrdata);
725 result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
726 check_result(result, "dns_rdata_tostruct(DS)");
727
994e6569 728 ds_ok = false;
ba37674d
EH
729 for (i = 0; i < nkey; i++) {
730 if (algo[i] == ds.algorithm) {
994e6569 731 ds_ok = true;
ba37674d
EH
732 }
733 }
734 if (!ds_ok) {
735 vbprintf(0, "missing signature for algorithm %d "
736 "(key %d)\n", ds.algorithm, ds.key_tag);
994e6569 737 all_ok = false;
ba37674d
EH
738 }
739 }
740
741 isc_mem_put(mctx, algo, nkey);
742 return (all_ok);
743}
744
745static dns_rdata_t *
746rdata_get(void) {
747 dns_rdata_t *rdata;
748
749 rdata = isc_mem_get(mctx, sizeof(*rdata));
750 if (rdata == NULL) {
751 fatal("allocating DS rdata: %s",
752 isc_result_totext(ISC_R_NOMEMORY));
753 }
754 dns_rdata_init(rdata);
755
756 return (rdata);
757}
758
759static isc_result_t
760rdata_put(isc_result_t result, dns_rdatalist_t *rdlist, dns_rdata_t *rdata) {
761 if (result == ISC_R_SUCCESS) {
762 ISC_LIST_APPEND(rdlist->rdata, rdata, link);
763 } else {
764 isc_mem_put(mctx, rdata, sizeof(*rdata));
765 }
766
767 return (result);
768}
769
770/*
771 * This basically copies the rdata into the buffer, but going via the
772 * unpacked struct has the side-effect of changing the rdatatype. The
773 * dns_rdata_cds_t and dns_rdata_ds_t types are aliases.
774 */
775static isc_result_t
776ds_from_cds(dns_rdatalist_t *dslist, isc_buffer_t *buf, dns_rdata_t *cds) {
777 isc_result_t result;
778 dns_rdata_ds_t ds;
779 dns_rdata_t *rdata;
780
781 rdata = rdata_get();
782
783 result = dns_rdata_tostruct(cds, &ds, NULL);
784 check_result(result, "dns_rdata_tostruct(CDS)");
785 ds.common.rdtype = dns_rdatatype_ds;
786
787 result = dns_rdata_fromstruct(rdata, rdclass, dns_rdatatype_ds,
788 &ds, buf);
789
790 return (rdata_put(result, dslist, rdata));
791}
792
793static isc_result_t
794ds_from_cdnskey(dns_rdatalist_t *dslist, isc_buffer_t *buf,
795 dns_rdata_t *cdnskey)
796{
797 isc_result_t result;
798 unsigned i, n;
799
800 n = sizeof(dtype)/sizeof(dtype[0]);
801 for (i = 0; i < n; i++) {
802 if (dtype[i] != 0) {
803 dns_rdata_t *rdata;
804 isc_region_t r;
805
806 isc_buffer_availableregion(buf, &r);
807 if (r.length < DNS_DS_BUFFERSIZE) {
808 return (ISC_R_NOSPACE);
809 }
810
ba37674d
EH
811 rdata = rdata_get();
812 result = dns_ds_buildrdata(name, cdnskey, dtype[i],
813 r.base, rdata);
814 if (result == ISC_R_SUCCESS) {
815 isc_buffer_add(buf, DNS_DS_BUFFERSIZE);
816 }
817
818 result = rdata_put(result, dslist, rdata);
819 if (result != ISC_R_SUCCESS) {
820 return (result);
821 }
822 }
823 }
824
825 return (ISC_R_SUCCESS);
826}
827
ba37674d
EH
828static void
829make_new_ds_set(ds_maker_func_t *ds_from_rdata,
cb6a185c 830 uint32_t ttl, dns_rdataset_t *rdset)
ba37674d
EH
831{
832 unsigned int size = 16;
833 for (;;) {
834 isc_result_t result;
835 dns_rdatalist_t *dslist;
836
837 dslist = isc_mem_get(mctx, sizeof(*dslist));
838 if (dslist == NULL) {
839 fatal("allocating new DS list: %s",
840 isc_result_totext(ISC_R_NOMEMORY));
841 }
842
843 dns_rdatalist_init(dslist);
844 dslist->rdclass = rdclass;
845 dslist->type = dns_rdatatype_ds;
846 dslist->ttl = ttl;
847
848 dns_rdataset_init(&new_ds_set);
849 result = dns_rdatalist_tordataset(dslist, &new_ds_set);
850 check_result(result, "dns_rdatalist_tordataset(dslist)");
851
852 result = isc_buffer_allocate(mctx, &new_ds_buf, size);
853 check_result(result, "building new DS records");
854
855 for (result = dns_rdataset_first(rdset);
856 result == ISC_R_SUCCESS;
857 result = dns_rdataset_next(rdset))
858 {
859 isc_result_t tresult;
860 dns_rdata_t rdata = DNS_RDATA_INIT;
861
862 dns_rdataset_current(rdset, &rdata);
863
864 tresult = ds_from_rdata(dslist, new_ds_buf, &rdata);
865 if (tresult == ISC_R_NOSPACE) {
866 vbprintf(20, "DS list buffer size %u\n", size);
867 freelist(&new_ds_set);
868 isc_buffer_free(&new_ds_buf);
869 size *= 2;
870 break;
871 }
872
873 check_result(tresult, "ds_from_rdata()");
874 }
875
876 if (result == ISC_R_NOMORE) {
877 break;
878 }
879 }
880}
881
882static inline int
883rdata_cmp(const void *rdata1, const void *rdata2) {
884 return (dns_rdata_compare((const dns_rdata_t *)rdata1,
885 (const dns_rdata_t *)rdata2));
886}
887
888/*
889 * Ensure that every key identified by the DS RRset has the same set of
890 * digest types.
891 */
994e6569 892static bool
ba37674d
EH
893consistent_digests(dns_rdataset_t *dsset) {
894 isc_result_t result;
895 dns_rdata_t *arrdata;
896 dns_rdata_ds_t *ds;
897 dns_keytag_t key_tag;
0f219714 898 dns_secalg_t algorithm;
994e6569 899 bool match;
ba37674d
EH
900 int i, j, n, d;
901
902 /*
903 * First sort the dsset. DS rdata fields are tag, algorithm, digest,
904 * so sorting them brings together all the records for each key.
905 */
906
907 n = dns_rdataset_count(dsset);
908
909 arrdata = isc_mem_get(mctx, n * sizeof(dns_rdata_t));
910 if (arrdata == NULL) {
911 fatal("allocating DS rdata array: %s",
912 isc_result_totext(ISC_R_NOMEMORY));
913 }
914
915 for (result = dns_rdataset_first(dsset), i = 0;
916 result == ISC_R_SUCCESS;
917 result = dns_rdataset_next(dsset), i++)
918 {
919 dns_rdata_init(&arrdata[i]);
920 dns_rdataset_current(dsset, &arrdata[i]);
921 }
922
923 qsort(arrdata, n, sizeof(dns_rdata_t), rdata_cmp);
924
925 /*
926 * Convert sorted arrdata to more accessible format
927 */
928 ds = isc_mem_get(mctx, n * sizeof(dns_rdata_ds_t));
929 if (ds == NULL) {
930 fatal("allocating unpacked DS array: %s",
931 isc_result_totext(ISC_R_NOMEMORY));
932 }
933
934 for (i = 0; i < n; i++) {
935 result = dns_rdata_tostruct(&arrdata[i], &ds[i], NULL);
936 check_result(result, "dns_rdata_tostruct(DS)");
937 }
938
939 /*
940 * Count number of digest types (d) for first key
941 */
942 key_tag = ds[0].key_tag;
943 algorithm = ds[0].algorithm;
944 for (d = 0, i = 0; i < n; i++, d++) {
945 if (ds[i].key_tag != key_tag || ds[i].algorithm != algorithm) {
946 break;
947 }
948 }
949
950 /*
951 * Check subsequent keys match the first one
952 */
994e6569 953 match = true;
ba37674d
EH
954 while (i < n) {
955 key_tag = ds[i].key_tag;
956 algorithm = ds[i].algorithm;
957 for (j = 0; j < d && i+j < n; j++) {
958 if (ds[i+j].key_tag != key_tag ||
959 ds[i+j].algorithm != algorithm ||
960 ds[i+j].digest_type != ds[j].digest_type)
961 {
994e6569 962 match = false;
ba37674d
EH
963 }
964 }
965 i += d;
966 }
967
968 /*
969 * Done!
970 */
971 isc_mem_put(mctx, ds, n * sizeof(dns_rdata_ds_t));
972 isc_mem_put(mctx, arrdata, n * sizeof(dns_rdata_t));
973
974 return (match);
975}
976
977static void
978print_diff(const char *cmd, dns_rdataset_t *rdataset) {
979 isc_buffer_t *buf;
980 isc_region_t r;
981 unsigned char *nl;
982 size_t len;
983
984 buf = formatset(rdataset);
985 isc_buffer_usedregion(buf, &r);
986
987 while ((nl = memchr(r.base, '\n', r.length)) != NULL) {
988 len = nl - r.base + 1;
989 printf("update %s %.*s", cmd, (int)len, (char *)r.base);
990 isc_region_consume(&r, len);
991 }
992
993 isc_buffer_free(&buf);
994}
995
996static void
cb6a185c 997update_diff(const char *cmd, uint32_t ttl,
ba37674d
EH
998 dns_rdataset_t *addset, dns_rdataset_t *delset)
999{
1000 isc_result_t result;
1001 dns_db_t *db;
1002 dns_dbnode_t *node;
1003 dns_dbversion_t *ver;
1004 dns_rdataset_t diffset;
cb6a185c 1005 uint32_t save;
ba37674d
EH
1006
1007 db = NULL;
1008 result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone,
1009 rdclass, 0, NULL, &db);
1010 check_result(result, "dns_db_create()");
1011
1012 ver = NULL;
1013 result = dns_db_newversion(db, &ver);
1014 check_result(result, "dns_db_newversion()");
1015
1016 node = NULL;
994e6569 1017 result = dns_db_findnode(db, name, true, &node);
ba37674d
EH
1018 check_result(result, "dns_db_findnode()");
1019
1020 dns_rdataset_init(&diffset);
1021
1022 result = dns_db_addrdataset(db, node, ver, 0, addset,
1023 DNS_DBADD_MERGE, NULL);
1024 check_result(result, "dns_db_addrdataset()");
1025
1026 result = dns_db_subtractrdataset(db, node, ver, delset,
1027 0, &diffset);
1028 if (result == DNS_R_UNCHANGED) {
1029 save = addset->ttl;
1030 addset->ttl = ttl;
1031 print_diff(cmd, addset);
1032 addset->ttl = save;
1033 } else if (result != DNS_R_NXRRSET) {
1034 check_result(result, "dns_db_subtractrdataset()");
1035 diffset.ttl = ttl;
1036 print_diff(cmd, &diffset);
1037 dns_rdataset_disassociate(&diffset);
1038 }
1039
1040 dns_db_detachnode(db, &node);
994e6569 1041 dns_db_closeversion(db, &ver, false);
ba37674d
EH
1042 dns_db_detach(&db);
1043}
1044
1045static void
cb6a185c 1046nsdiff(uint32_t ttl, dns_rdataset_t *oldset, dns_rdataset_t *newset) {
ba37674d
EH
1047 if (ttl == 0) {
1048 vbprintf(1, "warning: no TTL in nsupdate script\n");
1049 }
1050 update_diff("add", ttl, newset, oldset);
1051 update_diff("del", 0, oldset, newset);
1052 if (verbose > 0) {
1053 printf("show\nsend\nanswer\n");
1054 } else {
1055 printf("send\n");
1056 }
1057 if (fflush(stdout) == EOF) {
1058 fatal("write stdout: %s", strerror(errno));
1059 }
1060}
1061
1062ISC_PLATFORM_NORETURN_PRE static void
1063usage(void) ISC_PLATFORM_NORETURN_POST;
1064
1065static void
1066usage(void) {
1067 fprintf(stderr, "Usage:\n");
1068 fprintf(stderr,
1069 " %s options [options] -f <file> -d <path> <domain>\n",
1070 program);
1071 fprintf(stderr, "Version: %s\n", VERSION);
1072 fprintf(stderr, "Options:\n"
27593e65 1073" -a <algorithm> digest algorithm (SHA-1 / SHA-256 / SHA-384)\n"
ba37674d
EH
1074" -c <class> of domain (default IN)\n"
1075" -D prefer CDNSKEY records instead of CDS\n"
1076" -d <file|dir> where to find parent dsset- file\n"
1077" -f <file> child DNSKEY+CDNSKEY+CDS+RRSIG records\n"
1078" -i[extension] update dsset- file in place\n"
1079" -s <start-time> oldest permitted child signatures\n"
1080" -u emit nsupdate script\n"
1081" -T <ttl> TTL of DS records\n"
1082" -V print version\n"
1083" -v <verbosity>\n"
1084 );
1085 exit(1);
1086}
1087
1088int
1089main(int argc, char *argv[]) {
1090 const char *child_path = NULL;
1091 const char *ds_path = NULL;
1092 const char *inplace = NULL;
1093 isc_result_t result;
994e6569
OS
1094 bool prefer_cdnskey = false;
1095 bool nsupdate = false;
cb6a185c 1096 uint32_t ttl = 0;
ba37674d
EH
1097 int ch;
1098 char *endp;
1099
1100 result = isc_mem_create(0, 0, &mctx);
1101 if (result != ISC_R_SUCCESS) {
1102 fatal("out of memory");
1103 }
1104
c3b8130f 1105#if USE_PKCS11
ba37674d
EH
1106 pk11_result_register();
1107#endif
1108 dns_result_register();
1109
994e6569 1110 isc_commandline_errprint = false;
ba37674d
EH
1111
1112#define OPTIONS "a:c:Dd:f:i:ms:T:uv:V"
1113 while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) {
1114 switch (ch) {
1115 case 'a':
796a6c4e 1116 add_dtype(strtodsdigest(isc_commandline_argument));
ba37674d
EH
1117 break;
1118 case 'c':
1119 rdclass = strtoclass(isc_commandline_argument);
1120 break;
1121 case 'D':
994e6569 1122 prefer_cdnskey = true;
ba37674d
EH
1123 break;
1124 case 'd':
1125 ds_path = isc_commandline_argument;
1126 break;
1127 case 'f':
1128 child_path = isc_commandline_argument;
1129 break;
1130 case 'i':
1131 /*
1132 * This is a bodge to make the argument optional,
1133 * so that it works just like sed(1).
1134 */
1135 if (isc_commandline_argument ==
1136 argv[isc_commandline_index - 1])
1137 {
1138 isc_commandline_index--;
1139 inplace = "";
1140 } else {
1141 inplace = isc_commandline_argument;
1142 }
1143 break;
1144 case 'm':
1145 isc_mem_debugging = ISC_MEM_DEBUGTRACE |
1146 ISC_MEM_DEBUGRECORD;
1147 break;
1148 case 's':
1149 startstr = isc_commandline_argument;
1150 break;
1151 case 'T':
1152 ttl = strtottl(isc_commandline_argument);
1153 break;
1154 case 'u':
994e6569 1155 nsupdate = true;
ba37674d
EH
1156 break;
1157 case 'V':
1158 /* Does not return. */
1159 version(program);
1160 break;
1161 case 'v':
1162 verbose = strtoul(isc_commandline_argument, &endp, 0);
1163 if (*endp != '\0') {
1164 fatal("-v must be followed by a number");
1165 }
1166 break;
1167 default:
1168 usage();
1169 break;
1170 }
1171 }
1172 argv += isc_commandline_index;
1173 argc -= isc_commandline_index;
1174
1175 if (argc != 1) {
1176 usage();
1177 }
1178 initname(argv[0]);
1179
1180 /*
1181 * Default digest type if none specified.
1182 */
1183 if (dtype[0] == 0) {
1184 dtype[0] = DNS_DSDIGEST_SHA256;
1185 }
1186
1187 setup_logging(mctx, &lctx);
1188
3a4f820d 1189 result = dst_lib_init(mctx, NULL);
ba37674d
EH
1190 if (result != ISC_R_SUCCESS) {
1191 fatal("could not initialize dst: %s",
1192 isc_result_totext(result));
1193 }
ba37674d
EH
1194
1195 if (ds_path == NULL) {
1196 fatal("missing -d DS pathname");
1197 }
1198 load_parent_set(ds_path);
1199
1200 /*
1201 * Preserve the TTL if it wasn't overridden.
1202 */
1203 if (ttl == 0) {
1204 ttl = old_ds_set.ttl;
1205 }
1206
1207 if (child_path == NULL) {
1208 fatal("path to file containing child data must be specified");
1209 }
1210
1211 load_child_sets(child_path);
1212
1213 /*
1214 * Check child records have accompanying RRSIGs and DNSKEYs
1215 */
1216
1217 if (!dns_rdataset_isassociated(&dnskey_set) ||
1218 !dns_rdataset_isassociated(&dnskey_sig))
1219 {
1220 fatal("could not find signed DNSKEY RRset for %s", namestr);
1221 }
1222
1223 if (dns_rdataset_isassociated(&cdnskey_set) &&
1224 !dns_rdataset_isassociated(&cdnskey_sig))
1225 {
1226 fatal("missing RRSIG CDNSKEY records for %s", namestr);
1227 }
1228 if (dns_rdataset_isassociated(&cds_set) &&
1229 !dns_rdataset_isassociated(&cds_sig))
1230 {
1231 fatal("missing RRSIG CDS records for %s", namestr);
1232 }
1233
1234 vbprintf(1, "which child DNSKEY records match parent DS records?\n");
1235 old_key_tbl = match_keyset_dsset(&dnskey_set, &old_ds_set, LOOSE);
1236
1237 /*
1238 * We have now identified the keys that are allowed to authenticate
1239 * the DNSKEY RRset (RFC 4035 section 5.2 bullet 2), and CDNSKEY and
1240 * CDS RRsets (RFC 7344 section 4.1 bullet 2).
1241 */
1242
1243 vbprintf(1, "verify DNSKEY signature(s)\n");
1244 if (!signed_loose(matching_sigs(old_key_tbl, &dnskey_set, &dnskey_sig)))
1245 {
1246 fatal("could not validate child DNSKEY RRset for %s", namestr);
1247 }
1248
1249 if (dns_rdataset_isassociated(&cdnskey_set)) {
1250 vbprintf(1, "verify CDNSKEY signature(s)\n");
1251 if (!signed_loose(matching_sigs(old_key_tbl,
1252 &cdnskey_set, &cdnskey_sig)))
1253 {
1254 fatal("could not validate child CDNSKEY RRset for %s",
1255 namestr);
1256 }
1257 }
1258 if (dns_rdataset_isassociated(&cds_set)) {
1259 vbprintf(1, "verify CDS signature(s)\n");
1260 if (!signed_loose(matching_sigs(old_key_tbl,
1261 &cds_set, &cds_sig)))
1262 {
1263 fatal("could not validate child CDS RRset for %s",
1264 namestr);
1265 }
1266 }
1267
1268 free_keytable(&old_key_tbl);
1269
1270 /*
1271 * Report the result of the replay attack protection checks
1272 * used for the output file timestamp
1273 */
1274 if (oldestsig.timesigned != 0 && verbose > 0) {
1275 char type[32];
1276 dns_rdatatype_format(oldestsig.covered, type, sizeof(type));
1277 verbose_time(1, "child signature inception time",
1278 oldestsig.timesigned);
1279 vbprintf(2, "from RRSIG %s by key %d\n",
1280 type, oldestsig.keyid);
1281 }
1282
1283 /*
1284 * Sucessfully do nothing if there's neither CDNSKEY nor CDS
1285 * RFC 7344 section 4.1 first paragraph
1286 */
1287 if (!dns_rdataset_isassociated(&cdnskey_set) &&
1288 !dns_rdataset_isassociated(&cds_set))
1289 {
1290 vbprintf(1, "%s has neither CDS nor CDNSKEY records\n",
1291 namestr);
1292 write_parent_set(ds_path, inplace, nsupdate, &old_ds_set);
1293 exit(0);
1294 }
1295
1296 /*
1297 * Make DS records from the CDS or CDNSKEY records
1298 * Prefer CDS if present, unless run with -D
1299 */
1300 if (prefer_cdnskey && dns_rdataset_isassociated(&cdnskey_set)) {
1301 make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
1302 } else if (dns_rdataset_isassociated(&cds_set)) {
1303 make_new_ds_set(ds_from_cds, ttl, &cds_set);
1304 } else {
1305 make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
1306 }
1307
1308 /*
1309 * Now we have a candidate DS RRset, we need to check it
1310 * won't break the delegation.
1311 */
1312 vbprintf(1, "which child DNSKEY records match new DS records?\n");
1313 new_key_tbl = match_keyset_dsset(&dnskey_set, &new_ds_set, TIGHT);
1314
1315 if (!consistent_digests(&new_ds_set)) {
1316 fatal("CDS records at %s do not cover each key "
1317 "with the same set of digest types", namestr);
1318 }
1319
1320 vbprintf(1, "verify DNSKEY signature(s)\n");
1321 if (!signed_strict(&new_ds_set,
1322 matching_sigs(new_key_tbl,
1323 &dnskey_set, &dnskey_sig)))
1324 {
1325 fatal("could not validate child DNSKEY RRset "
1326 "with new DS records for %s", namestr);
1327 }
1328
1329 free_keytable(&new_key_tbl);
1330
1331 /*
1332 * OK, it's all good!
1333 */
1334 if (nsupdate) {
1335 nsdiff(ttl, &old_ds_set, &new_ds_set);
1336 }
1337
1338 write_parent_set(ds_path, inplace, nsupdate, &new_ds_set);
1339
1340 free_all_sets();
1341 cleanup_logging(&lctx);
1342 dst_lib_destroy();
ba37674d
EH
1343 if (verbose > 10) {
1344 isc_mem_stats(mctx, stdout);
1345 }
1346 isc_mem_destroy(&mctx);
1347
1348 exit(0);
1349}