From 6f48d8b947f9d305d34dcba227ede9e7851b15c5 Mon Sep 17 00:00:00 2001 From: Lucas Date: Fri, 10 Dec 2021 22:41:31 +0000 Subject: [PATCH] Initial import --- COPYING | 121 ++++++++++++++ Makefile | 58 +++++++ README | 6 + data/Kexample.+013+38127.ds | 1 + data/Kexample.+013+38127.key | 1 + data/Kexample.+013+38127.private | 3 + data/Kexample.+013+57357.ds | 1 + data/Kexample.+013+57357.key | 1 + data/Kexample.+013+57357.private | 3 + data/Kexample.+013+58312.key | 1 + data/Kexample.+013+58312.private | 3 + data/example.zone | 14 ++ ex-print-zone.c | 80 +++++++++ ldnssec-keygen.c | 198 ++++++++++++++++++++++ ldnssec-sign-dnskey.c | 272 +++++++++++++++++++++++++++++++ util.c | 66 ++++++++ util.h | 25 +++ 17 files changed, 854 insertions(+) create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README create mode 100644 data/Kexample.+013+38127.ds create mode 100644 data/Kexample.+013+38127.key create mode 100644 data/Kexample.+013+38127.private create mode 100644 data/Kexample.+013+57357.ds create mode 100644 data/Kexample.+013+57357.key create mode 100644 data/Kexample.+013+57357.private create mode 100644 data/Kexample.+013+58312.key create mode 100644 data/Kexample.+013+58312.private create mode 100644 data/example.zone create mode 100644 ex-print-zone.c create mode 100644 ldnssec-keygen.c create mode 100644 ldnssec-sign-dnskey.c create mode 100644 util.c create mode 100644 util.h diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/COPYING @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cc70563 --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +# ldnssec-utils +# +# Written in 2021 by Lucas +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any +# warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# . +.POSIX: +.SUFFIXES: +.SUFFIXES: .o .c + +P = ldnssec-utils +V = 0.0 + +LIBHDR = util.h +LIBOBJ = util.o + +BIN = ldnssec-keygen ldnssec-sign-dnskey +BINEX = ex-print-zone +OBJ = ${BIN:=.o} ${BINEX:=.o} ${LIBOBJ} +SRC = ${OBJ:.o=.c} +DIST = COPYING Makefile ${LIBHDR} ${SRC} + +LDNS_INCS = -I/usr/local/include +LDNS_LIBS = -L/usr/local/lib -lldns + +.o: + ${CC} ${LDFLAGS} -o $@ $@.o ${LIBOBJ} ${LDNS_LIBS} + +.c.o: + ${CC} ${CFLAGS} ${LDNS_INCS} -c $< + +all: ${BINEX} ${BIN} + +clean: + rm -f ${BIN} ${BINEX} ${OBJ} ${P}-${V}.tgz + +dist: clean + pax -ws ',^,${P}-${V}/,' ${DIST} | gzip >${P}-${V}.tgz + +install: all + mkdir -p ${PREFIX}/bin + cp -f ${BIN} ${PREFIX}/bin + cd ${PREFIX}/bin && chmod 555 ${BIN} + +uninstall: + cd ${PREFIX}/bin && rm -f ${BIN} + +util.o: util.h + +ldnssec-keygen: ldnssec-keygen.o ${LIBOBJ} +ldnssec-sign-dnskey: ldnssec-sign-dnskey.o ${LIBOBJ} +ex-print-zone: ex-print-zone.o ${LIBOBJ} diff --git a/README b/README new file mode 100644 index 0000000..249df91 --- /dev/null +++ b/README @@ -0,0 +1,6 @@ +ldnssec-utils +============= + +Lucas' DNSSEC utils, built around LDNS[0]. + +[0]: https://nlnetlabs.nl/projects/ldns/about/ diff --git a/data/Kexample.+013+38127.ds b/data/Kexample.+013+38127.ds new file mode 100644 index 0000000..16721a9 --- /dev/null +++ b/data/Kexample.+013+38127.ds @@ -0,0 +1 @@ +example. IN DS 38127 13 2 45e1fdf3338cedce4676be9014a6b46604e00688be95f7a8854f541930be65b4 diff --git a/data/Kexample.+013+38127.key b/data/Kexample.+013+38127.key new file mode 100644 index 0000000..ef44ef2 --- /dev/null +++ b/data/Kexample.+013+38127.key @@ -0,0 +1 @@ +example. IN DNSKEY 257 3 13 r84HU+lP18H53xF6ZsOUGCGmvon0Gfkr5BgIof4KBFQ7hluFqnf0gF45xEzxr/o+NxksGmOeOKRVzNEABP3mLw== ;{id = 38127 (ksk), size = 256b} diff --git a/data/Kexample.+013+38127.private b/data/Kexample.+013+38127.private new file mode 100644 index 0000000..0353ef1 --- /dev/null +++ b/data/Kexample.+013+38127.private @@ -0,0 +1,3 @@ +Private-key-format: v1.2 +Algorithm: 13 (ECDSAP256SHA256) +PrivateKey: lM2XFN/WiR0wWTrpC7oDAYbQoDV8DGcA/w/kHq9F5pw= diff --git a/data/Kexample.+013+57357.ds b/data/Kexample.+013+57357.ds new file mode 100644 index 0000000..30b86c6 --- /dev/null +++ b/data/Kexample.+013+57357.ds @@ -0,0 +1 @@ +example. IN DS 57357 13 2 d65b3fd9c6a5d30eb9a5ab83149216116de9ab9e1f1c15255b37d79a8d64b8a9 diff --git a/data/Kexample.+013+57357.key b/data/Kexample.+013+57357.key new file mode 100644 index 0000000..eda69be --- /dev/null +++ b/data/Kexample.+013+57357.key @@ -0,0 +1 @@ +example. IN DNSKEY 257 3 13 1egQYIooCJBCelV2nEvGGn+YnSQlb4Hv6vdahRgsnnFTRlOCDk8YHN01E86tT+Q3weB4wmjiSlvu6hRfPC4rWA== ;{id = 57357 (ksk), size = 256b} diff --git a/data/Kexample.+013+57357.private b/data/Kexample.+013+57357.private new file mode 100644 index 0000000..f2877ca --- /dev/null +++ b/data/Kexample.+013+57357.private @@ -0,0 +1,3 @@ +Private-key-format: v1.2 +Algorithm: 13 (ECDSAP256SHA256) +PrivateKey: GuF9VrZ71HDIlLIEyeGthrab8q1rlm4A5cMMPPMAQwk= diff --git a/data/Kexample.+013+58312.key b/data/Kexample.+013+58312.key new file mode 100644 index 0000000..5457a1f --- /dev/null +++ b/data/Kexample.+013+58312.key @@ -0,0 +1 @@ +example. IN DNSKEY 256 3 13 t5+0WPq6TFtVmrmvLH6EOYftEFPyz1/M+gRA8NGwlqKzoCSjQ66TuM73c5DohVql/KUqdqfiVm/Hw7sT12Ypew== ;{id = 58312 (zsk), size = 256b} diff --git a/data/Kexample.+013+58312.private b/data/Kexample.+013+58312.private new file mode 100644 index 0000000..e13ba46 --- /dev/null +++ b/data/Kexample.+013+58312.private @@ -0,0 +1,3 @@ +Private-key-format: v1.2 +Algorithm: 13 (ECDSAP256SHA256) +PrivateKey: ECpe5kwRfHNVeiTLl+VbFMnOiQqW65LgVNwv7N3qu/g= diff --git a/data/example.zone b/data/example.zone new file mode 100644 index 0000000..9ff53b5 --- /dev/null +++ b/data/example.zone @@ -0,0 +1,14 @@ +$ORIGIN example. +@ 86400 IN SOA ns1.example. hostmaster.example. 2021120701 2h 15m 1w 30m + 86400 IN NS ns1.example. + 86400 IN NS ns2.example. + 86400 IN MX 0 . + 86400 IN TXT "v=spf1 -all" + 86400 IN DNSKEY 257 3 13 1egQYIooCJBCelV2nEvGGn+YnSQlb4Hv6vdahRgsnnFTRlOCDk8YHN01E86tT+Q3weB4wmjiSlvu6hRfPC4rWA== ;{id = 57357 (ksk), size = 256b} + 86400 IN DNSKEY 257 3 13 r84HU+lP18H53xF6ZsOUGCGmvon0Gfkr5BgIof4KBFQ7hluFqnf0gF45xEzxr/o+NxksGmOeOKRVzNEABP3mLw== ;{id = 38127 (ksk), size = 256b} + 86400 IN DNSKEY 256 3 13 t5+0WPq6TFtVmrmvLH6EOYftEFPyz1/M+gRA8NGwlqKzoCSjQ66TuM73c5DohVql/KUqdqfiVm/Hw7sT12Ypew== ;{id = 58312 (zsk), size = 256b} + +ns1 86400 IN A 192.0.2.1 + 86400 IN AAAA 2001:db8::6af:4864:a096:eb76 +ns2 86400 IN A 198.51.100.1 + 86400 IN AAAA 2001:db8::5e7f:b183:4f08:217f diff --git a/ex-print-zone.c b/ex-print-zone.c new file mode 100644 index 0000000..bdd6480 --- /dev/null +++ b/ex-print-zone.c @@ -0,0 +1,80 @@ +/* + * ldnssec-utils + * + * Written in 2021 by Lucas + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to the + * public domain worldwide. This software is distributed without any + * warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication + * along with this software. If not, see + * . + */ + +#include +#include +#include +#include + +#include + +#include "util.h" + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-f zone]\n", getprogname()); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + ldns_zone *zone; + ldns_status s; + FILE *fp; + char *filename; + int ch, line_nr; + + fp = NULL; + filename = NULL; + while ((ch = getopt(argc, argv, "f:")) != -1) { + switch (ch) { + case 'f': + if (fp != NULL) + errx(1, "-f can be used only once"); + filename = optarg; + fp = fopen(filename, "r"); + if (fp == NULL) + err(1, "fopen"); + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (fp == NULL) { + filename = "(stdin)"; + fp = stdin; + } + + fatal_check_minimum_ldns_revision(); + + s = ldns_zone_new_frm_fp_l(&zone, fp, NULL, LDNS_DEFAULT_TTL, + LDNS_RR_CLASS_IN, &line_nr); + if (s != LDNS_STATUS_OK) + errx(1, "ldns_zone_new_frm_fp_l: file %s line %d: %s", + filename, line_nr, ldns_get_errorstr_by_id(s)); + if (fp != stdin) + (void)fclose(fp); + + ldns_zone_print(stdout, zone); + + ldns_zone_deep_free(zone); + + return 0; +} diff --git a/ldnssec-keygen.c b/ldnssec-keygen.c new file mode 100644 index 0000000..6b96dad --- /dev/null +++ b/ldnssec-keygen.c @@ -0,0 +1,198 @@ +/* + * ldnssec-utils + * + * Written in 2021 by Lucas + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to the + * public domain worldwide. This software is distributed without any + * warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication + * along with this software. If not, see + * . + */ + +#include +#include +#include +#include + +#include + +#include "util.h" + +static void +usage(void) +{ + const char *p = getprogname(); + + fprintf(stderr, "Usage:\n" + "\t%s [-b bits] algorithm\n" + "\t%s [-a algorithm] [-d domain] [-k] -r ds|dnskey\n", + p, p); + exit(1); +} + +static int +print_record_main(ldns_hash hash, int ksk, const char *domain, int argc, + char *argv[]) +{ + ldns_key *key; + ldns_rr *dnskey_rr, *ds_rr; + ldns_rdf *rdf_dname; + ldns_status s; + const char *type; + char *str; + uint16_t flags; + int line_nr, do_ds; + + if (argc != 1) + usage(); + type = argv[0]; + + do_ds = strcasecmp(type, "DS") == 0; + if (strcasecmp(type, "DNSKEY") != 0 && !do_ds) + errx(1, "unsupport RR type %s", type); + + s = ldns_key_new_frm_fp_l(&key, stdin, &line_nr); + if (s != LDNS_STATUS_OK) + errx(1, "ldns_key_new_frm_fp_l: (stdin) line %d: %s", + line_nr, ldns_get_errorstr_by_id(s)); + + s = ldns_str2rdf_dname(&rdf_dname, domain); + if (s != LDNS_STATUS_OK) + errx(1, "ldns_str2rdf_dname: %s", ldns_get_errorstr_by_id(s)); + + flags = LDNS_KEY_ZONE_KEY; + if (ksk) + flags |= LDNS_KEY_SEP_KEY; + + ldns_key_set_pubkey_owner(key, rdf_dname); + ldns_key_set_flags(key, flags); + + dnskey_rr = ldns_key2rr(key); + if (dnskey_rr == NULL) + errx(1, "ldns_key2rr"); + + ds_rr = NULL; + if (do_ds) { + ds_rr = ldns_key_rr2ds(dnskey_rr, hash); + if (ds_rr == NULL) + errx(1, "ldns_key_rr2ds"); + } + + str = ldns_rr2str(do_ds ? ds_rr : dnskey_rr); + if (str == NULL) + errx(1, "ldns_rr2str"); + fputs(str, stdout); + + free(str); + ldns_rr_free(dnskey_rr); + ldns_rr_free(ds_rr); + ldns_key_deep_free(key); + + return 1; +} + +static int +keygen_main(uint16_t bits, int argc, char *argv[]) +{ + ldns_key *key; + ldns_signing_algorithm alg; + char *str; + + if (argc != 1) + usage(); + + alg = ldns_get_signing_algorithm_by_name(argv[0]); + /* + * ldns_key_algo_supported can't be used here as it uses + * ldns_signing_algorithms lookup table, which includes algorithms for + * TSIG. + */ + if (ldns_lookup_by_id(ldns_algorithms, alg) == NULL) + errx(1, "unsupported algorithm %s", argv[0]); + + key = ldns_key_new_frm_algorithm(alg, bits); + if (key == NULL) + errx(1, "error generating key of type %s", argv[0]); + + /* ldns_key_print uses stdout to signal errors */ + str = ldns_key2str(key); + if (str == NULL) + errx(1, "ldns_key2str"); + fputs(str, stdout); + + ldns_key_deep_free(key); + + return 0; +} + +int +main(int argc, char *argv[]) +{ + const ldns_lookup_table *lt; + ldns_key *key; + ldns_signing_algorithm alg; + ldns_hash hash_alg; + const char *errstr, *domain; + long long n; + int ch, rc; + int aflag, bflag, dflag, kflag, rflag; + uint16_t bits; + + aflag = bflag = dflag = kflag = rflag = 0; + hash_alg = LDNS_SHA256; + bits = 0; + domain = "."; + while ((ch = getopt(argc, argv, "a:b:d:kr")) != -1) { + switch (ch) { + case 'a': + aflag = 1; + hash_alg = ldnssec_get_hash_algorithm_by_name(optarg); + if (!ldns_lookup_by_id(ldnssec_hashes, hash_alg)) + errx(1, "-a: unsupported algorithm: %s", + optarg); + break; + case 'b': + bflag = 1; + n = strtonum(optarg, 0, UINT16_MAX, &errstr); + if (errstr != NULL) + errx(1, "-b: bits is %s: %s", errstr, optarg); + bits = n; + break; + case 'd': + dflag = 1; + domain = optarg; + break; + case 'k': + kflag = 1; + break; + case 'r': + rflag = 1; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (bflag && (aflag || dflag || rflag || kflag)) + errx(1, "-b is mutually exclusive with -d, -k and -r"); + if (aflag && !rflag) + errx(1, "can't use -a without -r"); + if (dflag && !rflag) + errx(1, "can't use -d without -r"); + if (kflag && !rflag) + errx(1, "can't use -k without -r"); + + rc = 1; + if (rflag) + rc = print_record_main(hash_alg, kflag, domain, argc, argv); + else + rc = keygen_main(bits, argc, argv); + + return rc; +} diff --git a/ldnssec-sign-dnskey.c b/ldnssec-sign-dnskey.c new file mode 100644 index 0000000..ecff2ca --- /dev/null +++ b/ldnssec-sign-dnskey.c @@ -0,0 +1,272 @@ +/* + * ldnssec-utils + * + * Written in 2021 by Lucas + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to the + * public domain worldwide. This software is distributed without any + * warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication + * along with this software. If not, see + * . + */ + +#include +#include +#include +#include + +#include + +#include "util.h" + +#define DEFAULT_DURATION (30 * 86400) /* 30 days */ + +static int +extend_key_from_zone(ldns_key *k, const ldns_zone *z) +{ + ldns_rr_list *rrs; + ldns_rr *soa, *key_rr; + size_t i; + int match = 0; + + soa = ldns_zone_soa(z); + rrs = ldns_zone_rrs(z); + key_rr = ldns_key2rr(k); + if (key_rr == NULL) + errx(1, "ldns_key2rr"); + for (i = 0; i < ldns_rr_list_rr_count(rrs); i++) { + ldns_rr *rr; + ldns_rdf *owner_rdf; + + rr = ldns_rr_list_rr(rrs, i); + if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_DNSKEY || + ldns_rdf_compare(ldns_rr_dnskey_protocol(rr), + ldns_rr_dnskey_protocol(key_rr)) != 0 || + ldns_rdf_compare(ldns_rr_dnskey_algorithm(rr), + ldns_rr_dnskey_algorithm(key_rr)) != 0 || + ldns_rdf_compare(ldns_rr_dnskey_key(rr), + ldns_rr_dnskey_key(key_rr)) != 0) + continue; + + /* key and DNSKEY have the same pubkey and algorithm */ + owner_rdf = ldns_rdf_clone(ldns_rr_owner(rr)); + if (owner_rdf == NULL) + errx(1, "ldns_rdf_clone"); + ldns_key_set_pubkey_owner(k, owner_rdf); + ldns_key_set_origttl(k, ldns_rr_ttl(rr)); + ldns_key_set_keytag(k, ldns_calc_keytag(rr)); + ldns_key_set_flags(k, + ldns_rdf2native_int16(ldns_rr_dnskey_flags(rr))); + match = 1; + break; + } + ldns_rr_free(key_rr); + + return match; +} + +static int +parse_datetime(const char *buf, struct tm *tm) +{ + char *s; + time_t t; + + t = time(NULL); + if (t == (time_t)-1) + err(1, "time"); + if (gmtime_r(&t, tm) == NULL) + errx(1, "gmtime_r"); + + /* attempt to parse as YYYYMMDDhhmmss */ + s = strptime(buf, "%Y %m %d %H %M %S", tm); + if (s != NULL && *s == '\0') + return 1; + + /* failed; attemp to parse as YYYYMMDD */ + s = strptime(buf, "%Y %m %d", tm); + if (s != NULL && *s == '\0') + return 1; + + return 0; +} + +static void +usage(void) +{ + const char *p = getprogname(); + + fprintf(stderr, "Usage:\n" + "\t%s [-d duration] [-i inception] key [key ...]\n" + "\t%s [-e expiration] [-i inception] key [key ...]\n", + p, p); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + ldns_zone *zone = NULL; + ldns_dnssec_zone *dnssec_zone; + ldns_key_list *keys; + ldns_rr_list *added_rrs, *origin_rrs; + ldns_rr *origin_soa; + ldns_status s; + const char *errstr; + struct tm tm; + time_t t; + size_t i; + long long n; + uint32_t expiration, inception, duration; + int ch, line_nr; + int dflag, eflag, iflag; + + dflag = eflag = iflag = 0; + duration = DEFAULT_DURATION; + while ((ch = getopt(argc, argv, "d:e:i:")) != -1) { + switch (ch) { + case 'd': + dflag = 1; + n = strtonum(optarg, 0, UINT32_MAX, &errstr); + if (errstr != NULL) + errx(1, "-d: duration is %s: %s", errstr, + optarg); + duration = n; + break; + case 'e': + eflag = 1; + if (!parse_datetime(optarg, &tm)) + errx(1, "-e: not in YYYYMMDDhhmmss or " + "YYYYMMDD format"); + t = mktime(&tm); + if (t == (time_t)-1) + errx(1, "mktime"); + expiration = t & 0xffffffff; + break; + case 'i': + iflag = 1; + if (!parse_datetime(optarg, &tm)) + errx(1, "-i: not in YYYYMMDDhhmmss or " + "YYYYMMDD format"); + t = mktime(&tm); + if (t == (time_t)-1) + errx(1, "mktime"); + inception = t & 0xffffffff; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (dflag && eflag) + errx(1, "-d and -e are mutually exclusive"); + if (argc == 0) + usage(); + + if (!iflag) { + t = time(NULL); + if (t == (time_t)-1) + err(1, "time"); + inception = t & 0xffffffff; + } + if (!eflag) + expiration = inception + duration; + + fatal_check_minimum_ldns_revision(); + + s = ldns_zone_new_frm_fp_l(&zone, stdin, NULL, LDNS_DEFAULT_TTL, + LDNS_RR_CLASS_IN, &line_nr); + if (s != LDNS_STATUS_OK) + errx(1, "ldns_zone_new_frm_fp_l: (stdin) line %d: %s", + line_nr, ldns_get_errorstr_by_id(s)); + + origin_soa = ldns_zone_soa(zone); + if (origin_soa == NULL) + errx(1, "no SOA in zone"); + origin_rrs = ldns_zone_rrs(zone); + + dnssec_zone = ldns_dnssec_zone_new(); + if (dnssec_zone == NULL) + err(1, "ldns_dnssec_zone_new"); + s = ldns_dnssec_zone_add_rr(dnssec_zone, origin_soa); + if (s != LDNS_STATUS_OK) + errx(1, "ldns_dnssec_zone_add_rr: %s", + ldns_get_errorstr_by_id(s)); + for (i = 0; i < ldns_rr_list_rr_count(origin_rrs); i++) { + ldns_rr *rr; + + rr = ldns_rr_list_rr(origin_rrs, i); + if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_DNSKEY || + ldns_rdf_compare(ldns_rr_owner(rr), + ldns_rr_owner(origin_soa)) != 0) + continue; + + s = ldns_dnssec_zone_add_rr(dnssec_zone, rr); + if (s != LDNS_STATUS_OK) + errx(1, "ldns_dnssec_zone_add_rr: %s", + ldns_get_errorstr_by_id(s)); + } + + keys = ldns_key_list_new(); + if (keys == NULL) + err(1, "ldns_key_list_new"); + for (; *argv != NULL; argv++) { + ldns_key *k = NULL; + FILE *fp; + + fp = fopen(*argv, "r"); + if (fp == NULL) + err(1, "fopen: %s", *argv); + s = ldns_key_new_frm_fp_l(&k, fp, &line_nr); + (void)fclose(fp); + if (s != LDNS_STATUS_OK) + errx(1, "ldns_key_new_frm_fp_l: %s line %d: %s", + *argv, line_nr, ldns_get_errorstr_by_id(s)); + + if (!extend_key_from_zone(k, zone)) + errx(1, "no DNSKEY in zone for private key \"%s\"", + *argv); + ldns_key_set_expiration(k, expiration); + ldns_key_set_inception(k, inception); + + if (!ldns_key_list_push_key(keys, k)) + err(1, "ldns_key_list_push_key"); + } + + added_rrs = ldns_rr_list_new(); + if (added_rrs == NULL) + err(1, "ldns_rr_list_new"); + s = ldns_dnssec_zone_create_rrsigs_flg(dnssec_zone, added_rrs, keys, + ldns_dnssec_default_replace_signatures, NULL, 0); + if (s != LDNS_STATUS_OK) + errx(1, "ldns_dnssec_zone_create_rrsigs_flg: %s", + ldns_get_errorstr_by_id(s)); + + for (i = 0; i < ldns_rr_list_rr_count(added_rrs); i++) { + ldns_rr *rr; + char *str; + + rr = ldns_rr_list_rr(added_rrs, i); + if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_RRSIG || + ldns_rdf2native_int16(ldns_rr_rrsig_typecovered(rr)) != + LDNS_RR_TYPE_DNSKEY) + continue; + + str = ldns_rr2str(rr); + if (str == NULL) + errx(1, "ldns_rr2str"); + fputs(str, stdout); + free(str); + } + + ldns_key_list_free(keys); + ldns_dnssec_zone_free(dnssec_zone); + ldns_zone_deep_free(zone); + ldns_rr_list_deep_free(added_rrs); + + return 0; +} diff --git a/util.c b/util.c new file mode 100644 index 0000000..0b2a2f5 --- /dev/null +++ b/util.c @@ -0,0 +1,66 @@ +/* + * ldnssec-utils + * + * Written in 2021 by Lucas + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to the + * public domain worldwide. This software is distributed without any + * warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication + * along with this software. If not, see + * . + */ + +#include + +#include + +#include "util.h" + +#define MINIMUM_LDNS_VERSION "1.7.1" +#define MINIMUM_LDNS_REVISION ((1<<16)|(7<<8)|(1)) + +ldns_lookup_table ldnssec_hashes[] = { + { LDNS_SHA1, "SHA1" }, + { LDNS_SHA256, "SHA256" }, + { LDNS_HASH_GOST, "GOST" }, + { LDNS_SHA384, "SHA384" }, + { 0, NULL }, +}; + +ldns_hash +ldnssec_get_hash_algorithm_by_name(const char *name) +{ + static ldns_lookup_table aliases[] = { + { LDNS_SHA1, "SHA-1" }, + { LDNS_SHA256, "SHA-256" }, + { LDNS_SHA384, "SHA-384" }, + { 0, NULL }, + }; + ldns_lookup_table *lt; + const char *errstr; + long long n; + + lt = ldns_lookup_by_name(ldnssec_hashes, name); + if (lt != NULL) + return lt->id; + lt = ldns_lookup_by_name(aliases, name); + if (lt != NULL) + return lt->id; + + n = strtonum(name, 0, UINT8_MAX, &errstr); + if (errstr == NULL) + return n; + + return 0; +} + +void +fatal_check_minimum_ldns_revision(void) +{ + if (MINIMUM_LDNS_REVISION > LDNS_REVISION) + errx(1, "ldns version should be greater or equal than " + MINIMUM_LDNS_VERSION "; got %s instead\n", ldns_version()); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..2545ed1 --- /dev/null +++ b/util.h @@ -0,0 +1,25 @@ +/* + * ldnssec-utils + * + * Written in 2021 by Lucas + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to the + * public domain worldwide. This software is distributed without any + * warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication + * along with this software. If not, see + * . + */ + +#ifndef _UTIL_H_INCLUDED +#define _UTIL_H_INCLUDED + +extern ldns_lookup_table ldnssec_hashes[]; + +ldns_hash ldnssec_get_hash_algorithm_by_name(const char *); + +void fatal_check_minimum_ldns_revision(void); + +#endif /* !_UTIL_H_INCLUDED */