273 lines
6.4 KiB
C
273 lines
6.4 KiB
C
/*
|
|
* 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
|
|
* <http://creativecommons.org/publicdomain/zero/1.0/>.
|
|
*/
|
|
|
|
#include <err.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include <ldns/ldns.h>
|
|
|
|
#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;
|
|
}
|