diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c index 3bff266b07..78cb8bc288 100644 --- a/bin/dnssec/dnssec-signzone.c +++ b/bin/dnssec/dnssec-signzone.c @@ -1,10 +1,10 @@ /* * Copyright (C) 1999 Internet Software Consortium. - * + * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. - * + * * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE @@ -20,6 +20,7 @@ #include #include #include +#include /* for getopt */ #include #include @@ -28,6 +29,7 @@ #include #include #include +#include #include #include @@ -43,13 +45,32 @@ #include #include #include +#include #include -#define MAXKEYS 10 +#define BUFSIZE 2048 #define is_zone_key(key) ((dst_key_flags(key) & DNS_KEYFLAG_OWNERMASK) \ == DNS_KEYOWNER_ZONE) +typedef struct signer_key_struct signer_key_t; +typedef struct signer_array_struct signer_array_t; + +struct signer_key_struct { + dst_key_t *key; + isc_boolean_t isdefault; + ISC_LINK(signer_key_t) link; +}; + +struct signer_array_struct { + unsigned char array[BUFSIZE]; + ISC_LINK(signer_array_t) link; +}; + +ISC_LIST(signer_key_t) keylist; +isc_stdtime_t start = 0, end = 0, now; +int cycle = -1; + static isc_mem_t *mctx = NULL; static inline void @@ -70,7 +91,7 @@ check_result(isc_result_t result, char *message) { static void set_bit(unsigned char *array, unsigned int index, unsigned int bit) { unsigned int byte, shift, mask; - + byte = array[index / 8]; shift = 7 - (index % 8); mask = 1 << shift; @@ -81,152 +102,331 @@ set_bit(unsigned char *array, unsigned int index, unsigned int bit) { array[index / 8] &= (~mask & 0xFF); } -dns_result_t -find_apex_keys(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, - dns_name_t *name, isc_mem_t *mctx, unsigned int maxkeys, - dst_key_t **keys, unsigned int *nkeys) +static void +signwithkey(dns_name_t *name, dns_rdataset_t *rdataset, dns_rdata_t *rdata, + dst_key_t *key, isc_buffer_t *b) { - dns_rdataset_t rdataset; - dns_rdata_t rdata; isc_result_t result; - dst_key_t *pubkey; - unsigned int count = 0; - *nkeys = 0; - dns_rdataset_init(&rdataset); - result = dns_db_findrdataset(db, node, ver, dns_rdatatype_key, 0, 0, - &rdataset, NULL); - check_result(result, "dns_db_findrdataset()"); - result = dns_rdataset_first(&rdataset); - check_result(result, "dns_rdataset_first()"); - while (result == ISC_R_SUCCESS && count < maxkeys) { - pubkey = NULL; - dns_rdataset_current(&rdataset, &rdata); - result = dns_dnssec_keyfromrdata(name, &rdata, mctx, &pubkey); - check_result(result, "dns_dnssec_keyfromrdata()"); - result = dst_key_fromfile(dst_key_name(pubkey), - dst_key_id(pubkey), - dst_key_alg(pubkey), - DST_TYPE_PRIVATE, - mctx, &keys[count++]); - check_result(result, "dst_key_fromfile()"); + dns_rdata_init(rdata); + result = dns_dnssec_sign(name, rdataset, key, &start, &end, + mctx, b, rdata); + check_result(result, "dns_dnssec_sign()"); +#if 0 + /* Verify the data. This won't work if the start time is reset */ + result = dns_dnssec_verify(name, rdataset, key, mctx, rdata); + check_result(result, "dns_dnssec_verify()"); +#endif +} + +static inline isc_boolean_t +issigningkey(signer_key_t *key) { + return (key->isdefault); +} + +static inline isc_boolean_t +iszonekey(signer_key_t *key, dns_db_t *db) { + char origin[1024]; + isc_buffer_t b; + isc_result_t result; + + isc_buffer_init(&b, origin, sizeof(origin), ISC_BUFFERTYPE_BINARY); + result = dns_name_totext(dns_db_origin(db), ISC_FALSE, &b); + check_result(result, "dns_name_totext()"); + + return (strcasecmp(dst_key_name(key->key), origin) == 0 && + (dst_key_flags(key->key) & DNS_KEYFLAG_OWNERMASK) == + DNS_KEYOWNER_ZONE); +} + +static signer_key_t * +keythatsigned(dns_rdata_generic_sig_t *sig) { + char keyname[1024]; + isc_buffer_t b; + isc_result_t result; + dst_key_t *pubkey = NULL, *privkey = NULL; + signer_key_t *key; + + isc_buffer_init(&b, keyname, sizeof(keyname), ISC_BUFFERTYPE_BINARY); + result = dns_name_totext(sig->signer, ISC_FALSE, &b); + check_result(result, "dns_name_totext()"); + + key = ISC_LIST_HEAD(keylist); + while (key != NULL) { + if (sig->keyid == dst_key_id(key->key) && + sig->algorithm == dst_key_alg(key->key) && + strcasecmp(keyname, dst_key_name(key->key)) == 0) + return key; + } + + result = dst_key_fromfile(keyname, sig->keyid, sig->algorithm, + DST_TYPE_PUBLIC, mctx, &pubkey); + if (result != ISC_R_SUCCESS) + return (NULL); + + key = isc_mem_get(mctx, sizeof(signer_key_t)); + if (key == NULL) + check_result(ISC_R_FAILURE, "isc_mem_get"); + + result = dst_key_fromfile(keyname, sig->keyid, sig->algorithm, + DST_TYPE_PRIVATE, mctx, &pubkey); + if (result == ISC_R_SUCCESS) { + key->key = privkey; dst_key_free(pubkey); - pubkey = NULL; + } + else + key->key = pubkey; + key->isdefault = ISC_FALSE; + ISC_LIST_APPEND(keylist, key, link); + return key; +} + +static isc_boolean_t +expecttofindkey(dns_name_t *name, dns_db_t *db, dns_dbversion_t *version) { + unsigned int options = DNS_DBFIND_NOWILD; + isc_result_t result; + + result = dns_db_find(db, name, version, dns_rdatatype_key, options, + 0, NULL, NULL, NULL, NULL); + switch (result) { + case DNS_R_SUCCESS: + case DNS_R_NXDOMAIN: + case DNS_R_NXRDATASET: + return ISC_TRUE; + case DNS_R_DELEGATION: + case DNS_R_CNAME: + case DNS_R_DNAME: + return ISC_FALSE; + default: + check_result(result, "dns_db_find"); + return ISC_FALSE; /* removess a warning */ + } +} + +static isc_boolean_t +setverifies(dns_name_t *name, dns_rdataset_t *set, signer_key_t *key, + dns_rdata_t *sig) +{ + isc_result_t result = dns_dnssec_verify(name, set, key->key, mctx, sig); + return (result == ISC_R_SUCCESS); +} + +#define allocbufferandrdata \ + isc_buffer_t b; \ + trdata = isc_mem_get(mctx, sizeof(dns_rdata_t)); \ + tdata = isc_mem_get(mctx, sizeof(signer_array_t)); \ + ISC_LIST_APPEND(arraylist, tdata, link); \ + if (trdata == NULL || tdata == NULL) \ + check_result(ISC_R_FAILURE, "isc_mem_get"); \ + isc_buffer_init(&b, tdata->array, sizeof(tdata->array), \ + ISC_BUFFERTYPE_BINARY); + +static void +signset(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, + dns_name_t *name, dns_rdataset_t *set) +{ + dns_rdatalist_t siglist; + dns_rdataset_t sigset, oldsigset; + dns_rdata_t oldsigrdata; + dns_rdata_t *trdata; + dns_rdata_generic_sig_t sig; + signer_key_t *key; + isc_result_t result; + isc_boolean_t notsigned = ISC_TRUE; + isc_boolean_t wassignedby[256], nowsignedby[256]; + signer_array_t *tdata; + ISC_LIST(signer_array_t) arraylist; + int i; + + ISC_LIST_INIT(siglist.rdata); + ISC_LIST_INIT(arraylist); + + for (i = 0; i < 256; i++) + wassignedby[i] = nowsignedby[i] = ISC_FALSE; + + dns_rdataset_init(&oldsigset); + result = dns_db_findrdataset(db, node, version, dns_rdatatype_sig, + set->type, 0, &oldsigset, NULL); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) + check_result(result, "dns_db_findrdataset()"); + + if (result != ISC_R_NOTFOUND) { + result = dns_rdataset_first(&oldsigset); + while (result == ISC_R_SUCCESS) { + isc_boolean_t expired; + isc_boolean_t keep = ISC_FALSE, resign = ISC_FALSE; + + dns_rdataset_current(&oldsigset, &oldsigrdata); + + result = dns_rdata_tostruct(&oldsigrdata, &sig, mctx); + check_result(result, "dns_rdata_tostruct"); + + expired = (now + cycle > sig.timeexpire); + + key = keythatsigned(&sig); + + if (key == NULL && + expecttofindkey(sig.signer, db, version)) + ; /* sig is dropped and not replaced */ + else if (key == NULL) + keep = ISC_TRUE; + else if (issigningkey(key)) { + if (!expired && + setverifies(name, set, key, &oldsigrdata)) + { + keep = ISC_TRUE; + wassignedby[sig.algorithm] = ISC_TRUE; + } + else { + wassignedby[sig.algorithm] = ISC_TRUE; + resign = ISC_TRUE; + } + } + else if (iszonekey(key, db)) { + if (!expired && + setverifies(name, set, key, &oldsigrdata)) + { + keep = ISC_TRUE; + wassignedby[sig.algorithm] = ISC_TRUE; + nowsignedby[sig.algorithm] = ISC_TRUE; + } + else { + wassignedby[sig.algorithm] = ISC_TRUE; + if (dst_key_isprivate(key->key)) + resign = ISC_TRUE; + } + } + else if (!expired) + keep = ISC_TRUE; + /* else sig has expired and we cannot regenerate it */ + + if (keep) { + allocbufferandrdata; + result = dns_rdata_fromstruct(trdata, + set->rdclass, + dns_rdatatype_sig, + &sig, &b); + nowsignedby[sig.algorithm] = ISC_TRUE; + ISC_LIST_APPEND(siglist.rdata, trdata, link); + } + else if (resign) { + allocbufferandrdata; + signwithkey(name, set, trdata, key->key, &b); + nowsignedby[sig.algorithm] = ISC_TRUE; + ISC_LIST_APPEND(siglist.rdata, trdata, link); + } + + dns_rdata_freestruct(&sig); + result = dns_rdataset_next(&oldsigset); + } + if (result == DNS_R_NOMORE) + result = ISC_R_SUCCESS; + check_result(result, "dns_db_dns_rdataset_first()/next()"); + dns_rdataset_disassociate(&oldsigset); + } + + for (i = 0; i < 256; i++) + if (wassignedby[i] != 0) { + notsigned = ISC_FALSE; + break; + } + + key = ISC_LIST_HEAD(keylist); + while (key != NULL) { + int alg = dst_key_alg(key->key); + if (key->isdefault && + (notsigned || (wassignedby[alg] && !nowsignedby[alg]))) + { + allocbufferandrdata; + signwithkey(name, set, trdata, key->key, &b); + ISC_LIST_APPEND(siglist.rdata, trdata, link); + } + key = ISC_LIST_NEXT(key, link); + } + + siglist.rdclass = set->rdclass; + siglist.type = dns_rdatatype_sig; + siglist.covers = set->type; + if (end - start < set->ttl) + siglist.ttl = end - start; + else + siglist.ttl = set->ttl; + dns_rdataset_init(&sigset); + result = dns_rdatalist_tordataset(&siglist, &sigset); + check_result(result, "dns_rdatalist_tordataset"); + result = dns_db_addrdataset(db, node, version, 0, &sigset, + ISC_FALSE, NULL); + if (result == DNS_R_UNCHANGED) + result = ISC_R_SUCCESS; + check_result(result, "dns_db_addrdataset"); + dns_rdataset_disassociate(&sigset); + + trdata = ISC_LIST_HEAD(siglist.rdata); + while (trdata != NULL) { + dns_rdata_t *next = ISC_LIST_NEXT(trdata, link); + isc_mem_put(mctx, trdata, sizeof(dns_rdata_t)); + trdata = next; + } + + tdata = ISC_LIST_HEAD(arraylist); + while (tdata != NULL) { + signer_array_t *next = ISC_LIST_NEXT(tdata, link); + isc_mem_put(mctx, tdata, sizeof(signer_array_t)); + tdata = next; + } +} + +static isc_boolean_t +hasnullkey(dns_rdataset_t rdataset) { + isc_result_t result; + dns_rdata_t rdata; + isc_uint32_t flags; + + result = dns_rdataset_first(&rdataset); + while (result == ISC_R_SUCCESS) { + dst_key_t *key = NULL; + + dns_rdataset_current(&rdataset, &rdata); + result = dns_dnssec_keyfromrdata(dns_rootname, + &rdata, mctx, &key); + check_result(result, "dns_dnssec_keyfromrdata()"); + flags = dst_key_flags(key); + dst_key_free(key); + if (((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) && + ((flags & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE)) + return (ISC_TRUE); result = dns_rdataset_next(&rdataset); } if (result != DNS_R_NOMORE) - check_result(result, "iteration over zone keys"); - result = DNS_R_SUCCESS; - if (count == 0) - check_result(ISC_R_FAILURE, "no key found"); - -/* failure:*/ - if (dns_rdataset_isassociated(&rdataset)) - dns_rdataset_disassociate(&rdataset); - if (pubkey != NULL) - dst_key_free(pubkey); - *nkeys = count; - return (result); -} - - -static void -sign_with_key(dns_name_t *name, dns_rdataset_t *rdataset, dns_rdata_t *rdata, - dns_rdatalist_t *sigrdatalist, isc_stdtime_t *now, - isc_stdtime_t *later, dst_key_t *key, - unsigned char *array, int len) -{ - isc_buffer_t b; - isc_region_t r; - isc_result_t result; - - r.base = array; - r.length = len; - memset(r.base, 0, r.length); - - dns_rdata_init(rdata); - isc_buffer_init(&b, r.base, r.length, ISC_BUFFERTYPE_BINARY); - result = dns_dnssec_sign(name, rdataset, key, now, later, - mctx, &b, rdata); - check_result(result, "dns_dnssec_sign()"); - result = dns_dnssec_verify(name, rdataset, key, mctx, rdata); - check_result(result, "dns_dnssec_verify()"); - ISC_LIST_APPEND(sigrdatalist->rdata, rdata, link); -} - -void -resign_set(dns_name_t *name, dns_name_t *origin, dns_rdataset_t *rdataset, - dns_rdata_t *rdata, dns_rdatalist_t *sigrdatalist, - dns_rdata_t *oldsigrdata, isc_stdtime_t *now, isc_stdtime_t *later, - dst_key_t **keys, int nkeys, unsigned char *array, int len) -{ - dns_rdata_generic_sig_t sig; - isc_result_t result; - isc_buffer_t b; - int i; - isc_boolean_t done = ISC_FALSE; - isc_boolean_t foundnonzone = ISC_FALSE; - - result = dns_rdata_tostruct(oldsigrdata, &sig, mctx); - check_result(result, "dns_rdata_tostruct()"); - - /* - * Is this a real signture that we should regenerate? - */ - if (dns_name_compare(sig.signer, origin) == 0) { - for (i = 0; i < nkeys; i++) { - dst_key_t *key = keys[i]; - if (dst_key_id(key) == sig.keyid && - dst_key_alg(key) == sig.algorithm) - { - if (!is_zone_key(key)) - foundnonzone = ISC_TRUE; - else - break; - } - } - if (i < nkeys) { - sign_with_key(name, rdataset, rdata, sigrdatalist, - now, later, keys[i], array, len); - done = ISC_TRUE; - } - } - if (!done) { - if (dns_name_compare(sig.signer, origin) != 0 || foundnonzone) { - printf("saving old sig...\n"); - isc_buffer_init(&b, array, len, ISC_BUFFERTYPE_BINARY); - result = dns_rdata_fromstruct(rdata, rdataset->rdclass, - dns_rdatatype_sig, - &sig, &b); - ISC_LIST_APPEND(sigrdatalist->rdata, rdata, link); - check_result(result, "dns_rdata_fromstruct()"); - } - else - printf("couldn't find key /%d, dropping sig\n", - sig.keyid); - } - - dns_rdata_freestruct(&sig); + check_result(result, "iteration over keys"); + return (ISC_FALSE); } static void -generate_sig(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, - dns_name_t *name, dst_key_t **keys, isc_boolean_t *defaultkey, - int nkeys) +signname(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, + dns_name_t *name) { isc_result_t result; dns_name_t *origin; - dns_rdata_t rdata, rdatas[MAXKEYS]; - dns_rdataset_t rdataset, sigrdataset, oldsigset; - dns_rdatalist_t sigrdatalist; + dns_rdata_t rdata; + dns_rdataset_t rdataset, nsset; dns_rdatasetiter_t *rdsiter; - isc_stdtime_t now, later; - unsigned char array[MAXKEYS][1024]; - int i; - isc_boolean_t alreadysigned; + isc_boolean_t atorigin = ISC_FALSE, isdelegation = ISC_FALSE; origin = dns_db_origin(db); + atorigin = (dns_name_compare(name, origin) == 0); + if (!atorigin) { + dns_rdataset_init(&nsset); + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_ns, 0, 0, &nsset, + NULL); + /* Is this a delegation point? */ + if (result == ISC_R_SUCCESS) { + isdelegation = ISC_TRUE; + dns_rdataset_disassociate(&nsset); + } + } dns_rdataset_init(&rdataset); rdsiter = NULL; result = dns_db_allrdatasets(db, node, version, 0, &rdsiter); @@ -235,36 +435,38 @@ generate_sig(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, while (result == ISC_R_SUCCESS) { dns_rdatasetiter_current(rdsiter, &rdataset); - if (rdataset.type == dns_rdatatype_sig || - (rdataset.type == dns_rdatatype_key && - dns_name_compare(name, origin) == 0)) - { - dns_rdataset_disassociate(&rdataset); - result = dns_rdatasetiter_next(rdsiter); - continue; - } + /* If this is a SIG set, skip it. */ + if (rdataset.type == dns_rdatatype_sig) + goto skip; - dns_rdataset_init(&oldsigset); - result = dns_db_findrdataset(db, node, version, - dns_rdatatype_sig, rdataset.type, - 0, &oldsigset, NULL); - if (result == ISC_R_SUCCESS) - alreadysigned = ISC_TRUE; - else if (result == ISC_R_NOTFOUND) { - alreadysigned = ISC_FALSE; - result = ISC_R_SUCCESS; - } - else - alreadysigned = ISC_FALSE; /* not that this matters */ - check_result(result, "dns_db_findrdataset()"); + /* If this is a KEY set at the apex, skip it. */ + if (rdataset.type == dns_rdatatype_key && atorigin) + goto skip; /* - * There probably should be a dns_nxtsetbit or something, - * but it can get complicated if we need to extend the - * length. In this case, since the NXT bit is set and + * If this name is a delegation point, skip all records + * except a KEY set containing a NULL key or an NXT set. + */ + if (isdelegation) { + switch (rdataset.type) { + case dns_rdatatype_nxt: + break; + case dns_rdatatype_key: + if (hasnullkey(rdataset)) + break; + goto skip; + default: + goto skip; + } + } + + /* + * There probably should be a dns_nxtsetbit, but it can get + * complicated if we need to extend the length of the + * bit set. In this case, since the NXT bit is set and * SIG < NXT, the easy way works. */ - if (rdataset.type == dns_rdatatype_nxt && !alreadysigned) { + if (rdataset.type == dns_rdatatype_nxt) { unsigned char *nxt_bits; dns_name_t nxtname; isc_region_t r, r2; @@ -278,55 +480,11 @@ generate_sig(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, dns_name_toregion(&nxtname, &r2); nxt_bits = r.base + r2.length; set_bit(nxt_bits, dns_rdatatype_sig, 1); - } - isc_stdtime_get(&now); - later = 100000 + now; - ISC_LIST_INIT(sigrdatalist.rdata); - - if (!alreadysigned) { - for (i = 0; i < nkeys; i++) { - if (!defaultkey[i] || !is_zone_key(keys[i])) - continue; - sign_with_key(name, &rdataset, &rdatas[i], - &sigrdatalist, &now, &later, - keys[i], array[i], - sizeof(array[i])); - } - } - else { - dns_rdata_t sigrdata; - - dns_rdata_init(&sigrdata); - result = dns_rdataset_first(&oldsigset); - i = 0; - while (result == ISC_R_SUCCESS) { - dns_rdataset_current(&oldsigset, &sigrdata); - resign_set(name, origin, &rdataset, &rdatas[i], - &sigrdatalist, &sigrdata, - &now, &later, keys, nkeys, array[i], - sizeof(array[i])); - i++; - result = dns_rdataset_next(&oldsigset); - } - dns_rdataset_disassociate(&oldsigset); - } - - sigrdatalist.rdclass = rdataset.rdclass; - sigrdatalist.type = dns_rdatatype_sig; - sigrdatalist.covers = rdataset.type; - sigrdatalist.ttl = rdataset.ttl; - dns_rdataset_init(&sigrdataset); - result = dns_rdatalist_tordataset(&sigrdatalist, &sigrdataset); - check_result(result, "dns_rdatalist_tordataset"); - result = dns_db_addrdataset(db, node, version, 0, &sigrdataset, - ISC_FALSE, NULL); - if (result == DNS_R_UNCHANGED) - result = ISC_R_SUCCESS; - check_result(result, "dns_db_addrdataset"); - dns_rdataset_disassociate(&sigrdataset); + signset(db, version, node, name, &rdataset); + skip: dns_rdataset_disassociate(&rdataset); result = dns_rdatasetiter_next(rdsiter); } @@ -398,67 +556,27 @@ next_active(dns_db_t *db, dns_dbversion_t *version, dns_dbiterator_t *dbiter, } static void -sign(char *filename) { +signzone(dns_db_t *db, dns_dbversion_t *version) { isc_result_t result, nxtresult; - dns_db_t *db; - dns_dbversion_t *wversion; dns_dbnode_t *node, *nextnode, *curnode; - char *origintext; dns_fixedname_t fname, fnextname; dns_name_t *name, *nextname, *target, curname; - isc_buffer_t b; - size_t len; dns_dbiterator_t *dbiter; - char newfilename[1024]; - dst_key_t *keys[MAXKEYS]; - isc_boolean_t defaultkey[MAXKEYS]; unsigned char curdata[1024]; isc_buffer_t curbuf; - unsigned int nkeys = 0; - unsigned int i; dns_fixedname_init(&fname); name = dns_fixedname_name(&fname); dns_fixedname_init(&fnextname); nextname = dns_fixedname_name(&fnextname); - origintext = strrchr(filename, '/'); - if (origintext == NULL) - origintext = filename; - else - origintext++; /* Skip '/'. */ - len = strlen(origintext); - isc_buffer_init(&b, origintext, len, ISC_BUFFERTYPE_TEXT); - isc_buffer_add(&b, len); - result = dns_name_fromtext(name, &b, dns_rootname, ISC_FALSE, NULL); - check_result(result, "dns_name_fromtext()"); - db = NULL; - result = dns_db_create(mctx, "rbt", name, ISC_FALSE, - dns_rdataclass_in, 0, NULL, &db); - check_result(result, "dns_db_create()"); - result = dns_db_load(db, filename); - check_result(result, "dns_db_load()"); - - node = NULL; - result = dns_db_findnode(db, name, ISC_FALSE, &node); - check_result(result, "dns_db_findnode()"); - result = find_apex_keys(db, NULL, node, name, mctx, MAXKEYS, - keys, &nkeys); - check_result(result, "dns_dnssec_findzonekeys()"); - dns_db_detachnode(db, &node); - for (i = 0; i < nkeys; i++) - defaultkey[i] = ISC_TRUE; - - wversion = NULL; - result = dns_db_newversion(db, &wversion); - check_result(result, "dns_db_newversion()"); - dbiter = NULL; result = dns_db_createiterator(db, ISC_FALSE, &dbiter); check_result(result, "dns_db_createiterator()"); result = dns_dbiterator_first(dbiter); node = NULL; - result = next_active(db, wversion, dbiter, name, &node); + dns_name_clone(dns_db_origin(db), name); + result = next_active(db, version, dbiter, name, &node); while (result == ISC_R_SUCCESS) { nextnode = NULL; curnode = NULL; @@ -469,7 +587,7 @@ sign(char *filename) { dns_dbiterator_current(dbiter, &curnode, &curname); result = dns_dbiterator_next(dbiter); if (result == ISC_R_SUCCESS) - result = next_active(db, wversion, dbiter, nextname, + result = next_active(db, version, dbiter, nextname, &nextnode); if (result == ISC_R_SUCCESS) target = nextname; @@ -479,10 +597,9 @@ sign(char *filename) { target = NULL; /* Make compiler happy. */ fatal("db iteration failed"); } - nxtresult = dns_buildnxt(db, wversion, node, target); - check_result(nxtresult, "dns_buildnxt()"); - generate_sig(db, wversion, node, &curname, keys, defaultkey, - nkeys); + nxtresult = dns_buildnxt(db, version, node, target); + check_result(nxtresult, "dns_buildnxt()"); + signname(db, version, node, &curname); dns_name_invalidate(&curname); dns_db_detachnode(db, &node); dns_db_detachnode(db, &curnode); @@ -491,36 +608,337 @@ sign(char *filename) { if (result != DNS_R_NOMORE) fatal("db iteration failed"); dns_dbiterator_destroy(&dbiter); - /* - * XXXRTH For now, we don't increment the SOA serial. - */ - dns_db_closeversion(db, &wversion, ISC_TRUE); - len = strlen(filename); - if (len + 4 + 1 > sizeof newfilename) - fatal("filename too long"); - sprintf(newfilename, "%s.new", filename); - result = dns_db_dump(db, NULL, newfilename); - check_result(result, "dns_db_dump"); - dns_db_detach(&db); - for (i = 0; i < nkeys; i++) - dst_key_free(keys[i]); +} + +static void +loadzone(char *file, char *origin, dns_db_t **db, dns_dbversion_t **version) { + isc_buffer_t b, b2; + unsigned char namedata[1024]; + int len; + dns_name_t name; + isc_result_t result; + + len = strlen(origin); + isc_buffer_init(&b, origin, len, ISC_BUFFERTYPE_TEXT); + isc_buffer_add(&b, len); + + isc_buffer_init(&b2, namedata, sizeof(namedata), ISC_BUFFERTYPE_BINARY); + + dns_name_init(&name, NULL); + result = dns_name_fromtext(&name, &b, dns_rootname, ISC_FALSE, &b2); + check_result(result, "dns_name_fromtext()"); + + result = dns_db_create(mctx, "rbt", &name, ISC_FALSE, + dns_rdataclass_in, 0, NULL, db); + check_result(result, "dns_db_create()"); + + result = dns_db_load(*db, file); + check_result(result, "dns_db_load()"); + + result = dns_db_newversion(*db, version); + check_result(result, "dns_db_newversion()"); +} + +static void +loadzonekeys(dns_db_t *db, dns_dbversion_t *version) { + dns_name_t *origin; + dns_dbnode_t *node; + isc_result_t result; + dst_key_t *keys[20]; + int nkeys, i; + + origin = dns_db_origin(db); + + node = NULL; + result = dns_db_findnode(db, origin, ISC_FALSE, &node); + check_result(result, "dns_db_findnode()"); + + result = dns_dnssec_findzonekeys(db, version, node, origin, mctx, + 20, keys, &nkeys); + check_result(result, "dns_dnssec_findzonekeys()"); + + for (i = 0; i < nkeys; i++) { + signer_key_t *key; + + key = isc_mem_get(mctx, sizeof(signer_key_t)); + if (key == NULL) + check_result(ISC_R_FAILURE, "isc_mem_get(key)"); + key->key = keys[i]; + key->isdefault = ISC_FALSE; + + ISC_LIST_APPEND(keylist, key, link); + } + dns_db_detachnode(db, &node); +} + + +static isc_stdtime_t +strtotime(char *str, isc_int64_t now, isc_int64_t base) { + isc_int64_t val, offset; + isc_result_t result; + char *endp = ""; + + if (str[0] == '+') { + offset = strtol(str + 1, &endp, 0); + val = base + offset; + } + else if (strncmp(str, "now+", 4) == 0) { + offset = strtol(str + 4, &endp, 0); + val = now + offset; + } + else { + result = dns_time64_fromtext(str, &val); + check_result(result, "dns_time64_fromtext()"); + } + if (*endp != '\0') + check_result(ISC_R_FAILURE, "strtol()"); + + return ((isc_stdtime_t) val); +} + +static void +usage() { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, "\tsigner [options] zonefile [keys]\n"); + + fprintf(stderr, "\n"); + + fprintf(stderr, "Options:\n"); + fprintf(stderr, "\t-s YYYYMMDDHHMMSS|+ttl:\n"); + fprintf(stderr, "\t\tSIG start time - absolute|offset (now)\n"); + fprintf(stderr, "\t-e YYYYMMDDHHMMSS|+ttl|now+ttl]:\n"); + fprintf(stderr, "\t\tSIG end time - absolute|from start|from now (now + 30 days)\n"); + fprintf(stderr, "\t-c ttl:\n"); + fprintf(stderr, "\t\tcycle period - regenerate if < cycle from end ( (end-start)/4 )\n"); + fprintf(stderr, "\t-v level:\n"); + fprintf(stderr, "\t\tverbose level (0)\n"); + fprintf(stderr, "\t-o origin:\n"); + fprintf(stderr, "\t\tzone origin (zonefile)\n"); + fprintf(stderr, "\t-f outfile:\n"); + fprintf(stderr, "\t\tfile the signed zone is written in " \ + "(zonefile + .signed)\n"); + + fprintf(stderr, "\n"); + + fprintf(stderr, "Signing Keys:\n"); + fprintf(stderr, "\tid:\t\t"); + fprintf(stderr, "zone key with matching keyid\n"); + fprintf(stderr, "\tid/alg:\t\t"); + fprintf(stderr, "zone key with matching keyid and algorithm\n"); + fprintf(stderr, "\tname/id/alg:\t"); + fprintf(stderr, "key with matching name, keyid and algorithm\n"); + fprintf(stderr, "\tnone:\t\t"); + fprintf(stderr, "all zone keys that have private keys\n"); + exit(0); } int main(int argc, char *argv[]) { - int i; + int i, ch; + char *startstr = NULL, *endstr = NULL; + char *origin = NULL, *file = NULL, *output = NULL; + char *endp; + int verbose = 0; + dns_db_t *db; + dns_dbversion_t *version; + signer_key_t *key; isc_result_t result; + extern char *optarg; + extern int optind; + dns_result_register(); result = isc_mem_create(0, 0, &mctx); check_result(result, "isc_mem_create()"); - argc--; - argv++; + while ((ch = getopt(argc, argv, "s:e:c:v:o:f:h")) != -1) { + switch (ch) { + case 's': + startstr = isc_mem_strdup(mctx, optarg); + if (startstr == NULL) + check_result(ISC_R_FAILURE, "isc_mem_strdup()"); + break; - for (i = 0; i < argc; i++) - sign(argv[i]); + case 'e': + endstr = isc_mem_strdup(mctx, optarg); + if (endstr == NULL) + check_result(ISC_R_FAILURE, "isc_mem_strdup()"); + break; + + case 'c': + endp = NULL; + cycle = strtol(optarg, &endp, 0); + if (endp != NULL) + check_result(ISC_R_FAILURE, "strtol()"); + break; + + case 'v': + endp = NULL; + verbose = strtol(optarg, &endp, 0); + if (endp != NULL) + check_result(ISC_R_FAILURE, "strtol()"); + break; + + case 'o': + origin = isc_mem_strdup(mctx, optarg); + if (origin == NULL) + check_result(ISC_R_FAILURE, "isc_mem_strdup()"); + break; + + case 'f': + output = isc_mem_strdup(mctx, optarg); + if (output == NULL) + check_result(ISC_R_FAILURE, "isc_mem_strdup()"); + break; + + case 'h': + usage(); + + } + } + + result = isc_stdtime_get(&now); + check_result(result, "isc_stdtime_get()"); + + if (startstr != NULL) { + start = strtotime(startstr, now, now); + isc_mem_free(mctx, startstr); + } + else + start = now; + + if (endstr != NULL) { + end = strtotime(endstr, now, start); + isc_mem_free(mctx, endstr); + } + else + end = start + (30 * 24 * 60 * 60); + + if (cycle == -1) { + cycle = (end - start) / 4; + } + + argc -= optind; + argv += optind; + + if (argc < 1) + check_result(ISC_R_FAILURE, "No zones specified"); + + file = isc_mem_strdup(mctx, argv[0]); + if (file == NULL) + check_result(ISC_R_FAILURE, "isc_mem_strdup()"); + + argc -= 1; + argv += 1; + + if (output == NULL) { + output = isc_mem_allocate(mctx, + strlen(file) + strlen(".signed") + 1); + if (output == NULL) + check_result(ISC_R_FAILURE, "isc_mem_allocate()"); + sprintf(output, "%s.signed", file); + } + + if (origin == NULL) + origin = file; + + db = NULL; + version = NULL; + loadzone(file, origin, &db, &version); + + ISC_LIST_INIT(keylist); + loadzonekeys(db, version); + + if (argc == 0) { + signer_key_t *key; + + key = ISC_LIST_HEAD(keylist); + while (key != NULL) { + key->isdefault = ISC_TRUE; + key = ISC_LIST_NEXT(key, link); + } + } + else { + for (i = 0; i < argc; i++) { + int id, alg; + char *idstr = NULL, *name = NULL, *algstr = NULL; + + idstr = argv[i]; + algstr = strchr(idstr, '/'); + if (algstr != NULL) { + *algstr++ = 0; + name = idstr; + idstr = algstr; + algstr = strchr(idstr + 1, '/'); + if (algstr != NULL) + *algstr++ = 0; + } + + endp = NULL; + id = strtol(idstr, &endp, 0); + if (endp != NULL) + check_result(ISC_R_FAILURE, "strtol"); + + if (algstr != NULL) { + endp = NULL; + alg = strtol(idstr, &endp, 0); + if (endp != NULL) + check_result(ISC_R_FAILURE, "strtol"); + } + else + alg = 0; + + if (name == NULL) + name = origin; + key = ISC_LIST_HEAD(keylist); + while (key != NULL) { + dst_key_t *dkey = key->key; + if (dst_key_id(dkey) == id && + (alg == 0 || dst_key_alg(dkey) == alg) && + strcasecmp(name, dst_key_name(dkey)) == 0) + { + key->isdefault = ISC_TRUE; + if (!dst_key_isprivate(dkey)) + check_result + (DST_R_NOTPRIVATEKEY, + "key specify"); + break; + } + } + if (key == NULL) { + dst_key_t *dkey = NULL; + result = dst_key_fromfile(name, id, alg, + DST_TYPE_PRIVATE, + mctx, &dkey); + check_result (result, "dst_key_fromfile"); + key->key = dkey; + key->isdefault = ISC_TRUE; + ISC_LIST_APPEND(keylist, key, link); + } + } + } + + signzone(db, version); + + /* should we update the SOA serial? */ + dns_db_closeversion(db, &version, ISC_TRUE); + result = dns_db_dump(db, NULL, output); + check_result(result, "dns_db_dump"); + dns_db_detach(&db); + + key = ISC_LIST_HEAD(keylist); + while (key != NULL) { + signer_key_t *next = ISC_LIST_NEXT(key, link); + dst_key_free(key->key); + isc_mem_put(mctx, key, sizeof(signer_key_t)); + key = next; + } + + if (origin != file) + isc_mem_free(mctx, origin); + isc_mem_free(mctx, file); + isc_mem_free(mctx, output); isc_mem_stats(mctx, stdout); isc_mem_destroy(&mctx); diff --git a/bin/tests/signer.c b/bin/tests/signer.c index 3bff266b07..78cb8bc288 100644 --- a/bin/tests/signer.c +++ b/bin/tests/signer.c @@ -1,10 +1,10 @@ /* * Copyright (C) 1999 Internet Software Consortium. - * + * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. - * + * * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE @@ -20,6 +20,7 @@ #include #include #include +#include /* for getopt */ #include #include @@ -28,6 +29,7 @@ #include #include #include +#include #include #include @@ -43,13 +45,32 @@ #include #include #include +#include #include -#define MAXKEYS 10 +#define BUFSIZE 2048 #define is_zone_key(key) ((dst_key_flags(key) & DNS_KEYFLAG_OWNERMASK) \ == DNS_KEYOWNER_ZONE) +typedef struct signer_key_struct signer_key_t; +typedef struct signer_array_struct signer_array_t; + +struct signer_key_struct { + dst_key_t *key; + isc_boolean_t isdefault; + ISC_LINK(signer_key_t) link; +}; + +struct signer_array_struct { + unsigned char array[BUFSIZE]; + ISC_LINK(signer_array_t) link; +}; + +ISC_LIST(signer_key_t) keylist; +isc_stdtime_t start = 0, end = 0, now; +int cycle = -1; + static isc_mem_t *mctx = NULL; static inline void @@ -70,7 +91,7 @@ check_result(isc_result_t result, char *message) { static void set_bit(unsigned char *array, unsigned int index, unsigned int bit) { unsigned int byte, shift, mask; - + byte = array[index / 8]; shift = 7 - (index % 8); mask = 1 << shift; @@ -81,152 +102,331 @@ set_bit(unsigned char *array, unsigned int index, unsigned int bit) { array[index / 8] &= (~mask & 0xFF); } -dns_result_t -find_apex_keys(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, - dns_name_t *name, isc_mem_t *mctx, unsigned int maxkeys, - dst_key_t **keys, unsigned int *nkeys) +static void +signwithkey(dns_name_t *name, dns_rdataset_t *rdataset, dns_rdata_t *rdata, + dst_key_t *key, isc_buffer_t *b) { - dns_rdataset_t rdataset; - dns_rdata_t rdata; isc_result_t result; - dst_key_t *pubkey; - unsigned int count = 0; - *nkeys = 0; - dns_rdataset_init(&rdataset); - result = dns_db_findrdataset(db, node, ver, dns_rdatatype_key, 0, 0, - &rdataset, NULL); - check_result(result, "dns_db_findrdataset()"); - result = dns_rdataset_first(&rdataset); - check_result(result, "dns_rdataset_first()"); - while (result == ISC_R_SUCCESS && count < maxkeys) { - pubkey = NULL; - dns_rdataset_current(&rdataset, &rdata); - result = dns_dnssec_keyfromrdata(name, &rdata, mctx, &pubkey); - check_result(result, "dns_dnssec_keyfromrdata()"); - result = dst_key_fromfile(dst_key_name(pubkey), - dst_key_id(pubkey), - dst_key_alg(pubkey), - DST_TYPE_PRIVATE, - mctx, &keys[count++]); - check_result(result, "dst_key_fromfile()"); + dns_rdata_init(rdata); + result = dns_dnssec_sign(name, rdataset, key, &start, &end, + mctx, b, rdata); + check_result(result, "dns_dnssec_sign()"); +#if 0 + /* Verify the data. This won't work if the start time is reset */ + result = dns_dnssec_verify(name, rdataset, key, mctx, rdata); + check_result(result, "dns_dnssec_verify()"); +#endif +} + +static inline isc_boolean_t +issigningkey(signer_key_t *key) { + return (key->isdefault); +} + +static inline isc_boolean_t +iszonekey(signer_key_t *key, dns_db_t *db) { + char origin[1024]; + isc_buffer_t b; + isc_result_t result; + + isc_buffer_init(&b, origin, sizeof(origin), ISC_BUFFERTYPE_BINARY); + result = dns_name_totext(dns_db_origin(db), ISC_FALSE, &b); + check_result(result, "dns_name_totext()"); + + return (strcasecmp(dst_key_name(key->key), origin) == 0 && + (dst_key_flags(key->key) & DNS_KEYFLAG_OWNERMASK) == + DNS_KEYOWNER_ZONE); +} + +static signer_key_t * +keythatsigned(dns_rdata_generic_sig_t *sig) { + char keyname[1024]; + isc_buffer_t b; + isc_result_t result; + dst_key_t *pubkey = NULL, *privkey = NULL; + signer_key_t *key; + + isc_buffer_init(&b, keyname, sizeof(keyname), ISC_BUFFERTYPE_BINARY); + result = dns_name_totext(sig->signer, ISC_FALSE, &b); + check_result(result, "dns_name_totext()"); + + key = ISC_LIST_HEAD(keylist); + while (key != NULL) { + if (sig->keyid == dst_key_id(key->key) && + sig->algorithm == dst_key_alg(key->key) && + strcasecmp(keyname, dst_key_name(key->key)) == 0) + return key; + } + + result = dst_key_fromfile(keyname, sig->keyid, sig->algorithm, + DST_TYPE_PUBLIC, mctx, &pubkey); + if (result != ISC_R_SUCCESS) + return (NULL); + + key = isc_mem_get(mctx, sizeof(signer_key_t)); + if (key == NULL) + check_result(ISC_R_FAILURE, "isc_mem_get"); + + result = dst_key_fromfile(keyname, sig->keyid, sig->algorithm, + DST_TYPE_PRIVATE, mctx, &pubkey); + if (result == ISC_R_SUCCESS) { + key->key = privkey; dst_key_free(pubkey); - pubkey = NULL; + } + else + key->key = pubkey; + key->isdefault = ISC_FALSE; + ISC_LIST_APPEND(keylist, key, link); + return key; +} + +static isc_boolean_t +expecttofindkey(dns_name_t *name, dns_db_t *db, dns_dbversion_t *version) { + unsigned int options = DNS_DBFIND_NOWILD; + isc_result_t result; + + result = dns_db_find(db, name, version, dns_rdatatype_key, options, + 0, NULL, NULL, NULL, NULL); + switch (result) { + case DNS_R_SUCCESS: + case DNS_R_NXDOMAIN: + case DNS_R_NXRDATASET: + return ISC_TRUE; + case DNS_R_DELEGATION: + case DNS_R_CNAME: + case DNS_R_DNAME: + return ISC_FALSE; + default: + check_result(result, "dns_db_find"); + return ISC_FALSE; /* removess a warning */ + } +} + +static isc_boolean_t +setverifies(dns_name_t *name, dns_rdataset_t *set, signer_key_t *key, + dns_rdata_t *sig) +{ + isc_result_t result = dns_dnssec_verify(name, set, key->key, mctx, sig); + return (result == ISC_R_SUCCESS); +} + +#define allocbufferandrdata \ + isc_buffer_t b; \ + trdata = isc_mem_get(mctx, sizeof(dns_rdata_t)); \ + tdata = isc_mem_get(mctx, sizeof(signer_array_t)); \ + ISC_LIST_APPEND(arraylist, tdata, link); \ + if (trdata == NULL || tdata == NULL) \ + check_result(ISC_R_FAILURE, "isc_mem_get"); \ + isc_buffer_init(&b, tdata->array, sizeof(tdata->array), \ + ISC_BUFFERTYPE_BINARY); + +static void +signset(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, + dns_name_t *name, dns_rdataset_t *set) +{ + dns_rdatalist_t siglist; + dns_rdataset_t sigset, oldsigset; + dns_rdata_t oldsigrdata; + dns_rdata_t *trdata; + dns_rdata_generic_sig_t sig; + signer_key_t *key; + isc_result_t result; + isc_boolean_t notsigned = ISC_TRUE; + isc_boolean_t wassignedby[256], nowsignedby[256]; + signer_array_t *tdata; + ISC_LIST(signer_array_t) arraylist; + int i; + + ISC_LIST_INIT(siglist.rdata); + ISC_LIST_INIT(arraylist); + + for (i = 0; i < 256; i++) + wassignedby[i] = nowsignedby[i] = ISC_FALSE; + + dns_rdataset_init(&oldsigset); + result = dns_db_findrdataset(db, node, version, dns_rdatatype_sig, + set->type, 0, &oldsigset, NULL); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) + check_result(result, "dns_db_findrdataset()"); + + if (result != ISC_R_NOTFOUND) { + result = dns_rdataset_first(&oldsigset); + while (result == ISC_R_SUCCESS) { + isc_boolean_t expired; + isc_boolean_t keep = ISC_FALSE, resign = ISC_FALSE; + + dns_rdataset_current(&oldsigset, &oldsigrdata); + + result = dns_rdata_tostruct(&oldsigrdata, &sig, mctx); + check_result(result, "dns_rdata_tostruct"); + + expired = (now + cycle > sig.timeexpire); + + key = keythatsigned(&sig); + + if (key == NULL && + expecttofindkey(sig.signer, db, version)) + ; /* sig is dropped and not replaced */ + else if (key == NULL) + keep = ISC_TRUE; + else if (issigningkey(key)) { + if (!expired && + setverifies(name, set, key, &oldsigrdata)) + { + keep = ISC_TRUE; + wassignedby[sig.algorithm] = ISC_TRUE; + } + else { + wassignedby[sig.algorithm] = ISC_TRUE; + resign = ISC_TRUE; + } + } + else if (iszonekey(key, db)) { + if (!expired && + setverifies(name, set, key, &oldsigrdata)) + { + keep = ISC_TRUE; + wassignedby[sig.algorithm] = ISC_TRUE; + nowsignedby[sig.algorithm] = ISC_TRUE; + } + else { + wassignedby[sig.algorithm] = ISC_TRUE; + if (dst_key_isprivate(key->key)) + resign = ISC_TRUE; + } + } + else if (!expired) + keep = ISC_TRUE; + /* else sig has expired and we cannot regenerate it */ + + if (keep) { + allocbufferandrdata; + result = dns_rdata_fromstruct(trdata, + set->rdclass, + dns_rdatatype_sig, + &sig, &b); + nowsignedby[sig.algorithm] = ISC_TRUE; + ISC_LIST_APPEND(siglist.rdata, trdata, link); + } + else if (resign) { + allocbufferandrdata; + signwithkey(name, set, trdata, key->key, &b); + nowsignedby[sig.algorithm] = ISC_TRUE; + ISC_LIST_APPEND(siglist.rdata, trdata, link); + } + + dns_rdata_freestruct(&sig); + result = dns_rdataset_next(&oldsigset); + } + if (result == DNS_R_NOMORE) + result = ISC_R_SUCCESS; + check_result(result, "dns_db_dns_rdataset_first()/next()"); + dns_rdataset_disassociate(&oldsigset); + } + + for (i = 0; i < 256; i++) + if (wassignedby[i] != 0) { + notsigned = ISC_FALSE; + break; + } + + key = ISC_LIST_HEAD(keylist); + while (key != NULL) { + int alg = dst_key_alg(key->key); + if (key->isdefault && + (notsigned || (wassignedby[alg] && !nowsignedby[alg]))) + { + allocbufferandrdata; + signwithkey(name, set, trdata, key->key, &b); + ISC_LIST_APPEND(siglist.rdata, trdata, link); + } + key = ISC_LIST_NEXT(key, link); + } + + siglist.rdclass = set->rdclass; + siglist.type = dns_rdatatype_sig; + siglist.covers = set->type; + if (end - start < set->ttl) + siglist.ttl = end - start; + else + siglist.ttl = set->ttl; + dns_rdataset_init(&sigset); + result = dns_rdatalist_tordataset(&siglist, &sigset); + check_result(result, "dns_rdatalist_tordataset"); + result = dns_db_addrdataset(db, node, version, 0, &sigset, + ISC_FALSE, NULL); + if (result == DNS_R_UNCHANGED) + result = ISC_R_SUCCESS; + check_result(result, "dns_db_addrdataset"); + dns_rdataset_disassociate(&sigset); + + trdata = ISC_LIST_HEAD(siglist.rdata); + while (trdata != NULL) { + dns_rdata_t *next = ISC_LIST_NEXT(trdata, link); + isc_mem_put(mctx, trdata, sizeof(dns_rdata_t)); + trdata = next; + } + + tdata = ISC_LIST_HEAD(arraylist); + while (tdata != NULL) { + signer_array_t *next = ISC_LIST_NEXT(tdata, link); + isc_mem_put(mctx, tdata, sizeof(signer_array_t)); + tdata = next; + } +} + +static isc_boolean_t +hasnullkey(dns_rdataset_t rdataset) { + isc_result_t result; + dns_rdata_t rdata; + isc_uint32_t flags; + + result = dns_rdataset_first(&rdataset); + while (result == ISC_R_SUCCESS) { + dst_key_t *key = NULL; + + dns_rdataset_current(&rdataset, &rdata); + result = dns_dnssec_keyfromrdata(dns_rootname, + &rdata, mctx, &key); + check_result(result, "dns_dnssec_keyfromrdata()"); + flags = dst_key_flags(key); + dst_key_free(key); + if (((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) && + ((flags & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE)) + return (ISC_TRUE); result = dns_rdataset_next(&rdataset); } if (result != DNS_R_NOMORE) - check_result(result, "iteration over zone keys"); - result = DNS_R_SUCCESS; - if (count == 0) - check_result(ISC_R_FAILURE, "no key found"); - -/* failure:*/ - if (dns_rdataset_isassociated(&rdataset)) - dns_rdataset_disassociate(&rdataset); - if (pubkey != NULL) - dst_key_free(pubkey); - *nkeys = count; - return (result); -} - - -static void -sign_with_key(dns_name_t *name, dns_rdataset_t *rdataset, dns_rdata_t *rdata, - dns_rdatalist_t *sigrdatalist, isc_stdtime_t *now, - isc_stdtime_t *later, dst_key_t *key, - unsigned char *array, int len) -{ - isc_buffer_t b; - isc_region_t r; - isc_result_t result; - - r.base = array; - r.length = len; - memset(r.base, 0, r.length); - - dns_rdata_init(rdata); - isc_buffer_init(&b, r.base, r.length, ISC_BUFFERTYPE_BINARY); - result = dns_dnssec_sign(name, rdataset, key, now, later, - mctx, &b, rdata); - check_result(result, "dns_dnssec_sign()"); - result = dns_dnssec_verify(name, rdataset, key, mctx, rdata); - check_result(result, "dns_dnssec_verify()"); - ISC_LIST_APPEND(sigrdatalist->rdata, rdata, link); -} - -void -resign_set(dns_name_t *name, dns_name_t *origin, dns_rdataset_t *rdataset, - dns_rdata_t *rdata, dns_rdatalist_t *sigrdatalist, - dns_rdata_t *oldsigrdata, isc_stdtime_t *now, isc_stdtime_t *later, - dst_key_t **keys, int nkeys, unsigned char *array, int len) -{ - dns_rdata_generic_sig_t sig; - isc_result_t result; - isc_buffer_t b; - int i; - isc_boolean_t done = ISC_FALSE; - isc_boolean_t foundnonzone = ISC_FALSE; - - result = dns_rdata_tostruct(oldsigrdata, &sig, mctx); - check_result(result, "dns_rdata_tostruct()"); - - /* - * Is this a real signture that we should regenerate? - */ - if (dns_name_compare(sig.signer, origin) == 0) { - for (i = 0; i < nkeys; i++) { - dst_key_t *key = keys[i]; - if (dst_key_id(key) == sig.keyid && - dst_key_alg(key) == sig.algorithm) - { - if (!is_zone_key(key)) - foundnonzone = ISC_TRUE; - else - break; - } - } - if (i < nkeys) { - sign_with_key(name, rdataset, rdata, sigrdatalist, - now, later, keys[i], array, len); - done = ISC_TRUE; - } - } - if (!done) { - if (dns_name_compare(sig.signer, origin) != 0 || foundnonzone) { - printf("saving old sig...\n"); - isc_buffer_init(&b, array, len, ISC_BUFFERTYPE_BINARY); - result = dns_rdata_fromstruct(rdata, rdataset->rdclass, - dns_rdatatype_sig, - &sig, &b); - ISC_LIST_APPEND(sigrdatalist->rdata, rdata, link); - check_result(result, "dns_rdata_fromstruct()"); - } - else - printf("couldn't find key /%d, dropping sig\n", - sig.keyid); - } - - dns_rdata_freestruct(&sig); + check_result(result, "iteration over keys"); + return (ISC_FALSE); } static void -generate_sig(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, - dns_name_t *name, dst_key_t **keys, isc_boolean_t *defaultkey, - int nkeys) +signname(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, + dns_name_t *name) { isc_result_t result; dns_name_t *origin; - dns_rdata_t rdata, rdatas[MAXKEYS]; - dns_rdataset_t rdataset, sigrdataset, oldsigset; - dns_rdatalist_t sigrdatalist; + dns_rdata_t rdata; + dns_rdataset_t rdataset, nsset; dns_rdatasetiter_t *rdsiter; - isc_stdtime_t now, later; - unsigned char array[MAXKEYS][1024]; - int i; - isc_boolean_t alreadysigned; + isc_boolean_t atorigin = ISC_FALSE, isdelegation = ISC_FALSE; origin = dns_db_origin(db); + atorigin = (dns_name_compare(name, origin) == 0); + if (!atorigin) { + dns_rdataset_init(&nsset); + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_ns, 0, 0, &nsset, + NULL); + /* Is this a delegation point? */ + if (result == ISC_R_SUCCESS) { + isdelegation = ISC_TRUE; + dns_rdataset_disassociate(&nsset); + } + } dns_rdataset_init(&rdataset); rdsiter = NULL; result = dns_db_allrdatasets(db, node, version, 0, &rdsiter); @@ -235,36 +435,38 @@ generate_sig(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, while (result == ISC_R_SUCCESS) { dns_rdatasetiter_current(rdsiter, &rdataset); - if (rdataset.type == dns_rdatatype_sig || - (rdataset.type == dns_rdatatype_key && - dns_name_compare(name, origin) == 0)) - { - dns_rdataset_disassociate(&rdataset); - result = dns_rdatasetiter_next(rdsiter); - continue; - } + /* If this is a SIG set, skip it. */ + if (rdataset.type == dns_rdatatype_sig) + goto skip; - dns_rdataset_init(&oldsigset); - result = dns_db_findrdataset(db, node, version, - dns_rdatatype_sig, rdataset.type, - 0, &oldsigset, NULL); - if (result == ISC_R_SUCCESS) - alreadysigned = ISC_TRUE; - else if (result == ISC_R_NOTFOUND) { - alreadysigned = ISC_FALSE; - result = ISC_R_SUCCESS; - } - else - alreadysigned = ISC_FALSE; /* not that this matters */ - check_result(result, "dns_db_findrdataset()"); + /* If this is a KEY set at the apex, skip it. */ + if (rdataset.type == dns_rdatatype_key && atorigin) + goto skip; /* - * There probably should be a dns_nxtsetbit or something, - * but it can get complicated if we need to extend the - * length. In this case, since the NXT bit is set and + * If this name is a delegation point, skip all records + * except a KEY set containing a NULL key or an NXT set. + */ + if (isdelegation) { + switch (rdataset.type) { + case dns_rdatatype_nxt: + break; + case dns_rdatatype_key: + if (hasnullkey(rdataset)) + break; + goto skip; + default: + goto skip; + } + } + + /* + * There probably should be a dns_nxtsetbit, but it can get + * complicated if we need to extend the length of the + * bit set. In this case, since the NXT bit is set and * SIG < NXT, the easy way works. */ - if (rdataset.type == dns_rdatatype_nxt && !alreadysigned) { + if (rdataset.type == dns_rdatatype_nxt) { unsigned char *nxt_bits; dns_name_t nxtname; isc_region_t r, r2; @@ -278,55 +480,11 @@ generate_sig(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, dns_name_toregion(&nxtname, &r2); nxt_bits = r.base + r2.length; set_bit(nxt_bits, dns_rdatatype_sig, 1); - } - isc_stdtime_get(&now); - later = 100000 + now; - ISC_LIST_INIT(sigrdatalist.rdata); - - if (!alreadysigned) { - for (i = 0; i < nkeys; i++) { - if (!defaultkey[i] || !is_zone_key(keys[i])) - continue; - sign_with_key(name, &rdataset, &rdatas[i], - &sigrdatalist, &now, &later, - keys[i], array[i], - sizeof(array[i])); - } - } - else { - dns_rdata_t sigrdata; - - dns_rdata_init(&sigrdata); - result = dns_rdataset_first(&oldsigset); - i = 0; - while (result == ISC_R_SUCCESS) { - dns_rdataset_current(&oldsigset, &sigrdata); - resign_set(name, origin, &rdataset, &rdatas[i], - &sigrdatalist, &sigrdata, - &now, &later, keys, nkeys, array[i], - sizeof(array[i])); - i++; - result = dns_rdataset_next(&oldsigset); - } - dns_rdataset_disassociate(&oldsigset); - } - - sigrdatalist.rdclass = rdataset.rdclass; - sigrdatalist.type = dns_rdatatype_sig; - sigrdatalist.covers = rdataset.type; - sigrdatalist.ttl = rdataset.ttl; - dns_rdataset_init(&sigrdataset); - result = dns_rdatalist_tordataset(&sigrdatalist, &sigrdataset); - check_result(result, "dns_rdatalist_tordataset"); - result = dns_db_addrdataset(db, node, version, 0, &sigrdataset, - ISC_FALSE, NULL); - if (result == DNS_R_UNCHANGED) - result = ISC_R_SUCCESS; - check_result(result, "dns_db_addrdataset"); - dns_rdataset_disassociate(&sigrdataset); + signset(db, version, node, name, &rdataset); + skip: dns_rdataset_disassociate(&rdataset); result = dns_rdatasetiter_next(rdsiter); } @@ -398,67 +556,27 @@ next_active(dns_db_t *db, dns_dbversion_t *version, dns_dbiterator_t *dbiter, } static void -sign(char *filename) { +signzone(dns_db_t *db, dns_dbversion_t *version) { isc_result_t result, nxtresult; - dns_db_t *db; - dns_dbversion_t *wversion; dns_dbnode_t *node, *nextnode, *curnode; - char *origintext; dns_fixedname_t fname, fnextname; dns_name_t *name, *nextname, *target, curname; - isc_buffer_t b; - size_t len; dns_dbiterator_t *dbiter; - char newfilename[1024]; - dst_key_t *keys[MAXKEYS]; - isc_boolean_t defaultkey[MAXKEYS]; unsigned char curdata[1024]; isc_buffer_t curbuf; - unsigned int nkeys = 0; - unsigned int i; dns_fixedname_init(&fname); name = dns_fixedname_name(&fname); dns_fixedname_init(&fnextname); nextname = dns_fixedname_name(&fnextname); - origintext = strrchr(filename, '/'); - if (origintext == NULL) - origintext = filename; - else - origintext++; /* Skip '/'. */ - len = strlen(origintext); - isc_buffer_init(&b, origintext, len, ISC_BUFFERTYPE_TEXT); - isc_buffer_add(&b, len); - result = dns_name_fromtext(name, &b, dns_rootname, ISC_FALSE, NULL); - check_result(result, "dns_name_fromtext()"); - db = NULL; - result = dns_db_create(mctx, "rbt", name, ISC_FALSE, - dns_rdataclass_in, 0, NULL, &db); - check_result(result, "dns_db_create()"); - result = dns_db_load(db, filename); - check_result(result, "dns_db_load()"); - - node = NULL; - result = dns_db_findnode(db, name, ISC_FALSE, &node); - check_result(result, "dns_db_findnode()"); - result = find_apex_keys(db, NULL, node, name, mctx, MAXKEYS, - keys, &nkeys); - check_result(result, "dns_dnssec_findzonekeys()"); - dns_db_detachnode(db, &node); - for (i = 0; i < nkeys; i++) - defaultkey[i] = ISC_TRUE; - - wversion = NULL; - result = dns_db_newversion(db, &wversion); - check_result(result, "dns_db_newversion()"); - dbiter = NULL; result = dns_db_createiterator(db, ISC_FALSE, &dbiter); check_result(result, "dns_db_createiterator()"); result = dns_dbiterator_first(dbiter); node = NULL; - result = next_active(db, wversion, dbiter, name, &node); + dns_name_clone(dns_db_origin(db), name); + result = next_active(db, version, dbiter, name, &node); while (result == ISC_R_SUCCESS) { nextnode = NULL; curnode = NULL; @@ -469,7 +587,7 @@ sign(char *filename) { dns_dbiterator_current(dbiter, &curnode, &curname); result = dns_dbiterator_next(dbiter); if (result == ISC_R_SUCCESS) - result = next_active(db, wversion, dbiter, nextname, + result = next_active(db, version, dbiter, nextname, &nextnode); if (result == ISC_R_SUCCESS) target = nextname; @@ -479,10 +597,9 @@ sign(char *filename) { target = NULL; /* Make compiler happy. */ fatal("db iteration failed"); } - nxtresult = dns_buildnxt(db, wversion, node, target); - check_result(nxtresult, "dns_buildnxt()"); - generate_sig(db, wversion, node, &curname, keys, defaultkey, - nkeys); + nxtresult = dns_buildnxt(db, version, node, target); + check_result(nxtresult, "dns_buildnxt()"); + signname(db, version, node, &curname); dns_name_invalidate(&curname); dns_db_detachnode(db, &node); dns_db_detachnode(db, &curnode); @@ -491,36 +608,337 @@ sign(char *filename) { if (result != DNS_R_NOMORE) fatal("db iteration failed"); dns_dbiterator_destroy(&dbiter); - /* - * XXXRTH For now, we don't increment the SOA serial. - */ - dns_db_closeversion(db, &wversion, ISC_TRUE); - len = strlen(filename); - if (len + 4 + 1 > sizeof newfilename) - fatal("filename too long"); - sprintf(newfilename, "%s.new", filename); - result = dns_db_dump(db, NULL, newfilename); - check_result(result, "dns_db_dump"); - dns_db_detach(&db); - for (i = 0; i < nkeys; i++) - dst_key_free(keys[i]); +} + +static void +loadzone(char *file, char *origin, dns_db_t **db, dns_dbversion_t **version) { + isc_buffer_t b, b2; + unsigned char namedata[1024]; + int len; + dns_name_t name; + isc_result_t result; + + len = strlen(origin); + isc_buffer_init(&b, origin, len, ISC_BUFFERTYPE_TEXT); + isc_buffer_add(&b, len); + + isc_buffer_init(&b2, namedata, sizeof(namedata), ISC_BUFFERTYPE_BINARY); + + dns_name_init(&name, NULL); + result = dns_name_fromtext(&name, &b, dns_rootname, ISC_FALSE, &b2); + check_result(result, "dns_name_fromtext()"); + + result = dns_db_create(mctx, "rbt", &name, ISC_FALSE, + dns_rdataclass_in, 0, NULL, db); + check_result(result, "dns_db_create()"); + + result = dns_db_load(*db, file); + check_result(result, "dns_db_load()"); + + result = dns_db_newversion(*db, version); + check_result(result, "dns_db_newversion()"); +} + +static void +loadzonekeys(dns_db_t *db, dns_dbversion_t *version) { + dns_name_t *origin; + dns_dbnode_t *node; + isc_result_t result; + dst_key_t *keys[20]; + int nkeys, i; + + origin = dns_db_origin(db); + + node = NULL; + result = dns_db_findnode(db, origin, ISC_FALSE, &node); + check_result(result, "dns_db_findnode()"); + + result = dns_dnssec_findzonekeys(db, version, node, origin, mctx, + 20, keys, &nkeys); + check_result(result, "dns_dnssec_findzonekeys()"); + + for (i = 0; i < nkeys; i++) { + signer_key_t *key; + + key = isc_mem_get(mctx, sizeof(signer_key_t)); + if (key == NULL) + check_result(ISC_R_FAILURE, "isc_mem_get(key)"); + key->key = keys[i]; + key->isdefault = ISC_FALSE; + + ISC_LIST_APPEND(keylist, key, link); + } + dns_db_detachnode(db, &node); +} + + +static isc_stdtime_t +strtotime(char *str, isc_int64_t now, isc_int64_t base) { + isc_int64_t val, offset; + isc_result_t result; + char *endp = ""; + + if (str[0] == '+') { + offset = strtol(str + 1, &endp, 0); + val = base + offset; + } + else if (strncmp(str, "now+", 4) == 0) { + offset = strtol(str + 4, &endp, 0); + val = now + offset; + } + else { + result = dns_time64_fromtext(str, &val); + check_result(result, "dns_time64_fromtext()"); + } + if (*endp != '\0') + check_result(ISC_R_FAILURE, "strtol()"); + + return ((isc_stdtime_t) val); +} + +static void +usage() { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, "\tsigner [options] zonefile [keys]\n"); + + fprintf(stderr, "\n"); + + fprintf(stderr, "Options:\n"); + fprintf(stderr, "\t-s YYYYMMDDHHMMSS|+ttl:\n"); + fprintf(stderr, "\t\tSIG start time - absolute|offset (now)\n"); + fprintf(stderr, "\t-e YYYYMMDDHHMMSS|+ttl|now+ttl]:\n"); + fprintf(stderr, "\t\tSIG end time - absolute|from start|from now (now + 30 days)\n"); + fprintf(stderr, "\t-c ttl:\n"); + fprintf(stderr, "\t\tcycle period - regenerate if < cycle from end ( (end-start)/4 )\n"); + fprintf(stderr, "\t-v level:\n"); + fprintf(stderr, "\t\tverbose level (0)\n"); + fprintf(stderr, "\t-o origin:\n"); + fprintf(stderr, "\t\tzone origin (zonefile)\n"); + fprintf(stderr, "\t-f outfile:\n"); + fprintf(stderr, "\t\tfile the signed zone is written in " \ + "(zonefile + .signed)\n"); + + fprintf(stderr, "\n"); + + fprintf(stderr, "Signing Keys:\n"); + fprintf(stderr, "\tid:\t\t"); + fprintf(stderr, "zone key with matching keyid\n"); + fprintf(stderr, "\tid/alg:\t\t"); + fprintf(stderr, "zone key with matching keyid and algorithm\n"); + fprintf(stderr, "\tname/id/alg:\t"); + fprintf(stderr, "key with matching name, keyid and algorithm\n"); + fprintf(stderr, "\tnone:\t\t"); + fprintf(stderr, "all zone keys that have private keys\n"); + exit(0); } int main(int argc, char *argv[]) { - int i; + int i, ch; + char *startstr = NULL, *endstr = NULL; + char *origin = NULL, *file = NULL, *output = NULL; + char *endp; + int verbose = 0; + dns_db_t *db; + dns_dbversion_t *version; + signer_key_t *key; isc_result_t result; + extern char *optarg; + extern int optind; + dns_result_register(); result = isc_mem_create(0, 0, &mctx); check_result(result, "isc_mem_create()"); - argc--; - argv++; + while ((ch = getopt(argc, argv, "s:e:c:v:o:f:h")) != -1) { + switch (ch) { + case 's': + startstr = isc_mem_strdup(mctx, optarg); + if (startstr == NULL) + check_result(ISC_R_FAILURE, "isc_mem_strdup()"); + break; - for (i = 0; i < argc; i++) - sign(argv[i]); + case 'e': + endstr = isc_mem_strdup(mctx, optarg); + if (endstr == NULL) + check_result(ISC_R_FAILURE, "isc_mem_strdup()"); + break; + + case 'c': + endp = NULL; + cycle = strtol(optarg, &endp, 0); + if (endp != NULL) + check_result(ISC_R_FAILURE, "strtol()"); + break; + + case 'v': + endp = NULL; + verbose = strtol(optarg, &endp, 0); + if (endp != NULL) + check_result(ISC_R_FAILURE, "strtol()"); + break; + + case 'o': + origin = isc_mem_strdup(mctx, optarg); + if (origin == NULL) + check_result(ISC_R_FAILURE, "isc_mem_strdup()"); + break; + + case 'f': + output = isc_mem_strdup(mctx, optarg); + if (output == NULL) + check_result(ISC_R_FAILURE, "isc_mem_strdup()"); + break; + + case 'h': + usage(); + + } + } + + result = isc_stdtime_get(&now); + check_result(result, "isc_stdtime_get()"); + + if (startstr != NULL) { + start = strtotime(startstr, now, now); + isc_mem_free(mctx, startstr); + } + else + start = now; + + if (endstr != NULL) { + end = strtotime(endstr, now, start); + isc_mem_free(mctx, endstr); + } + else + end = start + (30 * 24 * 60 * 60); + + if (cycle == -1) { + cycle = (end - start) / 4; + } + + argc -= optind; + argv += optind; + + if (argc < 1) + check_result(ISC_R_FAILURE, "No zones specified"); + + file = isc_mem_strdup(mctx, argv[0]); + if (file == NULL) + check_result(ISC_R_FAILURE, "isc_mem_strdup()"); + + argc -= 1; + argv += 1; + + if (output == NULL) { + output = isc_mem_allocate(mctx, + strlen(file) + strlen(".signed") + 1); + if (output == NULL) + check_result(ISC_R_FAILURE, "isc_mem_allocate()"); + sprintf(output, "%s.signed", file); + } + + if (origin == NULL) + origin = file; + + db = NULL; + version = NULL; + loadzone(file, origin, &db, &version); + + ISC_LIST_INIT(keylist); + loadzonekeys(db, version); + + if (argc == 0) { + signer_key_t *key; + + key = ISC_LIST_HEAD(keylist); + while (key != NULL) { + key->isdefault = ISC_TRUE; + key = ISC_LIST_NEXT(key, link); + } + } + else { + for (i = 0; i < argc; i++) { + int id, alg; + char *idstr = NULL, *name = NULL, *algstr = NULL; + + idstr = argv[i]; + algstr = strchr(idstr, '/'); + if (algstr != NULL) { + *algstr++ = 0; + name = idstr; + idstr = algstr; + algstr = strchr(idstr + 1, '/'); + if (algstr != NULL) + *algstr++ = 0; + } + + endp = NULL; + id = strtol(idstr, &endp, 0); + if (endp != NULL) + check_result(ISC_R_FAILURE, "strtol"); + + if (algstr != NULL) { + endp = NULL; + alg = strtol(idstr, &endp, 0); + if (endp != NULL) + check_result(ISC_R_FAILURE, "strtol"); + } + else + alg = 0; + + if (name == NULL) + name = origin; + key = ISC_LIST_HEAD(keylist); + while (key != NULL) { + dst_key_t *dkey = key->key; + if (dst_key_id(dkey) == id && + (alg == 0 || dst_key_alg(dkey) == alg) && + strcasecmp(name, dst_key_name(dkey)) == 0) + { + key->isdefault = ISC_TRUE; + if (!dst_key_isprivate(dkey)) + check_result + (DST_R_NOTPRIVATEKEY, + "key specify"); + break; + } + } + if (key == NULL) { + dst_key_t *dkey = NULL; + result = dst_key_fromfile(name, id, alg, + DST_TYPE_PRIVATE, + mctx, &dkey); + check_result (result, "dst_key_fromfile"); + key->key = dkey; + key->isdefault = ISC_TRUE; + ISC_LIST_APPEND(keylist, key, link); + } + } + } + + signzone(db, version); + + /* should we update the SOA serial? */ + dns_db_closeversion(db, &version, ISC_TRUE); + result = dns_db_dump(db, NULL, output); + check_result(result, "dns_db_dump"); + dns_db_detach(&db); + + key = ISC_LIST_HEAD(keylist); + while (key != NULL) { + signer_key_t *next = ISC_LIST_NEXT(key, link); + dst_key_free(key->key); + isc_mem_put(mctx, key, sizeof(signer_key_t)); + key = next; + } + + if (origin != file) + isc_mem_free(mctx, origin); + isc_mem_free(mctx, file); + isc_mem_free(mctx, output); isc_mem_stats(mctx, stdout); isc_mem_destroy(&mctx);