/* * 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; }