From: Matthijs Mekking Date: Thu, 17 Oct 2019 09:57:20 +0000 (+0200) Subject: Adjust signing code to use kasp X-Git-Tag: v9.15.6~26^2~16 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c125b721efe711c0b6d98eb27ebd9caaf4a42267;p=thirdparty%2Fbind9.git Adjust signing code to use kasp Update the signing code in lib/dns/zone.c and lib/dns/update.c to use kasp logic if a dnssec-policy is enabled. This means zones with dnssec-policy should no longer follow 'update-check-ksk' and 'dnssec-dnskey-kskonly' logic, instead the KASP keys configured dictate which RRset gets signed with what key. Also use the next rekey event from the key manager rather than setting it to one hour. Mark the zone dynamic, as otherwise a zone with dnssec-policy is not eligble for automatic DNSSEC maintenance. --- diff --git a/bin/named/server.c b/bin/named/server.c index ca220ec62c9..c788deb19e1 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -6257,8 +6257,10 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, strcasecmp(ztypestr, "master") == 0 || strcasecmp(ztypestr, "secondary") == 0 || strcasecmp(ztypestr, "slave") == 0) && - cfg_map_get(zoptions, "inline-signing", &signing) == ISC_R_SUCCESS && - cfg_obj_asboolean(signing)) + ((cfg_map_get(zoptions, "inline-signing", &signing) == + ISC_R_SUCCESS && cfg_obj_asboolean(signing)) || + (cfg_map_get(zoptions, "dnssec-policy", &signing) == + ISC_R_SUCCESS && signing != NULL))) { dns_zone_getraw(zone, &raw); if (raw == NULL) { diff --git a/bin/rndc/rndc.docbook b/bin/rndc/rndc.docbook index c5c1c4e0a24..83b7eb291ba 100644 --- a/bin/rndc/rndc.docbook +++ b/bin/rndc/rndc.docbook @@ -443,7 +443,8 @@ allowed to incrementally re-sign over time. - This command requires that the + This command requires that the zone is configured with a + dnssec-policy, or that the auto-dnssec zone option be set to maintain, and also requires the zone to be configured to @@ -849,7 +850,8 @@ re-signed with the new key set. - This command requires that the + This command requires that the zone is configured with a + dnssec-policy, or that the auto-dnssec zone option be set to allow or maintain, diff --git a/lib/dns/update.c b/lib/dns/update.c index 25827d4b543..4c621c404f2 100644 --- a/lib/dns/update.c +++ b/lib/dns/update.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -1076,6 +1077,7 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, { isc_result_t result; dns_dbnode_t *node = NULL; + dns_kasp_t *kasp = dns_zone_getkasp(zone); dns_rdataset_t rdataset; dns_rdata_t sig_rdata = DNS_RDATA_INIT; dns_stats_t* dnssecsignstats = dns_zone_getdnssecsignstats(zone); @@ -1085,6 +1087,11 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, bool added_sig = false; isc_mem_t *mctx = diff->mctx; + if (kasp != NULL) { + check_ksk = false; + keyset_kskonly = true; + } + dns_rdataset_init(&rdataset); isc_buffer_init(&buffer, data, sizeof(data)); @@ -1155,7 +1162,54 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, } } - if (both) { + if (kasp != NULL) { + /* + * A dnssec-policy is found. Check what RRsets this + * key should sign. + */ + isc_result_t kresult; + bool ksk = false; + bool zsk = false; + + kresult = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk); + if (kresult != ISC_R_SUCCESS) { + if (KSK(keys[i])) { + ksk = true; + } + } + kresult = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk); + if (kresult != ISC_R_SUCCESS) { + if (!KSK(keys[i])) { + zsk = true; + } + } + + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || + type == dns_rdatatype_cds) + { + /* + * DNSKEY RRset is signed with KSK. + * CDS and CDNSKEY RRsets too (RFC 7344, 4.1). + */ + if (!ksk) { + continue; + } + } else if (!zsk) { + /* + * Other RRsets are signed with ZSK. + */ + continue; + } + + /* + * If this key is revoked, it may only sign the + * DNSKEY RRset. + */ + if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) { + continue; + } + } else if (both) { /* * CDS and CDNSKEY are signed with KSK (RFC 7344, 4.1). */ diff --git a/lib/dns/zone.c b/lib/dns/zone.c index d03d55abf04..c88c1714ac4 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -1886,7 +1887,7 @@ zone_load(dns_zone_t *zone, unsigned int flags, bool locked) { isc_time_t now; isc_time_t loadtime; dns_db_t *db = NULL; - bool rbt, hasraw; + bool rbt, hasraw, is_dynamic; REQUIRE(DNS_ZONE_VALID(zone)); @@ -1951,11 +1952,13 @@ zone_load(dns_zone_t *zone, unsigned int flags, bool locked) { goto cleanup; } - if (zone->db != NULL && dns_zone_isdynamic(zone, false)) { + is_dynamic = dns_zone_isdynamic(zone, false) || + dns_zone_getkasp(zone) != NULL; + if (zone->db != NULL && is_dynamic) { /* - * This is a slave, stub, or dynamically updated - * zone being reloaded. Do nothing - the database - * we already have is guaranteed to be up-to-date. + * This is a slave, stub, dynamically updated, or kasp enabled + * zone being reloaded. Do nothing - the database we already + * have is guaranteed to be up-to-date. */ if (zone->type == dns_zone_master && !hasraw) result = DNS_R_DYNAMIC; @@ -3653,8 +3656,9 @@ set_resigntime(dns_zone_t *zone) { dns_db_t *db = NULL; /* We only re-sign zones that can be dynamically updated */ - if (zone->update_disabled) + if (zone->update_disabled) { return; + } if (!inline_secure(zone) && (zone->type != dns_zone_master || (zone->ssutable == NULL && @@ -3680,10 +3684,11 @@ set_resigntime(dns_zone_t *zone) { goto cleanup; } - resign = rdataset.resign - zone->sigresigninginterval; + resign = rdataset.resign - dns_zone_getsigresigninginterval(zone); dns_rdataset_disassociate(&rdataset); nanosecs = isc_random_uniform(1000000000); isc_time_set(&zone->resigntime, resign, nanosecs); + cleanup: dns_db_detach(&db); return; @@ -3700,6 +3705,7 @@ check_nsec3param(dns_zone_t *zone, dns_db_t *db) { dns_rdata_t rdata = DNS_RDATA_INIT; bool dynamic = (zone->type == dns_zone_master) ? dns_zone_isdynamic(zone, false) : false; + dynamic = dynamic || dns_zone_getkasp(zone); dns_rdataset_init(&rdataset); result = dns_db_findnode(db, &zone->origin, false, &node); @@ -4506,6 +4512,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, bool had_db = false; unsigned int options; dns_include_t *inc; + bool is_dynamic = false; INSIST(LOCKED_ZONE(zone)); if (inline_raw(zone)) { @@ -4646,7 +4653,9 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, * journal file if it isn't, as we wouldn't be able to apply * updates otherwise. */ - if (zone->journal != NULL && dns_zone_isdynamic(zone, true) && + is_dynamic = dns_zone_isdynamic(zone, true) || + dns_zone_getkasp(zone) != NULL; + if (zone->journal != NULL && is_dynamic && ! DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS)) { uint32_t jserial; @@ -4818,7 +4827,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, if (zone->type == dns_zone_master && (zone->update_acl != NULL || zone->ssutable != NULL) && - zone->sigresigninginterval < (3 * refresh) && + dns_zone_getsigresigninginterval(zone) < (3 * refresh) && dns_db_issecure(db)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, @@ -4950,10 +4959,11 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, resume_addnsec3chain(zone); } + is_dynamic = dns_zone_isdynamic(zone, false) || + dns_zone_getkasp(zone) != NULL; if (zone->type == dns_zone_master && !DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_NORESIGN) && - dns_zone_isdynamic(zone, false) && - dns_db_issecure(db)) + is_dynamic && dns_db_issecure(db)) { dns_name_t *name; dns_fixedname_t fixed; @@ -4976,7 +4986,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, "next resign: %s/%s " "in %d seconds", namebuf, typebuf, next.resign - timenow - - zone->sigresigninginterval); + dns_zone_getsigresigninginterval(zone)); dns_rdataset_disassociate(&next); } else { dnssec_log(zone, ISC_LOG_WARNING, @@ -5013,7 +5023,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, zone->nincludes++; } - if (! dns_db_ispersistent(db)) { + if (!dns_db_ispersistent(db)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO, "loaded serial %u%s", serial, dns_db_issecure(db) ? " (DNSSEC signed)" : ""); @@ -5069,7 +5079,7 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, } else if (zone->type == dns_zone_master || zone->type == dns_zone_redirect) { - if (! (inline_secure(zone) && result == ISC_R_FILENOTFOUND)) { + if (!(inline_secure(zone) && result == ISC_R_FILENOTFOUND)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "not loaded due to errors."); @@ -6260,22 +6270,41 @@ delsig_ok(dns_rdata_rrsig_t *rrsig_ptr, dst_key_t **keys, unsigned int nkeys, bool *warn) { unsigned int i = 0; + isc_result_t ret; bool have_ksk = false, have_zsk = false; bool have_pksk = false, have_pzsk = false; for (i = 0; i < nkeys; i++) { - if (rrsig_ptr->algorithm != dst_key_alg(keys[i])) + bool ksk, zsk; + + if (have_pksk && have_ksk && have_pzsk && have_zsk) { + break; + } + + if (rrsig_ptr->algorithm != dst_key_alg(keys[i])) { continue; - if (dst_key_isprivate(keys[i])) { - if (KSK(keys[i])) - have_ksk = have_pksk = true; - else - have_zsk = have_pzsk = true; - } else { - if (KSK(keys[i])) - have_ksk = true; - else - have_zsk = true; + } + + ret = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = KSK(keys[i]); + } + ret = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk); + if (ret != ISC_R_SUCCESS) { + zsk = !KSK(keys[i]); + } + + if (ksk) { + have_ksk = true; + if (dst_key_isprivate(keys[i])) { + have_pksk = true; + } + } + if (zsk) { + have_zsk = true; + if (dst_key_isprivate(keys[i])) { + have_pzsk = true; + } } } @@ -6494,6 +6523,7 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, { isc_result_t result; dns_dbnode_t *node = NULL; + dns_kasp_t *kasp = dns_zone_getkasp(zone); dns_stats_t* dnssecsignstats; dns_stats_t* dnssecrefreshstats; dns_rdataset_t rdataset; @@ -6502,6 +6532,11 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, isc_buffer_t buffer; unsigned int i, j; + if (kasp != NULL) { + check_ksk = false; + keyset_kskonly = true; + } + dns_rdataset_init(&rdataset); isc_buffer_init(&buffer, data, sizeof(data)); @@ -6575,7 +6610,54 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, } } - if (both) { + if (kasp != NULL) { + /* + * A dnssec-policy is found. Check what RRsets this + * key should sign. + */ + isc_result_t kresult; + bool ksk = false; + bool zsk = false; + + kresult = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk); + if (kresult != ISC_R_SUCCESS) { + if (KSK(keys[i])) { + ksk = true; + } + } + kresult = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk); + if (kresult != ISC_R_SUCCESS) { + if (!KSK(keys[i])) { + zsk = true; + } + } + + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || + type == dns_rdatatype_cds) + { + /* + * DNSKEY RRset is signed with KSK. + * CDS and CDNSKEY RRsets too (RFC 7344, 4.1). + */ + if (!ksk) { + continue; + } + } else if (!zsk) { + /* + * Other RRsets are signed with ZSK. + */ + continue; + } + + /* + * If this key is revoked, it may only sign the + * DNSKEY RRset. + */ + if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) { + continue; + } + } else if (both) { /* * CDS and CDNSKEY are signed with KSK (RFC 7344, 4.1). */ @@ -6688,7 +6770,7 @@ zone_resigninc(dns_zone_t *zone) { goto failure; } - sigvalidityinterval = zone->sigvalidityinterval; + sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); inception = now - 3600; /* Allow for clock skew. */ soaexpire = now + sigvalidityinterval; expiryinterval = dns_zone_getsigresigninginterval(zone); @@ -6735,7 +6817,8 @@ zone_resigninc(dns_zone_t *zone) { i = 0; while (result == ISC_R_SUCCESS) { - resign = rdataset.resign - zone->sigresigninginterval; + resign = rdataset.resign - + dns_zone_getsigresigninginterval(zone); covers = rdataset.covers; dns_rdataset_disassociate(&rdataset); @@ -7061,7 +7144,7 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, dns_dbnode_t *node, dns_dbversion_t *version, bool build_nsec3, bool build_nsec, dst_key_t *key, isc_stdtime_t inception, isc_stdtime_t expire, - unsigned int minimum, bool is_ksk, + unsigned int minimum, bool is_ksk, bool is_zsk, bool keyset_kskonly, bool is_bottom_of_zone, dns_diff_t *diff, int32_t *signatures, isc_mem_t *mctx) { @@ -7152,7 +7235,7 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, if (!is_ksk && keyset_kskonly) { goto next_rdataset; } - } else if (is_ksk) { + } else if (!is_zsk) { goto next_rdataset; } if (seen_ns && !seen_soa && @@ -7164,6 +7247,7 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, if (signed_with_good_key(zone, db, node, version, rdataset.type, key)) { goto next_rdataset; } + /* Calculate the signature, creating a RRSIG RDATA. */ isc_buffer_clear(&buffer); CHECK(dns_dnssec_sign(name, &rdataset, key, &inception, @@ -8753,13 +8837,14 @@ zone_sign(dns_zone_t *zone) { dns__zonediff_t zonediff; dns_fixedname_t fixed; dns_fixedname_t nextfixed; + dns_kasp_t *kasp; dns_name_t *name, *nextname; dns_rdataset_t rdataset; dns_signing_t *signing, *nextsigning; dns_signinglist_t cleanup; dst_key_t *zone_keys[DNS_MAXZONEKEYS]; int32_t signatures; - bool check_ksk, keyset_kskonly, is_ksk; + bool check_ksk, keyset_kskonly, is_ksk, is_zsk; bool with_ksk, with_zsk; bool commit = false; bool is_bottom_of_zone; @@ -8820,6 +8905,8 @@ zone_sign(dns_zone_t *zone) { goto cleanup; } + kasp = dns_zone_getkasp(zone); + sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); inception = now - 3600; /* Allow for clock skew. */ soaexpire = now + sigvalidityinterval; @@ -8856,8 +8943,10 @@ zone_sign(dns_zone_t *zone) { signing = ISC_LIST_HEAD(zone->signing); first = true; - check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); - keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); + check_ksk = (kasp != NULL) ? false : + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); + keyset_kskonly = (kasp != NULL) ? true : + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); /* Determine which type of chain to build */ CHECK(dns_private_chains(db, version, zone->privatetype, @@ -8875,7 +8964,7 @@ zone_sign(dns_zone_t *zone) { ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (signing->done || signing->db != zone->db) { /* - * The zone has been reloaded. We will have + * The zone has been reloaded. We will have to * created new signings as part of the reload * process so we can destroy this one. */ @@ -8903,8 +8992,15 @@ zone_sign(dns_zone_t *zone) { if (ALG(zone_keys[i]) == signing->algorithm && dst_key_id(zone_keys[i]) == signing->keyid) { - if (KSK(zone_keys[i])) + bool ksk = false; + isc_result_t ret = dst_key_getbool( + zone_keys[i], DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = KSK(zone_keys[i]); + } + if (ksk) { dst_key_free(&zone_keys[i]); + } continue; } zone_keys[j] = zone_keys[i]; @@ -9035,10 +9131,39 @@ zone_sign(dns_zone_t *zone) { } } } - if (both || REVOKE(zone_keys[i])) { + if (kasp != NULL) { + /* + * A dnssec-policy is found. Check what + * RRsets this key can sign. + */ + isc_result_t kresult; + is_ksk = false; + kresult = dst_key_getbool(zone_keys[i], + DST_BOOL_KSK, + &is_ksk); + if (kresult != ISC_R_SUCCESS) { + if (KSK(zone_keys[i])) { + is_ksk = true; + } + } + + is_zsk = false; + kresult = dst_key_getbool(zone_keys[i], + DST_BOOL_ZSK, + &is_zsk); + if (kresult != ISC_R_SUCCESS) { + if (!KSK(zone_keys[i])) { + is_zsk = true; + } + } + /* Treat as if we have both KSK and ZSK. */ + both = true; + } else if (both || REVOKE(zone_keys[i])) { is_ksk = KSK(zone_keys[i]); + is_zsk = !KSK(zone_keys[i]); } else { is_ksk = false; + is_zsk = true; } /* @@ -9046,7 +9171,7 @@ zone_sign(dns_zone_t *zone) { * the RRset is still signed at least once by a * KSK and a ZSK. */ - if (signing->deleteit && !is_ksk && with_zsk) { + if (signing->deleteit && is_zsk && with_zsk) { continue; } @@ -9057,7 +9182,7 @@ zone_sign(dns_zone_t *zone) { CHECK(sign_a_node(db, zone, name, node, version, build_nsec3, build_nsec, zone_keys[i], inception, expire, - zone->minimum, is_ksk, + zone->minimum, is_ksk, is_zsk, (both && keyset_kskonly), is_bottom_of_zone, zonediff.diff, &signatures, zone->mctx)); @@ -9068,10 +9193,10 @@ zone_sign(dns_zone_t *zone) { if (!signing->deleteit) { break; } - if (!is_ksk) { + if (is_zsk) { with_zsk = true; } - if (KSK(zone_keys[i])) { + if (is_ksk) { with_ksk = true; } } @@ -18473,6 +18598,7 @@ zone_rekey(dns_zone_t *zone) { dns_dnsseckeylist_t dnskeys, keys, rmkeys; dns_dnsseckey_t *key = NULL; dns_diff_t diff, _sig_diff; + dns_kasp_t *kasp; dns__zonediff_t zonediff; bool commit = false, newactive = false; bool newalg = false; @@ -18480,7 +18606,7 @@ zone_rekey(dns_zone_t *zone) { dns_ttl_t ttl = 3600; const char *dir = NULL; isc_mem_t *mctx = NULL; - isc_stdtime_t now; + isc_stdtime_t now, nexttime = 0; isc_time_t timenow; isc_interval_t ival; char timebuf[80]; @@ -18549,13 +18675,29 @@ zone_rekey(dns_zone_t *zone) { * True when called from "rndc sign". Indicates the zone should be * fully signed now. */ + kasp = dns_zone_getkasp(zone); fullsign = DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_FULLSIGN); result = dns_dnssec_findmatchingkeys(&zone->origin, dir, now, mctx, &keys); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_DEBUG(1), + "zone_rekey:dns_dnssec_findmatchingkeys failed: %s", + isc_result_totext(result)); + } + + if (kasp && (result == ISC_R_SUCCESS || result == ISC_R_NOTFOUND)) { + result = dns_keymgr_run(&zone->origin, zone->rdclass, dir, + mctx, &keys, kasp, now, &nexttime); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_rekey:dns_dnssec_keymgr failed: %s", + isc_result_totext(result)); + goto failure; + } + } + if (result == ISC_R_SUCCESS) { - bool check_ksk; - check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); result = dns_dnssec_updatekeys(&dnskeys, &keys, &rmkeys, &zone->origin, ttl, &diff, mctx, dnssec_report); @@ -18609,7 +18751,7 @@ zone_rekey(dns_zone_t *zone) { /* * This isn't a new algorithm; clear * first_sign so we won't sign the - * whole zone with this key later + * whole zone with this key later. */ key->first_sign = false; } else { @@ -18776,12 +18918,30 @@ zone_rekey(dns_zone_t *zone) { isc_time_settoepoch(&zone->refreshkeytime); + /* + * If keymgr provided a next time, use the calculated next rekey time. + */ + if (kasp != NULL && nexttime > 0) { + isc_time_t timenext; + + LOCK_ZONE(zone); + DNS_ZONE_TIME_ADD(&timenow, nexttime - now, &timenext); + zone->refreshkeytime = timenext; + UNLOCK_ZONE(zone); + + zone_settimer(zone, &timenow); + isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); + dnssec_log(zone, ISC_LOG_DEBUG(3), + "next key event in %u seconds: %s", + (nexttime - now), timebuf); + dnssec_log(zone, ISC_LOG_INFO, "next key event: %s", timebuf); + } /* * If we're doing key maintenance, set the key refresh timer to * the next scheduled key event or to 'dnssec-loadkeys-interval' * seconds in the future, whichever is sooner. */ - if (DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN)) { + else if (DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN)) { isc_time_t timethen; isc_stdtime_t then; @@ -19967,7 +20127,8 @@ dns_zone_setserial(dns_zone_t *zone, uint32_t serial) { LOCK_ZONE(zone); if (!inline_secure(zone)) { - if (!dns_zone_isdynamic(zone, true)) { + if (!dns_zone_isdynamic(zone, true) && + dns_zone_getkasp(zone) == NULL) { result = DNS_R_NOTDYNAMIC; goto failure; }