From c67379fb92b43229d9842a4df43eee401c31f5d0 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 2 Sep 2019 10:13:24 +0200 Subject: [PATCH 01/43] Change indentation in doc/arm/dnssec.xml This commit does not change anything significant, it just makes the file more readable in preparation for upcoming changes related to the `dnssec-policy` configuration option. --- doc/arm/dnssec.xml | 419 ++++++++++++++++++++++++++------------------- 1 file changed, 239 insertions(+), 180 deletions(-) diff --git a/doc/arm/dnssec.xml b/doc/arm/dnssec.xml index de922dcb5a..6210a12a7f 100644 --- a/doc/arm/dnssec.xml +++ b/doc/arm/dnssec.xml @@ -15,30 +15,36 @@
Converting from insecure to secure
- Changing a zone from insecure to secure can be done in two - ways: using a dynamic DNS update, or the - auto-dnssec zone option. - For either method, you need to configure - named so that it can see the - K* files which contain the public and private - parts of the keys that will be used to sign the zone. These files - will have been generated by - dnssec-keygen. You can do this by placing them - in the key-directory, as specified in - named.conf: - + + Changing a zone from insecure to secure can be done in two + ways: using a dynamic DNS update, or the + auto-dnssec zone option. + + + For either method, you need to configure + named so that it can see the + K* files which contain the public and private + parts of the keys that will be used to sign the zone. These files + will have been generated by + dnssec-keygen. You can do this by placing them + in the key-directory, as specified in + named.conf: + zone example.net { type master; update-policy local; file "dynamic/example.net/example.net"; key-directory "dynamic/example.net"; }; - - If one KSK and one ZSK DNSKEY key have been generated, this - configuration will cause all records in the zone to be signed - with the ZSK, and the DNSKEY RRset to be signed with the KSK as - well. An NSEC chain will be generated as part of the initial - signing process. + + + If one KSK and one ZSK DNSKEY key have been generated, this + configuration will cause all records in the zone to be signed + with the ZSK, and the DNSKEY RRset to be signed with the KSK as + well. An NSEC chain will be generated as part of the initial + signing process. + +
Dynamic DNS update method
@@ -50,16 +56,20 @@ > update add example.net DNSKEY 257 3 7 AwEAAd/7odU/64o2LGsifbLtQmtO8dFDtTAZXSX2+X3e/UNlq9IHq3Y0 XtC0Iuawl/qkaKVxXe2lo8Ct+dM6UehyCqk= > send - While the update request will complete almost immediately, - the zone will not be completely signed until - named has had time to walk the zone and - generate the NSEC and RRSIG records. The NSEC record at the apex - will be added last, to signal that there is a complete NSEC - chain. - If you wish to sign using NSEC3 instead of NSEC, you should - add an NSEC3PARAM record to the initial update request. If you - wish the NSEC3 chain to have the OPTOUT bit set, set it in the - flags field of the NSEC3PARAM record. + + While the update request will complete almost immediately, + the zone will not be completely signed until + named has had time to walk the zone and + generate the NSEC and RRSIG records. The NSEC record at the apex + will be added last, to signal that there is a complete NSEC + chain. + + + If you wish to sign using NSEC3 instead of NSEC, you should + add an NSEC3PARAM record to the initial update request. If you + wish the NSEC3 chain to have the OPTOUT bit set, set it in the + flags field of the NSEC3PARAM record. + % nsupdate > ttl 3600 @@ -68,90 +78,105 @@ > update add example.net NSEC3PARAM 1 1 100 1234567890 > send - Again, this update request will complete almost - immediately; however, the record won't show up until - named has had a chance to build/remove the - relevant chain. A private type record will be created to record - the state of the operation (see below for more details), and will - be removed once the operation completes. - While the initial signing and NSEC/NSEC3 chain generation - is happening, other updates are possible as well. + + Again, this update request will complete almost + immediately; however, the record won't show up until + named has had a chance to build/remove the + relevant chain. A private type record will be created to record + the state of the operation (see below for more details), and will + be removed once the operation completes. + + + While the initial signing and NSEC/NSEC3 chain generation + is happening, other updates are possible as well. + +
Fully automatic zone signing
- To enable automatic signing, add the - auto-dnssec option to the zone statement in - named.conf. - auto-dnssec has two possible arguments: - allow or - maintain. - With - auto-dnssec allow, - named can search the key directory for keys - matching the zone, insert them into the zone, and use them to - sign the zone. It will do so only when it receives an - rndc sign <zonename>. - - auto-dnssec maintain includes the above - functionality, but will also automatically adjust the zone's - DNSKEY records on schedule according to the keys' timing metadata. - (See and - for more information.) + To enable automatic signing, add the + auto-dnssec option to the zone statement in + named.conf. + auto-dnssec has two possible arguments: + allow or + maintain. - named will periodically search the key directory - for keys matching the zone, and if the keys' metadata indicates - that any change should be made the zone, such as adding, removing, - or revoking a key, then that action will be carried out. By default, - the key directory is checked for changes every 60 minutes; this period - can be adjusted with the , up - to a maximum of 24 hours. The rndc loadkeys forces - named to check for key updates immediately. + With auto-dnssec allow, + named can search the key directory for keys + matching the zone, insert them into the zone, and use them to + sign the zone. It will do so only when it receives an + rndc sign <zonename>. - If keys are present in the key directory the first time the zone - is loaded, the zone will be signed immediately, without waiting for an - rndc sign or rndc loadkeys - command. (Those commands can still be used when there are unscheduled - key changes, however.) + + auto-dnssec maintain includes the above + functionality, but will also automatically adjust the zone's + DNSKEY records on schedule according to the keys' timing metadata. + (See and + for more information.) - When new keys are added to a zone, the TTL is set to match that - of any existing DNSKEY RRset. If there is no existing DNSKEY RRset, - then the TTL will be set to the TTL specified when the key was - created (using the dnssec-keygen -L option), if - any, or to the SOA TTL. + named will periodically search the key directory + for keys matching the zone, and if the keys' metadata indicates + that any change should be made the zone, such as adding, removing, + or revoking a key, then that action will be carried out. By default, + the key directory is checked for changes every 60 minutes; this period + can be adjusted with the , up + to a maximum of 24 hours. The rndc loadkeys forces + named to check for key updates immediately. - If you wish the zone to be signed using NSEC3 instead of NSEC, - submit an NSEC3PARAM record via dynamic update prior to the - scheduled publication and activation of the keys. If you wish the - NSEC3 chain to have the OPTOUT bit set, set it in the flags field - of the NSEC3PARAM record. The NSEC3PARAM record will not appear in - the zone immediately, but it will be stored for later reference. When - the zone is signed and the NSEC3 chain is completed, the NSEC3PARAM - record will appear in the zone. + If keys are present in the key directory the first time the zone + is loaded, the zone will be signed immediately, without waiting for an + rndc sign or rndc loadkeys + command. (Those commands can still be used when there are unscheduled + key changes, however.) - Using the - auto-dnssec option requires the zone to be - configured to allow dynamic updates, by adding an - allow-update or - update-policy statement to the zone - configuration. If this has not been done, the configuration will - fail. + + When new keys are added to a zone, the TTL is set to match that + of any existing DNSKEY RRset. If there is no existing DNSKEY RRset, + then the TTL will be set to the TTL specified when the key was + created (using the dnssec-keygen -L option), if + any, or to the SOA TTL. + + + If you wish the zone to be signed using NSEC3 instead of NSEC, + submit an NSEC3PARAM record via dynamic update prior to the + scheduled publication and activation of the keys. If you wish the + NSEC3 chain to have the OPTOUT bit set, set it in the flags field + of the NSEC3PARAM record. The NSEC3PARAM record will not appear in + the zone immediately, but it will be stored for later reference. When + the zone is signed and the NSEC3 chain is completed, the NSEC3PARAM + record will appear in the zone. + + + Using the + auto-dnssec option requires the zone to be + configured to allow dynamic updates, by adding an + allow-update or + update-policy statement to the zone + configuration. If this has not been done, the configuration will + fail. + +
Private-type records
- The state of the signing process is signaled by - private-type records (with a default type value of 65534). When - signing is complete, these records will have a nonzero value for - the final octet (for those records which have a nonzero initial - octet). - The private type record format: If the first octet is - non-zero then the record indicates that the zone needs to be - signed with the key matching the record, or that all signatures - that match the record should be removed. + + The state of the signing process is signaled by + private-type records (with a default type value of 65534). When + signing is complete, these records will have a nonzero value for + the final octet (for those records which have a nonzero initial + octet). + + + The private type record format: If the first octet is + non-zero then the record indicates that the zone needs to be + signed with the key matching the record, or that all signatures + that match the record should be removed. + @@ -161,14 +186,18 @@ complete flag (octet 5) - Only records flagged as "complete" can be removed via - dynamic update. Attempts to remove other private type records - will be silently ignored. - If the first octet is zero (this is a reserved algorithm - number that should never appear in a DNSKEY record) then the - record indicates changes to the NSEC3 chains are in progress. The - rest of the record contains an NSEC3PARAM record. The flag field - tells what operation to perform based on the flag bits. + + Only records flagged as "complete" can be removed via + dynamic update. Attempts to remove other private type records + will be silently ignored. + + + If the first octet is zero (this is a reserved algorithm + number that should never appear in a DNSKEY record) then the + record indicates changes to the NSEC3 chains are in progress. The + rest of the record contains an NSEC3PARAM record. The flag field + tells what operation to perform based on the flag bits. + @@ -181,106 +210,136 @@
DNSKEY rollovers
- As with insecure-to-secure conversions, rolling DNSSEC - keys can be done in two ways: using a dynamic DNS update, or the - auto-dnssec zone option. + + As with insecure-to-secure conversions, rolling DNSSEC + keys can be done in two ways: using a dynamic DNS update, or the + auto-dnssec zone option. + +
Dynamic DNS update method
- To perform key rollovers via dynamic update, you need to add - the K* files for the new keys so that - named can find them. You can then add the new - DNSKEY RRs via dynamic update. - named will then cause the zone to be signed - with the new keys. When the signing is complete the private type - records will be updated so that the last octet is non - zero. - If this is for a KSK you need to inform the parent and any - trust anchor repositories of the new KSK. - You should then wait for the maximum TTL in the zone before - removing the old DNSKEY. If it is a KSK that is being updated, - you also need to wait for the DS RRset in the parent to be - updated and its TTL to expire. This ensures that all clients will - be able to verify at least one signature when you remove the old - DNSKEY. - The old DNSKEY can be removed via UPDATE. Take care to - specify the correct key. - named will clean out any signatures generated - by the old key after the update completes. + + To perform key rollovers via dynamic update, you need to add + the K* files for the new keys so that + named can find them. You can then add the new + DNSKEY RRs via dynamic update. + named will then cause the zone to be signed + with the new keys. When the signing is complete the private type + records will be updated so that the last octet is non + zero. + + + If this is for a KSK you need to inform the parent and any + trust anchor repositories of the new KSK. + + + You should then wait for the maximum TTL in the zone before + removing the old DNSKEY. If it is a KSK that is being updated, + you also need to wait for the DS RRset in the parent to be + updated and its TTL to expire. This ensures that all clients will + be able to verify at least one signature when you remove the old + DNSKEY. + + + The old DNSKEY can be removed via UPDATE. Take care to + specify the correct key. + named will clean out any signatures generated + by the old key after the update completes. + +
Automatic key rollovers
- When a new key reaches its activation date (as set by - dnssec-keygen or dnssec-settime), - if the auto-dnssec zone option is set to - maintain, named will - automatically carry out the key rollover. If the key's algorithm - has not previously been used to sign the zone, then the zone will - be fully signed as quickly as possible. However, if the new key - is replacing an existing key of the same algorithm, then the - zone will be re-signed incrementally, with signatures from the - old key being replaced with signatures from the new key as their - signature validity periods expire. By default, this rollover - completes in 30 days, after which it will be safe to remove the - old key from the DNSKEY RRset. + + When a new key reaches its activation date (as set by + dnssec-keygen or dnssec-settime), + if the auto-dnssec zone option is set to + maintain, named will + automatically carry out the key rollover. If the key's algorithm + has not previously been used to sign the zone, then the zone will + be fully signed as quickly as possible. However, if the new key + is replacing an existing key of the same algorithm, then the + zone will be re-signed incrementally, with signatures from the + old key being replaced with signatures from the new key as their + signature validity periods expire. By default, this rollover + completes in 30 days, after which it will be safe to remove the + old key from the DNSKEY RRset. + +
NSEC3PARAM rollovers via UPDATE
- Add the new NSEC3PARAM record via dynamic update. When the - new NSEC3 chain has been generated, the NSEC3PARAM flag field - will be zero. At this point you can remove the old NSEC3PARAM - record. The old chain will be removed after the update request - completes. + + Add the new NSEC3PARAM record via dynamic update. When the + new NSEC3 chain has been generated, the NSEC3PARAM flag field + will be zero. At this point you can remove the old NSEC3PARAM + record. The old chain will be removed after the update request + completes. + +
Converting from NSEC to NSEC3
- To do this, you just need to add an NSEC3PARAM record. When - the conversion is complete, the NSEC chain will have been removed - and the NSEC3PARAM record will have a zero flag field. The NSEC3 - chain will be generated before the NSEC chain is - destroyed. + + To do this, you just need to add an NSEC3PARAM record. When + the conversion is complete, the NSEC chain will have been removed + and the NSEC3PARAM record will have a zero flag field. The NSEC3 + chain will be generated before the NSEC chain is + destroyed. + +
Converting from NSEC3 to NSEC
- To do this, use nsupdate to - remove all NSEC3PARAM records with a zero flag - field. The NSEC chain will be generated before the NSEC3 chain is - removed. + + To do this, use nsupdate to + remove all NSEC3PARAM records with a zero flag + field. The NSEC chain will be generated before the NSEC3 chain is + removed. + +
Converting from secure to insecure
- To convert a signed zone to unsigned using dynamic DNS, - delete all the DNSKEY records from the zone apex using - nsupdate. All signatures, NSEC or NSEC3 chains, - and associated NSEC3PARAM records will be removed automatically. - This will take place after the update request completes. - This requires the - dnssec-secure-to-insecure option to be set to - yes in - named.conf. - In addition, if the auto-dnssec maintain - zone statement is used, it should be removed or changed to - allow instead (or it will re-sign). + + To convert a signed zone to unsigned using dynamic DNS, + delete all the DNSKEY records from the zone apex using + nsupdate. All signatures, NSEC or NSEC3 chains, + and associated NSEC3PARAM records will be removed automatically. + This will take place after the update request completes. + This requires the + dnssec-secure-to-insecure option to be set to + yes in + named.conf. + In addition, if the auto-dnssec maintain + zone statement is used, it should be removed or changed to + allow instead (or it will re-sign). +
Periodic re-signing
- In any secure zone which supports dynamic updates, named - will periodically re-sign RRsets which have not been re-signed as - a result of some update action. The signature lifetimes will be - adjusted so as to spread the re-sign load over time rather than - all at once. + + In any secure zone which supports dynamic updates, named + will periodically re-sign RRsets which have not been re-signed as + a result of some update action. The signature lifetimes will be + adjusted so as to spread the re-sign load over time rather than + all at once. + +
NSEC3 and OPTOUT
- named only supports creating new NSEC3 chains - where all the NSEC3 records in the zone have the same OPTOUT - state. - named supports UPDATES to zones where the NSEC3 - records in the chain have mixed OPTOUT state. - named does not support changing the OPTOUT - state of an individual NSEC3 record, the entire chain needs to be - changed if the OPTOUT state of an individual NSEC3 needs to be - changed. + named only supports creating new NSEC3 chains + where all the NSEC3 records in the zone have the same OPTOUT + state. + named supports UPDATES to zones where the NSEC3 + records in the chain have mixed OPTOUT state. + named does not support changing the OPTOUT + state of an individual NSEC3 record, the entire chain needs to be + changed if the OPTOUT state of an individual NSEC3 needs to be + changed. +
From b7c5bfb203ea547f552e37c4ae14663c92953764 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 2 Sep 2019 15:46:28 +0200 Subject: [PATCH 02/43] Extend ttlval to accept ISO 8601 durations The ttlval configuration types are replaced by duration configuration types. The duration is an ISO 8601 duration that is going to be used for DNSSEC key timings such as key lifetimes, signature resign intervals and refresh periods, etc. But it is also still allowed to use the BIND ttlval ways of configuring intervals (number plus optional unit). A duration is stored as an array of 7 different time parts. A duration can either be expressed in weeks, or in a combination of the other datetime indicators. Add several unit tests to ensure the correct value is parsed given different string values. --- bin/check/named-checkconf.c | 2 +- bin/named/named.conf.docbook | 72 +++--- bin/named/server.c | 52 +++-- bin/named/zoneconf.c | 4 +- doc/arm/master.zoneopt.xml | 2 +- doc/arm/options.grammar.xml | 36 +-- doc/arm/redirect.zoneopt.xml | 2 +- lib/bind9/check.c | 16 +- lib/isccfg/include/isccfg/cfg.h | 19 ++ lib/isccfg/include/isccfg/grammar.h | 31 +++ lib/isccfg/namedconf.c | 81 ++----- lib/isccfg/parser.c | 349 +++++++++++++++++++++++++++- lib/isccfg/tests/Kyuafile | 1 + lib/isccfg/tests/Makefile.in | 9 +- lib/isccfg/tests/duration_test.c | 207 +++++++++++++++++ lib/isccfg/win32/libisccfg.def | 5 + util/copyrights | 1 + 17 files changed, 739 insertions(+), 150 deletions(-) create mode 100644 lib/isccfg/tests/duration_test.c diff --git a/bin/check/named-checkconf.c b/bin/check/named-checkconf.c index a0dea04667..c8b12911bb 100644 --- a/bin/check/named-checkconf.c +++ b/bin/check/named-checkconf.c @@ -421,7 +421,7 @@ configure_zone(const char *vclass, const char *view, obj = NULL; if (get_maps(maps, "max-zone-ttl", &obj)) { - maxttl = cfg_obj_asuint32(obj); + maxttl = cfg_obj_asduration(obj); zone_options |= DNS_ZONEOPT_CHECKTTL; } diff --git a/bin/named/named.conf.docbook b/bin/named/named.conf.docbook index a013873e18..ecae8014cc 100644 --- a/bin/named/named.conf.docbook +++ b/bin/named/named.conf.docbook @@ -208,7 +208,7 @@ options { [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... } ] [ zone-directory quoted_string ] [ - in-memory boolean ] [ min-update-interval ttlval ]; ... }; + in-memory boolean ] [ min-update-interval duration ]; ... }; check-dup-records ( fail | warn | ignore ); check-integrity boolean; check-mx ( fail | warn | ignore ); @@ -290,18 +290,18 @@ options { fstrm-set-output-notify-threshold integer; fstrm-set-output-queue-model ( mpsc | spsc ); fstrm-set-output-queue-size integer; - fstrm-set-reopen-interval ttlval; + fstrm-set-reopen-interval duration; geoip-directory ( quoted_string | none ); glue-cache boolean; heartbeat-interval integer; hostname ( quoted_string | none ); inline-signing boolean; - interface-interval ttlval; + interface-interval duration; ixfr-from-differences ( primary | master | secondary | slave | boolean ); keep-response-order { address_match_element; ... }; key-directory quoted_string; - lame-ttl ttlval; + lame-ttl duration; listen-on [ port integer ] [ dscp integer ] { address_match_element; ... }; @@ -315,28 +315,28 @@ options { masterfile-style ( full | relative ); match-mapped-addresses boolean; max-cache-size ( default | unlimited | sizeval | percentage ); - max-cache-ttl ttlval; + max-cache-ttl duration; max-clients-per-query integer; max-journal-size ( default | unlimited | sizeval ); - max-ncache-ttl ttlval; + max-ncache-ttl duration; max-records integer; max-recursion-depth integer; max-recursion-queries integer; max-refresh-time integer; max-retry-time integer; max-rsa-exponent-size integer; - max-stale-ttl ttlval; + max-stale-ttl duration; max-transfer-idle-in integer; max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; max-udp-size integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); memstatistics boolean; memstatistics-file quoted_string; message-compression boolean; - min-cache-ttl ttlval; - min-ncache-ttl ttlval; + min-cache-ttl duration; + min-ncache-ttl duration; min-refresh-time integer; min-retry-time integer; minimal-any boolean; @@ -353,8 +353,8 @@ options { notify-source-v6 ( ipv6_address | * ) [ port ( integer | * ) ] [ dscp integer ]; notify-to-soa boolean; - nta-lifetime ttlval; - nta-recheck ttlval; + nta-lifetime duration; + nta-recheck duration; nxdomain-redirect string; pid-file ( quoted_string | none ); port integer; @@ -401,13 +401,13 @@ options { response-padding { address_match_element; ... } block-size integer; response-policy { zone string [ add-soa boolean ] [ log - boolean ] [ max-policy-ttl ttlval ] [ min-update-interval - ttlval ] [ policy ( cname | disabled | drop | given | no-op | + boolean ] [ max-policy-ttl duration ] [ min-update-interval + duration ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only quoted_string ) ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ]; ... } [ add-soa boolean ] [ - break-dnssec boolean ] [ max-policy-ttl ttlval ] [ - min-update-interval ttlval ] [ min-ns-dots integer ] [ + break-dnssec boolean ] [ max-policy-ttl duration ] [ + min-update-interval duration ] [ min-ns-dots integer ] [ nsip-wait-recurse boolean ] [ qname-wait-recurse boolean ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ] [ dnsrps-enable boolean ] [ @@ -421,7 +421,7 @@ options { serial-query-rate integer; serial-update-method ( date | increment | unixtime ); server-id ( quoted_string | none | hostname ); - servfail-ttl ttlval; + servfail-ttl duration; session-keyalg string; session-keyfile ( quoted_string | none ); session-keyname string; @@ -432,7 +432,7 @@ options { sortlist { address_match_element; ... }; stacksize ( default | unlimited | sizeval ); stale-answer-enable boolean; - stale-answer-ttl ttlval; + stale-answer-ttl duration; startup-notify-rate integer; statistics-file quoted_string; synth-from-dnssec boolean; @@ -564,7 +564,7 @@ view string [ class ] { [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... } ] [ zone-directory quoted_string ] [ - in-memory boolean ] [ min-update-interval ttlval ]; ... }; + in-memory boolean ] [ min-update-interval duration ]; ... }; check-dup-records ( fail | warn | ignore ); check-integrity boolean; check-mx ( fail | warn | ignore ); @@ -642,7 +642,7 @@ view string [ class ] { secret string; }; key-directory quoted_string; - lame-ttl ttlval; + lame-ttl duration; lmdb-mapsize sizeval; managed-keys { string ( static-key | initial-key @@ -655,25 +655,25 @@ view string [ class ] { match-destinations { address_match_element; ... }; match-recursive-only boolean; max-cache-size ( default | unlimited | sizeval | percentage ); - max-cache-ttl ttlval; + max-cache-ttl duration; max-clients-per-query integer; max-journal-size ( default | unlimited | sizeval ); - max-ncache-ttl ttlval; + max-ncache-ttl duration; max-records integer; max-recursion-depth integer; max-recursion-queries integer; max-refresh-time integer; max-retry-time integer; - max-stale-ttl ttlval; + max-stale-ttl duration; max-transfer-idle-in integer; max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; max-udp-size integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); message-compression boolean; - min-cache-ttl ttlval; - min-ncache-ttl ttlval; + min-cache-ttl duration; + min-ncache-ttl duration; min-refresh-time integer; min-retry-time integer; minimal-any boolean; @@ -689,8 +689,8 @@ view string [ class ] { notify-source-v6 ( ipv6_address | * ) [ port ( integer | * ) ] [ dscp integer ]; notify-to-soa boolean; - nta-lifetime ttlval; - nta-recheck ttlval; + nta-lifetime duration; + nta-recheck duration; nxdomain-redirect string; plugin ( query ) string [ { unspecified-text } ]; @@ -732,13 +732,13 @@ view string [ class ] { response-padding { address_match_element; ... } block-size integer; response-policy { zone string [ add-soa boolean ] [ log - boolean ] [ max-policy-ttl ttlval ] [ min-update-interval - ttlval ] [ policy ( cname | disabled | drop | given | no-op | + boolean ] [ max-policy-ttl duration ] [ min-update-interval + duration ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only quoted_string ) ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ]; ... } [ add-soa boolean ] [ - break-dnssec boolean ] [ max-policy-ttl ttlval ] [ - min-update-interval ttlval ] [ min-ns-dots integer ] [ + break-dnssec boolean ] [ max-policy-ttl duration ] [ + min-update-interval duration ] [ min-ns-dots integer ] [ nsip-wait-recurse boolean ] [ qname-wait-recurse boolean ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ] [ dnsrps-enable boolean ] [ @@ -783,14 +783,14 @@ view string [ class ] { integer | * ) ] [ dscp integer ]; transfers integer; }; - servfail-ttl ttlval; + servfail-ttl duration; sig-signing-nodes integer; sig-signing-signatures integer; sig-signing-type integer; sig-validity-interval integer [ integer ]; sortlist { address_match_element; ... }; stale-answer-enable boolean; - stale-answer-ttl ttlval; + stale-answer-ttl duration; synth-from-dnssec boolean; transfer-format ( many-answers | one-answer ); transfer-source ( ipv4_address | * ) [ port ( integer | * ) ] [ @@ -867,7 +867,7 @@ view string [ class ] { max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); min-refresh-time integer; min-retry-time integer; multi-master boolean; @@ -967,7 +967,7 @@ zone string [ class ] { max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); min-refresh-time integer; min-retry-time integer; multi-master boolean; diff --git a/bin/named/server.c b/bin/named/server.c index e7f87e349e..8730225864 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -2039,7 +2039,13 @@ conf_dnsrps_num(const cfg_obj_t *obj, const char *name, return; } - conf_dnsrps_sadd(ctx, " %s %d", name, cfg_obj_asuint32(sub_obj)); + if (cfg_obj_isduration(sub_obj)) { + conf_dnsrps_sadd(ctx, " %s %d", name, + cfg_obj_asduration(sub_obj)); + } else { + conf_dnsrps_sadd(ctx, " %s %d", name, + cfg_obj_asuint32(sub_obj)); + } } /* @@ -2221,15 +2227,15 @@ configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element, } obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); - if (cfg_obj_isuint32(obj)) { - zone->max_policy_ttl = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + zone->max_policy_ttl = cfg_obj_asduration(obj); } else { zone->max_policy_ttl = ttl_default; } obj = cfg_tuple_get(rpz_obj, "min-update-interval"); - if (cfg_obj_isuint32(obj)) { - zone->min_update_interval = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + zone->min_update_interval = cfg_obj_asduration(obj); } else { zone->min_update_interval = minupdateinterval_default; } @@ -2448,14 +2454,14 @@ configure_rpz(dns_view_t *view, const cfg_obj_t **maps, } sub_obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); - if (cfg_obj_isuint32(sub_obj)) - ttl_default = cfg_obj_asuint32(sub_obj); + if (cfg_obj_isduration(sub_obj)) + ttl_default = cfg_obj_asduration(sub_obj); else ttl_default = DNS_RPZ_MAX_TTL_DEFAULT; sub_obj = cfg_tuple_get(rpz_obj, "min-update-interval"); - if (cfg_obj_isuint32(sub_obj)) - minupdateinterval_default = cfg_obj_asuint32(sub_obj); + if (cfg_obj_isduration(sub_obj)) + minupdateinterval_default = cfg_obj_asduration(sub_obj); else minupdateinterval_default = DNS_RPZ_MINUPDATEINTERVAL_DEFAULT; @@ -2992,8 +2998,8 @@ configure_catz_zone(dns_view_t *view, const cfg_obj_t *config, } obj = cfg_tuple_get(catz_obj, "min-update-interval"); - if (obj != NULL && cfg_obj_isuint32(obj)) - opts->min_update_interval = cfg_obj_asuint32(obj); + if (obj != NULL && cfg_obj_isduration(obj)) + opts->min_update_interval = cfg_obj_asduration(obj); cleanup: if (pview != NULL) @@ -3641,7 +3647,7 @@ configure_dnstap(const cfg_obj_t **maps, dns_view_t *view) { result = named_config_get(maps, "fstrm-set-reopen-interval", &obj); if (result == ISC_R_SUCCESS) { - i = cfg_obj_asuint32(obj); + i = cfg_obj_asduration(obj); fstrm_iothr_options_set_reopen_interval(fopt, i); } @@ -4217,22 +4223,22 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "max-cache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->maxcachettl = cfg_obj_asuint32(obj); + view->maxcachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "max-ncache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->maxncachettl = cfg_obj_asuint32(obj); + view->maxncachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "min-cache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->mincachettl = cfg_obj_asuint32(obj); + view->mincachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "min-ncache-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->minncachettl = cfg_obj_asuint32(obj); + view->minncachettl = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "synth-from-dnssec", &obj); @@ -4242,7 +4248,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "max-stale-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - max_stale_ttl = ISC_MAX(cfg_obj_asuint32(obj), 1); + max_stale_ttl = ISC_MAX(cfg_obj_asduration(obj), 1); obj = NULL; result = named_config_get(maps, "stale-answer-enable", &obj); @@ -4392,7 +4398,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "stale-answer-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - view->staleanswerttl = ISC_MAX(cfg_obj_asuint32(obj), 1); + view->staleanswerttl = ISC_MAX(cfg_obj_asduration(obj), 1); /* * Resolver. @@ -4512,7 +4518,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "lame-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - lame_ttl = cfg_obj_asuint32(obj); + lame_ttl = cfg_obj_asduration(obj); if (lame_ttl > 1800) lame_ttl = 1800; dns_resolver_setlamettl(view->resolver, lame_ttl); @@ -5216,12 +5222,12 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "nta-recheck", &obj); INSIST(result == ISC_R_SUCCESS); - view->nta_recheck = cfg_obj_asuint32(obj); + view->nta_recheck = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "nta-lifetime", &obj); INSIST(result == ISC_R_SUCCESS); - view->nta_lifetime = cfg_obj_asuint32(obj); + view->nta_lifetime = cfg_obj_asduration(obj); obj = NULL; result = named_config_get(maps, "preferred-glue", &obj); @@ -5464,7 +5470,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, obj = NULL; result = named_config_get(maps, "servfail-ttl", &obj); INSIST(result == ISC_R_SUCCESS); - fail_ttl = cfg_obj_asuint32(obj); + fail_ttl = cfg_obj_asduration(obj); if (fail_ttl > 30) fail_ttl = 30; dns_view_setfailttl(view, fail_ttl); @@ -8560,7 +8566,7 @@ load_configuration(const char *filename, named_server_t *server, obj = NULL; result = named_config_get(maps, "interface-interval", &obj); INSIST(result == ISC_R_SUCCESS); - interface_interval = cfg_obj_asuint32(obj) * 60; + interface_interval = cfg_obj_asduration(obj); if (interface_interval == 0) { CHECK(isc_timer_reset(server->interface_timer, isc_timertype_inactive, diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index aebea7aa1a..70b3bdba77 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -1045,8 +1045,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, } else if (result == ISC_R_SUCCESS) { dns_ttl_t maxttl = 0; /* unlimited */ - if (cfg_obj_isuint32(obj)) - maxttl = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) + maxttl = cfg_obj_asduration(obj); dns_zone_setmaxttl(zone, maxttl); if (raw != NULL) dns_zone_setmaxttl(raw, maxttl); diff --git a/doc/arm/master.zoneopt.xml b/doc/arm/master.zoneopt.xml index d612445880..0ed86ecc75 100644 --- a/doc/arm/master.zoneopt.xml +++ b/doc/arm/master.zoneopt.xml @@ -51,7 +51,7 @@ max-records integer; max-transfer-idle-out integer; max-transfer-time-out integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); notify ( explicit | master-only | boolean ); notify-delay integer; notify-source ( ipv4_address | * ) [ port ( integer | * ) ] [ dscp integer ]; diff --git a/doc/arm/options.grammar.xml b/doc/arm/options.grammar.xml index 3cd76e6d3d..64a95defb4 100644 --- a/doc/arm/options.grammar.xml +++ b/doc/arm/options.grammar.xml @@ -45,7 +45,7 @@ [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... } ] [ zone-directory quoted_string ] [ - in-memory boolean ] [ min-update-interval ttlval ]; ... }; + in-memory boolean ] [ min-update-interval duration ]; ... }; check-dup-records ( fail | warn | ignore ); check-integrity boolean; check-mx ( fail | warn | ignore ); @@ -127,18 +127,18 @@ fstrm-set-output-notify-threshold integer; fstrm-set-output-queue-model ( mpsc | spsc ); fstrm-set-output-queue-size integer; - fstrm-set-reopen-interval ttlval; + fstrm-set-reopen-interval duration; geoip-directory ( quoted_string | none ); glue-cache boolean; heartbeat-interval integer; hostname ( quoted_string | none ); inline-signing boolean; - interface-interval ttlval; + interface-interval duration; ixfr-from-differences ( primary | master | secondary | slave | boolean ); keep-response-order { address_match_element; ... }; key-directory quoted_string; - lame-ttl ttlval; + lame-ttl duration; listen-on [ port integer ] [ dscp integer ] { address_match_element; ... }; @@ -152,28 +152,28 @@ masterfile-style ( full | relative ); match-mapped-addresses boolean; max-cache-size ( default | unlimited | sizeval | percentage ); - max-cache-ttl ttlval; + max-cache-ttl duration; max-clients-per-query integer; max-journal-size ( default | unlimited | sizeval ); - max-ncache-ttl ttlval; + max-ncache-ttl duration; max-records integer; max-recursion-depth integer; max-recursion-queries integer; max-refresh-time integer; max-retry-time integer; max-rsa-exponent-size integer; - max-stale-ttl ttlval; + max-stale-ttl duration; max-transfer-idle-in integer; max-transfer-idle-out integer; max-transfer-time-in integer; max-transfer-time-out integer; max-udp-size integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); memstatistics boolean; memstatistics-file quoted_string; message-compression boolean; - min-cache-ttl ttlval; - min-ncache-ttl ttlval; + min-cache-ttl duration; + min-ncache-ttl duration; min-refresh-time integer; min-retry-time integer; minimal-any boolean; @@ -190,8 +190,8 @@ notify-source-v6 ( ipv6_address | * ) [ port ( integer | * ) ] [ dscp integer ]; notify-to-soa boolean; - nta-lifetime ttlval; - nta-recheck ttlval; + nta-lifetime duration; + nta-recheck duration; nxdomain-redirect string; pid-file ( quoted_string | none ); port integer; @@ -238,13 +238,13 @@ response-padding { address_match_element; ... } block-size integer; response-policy { zone string [ add-soa boolean ] [ log - boolean ] [ max-policy-ttl ttlval ] [ min-update-interval - ttlval ] [ policy ( cname | disabled | drop | given | no-op | + boolean ] [ max-policy-ttl duration ] [ min-update-interval + duration ] [ policy ( cname | disabled | drop | given | no-op | nodata | nxdomain | passthru | tcp-only quoted_string ) ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ]; ... } [ add-soa boolean ] [ - break-dnssec boolean ] [ max-policy-ttl ttlval ] [ - min-update-interval ttlval ] [ min-ns-dots integer ] [ + break-dnssec boolean ] [ max-policy-ttl duration ] [ + min-update-interval duration ] [ min-ns-dots integer ] [ nsip-wait-recurse boolean ] [ qname-wait-recurse boolean ] [ recursive-only boolean ] [ nsip-enable boolean ] [ nsdname-enable boolean ] [ dnsrps-enable boolean ] [ @@ -258,7 +258,7 @@ serial-query-rate integer; serial-update-method ( date | increment | unixtime ); server-id ( quoted_string | none | hostname ); - servfail-ttl ttlval; + servfail-ttl duration; session-keyalg string; session-keyfile ( quoted_string | none ); session-keyname string; @@ -269,7 +269,7 @@ sortlist { address_match_element; ... }; stacksize ( default | unlimited | sizeval ); stale-answer-enable boolean; - stale-answer-ttl ttlval; + stale-answer-ttl duration; startup-notify-rate integer; statistics-file quoted_string; synth-from-dnssec boolean; diff --git a/doc/arm/redirect.zoneopt.xml b/doc/arm/redirect.zoneopt.xml index 335cfa0387..91622cc2c1 100644 --- a/doc/arm/redirect.zoneopt.xml +++ b/doc/arm/redirect.zoneopt.xml @@ -21,7 +21,7 @@ masterfile-style ( full | relative ); masters [ port integer ] [ dscp integer ] { ( masters | ipv4_address [ port integer ] | ipv6_address [ port integer ] ) [ key string ]; ... }; max-records integer; - max-zone-ttl ( unlimited | ttlval ); + max-zone-ttl ( unlimited | duration ); zone-statistics ( full | terse | none | boolean ); }; diff --git a/lib/bind9/check.c b/lib/bind9/check.c index 1426c41867..86d7482157 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -929,7 +929,11 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, (void)cfg_map_get(options, intervals[i].name, &obj); if (obj == NULL) continue; - val = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + val = cfg_obj_asduration(obj); + } else { + val = cfg_obj_asuint32(obj); + } if (val > intervals[i].max) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s '%u' is out of range (0..%u)", @@ -1176,7 +1180,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, obj = NULL; (void)cfg_map_get(options, "nta-lifetime", &obj); if (obj != NULL) { - lifetime = cfg_obj_asuint32(obj); + lifetime = cfg_obj_asduration(obj); if (lifetime > 604800) { /* 7 days */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-lifetime' cannot exceed one week"); @@ -1193,7 +1197,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, obj = NULL; (void)cfg_map_get(options, "nta-recheck", &obj); if (obj != NULL) { - uint32_t recheck = cfg_obj_asuint32(obj); + uint32_t recheck = cfg_obj_asduration(obj); if (recheck > 604800) { /* 7 days */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-recheck' cannot exceed one week"); @@ -1271,7 +1275,11 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, if (obj == NULL) continue; - value = cfg_obj_asuint32(obj); + if (cfg_obj_isduration(obj)) { + value = cfg_obj_asduration(obj); + } else { + value = cfg_obj_asuint32(obj); + } if (value < fstrm[i].min || (fstrm[i].max != 0U && value > fstrm[i].max)) { if (fstrm[i].max != 0U) diff --git a/lib/isccfg/include/isccfg/cfg.h b/lib/isccfg/include/isccfg/cfg.h index 35729a4991..2aefdce110 100644 --- a/lib/isccfg/include/isccfg/cfg.h +++ b/lib/isccfg/include/isccfg/cfg.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -331,6 +332,24 @@ cfg_obj_aspercentage(const cfg_obj_t *obj); * \li A 32-bit unsigned integer. */ +bool +cfg_obj_isduration(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is of duration type. + */ + +uint32_t +cfg_obj_asduration(const cfg_obj_t *obj); +/*%< + * Returns the value of a configuration object of duration + * + * Requires: + * \li 'obj' points to a valid configuration object of duration type. + * + * Returns: + * \li A duration in seconds. + */ + bool cfg_obj_isstring(const cfg_obj_t *obj); /*%< diff --git a/lib/isccfg/include/isccfg/grammar.h b/lib/isccfg/include/isccfg/grammar.h index e931282a0f..ad3dd81d17 100644 --- a/lib/isccfg/include/isccfg/grammar.h +++ b/lib/isccfg/include/isccfg/grammar.h @@ -82,6 +82,9 @@ typedef struct cfg_printer cfg_printer_t; typedef ISC_LIST(cfg_listelt_t) cfg_list_t; typedef struct cfg_map cfg_map_t; typedef struct cfg_rep cfg_rep_t; +typedef struct cfg_duration cfg_duration_t; + +#define CFG_DURATION_MAXLEN 64 /* * Function types for configuration object methods @@ -154,6 +157,24 @@ struct cfg_netprefix { unsigned int prefixlen; }; +/*% + * A configuration object to store ISO 8601 durations. + */ +struct cfg_duration { + /* + * The duration is stored in multiple parts: + * [0] Years + * [1] Months + * [2] Weeks + * [3] Days + * [4] Hours + * [5] Minutes + * [6] Seconds + */ + uint32_t parts[7]; + bool iso8601; +}; + /*% * A configuration data representation. */ @@ -183,6 +204,7 @@ struct cfg_obj { isc_dscp_t dscp; } sockaddrdscp; cfg_netprefix_t netprefix; + cfg_duration_t duration; } value; isc_refcount_t references; /*%< reference counter */ const char * file; @@ -290,6 +312,7 @@ LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_netprefix; LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_void; LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_fixedpoint; LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_percentage; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_duration; /*@}*/ /*@{*/ @@ -320,6 +343,7 @@ LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_token; LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_unsupported; LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_fixedpoint; LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_percentage; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_duration; /*@}*/ isc_result_t @@ -504,6 +528,13 @@ cfg_parse_percentage(cfg_parser_t *pctx, const cfg_type_t *type, void cfg_print_percentage(cfg_printer_t *pctx, const cfg_obj_t *obj); +isc_result_t +cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret); + +void +cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj); + isc_result_t cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index d427ec488d..3282a8b01f 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -121,7 +121,6 @@ static cfg_type_t cfg_type_sizeval; static cfg_type_t cfg_type_sockaddr4wild; static cfg_type_t cfg_type_sockaddr6wild; static cfg_type_t cfg_type_statschannels; -static cfg_type_t cfg_type_ttlval; static cfg_type_t cfg_type_view; static cfg_type_t cfg_type_viewopts; static cfg_type_t cfg_type_zone; @@ -1054,7 +1053,7 @@ options_clauses[] = { { "fstrm-set-output-notify-threshold", &cfg_type_uint32, 0 }, { "fstrm-set-output-queue-model", &cfg_type_fstrm_model, 0 }, { "fstrm-set-output-queue-size", &cfg_type_uint32, 0 }, - { "fstrm-set-reopen-interval", &cfg_type_ttlval, 0 }, + { "fstrm-set-reopen-interval", &cfg_type_duration, 0 }, #else { "fstrm-set-buffer-hint", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED }, @@ -1068,7 +1067,7 @@ options_clauses[] = { CFG_CLAUSEFLAG_NOTCONFIGURED }, { "fstrm-set-output-queue-size", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED }, - { "fstrm-set-reopen-interval", &cfg_type_ttlval, + { "fstrm-set-reopen-interval", &cfg_type_duration, CFG_CLAUSEFLAG_NOTCONFIGURED }, #endif /* HAVE_DNSTAP */ #if defined(HAVE_GEOIP2) @@ -1083,7 +1082,7 @@ options_clauses[] = { { "host-statistics", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT }, { "host-statistics-max", &cfg_type_uint32, CFG_CLAUSEFLAG_ANCIENT }, { "hostname", &cfg_type_qstringornone, 0 }, - { "interface-interval", &cfg_type_ttlval, 0 }, + { "interface-interval", &cfg_type_duration, 0 }, { "keep-response-order", &cfg_type_bracketed_aml, 0 }, { "listen-on", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI }, { "listen-on-v6", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI }, @@ -1611,8 +1610,8 @@ static cfg_tuplefielddef_t rpz_zone_fields[] = { { "zone name", &cfg_type_rpz_zone, 0 }, { "add-soa", &cfg_type_boolean, 0 }, { "log", &cfg_type_boolean, 0 }, - { "max-policy-ttl", &cfg_type_ttlval, 0 }, - { "min-update-interval", &cfg_type_ttlval, 0 }, + { "max-policy-ttl", &cfg_type_duration, 0 }, + { "min-update-interval", &cfg_type_duration, 0 }, { "policy", &cfg_type_rpz_policy, 0 }, { "recursive-only", &cfg_type_boolean, 0 }, { "nsip-enable", &cfg_type_boolean, 0 }, @@ -1633,8 +1632,8 @@ static cfg_tuplefielddef_t rpz_fields[] = { { "zone list", &cfg_type_rpz_list, 0 }, { "add-soa", &cfg_type_boolean, 0 }, { "break-dnssec", &cfg_type_boolean, 0 }, - { "max-policy-ttl", &cfg_type_ttlval, 0 }, - { "min-update-interval", &cfg_type_ttlval, 0 }, + { "max-policy-ttl", &cfg_type_duration, 0 }, + { "min-update-interval", &cfg_type_duration, 0 }, { "min-ns-dots", &cfg_type_uint32, 0 }, { "nsip-wait-recurse", &cfg_type_boolean, 0 }, { "qname-wait-recurse", &cfg_type_boolean, 0 }, @@ -1671,7 +1670,7 @@ static cfg_tuplefielddef_t catz_zone_fields[] = { { "default-masters", &cfg_type_namesockaddrkeylist, 0 }, { "zone-directory", &cfg_type_qstring, 0 }, { "in-memory", &cfg_type_boolean, 0 }, - { "min-update-interval", &cfg_type_ttlval, 0 }, + { "min-update-interval", &cfg_type_duration, 0 }, { NULL, NULL, 0 } }; static cfg_type_t cfg_type_catz_tuple = { @@ -1899,7 +1898,7 @@ view_clauses[] = { { "filter-aaaa-on-v6", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, { "glue-cache", &cfg_type_boolean, 0 }, { "ixfr-from-differences", &cfg_type_ixfrdifftype, 0 }, - { "lame-ttl", &cfg_type_ttlval, 0 }, + { "lame-ttl", &cfg_type_duration, 0 }, #ifdef HAVE_LMDB { "lmdb-mapsize", &cfg_type_sizeval, 0 }, #else @@ -1907,16 +1906,16 @@ view_clauses[] = { #endif { "max-acache-size", &cfg_type_sizenodefault, CFG_CLAUSEFLAG_OBSOLETE }, { "max-cache-size", &cfg_type_sizeorpercent, 0 }, - { "max-cache-ttl", &cfg_type_ttlval, 0 }, + { "max-cache-ttl", &cfg_type_duration, 0 }, { "max-clients-per-query", &cfg_type_uint32, 0 }, - { "max-ncache-ttl", &cfg_type_ttlval, 0 }, + { "max-ncache-ttl", &cfg_type_duration, 0 }, { "max-recursion-depth", &cfg_type_uint32, 0 }, { "max-recursion-queries", &cfg_type_uint32, 0 }, - { "max-stale-ttl", &cfg_type_ttlval, 0 }, + { "max-stale-ttl", &cfg_type_duration, 0 }, { "max-udp-size", &cfg_type_uint32, 0 }, { "message-compression", &cfg_type_boolean, 0 }, - { "min-cache-ttl", &cfg_type_ttlval, 0 }, - { "min-ncache-ttl", &cfg_type_ttlval, 0 }, + { "min-cache-ttl", &cfg_type_duration, 0 }, + { "min-ncache-ttl", &cfg_type_duration, 0 }, { "min-roots", &cfg_type_uint32, CFG_CLAUSEFLAG_ANCIENT }, { "minimal-any", &cfg_type_boolean, 0 }, { "minimal-responses", &cfg_type_minimal, 0 }, @@ -1924,8 +1923,8 @@ view_clauses[] = { { "no-case-compress", &cfg_type_bracketed_aml, 0 }, { "nocookie-udp-size", &cfg_type_uint32, 0 }, { "nosit-udp-size", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE }, - { "nta-lifetime", &cfg_type_ttlval, 0 }, - { "nta-recheck", &cfg_type_ttlval, 0 }, + { "nta-lifetime", &cfg_type_duration, 0 }, + { "nta-recheck", &cfg_type_duration, 0 }, { "nxdomain-redirect", &cfg_type_astring, 0 }, { "preferred-glue", &cfg_type_astring, 0 }, { "prefetch", &cfg_type_prefetch, 0 }, @@ -1955,10 +1954,10 @@ view_clauses[] = { { "root-key-sentinel", &cfg_type_boolean, 0 }, { "rrset-order", &cfg_type_rrsetorder, 0 }, { "send-cookie", &cfg_type_boolean, 0 }, - { "servfail-ttl", &cfg_type_ttlval, 0 }, + { "servfail-ttl", &cfg_type_duration, 0 }, { "sortlist", &cfg_type_bracketed_aml, 0 }, { "stale-answer-enable", &cfg_type_boolean, 0 }, - { "stale-answer-ttl", &cfg_type_ttlval, 0 }, + { "stale-answer-ttl", &cfg_type_duration, 0 }, { "suppress-initial-notify", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI }, { "synth-from-dnssec", &cfg_type_boolean, 0 }, { "topology", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_ANCIENT }, @@ -3761,54 +3760,14 @@ static cfg_type_t cfg_type_masterselement = { doc_masterselement, NULL, NULL }; -static isc_result_t -parse_ttlval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { - isc_result_t result; - cfg_obj_t *obj = NULL; - uint32_t ttl; - - UNUSED(type); - - CHECK(cfg_gettoken(pctx, 0)); - if (pctx->token.type != isc_tokentype_string) { - result = ISC_R_UNEXPECTEDTOKEN; - goto cleanup; - } - - result = dns_ttl_fromtext(&pctx->token.value.as_textregion, &ttl); - if (result == ISC_R_RANGE ) { - cfg_parser_error(pctx, CFG_LOG_NEAR, "TTL out of range "); - return (result); - } else if (result != ISC_R_SUCCESS) - goto cleanup; - - CHECK(cfg_create_obj(pctx, &cfg_type_uint32, &obj)); - obj->value.uint32 = ttl; - *ret = obj; - return (ISC_R_SUCCESS); - - cleanup: - cfg_parser_error(pctx, CFG_LOG_NEAR, - "expected integer and optional unit"); - return (result); -} - -/*% - * A TTL value (number + optional unit). - */ -static cfg_type_t cfg_type_ttlval = { - "ttlval", parse_ttlval, cfg_print_uint64, cfg_doc_terminal, - &cfg_rep_uint64, NULL -}; - static isc_result_t parse_maxttl(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { - return (cfg_parse_enum_or_other(pctx, type, &cfg_type_ttlval, ret)); + return (cfg_parse_enum_or_other(pctx, type, &cfg_type_duration, ret)); } static void doc_maxttl(cfg_printer_t *pctx, const cfg_type_t *type) { - cfg_doc_enum_or_other(pctx, type, &cfg_type_ttlval); + cfg_doc_enum_or_other(pctx, type, &cfg_type_duration); } /*% diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c index 0c7987b881..68db91a1d1 100644 --- a/lib/isccfg/parser.c +++ b/lib/isccfg/parser.c @@ -34,6 +34,8 @@ #include #include +#include + /* Shorthand */ #define CAT CFG_LOGCATEGORY_CONFIG #define MOD CFG_LOGMODULE_PARSER @@ -132,6 +134,7 @@ LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_fixedpoint = { "fixedpoint", free_noop }; LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_percentage = { "percentage", free_noop }; +LIBISCCFG_EXTERNAL_DATA cfg_rep_t cfg_rep_duration = { "duration", free_noop }; /* * Configuration type definitions. @@ -977,9 +980,353 @@ LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_uint64 = { &cfg_rep_uint64, NULL }; +/* + * Get the number of digits in a number. + */ +static size_t +numlen(time_t num) { + uint32_t period = (uint32_t) num; + size_t count = 0; + + if (period == 0) { + return 1; + } + while (period > 0) { + count++; + period /= 10; + } + return (count); +} + +/* + * duration + */ +void +cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj) { + char buf[CFG_DURATION_MAXLEN]; + char *str; + const char *indicators = "YMWDHMS"; + int count, i; + int durationlen[7]; + cfg_duration_t duration; + /* + * D ? The duration has a date part. + * T ? The duration has a time part. + */ + bool D = false, T = false; + + REQUIRE(pctx != NULL); + REQUIRE(obj != NULL); + + duration = obj->value.duration; + + /* If this is not an ISO 8601 duration, just print it as a number. */ + if (!duration.iso8601) { + return (cfg_print_rawuint(pctx, duration.parts[6])); + } + + /* Calculate length of string. */ + buf[0] = 'P'; + buf[1] = '\0'; + str = &buf[1]; + count = 2; + for (i = 0; i < 6; i++) { + if (duration.parts[i] > 0) { + durationlen[i] = 1 + numlen(duration.parts[i]); + if (i < 4) { + D = true; + } else { + T = true; + } + } else { + durationlen[i] = 0; + } + count += durationlen[i]; + } + /* + * Special case for seconds which is not taken into account in the + * above for loop: Count the length of the seconds part if it is + * non-zero, or if all the other parts are also zero. In the latter + * case this function will print "PT0S". + */ + if (duration.parts[6] > 0 || + (!D && !duration.parts[4] && !duration.parts[5])) { + durationlen[6] = 1 + numlen(duration.parts[6]); + T = true; + count += durationlen[6]; + } + /* Add one character for the time indicator. */ + if (T) { + count++; + } + INSIST(count < CFG_DURATION_MAXLEN); + + /* Now print the duration. */ + for (i = 0; i < 6; i++) { + /* + * We don't check here if weeks and other time indicator are + * used mutually exclusively. + */ + if (duration.parts[i] > 0) { + snprintf(str, durationlen[i]+2, "%u%c", + (uint32_t) duration.parts[i], indicators[i]); + str += durationlen[i]+1; + } + if (i == 3 && T) { + snprintf(str, 2, "T"); + str += 1; + } + + } + /* Special case for seconds. */ + if (duration.parts[6] > 0 || + (!D && !duration.parts[4] && !duration.parts[3])) { + snprintf(str, durationlen[6]+2, "%u%c", + (uint32_t) duration.parts[6], indicators[6]); + } + cfg_print_chars(pctx, buf, strlen(buf)); +} + +bool +cfg_obj_isduration(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (obj->type->rep == &cfg_rep_duration); +} + +uint32_t +cfg_obj_asduration(const cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_duration); + uint32_t duration = 0; + duration += obj->value.duration.parts[6]; // Seconds + duration += obj->value.duration.parts[5]*60; // Minutes + duration += obj->value.duration.parts[4]*3600; // Hours + duration += obj->value.duration.parts[3]*86400; // Days + duration += obj->value.duration.parts[2]*86400*7; // Weaks + /* + * The below additions are not entirely correct + * because days may very per month and per year. + */ + duration += obj->value.duration.parts[1]*86400*31; // Months + duration += obj->value.duration.parts[0]*86400*365; // Years + return (duration); +} + +/* + * duration_fromtext initially taken from OpenDNSSEC code base. + * Modified to fit the BIND 9 code. + * + * Copyright (c) 2009-2018 NLNet Labs. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +static isc_result_t +duration_fromtext(isc_textregion_t *source, cfg_duration_t *duration) { + char buf[CFG_DURATION_MAXLEN]; + char *P, *X, *T, *W, *str; + bool not_weeks = false; + int i; + + /* + * Copy the buffer as it may not be NULL terminated. + * Anyone having a duration longer than 63 characters is crazy. + */ + if (source->length > sizeof(buf) - 1) { + return (ISC_R_BADNUMBER); + } + /* Copy source->length bytes and NULL terminate. */ + snprintf(buf, sizeof(buf), "%.*s", (int)source->length, source->base); + str = buf; + + /* Clear out duration. */ + for (i = 0; i < 7; i++) { + duration->parts[i] = 0; + } + + /* Every duration starts with 'P' */ + P = strchr(str, 'P'); + if (!P) { + return (ISC_R_BADNUMBER); + } + + /* Record the time indicator. */ + T = strchr(str, 'T'); + + /* Record years. */ + X = strchr(str, 'Y'); + if (X) { + duration->parts[0] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record months. */ + X = strchr(str, 'M'); + /* + * M could be months or minutes. This is months if there is no time + * part, or this M indicator is before the time indicator. + */ + if (X && (!T || (size_t) (X-P) < (size_t) (T-P))) { + duration->parts[1] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record days. */ + X = strchr(str, 'D'); + if (X) { + duration->parts[3] = atoi(str+1); + str = X; + not_weeks = true; + } + + /* Time part? */ + if (T) { + str = T; + not_weeks = true; + } + + /* Record hours. */ + X = strchr(str, 'H'); + if (X && T) { + duration->parts[4] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record minutes. */ + X = strrchr(str, 'M'); + /* + * M could be months or minutes. This is minutes if there is a time + * part and the M indicator is behind the time indicator. + */ + if (X && T && (size_t) (X-P) > (size_t) (T-P)) { + duration->parts[5] = atoi(str+1); + str = X; + not_weeks = true; + } + /* Record seconds. */ + X = strchr(str, 'S'); + if (X && T) { + duration->parts[6] = atoi(str+1); + str = X; + not_weeks = true; + } + + /* Or is the duration configured in weeks? */ + W = strchr(buf, 'W'); + if (W) { + if (not_weeks) { + /* Mix of weeks and other indicators is not allowed */ + return (ISC_R_BADNUMBER); + } else { + duration->parts[2] = atoi(str+1); + str = W; + } + } + + /* Deal with trailing garbage. */ + if (str[1] != '\0') { + return (ISC_R_BADNUMBER); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + cfg_obj_t *obj = NULL; + cfg_duration_t duration; + + UNUSED(type); + + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type != isc_tokentype_string) { + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + + if (TOKEN_STRING(pctx)[0] == 'P') { + result = duration_fromtext(&pctx->token.value.as_textregion, + &duration); + duration.iso8601 = true; + } else { + uint32_t ttl; + result = dns_ttl_fromtext(&pctx->token.value.as_textregion, + &ttl); + /* + * With dns_ttl_fromtext() the information on optional units. + * is lost, and is treated as seconds from now on. + */ + duration.parts[0] = 0; + duration.parts[1] = 0; + duration.parts[2] = 0; + duration.parts[3] = 0; + duration.parts[4] = 0; + duration.parts[5] = 0; + duration.parts[6] = ttl; + duration.iso8601 = false; + } + if (result == ISC_R_RANGE) { + cfg_parser_error(pctx, CFG_LOG_NEAR, + "duration or TTL out of range"); + return (result); + } else if (result != ISC_R_SUCCESS) { + goto cleanup; + } + CHECK(cfg_create_obj(pctx, &cfg_type_duration, &obj)); + obj->value.duration = duration; + *ret = obj; + return (ISC_R_SUCCESS); + +cleanup: + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected ISO 8601 duration or TTL value"); + return (result); +} + +/*% + * A duration as defined by ISO 8601 (P[n]Y[n]M[n]DT[n]H[n]M[n]S). + * - P is the duration indicator ("period") placed at the start. + * - Y is the year indicator that follows the value for the number of years. + * - M is the month indicator that follows the value for the number of months. + * - D is the day indicator that follows the value for the number of days. + * - T is the time indicator that precedes the time components. + * - H is the hour indicator that follows the value for the number of hours. + * - M is the minute indicator that follows the value for the number of + * minutes. + * - S is the second indicator that follows the value for the number of + * seconds. + * + * A duration can also be a TTL value (number + optional unit). + */ +LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_duration = { + "duration", cfg_parse_duration, cfg_print_duration, cfg_doc_terminal, + &cfg_rep_duration, NULL +}; + /* * qstring (quoted string), ustring (unquoted string), astring - * (any string) + * (any string), sstring (secret string) */ /* Create a string object from a null-terminated C string. */ diff --git a/lib/isccfg/tests/Kyuafile b/lib/isccfg/tests/Kyuafile index dc14aace41..89d50352f2 100644 --- a/lib/isccfg/tests/Kyuafile +++ b/lib/isccfg/tests/Kyuafile @@ -1,4 +1,5 @@ syntax(2) test_suite('bind9') +tap_test_program{name='duration_test'} tap_test_program{name='parser_test'} diff --git a/lib/isccfg/tests/Makefile.in b/lib/isccfg/tests/Makefile.in index 9e65962098..4517992e03 100644 --- a/lib/isccfg/tests/Makefile.in +++ b/lib/isccfg/tests/Makefile.in @@ -30,13 +30,18 @@ ISCCFGDEPLIBS = ../libisccfg.@A@ LIBS = @LIBS@ @CMOCKA_LIBS@ OBJS = -SRCS = parser_test.c +SRCS = duration_test.c parser_test.c SUBDIRS = -TARGETS = parser_test@EXEEXT@ +TARGETS = duration_test@EXEEXT@ parser_test@EXEEXT@ @BIND9_MAKE_RULES@ +duration_test@EXEEXT@: duration_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} ${ISCCFGDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ duration_test.@O@ \ + ${ISCCFGLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS} + parser_test@EXEEXT@: parser_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} ${ISCCFGDEPLIBS} ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ ${LDFLAGS} -o $@ parser_test.@O@ \ diff --git a/lib/isccfg/tests/duration_test.c b/lib/isccfg/tests/duration_test.c new file mode 100644 index 0000000000..2e0249bb07 --- /dev/null +++ b/lib/isccfg/tests/duration_test.c @@ -0,0 +1,207 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include +#include +#include + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define CHECK(r) \ + do { \ + result = (r); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +isc_mem_t *mctx = NULL; +isc_log_t *lctx = NULL; +static isc_logcategory_t categories[] = { + { "", 0 }, + { "client", 0 }, + { "network", 0 }, + { "update", 0 }, + { "queries", 0 }, + { "unmatched", 0 }, + { "update-security", 0 }, + { "query-errors", 0 }, + { NULL, 0 } +}; + +static void +cleanup() { + if (lctx != NULL) { + isc_log_destroy(&lctx); + } + if (mctx != NULL) { + isc_mem_destroy(&mctx); + } +} + +static isc_result_t +setup() { + isc_result_t result; + + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + isc_mem_create(&mctx); + + isc_logdestination_t destination; + isc_logconfig_t *logconfig = NULL; + + CHECK(isc_log_create(mctx, &lctx, &logconfig)); + isc_log_registercategories(lctx, categories); + isc_log_setcontext(lctx); + + destination.file.stream = stderr; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + CHECK(isc_log_createchannel(logconfig, "stderr", + ISC_LOG_TOFILEDESC, + ISC_LOG_DYNAMIC, + &destination, 0)); + CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL)); + + return (ISC_R_SUCCESS); + + cleanup: + cleanup(); + return (result); +} + +struct duration_conf { + const char* string; + uint32_t time; +}; +typedef struct duration_conf duration_conf_t; + +/* test cfg_obj_asduration() */ +static void +cfg_obj_asduration_test(void **state) { + isc_result_t result; + duration_conf_t durations[] = { + { .string = "PT0S", .time = 0 }, + { .string = "PT42S", .time = 42 }, + { .string = "PT10M", .time = 600 }, + { .string = "PT10M4S", .time = 604 }, + { .string = "PT2H", .time = 7200 }, + { .string = "PT2H3S", .time = 7203 }, + { .string = "PT2H1M3S", .time = 7263 }, + { .string = "P7D", .time = 604800 }, + { .string = "P7DT2H", .time = 612000 }, + { .string = "P2W", .time = 1209600 }, + { .string = "P3M", .time = 8035200 }, + { .string = "P3MT10M", .time = 8035800 }, + { .string = "P5Y", .time = 157680000 }, + { .string = "P5YT2H", .time = 157687200 }, + { .string = "P1Y1M1DT1H1M1S", .time = 34304461 }, + { .string = "0", .time = 0 }, + { .string = "30", .time = 30 }, + { .string = "42s", .time = 42 }, + { .string = "10m", .time = 600 }, + { .string = "2h", .time = 7200 }, + { .string = "7d", .time = 604800 }, + { .string = "2w", .time = 1209600 }, + }; + int num = 22; + isc_buffer_t buf1; + cfg_parser_t *p1 = NULL; + cfg_obj_t *c1 = NULL; + + UNUSED(state); + + setup(); + + for (int i = 0; i < num; i++) { + const cfg_listelt_t *element; + const cfg_obj_t *kasps = NULL; + char conf[64]; + sprintf(&conf[0], + "dnssec-policy \"dp\"\n{\nsignatures-refresh %s;\n};\n", + durations[i].string); + + isc_buffer_init(&buf1, conf, strlen(conf) - 1); + isc_buffer_add(&buf1, strlen(conf) - 1); + + /* Parse with default line numbering */ + result = cfg_parser_create(mctx, lctx, &p1); + assert_int_equal(result, ISC_R_SUCCESS); + + result = cfg_parse_buffer(p1, &buf1, "text1", 0, + &cfg_type_namedconf, 0, &c1); + assert_int_equal(result, ISC_R_SUCCESS); + + (void)cfg_map_get(c1, "dnssec-policy", &kasps); + assert_non_null(kasps); + for (element = cfg_list_first(kasps); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *d1 = NULL; + const cfg_obj_t *kopts = NULL; + cfg_obj_t *kconf = cfg_listelt_value(element); + assert_non_null(kconf); + + kopts = cfg_tuple_get(kconf, "options"); + result = cfg_map_get(kopts, "signatures-refresh", &d1); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_int_equal(durations[i].time, + cfg_obj_asduration(d1)); + } + + cfg_obj_destroy(p1, &c1); + cfg_parser_destroy(&p1); + } + + cleanup(); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(cfg_obj_asduration_test), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (0); +} + +#endif diff --git a/lib/isccfg/win32/libisccfg.def b/lib/isccfg/win32/libisccfg.def index 96039fbf04..61e6e86ce4 100644 --- a/lib/isccfg/win32/libisccfg.def +++ b/lib/isccfg/win32/libisccfg.def @@ -36,6 +36,7 @@ cfg_map_get cfg_map_getname cfg_map_nextclause cfg_obj_asboolean +cfg_obj_asduration cfg_obj_asfixedpoint cfg_obj_asnetprefix cfg_obj_aspercentage @@ -48,6 +49,7 @@ cfg_obj_destroy cfg_obj_file cfg_obj_getdscp cfg_obj_isboolean +cfg_obj_isduration cfg_obj_isfixedpoint cfg_obj_islist cfg_obj_ismap @@ -68,6 +70,7 @@ cfg_parse_boolean cfg_parse_bracketed_list cfg_parse_buffer cfg_parse_dscp +cfg_parse_duration cfg_parse_enum cfg_parse_enum_or_other cfg_parse_file @@ -107,6 +110,7 @@ cfg_print_bracketed_list cfg_print_chars cfg_print_clauseflags cfg_print_cstr +cfg_print_duration cfg_print_fixedpoint cfg_print_grammar cfg_print_indent @@ -131,6 +135,7 @@ cfg_ungettoken ; Exported Data ;cfg_rep_boolean +;cfg_rep_duration ;cfg_rep_fixedpoint ;cfg_rep_list ;cfg_rep_map diff --git a/util/copyrights b/util/copyrights index 5671c1c42a..d10b5bcb55 100644 --- a/util/copyrights +++ b/util/copyrights @@ -2440,6 +2440,7 @@ ./lib/isccfg/namedconf.c C 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 ./lib/isccfg/parser.c C 2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 ./lib/isccfg/tests/Kyuafile X 2017,2018,2019 +./lib/isccfg/tests/duration_test.c C 2019 ./lib/isccfg/tests/parser_test.c C 2016,2018,2019 ./lib/isccfg/version.c C 1998,1999,2000,2001,2004,2005,2007,2016,2018,2019 ./lib/isccfg/win32/DLLMain.c C 2001,2004,2007,2016,2018,2019 From 1fbd8bb1b3c4a5607cad0d0bd655a764a1eb367a Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 2 Sep 2019 15:51:10 +0200 Subject: [PATCH 03/43] Design documentation 'dnssec-policy' Initial design document. --- doc/design/dnssec-policy | 227 +++++++++++++++++++++++++++++++++++++++ util/copyrights | 1 + 2 files changed, 228 insertions(+) create mode 100644 doc/design/dnssec-policy diff --git a/doc/design/dnssec-policy b/doc/design/dnssec-policy new file mode 100644 index 0000000000..2079c8edc2 --- /dev/null +++ b/doc/design/dnssec-policy @@ -0,0 +1,227 @@ +Copyright (C) Internet Systems Consortium, Inc. ("ISC") + +See COPYRIGHT in the source root or http://isc.org/copyright.html for terms. + +# DNSSEC Key and Signing Policy + +A DNSSEC key and signing policy (KASP) defines a DNSSEC policy that can be +applied to one or more zones. + +For some background information, see: + + https://www.ietf.org/archive/id/draft-mekking-dnsop-kasp-00.txt + +# DNSSEC in BIND 9 + +DNSSEC is first implemented in BIND 9. Many adaptations have been made since +then. A lot of configuration knobs were added. One aim with introducing KASP +configuration is that all these configuration options are grouped together, +making the named configuration more intuitive when it comes to DNSSEC, and +making it easier to turn on DNSSEC for zones. Instead of configuring many +different options per zone, you would be able to do the following: + +``` +zone "example.com." { + ... + dnssec-policy "_default"; +}; +``` + +## Existing DNSSEC configuration options + +### Signing + +The following configuration options exist nowadays for `named` to maintain +DNSSEC signed zones. These will no longer work if an explicit DNSSEC policy +is set for a zone. + +1. `auto-dnssec`: When setting a DNSSEC policy for a zone instead, the + behavior will be as if `auto-dnssec` was set to `maintain`. + +1. `dnskey-sig-validity`: This option will be replaced in favor of the KASP + configuration value `signatures-validity-dnskey`. + +1. `dnssec-dnskey-kskonly`: This option will be removed and the key + configuration from the policy will be used to determine what RRsets will be + signed with which keys (Keys will have a role "KSK" and/or "ZSK"). + +1. `dnssec-loadkeys-interval`: This option will determine how the period that + BIND 9 will check its key repository (default once per hour) to see if + there are new keys added or if existing keys metadata has changed. This + option might go away because the entity that performs DNSSEC maintenance + knows exactly when the next step needs to happen. We can set the interval + accordingly. This does mean that whenever a new key is added or deprecated + manually, the interval needs to be set to now. Alternatively, we keep this + option and only pick up new keys when at a certain interval. + +1. `dnssec-secure-to-insecure`: This option allows a dynamic zone to + transition from secure to insecure. This seems to be a safety check + when named is not responsible for signing. This will likely go away + because explicitly removing the dnssec-policy will be the same signal + to (safely) make the zone insecure. + +1. `dnssec-update-mode`: This option determines how DNSSEC signed dynamic + zones are updated. Default is `maintain` and it is unclear how it is + different from `auto-dnssec`. With KASP, the behavior will be as if + the `dnssec-update-mode` was set to `maintain`. If you want DNSSEC + maintenance to be done outside `named`, you should not configure a + `dnssec-policy` for that zone. + +1. `inline-signing`: When set to "yes", this option will sign transferred + unsigned zones, and unsigned zone from file. This is also no longer needed + when KASP is introduced because when setting a `dnssec-policy` for a + secondary zone or a zone with zone file, this indicates that + `inline-signing` is desired. + +1. `max-zone-ttl`: This will cap all TTLs in a zone file to the specified + value. Although this option may be used for non-DNSSEC zones, it is really + only useful for DNSSEC-signed zones because when performing key rollovers + the timing depends on the largest TTL in the zone. The value set in the + `dnssec-policy` statement will override the existing `max-zone-ttl` value. + +1. `sig-signing-nodes`: This specifies the number of nodes to be examined + in a quantum when signing a zone with a new DNSKEY. This presumable is + to avoid keeping the database connection open for a long time. With the + current database approach this probably needs to stay. + +1. `sig-signing-signatures`: This specifies a threshold number of how many + signatures will be generated in a quantum. Similar to `sig-signing-nodes`. + +1. `sig-signing-type`: Internal record type number, used to track zone + signing process. This likely will go away in favor of a new method. + +1. `sig-validity-interval`: Specifies the number of days a signature is valid. + The second optional value is the refresh interval. Thos option will + be replaced by KASP configuration values "signatures-validity" and + "signatures-refresh". + +1. `update-check-ksk`: When set to "no", KSK will also sign non-DNSKEY RRsets. + This option will go away and key roles will be used to determine what + keys sign which RRsets (A KSK that should sign all RRsets will have both + the KSK and ZSK role and is referred to as a CSK). + +Other DNSSEC related configuration options that are not related to the policy +are likely to stay: + +1. `key-directory`: This is where the DNSKEY key files can be found. + +1. `serial-update-method`: This is used for dynamic zones to determne how + the SOA SERIAL should be updated. There will likely be a separate + configuration option for the serial update method when resigning a zone. + + +# KASP Configuration + +The KASP Configuration may look something like the example configuration +below. This includes all options as described in the KASP draft, but we may +decide that some options are not required. + +``` +dnssec-policy "nsec3" { + + description "policy for zones that require zone walking mitigation"; + + // Signatures + signatures-resign PT2H; + signatures-refresh P3D; + signatures-validity P14D; + signatures-validity-dnskey P14D; + + // Denial of existence + denial-type nsec3; + nsec3-param ttl 0 hash algorithm 1 iterations 5 optout; + nsec3-salt length 8 resalt P100D; + + // Keys + dnskey-ttl 3600; + publish-safety PT3600S; + retire-safety PT3600S; + share-keys no; + purge-keys-after P14D; + + keys { + ksk key-directory P5Y ECDSAP256SHA256; + zsk key-directory P30D ECDSAP256SHA256; + csk key-directory PT0S 8 2048; + }; + + // Parent synchronization + cds yes; + cdnskey yes; + check-ds { 127.0.0.53; }; + check-ds-interval PT3600S; + + // Zone properties + zone-propagation-delay PT3600S; + zone-registration-delay PT3600S; + zone-soa-ttl 3600; + zone-soa-minimum 3600; + zone-soa-serial-update-method unixtime; + zone-max-ttl 24h; + + // Parent properties + parent-propagation-delay PT24H; + parent-ds-ttl 3600; + parent-soa-ttl 3600; + parent-soa-minimum 3600; +}; +``` + +# KASP design + +## dnssec-policy versus dnssec-keymgr + +Key management in BIND 9 is currently implemented with a Python script +called `dnssec-keymgr`. It uses the DNSSEC tools for manipulating DNSSEC key +metadata. + +With `dnssec-policy` configured in `named.conf` you no longer need to manually +call `dnssec-keymgr` or the tools it wraps around, `dnssec-keygen` and +`dnssec-settime` (although it is still possible to use them). The policy in +`named.conf` will make `named` create keys when necessary and set the key +timings accordingly. + +## Key roles + +BIND 9.14 allows sign your zones with a Zone Signing Key (ZSK) and a +Key Signing Key (KSK). If you provide only one key, the zone will be signed +with just one key (effectively acting as a Combined Signing Key (CSK). If +one of the keys is offline, BIND 9 will temporarily change the key usage: A +KSK may sign DNSKEY unrelated RRsets. + +With BIND 9.14, ZSKs by default sign the complete zone, except when +`dnssec-dnskey-kskonly` and `update-check-ksk` are both set to `yes`. + +KASP introduces key roles making key usage more explicit, without depending +on state of the keys or additional configuration values. A key that has the +KSK role will always sign only DNSKEY related RRsets, and a key with a ZSK role +will always sign only DNSKEY unrelated RRsets. A key can have both roles, which +is referred to as a CSK. Below is an example configuration for the three types +of keys: +``` + keys { + ksk key-directory P5Y ECDSAP256SHA256; + zsk key-directory P30D ECDSAP256SHA256; + csk key-directory PT0S 8 2048; + }; +``` + +## NSEC3 + +Currently if you want to sign your zone with NSEC3 you can do so by introducing +an NSEC3PARAM record via Dynamic Update. This is no longer necessary with +`dnssec-policy` as you can configure NSEC3 usage in `named.conf`. + +## Changing policies + +You can change a zone's policy by referring to a different `dnssec-policy` +or by changing the `dnssec-policy` itself. After a reload of the configuration +key timings may be adjusted. This may trigger a key rollover (for example if +the key lifetimes have been shortened, or if other key properties have changed. + +## Key state machines + +Rollover correctness are guaranteed by key state machines. See for more +information: + + https://nlnetlabs.nl/downloads/publications/satin2012-Schaeffer.pdf diff --git a/util/copyrights b/util/copyrights index d10b5bcb55..31f059b23b 100644 --- a/util/copyrights +++ b/util/copyrights @@ -1499,6 +1499,7 @@ ./doc/design/db_rules TXT.BRIEF 1999,2000,2001,2004,2016,2018,2019 ./doc/design/decompression TXT.BRIEF 1999,2000,2001,2004,2016,2018,2019 ./doc/design/dispatch TXT.BRIEF 2000,2001,2004,2016,2018,2019 +./doc/design/dnssec-policy TXT.BRIEF 2019 ./doc/design/dscp TXT.BRIEF 2013,2016,2018,2019 ./doc/design/keydone TXT.BRIEF 2011,2016,2018,2019 ./doc/design/logging TXT.BRIEF 1999,2000,2001,2004,2016,2018,2019 From a50d707fdcf702e9e07a3ad5ea74761cb6e3d08d Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 2 Sep 2019 16:24:48 +0200 Subject: [PATCH 04/43] Introduce dnssec-policy configuration This commit introduces the initial `dnssec-policy` configuration statement. It has an initial set of options to deal with signature and key maintenance. Add some checks to ensure that dnssec-policy is configured at the right locations, and that policies referenced to in zone statements actually exist. Add some checks that when a user adds the new `dnssec-policy` configuration, it will no longer contain existing DNSSEC configuration options. Specifically: `inline-signing`, `auto-dnssec`, `dnssec-dnskey-kskonly`, `dnssec-secure-to-insecure`, `update-check-ksk`, `dnssec-update-mode`, `dnskey-sig-validity`, and `sig-validity-interval`. Test a good kasp configuration, and some bad configurations. --- bin/named/named.conf.docbook | 17 ++ bin/tests/system/checkconf/bad-kasp1.conf | 20 +++ bin/tests/system/checkconf/bad-kasp2.conf | 22 +++ bin/tests/system/checkconf/bad-kasp3.conf | 22 +++ bin/tests/system/checkconf/bad-kasp4.conf | 23 +++ bin/tests/system/checkconf/clean.sh | 1 + bin/tests/system/checkconf/good-kasp.conf | 47 +++++ .../kasp-and-other-dnssec-options.conf | 28 +++ bin/tests/system/checkconf/tests.sh | 33 ++++ doc/arm/Bv9ARM-book.xml | 162 ++++++++++++++++++ doc/arm/dnssec-policy.grammar.xml | 25 +++ doc/arm/master.zoneopt.xml | 1 + doc/arm/slave.zoneopt.xml | 1 + doc/design/dnssec-policy | 1 - doc/misc/master.zoneopt | 1 + doc/misc/options | 11 ++ doc/misc/slave.zoneopt | 1 + lib/bind9/check.c | 157 +++++++++++++++-- lib/isccfg/include/isccfg/namedconf.h | 3 + lib/isccfg/namedconf.c | 87 ++++++++++ util/copyrights | 1 + 21 files changed, 653 insertions(+), 11 deletions(-) create mode 100644 bin/tests/system/checkconf/bad-kasp1.conf create mode 100644 bin/tests/system/checkconf/bad-kasp2.conf create mode 100644 bin/tests/system/checkconf/bad-kasp3.conf create mode 100644 bin/tests/system/checkconf/bad-kasp4.conf create mode 100644 bin/tests/system/checkconf/good-kasp.conf create mode 100644 bin/tests/system/checkconf/kasp-and-other-dnssec-options.conf create mode 100644 doc/arm/dnssec-policy.grammar.xml diff --git a/bin/named/named.conf.docbook b/bin/named/named.conf.docbook index ecae8014cc..8221d4cce5 100644 --- a/bin/named/named.conf.docbook +++ b/bin/named/named.conf.docbook @@ -842,6 +842,7 @@ view string [ class ] { dnskey-sig-validity integer; dnssec-dnskey-kskonly boolean; dnssec-loadkeys-interval integer; + dnssec-policy string; dnssec-secure-to-insecure boolean; dnssec-update-mode ( maintain | no-resign ); file quoted_string; @@ -943,6 +944,7 @@ zone string [ class ] { dnskey-sig-validity integer; dnssec-dnskey-kskonly boolean; dnssec-loadkeys-interval integer; + dnssec-policy string; dnssec-secure-to-insecure boolean; dnssec-update-mode ( maintain | no-resign ); file quoted_string; @@ -1008,6 +1010,21 @@ zone string [ class ] {
+ DNSSEC-POLICY + + +dnssec-policy string { + dnskey-ttl ttlval; + keys { ( csk | ksk | zsk ) key-directory duration integer [ integer ] ; ... }; + publish-safety duration; + retire-safety duration; + signatures-refresh duration; + signatures-validity duration; + signatures-validity-dnskey duration; +}; + + + FILES /etc/named.conf diff --git a/bin/tests/system/checkconf/bad-kasp1.conf b/bin/tests/system/checkconf/bad-kasp1.conf new file mode 100644 index 0000000000..bad8ff2090 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp1.conf @@ -0,0 +1,20 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + dnssec-policy "notatzonelevel"; +}; + +zone "example.net" { + type master; + file "example.db"; +}; + diff --git a/bin/tests/system/checkconf/bad-kasp2.conf b/bin/tests/system/checkconf/bad-kasp2.conf new file mode 100644 index 0000000000..a7b44ab6d0 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp2.conf @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +include "good-kasp.conf"; + +// Bad zone configuration because this has dnssec-policy and other DNSSEC sign +// configuration options (auto-dnssec). +zone "example.net" { + type master; + file "example.db"; + dnssec-policy "test"; + auto-dnssec maintain; + allow-update { any; }; +}; diff --git a/bin/tests/system/checkconf/bad-kasp3.conf b/bin/tests/system/checkconf/bad-kasp3.conf new file mode 100644 index 0000000000..104100dc59 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp3.conf @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +include "good-kasp.conf"; + +// Bad zone configuration because this has dnssec-policy with no matching +// dnssec-policy configuration (good-kasp.conf has "test", zone refers to +// "nosuchpolicy". +zone "example.net" { + type master; + file "example.db"; + dnssec-policy "nosuchpolicy"; +}; + diff --git a/bin/tests/system/checkconf/bad-kasp4.conf b/bin/tests/system/checkconf/bad-kasp4.conf new file mode 100644 index 0000000000..efb2cbefa8 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp4.conf @@ -0,0 +1,23 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// Bad kasp configuration because this has an invalid duration for +// signatures-refresh. +dnssec-policy "badduration" { + signatures-refresh PT20Sabcd; +}; + +zone "example.net" { + type master; + file "example.db"; + dnssec-policy "badduration"; +}; + diff --git a/bin/tests/system/checkconf/clean.sh b/bin/tests/system/checkconf/clean.sh index 9ac839b09d..989daff341 100644 --- a/bin/tests/system/checkconf/clean.sh +++ b/bin/tests/system/checkconf/clean.sh @@ -10,6 +10,7 @@ # information regarding copyright ownership. rm -f good.conf.in good.conf.out badzero.conf *.out +rm -f good-kasp.conf.in rm -rf test.keydir rm -f checkconf.out* rm -f diff.out* diff --git a/bin/tests/system/checkconf/good-kasp.conf b/bin/tests/system/checkconf/good-kasp.conf new file mode 100644 index 0000000000..804637a345 --- /dev/null +++ b/bin/tests/system/checkconf/good-kasp.conf @@ -0,0 +1,47 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * This is just a random selection of DNSSEC configuration options. + */ + +/* cut here */ +dnssec-policy "test" { + dnskey-ttl 3600; + keys { + ksk key-directory P1Y 13 256; + zsk key-directory P30D 13; + csk key-directory P30D 8 2048; + }; + publish-safety PT3600S; + retire-safety PT3600S; + signatures-refresh P3D; + signatures-validity P2W; + signatures-validity-dnskey P14D; + zone-max-ttl 86400; + zone-propagation-delay PT5M; + parent-ds-ttl 7200; + parent-propagation-delay PT1H; + parent-registration-delay P1D; +}; +options { + dnssec-policy "default"; +}; +zone "example1" { + type master; + dnssec-policy "test"; + file "example1.db"; +}; +zone "example2" { + type master; + dnssec-policy "default"; + file "example2.db"; +}; diff --git a/bin/tests/system/checkconf/kasp-and-other-dnssec-options.conf b/bin/tests/system/checkconf/kasp-and-other-dnssec-options.conf new file mode 100644 index 0000000000..d7c1bf8123 --- /dev/null +++ b/bin/tests/system/checkconf/kasp-and-other-dnssec-options.conf @@ -0,0 +1,28 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +include "good-kasp.conf"; + +zone "nsec3.net" { + type master; + file "nsec3.db"; + dnssec-policy "test"; + auto-dnssec maintain; + dnskey-sig-validity 3600; + dnssec-dnskey-kskonly yes; + dnssec-secure-to-insecure yes; + dnssec-update-mode maintain; + inline-signing yes; + sig-validity-interval 3600; + update-check-ksk yes; + allow-update { any; }; +}; + diff --git a/bin/tests/system/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh index 6146754320..45e6cefb42 100644 --- a/bin/tests/system/checkconf/tests.sh +++ b/bin/tests/system/checkconf/tests.sh @@ -466,5 +466,38 @@ grep "'geoip-use-ecs' is obsolete" < checkconf.out$n > /dev/null || ret=1 if [ $ret != 0 ]; then echo_i "failed"; ret=1; fi status=`expr $status + $ret` +n=`expr $n + 1` +echo_i "checking named-checkconf kasp warnings ($n)" +ret=0 +$CHECKCONF kasp-and-other-dnssec-options.conf > checkconf.out$n 2>&1 +grep "'auto-dnssec maintain;' cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "dnskey-sig-validity: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "dnssec-dnskey-kskonly: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "dnssec-secure-to-insecure: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "dnssec-update-mode: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "inline-signing: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "sig-validity-interval: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +grep "update-check-ksk: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo_i "check that a good 'kasp' configuration is accepted ($n)" +ret=0 +$CHECKCONF good-kasp.conf > checkconf.out$n 2>/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo_i "checking that named-checkconf prints a known good kasp config ($n)" +ret=0 +awk 'BEGIN { ok = 0; } /cut here/ { ok = 1; getline } ok == 1 { print }' good-kasp.conf > good-kasp.conf.in +[ -s good-kasp.conf.in ] || ret=1 +$CHECKCONF -p good-kasp.conf.in | grep -v '^good-kasp.conf.in:' > good-kasp.conf.out 2>&1 || ret=1 +cmp good-kasp.conf.in good-kasp.conf.out || ret=1 + +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 96fcb0fb24..47b08440ef 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -3120,6 +3120,16 @@ $ORIGIN 0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. + + + dnssec-policy + + + + describes a DNSSEC key and signing policy for zones. + + + include @@ -11004,6 +11014,147 @@ example.com CNAME rpz-tcp-only.
+
<command>dnssec-policy</command> Statement Grammar + +
+ +
<command>dnssec-policy</command> Statement Definition + and Usage + + + The dnssec-policy statement defines a key and + signing policy (KASP) for zones. + + + KASP is used to determine how one or more zones need to be signed + with DNSSEC. For example, how often RRSIG records need to be + refreshed, or what cryptographic algorithms to use. + + + You can configure multiple policies. To attach a policy to a zone + simply add dnssec-policy "policy_name" + option to the zone statement with a matching + policy name. + + + + + + dnskey-ttl + + + The TTL of the DNSKEY resource records. + Default is 3600 seconds. + + + + + + keys + + + A list of keys to use. Each line represents one key. Here is + an example (for illustration purposes only) of some possible + keys in a dnssec-policy: + + +keys { + ksk key-directory P5Y 8 2048; + zsk key-directory P30D 8; + csk key-directory P6MT12H3M15S 13; +}; + + + + This example lists three keys. The first token determines + what RRsets the key will sign. If set to + ksk the key will sign the DNSKEY, CDS, + and CDNSKEY RRsets, if set to zsk the + key will sign the other RRsets, and if set to + csk the key will sign all RRsets. + + + The following part determines where the key will be stored. + Currently keys can only be stored in the configured + key-directory. + + + The third token tells how long the key may be used. In the + example the first key has a lifetime of 5 years, the second + key may be used for 30 days and the third key has a rather + peculiar lifetime of 6 months, 12 hours, 3 minutes and 15 + seconds. + + + The last token(s) are the key's algorithm and algorithm length. + The length may be omitted as shown in the example for the + second and third key. + + + + + + publish-safety + + + A margin that is added to the publish interval in key timing + equations to give some extra time to cover unforeseen events. + Default is PT5M (5 minutes). + + + + + + retire-safety + + + A margin that is added to the retire interval in key timing + equations to give some extra time to cover unforeseen events. + Default is PT5M (5 minutes). + + + + + + signatures-refresh + + + This determines when a RRSIG record needs to be refreshed. + The signatures is renewed when the time until the expiration + time is closer than signatures-refresh. + signatures-resign interval. + Default is P5D (5 days), meaning a + signature that will expire in 5 days or sooner will be + refreshed. + + + + + + signatures-validity + + + The validity period of an RRSIG record (minus the inception + offset and jitter). Default is P2W + (2 weeks). + + + + + + signatures-validity-dnskey + + + Like signatures-validity but for DNSKEY + records. Default is P2W (2 weeks). + + + + + + +
+
<command>managed-keys</command> Statement Grammar
@@ -11878,6 +12029,17 @@ view "external" { + + dnssec-policy + + + The key and signing policy for this zone. Set to + "default" if you want to make use + of the default policy. + + + + dnssec-update-mode diff --git a/doc/arm/dnssec-policy.grammar.xml b/doc/arm/dnssec-policy.grammar.xml new file mode 100644 index 0000000000..68e27d964c --- /dev/null +++ b/doc/arm/dnssec-policy.grammar.xml @@ -0,0 +1,25 @@ + + + + + +dnssec-policy string { + dnskey-ttl ttlval; + keys { ( csk | ksk | zsk ) key-directory duration integer [ integer ] ; ... }; + publish-safety duration; + retire-safety duration; + signatures-refresh duration; + signatures-validity duration; + signatures-validity-dnskey duration; +}; + + diff --git a/doc/arm/master.zoneopt.xml b/doc/arm/master.zoneopt.xml index 0ed86ecc75..054b440492 100644 --- a/doc/arm/master.zoneopt.xml +++ b/doc/arm/master.zoneopt.xml @@ -36,6 +36,7 @@ dnskey-sig-validity integer; dnssec-dnskey-kskonly boolean; dnssec-loadkeys-interval integer; + dnssec-policy string; dnssec-secure-to-insecure boolean; dnssec-update-mode ( maintain | no-resign ); file quoted_string; diff --git a/doc/arm/slave.zoneopt.xml b/doc/arm/slave.zoneopt.xml index 63c0a4acf1..e78f296119 100644 --- a/doc/arm/slave.zoneopt.xml +++ b/doc/arm/slave.zoneopt.xml @@ -29,6 +29,7 @@ dnskey-sig-validity integer; dnssec-dnskey-kskonly boolean; dnssec-loadkeys-interval integer; + dnssec-policy string; dnssec-update-mode ( maintain | no-resign ); file quoted_string; forward ( first | only ); diff --git a/doc/design/dnssec-policy b/doc/design/dnssec-policy index 2079c8edc2..3e695a2c39 100644 --- a/doc/design/dnssec-policy +++ b/doc/design/dnssec-policy @@ -122,7 +122,6 @@ dnssec-policy "nsec3" { description "policy for zones that require zone walking mitigation"; // Signatures - signatures-resign PT2H; signatures-refresh P3D; signatures-validity P14D; signatures-validity-dnskey P14D; diff --git a/doc/misc/master.zoneopt b/doc/misc/master.zoneopt index aa55ed33e8..694d84eb69 100644 --- a/doc/misc/master.zoneopt +++ b/doc/misc/master.zoneopt @@ -23,6 +23,7 @@ zone [ ] { dnskey-sig-validity ; dnssec-dnskey-kskonly ; dnssec-loadkeys-interval ; + dnssec-policy ; dnssec-secure-to-insecure ; dnssec-update-mode ( maintain | no-resign ); file ; diff --git a/doc/misc/options b/doc/misc/options index 509cc38cf9..6f5674692c 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -25,6 +25,17 @@ dnssec-keys { ( static-key | initial-key ) ; ... }; // may occur multiple times +dnssec-policy { + dnskey-ttl ; + keys { ( csk | ksk | zsk ) key-directory + [ ]; ... }; + publish-safety ; + retire-safety ; + signatures-refresh ; + signatures-validity ; + signatures-validity-dnskey ; +}; // may occur multiple times + dyndb { }; // may occur multiple times diff --git a/doc/misc/slave.zoneopt b/doc/misc/slave.zoneopt index 750392f254..2dc3fd535c 100644 --- a/doc/misc/slave.zoneopt +++ b/doc/misc/slave.zoneopt @@ -16,6 +16,7 @@ zone [ ] { dnskey-sig-validity ; dnssec-dnskey-kskonly ; dnssec-loadkeys-interval ; + dnssec-policy ; dnssec-update-mode ( maintain | no-resign ); file ; forward ( first | only ); diff --git a/lib/bind9/check.c b/lib/bind9/check.c index 86d7482157..73fb59bf82 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -856,6 +856,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, const char *str; isc_buffer_t b; uint32_t lifetime = 3600; + bool has_dnssecpolicy = false; const char *ccalg = "siphash24"; /* @@ -948,6 +949,44 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, } } + /* + * Check dnssec-policy at the view/options level + */ + obj = NULL; + (void)cfg_map_get(options, "dnssec-policy", &obj); + if (obj != NULL) { + bool bad_kasp = true; + if (optlevel == optlevel_zone && cfg_obj_isstring(obj)) { + bad_kasp = false; + } else if (optlevel == optlevel_config) { + if (cfg_obj_islist(obj)) { + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + if (!cfg_obj_istuple( + cfg_listelt_value(element))) + { + break; + } + } + bad_kasp = false; + } + } + + if (bad_kasp) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-policy may only be activated at " + "the top level and referenced to at the " + "zone level"); + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } + } + + has_dnssecpolicy = true; + } + obj = NULL; cfg_map_get(options, "max-rsa-exponent-size", &obj); if (obj != NULL) { @@ -996,6 +1035,13 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, result = ISC_R_RANGE; } } + + if (has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "sig-validity-interval: cannot be " + "configured if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } } obj = NULL; @@ -1012,6 +1058,12 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, result = ISC_R_RANGE; } + if (has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnskey-sig-validity: cannot be " + "configured if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } } obj = NULL; @@ -1117,8 +1169,9 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) result = tresult; } - if (symtab != NULL) + if (symtab != NULL) { isc_symtab_destroy(&symtab); + } } /* @@ -1858,6 +1911,7 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, bool dlz; dns_masterformat_t masterformat; bool ddns = false; + bool has_dnssecpolicy = false; const void *clauses = NULL; const char *option = NULL; static const char *acls[] = { @@ -2070,6 +2124,42 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, if (check_nonzero(zoptions, logctx) != ISC_R_SUCCESS) result = ISC_R_FAILURE; + /* + * Check if a dnssec-policy is set. + */ + obj = NULL; + (void)cfg_map_get(zoptions, "dnssec-policy", &obj); + if (obj != NULL) { + const cfg_obj_t *kasps = NULL; + const char* kaspname = cfg_obj_asstring(obj); + + if (strcmp(kaspname, "default") == 0) { + has_dnssecpolicy = true; + } else { + (void)cfg_map_get(config, "dnssec-policy", &kasps); + for (element = cfg_list_first(kasps); element != NULL; + element = cfg_list_next(element)) + { + const char* kn = cfg_obj_asstring( + cfg_tuple_get(cfg_listelt_value(element), + "name")); + if (strcmp(kaspname, kn) == 0) { + has_dnssecpolicy = true; + } + } + } + + if (!has_dnssecpolicy) { + cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, + "zone '%s': option 'dnssec-policy %s' " + "has no matching dnssec-policy config", + znamestr, kaspname); + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } + } + } + /* * Check validity of the zone options. */ @@ -2256,19 +2346,36 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, if (res1 == ISC_R_SUCCESS) signing = cfg_obj_asboolean(obj); + if (signing && has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "inline-signing: cannot be configured if " + "dnssec-policy is also set"); + result = ISC_R_FAILURE; + } + obj = NULL; arg = "off"; res3 = cfg_map_get(zoptions, "auto-dnssec", &obj); - if (res3 == ISC_R_SUCCESS) + if (res3 == ISC_R_SUCCESS) { arg = cfg_obj_asstring(obj); - if (strcasecmp(arg, "off") != 0 && !ddns && !signing) { - cfg_obj_log(obj, logctx, ISC_LOG_ERROR, - "'auto-dnssec %s;' requires%s " - "inline-signing to be configured for " - "the zone", arg, - (ztype == CFG_ZONE_MASTER) ? - " dynamic DNS or" : ""); - result = ISC_R_FAILURE; + } + if (strcasecmp(arg, "off") != 0) { + if (!ddns && !signing) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'auto-dnssec %s;' requires%s " + "inline-signing to be configured " + "for the zone", arg, + (ztype == CFG_ZONE_MASTER) ? + " dynamic DNS or" : ""); + result = ISC_R_FAILURE; + } + if (has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'auto-dnssec %s;' cannot be " + "configured if dnssec-policy is " + "also set", arg); + result = ISC_R_FAILURE; + } } obj = NULL; @@ -2293,6 +2400,21 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, "inline-signing when used in slave zone"); result = ISC_R_FAILURE; } + if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-dnskey-kskonly: cannot be " + "configured if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } + + obj = NULL; + res1 = cfg_map_get(zoptions, "dnssec-secure-to-insecure", &obj); + if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-secure-to-insecure: cannot be " + "configured if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } obj = NULL; res1 = cfg_map_get(zoptions, "dnssec-loadkeys-interval", &obj); @@ -2315,6 +2437,21 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, "inline-signing when used in slave zone"); result = ISC_R_FAILURE; } + if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "update-check-ksk: cannot be configured " + "if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } + + obj = NULL; + res1 = cfg_map_get(zoptions, "dnssec-update-mode", &obj); + if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-update-mode: cannot be configured " + "if dnssec-policy is also set"); + result = ISC_R_FAILURE; + } } /* diff --git a/lib/isccfg/include/isccfg/namedconf.h b/lib/isccfg/include/isccfg/namedconf.h index f75e56191f..f4f2c39ba1 100644 --- a/lib/isccfg/include/isccfg/namedconf.h +++ b/lib/isccfg/include/isccfg/namedconf.h @@ -49,4 +49,7 @@ LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_keyref; /*%< Zone options */ LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_zoneopts; +/*%< DNSSEC Key and Signing Policy options */ +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_dnssecpolicyopts; + #endif /* ISCCFG_NAMEDCONF_H */ diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 3282a8b01f..100cb0979d 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -82,6 +83,7 @@ static cfg_type_t cfg_type_controls_sockaddr; static cfg_type_t cfg_type_destinationlist; static cfg_type_t cfg_type_dialuptype; static cfg_type_t cfg_type_dlz; +static cfg_type_t cfg_type_dnssecpolicy; static cfg_type_t cfg_type_dnstap; static cfg_type_t cfg_type_dnstapoutput; static cfg_type_t cfg_type_dyndb; @@ -410,6 +412,20 @@ static cfg_type_t cfg_type_zone = { &cfg_rep_tuple, zone_fields }; +/*% + * A dnssec-policy statement. + */ +static cfg_tuplefielddef_t dnssecpolicy_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "options", &cfg_type_dnssecpolicyopts, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_dnssecpolicy = { + "dnssec-policy", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, dnssecpolicy_fields +}; + /*% * A "category" clause in the "logging" statement. */ @@ -465,6 +481,40 @@ static cfg_type_t cfg_type_managedkey = { &cfg_rep_tuple, managedkey_fields }; +/*% + * DNSSEC key roles. + */ +static const char *dnsseckeyrole_enums[] = { "csk", "ksk", "zsk", NULL }; +static cfg_type_t cfg_type_dnsseckeyrole = { + "dnssec-key-role", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, + &cfg_rep_string, &dnsseckeyrole_enums +}; + +/*% + * DNSSEC key storage types. + */ +static const char *dnsseckeystore_enums[] = { "key-directory", NULL }; +static cfg_type_t cfg_type_dnsseckeystore = { + "dnssec-key-storage", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, + &cfg_rep_string, &dnsseckeystore_enums +}; + +/*% + * A dnssec key, as used in the "keys" statement in a "dnssec-policy". + */ +static cfg_tuplefielddef_t kaspkey_fields[] = { + { "role", &cfg_type_dnsseckeyrole, 0 }, + { "keystore-type", &cfg_type_dnsseckeystore, 0 }, + { "lifetime", &cfg_type_duration, 0 }, + { "algorithm", &cfg_type_uint32, 0 }, + { "length", &cfg_type_optional_uint32, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_kaspkey = { + "kaspkey", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, kaspkey_fields +}; + static keyword_type_t wild_class_kw = { "class", &cfg_type_ustring }; static cfg_type_t cfg_type_optional_wild_class = { @@ -637,6 +687,14 @@ static cfg_type_t cfg_type_dnsseckeys = { cfg_doc_bracketed_list, &cfg_rep_list, &cfg_type_managedkey }; +/*% + * A list of key entries, used in a DNSSEC Key and Signing Policy. + */ +static cfg_type_t cfg_type_kaspkeys = { + "kaspkeys", cfg_parse_bracketed_list, cfg_print_bracketed_list, + cfg_doc_bracketed_list, &cfg_rep_list, &cfg_type_kaspkey +}; + static const char *forwardtype_enums[] = { "first", "only", NULL }; static cfg_type_t cfg_type_forwardtype = { "forwardtype", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, @@ -962,6 +1020,7 @@ static cfg_clausedef_t namedconf_clauses[] = { { "acl", &cfg_type_acl, CFG_CLAUSEFLAG_MULTI }, { "controls", &cfg_type_controls, CFG_CLAUSEFLAG_MULTI }, + { "dnssec-policy", &cfg_type_dnssecpolicy, CFG_CLAUSEFLAG_MULTI }, { "logging", &cfg_type_logging, 0 }, { "lwres", &cfg_type_bracketed_text, CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_OBSOLETE }, @@ -1997,6 +2056,21 @@ static cfg_type_t cfg_type_validityinterval = { &cfg_rep_tuple, validityinterval_fields }; +/*% + * Clauses that can be found in a 'dnssec-policy' statement. + */ +static cfg_clausedef_t +dnssecpolicy_clauses[] = { + { "dnskey-ttl", &cfg_type_duration, 0 }, + { "keys", &cfg_type_kaspkeys, 0 }, + { "publish-safety", &cfg_type_duration, 0 }, + { "retire-safety", &cfg_type_duration, 0 }, + { "signatures-refresh", &cfg_type_duration, 0 }, + { "signatures-validity", &cfg_type_duration, 0 }, + { "signatures-validity-dnskey", &cfg_type_duration, 0 }, + { NULL, NULL, 0 } +}; + /*% * Clauses that can be found in a 'zone' statement, * with defaults in the 'view' or 'options' statement. @@ -2241,6 +2315,9 @@ zone_only_clauses[] = { { "dlz", &cfg_type_astring, CFG_ZONE_MASTER | CFG_ZONE_SLAVE | CFG_ZONE_REDIRECT }, + { "dnssec-policy", &cfg_type_astring, + CFG_ZONE_MASTER | CFG_ZONE_SLAVE + }, { "file", &cfg_type_qstring, CFG_ZONE_MASTER | CFG_ZONE_SLAVE | CFG_ZONE_MIRROR | CFG_ZONE_STUB | CFG_ZONE_HINT | CFG_ZONE_REDIRECT @@ -2345,6 +2422,16 @@ LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_zoneopts = { "zoneopts", cfg_parse_map, cfg_print_map, cfg_doc_map, &cfg_rep_map, zone_clausesets }; +/*% The "dnssec-policy" statement syntax. */ +static cfg_clausedef_t * +dnssecpolicy_clausesets[] = { + dnssecpolicy_clauses, + NULL +}; +LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_dnssecpolicyopts = { + "dnssecpolicyopts", cfg_parse_map, cfg_print_map, + cfg_doc_map, &cfg_rep_map, dnssecpolicy_clausesets }; + /*% The "dynamically loadable zones" statement syntax. */ static cfg_clausedef_t diff --git a/util/copyrights b/util/copyrights index 31f059b23b..1f7fef1d1d 100644 --- a/util/copyrights +++ b/util/copyrights @@ -1410,6 +1410,7 @@ ./doc/arm/delegation-only.zoneopt.xml SGML 2018,2019 ./doc/arm/dlz.xml SGML 2012,2013,2014,2015,2016,2018,2019 ./doc/arm/dnssec-keys.grammar.xml SGML 2019 +./doc/arm/dnssec-policy.grammar.xml SGML 2019 ./doc/arm/dnssec.xml SGML 2010,2011,2015,2016,2017,2018,2019 ./doc/arm/dyndb.xml SGML 2015,2016,2018,2019 ./doc/arm/forward.zoneopt.xml SGML 2018,2019 From e9ccebd94e2e3aa025d53198bf258d8143f7f73e Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 3 Sep 2019 10:39:25 +0200 Subject: [PATCH 05/43] Introduce kasp structure This stores the dnssec-policy configuration and adds methods to create, destroy, and attach/detach, as well as find a policy with the same name in a list. Also, add structures and functions for creating and destroying kasp keys. --- lib/dns/Makefile.in | 6 +- lib/dns/include/dns/kasp.h | 246 ++++++++++++++++++++++++ lib/dns/include/dns/types.h | 4 + lib/dns/include/dns/zone.h | 18 ++ lib/dns/kasp.c | 168 ++++++++++++++++ lib/dns/win32/libdns.def.in | 11 ++ lib/dns/win32/libdns.vcxproj.filters.in | 6 + lib/dns/win32/libdns.vcxproj.in | 2 + lib/dns/zone.c | 29 +++ util/copyrights | 2 + 10 files changed, 489 insertions(+), 3 deletions(-) create mode 100644 lib/dns/include/dns/kasp.h create mode 100644 lib/dns/kasp.c diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in index d99b54919a..4e28fe4b33 100644 --- a/lib/dns/Makefile.in +++ b/lib/dns/Makefile.in @@ -64,7 +64,7 @@ DNSOBJS = acl.@O@ adb.@O@ badcache.@O@ byaddr.@O@ \ db.@O@ dbiterator.@O@ dbtable.@O@ diff.@O@ dispatch.@O@ \ dlz.@O@ dns64.@O@ dnsrps.@O@ dnssec.@O@ ds.@O@ dyndb.@O@ \ ecs.@O@ fixedname.@O@ forward.@O@ \ - ipkeylist.@O@ iptable.@O@ journal.@O@ keydata.@O@ \ + ipkeylist.@O@ iptable.@O@ journal.@O@ kasp.@O@ keydata.@O@ \ keytable.@O@ lib.@O@ log.@O@ lookup.@O@ \ master.@O@ masterdump.@O@ message.@O@ \ name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ nta.@O@ \ @@ -101,8 +101,8 @@ DNSSRCS = acl.c adb.c badcache. byaddr.c \ db.c dbiterator.c dbtable.c diff.c dispatch.c \ dlz.c dns64.c dnsrps.c dnssec.c ds.c dyndb.c \ ecs.c fixedname.c forward.c \ - ipkeylist.c iptable.c journal.c keydata.c keytable.c lib.c \ - log.c lookup.c master.c masterdump.c message.c \ + ipkeylist.c iptable.c journal.c kasp.c keydata.c keytable.c \ + lib.c log.c lookup.c master.c masterdump.c message.c \ name.c ncache.c nsec.c nsec3.c nta.c \ order.c peer.c portlist.c \ rbt.c rbtdb.c rcode.c rdata.c rdatalist.c \ diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h new file mode 100644 index 0000000000..12998d46ff --- /dev/null +++ b/lib/dns/include/dns/kasp.h @@ -0,0 +1,246 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef DNS_KASP_H +#define DNS_KASP_H 1 + +/***** + ***** Module Info + *****/ + +/*! \file dns/kasp.h + * \brief + * DNSSEC Key and Signing Policy (KASP) + * + * A "kasp" is a DNSSEC policy, that determines how a zone should be + * signed and maintained. + */ + +#include + +#include +#include +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/* Stores a KASP key */ +struct dns_kasp_key { + isc_mem_t* mctx; + + /* Locked by themselves. */ + isc_refcount_t references; + + /* Under owner's locking control. */ + ISC_LINK(struct dns_kasp_key) link; + + /* Configuration */ + time_t lifetime; + uint32_t algorithm; + int length; + uint8_t role; +}; + +/* Stores a DNSSEC policy */ +struct dns_kasp { + unsigned int magic; + isc_mem_t* mctx; + char* name; + + /* Internals. */ + isc_mutex_t lock; + bool frozen; + + /* Locked by themselves. */ + isc_refcount_t references; + + /* Under owner's locking control. */ + ISC_LINK(struct dns_kasp) link; + + /* Configuration: signatures */ + uint32_t signatures_refresh; + uint32_t signatures_validity; + uint32_t signatures_validity_dnskey; + + /* Configuration: Keys */ + dns_kasp_keylist_t keys; + uint32_t dnskey_ttl; + + /* Configuration: Timings */ + uint32_t publish_safety; + uint32_t retire_safety; + + // TODO: The rest of the KASP configuration +}; + +#define DNS_KASP_MAGIC ISC_MAGIC('K','A','S','P') +#define DNS_KASP_VALID(kasp) ISC_MAGIC_VALID(kasp, DNS_KASP_MAGIC) + +/* Defaults */ +#define DNS_KASP_SIG_REFRESH (86400*5) +#define DNS_KASP_SIG_VALIDITY (86400*14) +#define DNS_KASP_SIG_VALIDITY_DNSKEY (86400*14) +#define DNS_KASP_KEY_TTL (3600) +#define DNS_KASP_PUBLISH_SAFETY (300) +#define DNS_KASP_RETIRE_SAFETY (300) + +/* Key roles */ +#define DNS_KASP_KEY_ROLE_KSK 0x01 +#define DNS_KASP_KEY_ROLE_ZSK 0x02 + +isc_result_t +dns_kasp_create(isc_mem_t *mctx, const char* name, dns_kasp_t **kaspp); +/*%< + * Create a KASP. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'name' is a valid C string. + * + *\li kaspp != NULL && *kaspp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +void +dns_kasp_attach(dns_kasp_t *source, dns_kasp_t **targetp); +/*%< + * Attach '*targetp' to 'source'. + * + * Requires: + * + *\li 'source' is a valid, frozen kasp. + * + *\li 'targetp' points to a NULL dns_kasp_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + * + *\li While *targetp is attached, the kasp will not shut down. + */ + +void +dns_kasp_detach(dns_kasp_t **kaspp); +/*%< + * Detach KASP. + * + * Requires: + * + *\li 'kaspp' points to a valid dns_kasp_t * + * + * Ensures: + * + *\li *kaspp is NULL. + */ + +void +dns_kasp_freeze(dns_kasp_t *kasp); +/*%< + * Freeze kasp. No changes can be made to kasp configuration while frozen. + * + * Requires: + * + *\li 'kasp' is a valid, unfrozen kasp. + * + * Ensures: + * + *\li 'kasp' is frozen. + */ + +void +dns_kasp_thaw(dns_kasp_t *kasp); +/*%< + * Thaw kasp. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Ensures: + * + *\li 'kasp' is no longer frozen. + */ + +const char* +dns_kasp_getname(dns_kasp_t *kasp); +/*%< + * Get kasp name. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li name of 'kasp'. + */ + +isc_result_t +dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp); +/*%< + * Search for a kasp with name 'name' in 'list'. + * If found, '*kaspp' is (strongly) attached to it. + * + * Requires: + * + *\li 'kaspp' points to a NULL dns_kasp_t *. + * + * Returns: + * + *\li #ISC_R_SUCCESS A matching kasp was found. + *\li #ISC_R_NOTFOUND No matching kasp was found. + */ + +isc_result_t +dns_kasp_key_create(isc_mem_t* mctx, dns_kasp_key_t **keyp); +/*%< + * Create a key inside a KASP. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li keyp != NULL && *keyp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +void +dns_kasp_key_destroy(dns_kasp_key_t* key); +/*%< + * Destroy a KASP key. + * + * Requires: + * + *\li 'key' is a valid KASP key. + * + *\li kasp != NULL && key != NULL + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_KASP_H */ diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 329ee7d277..728fb0c0a1 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -91,6 +91,10 @@ typedef struct dns_fwdtable dns_fwdtable_t; typedef struct dns_geoip_databases dns_geoip_databases_t; typedef struct dns_iptable dns_iptable_t; typedef uint32_t dns_iterations_t; +typedef struct dns_kasp dns_kasp_t; +typedef ISC_LIST(dns_kasp_t) dns_kasplist_t; +typedef struct dns_kasp_key dns_kasp_key_t; +typedef ISC_LIST(dns_kasp_key_t) dns_kasp_keylist_t; typedef uint16_t dns_keyflags_t; typedef struct dns_keynode dns_keynode_t; typedef ISC_LIST(dns_keynode_t) dns_keynodelist_t; diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 0ad20356db..a80301f917 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -665,6 +665,24 @@ dns_zone_unload(dns_zone_t *zone); *\li 'zone' to be a valid zone. */ +dns_kasp_t* +dns_zone_getkasp(dns_zone_t *zone); +/*%< + * Returns the current kasp. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_setkasp(dns_zone_t *zone, dns_kasp_t* kasp); +/*%< + * Set kasp for zone. If a kasp is already set, it will be detached. + * + * Requires: + *\li 'zone' to be a valid zone. + */ + void dns_zone_setoption(dns_zone_t *zone, dns_zoneopt_t option, bool value); diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c new file mode 100644 index 0000000000..ce401cdb6a --- /dev/null +++ b/lib/dns/kasp.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +isc_result_t +dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp) +{ + dns_kasp_t *kasp; + + REQUIRE(name != NULL); + REQUIRE(kaspp != NULL && *kaspp == NULL); + + kasp = isc_mem_get(mctx, sizeof(*kasp)); + kasp->mctx = NULL; + isc_mem_attach(mctx, &kasp->mctx); + + kasp->name = isc_mem_strdup(mctx, name); + isc_mutex_init(&kasp->lock); + kasp->frozen = false; + + isc_refcount_init(&kasp->references, 1); + + ISC_LINK_INIT(kasp, link); + + kasp->signatures_refresh = DNS_KASP_SIG_REFRESH; + kasp->signatures_validity = DNS_KASP_SIG_VALIDITY; + kasp->signatures_validity_dnskey = DNS_KASP_SIG_VALIDITY_DNSKEY; + + ISC_LIST_INIT(kasp->keys); + + kasp->dnskey_ttl = DNS_KASP_KEY_TTL; + kasp->publish_safety = DNS_KASP_PUBLISH_SAFETY; + kasp->retire_safety = DNS_KASP_RETIRE_SAFETY; + + // TODO: The rest of the KASP configuration + + kasp->magic = DNS_KASP_MAGIC; + *kaspp = kasp; + + return (ISC_R_SUCCESS); +} + +void +dns_kasp_attach(dns_kasp_t *source, dns_kasp_t **targetp) { + REQUIRE(DNS_KASP_VALID(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + isc_refcount_increment(&source->references); + *targetp = source; +} + +static inline void +destroy(dns_kasp_t *kasp) { + dns_kasp_key_t *key; + dns_kasp_key_t *key_next; + + for (key = ISC_LIST_HEAD(kasp->keys); key != NULL; key = key_next) { + key_next = ISC_LIST_NEXT(key, link); + ISC_LIST_UNLINK(kasp->keys, key, link); + dns_kasp_key_destroy(key); + } + ISC_INSIST(ISC_LIST_EMPTY(kasp->keys)); + + isc_mem_free(kasp->mctx, kasp->name); + isc_mem_putanddetach(&kasp->mctx, kasp, sizeof(*kasp)); +} + +void +dns_kasp_detach(dns_kasp_t **kaspp) { + REQUIRE(kaspp != NULL && DNS_KASP_VALID(*kaspp)); + dns_kasp_t *kasp = *kaspp; + *kaspp = NULL; + + if (isc_refcount_decrement(&kasp->references) == 1) { + destroy(kasp); + } +} + +void +dns_kasp_freeze(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->frozen = true; +} + +void +dns_kasp_thaw(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + kasp->frozen = false; +} + +const char* +dns_kasp_getname(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + return kasp->name; +} + +isc_result_t +dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp) +{ + dns_kasp_t *kasp; + + if (list == NULL) { + return (ISC_R_NOTFOUND); + } + INSIST(list != NULL); + + for (kasp = ISC_LIST_HEAD(*list); kasp != NULL; + kasp = ISC_LIST_NEXT(kasp, link)) + { + if (strcmp(kasp->name, name) == 0) { + break; + } + } + if (kasp == NULL) { + return (ISC_R_NOTFOUND); + } + dns_kasp_attach(kasp, kaspp); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_kasp_key_create(isc_mem_t* mctx, dns_kasp_key_t **keyp) +{ + dns_kasp_key_t *key; + + REQUIRE(keyp != NULL && *keyp == NULL); + + key = isc_mem_get(mctx, sizeof(*key)); + key->mctx = NULL; + isc_mem_attach(mctx, &key->mctx); + + ISC_LINK_INIT(key, link); + + key->lifetime = 0; + key->algorithm = 0; + key->length = -1; + key->role = 0; + *keyp = key; + return (ISC_R_SUCCESS); +} + +void +dns_kasp_key_destroy(dns_kasp_key_t* key) +{ + REQUIRE(key != NULL); + isc_mem_putanddetach(&key->mctx, key, sizeof(*key)); +} diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index a3c7a90af5..ce6e55b00c 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -413,6 +413,15 @@ dns_journal_rollforward dns_journal_set_sourceserial dns_journal_write_transaction dns_journal_writediff +dns_kasp_create +dns_kasp_attach +dns_kasp_detach +dns_kasp_freeze +dns_kasp_getname +dns_kasp_key_create +dns_kasp_key_destroy +dns_kasp_thaw +dns_kasplist_find dns_keydata_fromdnskey dns_keydata_todnskey dns_keyflags_fromtext @@ -1154,6 +1163,7 @@ dns_zone_getidleout dns_zone_getincludes dns_zone_getjournal dns_zone_getjournalsize +dns_zone_getkasp dns_zone_getkeydirectory dns_zone_getkeyopts dns_zone_getkeyvalidityinterval @@ -1255,6 +1265,7 @@ dns_zone_setidleout dns_zone_setisself dns_zone_setjournal dns_zone_setjournalsize +dns_zone_setkasp dns_zone_setkeydirectory dns_zone_setkeyopt dns_zone_setkeyvalidityinterval diff --git a/lib/dns/win32/libdns.vcxproj.filters.in b/lib/dns/win32/libdns.vcxproj.filters.in index 56f12aeba2..b06e21792b 100644 --- a/lib/dns/win32/libdns.vcxproj.filters.in +++ b/lib/dns/win32/libdns.vcxproj.filters.in @@ -116,6 +116,9 @@ Library Source Files + + Library Source Files + Library Source Files @@ -449,6 +452,9 @@ Library Header Files + + Library Header Files + Library Header Files diff --git a/lib/dns/win32/libdns.vcxproj.in b/lib/dns/win32/libdns.vcxproj.in index 43c3acef8a..9a749d28e6 100644 --- a/lib/dns/win32/libdns.vcxproj.in +++ b/lib/dns/win32/libdns.vcxproj.in @@ -150,6 +150,7 @@ + @@ -265,6 +266,7 @@ + diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 6d54179557..720df2807a 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -303,6 +304,7 @@ struct dns_zone { uint32_t sigresigninginterval; dns_view_t *view; dns_view_t *prev_view; + dns_kasp_t *kasp; dns_checkmxfunc_t checkmx; dns_checksrvfunc_t checksrv; dns_checknsfunc_t checkns; @@ -1014,6 +1016,7 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) { zone->sigvalidityinterval = 30 * 24 * 3600; zone->keyvalidityinterval = 0; zone->sigresigninginterval = 7 * 24 * 3600; + zone->kasp = NULL; zone->view = NULL; zone->prev_view = NULL; zone->checkmx = NULL; @@ -1184,6 +1187,9 @@ zone_free(dns_zone_t *zone) { isc_mem_free(zone->mctx, zone->keydirectory); } zone->keydirectory = NULL; + if (zone->kasp != NULL) { + dns_kasp_detach(&zone->kasp); + } zone->journalsize = -1; if (zone->journal != NULL) { isc_mem_free(zone->mctx, zone->journal); @@ -5537,6 +5543,29 @@ dns_zone_setflag(dns_zone_t *zone, unsigned int flags, bool value) { } } +void +dns_zone_setkasp(dns_zone_t *zone, dns_kasp_t* kasp) +{ + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->kasp != NULL) { + dns_kasp_t* oldkasp = zone->kasp; + zone->kasp = NULL; + dns_kasp_detach(&oldkasp); + } + zone->kasp = kasp; + UNLOCK_ZONE(zone); +} + +dns_kasp_t* +dns_zone_getkasp(dns_zone_t *zone) +{ + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->kasp); +} + void dns_zone_setoption(dns_zone_t *zone, dns_zoneopt_t option, bool value) diff --git a/util/copyrights b/util/copyrights index 1f7fef1d1d..cff1a45fc0 100644 --- a/util/copyrights +++ b/util/copyrights @@ -1677,6 +1677,7 @@ ./lib/dns/include/dns/ipkeylist.h C 2016,2018,2019 ./lib/dns/include/dns/iptable.h C 2007,2012,2014,2016,2018,2019 ./lib/dns/include/dns/journal.h C 1999,2000,2001,2004,2005,2006,2007,2008,2009,2011,2013,2016,2017,2018,2019 +./lib/dns/include/dns/kasp.h C 2019 ./lib/dns/include/dns/keydata.h C 2009,2016,2018,2019 ./lib/dns/include/dns/keyflags.h C 1999,2000,2001,2004,2005,2006,2007,2016,2018,2019 ./lib/dns/include/dns/keytable.h C 2000,2001,2004,2005,2007,2009,2010,2014,2015,2016,2017,2018,2019 @@ -1744,6 +1745,7 @@ ./lib/dns/ipkeylist.c C 2016,2018,2019 ./lib/dns/iptable.c C 2007,2008,2009,2013,2014,2016,2017,2018,2019 ./lib/dns/journal.c C 1999,2000,2001,2002,2004,2005,2007,2008,2009,2010,2011,2013,2014,2015,2016,2017,2018,2019 +./lib/dns/kasp.c C 2019 ./lib/dns/key.c C 2001,2004,2005,2006,2007,2011,2016,2018,2019 ./lib/dns/keydata.c C 2009,2014,2016,2018,2019 ./lib/dns/keytable.c C 2000,2001,2004,2005,2007,2009,2010,2013,2014,2015,2016,2017,2018,2019 From 48ce026dc9e2b1aacfe47020ba23df2173653bf3 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 5 Sep 2019 12:14:55 +0200 Subject: [PATCH 06/43] Sync options in dnssec-keygen Code and documentation were not in line: - Remove -z option from code - Remove -k option from docbook - Add -d option to docbook - Add -T option to docbook --- bin/dnssec/dnssec-keygen.c | 3 --- bin/dnssec/dnssec-keygen.docbook | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index 85362db2a1..8a7869c64f 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -367,9 +367,6 @@ main(int argc, char **argv) { if (*endp != '\0') fatal("-v must be followed by a number"); break; - case 'z': - /* already the default */ - break; case 'G': genonly = true; break; diff --git a/bin/dnssec/dnssec-keygen.docbook b/bin/dnssec/dnssec-keygen.docbook index 5833b79841..f194631db3 100644 --- a/bin/dnssec/dnssec-keygen.docbook +++ b/bin/dnssec/dnssec-keygen.docbook @@ -66,6 +66,7 @@ + @@ -74,7 +75,6 @@ - @@ -84,6 +84,7 @@ + @@ -207,6 +208,18 @@ + + -d bits + + + Key size in bits. For the algorithms RSASHA1, NSEC3RSASA1, + RSASHA256 and RSASHA512 the key size must be in range 1024-4096. + DH size is between 128 and 4096. This option is ignored for + algorithms ECDSAP256SHA256, ECDSAP384SHA384, ED25519 and ED448. + + + + -E engine From 2829e2941064727df133baf8e62a54eb20d2bd65 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 11 Sep 2019 10:51:36 +0200 Subject: [PATCH 07/43] dnssec-keygen: Move key gen code in own function In preparation for key generation with dnssec-policy, where multiple keys may be created. --- bin/dnssec/dnssec-keygen.c | 599 +++++++++++++++++++++---------------- 1 file changed, 341 insertions(+), 258 deletions(-) diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index 8a7869c64f..cb2aa3804c 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -67,6 +67,60 @@ usage(void) ISC_PLATFORM_NORETURN_POST; static void progress(int p); +struct keygen_ctx { + const char *predecessor; + const char *policy; + const char *configfile; + const char *directory; + char *algname; + char *nametype; + char *type; + int generator; + int protocol; + int size; + int signatory; + dns_rdataclass_t rdclass; + int options; + int dbits; + dns_ttl_t ttl; + uint16_t kskflag; + uint16_t revflag; + dns_secalg_t alg; + /* timing data */ + int prepub; + isc_stdtime_t now; + isc_stdtime_t publish; + isc_stdtime_t activate; + isc_stdtime_t inactive; + isc_stdtime_t revokekey; + isc_stdtime_t deltime; + isc_stdtime_t syncadd; + isc_stdtime_t syncdel; + bool setpub; + bool setact; + bool setinact; + bool setrev; + bool setdel; + bool setsyncadd; + bool setsyncdel; + bool unsetpub; + bool unsetact; + bool unsetinact; + bool unsetrev; + bool unsetdel; + /* how to generate the key */ + bool setttl; + bool use_nsec3; + bool genonly; + bool showprogress; + bool quiet; + bool oldstyle; +}; + +typedef struct keygen_ctx keygen_ctx_t; + +static void keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv); + static void usage(void) { fprintf(stderr, "Usage:\n"); @@ -180,52 +234,26 @@ progress(int p) int main(int argc, char **argv) { char *algname = NULL, *freeit = NULL; - char *nametype = NULL, *type = NULL; char *classname = NULL; char *endp; - dst_key_t *key = NULL; - dns_fixedname_t fname; - dns_name_t *name; - uint16_t flags = 0, kskflag = 0, revflag = 0; - dns_secalg_t alg; - bool conflict = false, null_key = false; - bool oldstyle = false; isc_mem_t *mctx = NULL; - int ch, generator = 0, param = 0; - int protocol = -1, size = -1, signatory = 0; isc_result_t ret; isc_textregion_t r; - char filename[255]; - const char *directory = NULL; - const char *predecessor = NULL; - dst_key_t *prevkey = NULL; - isc_buffer_t buf; isc_log_t *log = NULL; const char *engine = NULL; - dns_rdataclass_t rdclass; - int options = DST_TYPE_PRIVATE | DST_TYPE_PUBLIC; - int dbits = 0; - dns_ttl_t ttl = 0; - bool use_nsec3 = false; - isc_stdtime_t publish = 0, activate = 0, revokekey = 0; - isc_stdtime_t inactive = 0, deltime = 0; - isc_stdtime_t now; - int prepub = -1; - bool setpub = false, setact = false; - bool setrev = false, setinact = false; - bool setdel = false, setttl = false; - bool unsetpub = false, unsetact = false; - bool unsetrev = false, unsetinact = false; - bool unsetdel = false; - bool genonly = false; - bool show_progress = false; unsigned char c; - isc_stdtime_t syncadd = 0, syncdel = 0; - bool setsyncadd = false; - bool setsyncdel = false; + int ch; - if (argc == 1) + keygen_ctx_t ctx = { + .options = DST_TYPE_PRIVATE | DST_TYPE_PUBLIC, + .prepub = -1, + .protocol = -1, + .size = -1, + }; + + if (argc == 1) { usage(); + } #if USE_PKCS11 pk11_result_register(); @@ -261,30 +289,30 @@ main(int argc, char **argv) { isc_mem_create(&mctx); - isc_stdtime_get(&now); + isc_stdtime_get(&ctx.now); while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { switch (ch) { case '3': - use_nsec3 = true; + ctx.use_nsec3 = true; break; case 'a': algname = isc_commandline_argument; break; case 'b': - size = strtol(isc_commandline_argument, &endp, 10); - if (*endp != '\0' || size < 0) + ctx.size = strtol(isc_commandline_argument, &endp, 10); + if (*endp != '\0' || ctx.size < 0) fatal("-b requires a non-negative number"); break; case 'C': - oldstyle = true; + ctx.oldstyle = true; break; case 'c': classname = isc_commandline_argument; break; case 'd': - dbits = strtol(isc_commandline_argument, &endp, 10); - if (*endp != '\0' || dbits < 0) + ctx.dbits = strtol(isc_commandline_argument, &endp, 10); + if (*endp != '\0' || ctx.dbits < 0) fatal("-d requires a non-negative number"); break; case 'E': @@ -298,58 +326,66 @@ main(int argc, char **argv) { case 'f': c = (unsigned char)(isc_commandline_argument[0]); if (toupper(c) == 'K') - kskflag = DNS_KEYFLAG_KSK; + ctx.kskflag = DNS_KEYFLAG_KSK; else if (toupper(c) == 'R') - revflag = DNS_KEYFLAG_REVOKE; + ctx.revflag = DNS_KEYFLAG_REVOKE; else fatal("unknown flag '%s'", isc_commandline_argument); break; case 'g': - generator = strtol(isc_commandline_argument, + ctx.generator = strtol(isc_commandline_argument, &endp, 10); - if (*endp != '\0' || generator <= 0) + if (*endp != '\0' || ctx.generator <= 0) fatal("-g requires a positive number"); break; case 'K': - directory = isc_commandline_argument; - ret = try_dir(directory); + ctx.directory = isc_commandline_argument; + ret = try_dir(ctx.directory); if (ret != ISC_R_SUCCESS) fatal("cannot open directory %s: %s", - directory, isc_result_totext(ret)); + ctx.directory, isc_result_totext(ret)); break; case 'L': - ttl = strtottl(isc_commandline_argument); - setttl = true; + ctx.ttl = strtottl(isc_commandline_argument); + ctx.setttl = true; + break; break; case 'n': - nametype = isc_commandline_argument; + ctx.nametype = isc_commandline_argument; break; case 'm': break; case 'p': - protocol = strtol(isc_commandline_argument, &endp, 10); - if (*endp != '\0' || protocol < 0 || protocol > 255) + ctx.protocol = strtol(isc_commandline_argument, &endp, + 10); + if (*endp != '\0' || ctx.protocol < 0 || + ctx.protocol > 255) + { fatal("-p must be followed by a number " "[0..255]"); + } break; case 'q': - quiet = true; + ctx.quiet = true; break; case 'r': fatal("The -r option has been deprecated.\n" "System random data is always used.\n"); break; case 's': - signatory = strtol(isc_commandline_argument, - &endp, 10); - if (*endp != '\0' || signatory < 0 || signatory > 15) + ctx.signatory = strtol(isc_commandline_argument, + &endp, 10); + if (*endp != '\0' || ctx.signatory < 0 || + ctx.signatory > 15) + { fatal("-s must be followed by a number " "[0..15]"); + } break; case 'T': if (strcasecmp(isc_commandline_argument, "KEY") == 0) - options |= DST_TYPE_KEY; + ctx.options |= DST_TYPE_KEY; else if (strcasecmp(isc_commandline_argument, "DNSKEY") == 0) /* default behavior */ @@ -359,7 +395,7 @@ main(int argc, char **argv) { isc_commandline_argument); break; case 't': - type = isc_commandline_argument; + ctx.type = isc_commandline_argument; break; case 'v': endp = NULL; @@ -368,75 +404,79 @@ main(int argc, char **argv) { fatal("-v must be followed by a number"); break; case 'G': - genonly = true; + ctx.genonly = true; break; case 'P': /* -Psync ? */ if (isoptarg("sync", argv, usage)) { - if (setsyncadd) + if (ctx.setsyncadd) fatal("-P sync specified more than " "once"); - syncadd = strtotime(isc_commandline_argument, - now, now, &setsyncadd); + ctx.syncadd = strtotime( + isc_commandline_argument, + ctx.now, ctx.now, + &ctx.setsyncadd); break; } (void)isoptarg("dnskey", argv, usage); - if (setpub || unsetpub) + if (ctx.setpub || ctx.unsetpub) fatal("-P specified more than once"); - publish = strtotime(isc_commandline_argument, - now, now, &setpub); - unsetpub = !setpub; + ctx.publish = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setpub); + ctx.unsetpub = !ctx.setpub; break; case 'A': - if (setact || unsetact) + if (ctx.setact || ctx.unsetact) fatal("-A specified more than once"); - activate = strtotime(isc_commandline_argument, - now, now, &setact); - unsetact = !setact; + ctx.activate = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setact); + ctx.unsetact = !ctx.setact; break; case 'R': - if (setrev || unsetrev) + if (ctx.setrev || ctx.unsetrev) fatal("-R specified more than once"); - revokekey = strtotime(isc_commandline_argument, - now, now, &setrev); - unsetrev = !setrev; + ctx.revokekey = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setrev); + ctx.unsetrev = !ctx.setrev; break; case 'I': - if (setinact || unsetinact) + if (ctx.setinact || ctx.unsetinact) fatal("-I specified more than once"); - inactive = strtotime(isc_commandline_argument, - now, now, &setinact); - unsetinact = !setinact; + ctx.inactive = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setinact); + ctx.unsetinact = !ctx.setinact; break; case 'D': /* -Dsync ? */ if (isoptarg("sync", argv, usage)) { - if (setsyncdel) + if (ctx.setsyncdel) fatal("-D sync specified more than " "once"); - syncdel = strtotime(isc_commandline_argument, - now, now, &setsyncdel); + ctx.syncdel = strtotime( + isc_commandline_argument, + ctx.now, ctx.now, + &ctx.setsyncdel); break; } (void)isoptarg("dnskey", argv, usage); - if (setdel || unsetdel) + if (ctx.setdel || ctx.unsetdel) fatal("-D specified more than once"); - deltime = strtotime(isc_commandline_argument, - now, now, &setdel); - unsetdel = !setdel; + ctx.deltime = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setdel); + ctx.unsetdel = !ctx.setdel; break; case 'S': - predecessor = isc_commandline_argument; + ctx.predecessor = isc_commandline_argument; break; case 'i': - prepub = strtottl(isc_commandline_argument); + ctx.prepub = strtottl(isc_commandline_argument); break; case 'F': /* Reserved for FIPS mode */ @@ -462,7 +502,7 @@ main(int argc, char **argv) { } if (!isatty(0)) - quiet = true; + ctx.quiet = true; ret = dst_lib_init(mctx, engine); if (ret != ISC_R_SUCCESS) @@ -471,47 +511,91 @@ main(int argc, char **argv) { setup_logging(mctx, &log); - if (predecessor == NULL) { - if (prepub == -1) - prepub = 0; + ctx.rdclass = strtoclass(classname); + if (ctx.predecessor == NULL) { if (argc < isc_commandline_index + 1) fatal("the key name was not specified"); if (argc > isc_commandline_index + 1) fatal("extraneous arguments"); + if (algname == NULL) + fatal("no algorithm specified"); + + r.base = algname; + r.length = strlen(algname); + ret = dns_secalg_fromtext(&ctx.alg, &r); + if (ret != ISC_R_SUCCESS) { + fatal("unknown algorithm %s", algname); + } + if (!dst_algorithm_supported(ctx.alg)) { + fatal("unsupported algorithm: %s", algname); + } + } + + keygen(&ctx, mctx, argc, argv); + + cleanup_logging(&log); + dst_lib_destroy(); + dns_name_destroy(); + if (verbose > 10) + isc_mem_stats(mctx, stdout); + isc_mem_destroy(&mctx); + + if (freeit != NULL) + free(freeit); + + return (0); +} + +static void +keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) +{ + char filename[255]; + char algstr[DNS_SECALG_FORMATSIZE]; + uint16_t flags = 0; + int param = 0; + bool null_key = false; + bool conflict = false; + bool show_progress = false; + isc_buffer_t buf; + dns_name_t *name; + dns_fixedname_t fname; + isc_result_t ret; + dst_key_t* key = NULL; + dst_key_t* prevkey = NULL; + + UNUSED(argc); + + dns_secalg_format(ctx->alg, algstr, sizeof(algstr)); + + if (ctx->predecessor == NULL) { + if (ctx->prepub == -1) + ctx->prepub = 0; + name = dns_fixedname_initname(&fname); isc_buffer_init(&buf, argv[isc_commandline_index], strlen(argv[isc_commandline_index])); isc_buffer_add(&buf, strlen(argv[isc_commandline_index])); ret = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL); - if (ret != ISC_R_SUCCESS) + if (ret != ISC_R_SUCCESS) { fatal("invalid key name %s: %s", argv[isc_commandline_index], isc_result_totext(ret)); - - if (algname == NULL) { - fatal("no algorithm specified"); } - r.base = algname; - r.length = strlen(algname); - ret = dns_secalg_fromtext(&alg, &r); - if (ret != ISC_R_SUCCESS) { - fatal("unknown algorithm %s", algname); - } - if (alg == DST_ALG_DH) { - options |= DST_TYPE_KEY; + if (!dst_algorithm_supported(ctx->alg)) { + fatal("unsupported algorithm: %s", algstr); } - if (!dst_algorithm_supported(alg)) { - fatal("unsupported algorithm: %d", alg); + if (ctx->alg == DST_ALG_DH) { + ctx->options |= DST_TYPE_KEY; } - if (use_nsec3) { - switch (alg) { + if (ctx->use_nsec3) { + switch (ctx->alg) { case DST_ALG_RSASHA1: - alg = DST_ALG_NSEC3RSASHA1; + ctx->alg = DST_ALG_NSEC3RSASHA1; break; case DST_ALG_NSEC3RSASHA1: case DST_ALG_RSASHA256: @@ -522,39 +606,39 @@ main(int argc, char **argv) { case DST_ALG_ED448: break; default: - fatal("%s is incompatible with NSEC3; " - "do not use the -3 option", algname); + fatal("algorithm %s is incompatible with NSEC3" + ", do not use the -3 option", algstr); } } - if (type != NULL && (options & DST_TYPE_KEY) != 0) { - if (strcasecmp(type, "NOAUTH") == 0) { + if (ctx->type != NULL && (ctx->options & DST_TYPE_KEY) != 0) { + if (strcasecmp(ctx->type, "NOAUTH") == 0) { flags |= DNS_KEYTYPE_NOAUTH; - } else if (strcasecmp(type, "NOCONF") == 0) { + } else if (strcasecmp(ctx->type, "NOCONF") == 0) { flags |= DNS_KEYTYPE_NOCONF; - } else if (strcasecmp(type, "NOAUTHCONF") == 0) { + } else if (strcasecmp(ctx->type, "NOAUTHCONF") == 0) { flags |= (DNS_KEYTYPE_NOAUTH | DNS_KEYTYPE_NOCONF); - if (size < 0) - size = 0; - } else if (strcasecmp(type, "AUTHCONF") == 0) { + if (ctx->size < 0) + ctx->size = 0; + } else if (strcasecmp(ctx->type, "AUTHCONF") == 0) { /* nothing */; } else { - fatal("invalid type %s", type); + fatal("invalid type %s", ctx->type); } } - if (size < 0) { - switch (alg) { + if (ctx->size < 0) { + switch (ctx->alg) { case DST_ALG_RSASHA1: case DST_ALG_NSEC3RSASHA1: case DST_ALG_RSASHA256: case DST_ALG_RSASHA512: - size = 2048; + ctx->size = 2048; if (verbose > 0) { fprintf(stderr, "key size not " "specified; defaulting" - " to %d\n", size); + " to %d\n", ctx->size); } break; case DST_ALG_ECDSA256: @@ -567,25 +651,28 @@ main(int argc, char **argv) { } } - if (!oldstyle && prepub > 0) { - if (setpub && setact && (activate - prepub) < publish) + if (!ctx->oldstyle && ctx->prepub > 0) { + if (ctx->setpub && ctx->setact && + (ctx->activate - ctx->prepub) < ctx->publish) + { fatal("Activation and publication dates " "are closer together than the\n\t" "prepublication interval."); - - if (!setpub && !setact) { - setpub = setact = true; - publish = now; - activate = now + prepub; - } else if (setpub && !setact) { - setact = true; - activate = publish + prepub; - } else if (setact && !setpub) { - setpub = true; - publish = activate - prepub; } - if ((activate - prepub) < now) + if (!ctx->setpub && !ctx->setact) { + ctx->setpub = ctx->setact = true; + ctx->publish = ctx->now; + ctx->activate = ctx->now + ctx->prepub; + } else if (ctx->setpub && !ctx->setact) { + ctx->setact = true; + ctx->activate = ctx->publish + ctx->prepub; + } else if (ctx->setact && !ctx->setpub) { + ctx->setpub = true; + ctx->publish = ctx->activate - ctx->prepub; + } + + if ((ctx->activate - ctx->prepub) < ctx->now) fatal("Time until activation is shorter " "than the\n\tprepublication interval."); } @@ -594,40 +681,40 @@ main(int argc, char **argv) { isc_stdtime_t when; int major, minor; - if (prepub == -1) - prepub = (30 * 86400); + if (ctx->prepub == -1) + ctx->prepub = (30 * 86400); - if (algname != NULL) + if (ctx->alg != 0) fatal("-S and -a cannot be used together"); - if (size >= 0) + if (ctx->size >= 0) fatal("-S and -b cannot be used together"); - if (nametype != NULL) + if (ctx->nametype != NULL) fatal("-S and -n cannot be used together"); - if (type != NULL) + if (ctx->type != NULL) fatal("-S and -t cannot be used together"); - if (setpub || unsetpub) + if (ctx->setpub || ctx->unsetpub) fatal("-S and -P cannot be used together"); - if (setact || unsetact) + if (ctx->setact || ctx->unsetact) fatal("-S and -A cannot be used together"); - if (use_nsec3) + if (ctx->use_nsec3) fatal("-S and -3 cannot be used together"); - if (oldstyle) + if (ctx->oldstyle) fatal("-S and -C cannot be used together"); - if (genonly) + if (ctx->genonly) fatal("-S and -G cannot be used together"); - ret = dst_key_fromnamedfile(predecessor, directory, + ret = dst_key_fromnamedfile(ctx->predecessor, ctx->directory, DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, mctx, &prevkey); if (ret != ISC_R_SUCCESS) fatal("Invalid keyfile %s: %s", - predecessor, isc_result_totext(ret)); + ctx->predecessor, isc_result_totext(ret)); if (!dst_key_isprivate(prevkey)) - fatal("%s is not a private key", predecessor); + fatal("%s is not a private key", ctx->predecessor); name = dst_key_name(prevkey); - alg = dst_key_alg(prevkey); - size = dst_key_size(prevkey); + ctx->alg = dst_key_alg(prevkey); + ctx->size = dst_key_size(prevkey); flags = dst_key_flags(prevkey); dst_key_format(prevkey, keystr, sizeof(keystr)); @@ -643,14 +730,15 @@ main(int argc, char **argv) { "You must use dnssec-settime -A to set one " "before generating a successor.", keystr); - ret = dst_key_gettime(prevkey, DST_TIME_INACTIVE, &activate); + ret = dst_key_gettime(prevkey, DST_TIME_INACTIVE, + &ctx->activate); if (ret != ISC_R_SUCCESS) fatal("Key %s has no inactivation date.\n\t" "You must use dnssec-settime -I to set one " "before generating a successor.", keystr); - publish = activate - prepub; - if (publish < now) + ctx->publish = ctx->activate - ctx->prepub; + if (ctx->publish < ctx->now) fatal("Key %s becomes inactive\n\t" "sooner than the prepublication period " "for the new key ends.\n\t" @@ -667,91 +755,88 @@ main(int argc, char **argv) { "You can use dnssec-settime -D to " "change this.\n", program, keystr); - setpub = setact = true; + ctx->setpub = ctx->setact = true; } - switch (alg) { + switch (ctx->alg) { case DNS_KEYALG_RSASHA1: case DNS_KEYALG_NSEC3RSASHA1: case DNS_KEYALG_RSASHA256: - if (size != 0 && (size < 1024 || size > MAX_RSA)) - fatal("RSA key size %d out of range", size); + if (ctx->size != 0 && (ctx->size < 1024 || ctx->size > MAX_RSA)) + fatal("RSA key size %d out of range", ctx->size); break; case DNS_KEYALG_RSASHA512: - if (size != 0 && (size < 1024 || size > MAX_RSA)) - fatal("RSA key size %d out of range", size); + if (ctx->size != 0 && (ctx->size < 1024 || ctx->size > MAX_RSA)) + fatal("RSA key size %d out of range", ctx->size); break; case DNS_KEYALG_DH: - if (size != 0 && (size < 128 || size > 4096)) - fatal("DH key size %d out of range", size); + if (ctx->size != 0 && (ctx->size < 128 || ctx->size > 4096)) + fatal("DH key size %d out of range", ctx->size); break; case DST_ALG_ECDSA256: - size = 256; + ctx->size = 256; break; case DST_ALG_ECDSA384: - size = 384; + ctx->size = 384; break; case DST_ALG_ED25519: - size = 256; + ctx->size = 256; break; case DST_ALG_ED448: - size = 456; + ctx->size = 456; break; } - if (alg != DNS_KEYALG_DH && generator != 0) + if (ctx->alg != DNS_KEYALG_DH && ctx->generator != 0) fatal("specified DH generator for a non-DH key"); - if (nametype == NULL) { - if ((options & DST_TYPE_KEY) != 0) /* KEY */ + if (ctx->nametype == NULL) { + if ((ctx->options & DST_TYPE_KEY) != 0) /* KEY */ fatal("no nametype specified"); flags |= DNS_KEYOWNER_ZONE; /* DNSKEY */ - } else if (strcasecmp(nametype, "zone") == 0) + } else if (strcasecmp(ctx->nametype, "zone") == 0) flags |= DNS_KEYOWNER_ZONE; - else if ((options & DST_TYPE_KEY) != 0) { /* KEY */ - if (strcasecmp(nametype, "host") == 0 || - strcasecmp(nametype, "entity") == 0) + else if ((ctx->options & DST_TYPE_KEY) != 0) { /* KEY */ + if (strcasecmp(ctx->nametype, "host") == 0 || + strcasecmp(ctx->nametype, "entity") == 0) flags |= DNS_KEYOWNER_ENTITY; - else if (strcasecmp(nametype, "user") == 0) + else if (strcasecmp(ctx->nametype, "user") == 0) flags |= DNS_KEYOWNER_USER; else - fatal("invalid KEY nametype %s", nametype); - } else if (strcasecmp(nametype, "other") != 0) /* DNSKEY */ - fatal("invalid DNSKEY nametype %s", nametype); + fatal("invalid KEY nametype %s", ctx->nametype); + } else if (strcasecmp(ctx->nametype, "other") != 0) /* DNSKEY */ + fatal("invalid DNSKEY nametype %s", ctx->nametype); - rdclass = strtoclass(classname); + if (ctx->directory == NULL) + ctx->directory = "."; - if (directory == NULL) - directory = "."; - - if ((options & DST_TYPE_KEY) != 0) /* KEY */ - flags |= signatory; + if ((ctx->options & DST_TYPE_KEY) != 0) /* KEY */ + flags |= ctx->signatory; else if ((flags & DNS_KEYOWNER_ZONE) != 0) { /* DNSKEY */ - flags |= kskflag; - flags |= revflag; + flags |= ctx->kskflag; + flags |= ctx->revflag; } - if (protocol == -1) - protocol = DNS_KEYPROTO_DNSSEC; - else if ((options & DST_TYPE_KEY) == 0 && - protocol != DNS_KEYPROTO_DNSSEC) - fatal("invalid DNSKEY protocol: %d", protocol); + if (ctx->protocol == -1) + ctx->protocol = DNS_KEYPROTO_DNSSEC; + else if ((ctx->options & DST_TYPE_KEY) == 0 && + ctx->protocol != DNS_KEYPROTO_DNSSEC) + fatal("invalid DNSKEY protocol: %d", ctx->protocol); if ((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) { - if (size > 0) + if (ctx->size > 0) fatal("specified null key with non-zero size"); if ((flags & DNS_KEYFLAG_SIGNATORYMASK) != 0) fatal("specified null key with signing authority"); } if ((flags & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE && - alg == DNS_KEYALG_DH) + ctx->alg == DNS_KEYALG_DH) { - fatal("a key with algorithm '%s' cannot be a zone key", - algname); + fatal("a key with algorithm %s cannot be a zone key", algstr); } - switch(alg) { + switch(ctx->alg) { case DNS_KEYALG_RSASHA1: case DNS_KEYALG_NSEC3RSASHA1: case DNS_KEYALG_RSASHA256: @@ -760,7 +845,7 @@ main(int argc, char **argv) { break; case DNS_KEYALG_DH: - param = generator; + param = ctx->generator; break; case DST_ALG_ECDSA256: @@ -779,31 +864,28 @@ main(int argc, char **argv) { do { conflict = false; - if (!quiet && show_progress) { + if (!ctx->quiet && show_progress) { fprintf(stderr, "Generating key pair."); - ret = dst_key_generate(name, alg, size, param, flags, - protocol, rdclass, mctx, &key, + ret = dst_key_generate(name, ctx->alg, ctx->size, + param, flags, ctx->protocol, + ctx->rdclass, mctx, &key, &progress); putc('\n', stderr); fflush(stderr); } else { - ret = dst_key_generate(name, alg, size, param, flags, - protocol, rdclass, mctx, &key, - NULL); + ret = dst_key_generate(name, ctx->alg, ctx->size, + param, flags, ctx->protocol, + ctx->rdclass, mctx, &key, NULL); } if (ret != ISC_R_SUCCESS) { char namestr[DNS_NAME_FORMATSIZE]; - char algstr[DNS_SECALG_FORMATSIZE]; dns_name_format(name, namestr, sizeof(namestr)); - dns_secalg_format(alg, algstr, sizeof(algstr)); fatal("failed to generate key %s/%s: %s\n", namestr, algstr, isc_result_totext(ret)); - /* NOTREACHED */ - exit(-1); } - dst_key_setbits(key, dbits); + dst_key_setbits(key, ctx->dbits); /* * Set key timing metadata (unless using -C) @@ -821,66 +903,77 @@ main(int argc, char **argv) { * an error; the inactivation date of the predecessor key * must be updated before a successor key can be created. */ - if (!oldstyle) { - dst_key_settime(key, DST_TIME_CREATED, now); + if (!ctx->oldstyle) { + dst_key_settime(key, DST_TIME_CREATED, ctx->now); - if (genonly && (setpub || setact)) + if (ctx->genonly && (ctx->setpub || ctx->setact)) fatal("cannot use -G together with " "-P or -A options"); - if (setpub) - dst_key_settime(key, DST_TIME_PUBLISH, publish); - else if (setact && !unsetpub) + if (ctx->setpub) dst_key_settime(key, DST_TIME_PUBLISH, - activate - prepub); - else if (!genonly && !unsetpub) - dst_key_settime(key, DST_TIME_PUBLISH, now); + ctx->publish); + else if (ctx->setact && !ctx->unsetpub) + dst_key_settime(key, DST_TIME_PUBLISH, + ctx->activate - ctx->prepub); + else if (!ctx->genonly && !ctx->unsetpub) + dst_key_settime(key, DST_TIME_PUBLISH, + ctx->now); - if (setact) + if (ctx->setact) dst_key_settime(key, DST_TIME_ACTIVATE, - activate); - else if (!genonly && !unsetact) - dst_key_settime(key, DST_TIME_ACTIVATE, now); + ctx->activate); + else if (!ctx->genonly && !ctx->unsetact) + dst_key_settime(key, DST_TIME_ACTIVATE, + ctx->now); - if (setrev) { - if (kskflag == 0) + if (ctx->setrev) { + if (ctx->kskflag == 0) fprintf(stderr, "%s: warning: Key is " "not flagged as a KSK, but -R " "was used. Revoking a ZSK is " "legal, but undefined.\n", program); - dst_key_settime(key, DST_TIME_REVOKE, revokekey); + dst_key_settime(key, DST_TIME_REVOKE, + ctx->revokekey); } - if (setinact) + if (ctx->setinact) dst_key_settime(key, DST_TIME_INACTIVE, - inactive); + ctx->inactive); - if (setdel) { - if (setinact && deltime < inactive) + if (ctx->setdel) { + if (ctx->setinact && + ctx->deltime < ctx->inactive) + { fprintf(stderr, "%s: warning: Key is " "scheduled to be deleted " "before it is scheduled to be " "made inactive.\n", program); - dst_key_settime(key, DST_TIME_DELETE, deltime); + } + dst_key_settime(key, DST_TIME_DELETE, + ctx->deltime); } - if (setsyncadd) + if (ctx->setsyncadd) dst_key_settime(key, DST_TIME_SYNCPUBLISH, - syncadd); + ctx->syncadd); - if (setsyncdel) + if (ctx->setsyncdel) dst_key_settime(key, DST_TIME_SYNCDELETE, - syncdel); + ctx->syncdel); } else { - if (setpub || setact || setrev || setinact || - setdel || unsetpub || unsetact || - unsetrev || unsetinact || unsetdel || genonly || - setsyncadd || setsyncdel) + if (ctx->setpub || ctx->setact || ctx->setrev || + ctx->setinact || ctx->setdel || ctx->unsetpub || + ctx->unsetact || ctx->unsetrev || + ctx->unsetinact || ctx->unsetdel || ctx->genonly || + ctx->setsyncadd || ctx->setsyncdel) + { fatal("cannot use -C together with " "-P, -A, -R, -I, -D, or -G options"); + } /* * Compatibility mode: Private-key-format * should be set to 1.2. @@ -889,15 +982,15 @@ main(int argc, char **argv) { } /* Set the default key TTL */ - if (setttl) - dst_key_setttl(key, ttl); + if (ctx->setttl) + dst_key_setttl(key, ctx->ttl); /* * Do not overwrite an existing key, or create a key * if there is a risk of ID collision due to this key * or another key being revoked. */ - if (key_collision(key, name, directory, mctx, NULL)) { + if (key_collision(key, name, ctx->directory, mctx, NULL)) { conflict = true; if (null_key) { dst_key_free(&key); @@ -907,7 +1000,8 @@ main(int argc, char **argv) { if (verbose > 0) { isc_buffer_clear(&buf); ret = dst_key_buildfilename(key, 0, - directory, &buf); + ctx->directory, + &buf); if (ret == ISC_R_SUCCESS) fprintf(stderr, "%s: %s already exists, or " @@ -925,7 +1019,7 @@ main(int argc, char **argv) { fatal("cannot generate a null key due to possible key ID " "collision"); - ret = dst_key_tofile(key, options, directory); + ret = dst_key_tofile(key, ctx->options, ctx->directory); if (ret != ISC_R_SUCCESS) { char keystr[DST_KEY_FORMATSIZE]; dst_key_format(key, keystr, sizeof(keystr)); @@ -940,18 +1034,7 @@ main(int argc, char **argv) { isc_result_totext(ret)); printf("%s\n", filename); dst_key_free(&key); - if (prevkey != NULL) + if (prevkey != NULL) { dst_key_free(&prevkey); - - cleanup_logging(&log); - dst_lib_destroy(); - dns_name_destroy(); - if (verbose > 10) - isc_mem_stats(mctx, stdout); - isc_mem_destroy(&mctx); - - if (freeit != NULL) - free(freeit); - - return (0); + } } From 1a9692f5c8312f678887e86de33462b2543fe58f Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 11 Sep 2019 10:53:53 +0200 Subject: [PATCH 08/43] dnssec-keygen: Move keygen function above main This is done in a separate commit to make diff easier. --- bin/dnssec/dnssec-keygen.c | 639 ++++++++++++++++++------------------- 1 file changed, 318 insertions(+), 321 deletions(-) diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index cb2aa3804c..54ba5dce65 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -119,8 +119,6 @@ struct keygen_ctx { typedef struct keygen_ctx keygen_ctx_t; -static void keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv); - static void usage(void) { fprintf(stderr, "Usage:\n"); @@ -231,323 +229,6 @@ progress(int p) (void) fflush(stderr); } -int -main(int argc, char **argv) { - char *algname = NULL, *freeit = NULL; - char *classname = NULL; - char *endp; - isc_mem_t *mctx = NULL; - isc_result_t ret; - isc_textregion_t r; - isc_log_t *log = NULL; - const char *engine = NULL; - unsigned char c; - int ch; - - keygen_ctx_t ctx = { - .options = DST_TYPE_PRIVATE | DST_TYPE_PUBLIC, - .prepub = -1, - .protocol = -1, - .size = -1, - }; - - if (argc == 1) { - usage(); - } - -#if USE_PKCS11 - pk11_result_register(); -#endif - dns_result_register(); - - isc_commandline_errprint = false; - - /* - * Process memory debugging argument first. - */ -#define CMDLINE_FLAGS "3A:a:b:Cc:D:d:E:eFf:Gg:hI:i:K:L:m:n:P:p:qR:r:S:s:T:t:" \ - "v:V" - while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { - switch (ch) { - case 'm': - if (strcasecmp(isc_commandline_argument, "record") == 0) - isc_mem_debugging |= ISC_MEM_DEBUGRECORD; - if (strcasecmp(isc_commandline_argument, "trace") == 0) - isc_mem_debugging |= ISC_MEM_DEBUGTRACE; - if (strcasecmp(isc_commandline_argument, "usage") == 0) - isc_mem_debugging |= ISC_MEM_DEBUGUSAGE; - if (strcasecmp(isc_commandline_argument, "size") == 0) - isc_mem_debugging |= ISC_MEM_DEBUGSIZE; - if (strcasecmp(isc_commandline_argument, "mctx") == 0) - isc_mem_debugging |= ISC_MEM_DEBUGCTX; - break; - default: - break; - } - } - isc_commandline_reset = true; - - isc_mem_create(&mctx); - - isc_stdtime_get(&ctx.now); - - while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { - switch (ch) { - case '3': - ctx.use_nsec3 = true; - break; - case 'a': - algname = isc_commandline_argument; - break; - case 'b': - ctx.size = strtol(isc_commandline_argument, &endp, 10); - if (*endp != '\0' || ctx.size < 0) - fatal("-b requires a non-negative number"); - break; - case 'C': - ctx.oldstyle = true; - break; - case 'c': - classname = isc_commandline_argument; - break; - case 'd': - ctx.dbits = strtol(isc_commandline_argument, &endp, 10); - if (*endp != '\0' || ctx.dbits < 0) - fatal("-d requires a non-negative number"); - break; - case 'E': - engine = isc_commandline_argument; - break; - case 'e': - fprintf(stderr, - "phased-out option -e " - "(was 'use (RSA) large exponent')\n"); - break; - case 'f': - c = (unsigned char)(isc_commandline_argument[0]); - if (toupper(c) == 'K') - ctx.kskflag = DNS_KEYFLAG_KSK; - else if (toupper(c) == 'R') - ctx.revflag = DNS_KEYFLAG_REVOKE; - else - fatal("unknown flag '%s'", - isc_commandline_argument); - break; - case 'g': - ctx.generator = strtol(isc_commandline_argument, - &endp, 10); - if (*endp != '\0' || ctx.generator <= 0) - fatal("-g requires a positive number"); - break; - case 'K': - ctx.directory = isc_commandline_argument; - ret = try_dir(ctx.directory); - if (ret != ISC_R_SUCCESS) - fatal("cannot open directory %s: %s", - ctx.directory, isc_result_totext(ret)); - break; - case 'L': - ctx.ttl = strtottl(isc_commandline_argument); - ctx.setttl = true; - break; - break; - case 'n': - ctx.nametype = isc_commandline_argument; - break; - case 'm': - break; - case 'p': - ctx.protocol = strtol(isc_commandline_argument, &endp, - 10); - if (*endp != '\0' || ctx.protocol < 0 || - ctx.protocol > 255) - { - fatal("-p must be followed by a number " - "[0..255]"); - } - break; - case 'q': - ctx.quiet = true; - break; - case 'r': - fatal("The -r option has been deprecated.\n" - "System random data is always used.\n"); - break; - case 's': - ctx.signatory = strtol(isc_commandline_argument, - &endp, 10); - if (*endp != '\0' || ctx.signatory < 0 || - ctx.signatory > 15) - { - fatal("-s must be followed by a number " - "[0..15]"); - } - break; - case 'T': - if (strcasecmp(isc_commandline_argument, "KEY") == 0) - ctx.options |= DST_TYPE_KEY; - else if (strcasecmp(isc_commandline_argument, - "DNSKEY") == 0) - /* default behavior */ - ; - else - fatal("unknown type '%s'", - isc_commandline_argument); - break; - case 't': - ctx.type = isc_commandline_argument; - break; - case 'v': - endp = NULL; - verbose = strtol(isc_commandline_argument, &endp, 0); - if (*endp != '\0') - fatal("-v must be followed by a number"); - break; - case 'G': - ctx.genonly = true; - break; - case 'P': - /* -Psync ? */ - if (isoptarg("sync", argv, usage)) { - if (ctx.setsyncadd) - fatal("-P sync specified more than " - "once"); - - ctx.syncadd = strtotime( - isc_commandline_argument, - ctx.now, ctx.now, - &ctx.setsyncadd); - break; - } - (void)isoptarg("dnskey", argv, usage); - if (ctx.setpub || ctx.unsetpub) - fatal("-P specified more than once"); - - ctx.publish = strtotime(isc_commandline_argument, - ctx.now, ctx.now, &ctx.setpub); - ctx.unsetpub = !ctx.setpub; - break; - case 'A': - if (ctx.setact || ctx.unsetact) - fatal("-A specified more than once"); - - ctx.activate = strtotime(isc_commandline_argument, - ctx.now, ctx.now, &ctx.setact); - ctx.unsetact = !ctx.setact; - break; - case 'R': - if (ctx.setrev || ctx.unsetrev) - fatal("-R specified more than once"); - - ctx.revokekey = strtotime(isc_commandline_argument, - ctx.now, ctx.now, &ctx.setrev); - ctx.unsetrev = !ctx.setrev; - break; - case 'I': - if (ctx.setinact || ctx.unsetinact) - fatal("-I specified more than once"); - - ctx.inactive = strtotime(isc_commandline_argument, - ctx.now, ctx.now, &ctx.setinact); - ctx.unsetinact = !ctx.setinact; - break; - case 'D': - /* -Dsync ? */ - if (isoptarg("sync", argv, usage)) { - if (ctx.setsyncdel) - fatal("-D sync specified more than " - "once"); - - ctx.syncdel = strtotime( - isc_commandline_argument, - ctx.now, ctx.now, - &ctx.setsyncdel); - break; - } - (void)isoptarg("dnskey", argv, usage); - if (ctx.setdel || ctx.unsetdel) - fatal("-D specified more than once"); - - ctx.deltime = strtotime(isc_commandline_argument, - ctx.now, ctx.now, &ctx.setdel); - ctx.unsetdel = !ctx.setdel; - break; - case 'S': - ctx.predecessor = isc_commandline_argument; - break; - case 'i': - ctx.prepub = strtottl(isc_commandline_argument); - break; - case 'F': - /* Reserved for FIPS mode */ - /* FALLTHROUGH */ - case '?': - if (isc_commandline_option != '?') - fprintf(stderr, "%s: invalid argument -%c\n", - program, isc_commandline_option); - /* FALLTHROUGH */ - case 'h': - /* Does not return. */ - usage(); - - case 'V': - /* Does not return. */ - version(program); - - default: - fprintf(stderr, "%s: unhandled option -%c\n", - program, isc_commandline_option); - exit(1); - } - } - - if (!isatty(0)) - ctx.quiet = true; - - ret = dst_lib_init(mctx, engine); - if (ret != ISC_R_SUCCESS) - fatal("could not initialize dst: %s", - isc_result_totext(ret)); - - setup_logging(mctx, &log); - - ctx.rdclass = strtoclass(classname); - - if (ctx.predecessor == NULL) { - if (argc < isc_commandline_index + 1) - fatal("the key name was not specified"); - if (argc > isc_commandline_index + 1) - fatal("extraneous arguments"); - - if (algname == NULL) - fatal("no algorithm specified"); - - r.base = algname; - r.length = strlen(algname); - ret = dns_secalg_fromtext(&ctx.alg, &r); - if (ret != ISC_R_SUCCESS) { - fatal("unknown algorithm %s", algname); - } - if (!dst_algorithm_supported(ctx.alg)) { - fatal("unsupported algorithm: %s", algname); - } - } - - keygen(&ctx, mctx, argc, argv); - - cleanup_logging(&log); - dst_lib_destroy(); - dns_name_destroy(); - if (verbose > 10) - isc_mem_stats(mctx, stdout); - isc_mem_destroy(&mctx); - - if (freeit != NULL) - free(freeit); - - return (0); -} - static void keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { @@ -912,7 +593,7 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) if (ctx->setpub) dst_key_settime(key, DST_TIME_PUBLISH, - ctx->publish); + ctx->publish); else if (ctx->setact && !ctx->unsetpub) dst_key_settime(key, DST_TIME_PUBLISH, ctx->activate - ctx->prepub); @@ -963,7 +644,6 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) if (ctx->setsyncdel) dst_key_settime(key, DST_TIME_SYNCDELETE, ctx->syncdel); - } else { if (ctx->setpub || ctx->setact || ctx->setrev || ctx->setinact || ctx->setdel || ctx->unsetpub || @@ -1033,8 +713,325 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) fatal("dst_key_buildfilename returned: %s\n", isc_result_totext(ret)); printf("%s\n", filename); + dst_key_free(&key); if (prevkey != NULL) { dst_key_free(&prevkey); } } + +int +main(int argc, char **argv) { + char *algname = NULL, *freeit = NULL; + char *classname = NULL; + char *endp; + isc_mem_t *mctx = NULL; + isc_result_t ret; + isc_textregion_t r; + isc_log_t *log = NULL; + const char *engine = NULL; + unsigned char c; + int ch; + + keygen_ctx_t ctx = { + .options = DST_TYPE_PRIVATE | DST_TYPE_PUBLIC, + .prepub = -1, + .protocol = -1, + .size = -1, + }; + + if (argc == 1) { + usage(); + } + +#if USE_PKCS11 + pk11_result_register(); +#endif + dns_result_register(); + + isc_commandline_errprint = false; + + /* + * Process memory debugging argument first. + */ +#define CMDLINE_FLAGS "3A:a:b:Cc:D:d:E:eFf:Gg:hI:i:K:L:m:n:P:p:qR:r:S:s:T:t:" \ + "v:V" + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case 'm': + if (strcasecmp(isc_commandline_argument, "record") == 0) + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + if (strcasecmp(isc_commandline_argument, "trace") == 0) + isc_mem_debugging |= ISC_MEM_DEBUGTRACE; + if (strcasecmp(isc_commandline_argument, "usage") == 0) + isc_mem_debugging |= ISC_MEM_DEBUGUSAGE; + if (strcasecmp(isc_commandline_argument, "size") == 0) + isc_mem_debugging |= ISC_MEM_DEBUGSIZE; + if (strcasecmp(isc_commandline_argument, "mctx") == 0) + isc_mem_debugging |= ISC_MEM_DEBUGCTX; + break; + default: + break; + } + } + isc_commandline_reset = true; + + isc_mem_create(&mctx); + isc_stdtime_get(&ctx.now); + + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case '3': + ctx.use_nsec3 = true; + break; + case 'a': + algname = isc_commandline_argument; + break; + case 'b': + ctx.size = strtol(isc_commandline_argument, &endp, 10); + if (*endp != '\0' || ctx.size < 0) + fatal("-b requires a non-negative number"); + break; + case 'C': + ctx.oldstyle = true; + break; + case 'c': + classname = isc_commandline_argument; + break; + case 'd': + ctx.dbits = strtol(isc_commandline_argument, &endp, 10); + if (*endp != '\0' || ctx.dbits < 0) + fatal("-d requires a non-negative number"); + break; + case 'E': + engine = isc_commandline_argument; + break; + case 'e': + fprintf(stderr, + "phased-out option -e " + "(was 'use (RSA) large exponent')\n"); + break; + case 'f': + c = (unsigned char)(isc_commandline_argument[0]); + if (toupper(c) == 'K') + ctx.kskflag = DNS_KEYFLAG_KSK; + else if (toupper(c) == 'R') + ctx.revflag = DNS_KEYFLAG_REVOKE; + else + fatal("unknown flag '%s'", + isc_commandline_argument); + break; + case 'g': + ctx.generator = strtol(isc_commandline_argument, + &endp, 10); + if (*endp != '\0' || ctx.generator <= 0) + fatal("-g requires a positive number"); + break; + case 'K': + ctx.directory = isc_commandline_argument; + ret = try_dir(ctx.directory); + if (ret != ISC_R_SUCCESS) + fatal("cannot open directory %s: %s", + ctx.directory, isc_result_totext(ret)); + break; + case 'L': + ctx.ttl = strtottl(isc_commandline_argument); + ctx.setttl = true; + break; + break; + case 'n': + ctx.nametype = isc_commandline_argument; + break; + case 'm': + break; + case 'p': + ctx.protocol = strtol(isc_commandline_argument, &endp, + 10); + if (*endp != '\0' || ctx.protocol < 0 || + ctx.protocol > 255) + { + fatal("-p must be followed by a number " + "[0..255]"); + } + break; + case 'q': + ctx.quiet = true; + break; + case 'r': + fatal("The -r option has been deprecated.\n" + "System random data is always used.\n"); + break; + case 's': + ctx.signatory = strtol(isc_commandline_argument, + &endp, 10); + if (*endp != '\0' || ctx.signatory < 0 || + ctx.signatory > 15) + { + fatal("-s must be followed by a number " + "[0..15]"); + } + break; + case 'T': + if (strcasecmp(isc_commandline_argument, "KEY") == 0) + ctx.options |= DST_TYPE_KEY; + else if (strcasecmp(isc_commandline_argument, + "DNSKEY") == 0) + /* default behavior */ + ; + else + fatal("unknown type '%s'", + isc_commandline_argument); + break; + case 't': + ctx.type = isc_commandline_argument; + break; + case 'v': + endp = NULL; + verbose = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') + fatal("-v must be followed by a number"); + break; + case 'G': + ctx.genonly = true; + break; + case 'P': + /* -Psync ? */ + if (isoptarg("sync", argv, usage)) { + if (ctx.setsyncadd) + fatal("-P sync specified more than " + "once"); + + ctx.syncadd = strtotime( + isc_commandline_argument, + ctx.now, ctx.now, + &ctx.setsyncadd); + break; + } + (void)isoptarg("dnskey", argv, usage); + if (ctx.setpub || ctx.unsetpub) + fatal("-P specified more than once"); + + ctx.publish = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setpub); + ctx.unsetpub = !ctx.setpub; + break; + case 'A': + if (ctx.setact || ctx.unsetact) + fatal("-A specified more than once"); + + ctx.activate = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setact); + ctx.unsetact = !ctx.setact; + break; + case 'R': + if (ctx.setrev || ctx.unsetrev) + fatal("-R specified more than once"); + + ctx.revokekey = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setrev); + ctx.unsetrev = !ctx.setrev; + break; + case 'I': + if (ctx.setinact || ctx.unsetinact) + fatal("-I specified more than once"); + + ctx.inactive = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setinact); + ctx.unsetinact = !ctx.setinact; + break; + case 'D': + /* -Dsync ? */ + if (isoptarg("sync", argv, usage)) { + if (ctx.setsyncdel) + fatal("-D sync specified more than " + "once"); + + ctx.syncdel = strtotime( + isc_commandline_argument, + ctx.now, ctx.now, + &ctx.setsyncdel); + break; + } + (void)isoptarg("dnskey", argv, usage); + if (ctx.setdel || ctx.unsetdel) + fatal("-D specified more than once"); + + ctx.deltime = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setdel); + ctx.unsetdel = !ctx.setdel; + break; + case 'S': + ctx.predecessor = isc_commandline_argument; + break; + case 'i': + ctx.prepub = strtottl(isc_commandline_argument); + break; + case 'F': + /* Reserved for FIPS mode */ + /* FALLTHROUGH */ + case '?': + if (isc_commandline_option != '?') + fprintf(stderr, "%s: invalid argument -%c\n", + program, isc_commandline_option); + /* FALLTHROUGH */ + case 'h': + /* Does not return. */ + usage(); + + case 'V': + /* Does not return. */ + version(program); + + default: + fprintf(stderr, "%s: unhandled option -%c\n", + program, isc_commandline_option); + exit(1); + } + } + + if (!isatty(0)) + ctx.quiet = true; + + ret = dst_lib_init(mctx, engine); + if (ret != ISC_R_SUCCESS) + fatal("could not initialize dst: %s", + isc_result_totext(ret)); + + setup_logging(mctx, &log); + + ctx.rdclass = strtoclass(classname); + + if (ctx.predecessor == NULL) { + if (argc < isc_commandline_index + 1) + fatal("the key name was not specified"); + if (argc > isc_commandline_index + 1) + fatal("extraneous arguments"); + + if (algname == NULL) + fatal("no algorithm specified"); + + r.base = algname; + r.length = strlen(algname); + ret = dns_secalg_fromtext(&ctx.alg, &r); + if (ret != ISC_R_SUCCESS) { + fatal("unknown algorithm %s", algname); + } + if (!dst_algorithm_supported(ctx.alg)) { + fatal("unsupported algorithm: %s", algname); + } + } + + keygen(&ctx, mctx, argc, argv); + + cleanup_logging(&log); + dst_lib_destroy(); + dns_name_destroy(); + if (verbose > 10) + isc_mem_stats(mctx, stdout); + isc_mem_destroy(&mctx); + + if (freeit != NULL) + free(freeit); + + return (0); +} From 7bfac50336f17035325441d56b2193b2e03ddcb5 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 11 Sep 2019 10:58:44 +0200 Subject: [PATCH 09/43] Add code for creating kasp from config Add code for creating, configuring, and destroying KASP keys. When using the default policy, create one CSK, no rollover. --- lib/isccfg/Makefile.in | 4 +- lib/isccfg/include/isccfg/kaspconf.h | 56 +++++ lib/isccfg/kaspconf.c | 206 ++++++++++++++++++ lib/isccfg/win32/libisccfg.def | 1 + lib/isccfg/win32/libisccfg.vcxproj.filters.in | 8 +- lib/isccfg/win32/libisccfg.vcxproj.in | 2 + util/copyrights | 2 + 7 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 lib/isccfg/include/isccfg/kaspconf.h create mode 100644 lib/isccfg/kaspconf.c diff --git a/lib/isccfg/Makefile.in b/lib/isccfg/Makefile.in index a04e88bcaa..269f4c6963 100644 --- a/lib/isccfg/Makefile.in +++ b/lib/isccfg/Makefile.in @@ -35,11 +35,11 @@ SUBDIRS = include TESTDIRS = @UNITTESTS@ # Alphabetically -OBJS = aclconf.@O@ dnsconf.@O@ log.@O@ namedconf.@O@ \ +OBJS = aclconf.@O@ dnsconf.@O@ kaspconf.@O@ log.@O@ namedconf.@O@ \ parser.@O@ version.@O@ # Alphabetically -SRCS = aclconf.c dnsconf.c log.c namedconf.c \ +SRCS = aclconf.c dnsconf.c kaspconf.c log.c namedconf.c \ parser.c version.c TARGETS = timestamp diff --git a/lib/isccfg/include/isccfg/kaspconf.h b/lib/isccfg/include/isccfg/kaspconf.h new file mode 100644 index 0000000000..9d18f445da --- /dev/null +++ b/lib/isccfg/include/isccfg/kaspconf.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +#ifndef ISCCFG_KASPCONF_H +#define ISCCFG_KASPCONF_H 1 + +#include + +#include + +#include + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +cfg_kasp_fromconfig(const cfg_obj_t *config, isc_mem_t* mctx, + dns_kasplist_t *kasplist, dns_kasp_t **kaspp); +/*%< + * Create and configure a KASP. If 'config' is NULL, the default configuration + * is used. If a 'kasplist' is provided, a lookup happens and if a KASP + * already exists with the same name, no new KASP is created, and no attach to + * 'kaspp' happens. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'name' is a valid C string. + * + *\li kaspp != NULL && *kaspp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS If creating and configuring the KASP succeeds. + *\li #ISC_R_EXISTS If 'kasplist' already has a kasp structure with 'name'. + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISCCFG_KASPCONF_H */ diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c new file mode 100644 index 0000000000..eafb4c3b35 --- /dev/null +++ b/lib/isccfg/kaspconf.c @@ -0,0 +1,206 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + + +/* + * Utility function for getting a configuration option. + */ +static isc_result_t +confget(cfg_obj_t const * const *maps, const char *name, const cfg_obj_t **obj) +{ + for (size_t i = 0;; i++) { + if (maps[i] == NULL) { + return (ISC_R_NOTFOUND); + } + if (cfg_map_get(maps[i], name, obj) == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + } +} + +/* + * Utility function for configuring durations. + */ +static time_t +get_duration(const cfg_obj_t **maps, const char* option, time_t dfl) +{ + const cfg_obj_t *obj; + isc_result_t result; + obj = NULL; + + result = confget(maps, option, &obj); + if (result == ISC_R_NOTFOUND) { + return (dfl); + } + INSIST(result == ISC_R_SUCCESS); + return (cfg_obj_asduration(obj)); +} + +/* + * Create a new kasp key derived from configuration. + */ +static isc_result_t +cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t* kasp) +{ + isc_result_t result; + dns_kasp_key_t *key = NULL; + + /* Create a new key reference. */ + result = dns_kasp_key_create(kasp->mctx, &key); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (config == NULL) { + /* We are creating a key reference for the default kasp. */ + key->role |= DNS_KASP_KEY_ROLE_KSK | DNS_KASP_KEY_ROLE_ZSK; + key->lifetime = 0; + key->algorithm = DNS_KEYALG_ECDSA256; + key->length = -1; + } else { + const char* rolestr; + const cfg_obj_t* obj; + + rolestr = cfg_obj_asstring(cfg_tuple_get(config, "role")); + if (strcmp(rolestr, "ksk") == 0) { + key->role |= DNS_KASP_KEY_ROLE_KSK; + } else if (strcmp(rolestr, "zsk") == 0) { + key->role |= DNS_KASP_KEY_ROLE_ZSK; + } else if (strcmp(rolestr, "csk") == 0) { + key->role |= DNS_KASP_KEY_ROLE_KSK; + key->role |= DNS_KASP_KEY_ROLE_ZSK; + } + key->lifetime = cfg_obj_asduration( + cfg_tuple_get(config, "lifetime")); + key->algorithm = cfg_obj_asuint32( + cfg_tuple_get(config, "algorithm")); + obj = cfg_tuple_get(config, "length"); + if (cfg_obj_isuint32(obj)) { + key->length = cfg_obj_asuint32(obj); + } + } + ISC_LIST_APPEND(kasp->keys, key, link); + ISC_INSIST(!(ISC_LIST_EMPTY(kasp->keys))); + return (result); +} + +isc_result_t +cfg_kasp_fromconfig(const cfg_obj_t *config, isc_mem_t* mctx, + dns_kasplist_t *kasplist, dns_kasp_t **kaspp) +{ + isc_result_t result; + const cfg_obj_t *maps[2]; + const cfg_obj_t *koptions = NULL; + const cfg_obj_t *keys = NULL; + const cfg_listelt_t *element = NULL; + const char *kaspname = NULL; + dns_kasp_t *kasp = NULL; + int i = 0; + + REQUIRE(kaspp != NULL && *kaspp == NULL); + + kaspname = (config != NULL) ? + cfg_obj_asstring(cfg_tuple_get(config, "name")) : + "default"; + + result = dns_kasplist_find(kasplist, kaspname, &kasp); + + if (result == ISC_R_SUCCESS) { + return (ISC_R_EXISTS); + } + if (result != ISC_R_NOTFOUND) { + return (result); + } + + /* No kasp with configured name was found in list, create new one. */ + INSIST(kasp == NULL); + result = dns_kasp_create(mctx, kaspname, &kasp); + if (result != ISC_R_SUCCESS) { + return (result); + } + INSIST(kasp != NULL); + + /* Append it to the list for future lookups. */ + ISC_LIST_APPEND(*kasplist, kasp, link); + ISC_INSIST(!(ISC_LIST_EMPTY(*kasplist))); + + /* Now configure. */ + INSIST(DNS_KASP_VALID(kasp)); + + if (config != NULL) { + koptions = cfg_tuple_get(config, "options"); + maps[i++] = koptions; + } + maps[i] = NULL; + + /* Configuration: Signatures */ + kasp->signatures_refresh = get_duration( + maps, "signatures-refresh", DNS_KASP_SIG_REFRESH); + kasp->signatures_validity = get_duration( + maps, "signatures-validity", DNS_KASP_SIG_VALIDITY); + kasp->signatures_validity_dnskey = get_duration( + maps, "signatures-validity-dnskey", + DNS_KASP_SIG_VALIDITY_DNSKEY); + + /* Configuration: Keys */ + kasp->dnskey_ttl = get_duration(maps, "dnskey-ttl", DNS_KASP_KEY_TTL); + kasp->publish_safety = get_duration(maps, "publish-safety", + DNS_KASP_PUBLISH_SAFETY); + kasp->retire_safety = get_duration(maps, "retire-safety", + DNS_KASP_RETIRE_SAFETY); + + (void)confget(maps, "keys", &keys); + if (keys == NULL) { + result = cfg_kaspkey_fromconfig(NULL, kasp); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } else { + for (element = cfg_list_first(keys); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kobj = cfg_listelt_value(element); + result = cfg_kaspkey_fromconfig(kobj, kasp); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + } + ISC_INSIST(!(ISC_LIST_EMPTY(kasp->keys))); + + // TODO: Rest of the configuration + + /* Success: Attach the kasp to the pointer and return. */ + dns_kasp_attach(kasp, kaspp); + return (ISC_R_SUCCESS); + +cleanup: + + /* Something bad happened, detach (destroys kasp) and return error. */ + dns_kasp_detach(&kasp); + return (result); +} diff --git a/lib/isccfg/win32/libisccfg.def b/lib/isccfg/win32/libisccfg.def index 61e6e86ce4..1c40b3c34b 100644 --- a/lib/isccfg/win32/libisccfg.def +++ b/lib/isccfg/win32/libisccfg.def @@ -24,6 +24,7 @@ cfg_doc_tuple cfg_doc_void cfg_gettoken cfg_is_enum +cfg_kasp_fromconfig cfg_list_first cfg_list_length cfg_list_next diff --git a/lib/isccfg/win32/libisccfg.vcxproj.filters.in b/lib/isccfg/win32/libisccfg.vcxproj.filters.in index 46b4e54bc5..91d4202d7e 100644 --- a/lib/isccfg/win32/libisccfg.vcxproj.filters.in +++ b/lib/isccfg/win32/libisccfg.vcxproj.filters.in @@ -30,6 +30,9 @@ Source Files + + Source Files + Source Files @@ -53,6 +56,9 @@ Header Files + + Header Files + Header Files @@ -63,4 +69,4 @@ Header Files - \ No newline at end of file + diff --git a/lib/isccfg/win32/libisccfg.vcxproj.in b/lib/isccfg/win32/libisccfg.vcxproj.in index a900a11e96..584341f82f 100644 --- a/lib/isccfg/win32/libisccfg.vcxproj.in +++ b/lib/isccfg/win32/libisccfg.vcxproj.in @@ -116,6 +116,7 @@ + @@ -127,6 +128,7 @@ + diff --git a/util/copyrights b/util/copyrights index cff1a45fc0..f81e084f1f 100644 --- a/util/copyrights +++ b/util/copyrights @@ -2437,9 +2437,11 @@ ./lib/isccfg/include/isccfg/cfg.h C 2000,2001,2002,2004,2005,2006,2007,2010,2013,2014,2015,2016,2018,2019 ./lib/isccfg/include/isccfg/dnsconf.h C 2009,2016,2018,2019 ./lib/isccfg/include/isccfg/grammar.h C 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2013,2014,2015,2016,2017,2018,2019 +./lib/isccfg/include/isccfg/kaspconf.h C 2019 ./lib/isccfg/include/isccfg/log.h C 2001,2004,2005,2006,2007,2009,2016,2018,2019 ./lib/isccfg/include/isccfg/namedconf.h C 2002,2004,2005,2006,2007,2009,2010,2014,2016,2018,2019 ./lib/isccfg/include/isccfg/version.h C 2001,2004,2005,2006,2007,2016,2018,2019 +./lib/isccfg/kaspconf.c C 2019 ./lib/isccfg/log.c C 2001,2004,2005,2006,2007,2016,2018,2019 ./lib/isccfg/namedconf.c C 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 ./lib/isccfg/parser.c C 2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 From e6ee5486cacf4ce414604a9c2cfd8eb02ce04e97 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 11 Sep 2019 11:36:39 +0200 Subject: [PATCH 10/43] Nit: fix typo (dnsssec-signzone) --- bin/tests/system/dnssec/tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 3ca6f92475..6d9ba5a646 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -1485,7 +1485,7 @@ n=$((n+1)) test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) -echo_i "checking that dnsssec-signzone updates originalttl on ttl changes ($n)" +echo_i "checking that dnssec-signzone updates originalttl on ttl changes ($n)" ret=0 zone=example key1=$($KEYGEN -K signer -q -a RSASHA1 -b 1024 -n zone $zone) From 68e8741c989999c33812ec15fb50b9f01a9c5784 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 11 Sep 2019 13:46:02 +0200 Subject: [PATCH 11/43] Fix: nums type in dst_keys This was isc_stdtime_t but should be uint32_t. --- lib/dns/dst_internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h index 3657733d49..86709135ac 100644 --- a/lib/dns/dst_internal.h +++ b/lib/dns/dst_internal.h @@ -108,7 +108,7 @@ struct dst_key { isc_stdtime_t times[DST_MAX_TIMES + 1]; /*%< timing metadata */ bool timeset[DST_MAX_TIMES + 1]; /*%< data set? */ - isc_stdtime_t nums[DST_MAX_NUMERIC + 1]; /*%< numeric metadata */ + uint32_t nums[DST_MAX_NUMERIC + 1]; /*%< numeric metadata */ bool numset[DST_MAX_NUMERIC + 1]; /*%< data set? */ bool inactive; /*%< private key not present as it is inactive */ From 7f4d1dbddfbc2571a7b407078b8526cf809e1afb Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 11 Sep 2019 16:31:41 +0200 Subject: [PATCH 12/43] Nit: fix typo in documentation dst_key_getnum --- lib/dns/include/dst/dst.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index 3146d88cb9..52d4d3319e 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -819,7 +819,7 @@ dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep); * Requires: * "key" is a valid key. * "type" is no larger than DST_MAX_NUMERIC - * "timep" is not null. + * "valuep" is not null. */ void From 77d2895a5ac93e74cddeb2327a2fafd8f79a411d Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 11 Sep 2019 16:29:33 +0200 Subject: [PATCH 13/43] Update dst key code to maintain key state Add a number of metadata variables (lifetime, ksk and zsk role). For the roles we add a new type of metadata (booleans). Add a function to write the state of the key to a separate file. Only write out known metadata to private file. With the introduction of the numeric metadata "Lifetime", adjust the write private key file functionality to only write out metadata it knows about. --- lib/dns/dst_api.c | 215 +++++++++++++++++++++++++++++++++--- lib/dns/dst_internal.h | 10 +- lib/dns/dst_parse.c | 10 +- lib/dns/include/dst/dst.h | 40 ++++++- lib/dns/win32/libdns.def.in | 3 + 5 files changed, 255 insertions(+), 23 deletions(-) diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index e874639f8d..97cc044da0 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -83,6 +83,8 @@ static dst_key_t * get_key_struct(const dns_name_t *name, isc_mem_t *mctx); static isc_result_t write_public_key(const dst_key_t *key, int type, const char *directory); +static isc_result_t write_key_state(const dst_key_t *key, int type, + const char *directory); static isc_result_t buildfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg, @@ -372,24 +374,35 @@ dst_key_tofile(const dst_key_t *key, int type, const char *directory) { REQUIRE(dst_initialized == true); REQUIRE(VALID_KEY(key)); - REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 0); + REQUIRE((type & + (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE)) != 0); CHECKALG(key->key_alg); - if (key->func->tofile == NULL) + if (key->func->tofile == NULL) { return (DST_R_UNSUPPORTEDALG); + } if ((type & DST_TYPE_PUBLIC) != 0) { ret = write_public_key(key, type, directory); - if (ret != ISC_R_SUCCESS) + if (ret != ISC_R_SUCCESS) { return (ret); + } + } + + if ((type & DST_TYPE_STATE) != 0) { + ret = write_key_state(key, type, directory); + if (ret != ISC_R_SUCCESS) { + return (ret); + } } if (((type & DST_TYPE_PRIVATE) != 0) && (key->key_flags & DNS_KEYFLAG_TYPEMASK) != DNS_KEYTYPE_NOKEY) + { return (key->func->tofile(key, directory)); - else - return (ret); + } + return (ISC_R_SUCCESS); } void @@ -884,14 +897,45 @@ dst_key_generate(const dns_name_t *name, unsigned int alg, return (ISC_R_SUCCESS); } +isc_result_t +dst_key_getbool(const dst_key_t *key, int type, bool *valuep) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(valuep != NULL); + REQUIRE(type <= DST_MAX_BOOLEAN); + if (!key->boolset[type]) { + return (ISC_R_NOTFOUND); + } + *valuep = key->bools[type]; + return (ISC_R_SUCCESS); +} + +void +dst_key_setbool(dst_key_t *key, int type, bool value) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_BOOLEAN); + key->bools[type] = value; + key->boolset[type] = true; +} + +void +dst_key_unsetbool(dst_key_t *key, int type) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_BOOLEAN); + key->boolset[type] = false; +} + isc_result_t dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep) { REQUIRE(VALID_KEY(key)); REQUIRE(valuep != NULL); REQUIRE(type <= DST_MAX_NUMERIC); - if (!key->numset[type]) + if (!key->numset[type]) { return (ISC_R_NOTFOUND); + } *valuep = key->nums[type]; return (ISC_R_SUCCESS); } @@ -918,8 +962,9 @@ dst_key_gettime(const dst_key_t *key, int type, isc_stdtime_t *timep) { REQUIRE(VALID_KEY(key)); REQUIRE(timep != NULL); REQUIRE(type <= DST_MAX_TIMES); - if (!key->timeset[type]) + if (!key->timeset[type]) { return (ISC_R_NOTFOUND); + } *timep = key->times[type]; return (ISC_R_SUCCESS); } @@ -939,6 +984,36 @@ dst_key_unsettime(dst_key_t *key, int type) { key->timeset[type] = false; } +isc_result_t +dst_key_getstate(const dst_key_t *key, int type, dst_key_state_t *statep) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(statep != NULL); + REQUIRE(type <= DST_MAX_KEYSTATES); + if (!key->keystateset[type]) { + return (ISC_R_NOTFOUND); + } + *statep = key->keystates[type]; + return (ISC_R_SUCCESS); +} + +void +dst_key_setstate(dst_key_t *key, int type, dst_key_state_t state) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_KEYSTATES); + key->keystates[type] = state; + key->keystateset[type] = true; +} + +void +dst_key_unsetstate(dst_key_t *key, int type) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_KEYSTATES); + key->keystateset[type] = false; +} + isc_result_t dst_key_getprivateformat(const dst_key_t *key, int *majorp, int *minorp) { REQUIRE(VALID_KEY(key)); @@ -1116,7 +1191,7 @@ dst_key_buildfilename(const dst_key_t *key, int type, REQUIRE(VALID_KEY(key)); REQUIRE(type == DST_TYPE_PRIVATE || type == DST_TYPE_PUBLIC || - type == 0); + type == DST_TYPE_STATE || type == 0); return (buildfilename(key->key_name, key->key_id, key->key_alg, type, directory, out)); @@ -1476,6 +1551,36 @@ issymmetric(const dst_key_t *key) { } } +/*% + * Write key boolean metadata to a file pointer, preceded by 'tag' + */ +static void +printbool(const dst_key_t *key, int type, const char *tag, FILE *stream) { + isc_result_t result; + bool value = 0; + + result = dst_key_getbool(key, type, &value); + if (result != ISC_R_SUCCESS) { + return; + } + fprintf(stream, "%s: %s\n", tag, value ? "yes" : "no"); +} + +/*% + * Write key numeric metadata to a file pointer, preceded by 'tag' + */ +static void +printnum(const dst_key_t *key, int type, const char *tag, FILE *stream) { + isc_result_t result; + uint32_t value = 0; + + result = dst_key_getnum(key, type, &value); + if (result != ISC_R_SUCCESS) { + return; + } + fprintf(stream, "%s: %u\n", tag, value); +} + /*% * Write key timing metadata to a file pointer, preceded by 'tag' */ @@ -1517,6 +1622,77 @@ printtime(const dst_key_t *key, int type, const char *tag, FILE *stream) { fprintf(stream, "%s: (set, unable to display)\n", tag); } +/*% + * Writes a key state to disk. + */ +static isc_result_t +write_key_state(const dst_key_t *key, int type, const char *directory) { + FILE *fp; + isc_buffer_t fileb; + char filename[NAME_MAX]; + isc_result_t ret; + isc_fsaccess_t access; + + REQUIRE(VALID_KEY(key)); + + /* + * Make the filename. + */ + isc_buffer_init(&fileb, filename, sizeof(filename)); + ret = dst_key_buildfilename(key, DST_TYPE_STATE, directory, &fileb); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + /* + * Create public key file. + */ + if ((fp = fopen(filename, "w")) == NULL) { + return (DST_R_WRITEERROR); + } + + if (issymmetric(key)) { + access = 0; + isc_fsaccess_add(ISC_FSACCESS_OWNER, + ISC_FSACCESS_READ | ISC_FSACCESS_WRITE, + &access); + (void)isc_fsaccess_set(filename, access); + } + + /* Write key state */ + if ((type & DST_TYPE_KEY) == 0) { + fprintf(fp, "; This is the state of key %d, for ", + key->key_id); + ret = dns_name_print(key->key_name, fp); + if (ret != ISC_R_SUCCESS) { + fclose(fp); + return (ret); + } + fputc('\n', fp); + + printtime(key, DST_TIME_CREATED, "Generated", fp); + printtime(key, DST_TIME_PUBLISH, "Published", fp); + printtime(key, DST_TIME_ACTIVATE, "Active", fp); + printtime(key, DST_TIME_INACTIVE, "Retired", fp); + printtime(key, DST_TIME_REVOKE, "Revoked", fp); + printtime(key, DST_TIME_DELETE, "Removed", fp); + + printnum(key, DST_NUM_LIFETIME, "Lifetime", fp); + fprintf(fp, "Algorithm: %u\n", key->key_alg); + fprintf(fp, "Length: %u\n", key->key_size); + + printbool(key, DST_BOOL_KSK, "KSK", fp); + printbool(key, DST_BOOL_ZSK, "ZSK", fp); + } + + fflush(fp); + if (ferror(fp)) + ret = DST_R_WRITEERROR; + fclose(fp); + + return (ret); +} + /*% * Writes a public key to disk in DNS format. */ @@ -1540,33 +1716,38 @@ write_public_key(const dst_key_t *key, int type, const char *directory) { isc_buffer_init(&classb, class_array, sizeof(class_array)); ret = dst_key_todns(key, &keyb); - if (ret != ISC_R_SUCCESS) + if (ret != ISC_R_SUCCESS) { return (ret); + } isc_buffer_usedregion(&keyb, &r); dns_rdata_fromregion(&rdata, key->key_class, dns_rdatatype_dnskey, &r); ret = dns_rdata_totext(&rdata, (dns_name_t *) NULL, &textb); - if (ret != ISC_R_SUCCESS) + if (ret != ISC_R_SUCCESS) { return (DST_R_INVALIDPUBLICKEY); + } ret = dns_rdataclass_totext(key->key_class, &classb); - if (ret != ISC_R_SUCCESS) + if (ret != ISC_R_SUCCESS) { return (DST_R_INVALIDPUBLICKEY); + } /* * Make the filename. */ isc_buffer_init(&fileb, filename, sizeof(filename)); ret = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, &fileb); - if (ret != ISC_R_SUCCESS) + if (ret != ISC_R_SUCCESS) { return (ret); + } /* * Create public key file. */ - if ((fp = fopen(filename, "w")) == NULL) + if ((fp = fopen(filename, "w")) == NULL) { return (DST_R_WRITEERROR); + } if (issymmetric(key)) { access = 0; @@ -1641,10 +1822,14 @@ buildfilename(dns_name_t *name, dns_keytag_t id, isc_result_t result; REQUIRE(out != NULL); - if ((type & DST_TYPE_PRIVATE) != 0) + if ((type & DST_TYPE_PRIVATE) != 0) { suffix = ".private"; - else if (type == DST_TYPE_PUBLIC) + } else if ((type & DST_TYPE_PUBLIC) != 0) { suffix = ".key"; + } else if ((type & DST_TYPE_STATE) != 0) { + suffix = ".state"; + } + if (directory != NULL) { if (isc_buffer_availablelength(out) < strlen(directory)) return (ISC_R_NOSPACE); diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h index 86709135ac..463954fa53 100644 --- a/lib/dns/dst_internal.h +++ b/lib/dns/dst_internal.h @@ -107,11 +107,13 @@ struct dst_key { } keydata; /*%< pointer to key in crypto pkg fmt */ isc_stdtime_t times[DST_MAX_TIMES + 1]; /*%< timing metadata */ - bool timeset[DST_MAX_TIMES + 1]; /*%< data set? */ + bool timeset[DST_MAX_TIMES + 1]; /*%< data set? */ uint32_t nums[DST_MAX_NUMERIC + 1]; /*%< numeric metadata */ - bool numset[DST_MAX_NUMERIC + 1]; /*%< data set? */ - bool inactive; /*%< private key not present as it is - inactive */ + bool numset[DST_MAX_NUMERIC + 1]; /*%< data set? */ + bool bools[DST_MAX_BOOLEAN + 1]; /*%< boolean metadata */ + bool boolset[DST_MAX_BOOLEAN + 1]; /*%< data set? */ + + bool inactive; /*%< private key not present as it is inactive */ bool external; /*%< external key */ int fmt_major; /*%< private key format, major version */ diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c index 4b83617a21..7130c29501 100644 --- a/lib/dns/dst_parse.c +++ b/lib/dns/dst_parse.c @@ -734,7 +734,9 @@ dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv, result = dst_key_getnum(key, i, &value); if (result != ISC_R_SUCCESS) continue; - fprintf(fp, "%s %u\n", numerictags[i], value); + if (numerictags[i] != NULL) { + fprintf(fp, "%s %u\n", numerictags[i], value); + } } for (i = 0; i < TIMING_NTAGS; i++) { result = dst_key_gettime(key, i, &when); @@ -750,8 +752,10 @@ dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv, isc_buffer_usedregion(&b, &r); - fprintf(fp, "%s %.*s\n", timetags[i], (int)r.length, - r.base); + if (timetags[i] != NULL) { + fprintf(fp, "%s %.*s\n", timetags[i], + (int)r.length, r.base); + } } } diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index 52d4d3319e..f747619a40 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -85,6 +85,7 @@ typedef struct dst_context dst_context_t; #define DST_TYPE_KEY 0x1000000 /* KEY key */ #define DST_TYPE_PRIVATE 0x2000000 #define DST_TYPE_PUBLIC 0x4000000 +#define DST_TYPE_STATE 0x8000000 /* Key timing metadata definitions */ #define DST_TIME_CREATED 0 @@ -103,7 +104,13 @@ typedef struct dst_context dst_context_t; #define DST_NUM_SUCCESSOR 1 #define DST_NUM_MAXTTL 2 #define DST_NUM_ROLLPERIOD 3 -#define DST_MAX_NUMERIC 3 +#define DST_NUM_LIFETIME 4 +#define DST_MAX_NUMERIC 4 + +/* Boolean metadata definitions */ +#define DST_BOOL_KSK 0 +#define DST_BOOL_ZSK 1 +#define DST_MAX_BOOLEAN 1 /* * Current format version number of the private key parser. @@ -811,6 +818,37 @@ dst_key_setflags(dst_key_t *key, uint32_t flags); * "key" is a valid key. */ +isc_result_t +dst_key_getbool(const dst_key_t *key, int type, bool *valuep); +/*%< + * Get a member of the boolean metadata array and place it in '*valuep'. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_BOOLEAN + * "valuep" is not null. + */ + +void +dst_key_setbool(dst_key_t *key, int type, bool value); +/*%< + * Set a member of the boolean metadata array. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_BOOLEAN + */ + +void +dst_key_unsetbool(dst_key_t *key, int type); +/*%< + * Flag a member of the boolean metadata array as "not set". + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_BOOLEAN + */ + isc_result_t dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep); /*%< diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index ce6e55b00c..2016d204ae 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -1400,6 +1400,7 @@ dst_key_fromlabel dst_key_fromnamedfile dst_key_generate dst_key_getbits +dst_key_getbool dst_key_getfilename dst_key_getgssctx dst_key_getnum @@ -1422,6 +1423,7 @@ dst_key_restore dst_key_rid dst_key_secretsize dst_key_setbits +dst_key_setbool dst_key_setexternal dst_key_setflags dst_key_setinactive @@ -1435,6 +1437,7 @@ dst_key_tkeytoken dst_key_tobuffer dst_key_todns dst_key_tofile +dst_key_unsetbool dst_key_unsetnum dst_key_unsettime dst_lib_destroy From 97a5698e069c82f5a15effcb141a94cd4daf9f17 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 11 Sep 2019 16:32:58 +0200 Subject: [PATCH 14/43] Add various get functions for kasp Write functions to access various elements of the kasp structure, and the kasp keys. This in preparation of code in dnssec-keygen, dnssec-settime, named... --- lib/dns/include/dns/kasp.h | 94 +++++++++++++++++++++++++++++++++++-- lib/dns/kasp.c | 91 +++++++++++++++++++++++++++++++++-- lib/dns/win32/libdns.def.in | 6 +++ 3 files changed, 184 insertions(+), 7 deletions(-) diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h index 12998d46ff..6c953a0636 100644 --- a/lib/dns/include/dns/kasp.h +++ b/lib/dns/include/dns/kasp.h @@ -187,13 +187,27 @@ dns_kasp_getname(dns_kasp_t *kasp); * * Requires: * - *\li 'kasp' is a valid, frozen kasp. + *\li 'kasp' is a valid kasp. * * Returns: * *\li name of 'kasp'. */ +dns_ttl_t +dns_kasp_dnskeyttl(dns_kasp_t *kasp); +/*%< + * Get dnskey ttl. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li DNSKEY TTL. + */ + isc_result_t dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp); /*%< @@ -236,9 +250,83 @@ dns_kasp_key_destroy(dns_kasp_key_t* key); * * Requires: * - *\li 'key' is a valid KASP key. + *\li key != NULL + */ + +uint32_t +dns_kasp_key_algorithm(dns_kasp_key_t *key); +/*%< + * Get the key algorithm. + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li Key algorithm. + */ + +unsigned int +dns_kasp_key_size(dns_kasp_key_t *key); +/*%< + * Get the key size. + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li Configured key size, or default key size for key algorithm if no size + * configured. + */ + +time_t +dns_kasp_key_lifetime(dns_kasp_key_t *key); +/*%< + * The lifetime of this key (how long may this key be active?) + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li Lifetime of key. + * + */ + +bool +dns_kasp_key_ksk(dns_kasp_key_t *key); +/*%< + * Does this key act as a KSK? + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li True, if the key role has DNS_KASP_KEY_ROLE_KSK set. + *\li False, otherwise. + * + */ + +bool +dns_kasp_key_zsk(dns_kasp_key_t *key); +/*%< + * Does this key act as a ZSK? + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li True, if the key role has DNS_KASP_KEY_ROLE_ZSK set. + *\li False, otherwise. * - *\li kasp != NULL && key != NULL */ ISC_LANG_ENDDECLS diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c index ce401cdb6a..f585129b32 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -21,6 +21,7 @@ #include #include +#include isc_result_t dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp) @@ -64,6 +65,7 @@ void dns_kasp_attach(dns_kasp_t *source, dns_kasp_t **targetp) { REQUIRE(DNS_KASP_VALID(source)); REQUIRE(targetp != NULL && *targetp == NULL); + isc_refcount_increment(&source->references); *targetp = source; } @@ -95,6 +97,12 @@ dns_kasp_detach(dns_kasp_t **kaspp) { } } +const char* +dns_kasp_getname(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + return kasp->name; +} + void dns_kasp_freeze(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -109,16 +117,17 @@ dns_kasp_thaw(dns_kasp_t *kasp) { kasp->frozen = false; } -const char* -dns_kasp_getname(dns_kasp_t *kasp) { +dns_ttl_t +dns_kasp_dnskeyttl(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); - return kasp->name; + REQUIRE(kasp->frozen); + return kasp->dnskey_ttl; } isc_result_t dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp) { - dns_kasp_t *kasp; + dns_kasp_t *kasp = NULL; if (list == NULL) { return (ISC_R_NOTFOUND); @@ -166,3 +175,77 @@ dns_kasp_key_destroy(dns_kasp_key_t* key) REQUIRE(key != NULL); isc_mem_putanddetach(&key->mctx, key, sizeof(*key)); } + +uint32_t +dns_kasp_key_algorithm(dns_kasp_key_t *key) { + + REQUIRE(key != NULL); + return key->algorithm; +} + +unsigned int +dns_kasp_key_size(dns_kasp_key_t *key) { + unsigned int size = 0; + unsigned int min = 0; + + REQUIRE(key != NULL); + + switch (key->algorithm) { + case DNS_KEYALG_RSASHA1: + case DNS_KEYALG_NSEC3RSASHA1: + case DNS_KEYALG_RSASHA256: + case DNS_KEYALG_RSASHA512: + min = DNS_KEYALG_RSASHA512 ? 1024 : 512; + if (key->length > -1) { + size = (unsigned int) key->length; + if (size < min) { + size = min; + } + if (size > 4096) { + size = 4096; + } + } else if (key->role & DNS_KASP_KEY_ROLE_KSK) { + size = 2048; + } else { + size = 1024; + } + break; + case DNS_KEYALG_ECDSA256: + size = 256; + break; + case DNS_KEYALG_ECDSA384: + size = 384; + break; + case DNS_KEYALG_ED25519: + size = 32; + break; + case DNS_KEYALG_ED448: + size = 57; + break; + default: + /* unsupported */ + break; + } + return size; +} + +time_t +dns_kasp_key_lifetime(dns_kasp_key_t *key) { + + REQUIRE(key != NULL); + return (key->lifetime); +} + +bool +dns_kasp_key_ksk(dns_kasp_key_t *key) { + + REQUIRE(key != NULL); + return (key->role & DNS_KASP_KEY_ROLE_KSK); +} + +bool +dns_kasp_key_zsk(dns_kasp_key_t *key) { + + REQUIRE(key != NULL); + return (key->role & DNS_KASP_KEY_ROLE_ZSK); +} diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 2016d204ae..beef70a5d7 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -416,10 +416,16 @@ dns_journal_writediff dns_kasp_create dns_kasp_attach dns_kasp_detach +dns_kasp_dnskeyttl dns_kasp_freeze dns_kasp_getname +dns_kasp_key_algorithm dns_kasp_key_create dns_kasp_key_destroy +dns_kasp_key_ksk +dns_kasp_key_lifetime +dns_kasp_key_size +dns_kasp_key_zsk dns_kasp_thaw dns_kasplist_find dns_keydata_fromdnskey From 09ac224c5c881824c7b6649905f16b7d3f1036f4 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 11 Sep 2019 16:38:49 +0200 Subject: [PATCH 15/43] dnssec-keygen can create keys given dnssec-policy This commit adds code for generating keys with dnssec-keygen given a specific dnssec-policy. The dnssec-policy can be set with a new option '-k'. The '-l' option can be used to set a configuration file that contains a specific dnssec-policy. Because the dnssec-policy dictates how the keys should look like, many of the existing dnssec-keygen options cannot be used together with '-k'. If the dnssec-policy lists multiple keys, dnssec-keygen has now the possibility to generate multiple keys at one run. Add two tests for creating keys with '-k': One with the default policy, one with multiple keys from the configuration. --- bin/dnssec/Makefile.in | 14 ++- bin/dnssec/dnssec-keygen.c | 196 ++++++++++++++++++++++++++++- bin/dnssec/dnssec-keygen.docbook | 30 +++++ bin/dnssec/win32/keygen.vcxproj.in | 12 +- bin/tests/system/kasp/clean.sh | 17 +++ bin/tests/system/kasp/kasp.conf | 25 ++++ bin/tests/system/kasp/setup.sh | 19 +++ bin/tests/system/kasp/tests.sh | 182 +++++++++++++++++++++++++++ util/copyrights | 3 + 9 files changed, 480 insertions(+), 18 deletions(-) create mode 100644 bin/tests/system/kasp/clean.sh create mode 100644 bin/tests/system/kasp/kasp.conf create mode 100644 bin/tests/system/kasp/setup.sh create mode 100644 bin/tests/system/kasp/tests.sh diff --git a/bin/dnssec/Makefile.in b/bin/dnssec/Makefile.in index 32d79f4292..7d3b15f5d3 100644 --- a/bin/dnssec/Makefile.in +++ b/bin/dnssec/Makefile.in @@ -15,24 +15,26 @@ VERSION=@BIND9_VERSION@ @BIND9_MAKE_INCLUDES@ -CINCLUDES = ${DNS_INCLUDES} ${ISC_INCLUDES} \ +CINCLUDES = ${DNS_INCLUDES} ${ISC_INCLUDES} ${ISCCFG_INCLUDES} \ ${OPENSSL_CFLAGS} -CDEFINES = -DVERSION=\"${VERSION}\" +CDEFINES = -DVERSION=\"${VERSION}\" -DNAMED_CONFFILE=\"${sysconfdir}/named.conf\" CWARNINGS = DNSLIBS = ../../lib/dns/libdns.@A@ ${MAXMINDDB_LIBS} @DNS_CRYPTO_LIBS@ +ISCCFGLIBS = ../../lib/isccfg/libisccfg.@A@ ISCLIBS = ../../lib/isc/libisc.@A@ ${OPENSSL_LIBS} ${JSON_C_LIBS} ${LIBXML2_LIBS} ISCNOSYMLIBS = ../../lib/isc/libisc-nosymtbl.@A@ ${OPENSSL_LIBS} ${JSON_C_LIBS} ${LIBXML2_LIBS} DNSDEPLIBS = ../../lib/dns/libdns.@A@ ISCDEPLIBS = ../../lib/isc/libisc.@A@ +ISCCFGDEPLIBS = ../../lib/isccfg/libisccfg.@A@ -DEPLIBS = ${DNSDEPLIBS} ${ISCDEPLIBS} +DEPLIBS = ${DNSDEPLIBS} ${ISCCFGDEPLIBS} ${ISCDEPLIBS} -LIBS = ${DNSLIBS} ${ISCLIBS} @LIBS@ +LIBS = ${DNSLIBS} ${ISCCFGLIBS} ${ISCLIBS} @LIBS@ -NOSYMLIBS = ${DNSLIBS} ${ISCNOSYMLIBS} @LIBS@ +NOSYMLIBS = ${DNSLIBS} ${ISCCFGLIBS} ${ISCNOSYMLIBS} @LIBS@ # Alphabetically TARGETS = dnssec-cds@EXEEXT@ dnssec-dsfromkey@EXEEXT@ \ @@ -48,7 +50,7 @@ SRCS = dnssec-cds.c dnssec-dsfromkey.c dnssec-importkey.c \ dnssec-settime.c dnssec-signzone.c dnssec-verify.c \ dnssectool.c -MANPAGES = dnssec-cds.8 dnssec-dsfromkey.8 dnssec-importkey.8 \ +MANPAGES = dnssec-cds.8 dnssec-dsfromkey.8 dnssec-importkey.8 \ dnssec-keyfromlabel.8 dnssec-keygen.8 dnssec-revoke.8 \ dnssec-settime.8 dnssec-signzone.8 dnssec-verify.8 diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index 54ba5dce65..09b7c09508 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -39,10 +39,16 @@ #include #include +#include +#include +#include +#include + #include #include #include +#include #include #include #include @@ -115,6 +121,10 @@ struct keygen_ctx { bool showprogress; bool quiet; bool oldstyle; + /* state */ + time_t lifetime; + bool ksk; + bool zsk; }; typedef struct keygen_ctx keygen_ctx_t; @@ -127,6 +137,9 @@ usage(void) { fprintf(stderr, " name: owner of the key\n"); fprintf(stderr, "Options:\n"); fprintf(stderr, " -K : write keys into directory\n"); + fprintf(stderr, " -k : generate keys for dnssec-policy\n"); + fprintf(stderr, " -l : configuration file with dnssec-policy " + "statement\n"); fprintf(stderr, " -a :\n"); fprintf(stderr, " RSASHA1 | NSEC3RSASHA1 |\n"); fprintf(stderr, " RSASHA256 | RSASHA512 |\n"); @@ -229,6 +242,53 @@ progress(int p) (void) fflush(stderr); } +static void +kasp_from_conf(cfg_obj_t* config, isc_mem_t* mctx, const char* name, + dns_kasp_t** kaspp) +{ + const cfg_listelt_t *element; + const cfg_obj_t *kasps = NULL; + dns_kasp_t *kasp = NULL, *kasp_next; + isc_result_t result = ISC_R_NOTFOUND; + dns_kasplist_t kasplist; + + ISC_LIST_INIT(kasplist); + + (void)cfg_map_get(config, "dnssec-policy", &kasps); + for (element = cfg_list_first(kasps); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kconfig = cfg_listelt_value(element); + kasp = NULL; + if (strcmp(cfg_obj_asstring(cfg_tuple_get(kconfig, "name")), + name) != 0) + { + continue; + } + + result = cfg_kasp_fromconfig(kconfig, mctx, &kasplist, &kasp); + if (result != ISC_R_SUCCESS) { + fatal("failed to configure dnssec-policy '%s': %s", + cfg_obj_asstring(cfg_tuple_get(kconfig, "name")), + isc_result_totext(result)); + } + INSIST(kasp != NULL); + dns_kasp_freeze(kasp); + break; + } + + *kaspp = kasp; + + /* + * Same cleanup for kasp list. + */ + for (kasp = ISC_LIST_HEAD(kasplist); kasp != NULL; kasp = kasp_next) { + kasp_next = ISC_LIST_NEXT(kasp, link); + ISC_LIST_UNLINK(kasplist, kasp, link); + dns_kasp_detach(&kasp); + } +} + static void keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { @@ -385,7 +445,8 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) fatal("-S and -G cannot be used together"); ret = dst_key_fromnamedfile(ctx->predecessor, ctx->directory, - DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, + (DST_TYPE_PUBLIC| + DST_TYPE_PRIVATE|DST_TYPE_STATE), mctx, &prevkey); if (ret != ISC_R_SUCCESS) fatal("Invalid keyfile %s: %s", @@ -665,6 +726,13 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) if (ctx->setttl) dst_key_setttl(key, ctx->ttl); + /* Set dnssec-policy related metadata */ + if (ctx->policy) { + dst_key_setnum(key, DST_NUM_LIFETIME, ctx->lifetime); + dst_key_setbool(key, DST_BOOL_KSK, ctx->ksk); + dst_key_setbool(key, DST_BOOL_ZSK, ctx->zsk); + } + /* * Do not overwrite an existing key, or create a key * if there is a risk of ID collision due to this key @@ -754,8 +822,8 @@ main(int argc, char **argv) { /* * Process memory debugging argument first. */ -#define CMDLINE_FLAGS "3A:a:b:Cc:D:d:E:eFf:Gg:hI:i:K:L:m:n:P:p:qR:r:S:s:T:t:" \ - "v:V" +#define CMDLINE_FLAGS "3A:a:b:Cc:D:d:E:eFf:Gg:hI:i:K:k:L:l:m:n:P:p:qR:r:S:s:" \ + "T:t:v:V" while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { switch (ch) { case 'm': @@ -834,10 +902,15 @@ main(int argc, char **argv) { fatal("cannot open directory %s: %s", ctx.directory, isc_result_totext(ret)); break; + case 'k': + ctx.policy = isc_commandline_argument; + break; case 'L': ctx.ttl = strtottl(isc_commandline_argument); ctx.setttl = true; break; + case 'l': + ctx.configfile = isc_commandline_argument; break; case 'n': ctx.nametype = isc_commandline_argument; @@ -1001,15 +1074,21 @@ main(int argc, char **argv) { ctx.rdclass = strtoclass(classname); + if (ctx.configfile == NULL || ctx.configfile[0] == '\0') { + ctx.configfile = NAMED_CONFFILE; + } + if (ctx.predecessor == NULL) { if (argc < isc_commandline_index + 1) fatal("the key name was not specified"); if (argc > isc_commandline_index + 1) fatal("extraneous arguments"); + } - if (algname == NULL) + if (ctx.predecessor == NULL && ctx.policy == NULL) { + if (algname == NULL) { fatal("no algorithm specified"); - + } r.base = algname; r.length = strlen(algname); ret = dns_secalg_fromtext(&ctx.alg, &r); @@ -1021,7 +1100,112 @@ main(int argc, char **argv) { } } - keygen(&ctx, mctx, argc, argv); + if (ctx.policy != NULL) { + if (ctx.nametype != NULL) { + fatal("-k and -n cannot be used together"); + } + if (ctx.predecessor != NULL) { + fatal("-k and -S cannot be used together"); + } + if (ctx.oldstyle) { + fatal("-k and -C cannot be used together"); + } + if (ctx.setttl) { + fatal("-k and -L cannot be used together"); + } + if (ctx.prepub > 0) { + fatal("-k and -i cannot be used together"); + } + if (ctx.size != -1) { + fatal("-k and -b cannot be used together"); + } + if (ctx.kskflag || ctx.revflag) { + fatal("-k and -f cannot be used together"); + } + if (ctx.options & DST_TYPE_KEY) { + fatal("-k and -T KEY cannot be used together"); + } + if (ctx.use_nsec3) { + fatal("-k and -3 cannot be used together"); + } + + if (ctx.setpub || ctx.setact || ctx.setrev || ctx.setinact || + ctx.setdel || ctx.unsetpub || ctx.unsetact || + ctx.unsetrev || ctx.unsetinact || ctx.unsetdel || + ctx.setsyncadd || ctx.setsyncdel) + { + fatal("cannot use -k together with " + "-P, -A, -R, -I, or -D options " + "(use dnssec-settime on keys afterwards)"); + } + + ctx.options |= DST_TYPE_STATE; + ctx.genonly = true; + + if (strcmp(ctx.policy, "default") == 0) { + ctx.use_nsec3 = false; + ctx.alg = DST_ALG_ECDSA256; + ctx.size = 0; + ctx.kskflag = DNS_KEYFLAG_KSK; + ctx.ttl = 3600; + ctx.setttl = true; + ctx.ksk = true; + ctx.zsk = true; + ctx.lifetime = 0; + + keygen(&ctx, mctx, argc, argv); + } else { + cfg_parser_t *parser = NULL; + cfg_obj_t *config = NULL; + dns_kasp_t* kasp = NULL; + dns_kasp_key_t* kaspkey = NULL; + + RUNTIME_CHECK(cfg_parser_create(mctx, log, &parser) + == ISC_R_SUCCESS); + if (cfg_parse_file(parser, ctx.configfile, + &cfg_type_namedconf, &config) != ISC_R_SUCCESS) + { + fatal("unable to load dnssec-policy '%s' from " + "'%s'", ctx.policy, ctx.configfile); + } + + kasp_from_conf(config, mctx, ctx.policy, &kasp); + if (kasp == NULL) { + fatal("failed to load dnssec-policy '%s'", + ctx.policy); + } + if (ISC_LIST_EMPTY(kasp->keys)) { + fatal("dnssec-policy '%s' has no keys " + "configured", ctx.policy); + } + + ctx.ttl = dns_kasp_dnskeyttl(kasp); + ctx.setttl = true; + + kaspkey = ISC_LIST_HEAD(kasp->keys); + + while (kaspkey != NULL) { + ctx.use_nsec3 = false; + ctx.alg = dns_kasp_key_algorithm(kaspkey); + ctx.size = dns_kasp_key_size(kaspkey); + ctx.kskflag = dns_kasp_key_ksk(kaspkey) ? + DNS_KEYFLAG_KSK : 0; + ctx.ksk = dns_kasp_key_ksk(kaspkey); + ctx.zsk = dns_kasp_key_zsk(kaspkey); + ctx.lifetime = dns_kasp_key_lifetime(kaspkey); + + keygen(&ctx, mctx, argc, argv); + + kaspkey = ISC_LIST_NEXT(kaspkey, link); + } + + dns_kasp_detach(&kasp); + cfg_obj_destroy(parser, &config); + cfg_parser_destroy(&parser); + } + } else { + keygen(&ctx, mctx, argc, argv); + } cleanup_logging(&log); dst_lib_destroy(); diff --git a/bin/dnssec/dnssec-keygen.docbook b/bin/dnssec/dnssec-keygen.docbook index f194631db3..46ebec3dcd 100644 --- a/bin/dnssec/dnssec-keygen.docbook +++ b/bin/dnssec/dnssec-keygen.docbook @@ -75,7 +75,9 @@ + + @@ -288,6 +290,24 @@ + + -k policy + + + Create keys for a specific dnssec-policy. If a policy uses + multiple keys, dnssec-keygen will generate + multiple keys. This will also create a ".state" file to keep + track of the key state. + + + This option creates keys according to the dnssec-policy + configuration, hence it cannot be used together with many of + the other options that dnssec-keygen + provides. + + + + -L ttl @@ -304,6 +324,16 @@ + + -l file + + + Provide a configuration file that contains a dnssec-policy + statement (matching the policy set with -k). + + + + -n nametype diff --git a/bin/dnssec/win32/keygen.vcxproj.in b/bin/dnssec/win32/keygen.vcxproj.in index 72d1d9c99f..ddef528081 100644 --- a/bin/dnssec/win32/keygen.vcxproj.in +++ b/bin/dnssec/win32/keygen.vcxproj.in @@ -66,15 +66,15 @@ $(OutDir)$(TargetName).pdb true ..\..\..\config.h - .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\isccfg\win32;..\..\..\lib\isccfg\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) CompileAsC Console true ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) - @OPENSSL_LIB@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) - $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIB@dnssectool.lib;libisc.lib;libisccfg.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\isccfg\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) @@ -94,7 +94,7 @@ .\$(Configuration)\ $(OutDir)$(TargetName).pdb ..\..\..\config.h - .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\isccfg\win32;..\..\..\lib\isccfg\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) CompileAsC @@ -104,8 +104,8 @@ true ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) Default - @OPENSSL_LIB@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) - $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIB@dnssectool.lib;libisc.lib;libisccfg.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\isccfg\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) diff --git a/bin/tests/system/kasp/clean.sh b/bin/tests/system/kasp/clean.sh new file mode 100644 index 0000000000..491354f51a --- /dev/null +++ b/bin/tests/system/kasp/clean.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +set -e + +rm -f ./keygen.* +rm -f ./K*.private ./K*.key ./K*.state ./K*.cmp +rm -f ./keys/K* +rmdir ./keys/ diff --git a/bin/tests/system/kasp/kasp.conf b/bin/tests/system/kasp/kasp.conf new file mode 100644 index 0000000000..2ef71b3f4d --- /dev/null +++ b/bin/tests/system/kasp/kasp.conf @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * This is just a random selection of configuration options. + */ + +dnssec-policy "kasp" { + dnskey-ttl 200; + + keys { + csk key-directory P1Y 13; + ksk key-directory P1Y 8; + zsk key-directory P30D 8 1024; + zsk key-directory P6M 8 2000; + }; +}; diff --git a/bin/tests/system/kasp/setup.sh b/bin/tests/system/kasp/setup.sh new file mode 100644 index 0000000000..05b71ac269 --- /dev/null +++ b/bin/tests/system/kasp/setup.sh @@ -0,0 +1,19 @@ +#!/bin/sh -e +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +set -e + +$SHELL clean.sh + +mkdir keys diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh new file mode 100644 index 0000000000..92de7567d8 --- /dev/null +++ b/bin/tests/system/kasp/tests.sh @@ -0,0 +1,182 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +SYSTEMTESTTOP=.. +. "$SYSTEMTESTTOP/conf.sh" + +set -e + +status=0 +n=0 +log=1 + +################################################################################ +# Utilities # +################################################################################ + +# Get the key ids from key files for zone $2 in directory $1 +# that matches algorithm $3. +get_keyids() { + dir=$1 + zone=$2 + algorithm=$(printf "%03d" $3) + start="${dir}/K${zone}.+${algorithm}+" + end=".key" + + ls ${start}*${end} | sed "s/$dir\/K${zone}.+${algorithm}+\([0-9]\{5\}\)${end}/\1/" +} + +log_error() { + test $log -eq 1 && echo_i "error: $1" + ret=$((ret+1)) +} + +# Check the created key in directory $1 for zone $2. +# $3: key role. Must be one of "csk", "ksk", or "zsk". +# $4: key identifier (zero padded) +# $5: algorithm number +# $6: algorithm (string format) +# $7: algorithm length +# $8: dnskey ttl +# $9: key lifetime +check_created_key() { + dir=$1 + zone=$2 + role=$3 + key_idpad=$4 + key_id=$(echo $key_idpad | sed 's/^0*//') + alg_num=$5 + alg_numpad=$(printf "%03d" $alg_num) + alg_string=$6 + length=$7 + dnskey_ttl=$8 + lifetime=$9 + + ksk="no" + zsk="no" + if [ "$role" == "ksk" ]; then + role2="key-signing" + ksk="yes" + flags="257" + elif [ "$role" == "zsk" ]; then + role2="zone-signing" + zsk="yes" + flags="256" + elif [ "$role" == "csk" ]; then + role2="key-signing" + zsk="yes" + ksk="yes" + flags="257" + fi + + KEY_FILE="${dir}/K${zone}.+${alg_numpad}+${key_idpad}.key" + PRIVATE_FILE="${dir}/K${zone}.+${alg_numpad}+${key_idpad}.private" + STATE_FILE="${dir}/K${zone}.+${alg_numpad}+${key_idpad}.state" + + # Check the public key file. We expect three lines: a comment, + # a "Created" line, and the DNSKEY record. + lines=$(cat $KEY_FILE | wc -l) + test "$lines" -eq 3 || log_error "bad public keyfile $KEY_FILE" + grep "This is a ${role2} key, keyid ${key_id}, for ${zone}." $KEY_FILE > /dev/null || log_error "mismatch top comment in $KEY_FILE" + grep "; Created:" $KEY_FILE > /dev/null || log_error "mismatch created comment in $KEY_FILE" + grep "${zone}\. ${dnskey_ttl} IN DNSKEY ${flags} 3 ${alg_num}" $KEY_FILE > /dev/null || log_error "mismatch DNSKEY record in $KEY_FILE" + # Now check the private key file. + grep "Private-key-format: v1.3" $PRIVATE_FILE > /dev/null || log_error "mismatch private key format in $PRIVATE_FILE" + grep "Algorithm: ${alg_num} (${alg_string})" $PRIVATE_FILE > /dev/null || log_error "mismatch algorithm in $PRIVATE_FILE" + grep "Created:" $PRIVATE_FILE > /dev/null || log_error "mismatch created in $PRIVATE_FILE" + # Now check the key state file. There should be seven lines: + # a top comment, "Generated", "Lifetime", "Algorithm", "Length", + # "KSK", and "ZSK". + lines=$(cat $STATE_FILE | wc -l) + test "$lines" -eq 7 || log_error "bad state keyfile $STATE_FILE" + grep "This is the state of key ${key_id}, for ${zone}." $STATE_FILE > /dev/null || log_error "mismatch top comment in $STATE_FILE" + # XXX: Could check if generated is ~now. + grep "Generated: " $STATE_FILE > /dev/null || log_error "mismatch generated in $STATE_FILE" + grep "Lifetime: ${lifetime}" $STATE_FILE > /dev/null || log_error "mismatch lifetime in $STATE_FILE" + grep "Algorithm: ${alg_num}" $STATE_FILE > /dev/null || log_error "mismatch algorithm in $STATE_FILE" + grep "Length: ${length}" $STATE_FILE > /dev/null || log_error "mismatch length in $STATE_FILE" + grep "KSK: ${ksk}" $STATE_FILE > /dev/null || log_error "mismatch ksk in $STATE_FILE" + grep "ZSK: ${zsk}" $STATE_FILE > /dev/null || log_error "mismatch zsk in $STATE_FILE" +} + +################################################################################ +# Tests # +################################################################################ + +# +# dnssec-keygen +# +n=$((n+1)) +echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" +ret=0 +$KEYGEN -k _default kasp > keygen.out._default.test$n 2>/dev/null || ret=1 +lines=$(cat keygen.out._default.test$n | wc -l) +test "$lines" -eq 1 || log_error "wrong number of keys created for policy _default" +KEY_ID=$(get_keyids "." "kasp" "13") +echo_i "check key $KEY_ID..." +check_created_key "." "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "3600" "0" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +n=$((n+1)) +echo_i "check that 'dnssec-keygen -k' (configured policy) creates valid files ($n)" +ret=0 +$KEYGEN -K keys -k kasp -l kasp.conf kasp > keygen.out.kasp.test$n 2>/dev/null || ret=1 +lines=$(cat keygen.out.kasp.test$n | wc -l) +test "$lines" -eq 4 || log_error "wrong number of keys created for policy kasp" +# Check one algorithm. +KEY_ID=$(get_keyids "keys" "kasp" "13") +echo_i "check key $KEY_ID..." +check_created_key "keys" "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "200" "31536000" +# Temporarily don't log errors because we are searching multiple files. +log=0 +# Check the other algorithm. +KEY_IDS=$(get_keyids "keys" "kasp" "8") +for KEY_ID in $KEY_IDS; do + echo_i "check key $KEY_ID..." + # There are three key files with the same algorithm. + # Check them until a match is found. + ret=0 && check_created_key "keys" "kasp" "ksk" $KEY_ID "8" "RSASHA256" "2048" "200" "31536000" + test "$ret" -gt 0 && ret=0 && check_created_key "keys" "kasp" "zsk" $KEY_ID "8" "RSASHA256" "1024" "200" "2592000" + test "$ret" -gt 0 && ret=0 && check_created_key "keys" "kasp" "zsk" $KEY_ID "8" "RSASHA256" "2000" "200" "16070400" + # If ret is non-zero, non of the files matched. + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + +done +# Turn error logs on again. +log=1 + +n=$((n+1)) +echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" +ret=0 +$KEYGEN -k default kasp > keygen.out.default.test$n 2>/dev/null || ret=1 +lines=$(cat keygen.out.default.test$n | wc -l) +test "$lines" -eq 1 || log_error "wrong number of keys created for policy default" +KEY_ID=$(get_keyids "." "kasp" "13") +echo_i "check key $KEY_ID..." +check_created_key "." "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "3600" "0" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# +# dnssec-settime +# + +# +# named +# + + +echo_i "exit status: $status" +[ $status -eq 0 ] || exit 1 + diff --git a/util/copyrights b/util/copyrights index f81e084f1f..25250dc10b 100644 --- a/util/copyrights +++ b/util/copyrights @@ -694,6 +694,9 @@ ./bin/tests/system/ixfr/prereq.sh SH 2001,2004,2007,2012,2014,2016,2018,2019 ./bin/tests/system/ixfr/setup.sh SH 2001,2004,2007,2011,2012,2013,2014,2016,2018,2019 ./bin/tests/system/ixfr/tests.sh SH 2001,2004,2007,2011,2012,2014,2016,2018,2019 +./bin/tests/system/kasp/clean.sh SH 2019 +./bin/tests/system/kasp/setup.sh SH 2019 +./bin/tests/system/kasp/tests.sh SH 2019 ./bin/tests/system/keepalive/clean.sh SH 2017,2018,2019 ./bin/tests/system/keepalive/expected X 2017,2018,2019 ./bin/tests/system/keepalive/setup.sh SH 2017,2018,2019 From 2924b19a9d35e37e06273af992847cc1ad2c37c8 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 3 Sep 2019 11:42:10 +0200 Subject: [PATCH 16/43] Parse dnssec-policy config into kasp Add code that actually stores the configuration into the kasp structure and attach it to the appropriate zone. --- bin/named/include/named/server.h | 1 + bin/named/include/named/zoneconf.h | 9 +- bin/named/server.c | 143 +++++++++++++++++++++-------- bin/named/zoneconf.c | 25 ++++- 4 files changed, 133 insertions(+), 45 deletions(-) diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index 8443201908..97e0082ee3 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -64,6 +64,7 @@ struct named_server { dns_loadmgr_t * loadmgr; dns_zonemgr_t * zonemgr; dns_viewlist_t viewlist; + dns_kasplist_t kasplist; ns_interfacemgr_t * interfacemgr; dns_db_t * in_roothints; diff --git a/bin/named/include/named/zoneconf.h b/bin/named/include/named/zoneconf.h index 23c1f65f53..88633b24f4 100644 --- a/bin/named/include/named/zoneconf.h +++ b/bin/named/include/named/zoneconf.h @@ -27,19 +27,18 @@ ISC_LANG_BEGINDECLS isc_result_t named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, const cfg_obj_t *zconfig, cfg_aclconfctx_t *ac, - dns_zone_t *zone, dns_zone_t *raw); + dns_kasplist_t* kasplist, dns_zone_t *zone, + dns_zone_t *raw); /*%< * Configure or reconfigure a zone according to the named.conf - * data in 'cctx' and 'czone'. + * data. * * The zone origin is not configured, it is assumed to have been set * at zone creation time. * * Require: - * \li 'lctx' to be initialized or NULL. - * \li 'cctx' to be initialized or NULL. * \li 'ac' to point to an initialized cfg_aclconfctx_t. - * \li 'czone' to be initialized. + * \li 'kasplist' to be initialized. * \li 'zone' to be initialized. */ diff --git a/bin/named/server.c b/bin/named/server.c index 8730225864..ca220ec62c 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -50,6 +50,7 @@ #include #include +#include #include #include @@ -68,6 +69,7 @@ #include #include #include +#include #include #include #include @@ -459,8 +461,8 @@ configure_alternates(const cfg_obj_t *config, dns_view_t *view, static isc_result_t configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, - dns_viewlist_t *viewlist, cfg_aclconfctx_t *aclconf, - bool added, bool old_rpz_ok, + dns_viewlist_t *viewlist, dns_kasplist_t* kasplist, + cfg_aclconfctx_t *aclconf, bool added, bool old_rpz_ok, bool modify); static isc_result_t @@ -2685,7 +2687,8 @@ catz_addmodzone_taskaction(isc_task_t *task, isc_event_t *event0) { dns_view_thaw(ev->view); result = configure_zone(cfg->config, zoneobj, cfg->vconfig, ev->cbd->server->mctx, ev->view, - &ev->cbd->server->viewlist, cfg->actx, + &ev->cbd->server->viewlist, + &ev->cbd->server->kasplist, cfg->actx, true, false, ev->mod); dns_view_freeze(ev->view); isc_task_endexclusive(task); @@ -3770,11 +3773,10 @@ register_one_plugin(const cfg_obj_t *config, const cfg_obj_t *obj, * global defaults in 'config' used exclusively. */ static isc_result_t -configure_view(dns_view_t *view, dns_viewlist_t *viewlist, - cfg_obj_t *config, cfg_obj_t *vconfig, - named_cachelist_t *cachelist, const cfg_obj_t *bindkeys, - isc_mem_t *mctx, cfg_aclconfctx_t *actx, - bool need_hints) +configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, + cfg_obj_t *vconfig, named_cachelist_t *cachelist, + dns_kasplist_t *kasplist, const cfg_obj_t *bindkeys, + isc_mem_t *mctx, cfg_aclconfctx_t *actx, bool need_hints) { const cfg_obj_t *maps[4]; const cfg_obj_t *cfgmaps[3]; @@ -3901,8 +3903,8 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, { const cfg_obj_t *zconfig = cfg_listelt_value(element); CHECK(configure_zone(config, zconfig, vconfig, mctx, view, - viewlist, actx, false, old_rpz_ok, - false)); + viewlist, kasplist, actx, false, + old_rpz_ok, false)); } /* @@ -5899,8 +5901,8 @@ create_view(const cfg_obj_t *vconfig, dns_viewlist_t *viewlist, static isc_result_t configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, - dns_viewlist_t *viewlist, cfg_aclconfctx_t *aclconf, - bool added, bool old_rpz_ok, + dns_viewlist_t *viewlist, dns_kasplist_t *kasplist, + cfg_aclconfctx_t *aclconf, bool added, bool old_rpz_ok, bool modify) { dns_view_t *pview = NULL; /* Production view */ @@ -6117,8 +6119,8 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, zone)); dns_zone_setstats(zone, named_g_server->zonestats); } - CHECK(named_zone_configure(config, vconfig, zconfig, - aclconf, zone, NULL)); + CHECK(named_zone_configure(config, vconfig, zconfig, aclconf, + kasplist, zone, NULL)); dns_zone_attach(zone, &view->redirect); goto cleanup; } @@ -6280,8 +6282,8 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, /* * Configure the zone. */ - CHECK(named_zone_configure(config, vconfig, zconfig, - aclconf, zone, raw)); + CHECK(named_zone_configure(config, vconfig, zconfig, aclconf, kasplist, + zone, raw)); /* * Add the zone to its view in the new view list. @@ -7573,9 +7575,10 @@ configure_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, element = cfg_list_next(element)) { const cfg_obj_t *zconfig = cfg_listelt_value(element); - CHECK(configure_zone(config, zconfig, vconfig, mctx, - view, &named_g_server->viewlist, actx, - true, false, false)); + CHECK(configure_zone(config, zconfig, vconfig, mctx, view, + &named_g_server->viewlist, + &named_g_server->kasplist, actx, true, + false, false)); } result = ISC_R_SUCCESS; @@ -7759,8 +7762,9 @@ configure_newzone(const cfg_obj_t *zconfig, cfg_obj_t *config, cfg_aclconfctx_t *actx) { return (configure_zone(config, zconfig, vconfig, mctx, view, - &named_g_server->viewlist, actx, true, - false, false)); + &named_g_server->viewlist, + &named_g_server->kasplist, actx, true, false, + false)); } /*% @@ -7995,9 +7999,13 @@ load_configuration(const char *filename, named_server_t *server, const cfg_obj_t *obj; const cfg_obj_t *options; const cfg_obj_t *usev4ports, *avoidv4ports, *usev6ports, *avoidv6ports; + const cfg_obj_t *kasps; + dns_kasp_t *kasp = NULL; + dns_kasp_t *kasp_next = NULL; + dns_kasplist_t tmpkasplist, kasplist; const cfg_obj_t *views; dns_view_t *view = NULL; - dns_view_t *view_next; + dns_view_t *view_next = NULL; dns_viewlist_t tmpviewlist; dns_viewlist_t viewlist, builtin_viewlist; in_port_t listen_port, udpport_low, udpport_high; @@ -8026,6 +8034,7 @@ load_configuration(const char *filename, named_server_t *server, dns_aclenv_t *env = ns_interfacemgr_getaclenv(named_g_server->interfacemgr); + ISC_LIST_INIT(kasplist); ISC_LIST_INIT(viewlist); ISC_LIST_INIT(builtin_viewlist); ISC_LIST_INIT(cachelist); @@ -8640,6 +8649,39 @@ load_configuration(const char *filename, named_server_t *server, */ (void)configure_session_key(maps, server, named_g_mctx); + /* + * Create the DNSSEC key and signing policies (KASP). + */ + kasps = NULL; + (void)cfg_map_get(config, "dnssec-policy", &kasps); + for (element = cfg_list_first(kasps); + element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kconfig = cfg_listelt_value(element); + kasp = NULL; + CHECK(cfg_kasp_fromconfig(kconfig, named_g_mctx, &kasplist, + &kasp)); + INSIST(kasp != NULL); + dns_kasp_freeze(kasp); + dns_kasp_detach(&kasp); + } + /* + * Create the default kasp. + */ + kasp = NULL; + CHECK(cfg_kasp_fromconfig(NULL, named_g_mctx, &kasplist, &kasp)); + INSIST(kasp != NULL); + dns_kasp_freeze(kasp); + dns_kasp_detach(&kasp); + + tmpkasplist = server->kasplist; + server->kasplist = kasplist; + kasplist = tmpkasplist; + + /* + * Configure the views. + */ views = NULL; (void)cfg_map_get(config, "view", &views); @@ -8718,8 +8760,8 @@ load_configuration(const char *filename, named_server_t *server, view = NULL; CHECK(find_view(vconfig, &viewlist, &view)); CHECK(configure_view(view, &viewlist, config, vconfig, - &cachelist, bindkeys, named_g_mctx, - named_g_aclconfctx, true)); + &cachelist, &server->kasplist, bindkeys, + named_g_mctx, named_g_aclconfctx, true)); dns_view_freeze(view); dns_view_detach(&view); } @@ -8732,9 +8774,8 @@ load_configuration(const char *filename, named_server_t *server, view = NULL; CHECK(find_view(NULL, &viewlist, &view)); CHECK(configure_view(view, &viewlist, config, NULL, - &cachelist, bindkeys, - named_g_mctx, named_g_aclconfctx, - true)); + &cachelist, &server->kasplist, bindkeys, + named_g_mctx, named_g_aclconfctx, true)); dns_view_freeze(view); dns_view_detach(&view); } @@ -8753,9 +8794,8 @@ load_configuration(const char *filename, named_server_t *server, CHECK(create_view(vconfig, &builtin_viewlist, &view)); CHECK(configure_view(view, &viewlist, config, vconfig, - &cachelist, bindkeys, - named_g_mctx, named_g_aclconfctx, - false)); + &cachelist, &server->kasplist, bindkeys, + named_g_mctx, named_g_aclconfctx, false)); dns_view_freeze(view); dns_view_detach(&view); view = NULL; @@ -9191,6 +9231,10 @@ load_configuration(const char *filename, named_server_t *server, dns_view_detach(&view); } + if (kasp != NULL) { + dns_kasp_detach(&kasp); + } + ISC_LIST_APPENDLIST(viewlist, builtin_viewlist, link); /* @@ -9213,6 +9257,15 @@ load_configuration(const char *filename, named_server_t *server, dns_view_detach(&view); } + /* + * Same cleanup for kasp list. + */ + for (kasp = ISC_LIST_HEAD(kasplist); kasp != NULL; kasp = kasp_next) { + kasp_next = ISC_LIST_NEXT(kasp, link); + ISC_LIST_UNLINK(kasplist, kasp, link); + dns_kasp_detach(&kasp); + } + /* Same cleanup for cache list. */ while ((nsc = ISC_LIST_HEAD(cachelist)) != NULL) { ISC_LIST_UNLINK(cachelist, nsc, link); @@ -9460,7 +9513,8 @@ named_server_flushonshutdown(named_server_t *server, bool flush) { static void shutdown_server(isc_task_t *task, isc_event_t *event) { isc_result_t result; - dns_view_t *view, *view_next; + dns_view_t *view, *view_next = NULL; + dns_kasp_t *kasp, *kasp_next = NULL; named_server_t *server = (named_server_t *)event->ev_arg; bool flush = server->flushonshutdown; named_cache_t *nsc; @@ -9490,9 +9544,17 @@ shutdown_server(isc_task_t *task, isc_event_t *event) { (void) named_server_saventa(server); - for (view = ISC_LIST_HEAD(server->viewlist); - view != NULL; - view = view_next) { + for (kasp = ISC_LIST_HEAD(server->kasplist); kasp != NULL; + kasp = kasp_next) + { + kasp_next = ISC_LIST_NEXT(kasp, link); + ISC_LIST_UNLINK(server->kasplist, kasp, link); + dns_kasp_detach(&kasp); + } + + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; + view = view_next) + { view_next = ISC_LIST_NEXT(view, link); ISC_LIST_UNLINK(server->viewlist, view, link); if (flush) @@ -9610,6 +9672,7 @@ named_server_create(isc_mem_t *mctx, named_server_t **serverp) { /* Initialize server data structures. */ server->interfacemgr = NULL; + ISC_LIST_INIT(server->kasplist); ISC_LIST_INIT(server->viewlist); server->in_roothints = NULL; @@ -9797,6 +9860,7 @@ named_server_destroy(named_server_t **serverp) { isc_event_free(&server->reload_event); + INSIST(ISC_LIST_EMPTY(server->kasplist)); INSIST(ISC_LIST_EMPTY(server->viewlist)); INSIST(ISC_LIST_EMPTY(server->cachelist)); @@ -11766,7 +11830,10 @@ named_server_rekey(named_server_t *server, isc_lex_t *lex, keyopts = dns_zone_getkeyopts(zone); - /* "rndc loadkeys" requires "auto-dnssec maintain". */ + /* + * "rndc loadkeys" requires "auto-dnssec maintain" + * or a "dnssec-policy". + */ if ((keyopts & DNS_ZONEKEY_ALLOW) == 0) result = ISC_R_NOPERM; else if ((keyopts & DNS_ZONEKEY_MAINTAIN) == 0 && !fullsign) @@ -12931,7 +12998,8 @@ do_addzone(named_server_t *server, ns_cfgctx_t *cfg, dns_view_t *view, dns_view_thaw(view); result = configure_zone(cfg->config, zoneobj, cfg->vconfig, server->mctx, view, &server->viewlist, - cfg->actx, true, false, false); + &server->kasplist, cfg->actx, true, false, + false); dns_view_freeze(view); isc_task_endexclusive(server->task); @@ -13109,7 +13177,8 @@ do_modzone(named_server_t *server, ns_cfgctx_t *cfg, dns_view_t *view, dns_view_thaw(view); result = configure_zone(cfg->config, zoneobj, cfg->vconfig, server->mctx, view, &server->viewlist, - cfg->actx, true, false, true); + &server->kasplist, cfg->actx, true, false, + true); dns_view_freeze(view); exclusive = false; diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index 70b3bdba77..d8163d5c0c 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -840,8 +841,9 @@ process_notifytype(dns_notifytype_t ntype, dns_zonetype_t ztype, isc_result_t named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, - const cfg_obj_t *zconfig, cfg_aclconfctx_t *ac, - dns_zone_t *zone, dns_zone_t *raw) + const cfg_obj_t *zconfig, cfg_aclconfctx_t *ac, + dns_kasplist_t *kasplist, dns_zone_t *zone, + dns_zone_t *raw) { isc_result_t result; const char *zname; @@ -853,6 +855,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, const cfg_obj_t *options = NULL; const cfg_obj_t *obj; const char *filename = NULL; + const char *kaspname = NULL; const char *dupcheck; dns_notifytype_t notifytype = dns_notifytype_yes; uint32_t count; @@ -868,7 +871,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, int32_t journal_size; bool multi; bool alt; - dns_view_t *view; + dns_view_t *view = NULL; + dns_kasp_t *kasp = NULL; bool check = false, fail = false; bool warn = false, ignore = false; bool ixfrdiff; @@ -1192,6 +1196,21 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, */ if (ztype != dns_zone_stub && ztype != dns_zone_staticstub && ztype != dns_zone_redirect) { + obj = NULL; + result = cfg_map_get(zoptions, "dnssec-policy", &obj); + if (result == ISC_R_SUCCESS) { + kaspname = cfg_obj_asstring(obj); + result = dns_kasplist_find(kasplist, kaspname, &kasp); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(obj, named_g_lctx, + ISC_LOG_ERROR, + "'dnssec-policy '%s' not found ", + kaspname); + RETERR(result); + } + dns_zone_setkasp(zone, kasp); + } + obj = NULL; result = named_config_get(maps, "notify", &obj); INSIST(result == ISC_R_SUCCESS && obj != NULL); From c55625b0359f3c4d777efd882001d80de53962e7 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 12 Sep 2019 11:45:10 +0200 Subject: [PATCH 17/43] Add functionality to read key state from disk When reading a key from file, you can set the DST_TYPE_STATE option to also read the key state. This expects the Algorithm and Length fields go above the metadata, so update the write functionality to do so accordingly. Introduce new DST metadata types for KSK, ZSK, Lifetime and the timing metadata used in state files. --- lib/dns/dst_api.c | 258 +++++++++++++++++++++++++++++++++--- lib/dns/include/dst/dst.h | 42 ++++-- lib/dns/win32/libdns.def.in | 1 + 3 files changed, 273 insertions(+), 28 deletions(-) diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index 97cc044da0..eaacd89209 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -64,6 +64,63 @@ #define DST_AS_STR(t) ((t).value.as_textregion.base) +#define NEXTTOKEN(lex, opt, token) { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret != ISC_R_SUCCESS) \ + goto cleanup; \ + } + +#define NEXTTOKEN_OR_EOF(lex, opt, token) { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret == ISC_R_EOF) \ + break; \ + if (ret != ISC_R_SUCCESS) \ + goto cleanup; \ + } + +#define READLINE(lex, opt, token) \ + do { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret == ISC_R_EOF) \ + break; \ + else if (ret != ISC_R_SUCCESS) \ + goto cleanup; \ + } while ((*token).type != isc_tokentype_eol) + +#define BADTOKEN() { \ + ret = ISC_R_UNEXPECTEDTOKEN; \ + goto cleanup; \ + } + +#define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1) +static const char *numerictags[NUMERIC_NTAGS] = { + "Lifetime:" +}; + +#define BOOLEAN_NTAGS (DST_MAX_BOOLEAN + 1) +static const char *booleantags[BOOLEAN_NTAGS] = { + "KSK:", + "ZSK:" +}; + +#define TIMING_NTAGS (DST_MAX_TIMES + 1) +static const char *timingtags[TIMING_NTAGS] = { + "Generated:", + "Published:", + "Active:", + "Retired:", + "Revoked:", + "Removed:", + + "DSPublish:", + "SyncPublish:", + "SyncDelete:" +}; + +#define STATE_ALGORITHM_STR "Algorithm:" +#define STATE_LENGTH_STR "Length:" +#define MAX_NTAGS (DST_MAX_NUMERIC + DST_MAX_BOOLEAN + DST_MAX_TIMES) + static dst_func_t *dst_t_func[DST_MAX_ALGS]; static bool dst_initialized = false; @@ -84,7 +141,7 @@ static dst_key_t * get_key_struct(const dns_name_t *name, static isc_result_t write_public_key(const dst_key_t *key, int type, const char *directory); static isc_result_t write_key_state(const dst_key_t *key, int type, - const char *directory); + const char *directory); static isc_result_t buildfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg, @@ -424,7 +481,8 @@ dst_key_getfilename(dns_name_t *name, dns_keytag_t id, REQUIRE(dst_initialized == true); REQUIRE(dns_name_isabsolute(name)); - REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 0); + REQUIRE((type & + (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE)) != 0); REQUIRE(mctx != NULL); REQUIRE(buf != NULL); @@ -546,6 +604,25 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, return (result); } + /* + * Read the state file, if requested by type. + */ + if ((type & DST_TYPE_STATE) != 0) { + newfilenamelen = strlen(filename) + 7; + if (dirname != NULL) { + newfilenamelen += strlen(dirname) + 1; + } + newfilename = isc_mem_get(mctx, newfilenamelen); + result = addsuffix(newfilename, newfilenamelen, + dirname, filename, ".state"); + INSIST(result == ISC_R_SUCCESS); + + result = dst_key_read_state(newfilename, mctx, pubkey); + isc_mem_put(mctx, newfilename, newfilenamelen); + newfilename = NULL; + RETERR(result); + } + key = get_key_struct(pubkey->key_name, pubkey->key_alg, pubkey->key_flags, pubkey->key_proto, 0, pubkey->key_class, pubkey->key_ttl, mctx); @@ -1396,7 +1473,7 @@ dst_key_setinactive(dst_key_t *key, bool inactive) { } /*% - * Reads a public key from disk + * Reads a public key from disk. */ isc_result_t dst_key_read_public(const char *filename, int type, @@ -1438,17 +1515,6 @@ dst_key_read_public(const char *filename, int type, if (ret != ISC_R_SUCCESS) goto cleanup; -#define NEXTTOKEN(lex, opt, token) { \ - ret = isc_lex_gettoken(lex, opt, token); \ - if (ret != ISC_R_SUCCESS) \ - goto cleanup; \ - } - -#define BADTOKEN() { \ - ret = ISC_R_UNEXPECTEDTOKEN; \ - goto cleanup; \ - } - /* Read the domain name */ NEXTTOKEN(lex, opt, &token); if (token.type != isc_tokentype_string) @@ -1521,6 +1587,165 @@ dst_key_read_public(const char *filename, int type, return (ret); } +static int +find_metadata(const char *s, const char *tags[], int ntags) { + for (int i = 0; i < ntags; i++) { + if (tags[i] != NULL && strcasecmp(s, tags[i]) == 0) + return (i); + } + return (-1); +} + +static int +find_numericdata(const char *s) { + return (find_metadata(s, numerictags, NUMERIC_NTAGS)); +} + +static int +find_booleandata(const char *s) { + return (find_metadata(s, booleantags, BOOLEAN_NTAGS)); +} + +static int +find_timingdata(const char *s) { + return (find_metadata(s, timingtags, TIMING_NTAGS)); +} + + +/*% + * Reads a key state from disk. + */ +isc_result_t +dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) +{ + isc_lex_t *lex = NULL; + isc_token_t token; + isc_result_t ret; + unsigned int opt = ISC_LEXOPT_DNSMULTILINE; + + ret = isc_lex_create(mctx, 1500, &lex); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + isc_lex_setcomments(lex, ISC_LEXCOMMENT_DNSMASTERFILE); + + ret = isc_lex_openfile(lex, filename); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Read the algorithm line. + */ + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string || + strcmp(DST_AS_STR(token), STATE_ALGORITHM_STR) != 0) + { + BADTOKEN(); + } + + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); + if (token.type != isc_tokentype_number || + token.value.as_ulong != (unsigned long) dst_key_alg(key)) + { + BADTOKEN(); + } + + /* + * Read the length line. + */ + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string || + strcmp(DST_AS_STR(token), STATE_LENGTH_STR) != 0) + { + BADTOKEN(); + } + + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); + if (token.type != isc_tokentype_number || + token.value.as_ulong != (unsigned long) dst_key_size(key)) + { + BADTOKEN(); + } + + /* + * Read the metadata. + */ + for (int n = 0; n < MAX_NTAGS; n++) { + int tag; + + NEXTTOKEN_OR_EOF(lex, opt, &token); + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + /* Numeric metadata */ + tag = find_numericdata(DST_AS_STR(token)); + if (tag >= 0) { + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); + if (token.type != isc_tokentype_number) { + BADTOKEN(); + } + dst_key_setnum(key, tag, token.value.as_ulong); + + goto next; + } + + /* Boolean metadata */ + tag = find_booleandata(DST_AS_STR(token)); + if (tag >= 0) { + INSIST(tag < BOOLEAN_NTAGS); + + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + if (strcmp(DST_AS_STR(token), "yes") == 0) { + dst_key_setbool(key, tag, true); + } else if (strcmp(DST_AS_STR(token), "no") == 0) { + dst_key_setbool(key, tag, false); + } else { + BADTOKEN(); + } + + goto next; + } + + /* Timing metadata */ + tag = find_timingdata(DST_AS_STR(token)); + if (tag >= 0) { + uint32_t when; + + INSIST(tag < TIMING_NTAGS); + + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + ret = dns_time32_fromtext(DST_AS_STR(token), &when); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + dst_key_settime(key, tag, when); + + goto next; + } + +next: + READLINE(lex, opt, &token); + } + + /* Done, successfully parsed the whole file. */ + ret = ISC_R_SUCCESS; + +cleanup: + if (lex != NULL) { + isc_lex_destroy(&lex); + } + return (ret); +} + static bool issymmetric(const dst_key_t *key) { REQUIRE(dst_initialized == true); @@ -1670,6 +1895,9 @@ write_key_state(const dst_key_t *key, int type, const char *directory) { } fputc('\n', fp); + fprintf(fp, "Algorithm: %u\n", key->key_alg); + fprintf(fp, "Length: %u\n", key->key_size); + printtime(key, DST_TIME_CREATED, "Generated", fp); printtime(key, DST_TIME_PUBLISH, "Published", fp); printtime(key, DST_TIME_ACTIVATE, "Active", fp); @@ -1678,8 +1906,6 @@ write_key_state(const dst_key_t *key, int type, const char *directory) { printtime(key, DST_TIME_DELETE, "Removed", fp); printnum(key, DST_NUM_LIFETIME, "Lifetime", fp); - fprintf(fp, "Algorithm: %u\n", key->key_alg); - fprintf(fp, "Length: %u\n", key->key_size); printbool(key, DST_BOOL_KSK, "KSK", fp); printbool(key, DST_BOOL_ZSK, "ZSK", fp); diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index f747619a40..e92de43417 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -317,16 +317,17 @@ dst_key_fromfile(dns_name_t *name, dns_keytag_t id, unsigned int alg, int type, const char *directory, isc_mem_t *mctx, dst_key_t **keyp); /*%< * Reads a key from permanent storage. The key can either be a public or - * private key, and is specified by name, algorithm, and id. If a private key - * is specified, the public key must also be present. If directory is NULL, - * the current directory is assumed. + * private key, or a key state. It specified by name, algorithm, and id. If + * a private key or key state is specified, the public key must also be + * present. If directory is NULL, the current directory is assumed. * * Requires: * \li "name" is a valid absolute dns name. * \li "id" is a valid key tag identifier. * \li "alg" is a supported key algorithm. - * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union. - * DST_TYPE_KEY look for a KEY record otherwise DNSKEY + * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE or the bitwise union. + * DST_TYPE_KEY look for a KEY record otherwise DNSKEY. + * DST_TYPE_STATE to also read the key state. * \li "mctx" is a valid memory context. * \li "keyp" is not NULL and "*keyp" is NULL. * @@ -343,8 +344,8 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, int type, isc_mem_t *mctx, dst_key_t **keyp); /*%< * Reads a key from permanent storage. The key can either be a public or - * key, and is specified by filename. If a private key is specified, the - * public key must also be present. + * private key, or a key stae. It is specified by filename. If a private key + * or key state is specified, the public key must also be present. * * If 'dirname' is not NULL, and 'filename' is a relative path, * then the file is looked up relative to the given directory. @@ -352,8 +353,9 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, * * Requires: * \li "filename" is not NULL - * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union - * DST_TYPE_KEY look for a KEY record otherwise DNSKEY + * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union. + * DST_TYPE_KEY look for a KEY record otherwise DNSKEY. + * DST_TYPE_STATE to also read the key state. * \li "mctx" is a valid memory context * \li "keyp" is not NULL and "*keyp" is NULL. * @@ -373,9 +375,9 @@ dst_key_read_public(const char *filename, int type, * Reads a public key from permanent storage. The key must be a public key. * * Requires: - * \li "filename" is not NULL - * \li "type" is DST_TYPE_KEY look for a KEY record otherwise DNSKEY - * \li "mctx" is a valid memory context + * \li "filename" is not NULL. + * \li "type" is DST_TYPE_KEY look for a KEY record otherwise DNSKEY. + * \li "mctx" is a valid memory context. * \li "keyp" is not NULL and "*keyp" is NULL. * * Returns: @@ -388,6 +390,22 @@ dst_key_read_public(const char *filename, int type, * \li If successful, *keyp will contain a valid key. */ +isc_result_t +dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *keyp); +/*%< + * Reads a key state from permanent storage. + * + * Requires: + * \li "filename" is not NULL. + * \li "mctx" is a valid memory context. + * \li "keyp" is not NULL and "*keyp" is NULL. + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_UNEXPECTEDTOKEN if the file can not be parsed as a public key + * \li any other result indicates failure + */ + isc_result_t dst_key_tofile(const dst_key_t *key, int type, const char *directory); /*%< diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index beef70a5d7..a5c7949118 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -1425,6 +1425,7 @@ dst_key_privatefrombuffer dst_key_proto dst_key_pubcompare dst_key_read_public +dst_key_read_state dst_key_restore dst_key_rid dst_key_secretsize From 72042a06d662742f04bbeb83aade6a94a8b9968e Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 12 Sep 2019 11:57:55 +0200 Subject: [PATCH 18/43] dnssec-settime: Allow manipulating state files Introduce a new option '-s' for dnssec-settime that when manipulating timing metadata, it also updates the key state file. For testing purposes, add options to dnssec-settime to set key states and when they last changed. The dst code adds ways to write and read the new key states and timing metadata. It updates the parsing code for private key files to not parse the newly introduced metadata (these are for state files only). Introduce key goal (the state the key wants to be in). --- bin/dnssec/dnssec-settime.c | 243 +++++++++++++++++++++++++----- bin/dnssec/dnssec-settime.docbook | 93 ++++++++++++ bin/dnssec/dnssectool.c | 20 +++ bin/dnssec/dnssectool.h | 2 + bin/tests/system/kasp/tests.sh | 76 +++++++--- lib/dns/dst_api.c | 179 +++++++++++++++++----- lib/dns/dst_internal.h | 5 + lib/dns/dst_parse.c | 11 +- lib/dns/include/dst/dst.h | 80 +++++++++- lib/dns/win32/libdns.def.in | 3 + 10 files changed, 618 insertions(+), 94 deletions(-) diff --git a/bin/dnssec/dnssec-settime.c b/bin/dnssec/dnssec-settime.c index bcf32c72b7..cc72e55662 100644 --- a/bin/dnssec/dnssec-settime.c +++ b/bin/dnssec/dnssec-settime.c @@ -88,6 +88,15 @@ usage(void) { fprintf(stderr, " -i : prepublication interval for " "successor key " "(default: 30 days)\n"); + fprintf(stderr, "Key state options:\n"); + fprintf(stderr, " -s: update key state file (default no)\n"); + fprintf(stderr, " -g state: set the goal state for this key\n"); + fprintf(stderr, " -d state date/[+-]offset: set the DS state\n"); + fprintf(stderr, " -k state date/[+-]offset: set the DNSKEY state\n"); + fprintf(stderr, " -r state date/[+-]offset: set the RRSIG (KSK) " + "state\n"); + fprintf(stderr, " -z state date/[+-]offset: set the RRSIG (ZSK) " + "state\n"); fprintf(stderr, "Printing options:\n"); fprintf(stderr, " -p C/P/Psync/A/R/I/D/Dsync/all: print a " "particular time value or values\n"); @@ -123,29 +132,87 @@ printtime(dst_key_t *key, int type, const char *tag, bool epoch, } } +static void +writekey(dst_key_t *key, const char *directory, bool write_state) +{ + char newname[1024]; + char keystr[DST_KEY_FORMATSIZE]; + isc_buffer_t buf; + isc_result_t result; + int options = DST_TYPE_PUBLIC|DST_TYPE_PRIVATE; + + if (write_state) { + options |= DST_TYPE_STATE; + } + + isc_buffer_init(&buf, newname, sizeof(newname)); + result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build public key filename: %s", + isc_result_totext(result)); + } + + result = dst_key_tofile(key, options, directory); + if (result != ISC_R_SUCCESS) { + dst_key_format(key, keystr, sizeof(keystr)); + fatal("Failed to write key %s: %s", keystr, + isc_result_totext(result)); + } + printf("%s\n", newname); + + isc_buffer_clear(&buf); + result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build private key filename: %s", + isc_result_totext(result)); + } + printf("%s\n", newname); + + if (write_state) { + isc_buffer_clear(&buf); + result = dst_key_buildfilename(key, DST_TYPE_STATE, directory, + &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build key state filename: %s", + isc_result_totext(result)); + } + printf("%s\n", newname); + } +} + int main(int argc, char **argv) { isc_result_t result; const char *engine = NULL; const char *filename = NULL; char *directory = NULL; - char newname[1024]; char keystr[DST_KEY_FORMATSIZE]; char *endp, *p; int ch; const char *predecessor = NULL; dst_key_t *prevkey = NULL; dst_key_t *key = NULL; - isc_buffer_t buf; dns_name_t *name = NULL; dns_secalg_t alg = 0; unsigned int size = 0; uint16_t flags = 0; int prepub = -1; + int options; dns_ttl_t ttl = 0; isc_stdtime_t now; + isc_stdtime_t dstime = 0, dnskeytime = 0; + isc_stdtime_t krrsigtime = 0, zrrsigtime = 0; isc_stdtime_t pub = 0, act = 0, rev = 0, inact = 0, del = 0; isc_stdtime_t prevact = 0, previnact = 0, prevdel = 0; + dst_key_state_t goal = DST_KEY_STATE_NA; + dst_key_state_t ds = DST_KEY_STATE_NA; + dst_key_state_t dnskey = DST_KEY_STATE_NA; + dst_key_state_t krrsig = DST_KEY_STATE_NA; + dst_key_state_t zrrsig = DST_KEY_STATE_NA; + bool setgoal = false, setds = false, setdnskey = false; + bool setkrrsig = false, setzrrsig = false; + bool setdstime = false, setdnskeytime = false; + bool setkrrsigtime = false, setzrrsigtime = false; bool setpub = false, setact = false; bool setrev = false, setinact = false; bool setdel = false, setttl = false; @@ -156,14 +223,17 @@ main(int argc, char **argv) { bool printact = false, printrev = false; bool printinact = false, printdel = false; bool force = false; - bool epoch = false; - bool changed = false; + bool epoch = false; + bool changed = false; + bool write_state = false; isc_log_t *log = NULL; isc_stdtime_t syncadd = 0, syncdel = 0; bool unsetsyncadd = false, setsyncadd = false; bool unsetsyncdel = false, setsyncdel = false; bool printsyncadd = false, printsyncdel = false; + options = DST_TYPE_PUBLIC|DST_TYPE_PRIVATE|DST_TYPE_STATE; + if (argc == 1) usage(); @@ -180,7 +250,7 @@ main(int argc, char **argv) { isc_stdtime_get(&now); -#define CMDLINE_FLAGS "A:D:E:fhI:i:K:L:P:p:R:S:uv:V" +#define CMDLINE_FLAGS "A:D:d:E:fg:hI:i:K:k:L:P:p:R:r:S:suv:Vz:" while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { switch (ch) { case 'E': @@ -339,6 +409,70 @@ main(int argc, char **argv) { case 'i': prepub = strtottl(isc_commandline_argument); break; + case 's': + write_state = true; + break; + case 'g': + if (setgoal) { + fatal("-g specified more than once"); + } + + goal = strtokeystate(isc_commandline_argument); + if (goal != DST_KEY_STATE_NA && + goal != DST_KEY_STATE_HIDDEN && + goal != DST_KEY_STATE_OMNIPRESENT) { + fatal("-g must be either none, hidden, or " + "omnipresent"); + } + setgoal = true; + break; + case 'd': + if (setds) { + fatal("-d specified more than once"); + } + + ds = strtokeystate(isc_commandline_argument); + setds = true; + /* time */ + (void)isoptarg(isc_commandline_argument, argv, usage); + dstime = strtotime(isc_commandline_argument, + now, now, &setdstime); + break; + case 'k': + if (setdnskey) { + fatal("-k specified more than once"); + } + + dnskey = strtokeystate(isc_commandline_argument); + setdnskey = true; + /* time */ + (void)isoptarg(isc_commandline_argument, argv, usage); + dnskeytime = strtotime(isc_commandline_argument, + now, now, &setdnskeytime); + break; + case 'r': + if (setkrrsig) { + fatal("-r specified more than once"); + } + + krrsig = strtokeystate(isc_commandline_argument); + setkrrsig = true; + /* time */ + (void)isoptarg(isc_commandline_argument, argv, usage); + krrsigtime = strtotime(isc_commandline_argument, + now, now, &setkrrsigtime); + break; + case 'z': + if (setzrrsig) { + fatal("-z specified more than once"); + } + + zrrsig = strtokeystate(isc_commandline_argument); + setzrrsig = true; + (void)isoptarg(isc_commandline_argument, argv, usage); + zrrsigtime = strtotime(isc_commandline_argument, + now, now, &setzrrsigtime); + break; case '?': if (isc_commandline_option != '?') fprintf(stderr, "%s: invalid argument -%c\n", @@ -365,6 +499,12 @@ main(int argc, char **argv) { if (argc > isc_commandline_index + 1) fatal("Extraneous arguments"); + if ((setgoal || setds || setdnskey || setkrrsig || setzrrsig) && + !write_state) + { + fatal("Options -g, -d, -k, -r and -z require -s to be set"); + } + result = dst_lib_init(mctx, engine); if (result != ISC_R_SUCCESS) fatal("Could not initialize dst: %s", @@ -381,9 +521,7 @@ main(int argc, char **argv) { if (setact || unsetact) fatal("-S and -A cannot be used together"); - result = dst_key_fromnamedfile(predecessor, directory, - DST_TYPE_PUBLIC | - DST_TYPE_PRIVATE, + result = dst_key_fromnamedfile(predecessor, directory, options, mctx, &prevkey); if (result != ISC_R_SUCCESS) fatal("Invalid keyfile %s: %s", @@ -475,9 +613,8 @@ main(int argc, char **argv) { isc_result_totext(result)); } - result = dst_key_fromnamedfile(filename, directory, - DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, - mctx, &key); + result = dst_key_fromnamedfile(filename, directory, options, mctx, + &key); if (result != ISC_R_SUCCESS) fatal("Invalid keyfile %s: %s", filename, isc_result_totext(result)); @@ -588,6 +725,63 @@ main(int argc, char **argv) { changed = true; } + /* + * Make sure the key state goals are written. + */ + if (write_state) { + if (setgoal) { + if (goal == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_GOAL); + } else { + dst_key_setstate(key, DST_KEY_GOAL, goal); + } + changed = true; + } + if (setds) { + if (ds == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_DS); + dst_key_unsettime(key, DST_TIME_DS); + } else { + dst_key_setstate(key, DST_KEY_DS, ds); + dst_key_settime(key, DST_TIME_DS, dstime); + } + changed = true; + } + if (setdnskey) { + if (dnskey == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_DNSKEY); + dst_key_unsettime(key, DST_TIME_DNSKEY); + } else { + dst_key_setstate(key, DST_KEY_DNSKEY, dnskey); + dst_key_settime(key, DST_TIME_DNSKEY, + dnskeytime); + } + changed = true; + } + if (setkrrsig) { + if (krrsig == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_KRRSIG); + dst_key_unsettime(key, DST_TIME_KRRSIG); + } else { + dst_key_setstate(key, DST_KEY_KRRSIG, krrsig); + dst_key_settime(key, DST_TIME_KRRSIG, + krrsigtime); + } + changed = true; + } + if (setzrrsig) { + if (zrrsig == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_ZRRSIG); + dst_key_unsettime(key, DST_TIME_ZRRSIG); + } else { + dst_key_setstate(key, DST_KEY_ZRRSIG, zrrsig); + dst_key_settime(key, DST_TIME_ZRRSIG, + zrrsigtime); + } + changed = true; + } + } + if (!changed && setttl) changed = true; @@ -621,32 +815,7 @@ main(int argc, char **argv) { epoch, stdout); if (changed) { - isc_buffer_init(&buf, newname, sizeof(newname)); - result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, - &buf); - if (result != ISC_R_SUCCESS) { - fatal("Failed to build public key filename: %s", - isc_result_totext(result)); - } - - result = dst_key_tofile(key, DST_TYPE_PUBLIC|DST_TYPE_PRIVATE, - directory); - if (result != ISC_R_SUCCESS) { - dst_key_format(key, keystr, sizeof(keystr)); - fatal("Failed to write key %s: %s", keystr, - isc_result_totext(result)); - } - - printf("%s\n", newname); - - isc_buffer_clear(&buf); - result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, - &buf); - if (result != ISC_R_SUCCESS) { - fatal("Failed to build private key filename: %s", - isc_result_totext(result)); - } - printf("%s\n", newname); + writekey(key, directory, write_state); } if (prevkey != NULL) diff --git a/bin/dnssec/dnssec-settime.docbook b/bin/dnssec/dnssec-settime.docbook index bf432bd44e..905d72af8f 100644 --- a/bin/dnssec/dnssec-settime.docbook +++ b/bin/dnssec/dnssec-settime.docbook @@ -64,6 +64,12 @@ + + + + + + keyfile @@ -88,11 +94,30 @@ When key metadata fields are changed, both files of a key pair (Knnnn.+aaa+iiiii.key and Knnnn.+aaa+iiiii.private) are regenerated. + + Metadata fields are stored in the private file. A human-readable description of the metadata is also placed in comments in the key file. The private file's permissions are always set to be inaccessible to anyone other than the owner (mode 0600). + + When working with state files, it is possible to update the timing + metadata in those files as well with . If this + option is used you can also update key states with + (DS), (DNSKEY), (RRSIG of KSK), + or (RRSIG of ZSK). Allowed states are HIDDEN, + RUMOURED, OMNIPRESENT, and UNRETENTIVE. + + + You can also set the goal state of the key with . + This should be either HIDDEN or OMNIPRESENT (representing whether the + key should be removed from the zone, or published). + + + It is NOT RECOMMENDED to manipulate state files manually except for + testing purposes. + OPTIONS @@ -319,6 +344,74 @@ + KEY STATE OPTIONS + + + Known key states are HIDDEN, RUMOURED, OMNIPRESENT and UNRETENTIVE. + These should not be set manually except for testing purposes. + + + + + + -s + + + When setting key timing data, also update the state file. + + + + + + -g + + + Set the goal state for this key. Must be HIDDEN or OMNIPRESENT. + + + + + + -d + + + Set the DS state for this key, and when it was last changed. + + + + + + -k + + + Set the DNSKEY state for this key, and when it was last changed. + + + + + + -r + + + Set the RRSIG (KSK) state for this key, and when it was last + changed. + + + + + + -z + + + Set the RRSIG (ZSK) state for this key, and when it was last + changed. + + + + + + + PRINTING OPTIONS diff --git a/bin/dnssec/dnssectool.c b/bin/dnssec/dnssectool.c index d409965fed..aadd2ee39c 100644 --- a/bin/dnssec/dnssectool.c +++ b/bin/dnssec/dnssectool.c @@ -57,6 +57,11 @@ #include "dnssectool.h" +#define KEYSTATES_NVALUES 4 +static const char *keystates[KEYSTATES_NVALUES] = { + "hidden", "rumoured", "omnipresent", "unretentive", +}; + int verbose = 0; bool quiet = false; uint8_t dtype[8]; @@ -244,6 +249,21 @@ strtottl(const char *str) { return (ttl); } +dst_key_state_t +strtokeystate(const char *str) { + if (isnone(str)) { + return (DST_KEY_STATE_NA); + } + + for (int i = 0; i < KEYSTATES_NVALUES; i++) { + if (keystates[i] != NULL && + strcasecmp(str, keystates[i]) == 0) { + return (dst_key_state_t) i; + } + } + fatal("unknown key state"); +} + isc_stdtime_t strtotime(const char *str, int64_t now, int64_t base, bool *setp) diff --git a/bin/dnssec/dnssectool.h b/bin/dnssec/dnssectool.h index cddfb2f902..3e88e2959e 100644 --- a/bin/dnssec/dnssectool.h +++ b/bin/dnssec/dnssectool.h @@ -71,6 +71,8 @@ cleanup_logging(isc_log_t **logp); dns_ttl_t strtottl(const char *str); +dst_key_state_t strtokeystate(const char *str); + isc_stdtime_t strtotime(const char *str, int64_t now, int64_t base, bool *setp); diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index 92de7567d8..d7353ab9fa 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -17,7 +17,6 @@ set -e status=0 n=0 -log=1 ################################################################################ # Utilities # @@ -35,9 +34,14 @@ get_keyids() { ls ${start}*${end} | sed "s/$dir\/K${zone}.+${algorithm}+\([0-9]\{5\}\)${end}/\1/" } +# By default log errors and don't quit immediately. +_log=1 +_continue=1 log_error() { - test $log -eq 1 && echo_i "error: $1" + test $_log -eq 1 && echo_i "error: $1" ret=$((ret+1)) + + test $_continue -eq 1 || exit 1 } # Check the created key in directory $1 for zone $2. @@ -115,18 +119,6 @@ check_created_key() { # # dnssec-keygen # -n=$((n+1)) -echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" -ret=0 -$KEYGEN -k _default kasp > keygen.out._default.test$n 2>/dev/null || ret=1 -lines=$(cat keygen.out._default.test$n | wc -l) -test "$lines" -eq 1 || log_error "wrong number of keys created for policy _default" -KEY_ID=$(get_keyids "." "kasp" "13") -echo_i "check key $KEY_ID..." -check_created_key "." "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "3600" "0" -test "$ret" -eq 0 || echo_i "failed" -status=$((status+ret)) - n=$((n+1)) echo_i "check that 'dnssec-keygen -k' (configured policy) creates valid files ($n)" ret=0 @@ -138,7 +130,7 @@ KEY_ID=$(get_keyids "keys" "kasp" "13") echo_i "check key $KEY_ID..." check_created_key "keys" "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "200" "31536000" # Temporarily don't log errors because we are searching multiple files. -log=0 +_log=0 # Check the other algorithm. KEY_IDS=$(get_keyids "keys" "kasp" "8") for KEY_ID in $KEY_IDS; do @@ -151,10 +143,21 @@ for KEY_ID in $KEY_IDS; do # If ret is non-zero, non of the files matched. test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) - done # Turn error logs on again. -log=1 +_log=1 + +n=$((n+1)) +echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" +ret=0 +$KEYGEN -k _default kasp > keygen.out._default.test$n 2>/dev/null || ret=1 +lines=$(cat keygen.out._default.test$n | wc -l) +test "$lines" -eq 1 || log_error "wrong number of keys created for policy _default" +KEY_ID=$(get_keyids "." "kasp" "13") +echo_i "check key $KEY_ID..." +check_created_key "." "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "3600" "0" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) n=$((n+1)) echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" @@ -171,6 +174,45 @@ status=$((status+ret)) # # dnssec-settime # +BASE_FILE="K${zone}.+${alg_numpad}+${key_idpad}" +KEY_FILE="${BASE_FILE}.key" +PRIVATE_FILE="${BASE_FILE}.private" +STATE_FILE="${BASE_FILE}.state" +CMP_FILE="${BASE_FILE}.cmp" + +n=$((n+1)) +echo_i "check that 'dnssec-settime' by default does not edit key state file ($n)" +ret=0 +cp $STATE_FILE $CMP_FILE +$SETTIME -P +3600 $BASE_FILE >/dev/null || log_error "settime failed" +grep "; Publish: " $KEY_FILE > /dev/null || log_error "mismatch published in $KEY_FILE" +grep "Publish: " $PRIVATE_FILE > /dev/null || log_error "mismatch published in $PRIVATE_FILE" +$DIFF $CMP_FILE $STATE_FILE || log_error "unexpected file change in $STATE_FILE" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +n=$((n+1)) +echo_i "check that 'dnssec-settime -s' also sets time metadata in key state file ($n)" +ret=0 +cp $STATE_FILE $CMP_FILE +now=$(date +%Y%m%d%H%M%S) +$SETTIME -s -P $now $BASE_FILE >/dev/null || log_error "settime failed" +grep "; Publish: $now" $KEY_FILE > /dev/null || log_error "mismatch published in $KEY_FILE" +grep "Publish: $now" $PRIVATE_FILE > /dev/null || log_error "mismatch published in $PRIVATE_FILE" +grep "Published: $now" $STATE_FILE > /dev/null || log_error "mismatch published in $STATE_FILE" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +n=$((n+1)) +echo_i "check that 'dnssec-settime -s' also unsets time metadata in key state file ($n)" +ret=0 +cp $STATE_FILE $CMP_FILE +$SETTIME -s -P none $BASE_FILE >/dev/null || log_error "settime failed" +grep "; Publish:" $KEY_FILE > /dev/null && log_error "unexpected published in $KEY_FILE" +grep "Publish:" $PRIVATE_FILE > /dev/null && log_error "unexpected published in $PRIVATE_FILE" +grep "Published:" $STATE_FILE > /dev/null && log_error "unexpected published in $STATE_FILE" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) # # named diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index eaacd89209..978f8839c4 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -70,20 +70,21 @@ goto cleanup; \ } -#define NEXTTOKEN_OR_EOF(lex, opt, token) { \ - ret = isc_lex_gettoken(lex, opt, token); \ - if (ret == ISC_R_EOF) \ - break; \ - if (ret != ISC_R_SUCCESS) \ - goto cleanup; \ - } +#define NEXTTOKEN_OR_EOF(lex, opt, token) \ + do { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret == ISC_R_EOF) \ + break; \ + if (ret != ISC_R_SUCCESS) \ + goto cleanup; \ + } while ((*token).type == isc_tokentype_eol); \ #define READLINE(lex, opt, token) \ do { \ ret = isc_lex_gettoken(lex, opt, token); \ if (ret == ISC_R_EOF) \ break; \ - else if (ret != ISC_R_SUCCESS) \ + if (ret != ISC_R_SUCCESS) \ goto cleanup; \ } while ((*token).type != isc_tokentype_eol) @@ -94,6 +95,10 @@ #define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1) static const char *numerictags[NUMERIC_NTAGS] = { + "Predecessor:", + "Successor:", + "MaxTTL:", + "RollPeriod:", "Lifetime:" }; @@ -108,18 +113,38 @@ static const char *timingtags[TIMING_NTAGS] = { "Generated:", "Published:", "Active:", - "Retired:", "Revoked:", + "Retired:", "Removed:", "DSPublish:", "SyncPublish:", - "SyncDelete:" + "SyncDelete:", + + "DNSKEYChange:", + "ZRRSIGChange:", + "KRRSIGChange:", + "DSChange:" +}; + +#define KEYSTATES_NTAGS (DST_MAX_KEYSTATES + 1) +static const char *keystatestags[KEYSTATES_NTAGS] = { + "DNSKEYState:", + "ZRRSIGState:", + "KRRSIGState:", + "DSState:", + "GoalState:" +}; + +#define KEYSTATES_NVALUES 4 +static const char *keystates[KEYSTATES_NVALUES] = { + "hidden", "rumoured", "omnipresent", "unretentive", }; #define STATE_ALGORITHM_STR "Algorithm:" #define STATE_LENGTH_STR "Length:" -#define MAX_NTAGS (DST_MAX_NUMERIC + DST_MAX_BOOLEAN + DST_MAX_TIMES) +#define MAX_NTAGS (DST_MAX_NUMERIC + DST_MAX_BOOLEAN + \ + DST_MAX_TIMES + DST_MAX_KEYSTATES) static dst_func_t *dst_t_func[DST_MAX_ALGS]; @@ -604,10 +629,23 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, return (result); } + key = get_key_struct(pubkey->key_name, pubkey->key_alg, + pubkey->key_flags, pubkey->key_proto, + pubkey->key_size, pubkey->key_class, + pubkey->key_ttl, mctx); + if (key == NULL) { + dst_key_free(&pubkey); + return (ISC_R_NOMEMORY); + } + + if (key->func->parse == NULL) + RETERR(DST_R_UNSUPPORTEDALG); + /* * Read the state file, if requested by type. */ if ((type & DST_TYPE_STATE) != 0) { + newfilenamelen = strlen(filename) + 7; if (dirname != NULL) { newfilenamelen += strlen(dirname) + 1; @@ -617,23 +655,17 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, dirname, filename, ".state"); INSIST(result == ISC_R_SUCCESS); - result = dst_key_read_state(newfilename, mctx, pubkey); + result = dst_key_read_state(newfilename, mctx, &key); + if (result == ISC_R_FILENOTFOUND) { + /* Having no state is valid. */ + result = ISC_R_SUCCESS; + } + isc_mem_put(mctx, newfilename, newfilenamelen); newfilename = NULL; RETERR(result); } - key = get_key_struct(pubkey->key_name, pubkey->key_alg, - pubkey->key_flags, pubkey->key_proto, 0, - pubkey->key_class, pubkey->key_ttl, mctx); - if (key == NULL) { - dst_key_free(&pubkey); - return (ISC_R_NOMEMORY); - } - - if (key->func->parse == NULL) - RETERR(DST_R_UNSUPPORTEDALG); - newfilenamelen = strlen(filename) + 9; if (dirname != NULL) newfilenamelen += strlen(dirname) + 1; @@ -1611,17 +1643,32 @@ find_timingdata(const char *s) { return (find_metadata(s, timingtags, TIMING_NTAGS)); } +static int +find_keystatedata(const char *s) { + return (find_metadata(s, keystatestags, KEYSTATES_NTAGS)); +} + +static isc_result_t +keystate_fromtext(const char *s, dst_key_state_t *state) { + for (int i = 0; i < KEYSTATES_NVALUES; i++) { + if (keystates[i] != NULL && strcasecmp(s, keystates[i]) == 0) { + *state = (dst_key_state_t) i; + return (ISC_R_SUCCESS); + } + } + return (ISC_R_NOTFOUND); +} /*% * Reads a key state from disk. */ isc_result_t -dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) +dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t **keyp) { isc_lex_t *lex = NULL; isc_token_t token; isc_result_t ret; - unsigned int opt = ISC_LEXOPT_DNSMULTILINE; + unsigned int opt = ISC_LEXOPT_EOL; ret = isc_lex_create(mctx, 1500, &lex); if (ret != ISC_R_SUCCESS) { @@ -1634,6 +1681,11 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) goto cleanup; } + /* + * Read the comment line. + */ + READLINE(lex, opt, &token); + /* * Read the algorithm line. */ @@ -1646,11 +1698,13 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); if (token.type != isc_tokentype_number || - token.value.as_ulong != (unsigned long) dst_key_alg(key)) + token.value.as_ulong != (unsigned long) dst_key_alg(*keyp)) { BADTOKEN(); } + READLINE(lex, opt, &token); + /* * Read the length line. */ @@ -1663,11 +1717,13 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); if (token.type != isc_tokentype_number || - token.value.as_ulong != (unsigned long) dst_key_size(key)) + token.value.as_ulong != (unsigned long) dst_key_size(*keyp)) { BADTOKEN(); } + READLINE(lex, opt, &token); + /* * Read the metadata. */ @@ -1675,6 +1731,9 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) int tag; NEXTTOKEN_OR_EOF(lex, opt, &token); + if (ret == ISC_R_EOF) { + break; + } if (token.type != isc_tokentype_string) { BADTOKEN(); } @@ -1682,12 +1741,14 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) /* Numeric metadata */ tag = find_numericdata(DST_AS_STR(token)); if (tag >= 0) { + INSIST(tag < NUMERIC_NTAGS); + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); if (token.type != isc_tokentype_number) { BADTOKEN(); } - dst_key_setnum(key, tag, token.value.as_ulong); + dst_key_setnum(*keyp, tag, token.value.as_ulong); goto next; } @@ -1700,14 +1761,14 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) if (token.type != isc_tokentype_string) { BADTOKEN(); } + if (strcmp(DST_AS_STR(token), "yes") == 0) { - dst_key_setbool(key, tag, true); + dst_key_setbool(*keyp, tag, true); } else if (strcmp(DST_AS_STR(token), "no") == 0) { - dst_key_setbool(key, tag, false); + dst_key_setbool(*keyp, tag, false); } else { BADTOKEN(); } - goto next; } @@ -1727,13 +1788,35 @@ dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *key) if (ret != ISC_R_SUCCESS) { goto cleanup; } - dst_key_settime(key, tag, when); + dst_key_settime(*keyp, tag, when); + goto next; + } + + /* Keystate metadata */ + tag = find_keystatedata(DST_AS_STR(token)); + if (tag >= 0) { + dst_key_state_t state; + + INSIST(tag < KEYSTATES_NTAGS); + + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + ret = keystate_fromtext(DST_AS_STR(token), &state); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + dst_key_setstate(*keyp, tag, state); goto next; } next: READLINE(lex, opt, &token); + } /* Done, successfully parsed the whole file. */ @@ -1847,6 +1930,21 @@ printtime(const dst_key_t *key, int type, const char *tag, FILE *stream) { fprintf(stream, "%s: (set, unable to display)\n", tag); } +/*% + * Write key state metadata to a file pointer, preceded by 'tag' + */ +static void +printstate(const dst_key_t *key, int type, const char *tag, FILE *stream) { + isc_result_t result; + dst_key_state_t value = 0; + + result = dst_key_getstate(key, type, &value); + if (result != ISC_R_SUCCESS) { + return; + } + fprintf(stream, "%s: %s\n", tag, keystates[value]); +} + /*% * Writes a key state to disk. */ @@ -1898,6 +1996,11 @@ write_key_state(const dst_key_t *key, int type, const char *directory) { fprintf(fp, "Algorithm: %u\n", key->key_alg); fprintf(fp, "Length: %u\n", key->key_size); + printnum(key, DST_NUM_LIFETIME, "Lifetime", fp); + + printbool(key, DST_BOOL_KSK, "KSK", fp); + printbool(key, DST_BOOL_ZSK, "ZSK", fp); + printtime(key, DST_TIME_CREATED, "Generated", fp); printtime(key, DST_TIME_PUBLISH, "Published", fp); printtime(key, DST_TIME_ACTIVATE, "Active", fp); @@ -1905,10 +2008,16 @@ write_key_state(const dst_key_t *key, int type, const char *directory) { printtime(key, DST_TIME_REVOKE, "Revoked", fp); printtime(key, DST_TIME_DELETE, "Removed", fp); - printnum(key, DST_NUM_LIFETIME, "Lifetime", fp); + printtime(key, DST_TIME_DNSKEY, "DNSKEYChange", fp); + printtime(key, DST_TIME_ZRRSIG, "ZRRSIGChange", fp); + printtime(key, DST_TIME_KRRSIG, "KRRSIGChange", fp); + printtime(key, DST_TIME_DS, "DSChange", fp); - printbool(key, DST_BOOL_KSK, "KSK", fp); - printbool(key, DST_BOOL_ZSK, "ZSK", fp); + printstate(key, DST_KEY_DNSKEY, "DNSKEYState", fp); + printstate(key, DST_KEY_ZRRSIG, "ZRRSIGState", fp); + printstate(key, DST_KEY_KRRSIG, "KRRSIGState", fp); + printstate(key, DST_KEY_DS, "DSState", fp); + printstate(key, DST_KEY_GOAL, "GoalState", fp); } fflush(fp); diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h index 463954fa53..0d8be243d8 100644 --- a/lib/dns/dst_internal.h +++ b/lib/dns/dst_internal.h @@ -108,11 +108,16 @@ struct dst_key { isc_stdtime_t times[DST_MAX_TIMES + 1]; /*%< timing metadata */ bool timeset[DST_MAX_TIMES + 1]; /*%< data set? */ + uint32_t nums[DST_MAX_NUMERIC + 1]; /*%< numeric metadata */ bool numset[DST_MAX_NUMERIC + 1]; /*%< data set? */ + bool bools[DST_MAX_BOOLEAN + 1]; /*%< boolean metadata */ bool boolset[DST_MAX_BOOLEAN + 1]; /*%< data set? */ + dst_key_state_t keystates[DST_MAX_KEYSTATES + 1]; /*%< key states */ + bool keystateset[DST_MAX_KEYSTATES + 1]; /*%< data set? */ + bool inactive; /*%< private key not present as it is inactive */ bool external; /*%< external key */ diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c index 7130c29501..e989be5301 100644 --- a/lib/dns/dst_parse.c +++ b/lib/dns/dst_parse.c @@ -61,7 +61,11 @@ static const char *timetags[TIMING_NTAGS] = { "Delete:", "DSPublish:", "SyncPublish:", - "SyncDelete:" + "SyncDelete:", + NULL, + NULL, + NULL, + NULL }; #define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1) @@ -69,7 +73,8 @@ static const char *numerictags[NUMERIC_NTAGS] = { "Predecessor:", "Successor:", "MaxTTL:", - "RollPeriod:" + "RollPeriod:", + NULL }; struct parse_map { @@ -754,7 +759,7 @@ dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv, if (timetags[i] != NULL) { fprintf(fp, "%s %.*s\n", timetags[i], - (int)r.length, r.base); + (int)r.length, r.base); } } } diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index e92de43417..7f8517c4a9 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -44,6 +44,38 @@ ISC_LANG_BEGINDECLS typedef struct dst_key dst_key_t; typedef struct dst_context dst_context_t; +/*% + * Key states for the DNSSEC records related to a key: DNSKEY, RRSIG (ksk), + * RRSIG (zsk), and DS. + * + * DST_KEY_STATE_HIDDEN: Records of this type are not published in zone. + * This may be because the key parts were never + * introduced in the zone, or because the key has + * retired and has no records of this type left in + * the zone. + * DST_KEY_STATE_RUMOURED: Records of this type are published in zone, but + * not long enough to ensure all resolvers know + * about it. + * DST_KEY_STATE_OMNIPRESENT: Records of this type are published in zone long + * enough so that all resolvers that know about + * these records, no longer have outdated data. + * DST_KEY_STATE_UNRETENTIVE: Records of this type have been removed from the + * zone, but there may be resolvers that still have + * have predecessor records cached. Note that RRSIG + * records in this state may actually still be in the + * zone because they are reused, but retired RRSIG + * records will never be refreshed: A successor key + * is used to create signatures. + * DST_KEY_STATE_NA: The state is not applicable for this record type. + */ +typedef enum dst_key_state { + DST_KEY_STATE_HIDDEN = 0, + DST_KEY_STATE_RUMOURED = 1, + DST_KEY_STATE_OMNIPRESENT = 2, + DST_KEY_STATE_UNRETENTIVE = 3, + DST_KEY_STATE_NA = 4 +} dst_key_state_t; + /* DST algorithm codes */ #define DST_ALG_UNKNOWN 0 #define DST_ALG_RSA 1 /* Used for parsing RSASHA1, RSASHA256 and RSASHA512 */ @@ -97,7 +129,11 @@ typedef struct dst_context dst_context_t; #define DST_TIME_DSPUBLISH 6 #define DST_TIME_SYNCPUBLISH 7 #define DST_TIME_SYNCDELETE 8 -#define DST_MAX_TIMES 8 +#define DST_TIME_DNSKEY 9 +#define DST_TIME_ZRRSIG 10 +#define DST_TIME_KRRSIG 11 +#define DST_TIME_DS 12 +#define DST_MAX_TIMES 12 /* Numeric metadata definitions */ #define DST_NUM_PREDECESSOR 0 @@ -112,6 +148,14 @@ typedef struct dst_context dst_context_t; #define DST_BOOL_ZSK 1 #define DST_MAX_BOOLEAN 1 +/* Key state metadata definitions */ +#define DST_KEY_DNSKEY 0 +#define DST_KEY_ZRRSIG 1 +#define DST_KEY_KRRSIG 2 +#define DST_KEY_DS 3 +#define DST_KEY_GOAL 4 +#define DST_MAX_KEYSTATES 4 + /* * Current format version number of the private key parser. * @@ -391,7 +435,7 @@ dst_key_read_public(const char *filename, int type, */ isc_result_t -dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t *keyp); +dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t **keyp); /*%< * Reads a key state from permanent storage. * @@ -929,6 +973,38 @@ dst_key_unsettime(dst_key_t *key, int type); * "type" is no larger than DST_MAX_TIMES */ +isc_result_t +dst_key_getstate(const dst_key_t *key, int type, dst_key_state_t *statep); +/*%< + * Get a member of the keystate metadata array and place it in '*statep'. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_KEYSTATES + * "statep" is not null. + */ + +void +dst_key_setstate(dst_key_t *key, int type, dst_key_state_t state); +/*%< + * Set a member of the keystate metadata array. + * + * Requires: + * "key" is a valid key. + * "state" is a valid state. + * "type" is no larger than DST_MAX_KEYSTATES + */ + +void +dst_key_unsetstate(dst_key_t *key, int type); +/*%< + * Flag a member of the keystate metadata array as "not set". + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_KEYSTATES + */ + isc_result_t dst_key_getprivateformat(const dst_key_t *key, int *majorp, int *minorp); /*%< diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index a5c7949118..153efc2e7b 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -1411,6 +1411,7 @@ dst_key_getfilename dst_key_getgssctx dst_key_getnum dst_key_getprivateformat +dst_key_getstate dst_key_gettime dst_key_getttl dst_key_id @@ -1436,6 +1437,7 @@ dst_key_setflags dst_key_setinactive dst_key_setnum dst_key_setprivateformat +dst_key_setstate dst_key_settime dst_key_setttl dst_key_sigsize @@ -1446,6 +1448,7 @@ dst_key_todns dst_key_tofile dst_key_unsetbool dst_key_unsetnum +dst_key_unsetstate dst_key_unsettime dst_lib_destroy dst_lib_init From 53e76f888b9985abcafca9f87b32b4b5754367a0 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 1 Oct 2019 11:11:47 +0200 Subject: [PATCH 19/43] Allow DNSSEC records in kasp enabled zone When signing a zone with dnssec-policy, we don't mind DNSSEC records. This is useful for testing purposes, and perhaps it is better to signal this behavior with a different configuration option. --- lib/dns/zone.c | 60 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 720df2807a..9d1486413c 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -6907,13 +6907,16 @@ next_active(dns_db_t *db, dns_dbversion_t *version, dns_name_t *oldname, } static bool -signed_with_key(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, - dns_rdatatype_t type, dst_key_t *key) +signed_with_good_key(dns_zone_t* zone, dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, dns_rdatatype_t type, + dst_key_t *key) { isc_result_t result; dns_rdataset_t rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_rrsig_t rrsig; + int count = 0; + dns_kasp_t *kasp = dns_zone_getkasp(zone); dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, version, dns_rdatatype_rrsig, @@ -6933,8 +6936,49 @@ signed_with_key(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, dns_rdataset_disassociate(&rdataset); return (true); } + if (rrsig.algorithm == dst_key_alg(key)) { + count++; + } dns_rdata_reset(&rdata); } + + if (kasp) { + dns_kasp_key_t* kkey; + int ksk_count = 0, zsk_count = 0; + bool approved = false; + + for (kkey = ISC_LIST_HEAD(kasp->keys); kkey != NULL; + kkey = ISC_LIST_NEXT(kkey, link)) + { + if (dns_kasp_key_algorithm(kkey) != dst_key_alg(key)) { + continue; + } + if (dns_kasp_key_ksk(kkey)) { + ksk_count++; + } + if (dns_kasp_key_zsk(kkey)) { + zsk_count++; + } + } + + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || type == dns_rdatatype_cds) + { + /* + * CDS and CDNSKEY are signed with KSK like DNSKEY. + * (RFC 7344, section 4.1 specifies that they must + * be signed with a key in the current DS RRset, + * which would only include KSK's.) + */ + approved = (ksk_count == count); + } else { + approved = (zsk_count == count); + } + + dns_rdataset_disassociate(&rdataset); + return (approved); + } + dns_rdataset_disassociate(&rdataset); return (false); } @@ -7117,7 +7161,7 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, { goto next_rdataset; } - if (signed_with_key(db, node, version, rdataset.type, key)) { + if (signed_with_good_key(zone, db, node, version, rdataset.type, key)) { goto next_rdataset; } /* Calculate the signature, creating a RRSIG RDATA. */ @@ -15346,8 +15390,14 @@ receive_secure_db(isc_task_t *task, isc_event_t *event) { rdataset.type == dns_rdatatype_nsec3 || rdataset.type == dns_rdatatype_dnskey || rdataset.type == dns_rdatatype_nsec3param) { - dns_rdataset_disassociate(&rdataset); - continue; + /* + * Allow DNSSEC records with dnssec-policy. + * WMM: Perhaps add config option for it. + */ + if (dns_zone_getkasp(zone) == NULL) { + dns_rdataset_disassociate(&rdataset); + continue; + } } if (rdataset.type == dns_rdatatype_soa && have_oldserial) { From da0ae5299f51c0ffdd4edc3b880d105a64590cf6 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 1 Oct 2019 15:40:01 +0200 Subject: [PATCH 20/43] arm: Update DNSSEC documentation --- doc/arm/dnssec.xml | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/doc/arm/dnssec.xml b/doc/arm/dnssec.xml index 6210a12a7f..be702849c3 100644 --- a/doc/arm/dnssec.xml +++ b/doc/arm/dnssec.xml @@ -16,9 +16,10 @@ - Changing a zone from insecure to secure can be done in two - ways: using a dynamic DNS update, or the - auto-dnssec zone option. + Changing a zone from insecure to secure can be done in three + ways: using a dynamic DNS update, use the + auto-dnssec zone option, or set a DNSSEC + policy for the zone with dnssec-policy. For either method, you need to configure @@ -26,8 +27,9 @@ K* files which contain the public and private parts of the keys that will be used to sign the zone. These files will have been generated by - dnssec-keygen. You can do this by placing them - in the key-directory, as specified in + dnssec-keygen (or created when needed by + named if dnssec-policy is + used). Keys should be placed in the key-directory, as specified in named.conf: zone example.net { @@ -44,6 +46,18 @@ well. An NSEC chain will be generated as part of the initial signing process. + + With dnssec-policy you specify what keys should + be KSK and/or ZSK. If you want a key to sign all records with a key + you will need to specify a CSK: + + + dnssec-policy csk { + keys { + csk key-directory P5Y 13; + }; + }; +
Dynamic DNS update method @@ -95,7 +109,8 @@
- To enable automatic signing, add the + To enable automatic signing, you can set a + dnssec-policy, or add the auto-dnssec option to the zone statement in named.conf. auto-dnssec has two possible arguments: @@ -117,6 +132,13 @@ (See and for more information.) + + dnssec-policy is like + auto-dnssec maintain, but will also automatically + create new keys when necessary. Also any configuration related + to DNSSEC signing is retrieved from the policy (ignoring existing + DNSSEC named.conf options). + named will periodically search the key directory for keys matching the zone, and if the keys' metadata indicates @@ -288,6 +310,9 @@ chain will be generated before the NSEC chain is destroyed. + + NSEC3 is not supported yet with dnssec-policy. +
Converting from NSEC3 to NSEC From dcf79ce61f83c0f021209cd718e062dfff2be090 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 16 Oct 2019 17:43:30 +0200 Subject: [PATCH 21/43] keygen/settime: Write out successor/predecessor When creating a successor key, or calculating time for a successor key, write out the successor and predecessor metadata to the related files. --- bin/dnssec/dnssec-keygen.c | 13 +++++++++++++ bin/dnssec/dnssec-settime.c | 8 ++++++++ lib/dns/dst_api.c | 2 ++ 3 files changed, 23 insertions(+) diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index 09b7c09508..f0600232ef 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -767,6 +767,19 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) fatal("cannot generate a null key due to possible key ID " "collision"); + if (ctx->predecessor != NULL && prevkey != NULL) { + dst_key_setnum(prevkey, DST_NUM_SUCCESSOR, dst_key_id(key)); + dst_key_setnum(key, DST_NUM_PREDECESSOR, dst_key_id(prevkey)); + + ret = dst_key_tofile(prevkey, ctx->options, ctx->directory); + if (ret != ISC_R_SUCCESS) { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(prevkey, keystr, sizeof(keystr)); + fatal("failed to update predecessor %s: %s\n", keystr, + isc_result_totext(ret)); + } + } + ret = dst_key_tofile(key, ctx->options, ctx->directory); if (ret != ISC_R_SUCCESS) { char keystr[DST_KEY_FORMATSIZE]; diff --git a/bin/dnssec/dnssec-settime.c b/bin/dnssec/dnssec-settime.c index cc72e55662..1cc12e5190 100644 --- a/bin/dnssec/dnssec-settime.c +++ b/bin/dnssec/dnssec-settime.c @@ -715,6 +715,11 @@ main(int argc, char **argv) { if (setttl) dst_key_setttl(key, ttl); + if (predecessor != NULL && prevkey != NULL) { + dst_key_setnum(prevkey, DST_NUM_SUCCESSOR, dst_key_id(key)); + dst_key_setnum(key, DST_NUM_PREDECESSOR, dst_key_id(prevkey)); + } + /* * No metadata changes were made but we're forcing an upgrade * to the new format anyway: use "-P now -A now" as the default @@ -816,6 +821,9 @@ main(int argc, char **argv) { if (changed) { writekey(key, directory, write_state); + if (predecessor != NULL && prevkey != NULL) { + writekey(prevkey, directory, write_state); + } } if (prevkey != NULL) diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index 978f8839c4..54dcd06d7d 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -1997,6 +1997,8 @@ write_key_state(const dst_key_t *key, int type, const char *directory) { fprintf(fp, "Length: %u\n", key->key_size); printnum(key, DST_NUM_LIFETIME, "Lifetime", fp); + printnum(key, DST_NUM_PREDECESSOR, "Predecessor", fp); + printnum(key, DST_NUM_SUCCESSOR, "Successor", fp); printbool(key, DST_BOOL_KSK, "KSK", fp); printbool(key, DST_BOOL_ZSK, "ZSK", fp); From 1f0d6296a1501e372a0abb1f1f0c76b7815d361f Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 16 Oct 2019 18:36:38 +0200 Subject: [PATCH 22/43] kasp: Expose more key timings When doing rollover in a timely manner we need to have access to the relevant kasp configured durations. Most of these are simple get functions, but 'dns_kasp_signdelay' will calculate the maximum time that is needed with this policy to resign the complete zone (taking into account the refresh interval and signature validity). Introduce parent-propagation-delay, parent-registration-delay, parent-ds-ttl, zone-max-ttl, zone-propagation-delay. --- bin/named/named.conf.docbook | 5 + doc/arm/Bv9ARM-book.xml | 77 +++++++++++++++ doc/arm/dnssec-policy.grammar.xml | 5 + doc/misc/options | 5 + lib/dns/include/dns/kasp.h | 159 ++++++++++++++++++++++++++++++ lib/dns/kasp.c | 84 ++++++++++++++++ lib/dns/win32/libdns.def.in | 13 ++- lib/isccfg/kaspconf.c | 19 ++++ lib/isccfg/namedconf.c | 5 + 9 files changed, 371 insertions(+), 1 deletion(-) diff --git a/bin/named/named.conf.docbook b/bin/named/named.conf.docbook index 8221d4cce5..f9696fa3fa 100644 --- a/bin/named/named.conf.docbook +++ b/bin/named/named.conf.docbook @@ -1016,11 +1016,16 @@ zone string [ class ] { dnssec-policy string { dnskey-ttl ttlval; keys { ( csk | ksk | zsk ) key-directory duration integer [ integer ] ; ... }; + parent-ds-ttl duration; + parent-propagation-delay duration; + parent-registration-delay duration; publish-safety duration; retire-safety duration; signatures-refresh duration; signatures-validity duration; signatures-validity-dnskey duration; + zone-max-ttl duration; + zone-propagation-delay duration; }; diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 47b08440ef..d0c21560d9 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -11151,6 +11151,83 @@ example.com CNAME rpz-tcp-only. + + zone-max-ttl + + + Like max-zone-ttl, specifies the maximum + permissible TTL value in seconds. When loading a zone file + using a or + text or raw, + any record encountered with a TTL higher than + will be capped to the maximum + permissible TTL value. + + + This is needed in DNSSEC-maintained zones because when + rolling to a new DNSKEY, the old key needs to remain + available until RRSIG records have expired from + caches. The option guarantees + that the largest TTL in the zone will be no higher than the + set value. + + + (NOTE: Because map-format files + load directly into memory, this option cannot be + used with them.) + + + The default value is PT24H (24 hours). + A of zero is treated as if + the default value is in use. + + + + + + zone-propagation-delay + + + The expected propagation delay from when a zone is updated + and when the new version of the zone is served by all its + name servers. Default is PT5M (5 minutes). + + + + + + parent-ds-ttl + + + The TTL of the DS RRset that the parent uses. Default is + PT1H (1 hour). + + + + + + parent-propagation-delay + + + The expected propagation delay from when the parent zone is + updated and when the new version of the parent zone is served + by all its name servers. Default is + PT1H (1 hour). + + + + + + parent-registration-delay + + + The expected registration delay from when a DS RRset change + is requested and when the DS RRset has been updated in the + parent zone. Default is P1D (1 day). + + + +
diff --git a/doc/arm/dnssec-policy.grammar.xml b/doc/arm/dnssec-policy.grammar.xml index 68e27d964c..c7df40c4d3 100644 --- a/doc/arm/dnssec-policy.grammar.xml +++ b/doc/arm/dnssec-policy.grammar.xml @@ -15,11 +15,16 @@ dnssec-policy string { dnskey-ttl ttlval; keys { ( csk | ksk | zsk ) key-directory duration integer [ integer ] ; ... }; + parent-ds-ttl duration; + parent-propagation-delay duration; + parent-registration-delay duration; publish-safety duration; retire-safety duration; signatures-refresh duration; signatures-validity duration; signatures-validity-dnskey duration; + zone-max-ttl duration; + zone-propagation-delay duration; }; diff --git a/doc/misc/options b/doc/misc/options index 6f5674692c..cb00923715 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -29,11 +29,16 @@ dnssec-policy { dnskey-ttl ; keys { ( csk | ksk | zsk ) key-directory [ ]; ... }; + parent-ds-ttl ; + parent-propagation-delay ; + parent-registration-delay ; publish-safety ; retire-safety ; signatures-refresh ; signatures-validity ; signatures-validity-dnskey ; + zone-max-ttl ; + zone-propagation-delay ; }; // may occur multiple times dyndb { diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h index 6c953a0636..c6b156119a 100644 --- a/lib/dns/include/dns/kasp.h +++ b/lib/dns/include/dns/kasp.h @@ -81,6 +81,15 @@ struct dns_kasp { uint32_t publish_safety; uint32_t retire_safety; + /* Zone settings */ + dns_ttl_t zone_max_ttl; + time_t zone_propagation_delay; + + /* Parent settings */ + dns_ttl_t parent_ds_ttl; + time_t parent_propagation_delay; + time_t parent_registration_delay; + // TODO: The rest of the KASP configuration }; @@ -92,8 +101,13 @@ struct dns_kasp { #define DNS_KASP_SIG_VALIDITY (86400*14) #define DNS_KASP_SIG_VALIDITY_DNSKEY (86400*14) #define DNS_KASP_KEY_TTL (3600) +#define DNS_KASP_DS_TTL (3600) #define DNS_KASP_PUBLISH_SAFETY (300) #define DNS_KASP_RETIRE_SAFETY (300) +#define DNS_KASP_ZONE_MAXTTL (86400) +#define DNS_KASP_ZONE_PROPDELAY (300) +#define DNS_KASP_PARENT_PROPDELAY (3600) +#define DNS_KASP_PARENT_REGDELAY (86400) /* Key roles */ #define DNS_KASP_KEY_ROLE_KSK 0x01 @@ -194,6 +208,53 @@ dns_kasp_getname(dns_kasp_t *kasp); *\li name of 'kasp'. */ +time_t +dns_kasp_signdelay(dns_kasp_t *kasp); +/*%< + * Get the delay that is needed to ensure that all existing RRsets have been + * re-signed with a successor key. This is the signature validity minus the + * signature refresh time (that indicates how far before signature expiry an + * RRSIG should be refreshed). + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li signature refresh interval. + */ + +time_t +dns_kasp_sigrefresh(dns_kasp_t *kasp); +/*%< + * Get signature refresh interval. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li signature refresh interval. + */ + +time_t +dns_kasp_sigvalidity(dns_kasp_t *kasp); +time_t +dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp); +/*%< + * Get signature validity. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li signature validity. + */ + dns_ttl_t dns_kasp_dnskeyttl(dns_kasp_t *kasp); /*%< @@ -208,6 +269,104 @@ dns_kasp_dnskeyttl(dns_kasp_t *kasp); *\li DNSKEY TTL. */ +time_t +dns_kasp_publishsafety(dns_kasp_t *kasp); +/*%< + * Get publish safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Publish safety interval. + */ + +time_t +dns_kasp_retiresafety(dns_kasp_t *kasp); +/*%< + * Get retire safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Retire safety interval. + */ + +dns_ttl_t +dns_kasp_zonemaxttl(dns_kasp_t *kasp); +/*%< + * Get maximum zone TTL. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Maximum zone TTL. + */ + +time_t +dns_kasp_zonepropagationdelay(dns_kasp_t *kasp); +/*%< + * Get zone propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Zone propagation delay. + */ + +dns_ttl_t +dns_kasp_dsttl(dns_kasp_t *kasp); +/*%< + * Get DS TTL (should match that of the parent DS record). + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Expected parent DS TTL. + */ + +time_t +dns_kasp_parentpropagationdelay(dns_kasp_t *kasp); +/*%< + * Get parent zone propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Parent zone propagation delay. + */ + +time_t +dns_kasp_parentregistrationdelay(dns_kasp_t *kasp); +/*%< + * Get parent registration delay for submitting new DS. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Parent registration delay. + */ + isc_result_t dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp); /*%< diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c index f585129b32..6ec8236785 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -53,6 +53,13 @@ dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp) kasp->publish_safety = DNS_KASP_PUBLISH_SAFETY; kasp->retire_safety = DNS_KASP_RETIRE_SAFETY; + kasp->zone_max_ttl = DNS_KASP_ZONE_MAXTTL; + kasp->zone_propagation_delay = DNS_KASP_ZONE_PROPDELAY; + + kasp->parent_ds_ttl = DNS_KASP_DS_TTL; + kasp->parent_propagation_delay = DNS_KASP_PARENT_PROPDELAY; + kasp->parent_registration_delay = DNS_KASP_PARENT_REGDELAY; + // TODO: The rest of the KASP configuration kasp->magic = DNS_KASP_MAGIC; @@ -117,6 +124,34 @@ dns_kasp_thaw(dns_kasp_t *kasp) { kasp->frozen = false; } +time_t +dns_kasp_signdelay(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->signatures_validity - kasp->signatures_refresh); +} + +time_t +dns_kasp_sigrefresh(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return kasp->signatures_refresh; +} + +time_t +dns_kasp_sigvalidity(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return kasp->signatures_validity; +} + +time_t +dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return kasp->signatures_validity_dnskey; +} + dns_ttl_t dns_kasp_dnskeyttl(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -124,6 +159,55 @@ dns_kasp_dnskeyttl(dns_kasp_t *kasp) { return kasp->dnskey_ttl; } +time_t +dns_kasp_publishsafety(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return kasp->publish_safety; +} + +time_t +dns_kasp_retiresafety(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return kasp->retire_safety; +} + +dns_ttl_t +dns_kasp_zonemaxttl(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return kasp->zone_max_ttl; +} + +time_t +dns_kasp_zonepropagationdelay(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return kasp->zone_propagation_delay; +} + +dns_ttl_t +dns_kasp_dsttl(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return kasp->parent_ds_ttl; +} + +time_t +dns_kasp_parentpropagationdelay(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return kasp->parent_propagation_delay; +} + +time_t +dns_kasp_parentregistrationdelay(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return kasp->parent_registration_delay; +} + isc_result_t dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp) { diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 153efc2e7b..de79d7bfb1 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -413,10 +413,11 @@ dns_journal_rollforward dns_journal_set_sourceserial dns_journal_write_transaction dns_journal_writediff -dns_kasp_create dns_kasp_attach +dns_kasp_create dns_kasp_detach dns_kasp_dnskeyttl +dns_kasp_dsttl dns_kasp_freeze dns_kasp_getname dns_kasp_key_algorithm @@ -426,7 +427,17 @@ dns_kasp_key_ksk dns_kasp_key_lifetime dns_kasp_key_size dns_kasp_key_zsk +dns_kasp_parentpropagationdelay +dns_kasp_parentregistrationdelay +dns_kasp_publishsafety +dns_kasp_retiresafety +dns_kasp_signdelay +dns_kasp_sigrefresh +dns_kasp_sigvalidity +dns_kasp_sigvalidity_dnskey dns_kasp_thaw +dns_kasp_zonemaxttl +dns_kasp_zonepropagationdelay dns_kasplist_find dns_keydata_fromdnskey dns_keydata_todnskey diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index eafb4c3b35..37eb1e3c69 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -192,6 +192,25 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, isc_mem_t* mctx, } ISC_INSIST(!(ISC_LIST_EMPTY(kasp->keys))); + /* Configuration: Zone settings */ + kasp->zone_max_ttl = get_duration(maps, "zone-max-ttl", + DNS_KASP_ZONE_MAXTTL); + kasp->zone_propagation_delay = get_duration(maps, + "zone-propagation-delay", + DNS_KASP_ZONE_PROPDELAY); + + /* Configuration: Parent settings */ + kasp->parent_ds_ttl = get_duration(maps, "parent-ds-ttl", + DNS_KASP_DS_TTL); + kasp->parent_propagation_delay = get_duration( + maps, + "parent-propagation-delay", + DNS_KASP_PARENT_PROPDELAY); + kasp->parent_registration_delay = get_duration( + maps, + "parent-registration-delay", + DNS_KASP_PARENT_REGDELAY); + // TODO: Rest of the configuration /* Success: Attach the kasp to the pointer and return. */ diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 100cb0979d..7d0dd467db 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2068,6 +2068,11 @@ dnssecpolicy_clauses[] = { { "signatures-refresh", &cfg_type_duration, 0 }, { "signatures-validity", &cfg_type_duration, 0 }, { "signatures-validity-dnskey", &cfg_type_duration, 0 }, + { "zone-max-ttl", &cfg_type_duration, 0 }, + { "zone-propagation-delay", &cfg_type_duration, 0 }, + { "parent-ds-ttl", &cfg_type_duration, 0 }, + { "parent-propagation-delay", &cfg_type_duration, 0 }, + { "parent-registration-delay", &cfg_type_duration, 0 }, { NULL, NULL, 0 } }; From 314b90dfddec4a507a28772e7e43566b89fc5c5b Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 17 Oct 2019 10:21:12 +0200 Subject: [PATCH 23/43] Useful dst_key functions Add a couple of dst_key functions for determining hints that consider key states if they are available. - dst_key_is_unused: A key has no timing metadata set other than Created. - dst_key_is_published: A key has publish timing metadata <= now, DNSKEY state in RUMOURED or OMNIPRESENT. - dst_key_is_active: A key has active timing metadata <= now, RRSIG state in RUMOURED or OMNIPRESENT. - dst_key_is_signing: KSK is_signing and is_active means different things than for a ZSK. A ZSK is active means it is also signing, but a KSK always signs its DNSKEY RRset but is considered active if its DS is present (rumoured or omnipresent). - dst_key_is_revoked: A key has revoke timing metadata <= now. - dst_key_is_removed: A key has delete timing metadata <= now, DNSKEY state in UNRETENTIVE or HIDDEN. --- lib/dns/dst_api.c | 305 ++++++++++++++++++++++++++++++++++++ lib/dns/include/dst/dst.h | 68 ++++++++ lib/dns/win32/libdns.def.in | 6 + 3 files changed, 379 insertions(+) diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index 54dcd06d7d..d8d8869e4a 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -2283,3 +2283,308 @@ dst_key_tkeytoken(const dst_key_t *key) { REQUIRE(VALID_KEY(key)); return (key->key_tkeytoken); } + +/* + * A key is considered unused if it does not have any timing metadata set + * other than "Created". + * + */ +bool +dst_key_is_unused(dst_key_t* key) +{ + isc_stdtime_t val; + dst_key_state_t st; + int state_type; + bool state_type_set; + + REQUIRE(VALID_KEY(key)); + + /* + * None of the key timing metadata, except Created, may be set. Key + * state times may be set only if their respective state is HIDDEN. + */ + for (int i = 0; i < DST_MAX_TIMES+1; i++) { + state_type_set = false; + + switch (i) { + case DST_TIME_CREATED: + break; + case DST_TIME_DNSKEY: + state_type = DST_KEY_DNSKEY; + state_type_set = true; + break; + case DST_TIME_ZRRSIG: + state_type = DST_KEY_ZRRSIG; + state_type_set = true; + break; + case DST_TIME_KRRSIG: + state_type = DST_KEY_KRRSIG; + state_type_set = true; + break; + case DST_TIME_DS: + state_type = DST_KEY_DS; + state_type_set = true; + break; + default: + break; + } + + /* Created is fine. */ + if (i == DST_TIME_CREATED) { + continue; + } + /* No such timing metadata found, that is fine too. */ + if (dst_key_gettime(key, i, &val) == ISC_R_NOTFOUND) { + continue; + } + /* + * Found timing metadata and it is not related to key states. + * This key is used. + */ + if (!state_type_set) { + return false; + } + /* + * If the state is not HIDDEN, the key is in use. + * If the state is not set, this is odd and we default to NA. + */ + if (dst_key_getstate(key, state_type, &st) != ISC_R_SUCCESS) { + st = DST_KEY_STATE_NA; + } + if (st != DST_KEY_STATE_HIDDEN) { + return false; + } + } + /* This key is unused. */ + return true; +} + + +static void +get_ksk_zsk(dst_key_t *key, bool *ksk, bool *zsk) +{ + bool k = false, z = false; + + if (dst_key_getbool(key, DST_BOOL_KSK, &k) == ISC_R_SUCCESS) { + *ksk = k; + } else { + *ksk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) != 0); + } + if (dst_key_getbool(key, DST_BOOL_ZSK, &z) == ISC_R_SUCCESS) { + *zsk = z; + } else { + *zsk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) == 0); + } +} + +/* Hints on key whether it can be published and/or used for signing. */ + +bool +dst_key_is_published(dst_key_t *key, isc_stdtime_t now, + isc_stdtime_t *publish) +{ + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when; + bool state_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_PUBLISH, &when); + if (result == ISC_R_SUCCESS) { + *publish = when; + time_ok = (when <= now); + } + + /* Check key states: + * If the DNSKEY state is RUMOURED or OMNIPRESENT, it means it + * should be published. + */ + result = dst_key_getstate(key, DST_KEY_DNSKEY, &state); + if (result == ISC_R_SUCCESS) { + state_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + } + + return state_ok && time_ok; +} + +bool +dst_key_is_active(dst_key_t *key, isc_stdtime_t now) +{ + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when = 0; + bool ksk = false, zsk = false, inactive = false; + bool ds_ok = true, zrrsig_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_INACTIVE, &when); + if (result == ISC_R_SUCCESS) { + inactive = (when <= now); + } + + result = dst_key_gettime(key, DST_TIME_ACTIVATE, &when); + if (result == ISC_R_SUCCESS) { + time_ok = (when <= now); + } + + get_ksk_zsk(key, &ksk, &zsk); + + /* Check key states: + * KSK: If the DS is RUMOURED or OMNIPRESENT the key is considered + * active. + */ + if (ksk) { + result = dst_key_getstate(key, DST_KEY_DS, &state); + if (result == ISC_R_SUCCESS) { + ds_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } + /* + * ZSK: If the ZRRSIG state is RUMOURED or OMNIPRESENT, it means the + * key is active. + */ + if (zsk) { + result = dst_key_getstate(key, DST_KEY_ZRRSIG, &state); + if (result == ISC_R_SUCCESS) { + zrrsig_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } + return ds_ok && zrrsig_ok && time_ok && !inactive; +} + + +bool +dst_key_is_signing(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *active) +{ + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when = 0; + bool ksk = false, zsk = false, inactive = false; + bool krrsig_ok = true, zrrsig_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_INACTIVE, &when); + if (result == ISC_R_SUCCESS) { + inactive = (when <= now); + } + + result = dst_key_gettime(key, DST_TIME_ACTIVATE, &when); + if (result == ISC_R_SUCCESS) { + *active = when; + time_ok = (when <= now); + } + + get_ksk_zsk(key, &ksk, &zsk); + + /* Check key states: + * If the RRSIG state is RUMOURED or OMNIPRESENT, it means the key + * is active. + */ + if (ksk) { + result = dst_key_getstate(key, DST_KEY_KRRSIG, &state); + if (result == ISC_R_SUCCESS) { + krrsig_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } + if (zsk) { + result = dst_key_getstate(key, DST_KEY_ZRRSIG, &state); + if (result == ISC_R_SUCCESS) { + zrrsig_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } + return krrsig_ok && zrrsig_ok && time_ok && !inactive; +} + +bool +dst_key_is_revoked(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *revoke) +{ + isc_result_t result; + isc_stdtime_t when = 0; + bool time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_REVOKE, &when); + if (result == ISC_R_SUCCESS) { + *revoke = when; + time_ok = (when <= now); + } + + return time_ok; +} + +bool +dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove) +{ + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when = 0; + bool state_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + if (dst_key_is_unused(key)) { + /* This key was never used. */ + return false; + } + + result = dst_key_gettime(key, DST_TIME_DELETE, &when); + if (result == ISC_R_SUCCESS) { + *remove = when; + time_ok = (when <= now); + } + + /* Check key states: + * If the DNSKEY state is UNRETENTIVE or HIDDEN, it means the key + * should not be published. + */ + result = dst_key_getstate(key, DST_KEY_DNSKEY, &state); + if (result == ISC_R_SUCCESS) { + state_ok = ((state == DST_KEY_STATE_UNRETENTIVE) || + (state == DST_KEY_STATE_HIDDEN)); + /* + * Key states trump timing metadata. + * Ignore delete time. + */ + time_ok = true; + } + + return state_ok && time_ok; +} diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index 7f8517c4a9..a37725f404 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -1093,9 +1093,77 @@ dst_key_setinactive(dst_key_t *key, bool inactive); void dst_key_setexternal(dst_key_t *key, bool value); +/*%< + * Set key external state. + * + * Requires: + * 'key' to be valid. + */ bool dst_key_isexternal(dst_key_t *key); +/*%< + * Check if this is an external key. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_unused(dst_key_t *key); +/*%< + * Check if this key is unused. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_published(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *publish); +/*%< + * Check if it is safe to publish this key (e.g. put the DNSKEY in the zone). + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_active(dst_key_t *key, isc_stdtime_t now); +/*%< + * Check if this key is active. This means that it is creating RRSIG records + * (ZSK), or that it is used to create a chain of trust (KSK), or both (CSK). + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_signing(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *active); +/*%< + * Check if it is safe to use this key for signing. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_revoked(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *revoke); +/*%< + * Check if this key is revoked. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove); +/*%< + * Check if this key is removed from the zone (e.g. the DNSKEY record should + * no longer be in the zone). + * + * Requires: + * 'key' to be valid. + */ ISC_LANG_ENDDECLS diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index de79d7bfb1..72d39c478e 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -1426,6 +1426,12 @@ dst_key_getstate dst_key_gettime dst_key_getttl dst_key_id +dst_key_is_active +dst_key_is_published +dst_key_is_removed +dst_key_is_revoked +dst_key_is_signing +dst_key_is_unused dst_key_inactive dst_key_isexternal dst_key_isnullkey From 7e7aa5387c1bb5875322673c1dd5d6f32784bab2 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 17 Oct 2019 11:19:35 +0200 Subject: [PATCH 24/43] Introduce keymgr in named Add a key manager to named. If a 'dnssec-policy' is set, 'named' will run a key manager on the matching keys. This will do a couple of things: 1. Create keys when needed (in case of rollover for example) according to the set policy. 2. Retire keys that are in excess of the policy. 3. Maintain key states according to "Flexible and Robust Key Rollover" [1]. After key manager ran, key files will be saved to disk. [1] https://matthijsmekking.nl/static/pdf/satin2012-Schaeffer.pdf KEY GENERATION Create keys according to DNSSEC policy. Zones configured with 'dnssec-policy' will allow 'named' to create DNSSEC keys (similar to dnssec-keymgr) if not available. KEY ROLLOVER Rather than determining the desired state from timing metadata, add a key state goal. Any keys that are created or picked from the key ring and selected to be a successor has its key state goal set to OMNIPRESENT (this key wants to be signing!). At the same time, a key that is being retired has its key state goal set to HIDDEN. The keymgr state machine with the three rules will make sure no introduction or withdrawal of DNSSEC records happens too soon. KEY TIMINGS All timings are based on RFC 7583. The keymgr will return when the next action is happening so that the zone can set the proper rekey event. Prior to this change the rekey event will run every hour by default (configurable), but with kasp we can determine exactly when we need to run again. The prepublication time is derived from policy. --- lib/dns/Makefile.in | 6 +- lib/dns/include/dns/keymgr.h | 57 + lib/dns/keymgr.c | 1573 +++++++++++++++++++++++ lib/dns/win32/libdns.def.in | 1 + lib/dns/win32/libdns.vcxproj.filters.in | 6 + lib/dns/win32/libdns.vcxproj.in | 2 + util/copyrights | 2 + 7 files changed, 1644 insertions(+), 3 deletions(-) create mode 100644 lib/dns/include/dns/keymgr.h create mode 100644 lib/dns/keymgr.c diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in index 4e28fe4b33..a3cf51436d 100644 --- a/lib/dns/Makefile.in +++ b/lib/dns/Makefile.in @@ -65,7 +65,7 @@ DNSOBJS = acl.@O@ adb.@O@ badcache.@O@ byaddr.@O@ \ dlz.@O@ dns64.@O@ dnsrps.@O@ dnssec.@O@ ds.@O@ dyndb.@O@ \ ecs.@O@ fixedname.@O@ forward.@O@ \ ipkeylist.@O@ iptable.@O@ journal.@O@ kasp.@O@ keydata.@O@ \ - keytable.@O@ lib.@O@ log.@O@ lookup.@O@ \ + keymgr.@O@ keytable.@O@ lib.@O@ log.@O@ lookup.@O@ \ master.@O@ masterdump.@O@ message.@O@ \ name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ nta.@O@ \ order.@O@ peer.@O@ portlist.@O@ private.@O@ \ @@ -100,8 +100,8 @@ DNSSRCS = acl.c adb.c badcache. byaddr.c \ cache.c callbacks.c clientinfo.c compress.c \ db.c dbiterator.c dbtable.c diff.c dispatch.c \ dlz.c dns64.c dnsrps.c dnssec.c ds.c dyndb.c \ - ecs.c fixedname.c forward.c \ - ipkeylist.c iptable.c journal.c kasp.c keydata.c keytable.c \ + ecs.c fixedname.c forward.c ipkeylist.c iptable.c \ + journal.c kasp.c keydata.c keymgr.c keytable.c \ lib.c log.c lookup.c master.c masterdump.c message.c \ name.c ncache.c nsec.c nsec3.c nta.c \ order.c peer.c portlist.c \ diff --git a/lib/dns/include/dns/keymgr.h b/lib/dns/include/dns/keymgr.h new file mode 100644 index 0000000000..a97438bf3d --- /dev/null +++ b/lib/dns/include/dns/keymgr.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +#ifndef DNS_KEYMGR_H +#define DNS_KEYMGR_H 1 + +/*! \file dns/keymgr.h */ + +#include +#include + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, + const char *directory, isc_mem_t *mctx, + dns_dnsseckeylist_t *keyring, dns_kasp_t *kasp, + isc_stdtime_t now, isc_stdtime_t *nexttime); +/*%< + * Manage keys in 'keylist' and update timing data according to 'kasp' policy. + * Create new keys for 'origin' if necessary in 'directory'. Append all such + * keys, along with use hints gleaned from their metadata, onto 'keylist'. + * + * Update key states and store changes back to disk. Store when to run next + * in 'nexttime'. + * + * Requires: + *\li 'origin' is a valid FQDN. + *\li 'mctx' is a valid memory context. + *\li 'keyring' is not NULL. + *\li 'kasp' is not NULL. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li any error returned by dst_key_generate(), isc_dir_open(), + * dst_key_to_file(), or dns_dnsseckey_create(). + * + * Ensures: + *\li On error, keypool is unchanged + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_KEYMGR_H */ diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c new file mode 100644 index 0000000000..c8eacc23bd --- /dev/null +++ b/lib/dns/keymgr.c @@ -0,0 +1,1573 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define RETERR(x) do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +/* + * Set key state to HIDDEN and change last changed to now, + * only if key state has not been set before. + */ +#define INITIALIZE_STATE(key, state, time) \ + do { \ + dst_key_state_t s; \ + if (dst_key_getstate((key), (state), &s) == \ + ISC_R_NOTFOUND) \ + { \ + isc_stdtime_t t; \ + dst_key_gettime((key), DST_TIME_CREATED, &t); \ + dst_key_setstate((key), (state), HIDDEN); \ + dst_key_settime((key), (time), t); \ + } \ + } while (0) + +/* Shorter keywords for better readability. */ +#define HIDDEN DST_KEY_STATE_HIDDEN +#define RUMOURED DST_KEY_STATE_RUMOURED +#define OMNIPRESENT DST_KEY_STATE_OMNIPRESENT +#define UNRETENTIVE DST_KEY_STATE_UNRETENTIVE +#define NA DST_KEY_STATE_NA + +/* Quickly get key state timing metadata. */ +#define NUM_KEYSTATES (DST_MAX_KEYSTATES) +static int keystatetimes[NUM_KEYSTATES] = { + DST_TIME_DNSKEY, DST_TIME_ZRRSIG, DST_TIME_KRRSIG, DST_TIME_DS +}; +/* Readable key state types and values. */ +static const char* keystatetags[NUM_KEYSTATES] = { + "DNSKEY", "ZRRSIG", "KRRSIG", "DS" +}; +static const char* keystatestrings[4] = { + "HIDDEN", "RUMOURED", "OMNIPRESENT", "UNRETENTIVE" +}; + +/* + * Print key role. + * + */ +static const char* +keymgr_keyrole(dst_key_t* key) +{ + bool ksk, zsk; + dst_key_getbool(key, DST_BOOL_KSK, &ksk); + dst_key_getbool(key, DST_BOOL_ZSK, &zsk); + if (ksk && zsk) { + return ("CSK"); + } else if (ksk) { + return ("KSK"); + } else { + return ("ZSK"); + } + return ("NOSIGN"); +} + +/* + * Calculate prepublication time of a successor key of 'key'. + * This function can have side effects: + * If the lifetime is not set, it will be set now. + * If there should be a retire time and it is not set, it will be set now. + * If there is no active time set, which would be super weird, set it now. + * + * This returns when the successor key needs to be published in the zone. + * A special value of 0 means there is no need for a successor. + * + */ +static isc_stdtime_t +keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t* kasp, + uint32_t lifetime, isc_stdtime_t now) +{ + isc_result_t ret; + isc_stdtime_t active, retire, prepub; + bool ksk = false; + + REQUIRE(key != NULL); + REQUIRE(key->key != NULL); + + active = 0; + retire = 0; + prepub = dst_key_getttl(key->key) + dns_kasp_publishsafety(kasp) + + dns_kasp_zonepropagationdelay(kasp); + ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); + if (ret == ISC_R_SUCCESS && ksk) { + /* Add registration delay to the prepublication time. */ + prepub += dns_kasp_parentregistrationdelay(kasp); + } + + ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); + if (ret != ISC_R_SUCCESS) { + uint32_t klifetime = 0; + /* + * An active key must have an activate timing metadata. + */ + ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active); + if (ret != ISC_R_SUCCESS) { + /* Super weird, but if it happens, set it to now. */ + dst_key_settime(key->key, DST_TIME_ACTIVATE, now); + active = now; + } + + ret = dst_key_getnum(key->key, DST_NUM_LIFETIME, &klifetime); + if (ret != ISC_R_SUCCESS) { + dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime); + klifetime = lifetime; + } + if (klifetime == 0) { + /* + * No inactive time and no lifetime, + * so no need to start a rollover. + */ + return (0); + } + + retire = active + klifetime; + dst_key_settime(key->key, DST_TIME_INACTIVE, retire); + } + + /* + * Publish successor 'prepub' time before the 'retire' time of 'key'. + */ + return (retire - prepub); +} + + +static void +keymgr_key_retire(dns_dnsseckey_t *key, isc_stdtime_t now) +{ + char keystr[DST_KEY_FORMATSIZE]; + dst_key_state_t s; + bool ksk, zsk; + + REQUIRE(key != NULL); + REQUIRE(key->key != NULL); + + /* This key wants to retire and hide in a corner. */ + dst_key_settime(key->key, DST_TIME_INACTIVE, now); + dst_key_setstate(key->key, DST_KEY_GOAL, HIDDEN); + + /* This key may not have key states set yet. Pretend as if they are + * in the OMNIPRESENT state. + */ + if (dst_key_getstate(key->key, DST_KEY_DNSKEY, &s) != ISC_R_SUCCESS) { + dst_key_setstate(key->key, DST_KEY_DNSKEY, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_DNSKEY, now); + } + + (void) dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); + if (ksk) { + if (dst_key_getstate(key->key, DST_KEY_KRRSIG, &s) != + ISC_R_SUCCESS) + { + dst_key_setstate(key->key, DST_KEY_KRRSIG, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_KRRSIG, now); + } + if (dst_key_getstate(key->key, DST_KEY_DS, &s) != + ISC_R_SUCCESS) + { + dst_key_setstate(key->key, DST_KEY_DS, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_DS, now); + } + } + (void) dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk); + if (zsk) { + if (dst_key_getstate(key->key, DST_KEY_ZRRSIG, &s) != + ISC_R_SUCCESS) + { + dst_key_setstate(key->key, DST_KEY_ZRRSIG, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_ZRRSIG, now); + } + } + + dst_key_format(key->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, + ISC_LOG_INFO, "keymgr: retire DNSKEY %s (%s)", keystr, + keymgr_keyrole(key->key)); +} + +/* + * Check if a dnsseckey matches kasp key configuration. A dnsseckey matches + * if it has the same algorithm and size, and if it has the same role as the + * kasp key configuration. + * + */ +static bool +keymgr_dnsseckey_kaspkey_match(dns_dnsseckey_t *dkey, dns_kasp_key_t *kkey) +{ + dst_key_t *key; + isc_result_t ret; + bool role = false; + + REQUIRE(dkey != NULL); + REQUIRE(kkey != NULL); + + key = dkey->key; + + /* Matching algorithms? */ + if (dst_key_alg(key) != dns_kasp_key_algorithm(kkey)) { + return (false); + } + /* Matching length? */ + if (dst_key_size(key) != dns_kasp_key_size(kkey)) { + return (false); + } + /* Matching role? */ + ret = dst_key_getbool(key, DST_BOOL_KSK, &role); + if (ret != ISC_R_SUCCESS || role != dns_kasp_key_ksk(kkey)) { + return (false); + } + ret = dst_key_getbool(key, DST_BOOL_ZSK, &role); + if (ret != ISC_R_SUCCESS || role != dns_kasp_key_zsk(kkey)) { + return (false); + } + + /* Found a match. */ + return (true); +} + +/* + * Create a new key for 'origin' given the kasp key configuration 'kkey'. + * This will check for key id collisions with keys in 'keylist'. + * The created key will be stored in 'dst_key'. + * + */ +static isc_result_t +keymgr_createkey(dns_kasp_key_t *kkey, const dns_name_t *origin, + dns_rdataclass_t rdclass, isc_mem_t *mctx, + dns_dnsseckeylist_t *keylist, dst_key_t **dst_key) +{ + bool conflict; + int keyflags = DNS_KEYOWNER_ZONE; + isc_result_t result = ISC_R_SUCCESS; + dst_key_t *newkey = NULL; + + do { + uint16_t id; + uint32_t rid; + uint32_t algo = dns_kasp_key_algorithm(kkey); + int size = dns_kasp_key_size(kkey); + + conflict = false; + + if (dns_kasp_key_ksk(kkey)) { + keyflags |= DNS_KEYFLAG_KSK; + } + RETERR(dst_key_generate(origin, algo, size, 0, keyflags, + DNS_KEYPROTO_DNSSEC, rdclass, mctx, + &newkey, NULL)); + + /* Key collision? */ + id = dst_key_id(newkey); + rid = dst_key_rid(newkey); + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keylist); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + if (dst_key_alg(dkey->key) != algo) { + continue; + } + if (dst_key_id(dkey->key) == id || + dst_key_rid(dkey->key) == id || + dst_key_id(dkey->key) == rid || + dst_key_rid(dkey->key) == rid) + { + /* Try again. */ + conflict = true; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_WARNING, + "keymgr: key collision id %d", + dst_key_id(newkey)); + dst_key_free(&newkey); + } + } + } while (conflict == true); + + INSIST(!conflict); + dst_key_setnum(newkey, DST_NUM_LIFETIME, dns_kasp_key_lifetime(kkey)); + dst_key_setbool(newkey, DST_BOOL_KSK, dns_kasp_key_ksk(kkey)); + dst_key_setbool(newkey, DST_BOOL_ZSK, dns_kasp_key_zsk(kkey)); + *dst_key = newkey; + return (ISC_R_SUCCESS); + +failure: + return (result); +} + +/* + * Return the desired state for this record 'type'. The desired state depends + * on whether the key wants to be active, or wants to retire. This implements + * the edges of our state machine: + * + * ----> OMNIPRESENT ---- + * | | + * | \|/ + * + * RUMOURED <----> UNRETENTIVE + * + * /|\ | + * | | + * ---- HIDDEN <---- + * + * A key that wants to be active eventually wants to have its record types + * in the OMNIPRESENT state (that is, all resolvers that know about these + * type of records know about these records specifically). + * + * A key that wants to be retired eventually wants to have its record types + * in the HIDDEN state (that is, all resolvers that know about these type + * of records specifically don't know about these records). + * + */ +static dst_key_state_t +keymgr_desiredstate(dns_dnsseckey_t *key, dst_key_state_t state) +{ + dst_key_state_t goal; + + if (dst_key_getstate(key->key, DST_KEY_GOAL, &goal) != ISC_R_SUCCESS) { + /* No goal? No movement. */ + return (state); + } + + if (goal == HIDDEN) { + switch (state) { + case RUMOURED: + case OMNIPRESENT: + return (UNRETENTIVE); + case HIDDEN: + case UNRETENTIVE: + return (HIDDEN); + default: + return (state); + } + } else if (goal == OMNIPRESENT) { + switch (state) { + case RUMOURED: + case OMNIPRESENT: + return (OMNIPRESENT); + case HIDDEN: + case UNRETENTIVE: + return (RUMOURED); + default: + return (state); + } + } + + /* Unknown goal. */ + return (state); +} + +/* + * Check if 'key' matches specific 'states'. + * A state in 'states' that is NA matches any state. + * A state in 'states' that is HIDDEN also matches if the state is not set. + * If 'next_state' is set (not NA), we are pretending as if record 'type' of + * 'subject' key already transitioned to the 'next state'. + * + */ +static bool +keymgr_key_match_state(dst_key_t *key, dst_key_t* subject, int type, + dst_key_state_t next_state, dst_key_state_t states[4]) +{ + REQUIRE(key != NULL); + + for (int i = 0; i < 4; i++) { + dst_key_state_t state; + if (states[i] == NA) { + continue; + } + if (next_state != NA && i == type && + dst_key_id(key) == dst_key_id(subject)) { + /* Check next state rather than current state. */ + state = next_state; + } else if (dst_key_getstate(key, i, &state) != ISC_R_SUCCESS) { + /* This is fine only if expected state is HIDDEN. */ + if (states[i] != HIDDEN) { + return (false); + } + continue; + } + if (state != states[i]) { + return (false); + } + } + /* Match. */ + return (true); +} + +/* + * Check if a 'k2' is a successor of 'k1'. This is a simplified version of + * Equation(2) of "Flexible and Robust Key Rollover" which defines a + * recursive relation. + * + */ +static bool +keymgr_key_is_successor(dst_key_t *k1, dst_key_t *k2) +{ + uint32_t suc = 0, pre = 0; + if (dst_key_getnum(k1, DST_NUM_SUCCESSOR, &suc) != ISC_R_SUCCESS) { + return (false); + } + if (dst_key_getnum(k2, DST_NUM_PREDECESSOR, &pre) != ISC_R_SUCCESS) { + return (false); + } + return (dst_key_id(k1) == pre && dst_key_id(k2) == suc); +} + +/* + * Check if a key exists in 'keyring' that matches 'states'. + * + * If 'match_algorithms', the key must also match the algorithm of 'key'. + * If 'next_state' is not NA, we are actually looking for a key as if + * 'key' already transitioned to the next state. + * If 'check_successor', we also want to make sure there is a successor + * relationship with the found key that matches 'states2'. + */ +static bool +keymgr_key_exists_with_state(dns_dnsseckeylist_t *keyring, + dns_dnsseckey_t *key, int type, + dst_key_state_t next_state, + dst_key_state_t states[4], + dst_key_state_t states2[4], + bool check_successor, bool match_algorithms) +{ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + if (match_algorithms && + (dst_key_alg(dkey->key) != dst_key_alg(key->key))) + { + continue; + } + + if (check_successor && + keymgr_key_match_state(dkey->key, key->key, type, + next_state, states2)) + { + /* Found a possible successor, look for predecessor. */ + for (dns_dnsseckey_t *pkey = ISC_LIST_HEAD(*keyring); + pkey != NULL; pkey = ISC_LIST_NEXT(pkey, link)) + { + if (pkey == dkey) { + continue; + } + if (!keymgr_key_match_state(pkey->key, + key->key, type, + next_state, states)) + { + continue; + } + + /* + * Found a possible predecessor, check + * relationship. + */ + if (keymgr_key_is_successor(pkey->key, + dkey->key)) + { + return (true); + } + } + } + + if (!check_successor && + keymgr_key_match_state(dkey->key, key->key, type, + next_state, states)) + { + return (true); + } + } + /* No match. */ + return (false); +} + +/* + * Check if a key has a successor. + */ +static bool +keymgr_key_has_successor(dns_dnsseckey_t *key, dns_dnsseckeylist_t *keyring) +{ + /* Don't worry about key states. */ + dst_key_state_t na[4] = { NA, NA, NA, NA }; + return (keymgr_key_exists_with_state(keyring, key, DST_KEY_DNSKEY, NA, + na, na, true, true)); +} + +/* + * Check if all keys have their DS hidden. If not, then there must be at + * least one key with an OMNIPRESENT DNSKEY. + * + * If 'next_state' is not NA, we are actually looking for a key as if + * 'key' already transitioned to the next state. + * If 'match_algorithms', only consider keys with same algorithm of 'key'. + * + */ +static bool +keymgr_ds_hidden_or_chained(dns_dnsseckeylist_t *keyring, + dns_dnsseckey_t *key, int type, + dst_key_state_t next_state, + bool match_algorithms, bool must_be_hidden) +{ + dst_key_state_t dnskey_omnipresent[4] = + { OMNIPRESENT, NA, OMNIPRESENT, NA }; /* (3e) */ + dst_key_state_t ds_hidden[4] = { NA, NA, NA, HIDDEN }; /* (3e) */ + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(dkey->key, keystr, sizeof(keystr)); + + if (match_algorithms && + (dst_key_alg(dkey->key) != dst_key_alg(key->key))) + { + continue; + } + + if (keymgr_key_match_state(dkey->key, key->key, type, + next_state, ds_hidden)) + { + /* This key has its DS hidden. */ + continue; + } + + if (must_be_hidden) { + return (false); + } + + /* + * This key does not have its DS hidden. There must be at + * least one key with the same algorithm that provides a + * chain of trust (can be this key). + */ + dnskey_omnipresent[DST_KEY_DS] = NA; + (void)dst_key_getstate(dkey->key, DST_KEY_DS, + &dnskey_omnipresent[DST_KEY_DS]); + if (!keymgr_key_exists_with_state(keyring, key, type, + next_state, + dnskey_omnipresent, na, + false, match_algorithms)) + { + /* There is no chain of trust. */ + return (false); + } + } + /* All good. */ + return (true); +} + +/* + * Check if all keys have their DNSKEY hidden. If not, then there must be at + * least one key with an OMNIPRESENT ZRRSIG. + * + * If 'next_state' is not NA, we are actually looking for a key as if + * 'key' already transitioned to the next state. + * If 'match_algorithms', only consider keys with same algorithm of 'key'. + * + */ +static bool +keymgr_dnskey_hidden_or_chained(dns_dnsseckeylist_t *keyring, + dns_dnsseckey_t *key, int type, + dst_key_state_t next_state, + bool match_algorithms) +{ + dst_key_state_t rrsig_omnipresent[4] = + { NA, OMNIPRESENT, NA, NA }; /* (3i) */ + dst_key_state_t dnskey_hidden[4] = { HIDDEN, NA, NA, NA }; /* (3i) */ + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + if (match_algorithms && + (dst_key_alg(dkey->key) != dst_key_alg(key->key))) + { + continue; + } + + if (keymgr_key_match_state(dkey->key, key->key, type, + next_state, dnskey_hidden)) + { + /* This key has its DNSKEY hidden. */ + continue; + } + + /* + * This key does not have its DNSKEY hidden. There must be at + * least one key with the same algorithm that has its RRSIG + * records OMNIPRESENT. + */ + rrsig_omnipresent[DST_KEY_DNSKEY] = NA; + (void)dst_key_getstate(dkey->key, DST_KEY_DNSKEY, + &rrsig_omnipresent[DST_KEY_DNSKEY]); + if (!keymgr_key_exists_with_state(keyring, key, type, + next_state, + rrsig_omnipresent, na, + false, match_algorithms)) + { + /* There is no chain of trust. */ + return (false); + } + } + /* All good. */ + return (true); +} + +/* + * Check for existence of DS. + * + */ +static bool +keymgr_have_ds(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state) +{ + dst_key_state_t states[2][4] = { + /* DNSKEY, ZRRSIG, KRRSIG, DS */ + { NA, NA, NA, OMNIPRESENT }, /* DS present */ + { NA, NA, NA, RUMOURED } /* DS introducing */ + }; + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + /* + * Equation (3a): + * There is a key with the DS in either RUMOURD or OMNIPRESENT state. + */ + return (keymgr_key_exists_with_state(keyring, key, type, + next_state, states[0], na, + false, false) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[1], na, + false, false)); +} + +/* + * Check for existence of DNSKEY, or at least a good DNSKEY state. + * See equations what are good DNSKEY states. + * + */ +static bool +keymgr_have_dnskey(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state) +{ + dst_key_state_t states[9][4] = { + /* DNSKEY, ZRRSIG, KRRSIG, DS */ + { OMNIPRESENT, NA, OMNIPRESENT, OMNIPRESENT }, /* (3b) */ + + { OMNIPRESENT, NA, OMNIPRESENT, UNRETENTIVE }, /* (3c)p */ + { OMNIPRESENT, NA, OMNIPRESENT, RUMOURED }, /* (3c)s */ + + { UNRETENTIVE, NA, UNRETENTIVE, OMNIPRESENT }, /* (3d)p */ + { OMNIPRESENT, NA, UNRETENTIVE, OMNIPRESENT }, /* (3d)p */ + { UNRETENTIVE, NA, OMNIPRESENT, OMNIPRESENT }, /* (3d)p */ + { RUMOURED, NA, RUMOURED, OMNIPRESENT }, /* (3d)s */ + { OMNIPRESENT, NA, RUMOURED, OMNIPRESENT }, /* (3d)s */ + { RUMOURED, NA, OMNIPRESENT, OMNIPRESENT }, /* (3d)s */ + }; + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + return ( + /* + * Equation (3b): + * There is a key with the same algorithm with its DNSKEY, + * KRRSIG and DS records in OMNIPRESENT state. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[0], na, + false, true) || + /* + * Equation (3c): + * There are two or more keys with an OMNIPRESENT DNSKEY and + * the DS records get swapped. These keys must be in a + * successor relation. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[1], states[2], + true, true) || + /* + * Equation (3d): + * There are two or more keys with an OMNIPRESENT DS and + * the DNSKEY records and its KRRSIG records get swapped. + * These keys must be in a successor relation. Since the + * state for DNSKEY and KRRSIG move independently, we have + * to check all combinations for DNSKEY and KRRSIG in + * OMNIPRESENT/UNRETENTIVE state for the predecessor, and + * OMNIPRESENT/RUMOURED state for the successor. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[3], states[6], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[3], states[7], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[3], states[8], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[4], states[6], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[4], states[7], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[4], states[8], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[5], states[6], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[5], states[7], + true, true) || + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[5], states[8], + true, true) || + /* + * Equation (3e): + * The key may be in any state as long as all keys have their + * DS HIDDEN, or when their DS is not HIDDEN, there must be a + * key with its DS in the same state and its DNSKEY omnipresent. + * In other words, if a DS record for the same algorithm is + * is still available to some validators, there must be a + * chain of trust for those validators. + */ + keymgr_ds_hidden_or_chained(keyring, key, type, + next_state, true, false) + ); +} + +/* + * Check for existence of RRSIG (zsk), or a good RRSIG state. + * See equations what are good RRSIG states. + * + */ +static bool +keymgr_have_rrsig(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state) +{ + dst_key_state_t states[11][4] = { + /* DNSKEY, ZRRSIG, KRRSIG, DS */ + { OMNIPRESENT, OMNIPRESENT, NA, NA }, /* (3f) */ + { UNRETENTIVE, OMNIPRESENT, NA, NA }, /* (3g)p */ + { RUMOURED, OMNIPRESENT, NA, NA }, /* (3g)s */ + { OMNIPRESENT, UNRETENTIVE, NA, NA }, /* (3h)p */ + { OMNIPRESENT, RUMOURED, NA, NA }, /* (3h)s */ + }; + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + return ( + /* + * If all DS records are hidden than this rule can be ignored. + */ + keymgr_ds_hidden_or_chained(keyring, key, type, + next_state, true, true) || + /* + * Equation (3f): + * There is a key with the same algorithm with its DNSKEY and + * ZRRSIG records in OMNIPRESENT state. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[0], na, + false, true) || + /* + * Equation (3g): + * There are two or more keys with OMNIPRESENT ZRRSIG + * records and the DNSKEY records get swapped. These keys + * must be in a successor relation. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[1], states[2], + true, true) || + /* + * Equation (3h): + * There are two or more keys with an OMNIPRESENT DNSKEY + * and the ZRRSIG records get swapped. These keys must be in + * a successor relation. + */ + keymgr_key_exists_with_state(keyring, key, type, + next_state, states[3], states[4], + true, true) || + /* + * Equation (3i): + * If no DNSKEYs are published, the state of the signatures is + * irrelevant. In case a DNSKEY is published however, there + * must be a path that can be validated from there. + */ + keymgr_dnskey_hidden_or_chained(keyring, key, type, + next_state, true) + ); +} + +/* + * Check if a transition in the state machine is allowed by the policy. + * This means when we do rollovers, we want to follow the rules of the + * 1. Pre-publish rollover method (in case of a ZSK) + * - First introduce the DNSKEY record. + * - Only if the DNSKEY record is OMNIPRESENT, introduce ZRRSIG records. + * + * 2. Double-KSK rollover method (in case of a KSK) + * - First introduce the DNSKEY record, as well as the KRRSIG records. + * - Only if the DNSKEY record is OMNIPRESENT, suggest to introduce the DS. + * + */ +static bool +keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next) +{ + dst_key_state_t dnskeystate = HIDDEN; + dst_key_state_t ksk_present[4] = + { OMNIPRESENT, NA, OMNIPRESENT, OMNIPRESENT }; + dst_key_state_t ds_rumoured[4] = + { OMNIPRESENT, NA, OMNIPRESENT, RUMOURED }; + dst_key_state_t ds_retired[4] = + { OMNIPRESENT, NA, OMNIPRESENT, UNRETENTIVE }; + dst_key_state_t ksk_rumoured[4] = { RUMOURED, NA, NA, OMNIPRESENT }; + dst_key_state_t ksk_retired[4] = { UNRETENTIVE, NA, NA, OMNIPRESENT }; + dst_key_state_t na[4] = { NA, NA, NA, NA }; /* successor n/a */ + + if (next != RUMOURED) { + /* + * Local policy only adds an extra barrier on transitions to + * the RUMOURED state. + */ + return (true); + } + + switch (type) { + case DST_KEY_DNSKEY: + /* No restrictions. */ + return (true); + case DST_KEY_ZRRSIG: + /* Make sure the DNSKEY record is OMNIPRESENT. */ + (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate); + if (dnskeystate == OMNIPRESENT) { + return (true); + } + /* + * Or are we introducing a new key for this algorithm? Because + * in that case allow publishing the RRSIG records before the + * DNSKEY. + */ + return (!( + keymgr_key_exists_with_state(keyring, key, type, + next, ksk_present, na, + false, true) || + keymgr_key_exists_with_state(keyring, key, type, + next, ds_retired, + ds_rumoured, true, + true) || + keymgr_key_exists_with_state(keyring, key, type, + next, ksk_retired, + ksk_rumoured, true, + true)) + ); + case DST_KEY_KRRSIG: + /* Only introduce if the DNSKEY is also introduced. */ + (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate); + return (dnskeystate != HIDDEN); + case DST_KEY_DS: + /* Make sure the DNSKEY record is OMNIPRESENT. */ + (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate); + return (dnskeystate == OMNIPRESENT); + default: + return (false); + } +} + +/* + * Check if a transition in the state machine is DNSSEC safe. + * This implements Equation(1) of "Flexible and Robust Key Rollover". + * + */ +static bool +keymgr_transition_allowed(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state) +{ + /* Debug logging. */ + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + bool rule1a, rule1b, rule2a, rule2b, rule3a, rule3b; + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key->key, keystr, sizeof(keystr)); + rule1a = keymgr_have_ds(keyring, key, type, NA); + rule1b = keymgr_have_ds(keyring, key, type, next_state); + rule2a = keymgr_have_dnskey(keyring, key, type, NA); + rule2b = keymgr_have_dnskey(keyring, key, type, next_state); + rule3a = keymgr_have_rrsig(keyring, key, type, NA); + rule3b = keymgr_have_rrsig(keyring, key, type, next_state); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: dnssec evaluation of %s %s record %s: " + "rule1=(~%s or %s) rule2=(~%s or %s) " + "rule3=(~%s or %s)", keymgr_keyrole(key->key), + keystr, keystatetags[type], + rule1a ? "true" : "false", + rule1b ? "true" : "false", + rule2a ? "true" : "false", + rule2b ? "true" : "false", + rule3a ? "true" : "false", + rule3b ? "true" : "false"); + } + + return ( + /* + * Rule 1: There must be a DS at all times. + * First check the current situation: if the rule check fails, + * we allow the transition to attempt to move us out of the + * invalid state. If the rule check passes, also check if + * the next state is also still a valid situation. + */ + (!keymgr_have_ds(keyring, key, type, NA) || + keymgr_have_ds(keyring, key, type, next_state)) && + /* + * Rule 2: There must be a DNSKEY at all times. Again, first + * check the current situation, then assess the next state. + */ + (!keymgr_have_dnskey(keyring, key, type, NA) || + keymgr_have_dnskey(keyring, key, type, next_state)) && + /* + * Rule 3: There must be RRSIG records at all times. Again, + * first check the current situation, then assess the next + * state. + */ + (!keymgr_have_rrsig(keyring, key, type, NA) || + keymgr_have_rrsig(keyring, key, type, next_state)) + ); +} + +/* + * Calculate the time when it is safe to do the next transition. + * + */ +static void +keymgr_transition_time(dns_dnsseckey_t* key, int type, + dst_key_state_t next_state, dns_kasp_t* kasp, + isc_stdtime_t now, isc_stdtime_t *when) +{ + isc_result_t ret; + isc_stdtime_t lastchange, nexttime = now; + + /* + * No need to wait if we move things into an uncertain state. + */ + if (next_state == RUMOURED || next_state == UNRETENTIVE) { + *when = now; + return; + } + + ret = dst_key_gettime(key->key, keystatetimes[type], &lastchange); + if (ret != ISC_R_SUCCESS) { + /* No last change, for safety purposes let's set it to now. */ + dst_key_settime(key->key, keystatetimes[type], now); + lastchange = now; + } + + switch(type) { + case DST_KEY_DNSKEY: + case DST_KEY_KRRSIG: + switch (next_state) { + case OMNIPRESENT: + /* + * RFC 7583: The publication interval (Ipub) is the + * amount of time that must elapse after the + * publication of a DNSKEY (plus RRSIG (KSK)) before + * it can be assumed that any resolvers that have the + * relevant RRset cached have a copy of the new + * information. This is the sum of the propagation + * delay (Dprp) and the DNSKEY TTL (TTLkey). This + * translates to zone-propagation-delay + dnskey-ttl. + * We will also add the publish-safety interval. + */ + nexttime = lastchange + dst_key_getttl(key->key) + + dns_kasp_zonepropagationdelay(kasp) + + dns_kasp_publishsafety(kasp); + break; + case HIDDEN: + /* + * Same as OMNIPRESENT but without the publish-safety + * interval. + */ + nexttime = lastchange + dst_key_getttl(key->key) + + dns_kasp_zonepropagationdelay(kasp); + break; + default: + nexttime = now; + break; + } + break; + case DST_KEY_ZRRSIG: + switch (next_state) { + case OMNIPRESENT: + case HIDDEN: + /* + * RFC 7583: The retire interval (Iret) is the amount + * of time that must elapse after a DNSKEY or + * associated data enters the retire state for any + * dependent information (RRSIG ZSK) to be purged from + * validating resolver caches. This is defined as: + * + * Iret = Dsgn + Dprp + TTLsig + * + * Where Dsgn is the Dsgn is the delay needed to + * ensure that all existing RRsets have been re-signed + * with the new key, Dprp is the propagation delay and + * TTLsig is the maximum TTL of all zone RRSIG + * records. This translates to: + * + * Dsgn + zone-propragation-delay + zone-max-ttl. + * + * We will also add the retire-safety interval. + */ + nexttime = lastchange + dns_kasp_signdelay(kasp) + + dns_kasp_zonemaxttl(kasp) + + dns_kasp_zonepropagationdelay(kasp) + + dns_kasp_retiresafety(kasp); + break; + default: + nexttime = now; + break; + } + break; + case DST_KEY_DS: + switch (next_state) { + case OMNIPRESENT: + case HIDDEN: + /* + * RFC 7583: The successor DS record is published in + * the parent zone and after the registration delay + * (Dreg), the time taken after the DS record has been + * submitted to the parent zone manager for it to be + * placed in the zone. Key N (the predecessor) must + * remain in the zone until any caches that contain a + * copy of the DS RRset have a copy containing the new + * DS record. This interval is the retire interval + * (Iret), given by: + * + * Iret = DprpP + TTLds + * + * So we need to wait Dreg + Iret before the DS becomes + * OMNIPRESENT. This translates to: + * + * parent-registration-delay + + * parent-propagation-delay + parent-ds-ttl. + * + * We will also add the retire-safety interval. + */ + nexttime = lastchange + dns_kasp_dsttl(kasp) + + dns_kasp_parentregistrationdelay(kasp) + + dns_kasp_parentpropagationdelay(kasp) + + dns_kasp_retiresafety(kasp); + break; + default: + nexttime = now; + break; + } + break; + default: + INSIST(0); + ISC_UNREACHABLE(); + break; + } + + *when = nexttime; +} + +/* + * Update keys. + * This implements Algorithm (1) of "Flexible and Robust Key Rollover". + * + */ +static isc_result_t +keymgr_update(dns_dnsseckeylist_t *keyring, dns_kasp_t* kasp, + isc_stdtime_t now, isc_stdtime_t *nexttime) +{ + bool changed; + + /* Repeat until nothing changed. */ +transition: + changed = false; + + /* For all keys in the zone. */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(dkey->key, keystr, sizeof(keystr)); + + /* For all records related to this key. */ + for (int i = 0; i < NUM_KEYSTATES; i++) { + isc_result_t ret; + isc_stdtime_t when; + dst_key_state_t state, next_state; + + ret = dst_key_getstate(dkey->key, i, &state); + if (ret == ISC_R_NOTFOUND) { + /* + * This record type is not applicable for this + * key, continue to the next record type. + */ + continue; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: examine %s %s type %s " + "in state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state]); + + /* Get the desired next state. */ + next_state = keymgr_desiredstate(dkey, state); + if (state == next_state) { + /* + * This record is in a stable state. + * No change needed, continue with the next + * record type. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: %s %s type %s in " + "stable state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], + keystatestrings[state]); + continue; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: can we transition %s %s type %s " + "state %s to state %s?", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + + /* Is the transition allowed according to policy? */ + if (!keymgr_policy_approval(keyring, dkey, i, + next_state)) + { + /* No, please respect rollover methods. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: policy says no to %s %s type %s " + "state %s to state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + + continue; + } + + /* Is the transition DNSSEC safe? */ + if (!keymgr_transition_allowed(keyring, dkey, i, + next_state)) + { + /* No, this would make the zone bogus. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: dnssec says no to %s %s type %s " + "state %s to state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + continue; + } + + /* Is it time to make the transition? */ + when = now; + keymgr_transition_time(dkey, i, next_state, kasp, now, + &when); + if (when > now) { + /* Not yet. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: time says no to %s %s type %s " + "state %s to state %s (wait %u seconds)", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state], when - now); + if (*nexttime == 0 || *nexttime > when) { + *nexttime = when; + } + continue; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: transition %s %s type %s " + "state %s to state %s!", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + + /* It is safe to make the transition. */ + dst_key_setstate(dkey->key, i, next_state); + dst_key_settime(dkey->key, keystatetimes[i], now); + changed = true; + } + } + + /* We changed something, continue processing. */ + if (changed) { + goto transition; + } + + return (ISC_R_SUCCESS); +} + +/* + * See if this key needs to be initialized with a role. A key created and + * derived from a dnssec-policy will have the required metadata available, + * otherwise these may be missing and need to be initialized. + * + */ +static void +keymgr_key_init_role(dns_dnsseckey_t *key) +{ + bool ksk, zsk; + isc_result_t ret; + + REQUIRE(key != NULL); + REQUIRE(key->key != NULL); + + ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) != 0); + dst_key_setbool(key->key, DST_BOOL_KSK, ksk); + } + ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk); + if (ret != ISC_R_SUCCESS) { + zsk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) == 0); + dst_key_setbool(key->key, DST_BOOL_ZSK, zsk); + } +} + +/* + * Examine 'keys' and match 'kasp' policy. + * + */ +isc_result_t +dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, + const char *directory, isc_mem_t *mctx, + dns_dnsseckeylist_t *keyring, dns_kasp_t *kasp, + isc_stdtime_t now, isc_stdtime_t *nexttime) +{ + isc_result_t result = ISC_R_SUCCESS; + dns_dnsseckeylist_t newkeys; + dns_kasp_key_t *kkey; + dns_dnsseckey_t *candidate = NULL; + dns_dnsseckey_t *newkey = NULL; + dst_key_t *dst_key = NULL; + isc_dir_t dir; + bool dir_open = false; + int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE); + char keystr[DST_KEY_FORMATSIZE]; + + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(keyring != NULL); + + ISC_LIST_INIT(newkeys); + isc_dir_init(&dir); + if (directory == NULL) { + directory = "."; + } + + RETERR(isc_dir_open(&dir, directory)); + dir_open = true; + + *nexttime = 0; + + /* Debug logging: what keys are available in the keyring? */ + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + if (ISC_LIST_EMPTY(*keyring)) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(origin, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: keyring empty (zone %s policy %s)", + namebuf, dns_kasp_getname(kasp)); + } + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + dst_key_format(dkey->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: keyring: dnskey %s (policy %s)", + keystr, + dns_kasp_getname(kasp)); + } + } + + /* Create keys according to the policy, if come in short. */ + for (kkey = ISC_LIST_HEAD(kasp->keys); kkey != NULL; + kkey = ISC_LIST_NEXT(kkey, link)) + { + isc_stdtime_t retire = 0, active = 0, prepub = 0; + uint32_t lifetime = dns_kasp_key_lifetime(kkey); + dns_dnsseckey_t *active_key = NULL; + + /* Do we have keys available for this kasp key? */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + /* Make sure this key knows about roles. */ + keymgr_key_init_role(dkey); + + if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) { + /* Found a match. */ + + /* Initialize lifetime. */ + uint32_t l; + if (dst_key_getnum(dkey->key, + DST_NUM_LIFETIME, &l) != + ISC_R_SUCCESS) + { + dst_key_setnum(dkey->key, + DST_NUM_LIFETIME, + lifetime); + } + + if (dst_key_is_active(dkey->key, now)) { + if (active_key != NULL) { + /* + * Multiple signing keys match + * the kasp key configuration. + * Retire excess keys. + */ + keymgr_key_retire(dkey, now); + } else { + /* Save the matched key. */ + active_key = dkey; + } + } + } + } + + /* Do we need to create a successor for the active key? */ + if (active_key != NULL) { + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + dst_key_format(active_key->key, keystr, + sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: DNSKEY %s (%s) matches " + "policy %s", keystr, + keymgr_keyrole(active_key->key), + dns_kasp_getname(kasp)); + } + + /* + * Calculate when the successor needs to be published + * in the zone. + */ + prepub = keymgr_prepublication_time(active_key, kasp, + lifetime, now); + if (prepub == 0 || prepub > now) { + /* No need to start rollover now. */ + if (*nexttime == 0 || prepub < *nexttime) { + *nexttime = prepub; + } + continue; + } + if (keymgr_key_has_successor(active_key, keyring)) { + /* Key already has successor. */ + continue; + } + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + dst_key_format(active_key->key, keystr, + sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: need successor for " + "DNSKEY %s (%s) (policy %s)", + keystr, + keymgr_keyrole(active_key->key), + dns_kasp_getname(kasp)); + } + } else if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + char namestr[DNS_NAME_FORMATSIZE]; + dns_name_format(origin, namestr, sizeof(namestr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: no active key found for %s " + "(policy %s)", namestr, + dns_kasp_getname(kasp)); + } + + /* It is time to do key rollover, we need a new key. */ + + /* + * Check if there is a key available in pool because keys + * may have been pregenerated with dnssec-keygen. + */ + for (candidate = ISC_LIST_HEAD(*keyring); + candidate != NULL; + candidate = ISC_LIST_NEXT(candidate, link)) + { + if (keymgr_dnsseckey_kaspkey_match(candidate, kkey) && + dst_key_is_unused(candidate->key)) + { + /* Found a candidate in keyring. */ + break; + } + } + + if (candidate == NULL) { + /* No key available in keyring, create a new one. */ + RETERR(keymgr_createkey(kkey, origin, rdclass, mctx, + keyring, &dst_key)); + dst_key_setttl(dst_key, dns_kasp_dnskeyttl(kasp)); + dst_key_settime(dst_key, DST_TIME_CREATED, now); + RETERR(dns_dnsseckey_create(mctx, &dst_key, &newkey)); + } else { + newkey = candidate; + dst_key_setnum(newkey->key, DST_NUM_LIFETIME, lifetime); + } + + /* Got a key. */ + if (active_key == NULL) { + /* + * If there is no active key found yet for this kasp + * key configuration, immediately make this key active. + */ + dst_key_settime(newkey->key, DST_TIME_PUBLISH, now); + dst_key_settime(newkey->key, DST_TIME_ACTIVATE, now); + active = now; + } else { + /* + * This is a successor. Mark the relationship. + */ + dst_key_setnum(newkey->key, DST_NUM_PREDECESSOR, + dst_key_id(active_key->key)); + dst_key_setnum(active_key->key, DST_NUM_SUCCESSOR, + dst_key_id(newkey->key)); + (void)dst_key_gettime(active_key->key, + DST_TIME_INACTIVE, &retire); + dst_key_settime(newkey->key, DST_TIME_PUBLISH, prepub); + dst_key_settime(newkey->key, DST_TIME_ACTIVATE, retire); + active = retire; + } + + /* This key wants to be present. */ + dst_key_setstate(newkey->key, DST_KEY_GOAL, OMNIPRESENT); + + /* Do we need to set retire time? */ + (void)dst_key_getnum(newkey->key, DST_NUM_LIFETIME, &lifetime); + if (lifetime > 0) { + dst_key_settime(newkey->key, DST_TIME_INACTIVE, + (active + lifetime)); + } + + /* Append dnsseckey to list of new keys. */ + dns_dnssec_get_hints(newkey, now); + newkey->source = dns_keysource_repository; + INSIST(!newkey->legacy); + if (candidate == NULL) { + ISC_LIST_APPEND(newkeys, newkey, link); + } + + /* Logging. */ + dst_key_format(newkey->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "keymgr: DNSKEY %s (%s) %s for policy %s", + keystr, keymgr_keyrole(newkey->key), + (candidate != NULL) ? "selected" : "created", + dns_kasp_getname(kasp)); + + /* Onwards to next kasp key configuration. */ + candidate = NULL; + newkey = NULL; + } + + /* Walked all kasp key configurations. Append new keys. */ + if (!ISC_LIST_EMPTY(newkeys)) { + ISC_LIST_APPENDLIST(*keyring, newkeys, link); + } + + /* Initialize key states (for keys that don't have them yet). */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + bool ksk = false, zsk = false; + + /* Set key states for all keys that do not have them. */ + INITIALIZE_STATE(dkey->key, DST_KEY_DNSKEY, DST_TIME_DNSKEY); + (void) dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk); + if (ksk) { + INITIALIZE_STATE(dkey->key, DST_KEY_KRRSIG, + DST_TIME_KRRSIG); + INITIALIZE_STATE(dkey->key, DST_KEY_DS, DST_TIME_DS); + } + (void) dst_key_getbool(dkey->key, DST_BOOL_ZSK, &zsk); + if (zsk) { + INITIALIZE_STATE(dkey->key, DST_KEY_ZRRSIG, + DST_TIME_ZRRSIG); + } + } + + /* Read to update key states. */ + keymgr_update(keyring, kasp, now, nexttime); + + /* Store key states and update hints. */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + dns_dnssec_get_hints(dkey, now); + RETERR(dst_key_tofile(dkey->key, options, directory)); + } + + result = ISC_R_SUCCESS; + +failure: + if (dir_open) { + isc_dir_close(&dir); + } + + INSIST(newkey == NULL); + if (result != ISC_R_SUCCESS) { + while ((newkey = ISC_LIST_HEAD(newkeys)) != NULL) { + ISC_LIST_UNLINK(newkeys, newkey, link); + INSIST(newkey->key != NULL); + dst_key_free(&newkey->key); + dns_dnsseckey_destroy(mctx, &newkey); + } + } + + return (result); +} diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 72d39c478e..950830951f 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -442,6 +442,7 @@ dns_kasplist_find dns_keydata_fromdnskey dns_keydata_todnskey dns_keyflags_fromtext +dns_keymgr_run dns_keynode_attach dns_keynode_create dns_keynode_detach diff --git a/lib/dns/win32/libdns.vcxproj.filters.in b/lib/dns/win32/libdns.vcxproj.filters.in index b06e21792b..89811c148d 100644 --- a/lib/dns/win32/libdns.vcxproj.filters.in +++ b/lib/dns/win32/libdns.vcxproj.filters.in @@ -122,6 +122,9 @@ Library Source Files + + Library Source Files + Library Source Files @@ -461,6 +464,9 @@ Library Header Files + + Library Header Files + Library Header Files diff --git a/lib/dns/win32/libdns.vcxproj.in b/lib/dns/win32/libdns.vcxproj.in index 9a749d28e6..89019d8912 100644 --- a/lib/dns/win32/libdns.vcxproj.in +++ b/lib/dns/win32/libdns.vcxproj.in @@ -152,6 +152,7 @@ + @@ -269,6 +270,7 @@ + diff --git a/util/copyrights b/util/copyrights index 25250dc10b..733bedd2b1 100644 --- a/util/copyrights +++ b/util/copyrights @@ -1683,6 +1683,7 @@ ./lib/dns/include/dns/kasp.h C 2019 ./lib/dns/include/dns/keydata.h C 2009,2016,2018,2019 ./lib/dns/include/dns/keyflags.h C 1999,2000,2001,2004,2005,2006,2007,2016,2018,2019 +./lib/dns/include/dns/keymgr.h C 2019 ./lib/dns/include/dns/keytable.h C 2000,2001,2004,2005,2007,2009,2010,2014,2015,2016,2017,2018,2019 ./lib/dns/include/dns/keyvalues.h C 1999,2000,2001,2003,2004,2005,2006,2007,2008,2009,2010,2012,2016,2017,2018,2019 ./lib/dns/include/dns/lib.h C 1999,2000,2001,2004,2005,2006,2007,2009,2016,2017,2018,2019 @@ -1751,6 +1752,7 @@ ./lib/dns/kasp.c C 2019 ./lib/dns/key.c C 2001,2004,2005,2006,2007,2011,2016,2018,2019 ./lib/dns/keydata.c C 2009,2014,2016,2018,2019 +./lib/dns/keymgr.c C 2019 ./lib/dns/keytable.c C 2000,2001,2004,2005,2007,2009,2010,2013,2014,2015,2016,2017,2018,2019 ./lib/dns/lib.c C 1999,2000,2001,2004,2005,2007,2009,2013,2014,2015,2016,2017,2018,2019 ./lib/dns/log.c C 1999,2000,2001,2003,2004,2005,2006,2007,2009,2011,2012,2013,2014,2015,2016,2017,2018,2019 From 09990672d9597bf7c8fa4e5d78b117944aad7cab Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 17 Oct 2019 11:38:56 +0200 Subject: [PATCH 25/43] Update zoneconf to use kasp config If a zone has a dnssec-policy set, use signature validity, dnskey signature validity, and signature refresh from dnssec-policy. Zones configured with 'dnssec-policy' will allow 'named' to create DNSSEC keys (similar to dnssec-keymgr) if not available. --- bin/named/zoneconf.c | 85 +++++++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 29 deletions(-) diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index d8163d5c0c..0978902573 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -1500,38 +1500,52 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, bool allow = false, maint = false; bool sigvalinsecs; - obj = NULL; - result = named_config_get(maps, "dnskey-sig-validity", &obj); - INSIST(result == ISC_R_SUCCESS && obj != NULL); - seconds = cfg_obj_asuint32(obj) * 86400; + if (kasp) { + seconds = (uint32_t) dns_kasp_sigvalidity_dnskey(kasp); + } else { + obj = NULL; + result = named_config_get(maps, "dnskey-sig-validity", + &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + seconds = cfg_obj_asuint32(obj) * 86400; + } dns_zone_setkeyvalidityinterval(zone, seconds); - obj = NULL; - result = named_config_get(maps, "sig-validity-interval", &obj); - INSIST(result == ISC_R_SUCCESS && obj != NULL); - - sigvalinsecs = ns_server_getoption(named_g_server->sctx, - NS_SERVER_SIGVALINSECS); - validity = cfg_tuple_get(obj, "validity"); - seconds = cfg_obj_asuint32(validity); - if (!sigvalinsecs) { - seconds *= 86400; - } - dns_zone_setsigvalidityinterval(zone, seconds); - - resign = cfg_tuple_get(obj, "re-sign"); - if (cfg_obj_isvoid(resign)) { - seconds /= 4; - } else if (!sigvalinsecs) { - if (seconds > 7 * 86400) { - seconds = cfg_obj_asuint32(resign) * 86400; - } else { - seconds = cfg_obj_asuint32(resign) * 3600; - } + if (kasp) { + seconds = (uint32_t) dns_kasp_sigvalidity(kasp); + dns_zone_setsigvalidityinterval(zone, seconds); + seconds = (uint32_t) dns_kasp_sigrefresh(kasp); + dns_zone_setsigresigninginterval(zone, seconds); } else { - seconds = cfg_obj_asuint32(resign); + obj = NULL; + result = named_config_get(maps, "sig-validity-interval", + &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + + sigvalinsecs = ns_server_getoption(named_g_server->sctx, + NS_SERVER_SIGVALINSECS); + validity = cfg_tuple_get(obj, "validity"); + seconds = cfg_obj_asuint32(validity); + if (!sigvalinsecs) { + seconds *= 86400; + } + dns_zone_setsigvalidityinterval(zone, seconds); + + resign = cfg_tuple_get(obj, "re-sign"); + if (cfg_obj_isvoid(resign)) { + seconds /= 4; + } else if (!sigvalinsecs) { + seconds = cfg_obj_asuint32(resign); + if (seconds > 7 * 86400) { + seconds *= 86400; + } else { + seconds *= 3600; + } + } else { + seconds = cfg_obj_asuint32(resign); + } + dns_zone_setsigresigninginterval(zone, seconds); } - dns_zone_setsigresigninginterval(zone, seconds); obj = NULL; result = named_config_get(maps, "key-directory", &obj); @@ -1560,12 +1574,20 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, INSIST(result == ISC_R_SUCCESS && obj != NULL); dns_zone_setoption(zone, DNS_ZONEOPT_UPDATECHECKKSK, cfg_obj_asboolean(obj)); + /* + * This setting will be ignored if dnssec-policy is used. + * named-checkconf will error if both are configured. + */ obj = NULL; result = named_config_get(maps, "dnssec-dnskey-kskonly", &obj); INSIST(result == ISC_R_SUCCESS && obj != NULL); dns_zone_setoption(zone, DNS_ZONEOPT_DNSKEYKSKONLY, cfg_obj_asboolean(obj)); + /* + * This setting will be ignored if dnssec-policy is used. + * named-checkconf will error if both are configured. + */ obj = NULL; result = named_config_get(maps, "dnssec-loadkeys-interval", @@ -1576,7 +1598,11 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, obj = NULL; result = cfg_map_get(zoptions, "auto-dnssec", &obj); - if (result == ISC_R_SUCCESS) { + if (dns_zone_getkasp(zone) != NULL) { + dns_zone_setkeyopt(zone, DNS_ZONEKEY_ALLOW, true); + dns_zone_setkeyopt(zone, DNS_ZONEKEY_CREATE, true); + dns_zone_setkeyopt(zone, DNS_ZONEKEY_MAINTAIN, true); + } else if (result == ISC_R_SUCCESS) { const char *arg = cfg_obj_asstring(obj); if (strcasecmp(arg, "allow") == 0) { allow = true; @@ -1589,6 +1615,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, ISC_UNREACHABLE(); } dns_zone_setkeyopt(zone, DNS_ZONEKEY_ALLOW, allow); + dns_zone_setkeyopt(zone, DNS_ZONEKEY_CREATE, false); dns_zone_setkeyopt(zone, DNS_ZONEKEY_MAINTAIN, maint); } } From fcf14b2b47a95ca9b0ec33554a084c5c89912946 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 17 Oct 2019 11:51:58 +0200 Subject: [PATCH 26/43] DNSSEC hints use dst_key functions and key states Update dns_dnssec_get_hints and dns_dnssec_keyactive to use dst_key functions and thus if dnssec-policy/KASP is used the key states are being considered. Add a new variable to 'struct dns_dnsseckey' to signal whether this key is a zone-signing key (it is no longer true that ksk == !zsk). Also introduce a hint for revoke. Update 'dns_dnssec_findzonekeys' and 'dns_dnssec_findmatchingkeys' to also read the key state file, if available. Remove 'allzsk' from 'dns_dnssec_updatekeys' as this was only a hint for logging. Also make get_hints() (now dns_dnssec_get_hints()) public so that we can use it in the key manager. --- bin/dnssec/dnssec-signzone.c | 2 +- lib/dns/dnssec.c | 212 +++++++++++++++-------------------- lib/dns/include/dns/dnssec.h | 19 +++- lib/dns/win32/libdns.def.in | 1 + lib/dns/zone.c | 3 +- 5 files changed, 105 insertions(+), 132 deletions(-) diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c index 47b7c257c0..5703dde634 100644 --- a/bin/dnssec/dnssec-signzone.c +++ b/bin/dnssec/dnssec-signzone.c @@ -2717,7 +2717,7 @@ build_final_keylist(void) { * Update keylist with information from from the key repository. */ dns_dnssec_updatekeys(&keylist, &matchkeys, NULL, gorigin, keyttl, - &diff, ignore_kskflag, mctx, report); + &diff, mctx, report); /* * Update keylist with sync records. diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c index 1b18480fd5..ac70dc9703 100644 --- a/lib/dns/dnssec.c +++ b/lib/dns/dnssec.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -582,10 +583,8 @@ cleanup_struct: bool dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) { isc_result_t result; - isc_stdtime_t publish, active, revoke, inactive, deltime; - bool pubset = false, actset = false; - bool revset = false, inactset = false; - bool delset = false; + isc_stdtime_t publish, active, revoke, remove; + bool hint_publish, hint_sign, hint_revoke, hint_remove; int major, minor; /* Is this an old-style key? */ @@ -594,40 +593,25 @@ dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) { /* * Smart signing started with key format 1.3; prior to that, all - * keys are assumed active + * keys are assumed active. */ if (major == 1 && minor <= 2) return (true); - result = dst_key_gettime(key, DST_TIME_PUBLISH, &publish); - if (result == ISC_R_SUCCESS) - pubset = true; + hint_publish = dst_key_is_published(key, now, &publish); + hint_sign = dst_key_is_signing(key, now, &active); + hint_revoke = dst_key_is_revoked(key, now, &revoke); + hint_remove = dst_key_is_removed(key, now, &remove); - result = dst_key_gettime(key, DST_TIME_ACTIVATE, &active); - if (result == ISC_R_SUCCESS) - actset = true; - - result = dst_key_gettime(key, DST_TIME_REVOKE, &revoke); - if (result == ISC_R_SUCCESS) - revset = true; - - result = dst_key_gettime(key, DST_TIME_INACTIVE, &inactive); - if (result == ISC_R_SUCCESS) - inactset = true; - - result = dst_key_gettime(key, DST_TIME_DELETE, &deltime); - if (result == ISC_R_SUCCESS) - delset = true; - - if ((inactset && inactive <= now) || (delset && deltime <= now)) + if (hint_remove) { return (false); - - if (revset && revoke <= now && pubset && publish <= now) + } + if (hint_publish && hint_revoke) { return (true); - - if (actset && active <= now) + } + if (hint_sign) { return (true); - + } return (false); } @@ -742,7 +726,8 @@ dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, result = dst_key_fromfile(dst_key_name(pubkey), dst_key_id(pubkey), dst_key_alg(pubkey), - DST_TYPE_PUBLIC|DST_TYPE_PRIVATE, + DST_TYPE_PUBLIC|DST_TYPE_PRIVATE| + DST_TYPE_STATE, directory, mctx, &keys[count]); @@ -761,7 +746,8 @@ dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, dst_key_id(pubkey), dst_key_alg(pubkey), DST_TYPE_PUBLIC| - DST_TYPE_PRIVATE, + DST_TYPE_PRIVATE| + DST_TYPE_STATE, directory, mctx, &keys[count]); if (result == ISC_R_SUCCESS && @@ -784,8 +770,9 @@ dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, result2 = dst_key_getfilename(dst_key_name(pubkey), dst_key_id(pubkey), dst_key_alg(pubkey), - (DST_TYPE_PUBLIC | - DST_TYPE_PRIVATE), + (DST_TYPE_PUBLIC| + DST_TYPE_PRIVATE| + DST_TYPE_STATE), directory, mctx, &buf); if (result2 != ISC_R_SUCCESS) { @@ -1219,6 +1206,7 @@ dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey, dk->force_sign = false; dk->hint_publish = false; dk->hint_sign = false; + dk->hint_revoke = false; dk->hint_remove = false; dk->first_sign = false; dk->is_active = false; @@ -1227,7 +1215,14 @@ dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey, dk->index = 0; /* KSK or ZSK? */ - dk->ksk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) != 0); + result = dst_key_getbool(dk->key, DST_BOOL_KSK, &dk->ksk); + if (result != ISC_R_SUCCESS) { + dk->ksk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) != 0); + } + result = dst_key_getbool(dk->key, DST_BOOL_ZSK, &dk->zsk); + if (result != ISC_R_SUCCESS) { + dk->zsk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) == 0); + } /* Is this an old-style key? */ result = dst_key_getprivateformat(dk->key, &major, &minor); @@ -1253,80 +1248,47 @@ dns_dnsseckey_destroy(isc_mem_t *mctx, dns_dnsseckey_t **dkp) { *dkp = NULL; } -static void -get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) { - isc_result_t result; - isc_stdtime_t publish, active, revoke, inactive, deltime; - bool pubset = false, actset = false; - bool revset = false, inactset = false; - bool delset = false; +void +dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) { + isc_stdtime_t publish = 0, active = 0, revoke = 0, remove = 0; REQUIRE(key != NULL && key->key != NULL); - result = dst_key_gettime(key->key, DST_TIME_PUBLISH, &publish); - if (result == ISC_R_SUCCESS) - pubset = true; + key->hint_publish = dst_key_is_published(key->key, now, &publish); + key->hint_sign = dst_key_is_signing(key->key, now, &active); + key->hint_revoke = dst_key_is_revoked(key->key, now, &revoke); + key->hint_remove = dst_key_is_removed(key->key, now, &remove); - result = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active); - if (result == ISC_R_SUCCESS) - actset = true; - - result = dst_key_gettime(key->key, DST_TIME_REVOKE, &revoke); - if (result == ISC_R_SUCCESS) - revset = true; - - result = dst_key_gettime(key->key, DST_TIME_INACTIVE, &inactive); - if (result == ISC_R_SUCCESS) - inactset = true; - - result = dst_key_gettime(key->key, DST_TIME_DELETE, &deltime); - if (result == ISC_R_SUCCESS) - delset = true; - - /* Metadata says publish (but possibly not activate) */ - if (pubset && publish <= now) + /* + * Activation date is set (maybe in the future), but publication date + * isn't. Most likely the user wants to publish now and activate later. + * Most likely because this is true for most rollovers, except for: + * 1. The unpopular ZSK Double-RRSIG method. + * 2. When introducing a new algorithm. + * These two cases are rare enough that we will set hint_publish + * anyway when hint_sign is set, because BIND 9 natively does not + * support the ZSK Double-RRSIG method, and when introducing a new + * algorihtm, we strive to publish its signatures and DNSKEY records + * at the same time. + */ + if (key->hint_sign && publish == 0) { key->hint_publish = true; - - /* Metadata says activate (so we must also publish) */ - if (actset && active <= now) { - key->hint_sign = true; - - /* Only publish if publish time has already passed. */ - if (pubset && publish <= now) - key->hint_publish = true; } /* - * Activation date is set (maybe in the future), but - * publication date isn't. Most likely the user wants to - * publish now and activate later. + * If activation date is in the future, make note of how far off. */ - if (actset && !pubset) - key->hint_publish = true; - - /* - * If activation date is in the future, make note of how far off - */ - if (key->hint_publish && actset && active > now) { + if (key->hint_publish && active > now) { key->prepublish = active - now; } /* - * Key has been marked inactive: we can continue publishing, - * but don't sign. - */ - if (key->hint_publish && inactset && inactive <= now) { - key->hint_sign = false; - } - - /* - * Metadata says revoke. If the key is published, - * we *have to* sign with it per RFC5011--even if it was - * not active before. + * Metadata says revoke. If the key is published, we *have to* sign + * with it per RFC5011 -- even if it was not active before. * * If it hasn't already been done, we should also revoke it now. */ - if (key->hint_publish && (revset && revoke <= now)) { + if (key->hint_publish && key->hint_revoke) { uint32_t flags; key->hint_sign = true; flags = dst_key_flags(key->key); @@ -1337,17 +1299,17 @@ get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) { } /* - * Metadata says delete, so don't publish this key or sign with it. + * Metadata says delete, so don't publish this key or sign with it + * (note that signatures of a removed key may still be reused). */ - if (delset && deltime <= now) { + if (key->hint_remove) { key->hint_publish = false; key->hint_sign = false; - key->hint_remove = true; } } /*% - * Get a list of DNSSEC keys from the key repository + * Get a list of DNSSEC keys from the key repository. */ isc_result_t dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, @@ -1416,10 +1378,10 @@ dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, continue; dstkey = NULL; - result = dst_key_fromnamedfile(dir.entry.name, - directory, + result = dst_key_fromnamedfile(dir.entry.name, directory, DST_TYPE_PUBLIC | - DST_TYPE_PRIVATE, + DST_TYPE_PRIVATE | + DST_TYPE_STATE, mctx, &dstkey); switch (alg) { @@ -1447,7 +1409,7 @@ dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, RETERR(dns_dnsseckey_create(mctx, &dstkey, &key)); key->source = dns_keysource_repository; - get_hints(key, now); + dns_dnssec_get_hints(key, now); if (key->legacy) { dns_dnsseckey_destroy(mctx, &key); @@ -1638,7 +1600,8 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, result = dst_key_fromfile(dst_key_name(pubkey), dst_key_id(pubkey), dst_key_alg(pubkey), - DST_TYPE_PUBLIC|DST_TYPE_PRIVATE, + (DST_TYPE_PUBLIC|DST_TYPE_PRIVATE| + DST_TYPE_STATE), directory, mctx, &privkey); /* @@ -1655,8 +1618,9 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, result = dst_key_fromfile(dst_key_name(pubkey), dst_key_id(pubkey), dst_key_alg(pubkey), - DST_TYPE_PUBLIC| - DST_TYPE_PRIVATE, + (DST_TYPE_PUBLIC| + DST_TYPE_PRIVATE| + DST_TYPE_STATE), directory, mctx, &privkey); if (result == ISC_R_SUCCESS && @@ -1680,7 +1644,8 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, dst_key_id(pubkey), dst_key_alg(pubkey), (DST_TYPE_PUBLIC | - DST_TYPE_PRIVATE), + DST_TYPE_PRIVATE| + DST_TYPE_STATE), directory, mctx, &buf); if (result2 != ISC_R_SUCCESS) { @@ -1800,7 +1765,7 @@ delrdata(dns_rdata_t *rdata, dns_diff_t *diff, const dns_name_t *origin, static isc_result_t publish_key(dns_diff_t *diff, dns_dnsseckey_t *key, const dns_name_t *origin, - dns_ttl_t ttl, isc_mem_t *mctx, bool allzsk, + dns_ttl_t ttl, isc_mem_t *mctx, void (*report)(const char *, ...)) { isc_result_t result; @@ -1813,7 +1778,7 @@ publish_key(dns_diff_t *diff, dns_dnsseckey_t *key, const dns_name_t *origin, dst_key_format(key->key, keystr, sizeof(keystr)); report("Fetching %s (%s) from key %s.\n", - keystr, key->ksk ? (allzsk ? "KSK/ZSK" : "KSK") : "ZSK", + keystr, key->ksk ? (key->zsk ? "CSK" : "KSK") : "ZSK", key->source == dns_keysource_user ? "file" : "repository"); if (key->prepublish && ttl > key->prepublish) { @@ -2023,8 +1988,7 @@ dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, isc_result_t dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, dns_dnsseckeylist_t *removed, const dns_name_t *origin, - dns_ttl_t hint_ttl, dns_diff_t *diff, - bool allzsk, isc_mem_t *mctx, + dns_ttl_t hint_ttl, dns_diff_t *diff, isc_mem_t *mctx, void (*report)(const char *, ...)) { isc_result_t result; @@ -2047,8 +2011,8 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, if (key->source == dns_keysource_user && (key->hint_publish || key->force_publish)) { - RETERR(publish_key(diff, key, origin, ttl, - mctx, allzsk, report)); + RETERR(publish_key(diff, key, origin, ttl, mctx, + report)); } if (key->source == dns_keysource_zoneapex) { ttl = dst_key_getttl(key->key); @@ -2125,14 +2089,14 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, (key1->hint_publish || key1->force_publish)) { RETERR(publish_key(diff, key1, origin, ttl, - mctx, allzsk, report)); + mctx, report)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, "DNSKEY %s (%s) is now published", keystr1, key1->ksk ? - (allzsk ? "KSK/ZSK" : "KSK") : + (key1->zsk ? "CSK" : "KSK") : "ZSK"); if (key1->hint_sign || key1->force_sign) { key1->first_sign = true; @@ -2143,7 +2107,7 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, "DNSKEY %s (%s) is now " "active", keystr1, key1->ksk ? - (allzsk ? "KSK/ZSK" : + (key1->zsk ? "CSK" : "KSK") : "ZSK"); } } @@ -2167,8 +2131,8 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, "DNSKEY %s (%s) is now deleted", - keystr2, key2->ksk ? (allzsk ? - "KSK/ZSK" : "KSK") : "ZSK"); + keystr2, key2->ksk ? (key2->zsk ? + "CSK" : "KSK") : "ZSK"); } else { dns_dnsseckey_destroy(mctx, &key2); } @@ -2192,15 +2156,15 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, ISC_LOG_INFO, "DNSKEY %s (%s) is now revoked; " "new ID is %05d", - keystr2, key2->ksk ? (allzsk ? - "KSK/ZSK" : "KSK") : "ZSK", + keystr2, key2->ksk ? (key2->zsk ? + "CSK" : "KSK") : "ZSK", dst_key_id(key1->key)); } else { dns_dnsseckey_destroy(mctx, &key2); } - RETERR(publish_key(diff, key1, origin, ttl, - mctx, allzsk, report)); + RETERR(publish_key(diff, key1, origin, ttl, mctx, + report)); ISC_LIST_UNLINK(*newkeys, key1, link); ISC_LIST_APPEND(*keys, key1, link); @@ -2224,8 +2188,8 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, "DNSKEY %s (%s) is now active", - keystr1, key1->ksk ? (allzsk ? - "KSK/ZSK" : "KSK") : "ZSK"); + keystr1, key1->ksk ? (key1->zsk ? + "CSK" : "KSK") : "ZSK"); } else if (key2->is_active && !key1->hint_sign && !key1->force_sign) { @@ -2234,8 +2198,8 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, "DNSKEY %s (%s) is now inactive", - keystr1, key1->ksk ? (allzsk ? - "KSK/ZSK" : "KSK") : "ZSK"); + keystr1, key1->ksk ? (key1->zsk ? + "CSK" : "KSK") : "ZSK"); } key2->hint_sign = key1->hint_sign; diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h index a03b3e3af5..767e5e9b23 100644 --- a/lib/dns/include/dns/dnssec.h +++ b/lib/dns/include/dns/dnssec.h @@ -53,12 +53,14 @@ struct dns_dnsseckey { bool force_publish; /*% publish regardless of metadata */ bool hint_sign; /*% metadata says to sign with this key */ bool force_sign; /*% sign with key regardless of metadata */ + bool hint_revoke; /*% metadata says revoke key */ bool hint_remove; /*% metadata says *don't* publish */ bool is_active; /*% key is already active */ bool first_sign; /*% key is newly becoming active */ unsigned int prepublish; /*% how long until active? */ dns_keysource_t source; /*% how the key was found */ bool ksk; /*% this is a key-signing key */ + bool zsk; /*% this is a zone-signing key */ bool legacy; /*% this is old-style key with no metadata (possibly generated by an older version of BIND9) and @@ -267,6 +269,16 @@ dns_dnsseckey_destroy(isc_mem_t *mctx, dns_dnsseckey_t **dkp); *\li '*dkp' is NULL. */ +void +dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now); +/*%< + * Get hints on DNSSEC key whether this key can be published + * and/or is active. Timing metadata is compared to 'now'. + * + * Requires: + *\li 'key' is a pointer to a DNSSEC key and is not NULL. + */ + isc_result_t dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, isc_stdtime_t now, isc_mem_t *mctx, @@ -312,8 +324,8 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, isc_result_t dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, dns_dnsseckeylist_t *removed, const dns_name_t *origin, - dns_ttl_t hint_ttl, dns_diff_t *diff, bool allzsk, - isc_mem_t *mctx, void (*report)(const char *, ...)); + dns_ttl_t hint_ttl, dns_diff_t *diff, isc_mem_t *mctx, + void (*report)(const char *, ...)); /*%< * Update the list of keys in 'keys' with new key information in 'newkeys'. * @@ -328,9 +340,6 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, * copy the key into that list; otherwise destroy it. * - Otherwise, make sure keys has current metadata. * - * If 'allzsk' is true, we are allowing KSK-flagged keys to be used as - * ZSKs. - * * 'hint_ttl' is the TTL to use for the DNSKEY RRset if there is no * existing RRset, and if none of the keys to be added has a default TTL * (in which case we would use the shortest one). If the TTL is longer diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 950830951f..c127aba804 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -316,6 +316,7 @@ dns_dns64_next dns_dns64_unlink dns_dnssec_findmatchingkeys dns_dnssec_findzonekeys +dns_dnssec_get_hints dns_dnssec_keyactive dns_dnssec_keyfromrdata dns_dnssec_keylistfromrdataset diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 9d1486413c..d03d55abf0 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -18558,8 +18558,7 @@ zone_rekey(dns_zone_t *zone) { check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); result = dns_dnssec_updatekeys(&dnskeys, &keys, &rmkeys, &zone->origin, ttl, &diff, - !check_ksk, mctx, - dnssec_report); + mctx, dnssec_report); /* * Keys couldn't be updated for some reason; * try again later. From c125b721efe711c0b6d98eb27ebd9caaf4a42267 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 17 Oct 2019 11:57:20 +0200 Subject: [PATCH 27/43] 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. --- bin/named/server.c | 6 +- bin/rndc/rndc.docbook | 6 +- lib/dns/update.c | 56 +++++++++- lib/dns/zone.c | 253 ++++++++++++++++++++++++++++++++++-------- 4 files changed, 270 insertions(+), 51 deletions(-) diff --git a/bin/named/server.c b/bin/named/server.c index ca220ec62c..c788deb19e 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 c5c1c4e0a2..83b7eb291b 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 25827d4b54..4c621c404f 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 d03d55abf0..c88c1714ac 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; } From 7c783ab909880e770d5f8159722b098d575ce58b Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 17 Oct 2019 14:22:38 +0200 Subject: [PATCH 28/43] Refactor kasp system test A significant refactor of the kasp system test in an attempt to make the test script somewhat brief. When writing a test case, you can/should use the functions 'zone_properties', 'key_properties', and 'key_timings' to set the expected values when checking a key with 'check_key'. All these four functions can be used to set environment variables that come in handy when testing output. --- bin/tests/system/kasp/clean.sh | 3 +- bin/tests/system/kasp/tests.sh | 597 ++++++++++++++++++++++++++------- 2 files changed, 480 insertions(+), 120 deletions(-) diff --git a/bin/tests/system/kasp/clean.sh b/bin/tests/system/kasp/clean.sh index 491354f51a..6c3f01d9ec 100644 --- a/bin/tests/system/kasp/clean.sh +++ b/bin/tests/system/kasp/clean.sh @@ -13,5 +13,4 @@ set -e rm -f ./keygen.* rm -f ./K*.private ./K*.key ./K*.state ./K*.cmp -rm -f ./keys/K* -rmdir ./keys/ +rm -rf ./keys/ diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index d7353ab9fa..b6e0e2ca47 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -13,134 +13,479 @@ SYSTEMTESTTOP=.. . "$SYSTEMTESTTOP/conf.sh" -set -e - status=0 n=0 -################################################################################ -# Utilities # -################################################################################ +############################################################################### +# Constants # +############################################################################### +DEFAULT_TTL=300 + +############################################################################### +# Key properties # +############################################################################### +ID=0 +EXPECT=1 +ROLE=2 +KSK=3 +ZSK=4 +LIFETIME=5 +ALG_NUM=6 +ALG_STR=7 +ALG_LEN=8 +PUBLISHED=9 +ACTIVE=10 +RETIRED=11 +REVOKED=12 +REMOVED=13 +GOAL=14 +STATE_DNSKEY=15 +STATE_ZRRSIG=16 +STATE_KRRSIG=17 +STATE_DS=18 +EXPECT_RRSIG=19 + +# Clear key state. +# +# This will update either the KEY1, KEY2, or KEY3 array. +key_clear() { + _key=( [$ID]="no" [$EXPECT]="no" \ + [$ROLE]="none" [$KSK]="no" [$ZSK]="no" \ + [$LIFETIME]="0" [$ALG_NUM]="0" \ + [$ALG_STR]="none" [$ALG_LEN]="0" \ + [$PUBLISHED]="none" [$ACTIVE]="none" \ + [$RETIRED]="none" [$REVOKED]="none" \ + [$REMOVED]="none" \ + [$GOAL]="none" [$STATE_DNSKEY]="none" \ + [$STATE_KRRSIG]="none" [$STATE_ZRRSIG]="none" \ + [$STATE_DS]="none" [$EXPECT_RRSIG]="no") + + if [ $1 == "KEY1" ]; then + KEY1=(${_key[*]}) + elif [ $1 == "KEY2" ]; then + KEY2=(${_key[*]}) + elif [ $1 == "KEY3" ]; then + KEY3=(${_key[*]}) + fi +} + +# Start clear. +key_clear "KEY1" +key_clear "KEY2" +key_clear "KEY3" + +############################################################################### +# Utilities # +############################################################################### + +# Call dig with default options. +dig_with_opts() { + "$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" "$@" +} + +# RNDC. +rndccmd() { + "$RNDC" -c "$SYSTEMTESTTOP/common/rndc.conf" -p "$CONTROLPORT" -s "$@" +} + +# Print IDs of keys used for generating RRSIG records for RRsets of type $1 +# found in dig output file $2. +get_keys_which_signed() { + _qtype=$1 + _output=$2 + # The key ID is the 11th column of the RRSIG record line. + awk -v qt="$_qtype" '$4 == "RRSIG" && $5 == qt {print $11}' < "$_output" +} # Get the key ids from key files for zone $2 in directory $1 # that matches algorithm $3. get_keyids() { - dir=$1 - zone=$2 - algorithm=$(printf "%03d" $3) - start="${dir}/K${zone}.+${algorithm}+" - end=".key" + _dir=$1 + _zone=$2 + _algorithm=$(printf "%03d" $3) + _start="${_dir}/K${_zone}.+${_algorithm}+" + _end=".key" - ls ${start}*${end} | sed "s/$dir\/K${zone}.+${algorithm}+\([0-9]\{5\}\)${end}/\1/" + ls ${_start}*${_end} | sed "s/$_dir\/K${_zone}.+${_algorithm}+\([0-9]\{5\}\)${_end}/\1/" } # By default log errors and don't quit immediately. _log=1 -_continue=1 log_error() { test $_log -eq 1 && echo_i "error: $1" ret=$((ret+1)) - - test $_continue -eq 1 || exit 1 } -# Check the created key in directory $1 for zone $2. -# $3: key role. Must be one of "csk", "ksk", or "zsk". -# $4: key identifier (zero padded) -# $5: algorithm number -# $6: algorithm (string format) -# $7: algorithm length -# $8: dnskey ttl -# $9: key lifetime -check_created_key() { - dir=$1 - zone=$2 - role=$3 - key_idpad=$4 - key_id=$(echo $key_idpad | sed 's/^0*//') - alg_num=$5 - alg_numpad=$(printf "%03d" $alg_num) - alg_string=$6 - length=$7 - dnskey_ttl=$8 - lifetime=$9 +# Set zone properties for testing keys. +# $1: Key directory +# $2: Zone name +# $3: Policy name +# $4: DNSKEY TTL +# $5: Number of keys +# +# This will set the following environment variables for testing: +# DIR, ZONE, POLICY, DNSKEY_TTL, NUM_KEYS +zone_properties() { + DIR=$1 + ZONE=$2 + POLICY=$3 + DNSKEY_TTL=$4 + NUM_KEYS=$5 +} - ksk="no" - zsk="no" - if [ "$role" == "ksk" ]; then - role2="key-signing" - ksk="yes" - flags="257" - elif [ "$role" == "zsk" ]; then - role2="zone-signing" - zsk="yes" - flags="256" - elif [ "$role" == "csk" ]; then - role2="key-signing" - zsk="yes" - ksk="yes" - flags="257" +# Set key properties for testing keys. +# $1: Key to update +# $2: Role +# $3: Lifetime +# $4: Algorithm (number) +# $5: Algorithm (string-format) +# $6: Algorithm length +# $7: Is signing +# +# This will update either the KEY1, KEY2 or KEY3 array. +key_properties() { + if [ $1 == "KEY1" ]; then + KEY1[$EXPECT]="yes" + KEY1[$ROLE]=$2 + KEY1[$KSK]="no" + KEY1[$ZSK]="no" + test $2 == "ksk" && KEY1[$KSK]="yes" + test $2 == "zsk" && KEY1[$ZSK]="yes" + test $2 == "csk" && KEY1[$KSK]="yes" + test $2 == "csk" && KEY1[$ZSK]="yes" + KEY1[$LIFETIME]=$3 + KEY1[$ALG_NUM]=$4 + KEY1[$ALG_STR]=$5 + KEY1[$ALG_LEN]=$6 + KEY1[$EXPECT_RRSIG]=$7 + elif [ $1 == "KEY2" ]; then + KEY2[$EXPECT]="yes" + KEY2[$ROLE]=$2 + KEY2[$KSK]="no" + KEY2[$ZSK]="no" + test $2 == "ksk" && KEY2[$KSK]="yes" + test $2 == "zsk" && KEY2[$ZSK]="yes" + test $2 == "csk" && KEY2[$KSK]="yes" + test $2 == "csk" && KEY2[$ZSK]="yes" + KEY2[$LIFETIME]=$3 + KEY2[$ALG_NUM]=$4 + KEY2[$ALG_STR]=$5 + KEY2[$ALG_LEN]=$6 + KEY2[$EXPECT_RRSIG]=$7 + elif [ $1 == "KEY3" ]; then + KEY3[$EXPECT]="yes" + KEY3[$ROLE]=$2 + KEY3[$KSK]="no" + KEY3[$ZSK]="no" + test $2 == "ksk" && KEY3[$KSK]="yes" + test $2 == "zsk" && KEY3[$ZSK]="yes" + test $2 == "csk" && KEY3[$KSK]="yes" + test $2 == "csk" && KEY3[$ZSK]="yes" + KEY3[$LIFETIME]=$3 + KEY3[$ALG_NUM]=$4 + KEY3[$ALG_STR]=$5 + KEY3[$ALG_LEN]=$6 + KEY3[$EXPECT_RRSIG]=$7 + fi +} + +# Set key timing metadata. Set to "none" to unset. +# These times are hard to test, so it is just an indication that we expect the +# respective timing metadata in the key files. +# $1: Key to update +# $2: Published +# $3: Active +# $4: Retired +# $5: Revoked +# $6: Removed +# +# This will update either the KEY1, KEY2 or KEY3 array. +key_timings() { + if [ $1 == "KEY1" ]; then + KEY1[$EXPECT]="yes" + KEY1[$PUBLISHED]=$2 + KEY1[$ACTIVE]=$3 + KEY1[$RETIRED]=$4 + KEY1[$REVOKED]=$5 + KEY1[$REMOVED]=$6 + elif [ $1 == "KEY2" ]; then + KEY2[$EXPECT]="yes" + KEY2[$PUBLISHED]=$2 + KEY2[$ACTIVE]=$3 + KEY2[$RETIRED]=$4 + KEY2[$REVOKED]=$5 + KEY2[$REMOVED]=$6 + elif [ $1 == "KEY3" ]; then + KEY3[$EXPECT]="yes" + KEY3[$PUBLISHED]=$2 + KEY3[$ACTIVE]=$3 + KEY3[$RETIRED]=$4 + KEY3[$REVOKED]=$5 + KEY3[$REMOVED]=$6 + fi +} + +# Set key state metadata. Set to "none" to unset. +# $1: Key to update +# $2: Goal state +# $3: DNSKEY state +# $4: RRSIG state (zsk) +# $5: RRSIG state (ksk) +# $6: DS state +# +# This will update either the KEY1, KEY2, OR KEY3 array. +key_states() { + if [ $1 == "KEY1" ]; then + KEY1[$EXPECT]="yes" + KEY1[$GOAL]=$2 + KEY1[$STATE_DNSKEY]=$3 + KEY1[$STATE_ZRRSIG]=$4 + KEY1[$STATE_KRRSIG]=$5 + KEY1[$STATE_DS]=$6 + elif [ $1 == "KEY2" ]; then + KEY2[$EXPECT]="yes" + KEY2[$GOAL]=$2 + KEY2[$STATE_DNSKEY]=$3 + KEY2[$STATE_ZRRSIG]=$4 + KEY2[$STATE_KRRSIG]=$5 + KEY2[$STATE_DS]=$6 + elif [ $1 == "KEY3" ]; then + KEY3[$EXPECT]="yes" + KEY3[$GOAL]=$2 + KEY3[$STATE_DNSKEY]=$3 + KEY3[$STATE_ZRRSIG]=$4 + KEY3[$STATE_KRRSIG]=$5 + KEY3[$STATE_DS]=$6 + fi +} + +# Check the key $1 with id $2. +# This requires environment variables to be set with 'zone_properties', +# 'key_properties', 'key_timings', and 'key_states'. +# +# This will set the following environment variables for testing: +# BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" +# KEY_FILE="${BASE_FILE}.key" +# PRIVATE_FILE="${BASE_FILE}.private" +# STATE_FILE="${BASE_FILE}.state" +# KEY_ID=$(echo $1 | sed 's/^0*//') +check_key() { + if [ $1 == "KEY1" ]; then + _key=(${KEY1[*]}) + elif [ $1 == "KEY2" ]; then + _key=(${KEY2[*]}) + elif [ $1 == "KEY3" ]; then + _key=(${KEY3[*]}) fi - KEY_FILE="${dir}/K${zone}.+${alg_numpad}+${key_idpad}.key" - PRIVATE_FILE="${dir}/K${zone}.+${alg_numpad}+${key_idpad}.private" - STATE_FILE="${dir}/K${zone}.+${alg_numpad}+${key_idpad}.state" + _dir=$DIR + _zone=$ZONE + _role="${_key[$ROLE]}" + _key_idpad=$2 + _key_id=$(echo $_key_idpad | sed 's/^0*//') + _alg_num="${_key[$ALG_NUM]}" + _alg_numpad=$(printf "%03d" $_alg_num) + _alg_string="${_key[$ALG_STR]}" + _length="${_key[$ALG_LEN]}" + _dnskey_ttl=$DNSKEY_TTL + _lifetime="${_key[$LIFETIME]}" - # Check the public key file. We expect three lines: a comment, - # a "Created" line, and the DNSKEY record. - lines=$(cat $KEY_FILE | wc -l) - test "$lines" -eq 3 || log_error "bad public keyfile $KEY_FILE" - grep "This is a ${role2} key, keyid ${key_id}, for ${zone}." $KEY_FILE > /dev/null || log_error "mismatch top comment in $KEY_FILE" - grep "; Created:" $KEY_FILE > /dev/null || log_error "mismatch created comment in $KEY_FILE" - grep "${zone}\. ${dnskey_ttl} IN DNSKEY ${flags} 3 ${alg_num}" $KEY_FILE > /dev/null || log_error "mismatch DNSKEY record in $KEY_FILE" + _published="${_key[$PUBLISHED]}" + _active="${_key[$ACTIVE]}" + _retired="${_key[$RETIRED]}" + _revoked="${_key[$REVOKED]}" + _removed="${_key[$REMOVED]}" + + _goal="${_key[$GOAL]}" + _state_dnskey="${_key[$STATE_DNSKEY]}" + _state_zrrsig="${_key[$STATE_ZRRSIG]}" + _state_krrsig="${_key[$STATE_KRRSIG]}" + _state_ds="${_key[$STATE_DS]}" + + _ksk="no" + _zsk="no" + if [ "$_role" == "ksk" ]; then + _role2="key-signing" + _ksk="yes" + _flags="257" + elif [ "$_role" == "zsk" ]; then + _role2="zone-signing" + _zsk="yes" + _flags="256" + elif [ "$_role" == "csk" ]; then + _role2="key-signing" + _zsk="yes" + _ksk="yes" + _flags="257" + fi + + BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" + KEY_FILE="${BASE_FILE}.key" + PRIVATE_FILE="${BASE_FILE}.private" + STATE_FILE="${BASE_FILE}.state" + KEY_ID="${_key_id}" + + test $_log -eq 1 && echo_i "check key $BASE_FILE" + + # Check the public key file. + grep "This is a ${_role2} key, keyid ${_key_id}, for ${_zone}." $KEY_FILE > /dev/null || log_error "mismatch top comment in $KEY_FILE" + grep "${_zone}\. ${_dnskey_ttl} IN DNSKEY ${_flags} 3 ${_alg_num}" $KEY_FILE > /dev/null || log_error "mismatch DNSKEY record in $KEY_FILE" # Now check the private key file. grep "Private-key-format: v1.3" $PRIVATE_FILE > /dev/null || log_error "mismatch private key format in $PRIVATE_FILE" - grep "Algorithm: ${alg_num} (${alg_string})" $PRIVATE_FILE > /dev/null || log_error "mismatch algorithm in $PRIVATE_FILE" + grep "Algorithm: ${_alg_num} (${_alg_string})" $PRIVATE_FILE > /dev/null || log_error "mismatch algorithm in $PRIVATE_FILE" + # Now check the key state file. + grep "This is the state of key ${_key_id}, for ${_zone}." $STATE_FILE > /dev/null || log_error "mismatch top comment in $STATE_FILE" + grep "Lifetime: ${_lifetime}" $STATE_FILE > /dev/null || log_error "mismatch lifetime in $STATE_FILE" + grep "Algorithm: ${_alg_num}" $STATE_FILE > /dev/null || log_error "mismatch algorithm in $STATE_FILE" + grep "Length: ${_length}" $STATE_FILE > /dev/null || log_error "mismatch length in $STATE_FILE" + grep "KSK: ${_ksk}" $STATE_FILE > /dev/null || log_error "mismatch ksk in $STATE_FILE" + grep "ZSK: ${_zsk}" $STATE_FILE > /dev/null || log_error "mismatch zsk in $STATE_FILE" + + # Check key states. + if [ "$_goal" == "none" ]; then + grep "GoalState: " $STATE_FILE > /dev/null && log_error "unexpected goal state in $STATE_FILE" + else + grep "GoalState: ${_goal}" $STATE_FILE > /dev/null || log_error "mismatch goal state in $STATE_FILE" + fi + + if [ "$_state_dnskey" == "none" ]; then + grep "DNSKEYState: " $STATE_FILE > /dev/null && log_error "unexpected dnskey state in $STATE_FILE" + grep "DNSKEYChange: " $STATE_FILE > /dev/null && log_error "unexpected dnskey change in $STATE_FILE" + else + grep "DNSKEYState: ${_state_dnskey}" $STATE_FILE > /dev/null || log_error "mismatch dnskey state in $STATE_FILE" + grep "DNSKEYChange: " $STATE_FILE > /dev/null || log_error "mismatch dnskey change in $STATE_FILE" + fi + + if [ "$_state_zrrsig" == "none" ]; then + grep "ZRRSIGState: " $STATE_FILE > /dev/null && log_error "unexpected zrrsig state in $STATE_FILE" + grep "ZRRSIGChange: " $STATE_FILE > /dev/null && log_error "unexpected zrrsig change in $STATE_FILE" + else + grep "ZRRSIGState: ${_state_zrrsig}" $STATE_FILE > /dev/null || log_error "mismatch zrrsig state in $STATE_FILE" + grep "ZRRSIGChange: " $STATE_FILE > /dev/null || log_error "mismatch zrrsig change in $STATE_FILE" + fi + + if [ "$_state_krrsig" == "none" ]; then + grep "KRRSIGState: " $STATE_FILE > /dev/null && log_error "unexpected krrsig state in $STATE_FILE" + grep "KRRSIGChange: " $STATE_FILE > /dev/null && log_error "unexpected krrsig change in $STATE_FILE" + else + grep "KRRSIGState: ${_state_krrsig}" $STATE_FILE > /dev/null || log_error "mismatch krrsig state in $STATE_FILE" + grep "KRRSIGChange: " $STATE_FILE > /dev/null || log_error "mismatch krrsig change in $STATE_FILE" + fi + + if [ "$_state_ds" == "none" ]; then + grep "DSState: " $STATE_FILE > /dev/null && log_error "unexpected ds state in $STATE_FILE" + grep "DSChange: " $STATE_FILE > /dev/null && log_error "unexpected ds change in $STATE_FILE" + else + grep "DSState: ${_state_ds}" $STATE_FILE > /dev/null || log_error "mismatch ds state in $STATE_FILE" + grep "DSChange: " $STATE_FILE > /dev/null || log_error "mismatch ds change in $STATE_FILE" + fi + + # Check timing metadata. + if [ "$_published" == "none" ]; then + grep "; Publish:" $KEY_FILE > /dev/null && log_error "unexpected publish comment in $KEY_FILE" + grep "Publish:" $PRIVATE_FILE > /dev/null && log_error "unexpected publish in $PRIVATE_FILE" + grep "Published: " $STATE_FILE > /dev/null && log_error "unexpected publish in $STATE_FILE" + else + grep "; Publish:" $KEY_FILE > /dev/null || log_error "mismatch publish comment in $KEY_FILE ($KEY_PUBLISHED)" + grep "Publish:" $PRIVATE_FILE > /dev/null || log_error "mismatch publish in $PRIVATE_FILE ($KEY_PUBLISHED)" + grep "Published:" $STATE_FILE > /dev/null || log_error "mismatch publish in $STATE_FILE ($KEY_PUBLISHED)" + fi + + if [ "$_active" == "none" ]; then + grep "; Activate:" $KEY_FILE > /dev/null && log_error "unexpected active comment in $KEY_FILE" + grep "Activate:" $PRIVATE_FILE > /dev/null && log_error "unexpected active in $PRIVATE_FILE" + grep "Active: " $STATE_FILE > /dev/null && log_error "unexpected active in $STATE_FILE" + else + grep "; Activate:" $KEY_FILE > /dev/null || log_error "mismatch active comment in $KEY_FILE" + grep "Activate:" $PRIVATE_FILE > /dev/null || log_error "mismatch active in $PRIVATE_FILE" + grep "Active: " $STATE_FILE > /dev/null || log_error "mismatch active in $STATE_FILE" + fi + + if [ "$_retired" == "none" ]; then + grep "; Inactive:" $KEY_FILE > /dev/null && log_error "unexpected retired comment in $KEY_FILE" + grep "Inactive:" $PRIVATE_FILE > /dev/null && log_error "unexpected retired in $PRIVATE_FILE" + grep "Retired: " $STATE_FILE > /dev/null && log_error "unexpected retired in $STATE_FILE" + else + grep "; Inactive:" $KEY_FILE > /dev/null || log_error "mismatch retired comment in $KEY_FILE" + grep "Inactive:" $PRIVATE_FILE > /dev/null || log_error "mismatch retired in $PRIVATE_FILE" + grep "Retired: " $STATE_FILE > /dev/null || log_error "mismatch retired in $STATE_FILE" + fi + + if [ "$_revoked" == "none" ]; then + grep "; Revoke:" $KEY_FILE > /dev/null && log_error "unexpected revoked comment in $KEY_FILE" + grep "Revoke:" $PRIVATE_FILE > /dev/null && log_error "unexpected revoked in $PRIVATE_FILE" + grep "Revoked: " $STATE_FILE > /dev/null && log_error "unexpected revoked in $STATE_FILE" + else + grep "; Revoke:" $KEY_FILE > /dev/null || log_error "mismatch revoked comment in $KEY_FILE" + grep "Revoke:" $PRIVATE_FILE > /dev/null || log_error "mismatch revoked in $PRIVATE_FILE" + grep "Revoked: " $STATE_FILE > /dev/null || log_error "mismatch revoked in $STATE_FILE" + fi + + if [ "$_removed" == "none" ]; then + grep "; Delete:" $KEY_FILE > /dev/null && log_error "unexpected removed comment in $KEY_FILE" + grep "Delete:" $PRIVATE_FILE > /dev/null && log_error "unexpected removed in $PRIVATE_FILE" + grep "Removed: " $STATE_FILE > /dev/null && log_error "unexpected removed in $STATE_FILE" + else + grep "; Delete:" $KEY_FILE > /dev/null || log_error "mismatch removed comment in $KEY_FILE" + grep "Delete:" $PRIVATE_FILE > /dev/null || log_error "mismatch removed in $PRIVATE_FILE" + grep "Removed: " $STATE_FILE > /dev/null || log_error "mismatch removed in $STATE_FILE" + fi + + grep "; Created:" $KEY_FILE > /dev/null || log_error "mismatch created comment in $KEY_FILE" grep "Created:" $PRIVATE_FILE > /dev/null || log_error "mismatch created in $PRIVATE_FILE" - # Now check the key state file. There should be seven lines: - # a top comment, "Generated", "Lifetime", "Algorithm", "Length", - # "KSK", and "ZSK". - lines=$(cat $STATE_FILE | wc -l) - test "$lines" -eq 7 || log_error "bad state keyfile $STATE_FILE" - grep "This is the state of key ${key_id}, for ${zone}." $STATE_FILE > /dev/null || log_error "mismatch top comment in $STATE_FILE" - # XXX: Could check if generated is ~now. grep "Generated: " $STATE_FILE > /dev/null || log_error "mismatch generated in $STATE_FILE" - grep "Lifetime: ${lifetime}" $STATE_FILE > /dev/null || log_error "mismatch lifetime in $STATE_FILE" - grep "Algorithm: ${alg_num}" $STATE_FILE > /dev/null || log_error "mismatch algorithm in $STATE_FILE" - grep "Length: ${length}" $STATE_FILE > /dev/null || log_error "mismatch length in $STATE_FILE" - grep "KSK: ${ksk}" $STATE_FILE > /dev/null || log_error "mismatch ksk in $STATE_FILE" - grep "ZSK: ${zsk}" $STATE_FILE > /dev/null || log_error "mismatch zsk in $STATE_FILE" } -################################################################################ -# Tests # -################################################################################ +############################################################################### +# Tests # +############################################################################### # # dnssec-keygen # +zone_properties "keys" "kasp" "kasp" "200" + n=$((n+1)) echo_i "check that 'dnssec-keygen -k' (configured policy) creates valid files ($n)" ret=0 -$KEYGEN -K keys -k kasp -l kasp.conf kasp > keygen.out.kasp.test$n 2>/dev/null || ret=1 -lines=$(cat keygen.out.kasp.test$n | wc -l) -test "$lines" -eq 4 || log_error "wrong number of keys created for policy kasp" -# Check one algorithm. -KEY_ID=$(get_keyids "keys" "kasp" "13") -echo_i "check key $KEY_ID..." -check_created_key "keys" "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "200" "31536000" +$KEYGEN -K keys -k $POLICY -l kasp.conf $ZONE > keygen.out.$POLICY.test$n 2>/dev/null || ret=1 +lines=$(cat keygen.out.$POLICY.test$n | wc -l) +test "$lines" -eq 4 || log_error "wrong number of keys created for policy kasp: $lines" # Temporarily don't log errors because we are searching multiple files. _log=0 +# Check one algorithm. +key_properties "KEY1" "csk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" +key_timings "KEY1" "none" "none" "none" "none" "none" +key_states "KEY1" "none" "none" "none" "none" "none" +id=$(get_keyids $DIR $ZONE "${KEY1[$ALG_NUM]}") +check_key "KEY1" $id +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) # Check the other algorithm. -KEY_IDS=$(get_keyids "keys" "kasp" "8") -for KEY_ID in $KEY_IDS; do - echo_i "check key $KEY_ID..." +key_properties "KEY1" "ksk" "31536000" "8" "RSASHA256" "2048" "yes" +key_timings "KEY1" "none" "none" "none" "none" "none" +key_states "KEY1" "none" "none" "none" "none" "none" + +key_properties "KEY2" "zsk" "2592000" "8" "RSASHA256" "1024" "yes" +key_timings "KEY2" "none" "none" "none" "none" "none" +key_states "KEY2" "none" "none" "none" "none" "none" + +key_properties "KEY3" "zsk" "16070400" "8" "RSASHA256" "2000" "yes" +key_timings "KEY3" "none" "none" "none" "none" "none" +key_states "KEY3" "none" "none" "none" "none" "none" + +ids=$(get_keyids $DIR $ZONE "${KEY1[$ALG_NUM]}") +for id in $ids; do # There are three key files with the same algorithm. # Check them until a match is found. - ret=0 && check_created_key "keys" "kasp" "ksk" $KEY_ID "8" "RSASHA256" "2048" "200" "31536000" - test "$ret" -gt 0 && ret=0 && check_created_key "keys" "kasp" "zsk" $KEY_ID "8" "RSASHA256" "1024" "200" "2592000" - test "$ret" -gt 0 && ret=0 && check_created_key "keys" "kasp" "zsk" $KEY_ID "8" "RSASHA256" "2000" "200" "16070400" - # If ret is non-zero, non of the files matched. + ret=0 && check_key "KEY1" $id + test "$ret" -eq 0 && continue + + ret=0 && check_key "KEY2" $id + test "$ret" -eq 0 && continue + + ret=0 && check_key "KEY3" $id + # If ret is still non-zero, non of the files matched. test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) done @@ -150,41 +495,45 @@ _log=1 n=$((n+1)) echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" ret=0 -$KEYGEN -k _default kasp > keygen.out._default.test$n 2>/dev/null || ret=1 -lines=$(cat keygen.out._default.test$n | wc -l) -test "$lines" -eq 1 || log_error "wrong number of keys created for policy _default" -KEY_ID=$(get_keyids "." "kasp" "13") -echo_i "check key $KEY_ID..." -check_created_key "." "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "3600" "0" +zone_properties "." "kasp" "default" "3600" +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" +key_timings "KEY1" "none" "none" "none" "none" "none" +key_states "KEY1" "none" "none" "none" "none" "none" +$KEYGEN -k $POLICY $ZONE > keygen.out.$POLICY.test$n 2>/dev/null || ret=1 +lines=$(cat keygen.out.default.test$n | wc -l) +test "$lines" -eq 1 || log_error "wrong number of keys created for policy default: $lines" +id=$(get_keyids $DIR $ZONE "${KEY1[$ALG_NUM]}") +check_key "KEY1" $id test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) n=$((n+1)) echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" ret=0 -$KEYGEN -k default kasp > keygen.out.default.test$n 2>/dev/null || ret=1 -lines=$(cat keygen.out.default.test$n | wc -l) -test "$lines" -eq 1 || log_error "wrong number of keys created for policy default" -KEY_ID=$(get_keyids "." "kasp" "13") -echo_i "check key $KEY_ID..." -check_created_key "." "kasp" "csk" $KEY_ID "13" "ECDSAP256SHA256" "256" "3600" "0" +zone_properties "." "kasp" "default" "3600" +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" +key_timings "KEY1" "none" "none" "none" "none" "none" +key_states "KEY1" "none" "none" "none" "none" "none" +$KEYGEN -k $POLICY $ZONE > keygen.out.$POLICY.test$n 2>/dev/null || ret=1 +lines=$(cat keygen.out.$POLICY.test$n | wc -l) +test "$lines" -eq 1 || log_error "wrong number of keys created for policy default: $lines" +id=$(get_keyids $DIR $ZONE "${KEY1[$ALG_NUM]}") +check_key "KEY1" $id test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) # # dnssec-settime # -BASE_FILE="K${zone}.+${alg_numpad}+${key_idpad}" -KEY_FILE="${BASE_FILE}.key" -PRIVATE_FILE="${BASE_FILE}.private" -STATE_FILE="${BASE_FILE}.state" -CMP_FILE="${BASE_FILE}.cmp" +# These test builds upon the latest created key with dnssec-keygen and uses the +# environment variables BASE_FILE, KEY_FILE, PRIVATE_FILE and STATE_FILE. +CMP_FILE="${BASE_FILE}.cmp" n=$((n+1)) echo_i "check that 'dnssec-settime' by default does not edit key state file ($n)" ret=0 cp $STATE_FILE $CMP_FILE -$SETTIME -P +3600 $BASE_FILE >/dev/null || log_error "settime failed" +$SETTIME -P +3600 $BASE_FILE > /dev/null || log_error "settime failed" grep "; Publish: " $KEY_FILE > /dev/null || log_error "mismatch published in $KEY_FILE" grep "Publish: " $PRIVATE_FILE > /dev/null || log_error "mismatch published in $PRIVATE_FILE" $DIFF $CMP_FILE $STATE_FILE || log_error "unexpected file change in $STATE_FILE" @@ -192,28 +541,41 @@ test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) n=$((n+1)) -echo_i "check that 'dnssec-settime -s' also sets time metadata in key state file ($n)" +echo_i "check that 'dnssec-settime -s' also sets publish time metadata and states in key state file ($n)" ret=0 cp $STATE_FILE $CMP_FILE now=$(date +%Y%m%d%H%M%S) -$SETTIME -s -P $now $BASE_FILE >/dev/null || log_error "settime failed" -grep "; Publish: $now" $KEY_FILE > /dev/null || log_error "mismatch published in $KEY_FILE" -grep "Publish: $now" $PRIVATE_FILE > /dev/null || log_error "mismatch published in $PRIVATE_FILE" -grep "Published: $now" $STATE_FILE > /dev/null || log_error "mismatch published in $STATE_FILE" +$SETTIME -s -P $now -g "omnipresent" -k "rumoured" $now -z "omnipresent" $now -r "rumoured" $now -d "hidden" $now $BASE_FILE > /dev/null || log_error "settime failed" +key_timings "KEY1" "published" "none" "none" "none" "none" +key_states "KEY1" "omnipresent" "rumoured" "omnipresent" "rumoured" "hidden" +check_key "KEY1" $id test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) n=$((n+1)) -echo_i "check that 'dnssec-settime -s' also unsets time metadata in key state file ($n)" +echo_i "check that 'dnssec-settime -s' also unsets publish time metadata and states in key state file ($n)" ret=0 cp $STATE_FILE $CMP_FILE -$SETTIME -s -P none $BASE_FILE >/dev/null || log_error "settime failed" -grep "; Publish:" $KEY_FILE > /dev/null && log_error "unexpected published in $KEY_FILE" -grep "Publish:" $PRIVATE_FILE > /dev/null && log_error "unexpected published in $PRIVATE_FILE" -grep "Published:" $STATE_FILE > /dev/null && log_error "unexpected published in $STATE_FILE" +$SETTIME -s -P "none" -g "none" -k "none" $now -z "none" $now -r "none" $now -d "none" $now $BASE_FILE > /dev/null || log_error "settime failed" +key_timings "KEY1" "none" "none" "none" "none" "none" +key_states "KEY1" "none" "none" "none" "none" "none" +check_key "KEY1" $id test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) +n=$((n+1)) +echo_i "check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase) ($n)" +ret=0 +cp $STATE_FILE $CMP_FILE +now=$(date +%Y%m%d%H%M%S) +$SETTIME -s -A $now -g "HIDDEN" -k "UNRETENTIVE" $now -z "UNRETENTIVE" $now -r "OMNIPRESENT" $now -d "OMNIPRESENT" $now $BASE_FILE > /dev/null || log_error "settime failed" +key_timings "KEY1" "none" "active" "none" "none" "none" +key_states "KEY1" "hidden" "unretentive" "unretentive" "omnipresent" "omnipresent" +check_key "KEY1" $id +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + + # # named # @@ -221,4 +583,3 @@ status=$((status+ret)) echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 - From c9f1ec8380860a44e44d00d7d0afe6ec169f4d81 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 17 Oct 2019 15:27:06 +0200 Subject: [PATCH 29/43] Add kasp tests Add more tests for kasp: - Add tests for different algorithms. - Add a test to ensure that an edit in an unsigned zone is picked up and properly signed. - Add two tests that ensures that a zone gets signed when it is configured as so-called 'inline-signing'. In other words, a secondary zone that is configured with a 'dnssec-policy'. A zone that is transferred over AXFR or IXFR will get signed. - Add a test to ensure signatures are reused if they are still fresh enough. - Adds two more tests to verify that expired and unfresh signatures will be regenerated. - Add tests for various cases with keys already available in the key-directory. --- bin/tests/system/kasp/README | 11 + bin/tests/system/kasp/clean.sh | 9 + bin/tests/system/kasp/ns2/named.conf.in | 42 ++ .../system/kasp/ns2/secondary.kasp.db.in | 27 + .../system/kasp/ns2/secondary.kasp.db.in2 | 28 + bin/tests/system/kasp/ns2/setup.sh | 21 + bin/tests/system/kasp/ns3/named.conf.in | 122 +++++ bin/tests/system/kasp/ns3/policies/kasp.conf | 70 +++ bin/tests/system/kasp/ns3/setup.sh | 50 ++ bin/tests/system/kasp/ns3/template.db.in | 25 + bin/tests/system/kasp/ns3/template2.db.in | 25 + bin/tests/system/kasp/setup.sh | 14 + bin/tests/system/kasp/tests.sh | 499 ++++++++++++++++++ util/copyrights | 3 + 14 files changed, 946 insertions(+) create mode 100644 bin/tests/system/kasp/README create mode 100644 bin/tests/system/kasp/ns2/named.conf.in create mode 100644 bin/tests/system/kasp/ns2/secondary.kasp.db.in create mode 100644 bin/tests/system/kasp/ns2/secondary.kasp.db.in2 create mode 100644 bin/tests/system/kasp/ns2/setup.sh create mode 100644 bin/tests/system/kasp/ns3/named.conf.in create mode 100644 bin/tests/system/kasp/ns3/policies/kasp.conf create mode 100644 bin/tests/system/kasp/ns3/setup.sh create mode 100644 bin/tests/system/kasp/ns3/template.db.in create mode 100644 bin/tests/system/kasp/ns3/template2.db.in diff --git a/bin/tests/system/kasp/README b/bin/tests/system/kasp/README new file mode 100644 index 0000000000..d543c1a779 --- /dev/null +++ b/bin/tests/system/kasp/README @@ -0,0 +1,11 @@ +Copyright (C) Internet Systems Consortium, Inc. ("ISC") + +See COPYRIGHT in the source root or http://isc.org/copyright.html for terms. + +The test setup for the KASP tests. + +ns1 is reserved for the root server. + +ns2 is running primary service for ns3. + +ns3 is an authoritative server for the various test domains. diff --git a/bin/tests/system/kasp/clean.sh b/bin/tests/system/kasp/clean.sh index 6c3f01d9ec..c9ef776eb6 100644 --- a/bin/tests/system/kasp/clean.sh +++ b/bin/tests/system/kasp/clean.sh @@ -14,3 +14,12 @@ set -e rm -f ./keygen.* rm -f ./K*.private ./K*.key ./K*.state ./K*.cmp rm -rf ./keys/ +rm -f dig.out* rrsig.out.* keyevent.out.* +rm -f ns*/named.conf ns*/named.memstats ns*/named.run* +rm -f ns*/*.jnl ns*/*.jbk +rm -f ns*/K*.private ns*/K*.key ns*/K*.state +rm -f ns*/dsset-* ns*/*.db ns*/*.db.signed +rm -f ns*/keygen.out.* ns*/settime.out.* ns*/signer.out.* +rm -f ns*/managed-keys.bind +# NS3 specific +rm -f ns3/zones ns3/*.db.infile diff --git a/bin/tests/system/kasp/ns2/named.conf.in b/bin/tests/system/kasp/ns2/named.conf.in new file mode 100644 index 0000000000..640def73b3 --- /dev/null +++ b/bin/tests/system/kasp/ns2/named.conf.in @@ -0,0 +1,42 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS2 + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +/* Primary service for ns3 */ + +zone "secondary.kasp" { + type master; + file "secondary.kasp.db"; + allow-transfer { 10.53.0.3; }; + notify yes; +}; diff --git a/bin/tests/system/kasp/ns2/secondary.kasp.db.in b/bin/tests/system/kasp/ns2/secondary.kasp.db.in new file mode 100644 index 0000000000..12c678e8b3 --- /dev/null +++ b/bin/tests/system/kasp/ns2/secondary.kasp.db.in @@ -0,0 +1,27 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA secondary.kasp. hostmaster.kasp. ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 + NS ns3 +ns2 A 10.53.0.2 +ns3 A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 + diff --git a/bin/tests/system/kasp/ns2/secondary.kasp.db.in2 b/bin/tests/system/kasp/ns2/secondary.kasp.db.in2 new file mode 100644 index 0000000000..6fb00a9c30 --- /dev/null +++ b/bin/tests/system/kasp/ns2/secondary.kasp.db.in2 @@ -0,0 +1,28 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA secondary.kasp. hostmaster.kasp. ( + 2 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 + NS ns3 +ns2 A 10.53.0.2 +ns3 A 10.53.0.3 + +a A 10.0.0.11 +b A 10.0.0.2 +c A 10.0.0.3 +d A 10.0.0.4 + diff --git a/bin/tests/system/kasp/ns2/setup.sh b/bin/tests/system/kasp/ns2/setup.sh new file mode 100644 index 0000000000..d495e05f52 --- /dev/null +++ b/bin/tests/system/kasp/ns2/setup.sh @@ -0,0 +1,21 @@ +#!/bin/sh -e +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +echo_i "ns2/setup.sh" + +echo_i "setting up zone: $zone" +zone="secondary.kasp" +zonefile="${zone}.db" +infile="${zonefile}.in" +cp $infile $zonefile diff --git a/bin/tests/system/kasp/ns3/named.conf.in b/bin/tests/system/kasp/ns3/named.conf.in new file mode 100644 index 0000000000..880f3fd50c --- /dev/null +++ b/bin/tests/system/kasp/ns3/named.conf.in @@ -0,0 +1,122 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS3 + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +include "policies/kasp.conf"; + +/* Zones that are getting initially signed */ + +/* The default case: No keys created, using default policy. */ +zone "default.kasp" { + type master; + file "default.kasp.db"; + dnssec-policy "default"; +}; + +/* A master zone with dnssec-policy, no keys created. */ +zone "rsasha1.kasp" { + type master; + file "rsasha1.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* A master zone with dnssec-policy but keys already created. */ +zone "dnssec-keygen.kasp" { + type master; + file "dnssec-keygen.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* A secondary zone with dnssec-policy. */ +zone "secondary.kasp" { + type secondary; + masters { 10.53.0.2; }; + file "secondary.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* + * A configured dnssec-policy but some keys already created. + */ +zone "some-keys.kasp" { + type master; + file "some-keys.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* + * A configured dnssec-policy but some keys already in use. + */ +zone "legacy-keys.kasp" { + type master; + file "legacy-keys.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* + * A configured dnssec-policy with (too) many keys pregenerated. + */ +zone "pregenerated.kasp" { + type master; + file "pregenerated.kasp.db"; + dnssec-policy "rsasha1"; +}; + +/* + * Different algorithms. + */ +zone "rsasha1-nsec3.kasp" { + type master; + file "rsasha1-nsec3.kasp.db"; + dnssec-policy "rsasha1-nsec3"; +}; +zone "rsasha256.kasp" { + type master; + file "rsasha256.kasp.db"; + dnssec-policy "rsasha256"; +}; +zone "rsasha512.kasp" { + type master; + file "rsasha512.kasp.db"; + dnssec-policy "rsasha512"; +}; +zone "ecdsa256.kasp" { + type master; + file "ecdsa256.kasp.db"; + dnssec-policy "ecdsa256"; +}; +zone "ecdsa384.kasp" { + type master; + file "ecdsa384.kasp.db"; + dnssec-policy "ecdsa384"; +}; diff --git a/bin/tests/system/kasp/ns3/policies/kasp.conf b/bin/tests/system/kasp/ns3/policies/kasp.conf new file mode 100644 index 0000000000..547c5c0429 --- /dev/null +++ b/bin/tests/system/kasp/ns3/policies/kasp.conf @@ -0,0 +1,70 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +dnssec-policy "rsasha1" { + dnskey-ttl 1234; + + keys { + ksk key-directory P10Y 5; + zsk key-directory P5Y 5; + zsk key-directory P1Y 5 2000; + }; +}; + +dnssec-policy "rsasha1-nsec3" { + dnskey-ttl 1234; + + keys { + ksk key-directory P10Y 7; + zsk key-directory P5Y 7; + zsk key-directory P1Y 7 2000; + }; +}; + +dnssec-policy "rsasha256" { + dnskey-ttl 1234; + + keys { + ksk key-directory P10Y 8; + zsk key-directory P5Y 8; + zsk key-directory P1Y 8 2000; + }; +}; + +dnssec-policy "rsasha512" { + dnskey-ttl 1234; + + keys { + ksk key-directory P10Y 10; + zsk key-directory P5Y 10; + zsk key-directory P1Y 10 2000; + }; +}; + +dnssec-policy "ecdsa256" { + dnskey-ttl 1234; + + keys { + ksk key-directory P10Y 13; + zsk key-directory P5Y 13; + zsk key-directory P1Y 13 256; + }; +}; + +dnssec-policy "ecdsa384" { + dnskey-ttl 1234; + + keys { + ksk key-directory P10Y 14; + zsk key-directory P5Y 14; + zsk key-directory P1Y 14 384; + }; +}; diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh new file mode 100644 index 0000000000..b2fcaa7edb --- /dev/null +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -0,0 +1,50 @@ +#!/bin/sh -e +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +echo_i "ns3/setup.sh" + +setup() { + zone="$1" + echo_i "setting up zone: $zone" + zonefile="${zone}.db" + infile="${zone}.db.infile" + echo $zone >> zones +} + +# +# Set up zones that will be initially signed. +# +for zn in default rsasha1 dnssec-keygen some-keys legacy-keys pregenerated \ + rsasha1-nsec3 rsasha256 rsasha512 ecdsa256 ecdsa384 +do + setup "${zn}.kasp" + cp template.db.in $zonefile +done + +# Some of these zones already have keys. +zone="dnssec-keygen.kasp" +$KEYGEN -k rsasha1 -l policies/kasp.conf $zone > keygen.out.$zone.1 2>&1 + +zone="some-keys.kasp" +$KEYGEN -P none -A none -a RSASHA1 -b 2000 -L 1234 $zone > keygen.out.$zone.1 2>&1 +$KEYGEN -P none -A none -a RSASHA1 -f KSK -L 1234 $zone > keygen.out.$zone.2 2>&1 + +zone="legacy.kasp" +$KEYGEN -a RSASHA1 -b 2000 -L 1234 $zone > keygen.out.$zone.1 2>&1 +$KEYGEN -a RSASHA1 -f KSK -L 1234 $zone > keygen.out.$zone.2 2>&1 + +zone="pregenerated.kasp" +$KEYGEN -k rsasha1 -l policies/kasp.conf $zone > keygen.out.$zone.1 2>&1 +$KEYGEN -k rsasha1 -l policies/kasp.conf $zone > keygen.out.$zone.2 2>&1 + diff --git a/bin/tests/system/kasp/ns3/template.db.in b/bin/tests/system/kasp/ns3/template.db.in new file mode 100644 index 0000000000..051a312891 --- /dev/null +++ b/bin/tests/system/kasp/ns3/template.db.in @@ -0,0 +1,25 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns3 +ns3 A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 + diff --git a/bin/tests/system/kasp/ns3/template2.db.in b/bin/tests/system/kasp/ns3/template2.db.in new file mode 100644 index 0000000000..3fe69f34c3 --- /dev/null +++ b/bin/tests/system/kasp/ns3/template2.db.in @@ -0,0 +1,25 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 2 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns3 +ns3 A 10.53.0.3 + +a A 10.0.0.11 +b A 10.0.0.2 +c A 10.0.0.3 +d A 10.0.0.4 diff --git a/bin/tests/system/kasp/setup.sh b/bin/tests/system/kasp/setup.sh index 05b71ac269..6bdf0035a8 100644 --- a/bin/tests/system/kasp/setup.sh +++ b/bin/tests/system/kasp/setup.sh @@ -17,3 +17,17 @@ set -e $SHELL clean.sh mkdir keys + +copy_setports ns2/named.conf.in ns2/named.conf +copy_setports ns3/named.conf.in ns3/named.conf + +# ns2: Setup zones +( + cd ns2 + $SHELL setup.sh +) +# ns3: Setup zones +( + cd ns3 + $SHELL setup.sh +) diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index b6e0e2ca47..f4b06b91ef 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -436,6 +436,62 @@ check_key() { grep "Generated: " $STATE_FILE > /dev/null || log_error "mismatch generated in $STATE_FILE" } +# Check the key with key id $1 and see if it is unused. +# This requires environment variables to be set with 'zone_properties', +# and 'key_properties'. +# +# This will set the following environment variables for testing: +# BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" +# KEY_FILE="${BASE_FILE}.key" +# PRIVATE_FILE="${BASE_FILE}.private" +# STATE_FILE="${BASE_FILE}.state" +# KEY_ID=$(echo $1 | sed 's/^0*//') +key_unused() { + _dir=$DIR + _zone=$ZONE + _key_idpad=$1 + _key_id=$(echo $_key_idpad | sed 's/^0*//') + _alg_num="${KEY1[$ALG_NUM]}" + _alg_numpad=$(printf "%03d" $_alg_num) + + BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" + KEY_FILE="${BASE_FILE}.key" + PRIVATE_FILE="${BASE_FILE}.private" + STATE_FILE="${BASE_FILE}.state" + KEY_ID="${_key_id}" + + test $_log -eq 1 && echo_i "key unused $KEY_ID?" + + # Check timing metadata. + grep "; Publish:" $KEY_FILE > /dev/null && log_error "unexpected publish comment in $KEY_FILE" + grep "Publish:" $PRIVATE_FILE > /dev/null && log_error "unexpected publish in $PRIVATE_FILE" + grep "Published: " $STATE_FILE > /dev/null && log_error "unexpected publish in $STATE_FILE" + grep "; Activate:" $KEY_FILE > /dev/null && log_error "unexpected active comment in $KEY_FILE" + grep "Activate:" $PRIVATE_FILE > /dev/null && log_error "unexpected active in $PRIVATE_FILE" + grep "Active: " $STATE_FILE > /dev/null && log_error "unexpected active in $STATE_FILE" + grep "; Inactive:" $KEY_FILE > /dev/null && log_error "unexpected retired comment in $KEY_FILE" + grep "Inactive:" $PRIVATE_FILE > /dev/null && log_error "unexpected retired in $PRIVATE_FILE" + grep "Retired: " $STATE_FILE > /dev/null && log_error "unexpected retired in $STATE_FILE" + grep "; Revoke:" $KEY_FILE > /dev/null && log_error "unexpected revoked comment in $KEY_FILE" + grep "Revoke:" $PRIVATE_FILE > /dev/null && log_error "unexpected revoked in $PRIVATE_FILE" + grep "Revoked: " $STATE_FILE > /dev/null && log_error "unexpected revoked in $STATE_FILE" + grep "; Delete:" $KEY_FILE > /dev/null && log_error "unexpected removed comment in $KEY_FILE" + grep "Delete:" $PRIVATE_FILE > /dev/null && log_error "unexpected removed in $PRIVATE_FILE" + grep "Removed: " $STATE_FILE > /dev/null && log_error "unexpected removed in $STATE_FILE" +} + +# Test: dnssec-verify zone $1. +dnssec_verify() +{ + n=$((n+1)) + echo_i "dnssec-verify zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @10.53.0.3 AXFR > dig.out.axfr.test$n || log_error "dig ${ZONE} AXFR failed" + $VERIFY -z -o $ZONE dig.out.axfr.test$n > /dev/null || log_error "dnssec verify zone $ZONE failed" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + ############################################################################### # Tests # ############################################################################### @@ -579,7 +635,450 @@ status=$((status+ret)) # # named # +# +# The NSEC record at the apex of the zone and its RRSIG records are +# added as part of the last step in signing a zone. We wait for the +# NSEC records to appear before proceeding with a counter to prevent +# infinite loops if there is a error. +# +n=$((n+1)) +echo_i "waiting for kasp signing changes to take effect ($n)" +i=0 +while [ $i -lt 30 ] +do + ret=0 + for z in `cat ns3/zones` + do + dig_with_opts $z @10.53.0.3 nsec > dig.out.ns3.test$n.$z || ret=1 + grep "NS SOA" dig.out.ns3.test$n.$z > /dev/null || ret=1 + grep "$z\..*IN.*RRSIG" dig.out.ns3.test$n.$z > /dev/null || ret=1 + done + i=`expr $i + 1` + if [ $ret = 0 ]; then break; fi + echo_i "waiting ... ($i)" + sleep 1 +done +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) +# +# Zone: default.kasp. +# + +# Check the zone with default kasp policy has loaded and is signed. +zone_properties "ns3" "default.kasp" "_default" "3600" +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" +# The first key is immediately published and activated. +key_timings "KEY1" "published" "active" "none" "none" "none" "none" +# DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait. +key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" + +n=$((n+1)) +echo_i "check key is created for zone ${ZONE} ($n)" +ret=0 +id=$(get_keyids $DIR $ZONE "${KEY1[$ALG_NUM]}") +check_key "KEY1" $id +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# Verify signed zone. +dnssec_verify $ZONE + +# Test DNSKEY query. +qtype="DNSKEY" +n=$((n+1)) +echo_i "check ${qtype} rrset is signed correctly for zone ${ZONE} ($n)" +ret=0 +dig_with_opts $ZONE @10.53.0.3 $qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${qtype} failed" +grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" +grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${qtype}.*257.*.3.*${KEY1[$ALG_NUM]}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${qtype} record in response" +lines=$(get_keys_which_signed $qtype dig.out.$DIR.test$n | wc -l) +test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" +get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with ${KEY_ID}" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# Test SOA query. +qtype="SOA" +n=$((n+1)) +echo_i "check ${qtype} rrset is signed correctly for zone ${ZONE} ($n)" +ret=0 +dig_with_opts $ZONE @10.53.0.3 $qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${qtype} failed" +grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" +grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*${qtype}.*mname1\..*\." dig.out.$DIR.test$n > /dev/null || log_error "missing ${qtype} record in response" +lines=$(get_keys_which_signed $qtype dig.out.$DIR.test$n | wc -l) +test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" +get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with ${KEY_ID}" +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# Update zone. +n=$((n+1)) +echo_i "check that we can update unsigned zone file and new record gets signed for zone ${ZONE} ($n)" +ret=0 +cp "${DIR}/template2.db.in" "${DIR}/${ZONE}.db" +rndccmd 10.53.0.3 reload $ZONE > /dev/null || log_error "rndc reload zone ${ZONE} failed" +_log=0 +i=0 +while [ $i -lt 5 ] +do + ret=0 + + dig_with_opts "a.${ZONE}" @10.53.0.3 A > dig.out.$DIR.test$n.a || log_error "dig a.${ZONE} A failed" + grep "status: NOERROR" dig.out.$DIR.test$n.a > /dev/null || log_error "mismatch status in DNS response" + grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.11" dig.out.$DIR.test$n.a > /dev/null || log_error "missing a.${ZONE} A record in response" + lines=$(get_keys_which_signed A dig.out.$DIR.test$n.a | wc -l) + test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" + get_keys_which_signed A dig.out.$DIR.test$n.a | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with ${KEY_ID}" + + dig_with_opts "d.${ZONE}" @10.53.0.3 A > dig.out.$DIR.test$n.d || log_error "dig d.${ZONE} A failed" + grep "status: NOERROR" dig.out.$DIR.test$n.d > /dev/null || log_error "mismatch status in DNS response" + grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.4" dig.out.$DIR.test$n.d > /dev/null || log_error "missing d.${ZONE} A record in response" + lines=$(get_keys_which_signed A dig.out.$DIR.test$n.d | wc -l) + test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" + get_keys_which_signed A dig.out.$DIR.test$n.d | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with ${KEY_ID}" + + i=`expr $i + 1` + if [ $ret = 0 ]; then break; fi + echo_i "waiting ... ($i)" + sleep 1 +done +_log=1 +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# +# Zone: rsasha1.kasp. +# +zone_properties "ns3" "rsasha1.kasp" "rsasha1" "1234" "3" +key_properties "KEY1" "ksk" "315360000" "5" "RSASHA1" "2048" "yes" +key_properties "KEY2" "zsk" "157680000" "5" "RSASHA1" "1024" "yes" +key_properties "KEY3" "zsk" "31536000" "5" "RSASHA1" "2000" "yes" +# The first keys are immediately published and activated. +# Because lifetime > 0, retired timing is also set. +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_timings "KEY2" "published" "active" "retired" "none" "none" +key_timings "KEY3" "published" "active" "retired" "none" "none" +# KSK: DNSKEY, RRSIG (ksk) published. DS needs to wait. +# ZSK: DNSKEY, RRSIG (zsk) published. +key_states "KEY1" "omnipresent" "rumoured" "none" "rumoured" "hidden" +key_states "KEY2" "omnipresent" "rumoured" "rumoured" "none" "none" +key_states "KEY3" "omnipresent" "rumoured" "rumoured" "none" "none" + +# Check keys for a configured zone. This verifies: +# 1. The right number of keys exist in the key pool ($1). +# 2. The right number of keys is active (always expect three keys). +# The algorithm expected is set with $2 (string) and $3 (number), and the +# expected sizes for the keys are set with $4 (ksk), $5 and $6 (zsk). +# A size set to 0 means the corresponding key (KEY1, KEY2 or KEY3) is not +# expected. +# +# It is expected that KEY1, KEY2 and KEY3 arrays are set correctly. Found key +# identifiers are stored in the right key array. +check_keys() +{ + n=$((n+1)) + echo_i "check keys are created for zone ${ZONE} ($n)" + ret=0 + + _key_algnum="${KEY1[$ALG_NUM]}" + + n=$((n+1)) + echo_i "check number of keys with algorithm ${_key_algnum} for zone ${ZONE} in dir ${DIR} ($n)" + ret=0 + _numkeys=$(get_keyids $DIR $ZONE $_key_algnum | wc -l) + test "$_numkeys" -eq $NUM_KEYS || log_error "bad number ($_numkeys) of key files for zone $ZONE (expected $NUM_KEYS)" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + + # Temporarily don't log errors because we are searching multiple files. + _log=0 + + # Clear key ids. + KEY1[$ID]="0" + KEY2[$ID]="0" + KEY3[$ID]="0" + + # Check key files. + _ids=$(get_keyids $DIR $ZONE "$_key_algnum") + for _id in $_ids; do + # There are three key files with the same algorithm. + # Check them until a match is found. + echo_i "check key $_id" + + if [ "0" == "${KEY1[$ID]}" ] && [ "${KEY1[$EXPECT]}" == "yes" ]; then + ret=0 + check_key "KEY1" $_id + test "$ret" -eq 0 && KEY1[$ID]=$KEY_ID && continue + fi + if [ "0" == "${KEY2[$ID]}" ] && [ "${KEY2[$EXPECT]}" == "yes" ]; then + ret=0 + check_key "KEY2" $_id + test "$ret" -eq 0 && KEY2[$ID]=$KEY_ID && continue + fi + if [ "0" == "${KEY3[$ID]}" ] && [ "${KEY3[$EXPECT]}" == "yes" ]; then + ret=0 + check_key "KEY3" $_id + test "$ret" -eq 0 && KEY3[$ID]=$KEY_ID && continue + fi + + # This may be an unused key. + ret=0 && key_unused $_id + test "$ret" -eq 0 && continue + + # If ret is still non-zero, non of the files matched. + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + done + + # Turn error logs on again. + _log=1 + + ret=0 + if [ "${KEY1[$EXPECT]}" == "yes" ]; then + test "0" == "${KEY1[$ID]}" && log_error "No KEY1 found for zone ${ZONE}" + fi + if [ "${KEY2[$EXPECT]}" == "yes" ]; then + test "0" == "${KEY2[$ID]}" && log_error "No KEY2 found for zone ${ZONE}" + fi + if [ "${KEY3[$EXPECT]}" == "yes" ]; then + test "0" == "${KEY3[$ID]}" && log_error "No KEY3 found for zone ${ZONE}" + fi + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + +# Check if RRset of type $1 in file $2 is signed with the right keys. +# The right keys are the ones that expect a signature and matches the role $3. +check_signatures() { + _qtype=$1 + _file=$2 + _role=$3 + + if [ "${KEY1[$EXPECT_RRSIG]}" == "yes" ] && [ "${KEY1[$_role]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with key ${KEY1[$ID]}" + elif [ "${KEY1[$EXPECT]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY1[$ID]}" + fi + + if [ "${KEY2[$EXPECT_RRSIG]}" == "yes" ] && [ "${KEY2[$_role]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with ${KEY2[$ID]}" + elif [ "${KEY2[$EXPECT]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY2[$ID]}" + fi + + if [ "${KEY3[$EXPECT_RRSIG]}" == "yes" ] && [ "${KEY3[$_role]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with ${KEY3[$ID]}" + elif [ "${KEY3[$EXPECT]}" == "yes" ]; then + get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY3[$ID]}" + fi +} + +# Test the apex of a configured zone. This checks that the SOA and DNSKEY +# RRsets are signed correctly and with the appropriate keys. +check_apex() { + + # Test DNSKEY query. + _qtype="DNSKEY" + n=$((n+1)) + echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response" + lines=$(get_keys_which_signed $_qtype dig.out.$DIR.test$n | wc -l) + check_signatures $_qtype dig.out.$DIR.test$n $KSK + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + + # Test SOA query. + _qtype="SOA" + n=$((n+1)) + echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response" + lines=$(get_keys_which_signed $_qtype dig.out.$DIR.test$n | wc -l) + check_signatures $_qtype dig.out.$DIR.test$n $ZSK + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + +# Test an RRset below the apex and verify it is signed correctly. +check_subdomain() { + _qtype="A" + n=$((n+1)) + echo_i "check ${_qtype} a.${ZONE} rrset is signed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts a.$ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig a.${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*10\.0\.0\.1" dig.out.$DIR.test$n > /dev/null || log_error "missing a.${ZONE} ${_qtype} record in response" + lines=$(get_keys_which_signed $_qtype dig.out.$DIR.test$n | wc -l) + check_signatures $_qtype dig.out.$DIR.test$n $ZSK + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: dnssec-keygen.kasp. +# +zone_properties "ns3" "dnssec-keygen.kasp" "rsasha1" "1234" "3" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: some-keys.kasp. +# +zone_properties "ns3" "some-keys.kasp" "rsasha1" "1234" "3" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: legacy-keys.kasp. +# +zone_properties "ns3" "legacy-keys.kasp" "rsasha1" "1234" "3" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: pregenerated.kasp. +# +# There are more pregenerated keys than needed, hence the number of keys is +# six, not three. +zone_properties "ns3" "pregenerated.kasp" "rsasha1" "1234" "6" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: secondary.kasp. +# +zone_properties "ns3" "secondary.kasp" "rsasha1" "1234" "3" +# KSK properties, timings and states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# Update zone. +n=$((n+1)) +echo_i "check that we correctly sign the zone after IXFR for zone ${ZONE} ($n)" +ret=0 +cp ns2/secondary.kasp.db.in2 ns2/secondary.kasp.db +rndccmd 10.53.0.2 reload $ZONE > /dev/null || log_error "rndc reload zone ${ZONE} failed" +_log=0 +i=0 +while [ $i -lt 5 ] +do + ret=0 + + dig_with_opts "a.${ZONE}" @10.53.0.3 A > dig.out.$DIR.test$n.a || log_error "dig a.${ZONE} A failed" + grep "status: NOERROR" dig.out.$DIR.test$n.a > /dev/null || log_error "mismatch status in DNS response" + grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.11" dig.out.$DIR.test$n.a > /dev/null || log_error "missing a.${ZONE} A record in response" + check_signatures $_qtype dig.out.$DIR.test$n.a $ZSK + + dig_with_opts "d.${ZONE}" @10.53.0.3 A > dig.out.$DIR.test$n.d || log_error "dig d.${ZONE} A failed" + grep "status: NOERROR" dig.out.$DIR.test$n.d > /dev/null || log_error "mismatch status in DNS response" + grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.4" dig.out.$DIR.test$n.d > /dev/null || log_error "missing d.${ZONE} A record in response" + lines=$(get_keys_which_signed A dig.out.$DIR.test$n.d | wc -l) + check_signatures $_qtype dig.out.$DIR.test$n.d $ZSK + + i=`expr $i + 1` + if [ $ret = 0 ]; then break; fi + echo_i "waiting ... ($i)" + sleep 1 +done +_log=1 +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +# TODO: we might want to test: +# - configuring a zone with too many active keys (should trigger retire). +# - configuring a zone with keys not matching the policy. + +# +# Zone: rsasha1-nsec3.kasp. +# +zone_properties "ns3" "rsasha1-nsec3.kasp" "rsasha1-nsec3" "1234" "3" +key_properties "KEY1" "ksk" "315360000" "7" "NSEC3RSASHA1" "2048" "yes" +key_properties "KEY2" "zsk" "157680000" "7" "NSEC3RSASHA1" "1024" "yes" +key_properties "KEY3" "zsk" "31536000" "7" "NSEC3RSASHA1" "2000" "yes" +# key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: rsasha256.kasp. +# +zone_properties "ns3" "rsasha256.kasp" "rsasha256" "1234" "3" +key_properties "KEY1" "ksk" "315360000" "8" "RSASHA256" "2048" "yes" +key_properties "KEY2" "zsk" "157680000" "8" "RSASHA256" "1024" "yes" +key_properties "KEY3" "zsk" "31536000" "8" "RSASHA256" "2000" "yes" +# key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: rsasha512.kasp. +# +zone_properties "ns3" "rsasha512.kasp" "rsasha512" "1234" "3" +key_properties "KEY1" "ksk" "315360000" "10" "RSASHA512" "2048" "yes" +key_properties "KEY2" "zsk" "157680000" "10" "RSASHA512" "1024" "yes" +key_properties "KEY3" "zsk" "31536000" "10" "RSASHA512" "2000" "yes" +# key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: ecdsa256.kasp. +# +zone_properties "ns3" "ecdsa256.kasp" "ecdsa256" "1234" "3" +key_properties "KEY1" "ksk" "315360000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY2" "zsk" "157680000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY3" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" +# key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# +# Zone: ecdsa512.kasp. +# +zone_properties "ns3" "ecdsa384.kasp" "ecdsa384" "1234" "3" +key_properties "KEY1" "ksk" "315360000" "14" "ECDSAP384SHA384" "384" "yes" +key_properties "KEY2" "zsk" "157680000" "14" "ECDSAP384SHA384" "384" "yes" +key_properties "KEY3" "zsk" "31536000" "14" "ECDSAP384SHA384" "384" "yes" +# key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# TODO: ED25519 and ED448. echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/util/copyrights b/util/copyrights index 733bedd2b1..3608a3a60e 100644 --- a/util/copyrights +++ b/util/copyrights @@ -694,7 +694,10 @@ ./bin/tests/system/ixfr/prereq.sh SH 2001,2004,2007,2012,2014,2016,2018,2019 ./bin/tests/system/ixfr/setup.sh SH 2001,2004,2007,2011,2012,2013,2014,2016,2018,2019 ./bin/tests/system/ixfr/tests.sh SH 2001,2004,2007,2011,2012,2014,2016,2018,2019 +./bin/tests/system/kasp/README TXT.BRIEF 2019 ./bin/tests/system/kasp/clean.sh SH 2019 +./bin/tests/system/kasp/ns2/setup.sh SH 2019 +./bin/tests/system/kasp/ns3/setup.sh SH 2019 ./bin/tests/system/kasp/setup.sh SH 2019 ./bin/tests/system/kasp/tests.sh SH 2019 ./bin/tests/system/keepalive/clean.sh SH 2017,2018,2019 From 36c72bf3c3653603a3f6efe4fc47ec1011f72949 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Thu, 17 Oct 2019 15:50:52 +0200 Subject: [PATCH 30/43] Test ZSK and KSK rollover Add tests for ZSK Pre-Publication and KSK Double-KSK rollover. Includes tests for next key event is scheduled at the right time. --- bin/tests/system/kasp/ns3/named.conf.in | 113 +++++ .../system/kasp/ns3/policies/autosign.conf | 66 +++ bin/tests/system/kasp/ns3/setup.sh | 308 ++++++++++++++ bin/tests/system/kasp/tests.sh | 402 ++++++++++++++++++ 4 files changed, 889 insertions(+) create mode 100644 bin/tests/system/kasp/ns3/policies/autosign.conf diff --git a/bin/tests/system/kasp/ns3/named.conf.in b/bin/tests/system/kasp/ns3/named.conf.in index 880f3fd50c..c7a830dc3a 100644 --- a/bin/tests/system/kasp/ns3/named.conf.in +++ b/bin/tests/system/kasp/ns3/named.conf.in @@ -33,6 +33,7 @@ controls { }; include "policies/kasp.conf"; +include "policies/autosign.conf"; /* Zones that are getting initially signed */ @@ -120,3 +121,115 @@ zone "ecdsa384.kasp" { file "ecdsa384.kasp.db"; dnssec-policy "ecdsa384"; }; + +/* + * Zones in different signing states. + */ + +/* + * Zone that has expired signatures. + */ +zone "expired-sigs.autosign" { + type master; + file "expired-sigs.autosign.db"; + dnssec-policy "autosign"; +}; + +/* + * Zone that has valid, fresh signatures. + */ +zone "fresh-sigs.autosign" { + type master; + file "fresh-sigs.autosign.db"; + dnssec-policy "autosign"; +}; + +/* + * Zone that has unfresh signatures. + */ +zone "unfresh-sigs.autosign" { + type master; + file "unfresh-sigs.autosign.db"; + dnssec-policy "autosign"; +}; + +/* + * Zone that has missing private ZSK. + */ +zone "zsk-missing.autosign" { + type master; + file "zsk-missing.autosign.db"; + dnssec-policy "autosign"; +}; + +/* + * Zone that has inactive ZSK. + */ +zone "zsk-retired.autosign" { + type master; + file "zsk-retired.autosign.db"; + dnssec-policy "autosign"; +}; + +/* + * Zones for testing ZSK Pre-Publication steps. + */ +zone "step1.zsk-prepub.autosign" { + type master; + file "step1.zsk-prepub.autosign.db"; + dnssec-policy "zsk-prepub"; +}; +zone "step2.zsk-prepub.autosign" { + type master; + file "step2.zsk-prepub.autosign.db"; + dnssec-policy "zsk-prepub"; +}; +zone "step3.zsk-prepub.autosign" { + type master; + file "step3.zsk-prepub.autosign.db"; + dnssec-policy "zsk-prepub"; +}; +zone "step4.zsk-prepub.autosign" { + type master; + file "step4.zsk-prepub.autosign.db"; + dnssec-policy "zsk-prepub"; +}; +zone "step5.zsk-prepub.autosign" { + type master; + file "step5.zsk-prepub.autosign.db"; + dnssec-policy "zsk-prepub"; +}; + +/* + * Zones for testing KSK Double-KSK steps. + */ +zone "step1.ksk-doubleksk.autosign" { + type master; + file "step1.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; +zone "step2.ksk-doubleksk.autosign" { + type master; + file "step2.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; +zone "step3.ksk-doubleksk.autosign" { + type master; + file "step3.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; +zone "step4.ksk-doubleksk.autosign" { + type master; + file "step4.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; +zone "step5.ksk-doubleksk.autosign" { + type master; + file "step5.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; +zone "step6.ksk-doubleksk.autosign" { + type master; + file "step6.ksk-doubleksk.autosign.db"; + dnssec-policy "ksk-doubleksk"; +}; diff --git a/bin/tests/system/kasp/ns3/policies/autosign.conf b/bin/tests/system/kasp/ns3/policies/autosign.conf new file mode 100644 index 0000000000..3a0d028d00 --- /dev/null +++ b/bin/tests/system/kasp/ns3/policies/autosign.conf @@ -0,0 +1,66 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +dnssec-policy "autosign" { + + signatures-refresh P1W; + signatures-validity P2W; + signatures-validity-dnskey P2W; + + dnskey-ttl 300; + + keys { + ksk key-directory P2Y 13; + zsk key-directory P1Y 13; + }; +}; + +dnssec-policy "zsk-prepub" { + + signatures-refresh P1W; + signatures-validity P2W; + signatures-validity-dnskey P2W; + + dnskey-ttl 3600; + publish-safety P1D; + retire-safety P2D; + + keys { + ksk key-directory P2Y 13; + zsk key-directory P30D 13; + }; + + zone-propagation-delay PT1H; + zone-max-ttl 1d; +}; + +dnssec-policy "ksk-doubleksk" { + + signatures-refresh P1W; + signatures-validity P2W; + signatures-validity-dnskey P2W; + + dnskey-ttl 2h; + publish-safety P1D; + retire-safety P2D; + + keys { + ksk key-directory P60D 13; + zsk key-directory P1Y 13; + }; + + zone-propagation-delay PT1H; + zone-max-ttl 1d; + + parent-ds-ttl 3600; + parent-registration-delay P1D; + parent-propagation-delay PT1H; +}; diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh index b2fcaa7edb..b384fa730b 100644 --- a/bin/tests/system/kasp/ns3/setup.sh +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -22,6 +22,23 @@ setup() { echo $zone >> zones } +private_type_record() { + _zone=$1 + _algorithm=$2 + _keyfile=$3 + + _id=$(keyfile_to_key_id "$_keyfile") + + printf "%s. 0 IN TYPE65534 \# 5 %02x%04x0000\n" $_zone $_algorithm $_id +} + + +# Make lines shorter by storing key states in environment variables. +H="HIDDEN" +R="RUMOURED" +O="OMNIPRESENT" +U="UNRETENTIVE" + # # Set up zones that will be initially signed. # @@ -48,3 +65,294 @@ zone="pregenerated.kasp" $KEYGEN -k rsasha1 -l policies/kasp.conf $zone > keygen.out.$zone.1 2>&1 $KEYGEN -k rsasha1 -l policies/kasp.conf $zone > keygen.out.$zone.2 2>&1 +# +# Set up zones that are already signed. +# + +# These signatures are set to expire long in the past, update immediately. +setup expired-sigs.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 300 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 300 $zone 2> keygen.out.$zone.2` +T="now-6mo" +$SETTIME -s -P $T -A $T -g $O -d $O $T -k $O $T -r $O $T $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $T -A $T -g $O -k $O $T -z $O $T $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -PS -x -s now-2mo -e now-1mo -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# These signatures are still good, and can be reused. +setup fresh-sigs.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 300 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 300 $zone 2> keygen.out.$zone.2` +T="now-6mo" +$SETTIME -s -P $T -A $T -g $O -d $O $T -k $O $T -r $O $T $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $T -A $T -g $O -k $O $T -z $O $T $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# These signatures are still good, but not fresh enough, update immediately. +setup unfresh-sigs.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 300 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 300 $zone 2> keygen.out.$zone.2` +T="now-6mo" +$SETTIME -s -P $T -A $T -g $O -d $O $T -k $O $T -r $O $T $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $T -A $T -g $O -k $O $T -z $O $T $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1w -e now+1w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# These signatures are already expired, and the private ZSK is missing. +setup zsk-missing.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 300 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 300 $zone 2> keygen.out.$zone.2` +T="now-6mo" +$SETTIME -s -P $T -A $T -g $O -d $O $T -k $O $T -r $O $T $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $T -A $T -g $O -k $O $T -z $O $T $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -PS -x -s now-2w -e now-1mi -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 +rm -f ${ZSK}.private + +# These signatures are already expired, and the private ZSK is retired. +setup zsk-retired.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 300 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 300 $zone 2> keygen.out.$zone.2` +T="now-6mo" +$SETTIME -s -P $T -A $T -g $O -d $O $T -k $O $T -r $O $T $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $T -A $T -g $O -k $O $T -z $O $T $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -PS -x -s now-2w -e now-1mi -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 +$SETTIME -s -I now -g HIDDEN $ZSK > settime.out.$zone.3 2>&1 + +# +# The zones at zsk-prepub.autosign represent the various steps of a ZSK +# Pre-Publication rollover. +# + +# Step 1: +# Introduce the first key. This will immediately be active. +setup step1.zsk-prepub.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2` +TactN="now" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 2: +# It is time to pre-publish the successor ZSK. +setup step2.zsk-prepub.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2` +# According to RFC 7583: Tpub(N+1) <= Tact(N) + Lzsk - Ipub +# Also: Ipub = Dprp + TTLkey (+publish-safety) +# so: Tact(N) = Tpub(N+1) + Ipub - Lzsk = now + (1d2h) - 30d = +# now + 26h - 30d = now − 694h +TactN="now-694h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 3: +# After the publication interval has passed the DNSKEY of the successor ZSK +# is OMNIPRESENT and the zone can thus be signed with the successor ZSK. +setup step3.zsk-prepub.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1` +ZSK1=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2` +ZSK2=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.3` +# According to RFC 7583: Tpub(N+1) <= Tact(N) + Lzsk - Ipub +# Also: Tret(N) = Tact(N+1) = Tact(N) + Lzsk +# so: Tact(N) = Tact(N+1) - Lzsk = now - 30d +# and: Tpub(N+1) = Tact(N+1) - Ipub = now - 26h +# and: Tret(N+1) = Tact(N+1) + Lzsk +TactN="now-30d" +TpubN1="now-26h" +TretN1="now+30d" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -I now -g $H -k $O $TactN -z $O $TactN $ZSK1 > settime.out.$zone.2 2>&1 +$SETTIME -s -S $ZSK1 -i 0 $ZSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A now -I $TretN1 -g $O -k $R $TpubN1 -z $H $TpubN1 $ZSK2 > settime.out.$zone.4 2>&1 +cat template.db.in "${KSK}.key" "${ZSK1}.key" "${ZSK2}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK1 >> "$infile" +private_type_record $zone 13 $ZSK2 >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 4: +# After the retire interval has passed the predecessor DNSKEY can be +# removed from the zone. +setup step4.zsk-prepub.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1` +ZSK1=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2` +ZSK2=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.3` +# According to RFC 7583: Tret(N) = Tact(N) + Lzsk +# Also: Tdea(N) = Tret(N) + Iret +# Also: Iret = Dsgn + Dprp + TTLsig (+retire-safety) +# so: Tact(N) = Tdea(N) - Iret - Lzsk = now - (1w1h1d2d) - 30d = +# now - (10d1h) - 30d = now - 961h +# and: Tret(N) = Tdea(N) - Iret = now - (10d1h) = now - 241h +# and: Tpub(N+1) = Tdea(N) - Iret - Ipub = now - (10d1h) - 26h = +# now - 267h +# and: Tact(N+1) = Tdea(N) - Iret = Tret(N) +# and: Tret(N+1) = Tdea(N) - Iret + Lzsk = now - (10d1h) + 30d = +# now + 479h +TactN="now-961h" +TretN="now-241h" +TpubN1="now-267h" +TactN1="${TretN}" +TretN1="now+479h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -z $U $TretN $ZSK1 > settime.out.$zone.2 2>&1 +$SETTIME -s -S $ZSK1 -i 0 $ZSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TactN1 -z $R $TactN1 $ZSK2 > settime.out.$zone.4 2>&1 +cat template.db.in "${KSK}.key" "${ZSK1}.key" "${ZSK2}.key" > "$infile" +$SIGNER -PS -x -s now-2w -e now-1mi -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 5: +# The predecessor DNSKEY is removed long enough that is has become HIDDEN. +setup step5.zsk-prepub.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 3600 $zone 2> keygen.out.$zone.1` +ZSK1=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.2` +ZSK2=`$KEYGEN -a ECDSAP256SHA256 -L 3600 $zone 2> keygen.out.$zone.3` +# Substract DNSKEY TTL from all the times (1h). +TactN="now-962h" +TretN="now-242h" +TpubN1="now-268h" +TactN1="${TretN}" +TretN1="now+478h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -I $TretN -D now -g $H -k $U $TretN -z $U $TretN $ZSK1 > settime.out.$zone.2 2>&1 +$SETTIME -s -S $ZSK1 -i 0 $ZSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TactN1 -z $R $TactN1 $ZSK2 > settime.out.$zone.4 2>&1 +cat template.db.in "${KSK}.key" "${ZSK1}.key" "${ZSK2}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK1 >> "$infile" +private_type_record $zone 13 $ZSK2 >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# +# The zones at ksk-doubleksk.autosign represent the various steps of a KSK +# Double-KSK rollover. +# + +# Step 1: +# Introduce the first key. This will immediately be active. +setup step1.ksk-doubleksk.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 7200 $zone 2> keygen.out.$zone.2` +TactN="now" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 2: +# It is time to submit the introduce the new KSK. +setup step2.ksk-doubleksk.autosign +KSK=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.1` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 7200 $zone 2> keygen.out.$zone.2` +# According to RFC 7583: Tpub(N+1) <= Tact(N) + Lksk - Dreg - IpubC +# Also: IpubC = DprpC + TTLkey (+publish-safety) +# so: Tact(N) = Tpub(N+1) - Lksk + Dreg + IpubC = now - 60d + (1d3h) +# now - 1440h + 27h = now - 1413h +TactN="now-1413h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN $KSK > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 3: +# It is time to submit the DS. +setup step3.ksk-doubleksk.autosign +KSK1=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.1` +KSK2=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.2` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 7200 $zone 2> keygen.out.$zone.3` +# According to RFC 7583: Tsbm(N+1) >= Trdy(N+1) +# Also: Tact(N+1) = Tsbm(N+1) + Dreg +# so: Tact(N) = Tsbm(N+1) + Dreg - Lksk = now + 1d - 60d = now - 59d +# and: Tret(N) = Tsbm(N+1) + Dreg = now + 1d +# and: Tpub(N+1) <= Tsbm(N+1) - IpubC = now + 27h +# and: Tret(N+1) = Tsbm(N+1) + Dreg + Lksk = 1d + 60d +TactN="now-59d" +TretN="now+1d" +TpubN1="now-27h" +TretN1="now+61d" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $O $TactN $KSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $KSK1 -i 0 $KSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TretN -I $TretN1 -g $O -k $R $TpubN1 -r $R $TpubN1 -d $H $TpubN1 $KSK2 > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK1}.key" "${KSK2}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK1 >> "$infile" +private_type_record $zone 13 $KSK2 >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 4: +# The DS should be swapped now. +setup step4.ksk-doubleksk.autosign +KSK1=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.1` +KSK2=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.2` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 7200 $zone 2> keygen.out.$zone.3` +# According to RFC 7583: Tdea(N) = Tret(N) + Iret +# Also: Tret(N) = Tsbm(N+1) + Dreg +# Also: Tact(N+1) = Tret(N) +# Also: Iret = DprpP + TTLds (+retire-safety) +# so: Tact(N) = Tdea(N) - Lksk - Iret = now - 60d - 2d2h = now - 1490h +# and: Tret(N) = Tdea(N) - Iret = now - 2d2h = 50h +# and: Tpub(N+1) = Tdea(N) - Iret - Dreg - IpubC = now - 50h - 1d - 1d3h = now - 101h +# and: Tsbm(N+1) = Tdea(N) - Iret - Dreg = now - 50h - 1d = now - 74h +# and: Tact(N+1) = Tret(N) +# and: Tret(N+1) = Tdea(N) + Lksk - Iret = now + 60d - 2d2h = now + 1390h +TactN="now-1490h" +TretN="now-50h" +TpubN1="now-101h" +TsbmN1="now-74h" +TactN1="${TretN}" +TretN1="now+1390h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $U $TsbmN1 $KSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $KSK1 -i 0 $KSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TsbmN1 -r $O $TsbmN1 -d $R $TsbmN1 $KSK2 > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK1}.key" "${KSK2}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK1 >> "$infile" +private_type_record $zone 13 $KSK2 >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 5: +# The predecessor DNSKEY is removed long enough that is has become HIDDEN. +setup step5.ksk-doubleksk.autosign +KSK1=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.1` +KSK2=`$KEYGEN -a ECDSAP256SHA256 -f KSK -L 7200 $zone 2> keygen.out.$zone.2` +ZSK=`$KEYGEN -a ECDSAP256SHA256 -L 7200 $zone 2> keygen.out.$zone.3` +# Substract DNSKEY TTL from all the times (2h). +TactN="now-1492h" +TretN="now-52h" +TpubN1="now-102h" +TsbmN1="now-75h" +TactN1="${TretN}" +TretN1="now+1388h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $U $TretN -r $U $TretN -d $H $TretN $KSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $KSK1 -i 0 $KSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TactN1 -r $O $TactN1 -d $O $TactN1 $KSK2 > settime.out.$zone.1 2>&1 +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -z $O $TactN $ZSK > settime.out.$zone.2 2>&1 +cat template.db.in "${KSK1}.key" "${KSK2}.key" "${ZSK}.key" > "$infile" +private_type_record $zone 13 $KSK1 >> "$infile" +private_type_record $zone 13 $KSK2 >> "$infile" +private_type_record $zone 13 $ZSK >> "$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index f4b06b91ef..6bd1d3db94 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -1080,5 +1080,407 @@ dnssec_verify # TODO: ED25519 and ED448. +# +# Zone: expired-sigs.autosign. +# +zone_properties "ns3" "expired-sigs.autosign" "autosign" "300" "2" +# Both KSK and ZSK stay OMNIPRESENT. +key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" +key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "none" "none" +key_timings "KEY2" "published" "active" "retired" "none" "none" +# Expect only two keys. +key_clear "KEY3" + +check_keys +check_apex +check_subdomain +dnssec_verify + +# Verify all signatures have been refreshed. +check_rrsig_refresh() { + # Apex. + _qtypes="DNSKEY SOA NS NSEC" + for _qtype in $_qtypes + do + n=$((n+1)) + echo_i "check ${_qtype} rrsig is refreshed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" + # If this exact RRSIG is also in the zone file it is not refreshed. + _rrsig=`cat rrsig.out.$ZONE.$_qtype` + grep "${_rrsig}" "${DIR}/${ZONE}.db" > /dev/null && log_error "RRSIG (${_qtype}) not refreshed in zone ${ZONE}" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + done + + # Below apex. + _labels="a b c ns3" + for _label in $_labels; + do + _qtypes="A NSEC" + for _qtype in $_qtypes + do + n=$((n+1)) + echo_i "check ${_label} ${_qtype} rrsig is refreshed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts "${_label}.${ZONE}" @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${_label}.${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" + _rrsig=`cat rrsig.out.$ZONE.$_qtype` + grep "${_rrsig}" "${DIR}/${ZONE}.db" > /dev/null && log_error "RRSIG (${_qtype}) not refreshed in zone ${ZONE}" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + done + done +} + +check_rrsig_refresh + +# +# Zone: fresh-sigs.autosign. +# +zone_properties "ns3" "fresh-sigs.autosign" "autosign" "300" "2" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify + +# Verify signature reuse. +check_rrsig_reuse() { + # Apex. + _qtypes="NS NSEC" + for _qtype in $_qtypes + do + n=$((n+1)) + echo_i "check ${_qtype} rrsig is reused correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" + # If this exact RRSIG is also in the zone file it is not refreshed. + _rrsig=$(awk '{print $5, $6, $7, $8, $9, $10, $11, $12, $13, $14;}' < rrsig.out.$ZONE.$_qtype) + grep "${_rrsig}" "${DIR}/${ZONE}.db" > /dev/null || log_error "RRSIG (${_qtype}) not reused in zone ${ZONE}" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + done + + # Below apex. + _labels="a b c ns3" + for _label in $_labels; + do + _qtypes="A NSEC" + for _qtype in $_qtypes + do + n=$((n+1)) + echo_i "check ${_label} ${_qtype} rrsig is reused correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts "${_label}.${ZONE}" @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${_label}.${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" + _rrsig=$(awk '{print $5, $6, $7, $8, $9, $10, $11, $12, $13, $14;}' < rrsig.out.$ZONE.$_qtype) + grep "${_rrsig}" "${DIR}/${ZONE}.db" > /dev/null || log_error "RRSIG (${_qtype}) not reused in zone ${ZONE}" + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + done + done +} + +check_rrsig_reuse + +# +# Zone: unfresh-sigs.autosign. +# +zone_properties "ns3" "unfresh-sigs.autosign" "autosign" "300" "2" +# key_properties, key_timings and key_states same as above. +check_keys +check_apex +check_subdomain +dnssec_verify +check_rrsig_refresh + +# +# Zone: zsk-missing.autosign. +# +zone_properties "ns3" "zsk-missing.autosign" "autosign" "300" "2" +# KSK stays OMNIPRESENT. +key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" +# key_properties, key_timings and key_states same as above. +# TODO + +# +# Zone: zsk-retired.autosign. +# +zone_properties "ns3" "zsk-retired.autosign" "autosign" "300" "3" +# KSK properties, timings and states same as above. +# The ZSK goal is set to HIDDEN but records stay OMNIPRESENT until the new ZSK +# is active. +key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" +key_timings "KEY2" "published" "active" "retired" "none" "none" +key_states "KEY2" "hidden" "omnipresent" "omnipresent" "none" "none" +# A new ZSK should be introduced, so expect a key with goal OMNIPRESENT, +# the DNSKEY introduced (RUMOURED) and the signatures HIDDEN. +key_properties "KEY3" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "no" +key_timings "KEY3" "published" "active" "retired" "none" "none" +key_states "KEY3" "omnipresent" "rumoured" "hidden" "none" "none" + +# +# Testing ZSK Pre-Publication rollover. +# + +# +# Zone: step1.zsk-prepub.autosign. +# +zone_properties "ns3" "step1.zsk-prepub.autosign" "zsk-prepub" "3600" "2" +# Both KSK (KEY1) and ZSK (KEY2) start in OMNIPRESENT. +key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "none" "none" +key_timings "KEY2" "published" "active" "retired" "none" "none" +# Initially only two keys. +key_clear "KEY3" +check_keys +check_apex +check_subdomain +dnssec_verify + +check_next_key_event() { + _expect=$1 + + n=$((n+1)) + echo_i "check next key event for zone ${ZONE} ($n)" + ret=0 + grep "zone ${ZONE}.*: next key event in .* seconds" "${DIR}/named.run" > keyevent.out.$ZONE.test$n || log_error "no next key event for zone ${ZONE}" + + _time=$(awk '{print $10}' < keyevent.out.$ZONE.test$n) + + # The next key event time must within 10 seconds of the + # expected time. + _expectmin=$((_expect-10)) + _expectmax=$((_expect+10)) + + test $_expectmin -le $_time || log_error "bad next key event time ${_time} for zone ${ZONE} (expect ${_expect})" + test $_expectmax -ge $_time || log_error "bad next key event time ${_time} for zone ${ZONE} (expect ${_expect})" + + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + +# Next key event is when the successor ZSK needs to be published. That is +# the ZSK lifetime - prepublication time. The prepublication time is DNSKEY +# TTL plus publish safety plus the zone propagation delay. For the +# zsk-prepub policy that means: 30d - 3600s + 1d + 1h = 2498400 seconds. +check_next_key_event 2498400 + +# +# Zone: step2.zsk-prepub.autosign. +# +zone_properties "ns3" "step2.zsk-prepub.autosign" "zsk-prepub" "3600" "3" +# KSK (KEY1) doesn't change. +# ZSK (KEY2) remains active, no change in properties/timings/states. +# New ZSK (KEY3) is prepublished. +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" +key_states "KEY3" "omnipresent" "rumoured" "hidden" "none" "none" +key_timings "KEY3" "published" "active" "retired" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor ZSK becomes OMNIPRESENT. That is the +# DNSKEY TTL plus the zone propagation delay, plus the publish-safety. For +# the zsk-prepub policy, this means: 3600s + 1h + 1d = 93600 seconds. +check_next_key_event 93600 + +# +# Zone: step3.zsk-prepub.autosign. +# +zone_properties "ns3" "step3.zsk-prepub.autosign" "zsk-prepub" "3600" "3" +# KSK (KEY1) doesn't change. +# ZSK (KEY2) properties and timing metadata same as above. +# ZSK (KEY2) no longer is actively signing, RRSIG state in UNRETENTIVE. +# New ZSK (KEY3) is now actively signing, RRSIG state in RUMOURED. +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" +key_states "KEY2" "hidden" "omnipresent" "unretentive" "none" "none" + +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" +key_states "KEY3" "omnipresent" "omnipresent" "rumoured" "none" "none" +check_keys +check_apex +# Subdomain still has good signatures of ZSK (KEY2) +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" +check_subdomain +dnssec_verify + +# Next key event is when all the RRSIG records have been replaced with +# signatures of the new ZSK, in other words when ZRRSIG becomes OMNIPRESENT. +# That is Dsgn plus the maximum zone TTL plus the zone propagation delay plus +# retire-safety. For the zsk-prepub policy that means: 1w (because 2w validity +# and refresh within a week) + 1d + 1h + 2d = 10d1h = 867600 seconds. +check_next_key_event 867600 + +# +# Zone: step4.zsk-prepub.autosign. +# +zone_properties "ns3" "step4.zsk-prepub.autosign" "zsk-prepub" "3600" "3" +# KSK (KEY1) doesn't change. +# ZSK (KEY2) properties and timing metadata same as above. +# ZSK (KEY2) DNSKEY is no longer needed. +# ZSK (KEY3) is now actively signing, RRSIG state in RUMOURED. +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" +key_states "KEY2" "hidden" "unretentive" "hidden" "none" "none" +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" +key_states "KEY3" "omnipresent" "omnipresent" "omnipresent" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DNSKEY enters the HIDDEN state. This is the +# DNSKEY TTL plus zone propagation delay. For the zsk-prepub policy this is: +# 3600s + 1h = 7200s +check_next_key_event 7200 + +# +# Zone: step5.zsk-prepub.autosign. +# +zone_properties "ns3" "step5.zsk-prepub.autosign" "zsk-prepub" "3600" "3" +# KSK (KEY1) doesn't change. +# ZSK (KEY2) properties and timing metadata same as above. +# ZSK (KEY3) DNSKEY is now completely HIDDEN and removed. +key_timings "KEY2" "published" "active" "retired" "none" "removed" +key_states "KEY2" "hidden" "hidden" "hidden" "none" "none" +# ZSK (KEY3) remains actively signing, staying in OMNIPRESENT. +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the new successor needs to be published. This is the +# ZSK lifetime minus Iret minus Ipub minus DNSKEY TTL. For the zsk-prepub +# policy this is: 30d - 867600s - 93600s - 3600s = 1627200 seconds. +check_next_key_event 1627200 + +# +# Testing KSK Double-KSK rollover. +# + +# +# Zone: step1.ksk-doubleksk.autosign. +# +zone_properties "ns3" "step1.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "2" +# Both KSK (KEY1) and ZSK (KEY2) start in OMNIPRESENT. +key_properties "KEY1" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" +key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" +key_timings "KEY2" "published" "active" "retired" "none" "none" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "none" "none" +# Initially only two keys. +key_clear "KEY3" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor KSK needs to be published. That is +# the KSK lifetime - prepublication time - DS registration delay. The +# prepublication time is DNSKEY TTL plus publish safety plus the zone +# propagation delay. For the ksk-doubleksk policy that means: +# 60d - (1d3h) - (1d) = 5000400 seconds. +check_next_key_event 5000400 + +# +# Zone: step2.ksk-doubleksk.autosign. +# +zone_properties "ns3" "step2.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" +# ZSK (KEY2) doesn't change. +# KSK (KEY1) remains active, no change in properties/timings/states. +# New KSK (KEY3) is prepublished (and signs DNSKEY RRset). +key_properties "KEY3" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "yes" +key_states "KEY3" "omnipresent" "rumoured" "none" "rumoured" "hidden" +key_timings "KEY3" "published" "active" "retired" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor KSK becomes OMNIPRESENT. That is the +# DNSKEY TTL plus the zone propagation delay, plus the publish-safety. For +# the ksk-doubleksk policy, this means: 7200s + 1h + 1d = 97200 seconds. +check_next_key_event 97200 + +# +# Zone: step3.ksk-doubleksk.autosign. +# +zone_properties "ns3" "step3.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" +# ZSK (KEY2) doesn't change. +# KSK (KEY1) DS will be removed, so it is UNRETENTIVE. +key_states "KEY1" "hidden" "omnipresent" "none" "omnipresent" "unretentive" +# New KSK (KEY3) has its DS submitted. +key_states "KEY3" "omnipresent" "omnipresent" "none" "omnipresent" "rumoured" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the predecessor DS has been replaced with the +# successor DS and enough time has passed such that the all validators that +# have this DS RRset cached only know about the successor DS. This is the +# registration delay plus the retire interval, which is the parent +# propagation delay plus the DS TTL plus the retire-safety. For the +# ksk-double-ksk policy this means: 1d + 1h + 3600s + 2d = 3d2h = +# 266400 seconds. +check_next_key_event 266400 + +# +# Zone: step4.ksk-doubleksk.autosign. +# +zone_properties "ns3" "step4.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" +# ZSK (KEY2) doesn't change. +# KSK (KEY1) DNSKEY can be removed. +key_properties "KEY1" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "no" +key_states "KEY1" "hidden" "unretentive" "none" "unretentive" "hidden" +# New KSK (KEY3) DS is now OMNIPRESENT. +key_states "KEY3" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DNSKEY enters the HIDDEN state. This is the +# DNSKEY TTL plus zone propagation delay. For the ksk-doubleksk policy this is: +# 7200s + 1h = 10800s +check_next_key_event 10800 + +# +# Zone: step5.ksk-doubleksk.autosign. +# +zone_properties "ns3" "step5.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" +# ZSK (KEY2) doesn't change. +# KSK (KEY1) DNSKEY is now HIDDEN. +key_states "KEY1" "hidden" "hidden" "none" "hidden" "hidden" +# New KSK (KEY3) stays OMNIPRESENT. +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the new successor needs to be published. This is the +# KSK lifetime minus Ipub minus Dreg minus Iret minus DNSKEY TTL. For the +# ksk-doubleksk this is: 60d - 1d3h - 1d - 2d2h - 2h = +# 5184000 - 97200 - 86400 - 180000 - 7200 = 4813200 seconds. +check_next_key_event 4813200 + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 From 6468ffc3369c290d7aaa92a33de0ce8979b2cdae Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 21 Oct 2019 13:26:30 +0200 Subject: [PATCH 31/43] Use keywords in dnssec-policy keys configuration Add keywords 'lifetime' and 'algorithm' to make the key configuration more clear. --- bin/named/named.conf.docbook | 2 +- bin/tests/system/checkconf/good-kasp.conf | 6 ++-- bin/tests/system/kasp/kasp.conf | 8 ++--- .../system/kasp/ns3/policies/autosign.conf | 12 +++---- bin/tests/system/kasp/ns3/policies/kasp.conf | 36 +++++++++---------- doc/arm/Bv9ARM-book.xml | 6 ++-- doc/arm/dnssec.xml | 2 +- doc/design/dnssec-policy | 6 ++-- doc/misc/options | 2 +- lib/isccfg/namedconf.c | 19 ++++++++-- 10 files changed, 57 insertions(+), 42 deletions(-) diff --git a/bin/named/named.conf.docbook b/bin/named/named.conf.docbook index f9696fa3fa..61016b6094 100644 --- a/bin/named/named.conf.docbook +++ b/bin/named/named.conf.docbook @@ -1015,7 +1015,7 @@ zone string [ class ] { dnssec-policy string { dnskey-ttl ttlval; - keys { ( csk | ksk | zsk ) key-directory duration integer [ integer ] ; ... }; + keys { ( csk | ksk | zsk ) key-directory lifetime duration algorithm integer [ integer ] ; ... }; parent-ds-ttl duration; parent-propagation-delay duration; parent-registration-delay duration; diff --git a/bin/tests/system/checkconf/good-kasp.conf b/bin/tests/system/checkconf/good-kasp.conf index 804637a345..041e6bfae8 100644 --- a/bin/tests/system/checkconf/good-kasp.conf +++ b/bin/tests/system/checkconf/good-kasp.conf @@ -17,9 +17,9 @@ dnssec-policy "test" { dnskey-ttl 3600; keys { - ksk key-directory P1Y 13 256; - zsk key-directory P30D 13; - csk key-directory P30D 8 2048; + ksk key-directory lifetime P1Y algorithm 13 256; + zsk key-directory lifetime P30D algorithm 13; + csk key-directory lifetime P30D algorithm 8 2048; }; publish-safety PT3600S; retire-safety PT3600S; diff --git a/bin/tests/system/kasp/kasp.conf b/bin/tests/system/kasp/kasp.conf index 2ef71b3f4d..5b09682fcf 100644 --- a/bin/tests/system/kasp/kasp.conf +++ b/bin/tests/system/kasp/kasp.conf @@ -17,9 +17,9 @@ dnssec-policy "kasp" { dnskey-ttl 200; keys { - csk key-directory P1Y 13; - ksk key-directory P1Y 8; - zsk key-directory P30D 8 1024; - zsk key-directory P6M 8 2000; + csk key-directory lifetime P1Y algorithm 13; + ksk key-directory lifetime P1Y algorithm 8; + zsk key-directory lifetime P30D algorithm 8 1024; + zsk key-directory lifetime P6M algorithm 8 2000; }; }; diff --git a/bin/tests/system/kasp/ns3/policies/autosign.conf b/bin/tests/system/kasp/ns3/policies/autosign.conf index 3a0d028d00..f04d219e6d 100644 --- a/bin/tests/system/kasp/ns3/policies/autosign.conf +++ b/bin/tests/system/kasp/ns3/policies/autosign.conf @@ -18,8 +18,8 @@ dnssec-policy "autosign" { dnskey-ttl 300; keys { - ksk key-directory P2Y 13; - zsk key-directory P1Y 13; + ksk key-directory lifetime P2Y algorithm 13; + zsk key-directory lifetime P1Y algorithm 13; }; }; @@ -34,8 +34,8 @@ dnssec-policy "zsk-prepub" { retire-safety P2D; keys { - ksk key-directory P2Y 13; - zsk key-directory P30D 13; + ksk key-directory lifetime P2Y algorithm 13; + zsk key-directory lifetime P30D algorithm 13; }; zone-propagation-delay PT1H; @@ -53,8 +53,8 @@ dnssec-policy "ksk-doubleksk" { retire-safety P2D; keys { - ksk key-directory P60D 13; - zsk key-directory P1Y 13; + ksk key-directory lifetime P60D algorithm 13; + zsk key-directory lifetime P1Y algorithm 13; }; zone-propagation-delay PT1H; diff --git a/bin/tests/system/kasp/ns3/policies/kasp.conf b/bin/tests/system/kasp/ns3/policies/kasp.conf index 547c5c0429..fa60476e65 100644 --- a/bin/tests/system/kasp/ns3/policies/kasp.conf +++ b/bin/tests/system/kasp/ns3/policies/kasp.conf @@ -13,9 +13,9 @@ dnssec-policy "rsasha1" { dnskey-ttl 1234; keys { - ksk key-directory P10Y 5; - zsk key-directory P5Y 5; - zsk key-directory P1Y 5 2000; + ksk key-directory lifetime P10Y algorithm 5; + zsk key-directory lifetime P5Y algorithm 5; + zsk key-directory lifetime P1Y algorithm 5 2000; }; }; @@ -23,9 +23,9 @@ dnssec-policy "rsasha1-nsec3" { dnskey-ttl 1234; keys { - ksk key-directory P10Y 7; - zsk key-directory P5Y 7; - zsk key-directory P1Y 7 2000; + ksk key-directory lifetime P10Y algorithm 7; + zsk key-directory lifetime P5Y algorithm 7; + zsk key-directory lifetime P1Y algorithm 7 2000; }; }; @@ -33,9 +33,9 @@ dnssec-policy "rsasha256" { dnskey-ttl 1234; keys { - ksk key-directory P10Y 8; - zsk key-directory P5Y 8; - zsk key-directory P1Y 8 2000; + ksk key-directory lifetime P10Y algorithm 8; + zsk key-directory lifetime P5Y algorithm 8; + zsk key-directory lifetime P1Y algorithm 8 2000; }; }; @@ -43,9 +43,9 @@ dnssec-policy "rsasha512" { dnskey-ttl 1234; keys { - ksk key-directory P10Y 10; - zsk key-directory P5Y 10; - zsk key-directory P1Y 10 2000; + ksk key-directory lifetime P10Y algorithm 10; + zsk key-directory lifetime P5Y algorithm 10; + zsk key-directory lifetime P1Y algorithm 10 2000; }; }; @@ -53,9 +53,9 @@ dnssec-policy "ecdsa256" { dnskey-ttl 1234; keys { - ksk key-directory P10Y 13; - zsk key-directory P5Y 13; - zsk key-directory P1Y 13 256; + ksk key-directory lifetime P10Y algorithm 13; + zsk key-directory lifetime P5Y algorithm 13; + zsk key-directory lifetime P1Y algorithm 13 256; }; }; @@ -63,8 +63,8 @@ dnssec-policy "ecdsa384" { dnskey-ttl 1234; keys { - ksk key-directory P10Y 14; - zsk key-directory P5Y 14; - zsk key-directory P1Y 14 384; + ksk key-directory lifetime P10Y algorithm 14; + zsk key-directory lifetime P5Y algorithm 14; + zsk key-directory lifetime P1Y algorithm 14 384; }; }; diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index d0c21560d9..2562c1f348 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -11059,9 +11059,9 @@ example.com CNAME rpz-tcp-only. keys { - ksk key-directory P5Y 8 2048; - zsk key-directory P30D 8; - csk key-directory P6MT12H3M15S 13; + ksk key-directory lifetime P5Y algorithm 8 2048; + zsk key-directory lifetime P30D algorithm 8; + csk key-directory lifetime P6MT12H3M15S algorithm 13; }; diff --git a/doc/arm/dnssec.xml b/doc/arm/dnssec.xml index be702849c3..3c0cf4dfec 100644 --- a/doc/arm/dnssec.xml +++ b/doc/arm/dnssec.xml @@ -54,7 +54,7 @@ dnssec-policy csk { keys { - csk key-directory P5Y 13; + csk key-directory lifetime P5Y algorithm 13; }; }; diff --git a/doc/design/dnssec-policy b/doc/design/dnssec-policy index 3e695a2c39..73f032b77d 100644 --- a/doc/design/dnssec-policy +++ b/doc/design/dnssec-policy @@ -199,9 +199,9 @@ is referred to as a CSK. Below is an example configuration for the three types of keys: ``` keys { - ksk key-directory P5Y ECDSAP256SHA256; - zsk key-directory P30D ECDSAP256SHA256; - csk key-directory PT0S 8 2048; + ksk key-directory lifetime P5Y algorithm ECDSAP256SHA256; + zsk key-directory lifetime P30D algorithm ECDSAP256SHA256; + csk key-directory lifetime PT0S algorithm 8 2048; }; ``` diff --git a/doc/misc/options b/doc/misc/options index cb00923715..61dad9bbba 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -27,7 +27,7 @@ dnssec-keys { ( static-key | dnssec-policy { dnskey-ttl ; - keys { ( csk | ksk | zsk ) key-directory + keys { ( csk | ksk | zsk ) key-directory lifetime algorithm [ ]; ... }; parent-ds-ttl ; parent-propagation-delay ; diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 7d0dd467db..746ee47a23 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -502,11 +502,23 @@ static cfg_type_t cfg_type_dnsseckeystore = { /*% * A dnssec key, as used in the "keys" statement in a "dnssec-policy". */ +static keyword_type_t algorithm_kw = { "algorithm", &cfg_type_uint32 }; +static cfg_type_t cfg_type_algorithm = { + "algorithm", parse_keyvalue, print_keyvalue, + doc_keyvalue, &cfg_rep_uint32, &algorithm_kw +}; + +static keyword_type_t lifetime_kw = { "lifetime", &cfg_type_duration }; +static cfg_type_t cfg_type_lifetime = { + "lifetime", parse_keyvalue, print_keyvalue, + doc_keyvalue, &cfg_rep_duration, &lifetime_kw +}; + static cfg_tuplefielddef_t kaspkey_fields[] = { { "role", &cfg_type_dnsseckeyrole, 0 }, { "keystore-type", &cfg_type_dnsseckeystore, 0 }, - { "lifetime", &cfg_type_duration, 0 }, - { "algorithm", &cfg_type_uint32, 0 }, + { "lifetime", &cfg_type_lifetime, 0 }, + { "algorithm", &cfg_type_algorithm, 0 }, { "length", &cfg_type_optional_uint32, 0 }, { NULL, NULL, 0 } }; @@ -515,6 +527,9 @@ static cfg_type_t cfg_type_kaspkey = { &cfg_rep_tuple, kaspkey_fields }; +/*% + * Wild class, type, name. + */ static keyword_type_t wild_class_kw = { "class", &cfg_type_ustring }; static cfg_type_t cfg_type_optional_wild_class = { From 67033bfd3d5280cfd14950705f0835522ef163e8 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 30 Oct 2019 14:38:28 +0100 Subject: [PATCH 32/43] Code changes for CSK Update dns_dnssec_keyactive to differentiate between the roles ZSK and KSK. A key is active if it is signing but that differs per role. A ZSK is signing if its ZRRSIG state is in RUMOURED or OMNIPRESENT, a KSK is signing if its KRRSIG state is in RUMOURED or OMNIPRESENT. This means that a key can be actively signing for one role but not the other. Add checks in inline signing (zone.c and update.c) to cover the case where a CSK is active in its KSK role but not the ZSK role. --- lib/dns/dnssec.c | 25 +++++++++++++++++++++---- lib/dns/dst_api.c | 7 +++---- lib/dns/include/dst/dst.h | 5 +++-- lib/dns/update.c | 9 +++++++++ lib/dns/zone.c | 26 ++++++++++++++++++++------ 5 files changed, 56 insertions(+), 16 deletions(-) diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c index ac70dc9703..388689144b 100644 --- a/lib/dns/dnssec.c +++ b/lib/dns/dnssec.c @@ -584,13 +584,25 @@ bool dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) { isc_result_t result; isc_stdtime_t publish, active, revoke, remove; - bool hint_publish, hint_sign, hint_revoke, hint_remove; + bool hint_publish, hint_zsign, hint_ksign, hint_revoke, hint_remove; int major, minor; + bool ksk = false, zsk = false; + isc_result_t ret; /* Is this an old-style key? */ result = dst_key_getprivateformat(key, &major, &minor); RUNTIME_CHECK(result == ISC_R_SUCCESS); + /* Is this a KSK? */ + ret = dst_key_getbool(key, DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) != 0); + } + ret = dst_key_getbool(key, DST_BOOL_ZSK, &zsk); + if (ret != ISC_R_SUCCESS) { + zsk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) == 0); + } + /* * Smart signing started with key format 1.3; prior to that, all * keys are assumed active. @@ -599,7 +611,8 @@ dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) { return (true); hint_publish = dst_key_is_published(key, now, &publish); - hint_sign = dst_key_is_signing(key, now, &active); + hint_zsign = dst_key_is_signing(key, DST_BOOL_ZSK, now, &active); + hint_ksign = dst_key_is_signing(key, DST_BOOL_KSK, now, &active); hint_revoke = dst_key_is_revoked(key, now, &revoke); hint_remove = dst_key_is_removed(key, now, &remove); @@ -609,7 +622,10 @@ dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) { if (hint_publish && hint_revoke) { return (true); } - if (hint_sign) { + if (hint_zsign && zsk) { + return (true); + } + if (hint_ksign && ksk) { return (true); } return (false); @@ -1255,7 +1271,8 @@ dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) { REQUIRE(key != NULL && key->key != NULL); key->hint_publish = dst_key_is_published(key->key, now, &publish); - key->hint_sign = dst_key_is_signing(key->key, now, &active); + key->hint_sign = dst_key_is_signing(key->key, DST_BOOL_ZSK, now, + &active); key->hint_revoke = dst_key_is_revoked(key->key, now, &revoke); key->hint_remove = dst_key_is_removed(key->key, now, &remove); diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index d8d8869e4a..cfa4bbcda6 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -2476,7 +2476,7 @@ dst_key_is_active(dst_key_t *key, isc_stdtime_t now) bool -dst_key_is_signing(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *active) +dst_key_is_signing(dst_key_t *key, int role, isc_stdtime_t now, isc_stdtime_t *active) { dst_key_state_t state; isc_result_t result; @@ -2503,7 +2503,7 @@ dst_key_is_signing(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *active) * If the RRSIG state is RUMOURED or OMNIPRESENT, it means the key * is active. */ - if (ksk) { + if (ksk && role == DST_BOOL_KSK) { result = dst_key_getstate(key, DST_KEY_KRRSIG, &state); if (result == ISC_R_SUCCESS) { krrsig_ok = ((state == DST_KEY_STATE_RUMOURED) || @@ -2515,8 +2515,7 @@ dst_key_is_signing(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *active) time_ok = true; inactive = false; } - } - if (zsk) { + } else if (zsk && role == DST_BOOL_ZSK) { result = dst_key_getstate(key, DST_KEY_ZRRSIG, &state); if (result == ISC_R_SUCCESS) { zrrsig_ok = ((state == DST_KEY_STATE_RUMOURED) || diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index a37725f404..febadabae7 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -1138,9 +1138,10 @@ dst_key_is_active(dst_key_t *key, isc_stdtime_t now); */ bool -dst_key_is_signing(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *active); +dst_key_is_signing(dst_key_t *key, int role, isc_stdtime_t now, + isc_stdtime_t *active); /*%< - * Check if it is safe to use this key for signing. + * Check if it is safe to use this key for signing, given the role. * * Requires: * 'key' to be valid. diff --git a/lib/dns/update.c b/lib/dns/update.c index 4c621c404f..9bd5896796 100644 --- a/lib/dns/update.c +++ b/lib/dns/update.c @@ -1167,6 +1167,7 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, * A dnssec-policy is found. Check what RRsets this * key should sign. */ + isc_stdtime_t when; isc_result_t kresult; bool ksk = false; bool zsk = false; @@ -1200,6 +1201,14 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, * Other RRsets are signed with ZSK. */ continue; + } else if (zsk && !dst_key_is_signing(keys[i], + DST_BOOL_ZSK, + inception, + &when)) { + /* + * This key is not active for zone-signing. + */ + continue; } /* diff --git a/lib/dns/zone.c b/lib/dns/zone.c index c88c1714ac..85832daef5 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -6616,6 +6616,7 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, * key should sign. */ isc_result_t kresult; + isc_stdtime_t when; bool ksk = false; bool zsk = false; @@ -6648,6 +6649,12 @@ add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, * Other RRsets are signed with ZSK. */ continue; + } else if (!dst_key_is_signing(keys[i], DST_BOOL_ZSK, + inception, &when)) { + /* + * This key is not active for zone-signing. + */ + continue; } /* @@ -7027,8 +7034,8 @@ signed_with_good_key(dns_zone_t* zone, dns_db_t *db, dns_dbnode_t *node, if (kasp) { dns_kasp_key_t* kkey; - int ksk_count = 0, zsk_count = 0; - bool approved = false; + int zsk_count = 0; + bool approved; for (kkey = ISC_LIST_HEAD(kasp->keys); kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link)) @@ -7036,9 +7043,6 @@ signed_with_good_key(dns_zone_t* zone, dns_db_t *db, dns_dbnode_t *node, if (dns_kasp_key_algorithm(kkey) != dst_key_alg(key)) { continue; } - if (dns_kasp_key_ksk(kkey)) { - ksk_count++; - } if (dns_kasp_key_zsk(kkey)) { zsk_count++; } @@ -7053,7 +7057,7 @@ signed_with_good_key(dns_zone_t* zone, dns_db_t *db, dns_dbnode_t *node, * be signed with a key in the current DS RRset, * which would only include KSK's.) */ - approved = (ksk_count == count); + approved = false; } else { approved = (zsk_count == count); } @@ -7149,6 +7153,7 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, dns_diff_t *diff, int32_t *signatures, isc_mem_t *mctx) { isc_result_t result; + dns_kasp_t *kasp = dns_zone_getkasp(zone); dns_rdatasetiter_t *iterator = NULL; dns_rdataset_t rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; @@ -7216,6 +7221,8 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, } result = dns_rdatasetiter_first(iterator); while (result == ISC_R_SUCCESS) { + isc_stdtime_t when; + dns_rdatasetiter_current(iterator, &rdataset); if (rdataset.type == dns_rdatatype_soa || rdataset.type == dns_rdatatype_rrsig) @@ -7237,7 +7244,14 @@ sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, } } else if (!is_zsk) { goto next_rdataset; + } else if (is_zsk && !dst_key_is_signing(key, DST_BOOL_ZSK, + inception, &when)) { + /* Only applies to dnssec-policy. */ + if (kasp != NULL) { + goto next_rdataset; + } } + if (seen_ns && !seen_soa && rdataset.type != dns_rdatatype_ds && rdataset.type != dns_rdatatype_nsec) From 9fbc86910833bb09ff02801cef22c3f59a29a820 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 30 Oct 2019 16:45:41 +0100 Subject: [PATCH 33/43] Test CSK rollover Test two CSK rollover scenarios, one where the DS is swapped before the zone signatures are all replaced, and one where the signatures are replaced sooner than the DS is swapped. --- bin/tests/system/kasp/ns3/named.conf.in | 70 +++ .../system/kasp/ns3/policies/autosign.conf | 44 ++ bin/tests/system/kasp/ns3/setup.sh | 289 ++++++++++++ bin/tests/system/kasp/tests.sh | 412 +++++++++++++++--- 4 files changed, 763 insertions(+), 52 deletions(-) diff --git a/bin/tests/system/kasp/ns3/named.conf.in b/bin/tests/system/kasp/ns3/named.conf.in index c7a830dc3a..1e11814542 100644 --- a/bin/tests/system/kasp/ns3/named.conf.in +++ b/bin/tests/system/kasp/ns3/named.conf.in @@ -233,3 +233,73 @@ zone "step6.ksk-doubleksk.autosign" { file "step6.ksk-doubleksk.autosign.db"; dnssec-policy "ksk-doubleksk"; }; + +/* + * Zones for testing CSK rollover steps. + */ +zone "step1.csk-roll.autosign" { + type master; + file "step1.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step2.csk-roll.autosign" { + type master; + file "step2.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step3.csk-roll.autosign" { + type master; + file "step3.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step4.csk-roll.autosign" { + type master; + file "step4.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step5.csk-roll.autosign" { + type master; + file "step5.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step6.csk-roll.autosign" { + type master; + file "step6.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; +zone "step7.csk-roll.autosign" { + type master; + file "step7.csk-roll.autosign.db"; + dnssec-policy "csk-roll"; +}; + +zone "step1.csk-roll2.autosign" { + type master; + file "step1.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; +zone "step2.csk-roll2.autosign" { + type master; + file "step2.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; +zone "step3.csk-roll2.autosign" { + type master; + file "step3.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; +zone "step4.csk-roll2.autosign" { + type master; + file "step4.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; +zone "step5.csk-roll2.autosign" { + type master; + file "step5.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; +zone "step6.csk-roll2.autosign" { + type master; + file "step6.csk-roll2.autosign.db"; + dnssec-policy "csk-roll2"; +}; diff --git a/bin/tests/system/kasp/ns3/policies/autosign.conf b/bin/tests/system/kasp/ns3/policies/autosign.conf index f04d219e6d..664693a445 100644 --- a/bin/tests/system/kasp/ns3/policies/autosign.conf +++ b/bin/tests/system/kasp/ns3/policies/autosign.conf @@ -64,3 +64,47 @@ dnssec-policy "ksk-doubleksk" { parent-registration-delay P1D; parent-propagation-delay PT1H; }; + +dnssec-policy "csk-roll" { + + signatures-refresh P5D; + signatures-validity 30d; + signatures-validity-dnskey 30d; + + dnskey-ttl 1h; + publish-safety PT1H; + retire-safety 2h; + + keys { + csk key-directory lifetime P6M algorithm 13; + }; + + zone-propagation-delay 1h; + zone-max-ttl P1D; + + parent-ds-ttl 1h; + parent-registration-delay 1d; + parent-propagation-delay 1h; +}; + +dnssec-policy "csk-roll2" { + + signatures-refresh 12h; + signatures-validity P1D; + signatures-validity-dnskey P1D; + + dnskey-ttl 1h; + publish-safety PT1H; + retire-safety 1h; + + keys { + csk key-directory lifetime P6M algorithm 13; + }; + + zone-propagation-delay PT1H; + zone-max-ttl 1d; + + parent-ds-ttl PT1H; + parent-registration-delay P1W; + parent-propagation-delay PT1H; +}; diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh index b384fa730b..782747b4b8 100644 --- a/bin/tests/system/kasp/ns3/setup.sh +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -356,3 +356,292 @@ private_type_record $zone 13 $KSK1 >> "$infile" private_type_record $zone 13 $KSK2 >> "$infile" private_type_record $zone 13 $ZSK >> "$infile" $SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# +# The zones at csk-roll.autosign represent the various steps of a CSK rollover +# (which is essentially a ZSK Pre-Publication / KSK Double-KSK rollover). +# + +# Step 1: +# Introduce the first key. This will immediately be active. +setup step1.csk-roll.autosign +CSK=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +TactN="now" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 13 $CSK >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 2: +# It is time to introduce the new CSK. +setup step2.csk-roll.autosign +CSK=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: ZSK: Tpub(N+1) <= Tact(N) + Lzsk - Ipub +# According to RFC 7583: KSK: Tpub(N+1) <= Tact(N) + Lksk - Dreg - IpubC +# Also: Ipub = Dprp + TTLkey (+publish-safety) +# Also: IpubC = DprpC + TTLkey (+publish-safety) +# Both sums are almost the same, but the KSK case has Dreg in the equation. +# so: Tact(N) = Tpub(N+1) - Lcsk + Dreg + IpubC = now - 6mo + 1d + 3h = +# now - 4464h + 24h + 3h = now - 4437h +TactN="now-4437h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 13 $CSK >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 3: +# It is time to submit the DS and to roll signatures. +setup step3.csk-roll.autosign +CSK1=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: Tsbm(N+1) >= Trdy(N+1) +# Also: Tact(N+1) = Tsbm(N+1) + Dreg +# so: Tact(N) = Tsbm(N+1) + Dreg - Lksk = now + 1d - 6mo = now - 185d +# and: Tret(N) = Tsbm(N+1) + Dreg = now + 1d +# and: Tpub(N+1) <= Tsbm(N+1) - IpubC = now - 3h +# and: Tret(N+1) = Tsbm(N+1) + Dreg + Lksk = now + 1d + 6mo = now + 187d +TactN="now-185d" +TretN="now+1d" +TpubN1="now-3h" +TretN1="now+187d" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TretN -I $TretN1 -g $O -k $R $TpubN1 -r $R $TpubN1 -d $H $TpubN1 -z $H $TpubN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 4: +# Some time later all the ZRRSIG records should be from the new CSK, and the +# DS should be swapped. The ZRRSIG records are all replaced after Iret +# which is Dsgn + Dprp + TTLsig + retire-safety (25d + 1h + 1d + 2h = 26d3h). +# The DS is swapped after Dreg + DprpP + TTLds + retire-safety +# (1d + 1h + 1h + 2h = 1d4h). In other words, the DS is swapped before all +# zone signatures are replaced. +setup step4.csk-roll.autosign +CSK1=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: Tdea(N) = Tret(N) + Iret +# Also: Iret = 1h + 1h + 2h = 4h +# Also: Tact(N+1) = Tret(N) +# so: Tact(N) = Tdea(N) - Lksk - Iret = now - 6mo - 4h = now - 4468h +# and: Tret(N) = Tdea(N) - Iret = now - 4h = now - 4h +# and: Tpub(N+1) = Tdea(N) - Iret - Dreg - IpubC = now - 4h - 1d - 3h = now - 31h +# and: Tsbm(N+1) = Tdea(N) - Iret - Dreg = now - 4h - 1d = now - 28h +# and: Tact(N+1) = Tret(N) +# and: Tret(N+1) = Tdea(N) + Lksk - Iret = now + 6mo - 4h = now + 4460h +TactN="now-4468h" +TretN="now-4h" +TpubN1="now-31h" +TsbmN1="now-28h" +TactN1="${TretN}" +TretN1="now+4460h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $U $TsbmN1 -z $U $TsbmN1 $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TsbmN1 -r $O $TsbmN1 -d $R $TsbmN1 -z $R $TsbmN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 5: +# After the DS is swapped in step 4, also the KRRSIG records can be removed. +# At this time these have all become hidden. +setup step5.csk-roll.autosign +CSK1=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# Substract DNSKEY TTL plus zone propagation delay from all the times (2h). +TactN="now-4470h" +TretN="now-6h" +TdeaN="now-2h" +TpubN1="now-33h" +TsbmN1="now-30h" +TactN1="${TretN}" +TretN1="now+4458h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $U $TdeaN -d $H $TdeaN -z $U $TsbmN1 $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TsbmN1 -r $O $TsbmN1 -d $O $TdeaN -z $R $TsbmN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 6: +# After the retire interval has passed the predecessor DNSKEY can be +# removed from the zone. +setup step6.csk-roll.autosign +CSK1=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: Tdea(N) = Tret(N) + Iret +# Also: Tret(N) = Tact(N) + Lzsk +# Also: Iret = Dsgn + Dprp + TTLsig (+retire-safety) +# so: Tact(N) = Tdea(N) - Iret - Lzsk = now - 25d1h1d2h - 6mo = +# now - 26d3h - 6mo = now - 627h - 4464h = now - 5091h +# and: Tret(N) = Tdea(N) - Iret = now - 627h +# and: Tpub(N+1) = Tdea(N) - Iret - Ipub = now - 627h - 3h = now - 630h +# and: Tact(N+1) = Tdea(N) - Iret = Tret(N) +# and: Tret(N+1) = Tdea(N) - Iret + Lzsk = now - 627h + 6mo = now + 3837h +TactN="now-5091h" +TretN="now-627h" +TdeaN="now-623h" +TpubN1="now-630h" +TsbmN1="now-627h" +TactN1="${TretN}" +TretN1="now+3837h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $H $TdeaN -d $H $TdeaN -z $U $TsbmN1 $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TsbmN1 -r $O $TsbmN1 -d $O $TdeaN -z $R $TsbmN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 7: +# Some time later the predecessor DNSKEY enters the HIDDEN state. +setup step7.csk-roll.autosign +CSK1=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# Substract DNSKEY TTL plus zone propagation delay from all the times (2h). +TactN="now-5093h" +TretN="now-629h" +TdeaN="now-625h" +TpubN1="now-632h" +TsbmN1="now-629h" +TactN1="${TretN}" +TretN1="now+3835h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $U now-2h -r $H $TdeaN -d $H $TdeaN -z $H $TsbmN1 $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TsbmN1 -r $O $TsbmN1 -d $O $TdeaN -z $O $TsbmN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# +# The zones at csk-roll2.autosign represent the various steps of a CSK rollover +# (which is essentially a ZSK Pre-Publication / KSK Double-KSK rollover). +# This scenario differs from the above one because the zone signatures (ZRRSIG) +# are replaced with the new key sooner than the DS is swapped. +# + +# Step 1: +# Introduce the first key. This will immediately be active. +setup step1.csk-roll2.autosign +CSK=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +TactN="now" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 13 $CSK >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 2: +# It is time to introduce the new CSK. +setup step2.csk-roll2.autosign +CSK=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: ZSK: Tpub(N+1) <= Tact(N) + Lzsk - Ipub +# According to RFC 7583: KSK: Tpub(N+1) <= Tact(N) + Lksk - Dreg - IpubC +# Also: Ipub = Dprp + TTLkey (+publish-safety) +# Also: IpubC = DprpC + TTLkey (+publish-safety) +# Both sums are almost the same, but the KSK case has Dreg in the equation. +# so: Tact(N) = Tpub(N+1) - Lcsk + Dreg + IpubC = now - 6mo + 1w + 3h = +# now - 4464h + 168h + 3h = now - 4635h +TactN="now-4635h" +$SETTIME -s -P $TactN -A $TactN -g $O -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK}.key" > "$infile" +private_type_record $zone 13 $CSK >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 3: +# It is time to submit the DS and to roll signatures. +setup step3.csk-roll2.autosign +CSK1=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: Tsbm(N+1) >= Trdy(N+1) +# Also: Tact(N+1) = Tsbm(N+1) + Dreg +# so: Tact(N) = Tsbm(N+1) + Dreg - Lksk = now + 1w - 6mo = now - 179d +# and: Tret(N) = Tsbm(N+1) + Dreg = now + 1w +# and: Tpub(N+1) <= Tsbm(N+1) - IpubC = now - 3h +# and: Tret(N+1) = Tsbm(N+1) + Dreg + Lksk = now + 1w + 6mo = now + 193d +TactN="now-179d" +TretN="now+1w" +TpubN1="now-3h" +TretN1="now+193d" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $O $TactN -z $O $TactN $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TretN -I $TretN1 -g $O -k $R $TpubN1 -r $R $TpubN1 -d $H $TpubN1 -z $H $TpubN1 $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 4: +# Some time later all the ZRRSIG records should be from the new CSK, and the +# DS should be swapped. The ZRRSIG records are all replaced after Iret +# which is Dsgn + Dprp + TTLsig + retire-safety (12h + 1h + 1d + 2h = 38h). +# The DS is swapped after Dreg + DprpP + TTLds + retire-safety +# (1w + 1h + 1h + 1h = 1w3h). In other words, the zone signatures are +# replaced before the DS is swapped. +setup step4.csk-roll2.autosign +CSK1=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# According to RFC 7583: Tdea(N) = Tret(N) + Iret +# Also: Tret(N) = Tact(N) + Lzsk +# Also: Iret = Dsgn + Dprp + TTLsig (+retire-safety) +# so: Tact(N) = Tdea(N) - Iret - Lzsk = now - 38h - 6mo = now - 4502h +# and: Tret(N) = Tdea(N) - Iret = now - 38h +# and: Tpub(N+1) = Tdea(N) - Iret - Ipub = now - 41h +# and: Tact(N+1) = Tdea(N) - Iret = Tret(N) +# and: Tret(N+1) = Tdea(N) - Iret + Lzsk = now - 38h + 6mo = now + 4426h +TactN="now-4502h" +TretN="now-38h" +TpubN1="now-41h" +TactN1="${TretN}" +TretN1="now+4426" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $U $TretN -z $U $TretN $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TretN -r $O $TretN -d $R $TretN -z $R $TretN $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 5: +# Some time later the DS can be swapped and the old DNSKEY can be removed from +# the zone. +setup step5.csk-roll2.autosign +CSK1=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# Substract Dreg + Iret (174h). +TactN="now-4676h" +TretN="now-212h" +TpubN1="now-215h" +TactN1="${TretN}" +TretN1="now+4252h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $O $TactN -r $O $TactN -d $U $TretN -z $H $TretN $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TretN -r $O $TretN -d $R $TretN -z $O $TretN $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 + +# Step 6: +# Some time later the predecessor DNSKEY enters the HIDDEN state. +setup step6.csk-roll2.autosign +CSK1=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +CSK2=`$KEYGEN -k csk-roll2 -l policies/autosign.conf $zone 2> keygen.out.$zone.1` +# Substract DNSKEY TTL plus zone propagation delay (2h). +TactN="now-4678h" +TretN="now-214h" +TdeaN="now-2h" +TpubN1="now-217h" +TactN1="${TretN}" +TretN1="now+4250h" +$SETTIME -s -P $TactN -A $TactN -I $TretN -g $H -k $U $TdeaN -r $U $TdeaN -d $H $TretN -z $H $TretN $CSK1 > settime.out.$zone.1 2>&1 +$SETTIME -s -S $CSK1 -i 0 $CSK2 > settime.out.$zone.3 2>&1 +$SETTIME -s -P $TpubN1 -A $TactN1 -I $TretN1 -g $O -k $O $TretN -r $O $TretN -d $O $TretN -z $O $TretN $CSK2 > settime.out.$zone.1 2>&1 +cat template.db.in "${CSK1}.key" "${CSK2}.key" > "$infile" +private_type_record $zone 13 $CSK1 >> "$infile" +private_type_record $zone 13 $CSK2 >> "$infile" +$SIGNER -S -z -x -s now-1h -e now+30d -o $zone -O full -f $zonefile $infile > signer.out.$zone.1 2>&1 diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index 6bd1d3db94..42f2eef2ab 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -43,7 +43,8 @@ STATE_DNSKEY=15 STATE_ZRRSIG=16 STATE_KRRSIG=17 STATE_DS=18 -EXPECT_RRSIG=19 +EXPECT_ZRRSIG=19 +EXPECT_KRRSIG=20 # Clear key state. # @@ -58,7 +59,8 @@ key_clear() { [$REMOVED]="none" \ [$GOAL]="none" [$STATE_DNSKEY]="none" \ [$STATE_KRRSIG]="none" [$STATE_ZRRSIG]="none" \ - [$STATE_DS]="none" [$EXPECT_RRSIG]="no") + [$STATE_DS]="none" \ + [$EXPECT_ZRRSIG]="no" [$EXPECT_KRRSIG]="no") if [ $1 == "KEY1" ]; then KEY1=(${_key[*]}) @@ -140,7 +142,8 @@ zone_properties() { # $4: Algorithm (number) # $5: Algorithm (string-format) # $6: Algorithm length -# $7: Is signing +# $7: Is zone signing +# $8: Is key signing # # This will update either the KEY1, KEY2 or KEY3 array. key_properties() { @@ -157,7 +160,8 @@ key_properties() { KEY1[$ALG_NUM]=$4 KEY1[$ALG_STR]=$5 KEY1[$ALG_LEN]=$6 - KEY1[$EXPECT_RRSIG]=$7 + KEY1[$EXPECT_ZRRSIG]=$7 + KEY1[$EXPECT_KRRSIG]=$8 elif [ $1 == "KEY2" ]; then KEY2[$EXPECT]="yes" KEY2[$ROLE]=$2 @@ -171,7 +175,8 @@ key_properties() { KEY2[$ALG_NUM]=$4 KEY2[$ALG_STR]=$5 KEY2[$ALG_LEN]=$6 - KEY2[$EXPECT_RRSIG]=$7 + KEY2[$EXPECT_ZRRSIG]=$7 + KEY2[$EXPECT_KRRSIG]=$8 elif [ $1 == "KEY3" ]; then KEY3[$EXPECT]="yes" KEY3[$ROLE]=$2 @@ -185,7 +190,8 @@ key_properties() { KEY3[$ALG_NUM]=$4 KEY3[$ALG_STR]=$5 KEY3[$ALG_LEN]=$6 - KEY3[$EXPECT_RRSIG]=$7 + KEY3[$EXPECT_ZRRSIG]=$7 + KEY3[$EXPECT_KRRSIG]=$8 fi } @@ -510,7 +516,7 @@ test "$lines" -eq 4 || log_error "wrong number of keys created for policy kasp: # Temporarily don't log errors because we are searching multiple files. _log=0 # Check one algorithm. -key_properties "KEY1" "csk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY1" "csk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "yes" key_timings "KEY1" "none" "none" "none" "none" "none" key_states "KEY1" "none" "none" "none" "none" "none" id=$(get_keyids $DIR $ZONE "${KEY1[$ALG_NUM]}") @@ -518,15 +524,15 @@ check_key "KEY1" $id test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) # Check the other algorithm. -key_properties "KEY1" "ksk" "31536000" "8" "RSASHA256" "2048" "yes" +key_properties "KEY1" "ksk" "31536000" "8" "RSASHA256" "2048" "no" "yes" key_timings "KEY1" "none" "none" "none" "none" "none" key_states "KEY1" "none" "none" "none" "none" "none" -key_properties "KEY2" "zsk" "2592000" "8" "RSASHA256" "1024" "yes" +key_properties "KEY2" "zsk" "2592000" "8" "RSASHA256" "1024" "yes" "no" key_timings "KEY2" "none" "none" "none" "none" "none" key_states "KEY2" "none" "none" "none" "none" "none" -key_properties "KEY3" "zsk" "16070400" "8" "RSASHA256" "2000" "yes" +key_properties "KEY3" "zsk" "16070400" "8" "RSASHA256" "2000" "yes" "no" key_timings "KEY3" "none" "none" "none" "none" "none" key_states "KEY3" "none" "none" "none" "none" "none" @@ -552,7 +558,7 @@ n=$((n+1)) echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" ret=0 zone_properties "." "kasp" "default" "3600" -key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" key_timings "KEY1" "none" "none" "none" "none" "none" key_states "KEY1" "none" "none" "none" "none" "none" $KEYGEN -k $POLICY $ZONE > keygen.out.$POLICY.test$n 2>/dev/null || ret=1 @@ -567,7 +573,7 @@ n=$((n+1)) echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" ret=0 zone_properties "." "kasp" "default" "3600" -key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" key_timings "KEY1" "none" "none" "none" "none" "none" key_states "KEY1" "none" "none" "none" "none" "none" $KEYGEN -k $POLICY $ZONE > keygen.out.$POLICY.test$n 2>/dev/null || ret=1 @@ -667,7 +673,7 @@ status=$((status+ret)) # Check the zone with default kasp policy has loaded and is signed. zone_properties "ns3" "default.kasp" "_default" "3600" -key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" # The first key is immediately published and activated. key_timings "KEY1" "published" "active" "none" "none" "none" "none" # DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait. @@ -751,9 +757,9 @@ status=$((status+ret)) # Zone: rsasha1.kasp. # zone_properties "ns3" "rsasha1.kasp" "rsasha1" "1234" "3" -key_properties "KEY1" "ksk" "315360000" "5" "RSASHA1" "2048" "yes" -key_properties "KEY2" "zsk" "157680000" "5" "RSASHA1" "1024" "yes" -key_properties "KEY3" "zsk" "31536000" "5" "RSASHA1" "2000" "yes" +key_properties "KEY1" "ksk" "315360000" "5" "RSASHA1" "2048" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "5" "RSASHA1" "1024" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "5" "RSASHA1" "2000" "yes" "no" # The first keys are immediately published and activated. # Because lifetime > 0, retired timing is also set. key_timings "KEY1" "published" "active" "retired" "none" "none" @@ -855,19 +861,25 @@ check_signatures() { _file=$2 _role=$3 - if [ "${KEY1[$EXPECT_RRSIG]}" == "yes" ] && [ "${KEY1[$_role]}" == "yes" ]; then + if [ $_role == $KSK ]; then + _expect_type=$EXPECT_KRRSIG + elif [ $_role == $ZSK ]; then + _expect_type=$EXPECT_ZRRSIG + fi + + if [ "${KEY1[$_expect_type]}" == "yes" ] && [ "${KEY1[$_role]}" == "yes" ]; then get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with key ${KEY1[$ID]}" elif [ "${KEY1[$EXPECT]}" == "yes" ]; then get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY1[$ID]}" fi - if [ "${KEY2[$EXPECT_RRSIG]}" == "yes" ] && [ "${KEY2[$_role]}" == "yes" ]; then + if [ "${KEY2[$_expect_type]}" == "yes" ] && [ "${KEY2[$_role]}" == "yes" ]; then get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with ${KEY2[$ID]}" elif [ "${KEY2[$EXPECT]}" == "yes" ]; then get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY2[$ID]}" fi - if [ "${KEY3[$EXPECT_RRSIG]}" == "yes" ] && [ "${KEY3[$_role]}" == "yes" ]; then + if [ "${KEY3[$_expect_type]}" == "yes" ] && [ "${KEY3[$_role]}" == "yes" ]; then get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with ${KEY3[$ID]}" elif [ "${KEY3[$EXPECT]}" == "yes" ]; then get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY3[$ID]}" @@ -880,6 +892,7 @@ check_apex() { # Test DNSKEY query. _qtype="DNSKEY" + _key_algnum="${KEY1[$ALG_NUM]}" n=$((n+1)) echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)" ret=0 @@ -1017,9 +1030,9 @@ status=$((status+ret)) # Zone: rsasha1-nsec3.kasp. # zone_properties "ns3" "rsasha1-nsec3.kasp" "rsasha1-nsec3" "1234" "3" -key_properties "KEY1" "ksk" "315360000" "7" "NSEC3RSASHA1" "2048" "yes" -key_properties "KEY2" "zsk" "157680000" "7" "NSEC3RSASHA1" "1024" "yes" -key_properties "KEY3" "zsk" "31536000" "7" "NSEC3RSASHA1" "2000" "yes" +key_properties "KEY1" "ksk" "315360000" "7" "NSEC3RSASHA1" "2048" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "7" "NSEC3RSASHA1" "1024" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "7" "NSEC3RSASHA1" "2000" "yes" "no" # key_timings and key_states same as above. check_keys check_apex @@ -1030,9 +1043,9 @@ dnssec_verify # Zone: rsasha256.kasp. # zone_properties "ns3" "rsasha256.kasp" "rsasha256" "1234" "3" -key_properties "KEY1" "ksk" "315360000" "8" "RSASHA256" "2048" "yes" -key_properties "KEY2" "zsk" "157680000" "8" "RSASHA256" "1024" "yes" -key_properties "KEY3" "zsk" "31536000" "8" "RSASHA256" "2000" "yes" +key_properties "KEY1" "ksk" "315360000" "8" "RSASHA256" "2048" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "8" "RSASHA256" "1024" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "8" "RSASHA256" "2000" "yes" "no" # key_timings and key_states same as above. check_keys check_apex @@ -1043,9 +1056,9 @@ dnssec_verify # Zone: rsasha512.kasp. # zone_properties "ns3" "rsasha512.kasp" "rsasha512" "1234" "3" -key_properties "KEY1" "ksk" "315360000" "10" "RSASHA512" "2048" "yes" -key_properties "KEY2" "zsk" "157680000" "10" "RSASHA512" "1024" "yes" -key_properties "KEY3" "zsk" "31536000" "10" "RSASHA512" "2000" "yes" +key_properties "KEY1" "ksk" "315360000" "10" "RSASHA512" "2048" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "10" "RSASHA512" "1024" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "10" "RSASHA512" "2000" "yes" "no" # key_timings and key_states same as above. check_keys check_apex @@ -1056,9 +1069,9 @@ dnssec_verify # Zone: ecdsa256.kasp. # zone_properties "ns3" "ecdsa256.kasp" "ecdsa256" "1234" "3" -key_properties "KEY1" "ksk" "315360000" "13" "ECDSAP256SHA256" "256" "yes" -key_properties "KEY2" "zsk" "157680000" "13" "ECDSAP256SHA256" "256" "yes" -key_properties "KEY3" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY1" "ksk" "315360000" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "13" "ECDSAP256SHA256" "256" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "no" # key_timings and key_states same as above. check_keys check_apex @@ -1069,9 +1082,9 @@ dnssec_verify # Zone: ecdsa512.kasp. # zone_properties "ns3" "ecdsa384.kasp" "ecdsa384" "1234" "3" -key_properties "KEY1" "ksk" "315360000" "14" "ECDSAP384SHA384" "384" "yes" -key_properties "KEY2" "zsk" "157680000" "14" "ECDSAP384SHA384" "384" "yes" -key_properties "KEY3" "zsk" "31536000" "14" "ECDSAP384SHA384" "384" "yes" +key_properties "KEY1" "ksk" "315360000" "14" "ECDSAP384SHA384" "384" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "14" "ECDSAP384SHA384" "384" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "14" "ECDSAP384SHA384" "384" "yes" "no" # key_timings and key_states same as above. check_keys check_apex @@ -1085,10 +1098,10 @@ dnssec_verify # zone_properties "ns3" "expired-sigs.autosign" "autosign" "300" "2" # Both KSK and ZSK stay OMNIPRESENT. -key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "no" "yes" key_timings "KEY1" "published" "active" "retired" "none" "none" key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" -key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "no" key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "none" "none" key_timings "KEY2" "published" "active" "retired" "none" "none" # Expect only two keys. @@ -1209,7 +1222,7 @@ check_rrsig_refresh # zone_properties "ns3" "zsk-missing.autosign" "autosign" "300" "2" # KSK stays OMNIPRESENT. -key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "no" "yes" key_timings "KEY1" "published" "active" "retired" "none" "none" key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" # key_properties, key_timings and key_states same as above. @@ -1222,12 +1235,12 @@ zone_properties "ns3" "zsk-retired.autosign" "autosign" "300" "3" # KSK properties, timings and states same as above. # The ZSK goal is set to HIDDEN but records stay OMNIPRESENT until the new ZSK # is active. -key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "no" key_timings "KEY2" "published" "active" "retired" "none" "none" key_states "KEY2" "hidden" "omnipresent" "omnipresent" "none" "none" # A new ZSK should be introduced, so expect a key with goal OMNIPRESENT, # the DNSKEY introduced (RUMOURED) and the signatures HIDDEN. -key_properties "KEY3" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "no" +key_properties "KEY3" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "no" "no" key_timings "KEY3" "published" "active" "retired" "none" "none" key_states "KEY3" "omnipresent" "rumoured" "hidden" "none" "none" @@ -1240,10 +1253,10 @@ key_states "KEY3" "omnipresent" "rumoured" "hidden" "none" "none" # zone_properties "ns3" "step1.zsk-prepub.autosign" "zsk-prepub" "3600" "2" # Both KSK (KEY1) and ZSK (KEY2) start in OMNIPRESENT. -key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "no" "yes" key_timings "KEY1" "published" "active" "retired" "none" "none" key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" -key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" "no" key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "none" "none" key_timings "KEY2" "published" "active" "retired" "none" "none" # Initially only two keys. @@ -1288,7 +1301,7 @@ zone_properties "ns3" "step2.zsk-prepub.autosign" "zsk-prepub" "3600" "3" # KSK (KEY1) doesn't change. # ZSK (KEY2) remains active, no change in properties/timings/states. # New ZSK (KEY3) is prepublished. -key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" "no" key_states "KEY3" "omnipresent" "rumoured" "hidden" "none" "none" key_timings "KEY3" "published" "active" "retired" "none" "none" check_keys @@ -1309,16 +1322,16 @@ zone_properties "ns3" "step3.zsk-prepub.autosign" "zsk-prepub" "3600" "3" # ZSK (KEY2) properties and timing metadata same as above. # ZSK (KEY2) no longer is actively signing, RRSIG state in UNRETENTIVE. # New ZSK (KEY3) is now actively signing, RRSIG state in RUMOURED. -key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" "no" key_states "KEY2" "hidden" "omnipresent" "unretentive" "none" "none" -key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" "no" key_states "KEY3" "omnipresent" "omnipresent" "rumoured" "none" "none" check_keys check_apex # Subdomain still has good signatures of ZSK (KEY2) -key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" -key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" "no" +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" "no" check_subdomain dnssec_verify @@ -1337,9 +1350,9 @@ zone_properties "ns3" "step4.zsk-prepub.autosign" "zsk-prepub" "3600" "3" # ZSK (KEY2) properties and timing metadata same as above. # ZSK (KEY2) DNSKEY is no longer needed. # ZSK (KEY3) is now actively signing, RRSIG state in RUMOURED. -key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" +key_properties "KEY2" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "no" "no" key_states "KEY2" "hidden" "unretentive" "hidden" "none" "none" -key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY3" "zsk" "2592000" "13" "ECDSAP256SHA256" "256" "yes" "no" key_states "KEY3" "omnipresent" "omnipresent" "omnipresent" "none" "none" check_keys check_apex @@ -1380,10 +1393,10 @@ check_next_key_event 1627200 # zone_properties "ns3" "step1.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "2" # Both KSK (KEY1) and ZSK (KEY2) start in OMNIPRESENT. -key_properties "KEY1" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY1" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "no" "yes" key_timings "KEY1" "published" "active" "retired" "none" "none" key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" -key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY2" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "no" key_timings "KEY2" "published" "active" "retired" "none" "none" key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "none" "none" # Initially only two keys. @@ -1407,7 +1420,7 @@ zone_properties "ns3" "step2.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" # ZSK (KEY2) doesn't change. # KSK (KEY1) remains active, no change in properties/timings/states. # New KSK (KEY3) is prepublished (and signs DNSKEY RRset). -key_properties "KEY3" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "yes" +key_properties "KEY3" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "no" "yes" key_states "KEY3" "omnipresent" "rumoured" "none" "rumoured" "hidden" key_timings "KEY3" "published" "active" "retired" "none" "none" check_keys @@ -1449,7 +1462,7 @@ check_next_key_event 266400 zone_properties "ns3" "step4.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" # ZSK (KEY2) doesn't change. # KSK (KEY1) DNSKEY can be removed. -key_properties "KEY1" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "no" +key_properties "KEY1" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "no" "no" key_states "KEY1" "hidden" "unretentive" "none" "unretentive" "hidden" # New KSK (KEY3) DS is now OMNIPRESENT. key_states "KEY3" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" @@ -1482,5 +1495,300 @@ dnssec_verify # 5184000 - 97200 - 86400 - 180000 - 7200 = 4813200 seconds. check_next_key_event 4813200 +# +# Testing CSK key rollover (1). +# + +# +# Zone: step1.csk-roll.autosign. +# +zone_properties "ns3" "step1.csk-roll.autosign" "csk-roll" "3600" "1" +# The CSK (KEY1) starts in OMNIPRESENT. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +# Initially only one key. +key_clear "KEY2" +key_clear "KEY3" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor CSK needs to be published. That is +# the CSK lifetime - prepublication time - DS registration delay. The +# prepublication time is DNSKEY TTL plus publish safety plus the zone +# propagation delay. For the csk-roll policy that means: +# 6mo - 1d - 3h = 15973200 seconds. +check_next_key_event 15973200 + +# +# Zone: step2.csk-roll.autosign. +# +# Set key properties for testing keys. +zone_properties "ns3" "step2.csk-roll.autosign" "csk-roll" "3600" "2" +# CSK (KEY1) remains active, no change in properties/timings/states. +# New CSK (KEY2) is prepublished (and signs DNSKEY RRset). +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_states "KEY2" "omnipresent" "rumoured" "hidden" "rumoured" "hidden" +key_timings "KEY2" "published" "active" "retired" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor CSK becomes OMNIPRESENT. That is the +# DNSKEY TTL plus the zone propagation delay, plus the publish-safety. For +# the csk-roll policy, this means 3 hours = 10800 seconds. +check_next_key_event 10800 + +# +# Zone: step3.csk-roll.autosign. +# +# Set key properties for testing keys. +zone_properties "ns3" "step3.csk-roll.autosign" "csk-roll" "3600" "2" +# CSK (KEY1) DS and ZRRSIG will be removed, so it is UNRETENTIVE. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_states "KEY1" "hidden" "omnipresent" "unretentive" "omnipresent" "unretentive" +# New CSK (KEY2) has its DS submitted, and is signing, so the DS and ZRRSIG +# are in RUMOURED state. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "rumoured" "omnipresent" "rumoured" +check_keys +check_apex +# Subdomain still has good signatures of old CSK (KEY1) +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +check_subdomain +dnssec_verify + +# Next key event is when the predecessor DS has been replaced with the +# successor DS and enough time has passed such that the all validators that +# have this DS RRset cached only know about the successor DS. This is the +# registration delay plus the retire interval, which is the parent +# propagation delay plus the DS TTL plus the retire-safety. For the +# csk-roll policy this means: 1d + 1h + 1h + 2h = 1d4h = 100800 seconds. +check_next_key_event 100800 + +# +# Zone: step4.csk-roll.autosign. +# +zone_properties "ns3" "step4.csk-roll.autosign" "csk-roll" "3600" "2" +# The old CSK (KEY1) DS is hidden. We still need to keep the DNSKEY public +# but can remove the KRRSIG records. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "omnipresent" "unretentive" "unretentive" "hidden" +# The new CSK (KEY2) DS is now OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "rumoured" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the KRRSIG enters the HIDDEN state. This is the +# DNSKEY TTL plus zone propagation delay. For the csk-roll policy this is: +# 1h + 1h = 7200 seconds. +check_next_key_event 7200 + +# +# Zone: step5.csk-roll.autosign. +# +zone_properties "ns3" "step5.csk-roll.autosign" "csk-roll" "3600" "2" +# The old CSK (KEY1) KRRSIG records are now all hidden. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "omnipresent" "unretentive" "hidden" "hidden" +# The new CSK (KEY2) state does not change. +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DNSKEY can be removed. This is when all ZRRSIG +# records have been replaced with signatures of the new CSK. We have +# calculated the interval to be 26d3h of which 1d4h (Dreg + Iret(KSK)) plus +# 2h (DNSKEY TTL + Dprp) have already passed. So next key event is in +# 26d3h - 1d4h - 2h = 597h = 2149200 seconds. +check_next_key_event 2149200 + +# +# Zone: step6.csk-roll.autosign. +# +zone_properties "ns3" "step6.csk-roll.autosign" "csk-roll" "3600" "2" +# The old CSK (KEY1) DNSKEY can be removed. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "unretentive" "hidden" "hidden" "hidden" +# The new CSK (KEY2) is now fully OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DNSKEY enters the HIDDEN state. This is the +# DNSKEY TTL plus zone propagation delay. For the csk-roll policy this is: +# 1h + 1h = 7200 seconds. +check_next_key_event 7200 + +# +# Zone: step7.csk-roll.autosign. +# +zone_properties "ns3" "step7.csk-roll.autosign" "csk-roll" "3600" "2" +# The old CSK (KEY1) is now completely HIDDEN. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "hidden" "hidden" "hidden" "hidden" +# The new CSK (KEY2) is now fully OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the new successor needs to be published. This is the +# CSK lifetime minus Ipub minus Dreg minus Iret minus DNSKEY TTL minus zone +# propagation delay. For the csk-roll this is: +# 6mo - 3h - 1d - 26d3h - 1h - 1h = 6mo - 27d8h = 13708800 seconds. +check_next_key_event 13708800 + +# +# Testing CSK key rollover (1). +# + +# +# Zone: step1.csk-roll2.autosign. +# +zone_properties "ns3" "step1.csk-roll2.autosign" "csk-roll2" "3600" "1" +# The CSK (KEY1) starts in OMNIPRESENT. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +# Initially only one key. +key_clear "KEY2" +key_clear "KEY3" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor CSK needs to be published. That is +# the CSK lifetime - prepublication time - DS registration delay. The +# prepublication time is DNSKEY TTL plus publish safety plus the zone +# propagation delay. For the csk-roll2 policy that means: +# 6mo - 3h - 1w = 15454800 seconds. +check_next_key_event 15454800 + +# +# Zone: step2.csk-roll2.autosign. +# +# Set key properties for testing keys. +zone_properties "ns3" "step2.csk-roll2.autosign" "csk-roll2" "3600" "2" +# CSK (KEY1) remains active, no change in properties/timings/states. +# New CSK (KEY2) is prepublished (and signs DNSKEY RRset). +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_states "KEY2" "omnipresent" "rumoured" "hidden" "rumoured" "hidden" +key_timings "KEY2" "published" "active" "retired" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the successor CSK becomes OMNIPRESENT. That is the +# DNSKEY TTL plus the zone propagation delay, plus the publish-safety. For +# the csk-roll2 policy, this means 3 hours = 10800 seconds. +check_next_key_event 10800 + +# +# Zone: step3.csk-roll2.autosign. +# +# Set key properties for testing keys. +zone_properties "ns3" "step3.csk-roll2.autosign" "csk-roll2" "3600" "2" +# CSK (KEY1) DS and ZRRSIG will be removed, so it is UNRETENTIVE. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_states "KEY1" "hidden" "omnipresent" "unretentive" "omnipresent" "unretentive" +# New CSK (KEY2) has its DS submitted, and is signing, so the DS and ZRRSIG +# are in RUMOURED state. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "rumoured" "omnipresent" "rumoured" +check_keys +check_apex +# Subdomain still has good signatures of old CSK (KEY1) +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +check_subdomain +dnssec_verify + +# Next key event is when the predecessor ZRRSIG records have been replaced +# with that of the successor and enough time has passed such that the all +# validators that have such signed RRsets in cache only know about the +# successor signatures. This is the retire interval: Dsgn plus the +# maximum zone TTL plus the zone propagation delay plus retire-safety. For the +# csk-roll2 policy that means: 12h (because 1d validity and refresh within +# 12 hours) + 1d + 1h + 1h = 38h = 136800 seconds. +check_next_key_event 136800 + +# +# Zone: step4.csk-roll2.autosign. +# +zone_properties "ns3" "step4.csk-roll2.autosign" "csk-roll2" "3600" "2" +# The old CSK (KEY1) ZRRSIG is now HIDDEN. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" +key_states "KEY1" "hidden" "omnipresent" "hidden" "omnipresent" "unretentive" +# The new CSK (KEY2) ZRRSIG is now OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "rumoured" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the predecessor DS has been replaced with the +# successor DS and enough time has passed such that the all validators that +# have this DS RRset cached only know about the successor DS. This is the +# registration delay plus the retire interval, which is the parent +# propagation delay plus the DS TTL plus the retire-safety. For the +# csk-roll2 policy this means: 1w + 1h + 1h + 1h = 171h = 615600 seconds. +# However, 136800 seconds have passed already, so 478800 seconds left. +check_next_key_event 478800 + +# +# Zone: step5.csk-roll2.autosign. +# +zone_properties "ns3" "step5.csk-roll2.autosign" "csk-roll2" "3600" "2" +# The old CSK (KEY1) DNSKEY can be removed. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "unretentive" "hidden" "unretentive" "hidden" +# The new CSK (KEY2) is now fully OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the DNSKEY enters the HIDDEN state. This is the +# DNSKEY TTL plus zone propagation delay. For the csk-roll policy this is: +# 1h + 1h = 7200 seconds. +check_next_key_event 7200 + +# +# Zone: step6.csk-roll2.autosign. +# +zone_properties "ns3" "step6.csk-roll2.autosign" "csk-roll" "3600" "2" +# The old CSK (KEY1) is now completely HIDDEN. +key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" +key_states "KEY1" "hidden" "hidden" "hidden" "hidden" "hidden" +# The new CSK (KEY2) is now fully OMNIPRESENT. +key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_states "KEY2" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Next key event is when the new successor needs to be published. +check_next_key_event 14684400 + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 From 29e6ec3181522e67921e76ed8580ad8f1593bb5a Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Wed, 30 Oct 2019 17:40:08 +0100 Subject: [PATCH 34/43] KASP timings all uint32_t Get rid of the warnings in the Windows build. --- lib/dns/include/dns/kasp.h | 32 +++++++++++++++----------------- lib/dns/kasp.c | 20 ++++++++++---------- lib/isccfg/kaspconf.c | 4 ++-- lib/isccfg/parser.c | 4 ++-- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h index c6b156119a..01cc0cee33 100644 --- a/lib/dns/include/dns/kasp.h +++ b/lib/dns/include/dns/kasp.h @@ -24,8 +24,6 @@ * signed and maintained. */ -#include - #include #include #include @@ -46,7 +44,7 @@ struct dns_kasp_key { ISC_LINK(struct dns_kasp_key) link; /* Configuration */ - time_t lifetime; + uint32_t lifetime; uint32_t algorithm; int length; uint8_t role; @@ -75,7 +73,7 @@ struct dns_kasp { /* Configuration: Keys */ dns_kasp_keylist_t keys; - uint32_t dnskey_ttl; + dns_ttl_t dnskey_ttl; /* Configuration: Timings */ uint32_t publish_safety; @@ -83,12 +81,12 @@ struct dns_kasp { /* Zone settings */ dns_ttl_t zone_max_ttl; - time_t zone_propagation_delay; + uint32_t zone_propagation_delay; /* Parent settings */ dns_ttl_t parent_ds_ttl; - time_t parent_propagation_delay; - time_t parent_registration_delay; + uint32_t parent_propagation_delay; + uint32_t parent_registration_delay; // TODO: The rest of the KASP configuration }; @@ -208,7 +206,7 @@ dns_kasp_getname(dns_kasp_t *kasp); *\li name of 'kasp'. */ -time_t +uint32_t dns_kasp_signdelay(dns_kasp_t *kasp); /*%< * Get the delay that is needed to ensure that all existing RRsets have been @@ -225,7 +223,7 @@ dns_kasp_signdelay(dns_kasp_t *kasp); *\li signature refresh interval. */ -time_t +uint32_t dns_kasp_sigrefresh(dns_kasp_t *kasp); /*%< * Get signature refresh interval. @@ -239,9 +237,9 @@ dns_kasp_sigrefresh(dns_kasp_t *kasp); *\li signature refresh interval. */ -time_t +uint32_t dns_kasp_sigvalidity(dns_kasp_t *kasp); -time_t +uint32_t dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp); /*%< * Get signature validity. @@ -269,7 +267,7 @@ dns_kasp_dnskeyttl(dns_kasp_t *kasp); *\li DNSKEY TTL. */ -time_t +uint32_t dns_kasp_publishsafety(dns_kasp_t *kasp); /*%< * Get publish safety interval. @@ -283,7 +281,7 @@ dns_kasp_publishsafety(dns_kasp_t *kasp); *\li Publish safety interval. */ -time_t +uint32_t dns_kasp_retiresafety(dns_kasp_t *kasp); /*%< * Get retire safety interval. @@ -311,7 +309,7 @@ dns_kasp_zonemaxttl(dns_kasp_t *kasp); *\li Maximum zone TTL. */ -time_t +uint32_t dns_kasp_zonepropagationdelay(dns_kasp_t *kasp); /*%< * Get zone propagation delay. @@ -339,7 +337,7 @@ dns_kasp_dsttl(dns_kasp_t *kasp); *\li Expected parent DS TTL. */ -time_t +uint32_t dns_kasp_parentpropagationdelay(dns_kasp_t *kasp); /*%< * Get parent zone propagation delay. @@ -353,7 +351,7 @@ dns_kasp_parentpropagationdelay(dns_kasp_t *kasp); *\li Parent zone propagation delay. */ -time_t +uint32_t dns_kasp_parentregistrationdelay(dns_kasp_t *kasp); /*%< * Get parent registration delay for submitting new DS. @@ -441,7 +439,7 @@ dns_kasp_key_size(dns_kasp_key_t *key); * configured. */ -time_t +uint32_t dns_kasp_key_lifetime(dns_kasp_key_t *key); /*%< * The lifetime of this key (how long may this key be active?) diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c index 6ec8236785..2df0e88886 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -124,28 +124,28 @@ dns_kasp_thaw(dns_kasp_t *kasp) { kasp->frozen = false; } -time_t +uint32_t dns_kasp_signdelay(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); return (kasp->signatures_validity - kasp->signatures_refresh); } -time_t +uint32_t dns_kasp_sigrefresh(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); return kasp->signatures_refresh; } -time_t +uint32_t dns_kasp_sigvalidity(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); return kasp->signatures_validity; } -time_t +uint32_t dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); @@ -159,14 +159,14 @@ dns_kasp_dnskeyttl(dns_kasp_t *kasp) { return kasp->dnskey_ttl; } -time_t +uint32_t dns_kasp_publishsafety(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); return kasp->publish_safety; } -time_t +uint32_t dns_kasp_retiresafety(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); @@ -180,7 +180,7 @@ dns_kasp_zonemaxttl(dns_kasp_t *kasp) { return kasp->zone_max_ttl; } -time_t +uint32_t dns_kasp_zonepropagationdelay(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); @@ -194,14 +194,14 @@ dns_kasp_dsttl(dns_kasp_t *kasp) { return kasp->parent_ds_ttl; } -time_t +uint32_t dns_kasp_parentpropagationdelay(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); return kasp->parent_propagation_delay; } -time_t +uint32_t dns_kasp_parentregistrationdelay(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); @@ -313,7 +313,7 @@ dns_kasp_key_size(dns_kasp_key_t *key) { return size; } -time_t +uint32_t dns_kasp_key_lifetime(dns_kasp_key_t *key) { REQUIRE(key != NULL); diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index 37eb1e3c69..0d19d882e4 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -46,8 +46,8 @@ confget(cfg_obj_t const * const *maps, const char *name, const cfg_obj_t **obj) /* * Utility function for configuring durations. */ -static time_t -get_duration(const cfg_obj_t **maps, const char* option, time_t dfl) +static uint32_t +get_duration(const cfg_obj_t **maps, const char* option, uint32_t dfl) { const cfg_obj_t *obj; isc_result_t result; diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c index 68db91a1d1..849478d624 100644 --- a/lib/isccfg/parser.c +++ b/lib/isccfg/parser.c @@ -984,8 +984,8 @@ LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_uint64 = { * Get the number of digits in a number. */ static size_t -numlen(time_t num) { - uint32_t period = (uint32_t) num; +numlen(uint32_t num) { + uint32_t period = num; size_t count = 0; if (period == 0) { From 1211c348bbe0695672dec18cb9597c1a4b11bdb3 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 4 Nov 2019 11:12:26 +0100 Subject: [PATCH 35/43] Add dst_key_copy_metadata function. When updating DNSSEC keys we would like to be able to copy the metadata from one key to another. --- lib/dns/dst_api.c | 49 +++++++++++++++++++++++++++++++++++++ lib/dns/include/dst/dst.h | 10 ++++++++ lib/dns/win32/libdns.def.in | 1 + 3 files changed, 60 insertions(+) diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index cfa4bbcda6..06e9c611ad 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -2587,3 +2587,52 @@ dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove) return state_ok && time_ok; } + +void +dst_key_copy_metadata(dst_key_t *to, dst_key_t *from) +{ + dst_key_state_t state; + isc_stdtime_t when; + uint32_t num; + bool yesno; + isc_result_t result; + + REQUIRE(VALID_KEY(to)); + REQUIRE(VALID_KEY(from)); + + for (int i = 0; i < DST_MAX_TIMES+1; i++) { + result = dst_key_gettime(from, i, &when); + if (result == ISC_R_SUCCESS) { + dst_key_settime(to, i, when); + } else { + dst_key_unsettime(to, i); + } + } + + for (int i = 0; i < DST_MAX_NUMERIC+1; i++) { + result = dst_key_getnum(from, i, &num); + if (result == ISC_R_SUCCESS) { + dst_key_setnum(to, i, num); + } else { + dst_key_unsetnum(to, i); + } + } + + for (int i = 0; i < DST_MAX_BOOLEAN+1; i++) { + result = dst_key_getbool(from, i, &yesno); + if (result == ISC_R_SUCCESS) { + dst_key_setbool(to, i, yesno); + } else { + dst_key_unsetnum(to, i); + } + } + + for (int i = 0; i < DST_MAX_KEYSTATES+1; i++) { + result = dst_key_getstate(from, i, &state); + if (result == ISC_R_SUCCESS) { + dst_key_setstate(to, i, state); + } else { + dst_key_unsetstate(to, i); + } + } +} diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index febadabae7..afa2068623 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -1166,6 +1166,16 @@ dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove); * 'key' to be valid. */ +void +dst_key_copy_metadata(dst_key_t *to, dst_key_t *from); +/*%< + * Copy key metadata from one key to another. + * + * Requires: + * 'to' and 'from' to be valid. + */ + + ISC_LANG_ENDDECLS #endif /* DST_DST_H */ diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index c127aba804..2d90054b6b 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -1405,6 +1405,7 @@ dst_key_attach dst_key_buildfilename dst_key_buildinternal dst_key_class +dst_key_copy_metadata dst_key_compare dst_key_computesecret dst_key_dump From 2e46dcbbce020f9bff3c8160d0594e1b144d6e03 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 4 Nov 2019 11:16:08 +0100 Subject: [PATCH 36/43] sign_apex() should also consider CDS/CDNSKEY The 'sign_apex()' function has special processing for signing the DNSKEY RRset such that it will always be signed with the active KSK. Since CDS and CDNSKEY are also signed with the KSK, it should have the same special processing. The special processing is moved into a new function 'tickle_apex_rrset()' and is applied to all three RR types (DNSKEY, CDS, CDNSKEY). In addition, when kasp is involved, update the DNSKEY TTL accordingly to what is in the policy. --- lib/dns/zone.c | 113 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 36 deletions(-) diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 85832daef5..89d008bc89 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -6383,7 +6383,9 @@ del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, result = dns_rdata_tostruct(&rdata, &rrsig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); - if (type != dns_rdatatype_dnskey) { + if (type != dns_rdatatype_dnskey && + type != dns_rdatatype_cds && + type != dns_rdatatype_cdnskey) { bool warn = false, deleted = false; if (delsig_ok(&rrsig, keys, nkeys, &warn)) { result = update_one_rr(db, ver, zonediff->diff, @@ -6438,7 +6440,7 @@ del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, } /* - * RRSIG(DNSKEY) requires special processing. + * KSK RRSIGs requires special processing. */ found = false; for (i = 0; i < nkeys; i++) { @@ -6446,7 +6448,7 @@ del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, rrsig.keyid == dst_key_id(keys[i])) { found = true; /* - * Mark offline RRSIG(DNSKEY). + * Mark offline DNSKEY. * We want the earliest offline expire time * iff there is a new offline signature. */ @@ -18368,6 +18370,57 @@ add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype, return (result); } + +/* + * See if dns__zone_updatesigs() will update signature for RRset 'rrtype' at + * the apex, and if not tickle them and cause to sign so that newly activated + * keys are used. + */ +static isc_result_t +tickle_apex_rrset(dns_rdatatype_t rrtype, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *ver, isc_stdtime_t now, dns_diff_t *diff, + dns__zonediff_t *zonediff, dst_key_t **keys, + unsigned int nkeys, isc_stdtime_t inception, + isc_stdtime_t keyexpire, bool check_ksk, bool keyset_kskonly) +{ + dns_difftuple_t *tuple; + isc_result_t result; + + for (tuple = ISC_LIST_HEAD(diff->tuples); + tuple != NULL; + tuple = ISC_LIST_NEXT(tuple, link)) + { + if (tuple->rdata.type == rrtype && + dns_name_equal(&tuple->name, &zone->origin)) + { + break; + } + } + + if (tuple == NULL) { + result = del_sigs(zone, db, ver, &zone->origin, rrtype, + zonediff, keys, nkeys, now, false); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "sign_apex:del_sigs -> %s", + dns_result_totext(result)); + return (result); + } + result = add_sigs(db, ver, &zone->origin, zone, rrtype, + zonediff->diff, keys, nkeys, zone->mctx, + inception, keyexpire, check_ksk, + keyset_kskonly); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "sign_apex:add_sigs -> %s", + dns_result_totext(result)); + return (result); + } + } + + return (ISC_R_SUCCESS); +} + static isc_result_t sign_apex(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now, dns_diff_t *diff, dns__zonediff_t *zonediff) @@ -18377,7 +18430,6 @@ sign_apex(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, bool check_ksk, keyset_kskonly; dst_key_t *zone_keys[DNS_MAXZONEKEYS]; unsigned int nkeys = 0, i; - dns_difftuple_t *tuple; result = dns__zone_findkeys(zone, db, ver, now, zone->mctx, DNS_MAXZONEKEYS, zone_keys, &nkeys); @@ -18402,40 +18454,27 @@ sign_apex(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); /* - * See if dns__zone_updatesigs() will update DNSKEY signature and if - * not cause them to sign so that newly activated keys are used. + * See if dns__zone_updatesigs() will update DNSKEY/CDS/CDNSKEY + * signature and if not cause them to sign so that newly activated + * keys are used. */ - for (tuple = ISC_LIST_HEAD(diff->tuples); - tuple != NULL; - tuple = ISC_LIST_NEXT(tuple, link)) - { - if (tuple->rdata.type == dns_rdatatype_dnskey && - dns_name_equal(&tuple->name, &zone->origin)) - { - break; - } + result = tickle_apex_rrset(dns_rdatatype_dnskey, zone, db, ver, now, + diff, zonediff, zone_keys, nkeys, inception, + keyexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + goto failure; } - - if (tuple == NULL) { - result = del_sigs(zone, db, ver, &zone->origin, - dns_rdatatype_dnskey, zonediff, - zone_keys, nkeys, now, false); - if (result != ISC_R_SUCCESS) { - dnssec_log(zone, ISC_LOG_ERROR, - "sign_apex:del_sigs -> %s", - dns_result_totext(result)); - goto failure; - } - result = add_sigs(db, ver, &zone->origin, zone, - dns_rdatatype_dnskey, zonediff->diff, - zone_keys, nkeys, zone->mctx, inception, - keyexpire, check_ksk, keyset_kskonly); - if (result != ISC_R_SUCCESS) { - dnssec_log(zone, ISC_LOG_ERROR, - "sign_apex:add_sigs -> %s", - dns_result_totext(result)); - goto failure; - } + result = tickle_apex_rrset(dns_rdatatype_cds, zone, db, ver, now, + diff, zonediff, zone_keys, nkeys, inception, + keyexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + goto failure; + } + result = tickle_apex_rrset(dns_rdatatype_cdnskey, zone, db, ver, now, + diff, zonediff, zone_keys, nkeys, inception, + keyexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + goto failure; } result = dns__zone_updatesigs(diff, db, ver, zone_keys, nkeys, zone, @@ -18701,6 +18740,8 @@ zone_rekey(dns_zone_t *zone) { } if (kasp && (result == ISC_R_SUCCESS || result == ISC_R_NOTFOUND)) { + ttl = dns_kasp_dnskeyttl(kasp); + result = dns_keymgr_run(&zone->origin, zone->rdclass, dir, mctx, &keys, kasp, now, &nexttime); if (result != ISC_R_SUCCESS) { From c3e0ac865f6a01569f0e3eafa5bc34b09f9068d7 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 4 Nov 2019 11:20:00 +0100 Subject: [PATCH 37/43] Add tests for CDS/CDNSKEY publication The kasp system tests are updated with 'check_cds' calls that will verify that the correct CDS and CDNSKEY records are published during a rollover and that they are signed with the correct KSK. This requires a change in 'dnssec.c' to check the kasp key states whether the CDS/CDNSKEY of a key should be published or not. If no kasp state exist, fall back to key timings. --- bin/tests/system/kasp/tests.sh | 58 ++++++++++++++++++++++++++++------ lib/dns/dnssec.c | 54 +++++++++++++++++++++++++------ 2 files changed, 93 insertions(+), 19 deletions(-) diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index 42f2eef2ab..a79a871cc6 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -700,7 +700,7 @@ grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch st grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${qtype}.*257.*.3.*${KEY1[$ALG_NUM]}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${qtype} record in response" lines=$(get_keys_which_signed $qtype dig.out.$DIR.test$n | wc -l) test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" -get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with ${KEY_ID}" +get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with key ${KEY_ID}" test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) @@ -714,7 +714,7 @@ grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch st grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*${qtype}.*mname1\..*\." dig.out.$DIR.test$n > /dev/null || log_error "missing ${qtype} record in response" lines=$(get_keys_which_signed $qtype dig.out.$DIR.test$n | wc -l) test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" -get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with ${KEY_ID}" +get_keys_which_signed $qtype dig.out.$DIR.test$n | grep "^${KEY_ID}$" > /dev/null || log_error "${qtype} RRset not signed with key ${KEY_ID}" test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) @@ -735,14 +735,14 @@ do grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.11" dig.out.$DIR.test$n.a > /dev/null || log_error "missing a.${ZONE} A record in response" lines=$(get_keys_which_signed A dig.out.$DIR.test$n.a | wc -l) test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" - get_keys_which_signed A dig.out.$DIR.test$n.a | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with ${KEY_ID}" + get_keys_which_signed A dig.out.$DIR.test$n.a | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with key ${KEY_ID}" dig_with_opts "d.${ZONE}" @10.53.0.3 A > dig.out.$DIR.test$n.d || log_error "dig d.${ZONE} A failed" grep "status: NOERROR" dig.out.$DIR.test$n.d > /dev/null || log_error "mismatch status in DNS response" grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.4" dig.out.$DIR.test$n.d > /dev/null || log_error "missing d.${ZONE} A record in response" lines=$(get_keys_which_signed A dig.out.$DIR.test$n.d | wc -l) test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" - get_keys_which_signed A dig.out.$DIR.test$n.d | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with ${KEY_ID}" + get_keys_which_signed A dig.out.$DIR.test$n.d | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with key ${KEY_ID}" i=`expr $i + 1` if [ $ret = 0 ]; then break; fi @@ -870,22 +870,59 @@ check_signatures() { if [ "${KEY1[$_expect_type]}" == "yes" ] && [ "${KEY1[$_role]}" == "yes" ]; then get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with key ${KEY1[$ID]}" elif [ "${KEY1[$EXPECT]}" == "yes" ]; then - get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY1[$ID]}" + get_keys_which_signed $_qtype $_file | grep "^${KEY1[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key ${KEY1[$ID]}" fi if [ "${KEY2[$_expect_type]}" == "yes" ] && [ "${KEY2[$_role]}" == "yes" ]; then - get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with ${KEY2[$ID]}" + get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with key ${KEY2[$ID]}" elif [ "${KEY2[$EXPECT]}" == "yes" ]; then - get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY2[$ID]}" + get_keys_which_signed $_qtype $_file | grep "^${KEY2[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key ${KEY2[$ID]}" fi if [ "${KEY3[$_expect_type]}" == "yes" ] && [ "${KEY3[$_role]}" == "yes" ]; then - get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with ${KEY3[$ID]}" + get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null || log_error "${_qtype} RRset not signed with key ${KEY3[$ID]}" elif [ "${KEY3[$EXPECT]}" == "yes" ]; then - get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with ${KEY3[$ID]}" + get_keys_which_signed $_qtype $_file | grep "^${KEY3[$ID]}$" > /dev/null && log_error "${_qtype} RRset signed unexpectedly with key ${KEY3[$ID]}" fi } +# Test CDS and CDNSKEY publication. +check_cds() { + + _qtype="CDS" + _key_algnum="${KEY1[$ALG_NUM]}" + + n=$((n+1)) + echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)" + ret=0 + dig_with_opts $ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" + + if [ "${KEY1[$STATE_DS]}" == "rumoured" ] || [ "${KEY1[$STATE_DS]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY1[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY1[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + elif [ "${KEY1[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY1[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY1[$ID]}" + fi + + if [ "${KEY2[$STATE_DS]}" == "rumoured" ] || [ "${KEY2[$STATE_DS]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY2[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY2[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + elif [ "${KEY2[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY2[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY2[$ID]}" + fi + + if [ "${KEY3[$STATE_DS]}" == "rumoured" ] || [ "${KEY3[$STATE_DS]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY3[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY3[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + elif [ "${KEY3[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*${KEY3[$ID]}.*${_key_algnum}.*2" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY3[$ID]}" + fi + + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) +} + # Test the apex of a configured zone. This checks that the SOA and DNSKEY # RRsets are signed correctly and with the appropriate keys. check_apex() { @@ -916,6 +953,9 @@ check_apex() { check_signatures $_qtype dig.out.$DIR.test$n $ZSK test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) + + # Test CDS publication. + check_cds } # Test an RRset below the apex and verify it is signed correctly. diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c index 388689144b..9e21cc35b1 100644 --- a/lib/dns/dnssec.c +++ b/lib/dns/dnssec.c @@ -635,7 +635,10 @@ dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) { * Indicate whether a key is scheduled to to have CDS/CDNSKEY records * published now. * - * Returns true iff. + * Returns true if. + * - kasp says the DS record should be published (e.g. the DS state is in + * RUMOURED or OMNIPRESENT state). + * Or: * - SyncPublish is set and in the past, AND * - SyncDelete is unset or in the future */ @@ -643,6 +646,7 @@ static bool syncpublish(dst_key_t *key, isc_stdtime_t now) { isc_result_t result; isc_stdtime_t when; + dst_key_state_t state; int major, minor; /* @@ -654,18 +658,29 @@ syncpublish(dst_key_t *key, isc_stdtime_t now) { /* * Smart signing started with key format 1.3 */ - if (major == 1 && minor <= 2) + if (major == 1 && minor <= 2) { return (false); + } + /* Check kasp state first. */ + result = dst_key_getstate(key, DST_KEY_DS, &state); + if (result == ISC_R_SUCCESS) { + return (state == DST_KEY_STATE_RUMOURED || + state == DST_KEY_STATE_OMNIPRESENT); + } + + /* If no kasp state, check timings. */ result = dst_key_gettime(key, DST_TIME_SYNCPUBLISH, &when); - if (result != ISC_R_SUCCESS) + if (result != ISC_R_SUCCESS) { return (false); - + } result = dst_key_gettime(key, DST_TIME_SYNCDELETE, &when); - if (result != ISC_R_SUCCESS) + if (result != ISC_R_SUCCESS) { return (true); - if (when <= now) + } + if (when <= now) { return (false); + } return (true); } @@ -673,12 +688,17 @@ syncpublish(dst_key_t *key, isc_stdtime_t now) { * Indicate whether a key is scheduled to to have CDS/CDNSKEY records * deleted now. * - * Returns true iff. SyncDelete is set and in the past. + * Returns true if: + * - kasp says the DS record should be unpublished (e.g. the DS state is in + * UNRETENTIVE or HIDDEN state). + * Or: + * - SyncDelete is set and in the past. */ static bool syncdelete(dst_key_t *key, isc_stdtime_t now) { isc_result_t result; isc_stdtime_t when; + dst_key_state_t state; int major, minor; /* @@ -690,14 +710,25 @@ syncdelete(dst_key_t *key, isc_stdtime_t now) { /* * Smart signing started with key format 1.3. */ - if (major == 1 && minor <= 2) + if (major == 1 && minor <= 2) { return (false); + } + /* Check kasp state first. */ + result = dst_key_getstate(key, DST_KEY_DS, &state); + if (result == ISC_R_SUCCESS) { + return (state == DST_KEY_STATE_UNRETENTIVE || + state == DST_KEY_STATE_HIDDEN); + } + + /* If no kasp state, check timings. */ result = dst_key_gettime(key, DST_TIME_SYNCDELETE, &when); - if (result != ISC_R_SUCCESS) + if (result != ISC_R_SUCCESS) { return (false); - if (when <= now) + } + if (when <= now) { return (true); + } return (false); } @@ -2135,6 +2166,9 @@ dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, /* Printable version of key2 (the old key, if any) */ dst_key_format(key2->key, keystr2, sizeof(keystr2)); + /* Copy key metadata. */ + dst_key_copy_metadata(key2->key, key1->key); + /* Match found: remove or update it as needed */ if (key1->hint_remove) { RETERR(remove_key(diff, key2, origin, ttl, mctx, From 70da58c871db4584d68fae12a2c5e9f677295d32 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 4 Nov 2019 12:04:52 +0100 Subject: [PATCH 38/43] kasp.c: return parenthesis (style) and REQUIRE This code was missing a lot of return parenthesis (violating our style guide) and a missing REQUIRE in 'dns_kasplist_find()'. --- lib/dns/kasp.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c index 2df0e88886..ff9293790b 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -107,7 +107,7 @@ dns_kasp_detach(dns_kasp_t **kaspp) { const char* dns_kasp_getname(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); - return kasp->name; + return (kasp->name); } void @@ -135,77 +135,77 @@ uint32_t dns_kasp_sigrefresh(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); - return kasp->signatures_refresh; + return (kasp->signatures_refresh); } uint32_t dns_kasp_sigvalidity(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); - return kasp->signatures_validity; + return (kasp->signatures_validity); } uint32_t dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); - return kasp->signatures_validity_dnskey; + return (kasp->signatures_validity_dnskey); } dns_ttl_t dns_kasp_dnskeyttl(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); - return kasp->dnskey_ttl; + return (kasp->dnskey_ttl); } uint32_t dns_kasp_publishsafety(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); - return kasp->publish_safety; + return (kasp->publish_safety); } uint32_t dns_kasp_retiresafety(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); - return kasp->retire_safety; + return (kasp->retire_safety); } dns_ttl_t dns_kasp_zonemaxttl(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); - return kasp->zone_max_ttl; + return (kasp->zone_max_ttl); } uint32_t dns_kasp_zonepropagationdelay(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); - return kasp->zone_propagation_delay; + return (kasp->zone_propagation_delay); } dns_ttl_t dns_kasp_dsttl(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); - return kasp->parent_ds_ttl; + return (kasp->parent_ds_ttl); } uint32_t dns_kasp_parentpropagationdelay(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); - return kasp->parent_propagation_delay; + return (kasp->parent_propagation_delay); } uint32_t dns_kasp_parentregistrationdelay(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(kasp->frozen); - return kasp->parent_registration_delay; + return (kasp->parent_registration_delay); } isc_result_t @@ -213,6 +213,8 @@ dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp) { dns_kasp_t *kasp = NULL; + REQUIRE(kaspp != NULL && *kaspp == NULL); + if (list == NULL) { return (ISC_R_NOTFOUND); } @@ -264,7 +266,7 @@ uint32_t dns_kasp_key_algorithm(dns_kasp_key_t *key) { REQUIRE(key != NULL); - return key->algorithm; + return (key->algorithm); } unsigned int @@ -310,7 +312,7 @@ dns_kasp_key_size(dns_kasp_key_t *key) { /* unsupported */ break; } - return size; + return (size); } uint32_t From f11ce44818f0f9c10088b64cacba5b3921a13faf Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Mon, 4 Nov 2019 16:26:39 +0100 Subject: [PATCH 39/43] Make kasp opaque --- bin/dnssec/dnssec-keygen.c | 4 +- lib/dns/include/dns/kasp.h | 150 +++++++++++++++++++++++++++++++++++- lib/dns/kasp.c | 109 +++++++++++++++++++++++++- lib/dns/keymgr.c | 2 +- lib/dns/win32/libdns.def.in | 14 ++++ lib/dns/zone.c | 2 +- lib/isccfg/kaspconf.c | 56 +++++++------- 7 files changed, 298 insertions(+), 39 deletions(-) diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index f0600232ef..40cffe2168 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -1187,7 +1187,7 @@ main(int argc, char **argv) { fatal("failed to load dnssec-policy '%s'", ctx.policy); } - if (ISC_LIST_EMPTY(kasp->keys)) { + if (ISC_LIST_EMPTY(dns_kasp_keys(kasp))) { fatal("dnssec-policy '%s' has no keys " "configured", ctx.policy); } @@ -1195,7 +1195,7 @@ main(int argc, char **argv) { ctx.ttl = dns_kasp_dnskeyttl(kasp); ctx.setttl = true; - kaspkey = ISC_LIST_HEAD(kasp->keys); + kaspkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); while (kaspkey != NULL) { ctx.use_nsec3 = false; diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h index 01cc0cee33..396ef5cade 100644 --- a/lib/dns/include/dns/kasp.h +++ b/lib/dns/include/dns/kasp.h @@ -237,6 +237,16 @@ dns_kasp_sigrefresh(dns_kasp_t *kasp); *\li signature refresh interval. */ +void +dns_kasp_setsigrefresh(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set signature refresh interval. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + uint32_t dns_kasp_sigvalidity(dns_kasp_t *kasp); uint32_t @@ -253,10 +263,22 @@ dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp); *\li signature validity. */ +void +dns_kasp_setsigvalidity(dns_kasp_t *kasp, uint32_t value); +void +dns_kasp_setsigvalidity_dnskey(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set signature validity. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + dns_ttl_t dns_kasp_dnskeyttl(dns_kasp_t *kasp); /*%< - * Get dnskey ttl. + * Get DNSKEY TTL. * * Requires: * @@ -267,6 +289,16 @@ dns_kasp_dnskeyttl(dns_kasp_t *kasp); *\li DNSKEY TTL. */ +void +dns_kasp_setdnskeyttl(dns_kasp_t *kasp, dns_ttl_t ttl); +/*%< + * Set DNSKEY TTL. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + uint32_t dns_kasp_publishsafety(dns_kasp_t *kasp); /*%< @@ -281,6 +313,16 @@ dns_kasp_publishsafety(dns_kasp_t *kasp); *\li Publish safety interval. */ +void +dns_kasp_setpublishsafety(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set publish safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + uint32_t dns_kasp_retiresafety(dns_kasp_t *kasp); /*%< @@ -295,6 +337,16 @@ dns_kasp_retiresafety(dns_kasp_t *kasp); *\li Retire safety interval. */ +void +dns_kasp_setretiresafety(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set retire safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + dns_ttl_t dns_kasp_zonemaxttl(dns_kasp_t *kasp); /*%< @@ -309,6 +361,16 @@ dns_kasp_zonemaxttl(dns_kasp_t *kasp); *\li Maximum zone TTL. */ +void +dns_kasp_setzonemaxttl(dns_kasp_t *kasp, dns_ttl_t ttl); +/*%< + * Set maximum zone TTL. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + uint32_t dns_kasp_zonepropagationdelay(dns_kasp_t *kasp); /*%< @@ -323,6 +385,16 @@ dns_kasp_zonepropagationdelay(dns_kasp_t *kasp); *\li Zone propagation delay. */ +void +dns_kasp_setzonepropagationdelay(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set zone propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + dns_ttl_t dns_kasp_dsttl(dns_kasp_t *kasp); /*%< @@ -337,6 +409,16 @@ dns_kasp_dsttl(dns_kasp_t *kasp); *\li Expected parent DS TTL. */ +void +dns_kasp_setdsttl(dns_kasp_t *kasp, dns_ttl_t ttl); +/*%< + * Set DS TTL. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + uint32_t dns_kasp_parentpropagationdelay(dns_kasp_t *kasp); /*%< @@ -351,6 +433,16 @@ dns_kasp_parentpropagationdelay(dns_kasp_t *kasp); *\li Parent zone propagation delay. */ +void +dns_kasp_setparentpropagationdelay(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set parent propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + uint32_t dns_kasp_parentregistrationdelay(dns_kasp_t *kasp); /*%< @@ -365,6 +457,16 @@ dns_kasp_parentregistrationdelay(dns_kasp_t *kasp); *\li Parent registration delay. */ +void +dns_kasp_setparentregistrationdelay(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set parent registration delay. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + isc_result_t dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp); /*%< @@ -381,14 +483,56 @@ dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp); *\li #ISC_R_NOTFOUND No matching kasp was found. */ +dns_kasp_keylist_t +dns_kasp_keys(dns_kasp_t *kasp); +/*%< + * Get the list of kasp keys. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +bool +dns_kasp_keylist_empty(dns_kasp_t *kasp); +/*%< + * Check if the keylist is empty. + * + * Requires: + * + *\li 'kasp' is a valid kasp. + * + * Returns: + * + *\li true if the keylist is empty, false otherwise. + */ + +void +dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key); +/*%< + * Add a key. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + *\li 'key' is not NULL. + */ + isc_result_t -dns_kasp_key_create(isc_mem_t* mctx, dns_kasp_key_t **keyp); +dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp); /*%< * Create a key inside a KASP. * * Requires: * - *\li 'mctx' is a valid memory context. + *\li 'kasp' is a valid kasp. * *\li keyp != NULL && *keyp == NULL * diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c index ff9293790b..66938d91e4 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -138,6 +138,13 @@ dns_kasp_sigrefresh(dns_kasp_t *kasp) { return (kasp->signatures_refresh); } +void +dns_kasp_setsigrefresh(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->signatures_refresh = value; +} + uint32_t dns_kasp_sigvalidity(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -145,6 +152,13 @@ dns_kasp_sigvalidity(dns_kasp_t *kasp) { return (kasp->signatures_validity); } +void +dns_kasp_setsigvalidity(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->signatures_validity = value; +} + uint32_t dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -152,6 +166,13 @@ dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp) { return (kasp->signatures_validity_dnskey); } +void +dns_kasp_setsigvalidity_dnskey(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->signatures_validity = value; +} + dns_ttl_t dns_kasp_dnskeyttl(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -159,6 +180,13 @@ dns_kasp_dnskeyttl(dns_kasp_t *kasp) { return (kasp->dnskey_ttl); } +void +dns_kasp_setdnskeyttl(dns_kasp_t *kasp, dns_ttl_t ttl) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->dnskey_ttl = ttl; +} + uint32_t dns_kasp_publishsafety(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -166,6 +194,13 @@ dns_kasp_publishsafety(dns_kasp_t *kasp) { return (kasp->publish_safety); } +void +dns_kasp_setpublishsafety(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->publish_safety = value; +} + uint32_t dns_kasp_retiresafety(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -173,6 +208,13 @@ dns_kasp_retiresafety(dns_kasp_t *kasp) { return (kasp->retire_safety); } +void +dns_kasp_setretiresafety(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->retire_safety = value; +} + dns_ttl_t dns_kasp_zonemaxttl(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -180,6 +222,13 @@ dns_kasp_zonemaxttl(dns_kasp_t *kasp) { return (kasp->zone_max_ttl); } +void +dns_kasp_setzonemaxttl(dns_kasp_t *kasp, dns_ttl_t ttl) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->zone_max_ttl = ttl; +} + uint32_t dns_kasp_zonepropagationdelay(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -187,6 +236,13 @@ dns_kasp_zonepropagationdelay(dns_kasp_t *kasp) { return (kasp->zone_propagation_delay); } +void +dns_kasp_setzonepropagationdelay(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->zone_propagation_delay = value; +} + dns_ttl_t dns_kasp_dsttl(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -194,6 +250,13 @@ dns_kasp_dsttl(dns_kasp_t *kasp) { return (kasp->parent_ds_ttl); } +void +dns_kasp_setdsttl(dns_kasp_t *kasp, dns_ttl_t ttl) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->parent_ds_ttl = ttl; +} + uint32_t dns_kasp_parentpropagationdelay(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -201,6 +264,13 @@ dns_kasp_parentpropagationdelay(dns_kasp_t *kasp) { return (kasp->parent_propagation_delay); } +void +dns_kasp_setparentpropagationdelay(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->parent_propagation_delay = value; +} + uint32_t dns_kasp_parentregistrationdelay(dns_kasp_t *kasp) { REQUIRE(DNS_KASP_VALID(kasp)); @@ -208,6 +278,13 @@ dns_kasp_parentregistrationdelay(dns_kasp_t *kasp) { return (kasp->parent_registration_delay); } +void +dns_kasp_setparentregistrationdelay(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + kasp->parent_registration_delay = value; +} + isc_result_t dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp) { @@ -234,16 +311,42 @@ dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp) return (ISC_R_SUCCESS); } +dns_kasp_keylist_t +dns_kasp_keys(dns_kasp_t *kasp) +{ + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + return (kasp->keys); +} + +bool +dns_kasp_keylist_empty(dns_kasp_t *kasp) +{ + REQUIRE(DNS_KASP_VALID(kasp)); + return (ISC_LIST_EMPTY(kasp->keys)); +} + +void +dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key) +{ + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + REQUIRE(key != NULL); + + ISC_LIST_APPEND(kasp->keys, key, link); +} + isc_result_t -dns_kasp_key_create(isc_mem_t* mctx, dns_kasp_key_t **keyp) +dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp) { dns_kasp_key_t *key; + REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(keyp != NULL && *keyp == NULL); - key = isc_mem_get(mctx, sizeof(*key)); + key = isc_mem_get(kasp->mctx, sizeof(*key)); key->mctx = NULL; - isc_mem_attach(mctx, &key->mctx); + isc_mem_attach(kasp->mctx, &key->mctx); ISC_LINK_INIT(key, link); diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c index c8eacc23bd..8c1441cacd 100644 --- a/lib/dns/keymgr.c +++ b/lib/dns/keymgr.c @@ -1330,7 +1330,7 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, } /* Create keys according to the policy, if come in short. */ - for (kkey = ISC_LIST_HEAD(kasp->keys); kkey != NULL; + for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link)) { isc_stdtime_t retire = 0, active = 0, prepub = 0; diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 2d90054b6b..31af4b1c1e 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -414,6 +414,7 @@ dns_journal_rollforward dns_journal_set_sourceserial dns_journal_write_transaction dns_journal_writediff +dns_kasp_addkey dns_kasp_attach dns_kasp_create dns_kasp_detach @@ -428,10 +429,23 @@ dns_kasp_key_ksk dns_kasp_key_lifetime dns_kasp_key_size dns_kasp_key_zsk +dns_kasp_keylist_empty +dns_kasp_keys dns_kasp_parentpropagationdelay dns_kasp_parentregistrationdelay dns_kasp_publishsafety dns_kasp_retiresafety +dns_kasp_setdnskeyttl +dns_kasp_setdsttl +dns_kasp_setparentpropagationdelay +dns_kasp_setparentregistrationdelay +dns_kasp_setpublishsafety +dns_kasp_setretiresafety +dns_kasp_setsigrefresh +dns_kasp_setsigvalidity +dns_kasp_setsigvalidity_dnskey +dns_kasp_setzonemaxttl +dns_kasp_setzonepropagationdelay dns_kasp_signdelay dns_kasp_sigrefresh dns_kasp_sigvalidity diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 89d008bc89..41af9487ac 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -7039,7 +7039,7 @@ signed_with_good_key(dns_zone_t* zone, dns_db_t *db, dns_dbnode_t *node, int zsk_count = 0; bool approved; - for (kkey = ISC_LIST_HEAD(kasp->keys); kkey != NULL; + for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link)) { if (dns_kasp_key_algorithm(kkey) != dst_key_alg(key)) { diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index 0d19d882e4..b1111f6891 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -71,7 +71,7 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t* kasp) dns_kasp_key_t *key = NULL; /* Create a new key reference. */ - result = dns_kasp_key_create(kasp->mctx, &key); + result = dns_kasp_key_create(kasp, &key); if (result != ISC_R_SUCCESS) { return (result); } @@ -103,8 +103,7 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t* kasp) key->length = cfg_obj_asuint32(obj); } } - ISC_LIST_APPEND(kasp->keys, key, link); - ISC_INSIST(!(ISC_LIST_EMPTY(kasp->keys))); + dns_kasp_addkey(kasp, key); return (result); } @@ -158,20 +157,21 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, isc_mem_t* mctx, maps[i] = NULL; /* Configuration: Signatures */ - kasp->signatures_refresh = get_duration( - maps, "signatures-refresh", DNS_KASP_SIG_REFRESH); - kasp->signatures_validity = get_duration( - maps, "signatures-validity", DNS_KASP_SIG_VALIDITY); - kasp->signatures_validity_dnskey = get_duration( - maps, "signatures-validity-dnskey", - DNS_KASP_SIG_VALIDITY_DNSKEY); + dns_kasp_setsigrefresh(kasp, get_duration(maps, "signatures-refresh", + DNS_KASP_SIG_REFRESH)); + dns_kasp_setsigvalidity(kasp, get_duration(maps, "signatures-validity", + DNS_KASP_SIG_VALIDITY)); + dns_kasp_setsigvalidity_dnskey(kasp, get_duration(maps, + "signatures-validity-dnskey", + DNS_KASP_SIG_VALIDITY_DNSKEY)); /* Configuration: Keys */ - kasp->dnskey_ttl = get_duration(maps, "dnskey-ttl", DNS_KASP_KEY_TTL); - kasp->publish_safety = get_duration(maps, "publish-safety", - DNS_KASP_PUBLISH_SAFETY); - kasp->retire_safety = get_duration(maps, "retire-safety", - DNS_KASP_RETIRE_SAFETY); + dns_kasp_setdnskeyttl(kasp, get_duration(maps, "dnskey-ttl", + DNS_KASP_KEY_TTL)); + dns_kasp_setpublishsafety(kasp, get_duration(maps, "publish-safety", + DNS_KASP_PUBLISH_SAFETY)); + dns_kasp_setretiresafety(kasp, get_duration(maps, "retire-safety", + DNS_KASP_RETIRE_SAFETY)); (void)confget(maps, "keys", &keys); if (keys == NULL) { @@ -190,26 +190,24 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, isc_mem_t* mctx, } } } - ISC_INSIST(!(ISC_LIST_EMPTY(kasp->keys))); + ISC_INSIST(!(dns_kasp_keylist_empty(kasp))); /* Configuration: Zone settings */ - kasp->zone_max_ttl = get_duration(maps, "zone-max-ttl", - DNS_KASP_ZONE_MAXTTL); - kasp->zone_propagation_delay = get_duration(maps, - "zone-propagation-delay", - DNS_KASP_ZONE_PROPDELAY); + dns_kasp_setzonemaxttl(kasp, get_duration(maps, "zone-max-ttl", + DNS_KASP_ZONE_MAXTTL)); + dns_kasp_setzonepropagationdelay(kasp, get_duration(maps, + "zone-propagation-delay", + DNS_KASP_ZONE_PROPDELAY)); /* Configuration: Parent settings */ - kasp->parent_ds_ttl = get_duration(maps, "parent-ds-ttl", - DNS_KASP_DS_TTL); - kasp->parent_propagation_delay = get_duration( - maps, + dns_kasp_setdsttl(kasp, get_duration(maps, "parent-ds-ttl", + DNS_KASP_DS_TTL)); + dns_kasp_setparentpropagationdelay(kasp, get_duration(maps, "parent-propagation-delay", - DNS_KASP_PARENT_PROPDELAY); - kasp->parent_registration_delay = get_duration( - maps, + DNS_KASP_PARENT_PROPDELAY)); + dns_kasp_setparentregistrationdelay(kasp, get_duration(maps, "parent-registration-delay", - DNS_KASP_PARENT_REGDELAY); + DNS_KASP_PARENT_REGDELAY)); // TODO: Rest of the configuration From 5eedd365d41fc8e3e75c8fe63de08eef70d4b0dd Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Tue, 5 Nov 2019 13:36:40 +1100 Subject: [PATCH 40/43] Insist that kasp is not linked. --- lib/dns/kasp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c index 66938d91e4..1784b46be0 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -82,6 +82,8 @@ destroy(dns_kasp_t *kasp) { dns_kasp_key_t *key; dns_kasp_key_t *key_next; + ISC_INSIST(!ISC_LINK_LINKED(kasp, link)); + for (key = ISC_LIST_HEAD(kasp->keys); key != NULL; key = key_next) { key_next = ISC_LIST_NEXT(key, link); ISC_LIST_UNLINK(kasp->keys, key, link); From ce1c1631b35794ff516789f01ec791e1f3162fe0 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Tue, 5 Nov 2019 13:38:00 +1100 Subject: [PATCH 41/43] move appending kasp to the list until we can't fail; document why we don't detach --- lib/isccfg/kaspconf.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index b1111f6891..b39eb9c44c 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -143,10 +143,6 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, isc_mem_t* mctx, } INSIST(kasp != NULL); - /* Append it to the list for future lookups. */ - ISC_LIST_APPEND(*kasplist, kasp, link); - ISC_INSIST(!(ISC_LIST_EMPTY(*kasplist))); - /* Now configure. */ INSIST(DNS_KASP_VALID(kasp)); @@ -211,8 +207,13 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, isc_mem_t* mctx, // TODO: Rest of the configuration + /* Append it to the list for future lookups. */ + ISC_LIST_APPEND(*kasplist, kasp, link); + ISC_INSIST(!(ISC_LIST_EMPTY(*kasplist))); + /* Success: Attach the kasp to the pointer and return. */ dns_kasp_attach(kasp, kaspp); + /* Don't detach as kasp is on '*kasplist' */ return (ISC_R_SUCCESS); cleanup: From 5f464d15a0342dafa200957acf401daa97c4da3f Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 5 Nov 2019 17:22:35 +0100 Subject: [PATCH 42/43] dnssec-policy inheritance from options/view 'dnssec-policy' can now also be set on the options and view level and a zone that does not set 'dnssec-policy' explicitly will inherit it from the view or options level. This requires a new keyword to be introduced: 'none'. If set to 'none' the zone will not be DNSSEC maintained, in other words it will stay unsigned. You can use this to break the inheritance. Of course you can also break the inheritance by referring to a different policy. The keywords 'default' and 'none' are not allowed when configuring your own dnssec-policy statement. Add appropriate tests for checking the configuration (checkconf) and add tests to the kasp system test to verify the inheritance works. Edit the kasp system test such that it can deal with unsigned zones and views (so setting a TSIG on the query). --- bin/named/config.c | 1 + bin/named/server.c | 3 +- bin/named/zoneconf.c | 21 +- bin/tests/system/checkconf/bad-kasp1.conf | 6 +- bin/tests/system/checkconf/bad-kasp5.conf | 22 ++ bin/tests/system/checkconf/good-kasp.conf | 16 +- bin/tests/system/checkconf/good.conf | 41 ++ bin/tests/system/checkconf/good.zonelist | 4 + bin/tests/system/kasp/README | 2 + bin/tests/system/kasp/clean.sh | 1 + bin/tests/system/kasp/ns2/named.conf.in | 16 + bin/tests/system/kasp/ns2/setup.sh | 14 +- bin/tests/system/kasp/ns2/template.tld.db.in | 25 ++ bin/tests/system/kasp/ns3/named.conf.in | 20 +- bin/tests/system/kasp/ns3/setup.sh | 9 +- bin/tests/system/kasp/ns4/named.conf.in | 117 ++++++ bin/tests/system/kasp/ns4/setup.sh | 28 ++ bin/tests/system/kasp/ns4/template.db.in | 25 ++ bin/tests/system/kasp/ns5/named.conf.in | 117 ++++++ bin/tests/system/kasp/ns5/setup.sh | 28 ++ bin/tests/system/kasp/ns5/template.db.in | 25 ++ bin/tests/system/kasp/setup.sh | 13 +- bin/tests/system/kasp/tests.sh | 370 +++++++++++++++---- lib/bind9/check.c | 64 +++- lib/isccfg/kaspconf.c | 2 + lib/isccfg/namedconf.c | 6 +- util/copyrights | 2 + 27 files changed, 895 insertions(+), 103 deletions(-) create mode 100644 bin/tests/system/checkconf/bad-kasp5.conf create mode 100644 bin/tests/system/kasp/ns2/template.tld.db.in create mode 100644 bin/tests/system/kasp/ns4/named.conf.in create mode 100644 bin/tests/system/kasp/ns4/setup.sh create mode 100644 bin/tests/system/kasp/ns4/template.db.in create mode 100644 bin/tests/system/kasp/ns5/named.conf.in create mode 100644 bin/tests/system/kasp/ns5/setup.sh create mode 100644 bin/tests/system/kasp/ns5/template.db.in diff --git a/bin/named/config.c b/bin/named/config.c index 6ea56d8881..aeabf49057 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -58,6 +58,7 @@ options {\n\ "\ # deallocate-on-exit ;\n\ # directory \n\ + dnssec-policy \"none\";\n\ dump-file \"named_dump.db\";\n\ edns-udp-size 4096;\n\ # fake-iquery ;\n" diff --git a/bin/named/server.c b/bin/named/server.c index c788deb19e..ca216599ff 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -6260,7 +6260,8 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, ((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))) + ISC_R_SUCCESS && signing != NULL && + strcmp(cfg_obj_asstring(signing), "none") != 0))) { dns_zone_getraw(zone, &raw); if (raw == NULL) { diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index 0978902573..cb0ec51fcf 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -1197,18 +1197,21 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, if (ztype != dns_zone_stub && ztype != dns_zone_staticstub && ztype != dns_zone_redirect) { obj = NULL; - result = cfg_map_get(zoptions, "dnssec-policy", &obj); + result = named_config_get(maps, "dnssec-policy", &obj); if (result == ISC_R_SUCCESS) { kaspname = cfg_obj_asstring(obj); - result = dns_kasplist_find(kasplist, kaspname, &kasp); - if (result != ISC_R_SUCCESS) { - cfg_obj_log(obj, named_g_lctx, - ISC_LOG_ERROR, - "'dnssec-policy '%s' not found ", - kaspname); - RETERR(result); + if (strcmp(kaspname, "none") != 0) { + result = dns_kasplist_find(kasplist, kaspname, + &kasp); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(obj, named_g_lctx, + ISC_LOG_ERROR, + "'dnssec-policy '%s' not " + "found ", kaspname); + RETERR(result); + } + dns_zone_setkasp(zone, kasp); } - dns_zone_setkasp(zone, kasp); } obj = NULL; diff --git a/bin/tests/system/checkconf/bad-kasp1.conf b/bin/tests/system/checkconf/bad-kasp1.conf index bad8ff2090..686160f983 100644 --- a/bin/tests/system/checkconf/bad-kasp1.conf +++ b/bin/tests/system/checkconf/bad-kasp1.conf @@ -9,12 +9,14 @@ * information regarding copyright ownership. */ -options { - dnssec-policy "notatzonelevel"; +// Using the keyword 'default' is not allowed. +dnssec-policy "default" { + signatures-refresh P5D; }; zone "example.net" { type master; file "example.db"; + dnssec-policy "default"; }; diff --git a/bin/tests/system/checkconf/bad-kasp5.conf b/bin/tests/system/checkconf/bad-kasp5.conf new file mode 100644 index 0000000000..a399079db5 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp5.conf @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// Using the keyword 'none' is not allowed. +dnssec-policy "none" { + signatures-refresh P5D; +}; + +zone "example.net" { + type master; + file "example.db"; + dnssec-policy "none"; +}; + diff --git a/bin/tests/system/checkconf/good-kasp.conf b/bin/tests/system/checkconf/good-kasp.conf index 041e6bfae8..04c1cef199 100644 --- a/bin/tests/system/checkconf/good-kasp.conf +++ b/bin/tests/system/checkconf/good-kasp.conf @@ -35,13 +35,25 @@ dnssec-policy "test" { options { dnssec-policy "default"; }; +options { + dnssec-policy "default"; +}; zone "example1" { type master; - dnssec-policy "test"; file "example1.db"; }; zone "example2" { type master; - dnssec-policy "default"; file "example2.db"; + dnssec-policy "test"; +}; +zone "example3" { + type master; + file "example3.db"; + dnssec-policy "default"; +}; +zone "example4" { + type master; + file "example4.db"; + dnssec-policy "none"; }; diff --git a/bin/tests/system/checkconf/good.conf b/bin/tests/system/checkconf/good.conf index b6136d6f3b..37d3de6504 100644 --- a/bin/tests/system/checkconf/good.conf +++ b/bin/tests/system/checkconf/good.conf @@ -14,6 +14,24 @@ */ /* cut here */ +dnssec-policy "test" { + dnskey-ttl 3600; + keys { + ksk key-directory lifetime P1Y algorithm 13 256; + zsk key-directory lifetime P30D algorithm 13; + csk key-directory lifetime P30D algorithm 8 2048; + }; + publish-safety PT3600S; + retire-safety PT3600S; + signatures-refresh P3D; + signatures-validity P2W; + signatures-validity-dnskey P14D; + zone-max-ttl 86400; + zone-propagation-delay PT5M; + parent-ds-ttl 7200; + parent-propagation-delay PT1H; + parent-registration-delay P1D; +}; options { avoid-v4-udp-ports { 100; @@ -60,6 +78,7 @@ options { validate-except { "corp"; }; + dnssec-policy "test"; transfer-source 0.0.0.0 dscp 63; zone-statistics none; }; @@ -140,6 +159,28 @@ view "third" { }; }; }; +view "fourth" { + zone "dnssec-test" { + type master; + file "dnssec-test.db"; + dnssec-policy "test"; + }; + zone "dnssec-default" { + type master; + file "dnssec-default.db"; + dnssec-policy "default"; + }; + zone "dnssec-inherit" { + type master; + file "dnssec-inherit.db"; + }; + zone "dnssec-none" { + type master; + file "dnssec-none.db"; + dnssec-policy "none"; + }; + dnssec-policy "default"; +}; view "chaos" chaos { zone "hostname.bind" chaos { type master; diff --git a/bin/tests/system/checkconf/good.zonelist b/bin/tests/system/checkconf/good.zonelist index e4504fc672..dff4d170ca 100644 --- a/bin/tests/system/checkconf/good.zonelist +++ b/bin/tests/system/checkconf/good.zonelist @@ -8,4 +8,8 @@ clone IN third in-view first dnssec IN third master p IN third primary s IN third secondary +dnssec-test IN fourth master +dnssec-default IN fourth master +dnssec-inherit IN fourth master +dnssec-none IN fourth master hostname.bind chaos chaos master diff --git a/bin/tests/system/kasp/README b/bin/tests/system/kasp/README index d543c1a779..ceafd19772 100644 --- a/bin/tests/system/kasp/README +++ b/bin/tests/system/kasp/README @@ -9,3 +9,5 @@ ns1 is reserved for the root server. ns2 is running primary service for ns3. ns3 is an authoritative server for the various test domains. + +ns4 and ns5 are authoritative servers for various test domains related to views. diff --git a/bin/tests/system/kasp/clean.sh b/bin/tests/system/kasp/clean.sh index c9ef776eb6..803dd703cd 100644 --- a/bin/tests/system/kasp/clean.sh +++ b/bin/tests/system/kasp/clean.sh @@ -21,5 +21,6 @@ rm -f ns*/K*.private ns*/K*.key ns*/K*.state rm -f ns*/dsset-* ns*/*.db ns*/*.db.signed rm -f ns*/keygen.out.* ns*/settime.out.* ns*/signer.out.* rm -f ns*/managed-keys.bind +rm -f ns*/*.mkeys # NS3 specific rm -f ns3/zones ns3/*.db.infile diff --git a/bin/tests/system/kasp/ns2/named.conf.in b/bin/tests/system/kasp/ns2/named.conf.in index 640def73b3..cad71da5b4 100644 --- a/bin/tests/system/kasp/ns2/named.conf.in +++ b/bin/tests/system/kasp/ns2/named.conf.in @@ -21,6 +21,7 @@ options { listen-on-v6 { none; }; allow-transfer { any; }; recursion no; + dnssec-policy "none"; }; key rndc_key { @@ -32,6 +33,21 @@ controls { inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; }; +/* Inherit dnssec-policy (which is none) */ + +zone "unsigned.tld" { + type master; + file "unsigned.tld.db"; +}; + +/* Override dnssec-policy */ + +zone "signed.tld" { + type master; + dnssec-policy "default"; + file "signed.tld.db"; +}; + /* Primary service for ns3 */ zone "secondary.kasp" { diff --git a/bin/tests/system/kasp/ns2/setup.sh b/bin/tests/system/kasp/ns2/setup.sh index d495e05f52..588735d0a6 100644 --- a/bin/tests/system/kasp/ns2/setup.sh +++ b/bin/tests/system/kasp/ns2/setup.sh @@ -14,8 +14,20 @@ echo_i "ns2/setup.sh" -echo_i "setting up zone: $zone" zone="secondary.kasp" +echo_i "setting up zone: $zone" zonefile="${zone}.db" infile="${zonefile}.in" cp $infile $zonefile + +zone="signed.tld" +echo_i "setting up zone: $zone" +zonefile="${zone}.db" +infile="template.tld.db.in" +cp $infile $zonefile + +zone="unsigned.tld" +echo_i "setting up zone: $zone" +zonefile="${zone}.db" +infile="template.tld.db.in" +cp $infile $zonefile diff --git a/bin/tests/system/kasp/ns2/template.tld.db.in b/bin/tests/system/kasp/ns2/template.tld.db.in new file mode 100644 index 0000000000..7d8b924f64 --- /dev/null +++ b/bin/tests/system/kasp/ns2/template.tld.db.in @@ -0,0 +1,25 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA secondary.kasp. hostmaster.kasp. ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 +ns2 A 10.53.0.2 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 + diff --git a/bin/tests/system/kasp/ns3/named.conf.in b/bin/tests/system/kasp/ns3/named.conf.in index 1e11814542..c9ae05894b 100644 --- a/bin/tests/system/kasp/ns3/named.conf.in +++ b/bin/tests/system/kasp/ns3/named.conf.in @@ -11,6 +11,9 @@ // NS3 +include "policies/kasp.conf"; +include "policies/autosign.conf"; + options { query-source address 10.53.0.3; notify-source 10.53.0.3; @@ -21,6 +24,7 @@ options { listen-on-v6 { none; }; allow-transfer { any; }; recursion no; + dnssec-policy "rsasha1"; }; key rndc_key { @@ -32,9 +36,6 @@ controls { inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; }; -include "policies/kasp.conf"; -include "policies/autosign.conf"; - /* Zones that are getting initially signed */ /* The default case: No keys created, using default policy. */ @@ -51,6 +52,19 @@ zone "rsasha1.kasp" { dnssec-policy "rsasha1"; }; +/* A zone that inherits dnssec-policy. */ +zone "inherit.kasp" { + type master; + file "inherit.kasp.db"; +}; + +/* A zone that overrides dnssec-policy. */ +zone "unsigned.kasp" { + type master; + file "unsigned.kasp.db"; + dnssec-policy "none"; +}; + /* A master zone with dnssec-policy but keys already created. */ zone "dnssec-keygen.kasp" { type master; diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh index 782747b4b8..5a4b44bca5 100644 --- a/bin/tests/system/kasp/ns3/setup.sh +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -43,12 +43,19 @@ U="UNRETENTIVE" # Set up zones that will be initially signed. # for zn in default rsasha1 dnssec-keygen some-keys legacy-keys pregenerated \ - rsasha1-nsec3 rsasha256 rsasha512 ecdsa256 ecdsa384 + rsasha1-nsec3 rsasha256 rsasha512 ecdsa256 ecdsa384 inherit do setup "${zn}.kasp" cp template.db.in $zonefile done +# Set up zone that stays unsigned. +zone="unsigned.kasp" +echo_i "setting up zone: $zone" +zonefile="${zone}.db" +infile="${zone}.db.infile" +cp template.db.in $zonefile + # Some of these zones already have keys. zone="dnssec-keygen.kasp" $KEYGEN -k rsasha1 -l policies/kasp.conf $zone > keygen.out.$zone.1 2>&1 diff --git a/bin/tests/system/kasp/ns4/named.conf.in b/bin/tests/system/kasp/ns4/named.conf.in new file mode 100644 index 0000000000..c8d4094f85 --- /dev/null +++ b/bin/tests/system/kasp/ns4/named.conf.in @@ -0,0 +1,117 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS4 + +key "sha1" { + algorithm "hmac-sha1"; + secret "FrSt77yPTFx6hTs4i2tKLB9LmE0="; +}; + +key "sha224" { + algorithm "hmac-sha224"; + secret "hXfwwwiag2QGqblopofai9NuW28q/1rH4CaTnA=="; +}; + +key "sha256" { + algorithm "hmac-sha256"; + secret "R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY="; +}; + +dnssec-policy "test" { + keys { + csk key-directory lifetime 0 algorithm 14; + }; +}; + +options { + query-source address 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + listen-on-v6 { none; }; + recursion no; + dnssec-policy "test"; +}; + +view "inherit" { + match-clients { key "sha1"; }; + + /* Inherit dnssec-policy 'test' */ + zone "inherit.inherit.signed" { + type master; + file "inherit.inherit.signed.db"; + }; + + /* Override dnssec-policy */ + zone "override.inherit.signed" { + type master; + dnssec-policy "default"; + file "override.inherit.signed.db"; + }; + + /* Unset dnssec-policy */ + zone "none.inherit.signed" { + type master; + dnssec-policy "none"; + file "none.inherit.signed.db"; + }; +}; + +view "override" { + match-clients { key "sha224"; }; + dnssec-policy "default"; + + /* Inherit dnssec-policy 'test' */ + zone "inherit.override.signed" { + type master; + file "inherit.override.signed.db"; + }; + + /* Override dnssec-policy */ + zone "override.override.signed" { + type master; + dnssec-policy "test"; + file "override.override.signed.db"; + }; + + /* Unset dnssec-policy */ + zone "none.override.signed" { + type master; + dnssec-policy "none"; + file "none.override.signed.db"; + }; +}; + +view "none" { + match-clients { key "sha256"; }; + dnssec-policy "none"; + + /* Inherit dnssec-policy 'none' */ + zone "inherit.none.signed" { + type master; + file "inherit.none.signed.db"; + }; + + /* Override dnssec-policy */ + zone "override.none.signed" { + type master; + dnssec-policy "test"; + file "override.none.signed.db"; + }; + + /* Unset dnssec-policy */ + zone "none.none.signed" { + type master; + dnssec-policy "none"; + file "none.none.signed.db"; + }; +}; diff --git a/bin/tests/system/kasp/ns4/setup.sh b/bin/tests/system/kasp/ns4/setup.sh new file mode 100644 index 0000000000..ca830dd028 --- /dev/null +++ b/bin/tests/system/kasp/ns4/setup.sh @@ -0,0 +1,28 @@ +#!/bin/sh -e +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +echo_i "ns4/setup.sh" + +# +# Set up zones that potentially will be initially signed. +# +for zn in inherit.inherit override.inherit none.inherit \ + inherit.override override.override none.override \ + inherit.none override.none none.none +do + zone="$zn.signed" + echo_i "setting up zone: $zone" + zonefile="${zone}.db" + cp template.db.in $zonefile +done diff --git a/bin/tests/system/kasp/ns4/template.db.in b/bin/tests/system/kasp/ns4/template.db.in new file mode 100644 index 0000000000..59946e07ba --- /dev/null +++ b/bin/tests/system/kasp/ns4/template.db.in @@ -0,0 +1,25 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns4 +ns4 A 10.53.0.4 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 + diff --git a/bin/tests/system/kasp/ns5/named.conf.in b/bin/tests/system/kasp/ns5/named.conf.in new file mode 100644 index 0000000000..2c9c8f6214 --- /dev/null +++ b/bin/tests/system/kasp/ns5/named.conf.in @@ -0,0 +1,117 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS5 + +key "sha1" { + algorithm "hmac-sha1"; + secret "FrSt77yPTFx6hTs4i2tKLB9LmE0="; +}; + +key "sha224" { + algorithm "hmac-sha224"; + secret "hXfwwwiag2QGqblopofai9NuW28q/1rH4CaTnA=="; +}; + +key "sha256" { + algorithm "hmac-sha256"; + secret "R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY="; +}; + +dnssec-policy "test" { + keys { + csk key-directory lifetime 0 algorithm 14; + }; +}; + +options { + query-source address 10.53.0.5; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.5; }; + listen-on-v6 { none; }; + recursion no; + dnssec-policy "none"; +}; + +view "inherit" { + match-clients { key "sha1"; }; + + /* Inherit dnssec-policy 'none' */ + zone "inherit.inherit.unsigned" { + type master; + file "inherit.inherit.unsigned.db"; + }; + + /* Override dnssec-policy */ + zone "override.inherit.unsigned" { + type master; + dnssec-policy "default"; + file "override.inherit.unsigned.db"; + }; + + /* Unset dnssec-policy */ + zone "none.inherit.unsigned" { + type master; + dnssec-policy "none"; + file "none.inherit.unsigned.db"; + }; +}; + +view "override" { + match-clients { key "sha224"; }; + dnssec-policy "default"; + + /* Inherit dnssec-policy 'default' */ + zone "inherit.override.unsigned" { + type master; + file "inherit.override.unsigned.db"; + }; + + /* Override dnssec-policy */ + zone "override.override.unsigned" { + type master; + dnssec-policy "test"; + file "override.override.unsigned.db"; + }; + + /* Unset dnssec-policy */ + zone "none.override.unsigned" { + type master; + dnssec-policy "none"; + file "none.override.unsigned.db"; + }; +}; + +view "none" { + match-clients { key "sha256"; }; + dnssec-policy "none"; + + /* Inherit dnssec-policy 'none' */ + zone "inherit.none.unsigned" { + type master; + file "inherit.none.unsigned.db"; + }; + + /* Override dnssec-policy */ + zone "override.none.unsigned" { + type master; + dnssec-policy "test"; + file "override.none.unsigned.db"; + }; + + /* Unset dnssec-policy */ + zone "none.none.unsigned" { + type master; + dnssec-policy "none"; + file "none.none.unsigned.db"; + }; +}; diff --git a/bin/tests/system/kasp/ns5/setup.sh b/bin/tests/system/kasp/ns5/setup.sh new file mode 100644 index 0000000000..b6f274e6a7 --- /dev/null +++ b/bin/tests/system/kasp/ns5/setup.sh @@ -0,0 +1,28 @@ +#!/bin/sh -e +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. "$SYSTEMTESTTOP/conf.sh" + +echo_i "ns5/setup.sh" + +# +# Set up zones that potentially will be initially signed. +# +for zn in inherit.inherit override.inherit none.inherit \ + inherit.override override.override none.override \ + inherit.none override.none none.none +do + zone="$zn.unsigned" + echo_i "setting up zone: $zone" + zonefile="${zone}.db" + cp template.db.in $zonefile +done diff --git a/bin/tests/system/kasp/ns5/template.db.in b/bin/tests/system/kasp/ns5/template.db.in new file mode 100644 index 0000000000..2f73182e72 --- /dev/null +++ b/bin/tests/system/kasp/ns5/template.db.in @@ -0,0 +1,25 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns5 +ns5 A 10.53.0.5 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 + diff --git a/bin/tests/system/kasp/setup.sh b/bin/tests/system/kasp/setup.sh index 6bdf0035a8..0d93046ae1 100644 --- a/bin/tests/system/kasp/setup.sh +++ b/bin/tests/system/kasp/setup.sh @@ -20,14 +20,23 @@ mkdir keys copy_setports ns2/named.conf.in ns2/named.conf copy_setports ns3/named.conf.in ns3/named.conf +copy_setports ns4/named.conf.in ns4/named.conf +copy_setports ns5/named.conf.in ns5/named.conf -# ns2: Setup zones +# Setup zones ( cd ns2 $SHELL setup.sh ) -# ns3: Setup zones ( cd ns3 $SHELL setup.sh ) +( + cd ns4 + $SHELL setup.sh +) +( + cd ns5 + $SHELL setup.sh +) diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index a79a871cc6..5fa2178e99 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -21,6 +21,14 @@ n=0 ############################################################################### DEFAULT_TTL=300 +############################################################################### +# Query properties # +############################################################################### +TSIG="" +SHA1="FrSt77yPTFx6hTs4i2tKLB9LmE0=" +SHA224="hXfwwwiag2QGqblopofai9NuW28q/1rH4CaTnA==" +SHA256="R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY=" + ############################################################################### # Key properties # ############################################################################### @@ -82,7 +90,12 @@ key_clear "KEY3" # Call dig with default options. dig_with_opts() { - "$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" "$@" + _tsig="" + if [ -n "$TSIG" ]; then + _tsig="-y $TSIG" + fi + + "$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p $PORT $_tsig "$@" } # RNDC. @@ -108,7 +121,9 @@ get_keyids() { _start="${_dir}/K${_zone}.+${_algorithm}+" _end=".key" - ls ${_start}*${_end} | sed "s/$_dir\/K${_zone}.+${_algorithm}+\([0-9]\{5\}\)${_end}/\1/" + if [ $_algorithm -ne 0 ]; then + ls ${_start}*${_end} | sed "s/$_dir\/K${_zone}.+${_algorithm}+\([0-9]\{5\}\)${_end}/\1/" + fi } # By default log errors and don't quit immediately. @@ -124,15 +139,17 @@ log_error() { # $3: Policy name # $4: DNSKEY TTL # $5: Number of keys +# $6: Name server # # This will set the following environment variables for testing: -# DIR, ZONE, POLICY, DNSKEY_TTL, NUM_KEYS +# DIR, ZONE, POLICY, DNSKEY_TTL, NUM_KEYS, SERVER zone_properties() { DIR=$1 ZONE=$2 POLICY=$3 DNSKEY_TTL=$4 NUM_KEYS=$5 + SERVER=$6 } # Set key properties for testing keys. @@ -492,7 +509,7 @@ dnssec_verify() n=$((n+1)) echo_i "dnssec-verify zone ${ZONE} ($n)" ret=0 - dig_with_opts $ZONE @10.53.0.3 AXFR > dig.out.axfr.test$n || log_error "dig ${ZONE} AXFR failed" + dig_with_opts $ZONE @${SERVER} AXFR > dig.out.axfr.test$n || log_error "dig ${ZONE} AXFR failed" $VERIFY -z -o $ZONE dig.out.axfr.test$n > /dev/null || log_error "dnssec verify zone $ZONE failed" test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) @@ -505,7 +522,7 @@ dnssec_verify() # # dnssec-keygen # -zone_properties "keys" "kasp" "kasp" "200" +zone_properties "keys" "kasp" "kasp" "200" "10.53.0.1" n=$((n+1)) echo_i "check that 'dnssec-keygen -k' (configured policy) creates valid files ($n)" @@ -557,7 +574,7 @@ _log=1 n=$((n+1)) echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" ret=0 -zone_properties "." "kasp" "default" "3600" +zone_properties "." "kasp" "default" "3600" "10.53.0.1" key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" key_timings "KEY1" "none" "none" "none" "none" "none" key_states "KEY1" "none" "none" "none" "none" "none" @@ -572,7 +589,7 @@ status=$((status+ret)) n=$((n+1)) echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)" ret=0 -zone_properties "." "kasp" "default" "3600" +zone_properties "." "kasp" "default" "3600" "10.53.0.1" key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" key_timings "KEY1" "none" "none" "none" "none" "none" key_states "KEY1" "none" "none" "none" "none" "none" @@ -672,7 +689,7 @@ status=$((status+ret)) # # Check the zone with default kasp policy has loaded and is signed. -zone_properties "ns3" "default.kasp" "_default" "3600" +zone_properties "ns3" "default.kasp" "default" "3600" "1" "10.53.0.3" key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" # The first key is immediately published and activated. key_timings "KEY1" "published" "active" "none" "none" "none" "none" @@ -695,7 +712,7 @@ qtype="DNSKEY" n=$((n+1)) echo_i "check ${qtype} rrset is signed correctly for zone ${ZONE} ($n)" ret=0 -dig_with_opts $ZONE @10.53.0.3 $qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${qtype} failed" +dig_with_opts $ZONE @${SERVER} $qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${qtype} failed" grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${qtype}.*257.*.3.*${KEY1[$ALG_NUM]}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${qtype} record in response" lines=$(get_keys_which_signed $qtype dig.out.$DIR.test$n | wc -l) @@ -709,7 +726,7 @@ qtype="SOA" n=$((n+1)) echo_i "check ${qtype} rrset is signed correctly for zone ${ZONE} ($n)" ret=0 -dig_with_opts $ZONE @10.53.0.3 $qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${qtype} failed" +dig_with_opts $ZONE @${SERVER} $qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${qtype} failed" grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*${qtype}.*mname1\..*\." dig.out.$DIR.test$n > /dev/null || log_error "missing ${qtype} record in response" lines=$(get_keys_which_signed $qtype dig.out.$DIR.test$n | wc -l) @@ -730,14 +747,14 @@ while [ $i -lt 5 ] do ret=0 - dig_with_opts "a.${ZONE}" @10.53.0.3 A > dig.out.$DIR.test$n.a || log_error "dig a.${ZONE} A failed" + dig_with_opts "a.${ZONE}" @${SERVER} A > dig.out.$DIR.test$n.a || log_error "dig a.${ZONE} A failed" grep "status: NOERROR" dig.out.$DIR.test$n.a > /dev/null || log_error "mismatch status in DNS response" grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.11" dig.out.$DIR.test$n.a > /dev/null || log_error "missing a.${ZONE} A record in response" lines=$(get_keys_which_signed A dig.out.$DIR.test$n.a | wc -l) test "$lines" -eq 1 || log_error "bad number ($lines) of RRSIG records in DNS response" get_keys_which_signed A dig.out.$DIR.test$n.a | grep "^${KEY_ID}$" > /dev/null || log_error "A RRset not signed with key ${KEY_ID}" - dig_with_opts "d.${ZONE}" @10.53.0.3 A > dig.out.$DIR.test$n.d || log_error "dig d.${ZONE} A failed" + dig_with_opts "d.${ZONE}" @${SERVER} A > dig.out.$DIR.test$n.d || log_error "dig d.${ZONE} A failed" grep "status: NOERROR" dig.out.$DIR.test$n.d > /dev/null || log_error "mismatch status in DNS response" grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.4" dig.out.$DIR.test$n.d > /dev/null || log_error "missing d.${ZONE} A record in response" lines=$(get_keys_which_signed A dig.out.$DIR.test$n.d | wc -l) @@ -756,7 +773,7 @@ status=$((status+ret)) # # Zone: rsasha1.kasp. # -zone_properties "ns3" "rsasha1.kasp" "rsasha1" "1234" "3" +zone_properties "ns3" "rsasha1.kasp" "rsasha1" "1234" "3" "10.53.0.3" key_properties "KEY1" "ksk" "315360000" "5" "RSASHA1" "2048" "no" "yes" key_properties "KEY2" "zsk" "157680000" "5" "RSASHA1" "1024" "yes" "no" key_properties "KEY3" "zsk" "31536000" "5" "RSASHA1" "2000" "yes" "no" @@ -895,7 +912,7 @@ check_cds() { n=$((n+1)) echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)" ret=0 - dig_with_opts $ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + dig_with_opts $ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" if [ "${KEY1[$STATE_DS]}" == "rumoured" ] || [ "${KEY1[$STATE_DS]}" == "omnipresent" ]; then @@ -933,9 +950,33 @@ check_apex() { n=$((n+1)) echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)" ret=0 - dig_with_opts $ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + dig_with_opts $ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" - grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response" + + if [ "${KEY1[$STATE_DNSKEY]}" == "rumoured" ] || [ "${KEY1[$STATE_DNSKEY]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY1[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + numkeys=$((numkeys+1)) + elif [ "${KEY1[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY1[$ID]}" + fi + + if [ "${KEY2[$STATE_DNSKEY]}" == "rumoured" ] || [ "${KEY2[$STATE_DNSKEY]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY2[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + numkeys=$((numkeys+1)) + elif [ "${KEY2[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\.*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY2[$ID]}" + fi + + if [ "${KEY3[$STATE_DNSKEY]}" == "rumoured" ] || [ "${KEY3[$STATE_DNSKEY]}" == "omnipresent" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response for key ${KEY3[$ID]}" + check_signatures $_qtype dig.out.$DIR.test$n $KSK + numkeys=$((numkeys+1)) + elif [ "${KEY3[$EXPECT]}" == "yes" ]; then + grep "${ZONE}\..*${DNSKEY_TTL}.*IN.*${_qtype}.*257.*.3.*${_key_algnum}" dig.out.$DIR.test$n > /dev/null && log_error "unexpected ${_qtype} record in response for key ${KEY3[$ID]}" + fi + lines=$(get_keys_which_signed $_qtype dig.out.$DIR.test$n | wc -l) check_signatures $_qtype dig.out.$DIR.test$n $KSK test "$ret" -eq 0 || echo_i "failed" @@ -946,7 +987,7 @@ check_apex() { n=$((n+1)) echo_i "check ${_qtype} rrset is signed correctly for zone ${ZONE} ($n)" ret=0 - dig_with_opts $ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + dig_with_opts $ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*" dig.out.$DIR.test$n > /dev/null || log_error "missing ${_qtype} record in response" lines=$(get_keys_which_signed $_qtype dig.out.$DIR.test$n | wc -l) @@ -964,7 +1005,7 @@ check_subdomain() { n=$((n+1)) echo_i "check ${_qtype} a.${ZONE} rrset is signed correctly for zone ${ZONE} ($n)" ret=0 - dig_with_opts a.$ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig a.${ZONE} ${_qtype} failed" + dig_with_opts a.$ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig a.${ZONE} ${_qtype} failed" grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*10\.0\.0\.1" dig.out.$DIR.test$n > /dev/null || log_error "missing a.${ZONE} ${_qtype} record in response" lines=$(get_keys_which_signed $_qtype dig.out.$DIR.test$n | wc -l) @@ -978,10 +1019,43 @@ check_apex check_subdomain dnssec_verify +# +# Zone: unsigned.kasp. +# +zone_properties "ns3" "unsigned.kasp" "none" "0" "0" "10.53.0.3" +key_clear "KEY1" +key_clear "KEY2" +key_clear "KEY3" +check_keys +check_apex +check_subdomain + +# +# Zone: inherit.kasp. +# +zone_properties "ns3" "inherit.kasp" "rsasha1" "1234" "3" "10.53.0.3" +key_properties "KEY1" "ksk" "315360000" "5" "RSASHA1" "2048" "no" "yes" +key_properties "KEY2" "zsk" "157680000" "5" "RSASHA1" "1024" "yes" "no" +key_properties "KEY3" "zsk" "31536000" "5" "RSASHA1" "2000" "yes" "no" +# The first keys are immediately published and activated. +# Because lifetime > 0, retired timing is also set. +key_timings "KEY1" "published" "active" "retired" "none" "none" +key_timings "KEY2" "published" "active" "retired" "none" "none" +key_timings "KEY3" "published" "active" "retired" "none" "none" +# KSK: DNSKEY, RRSIG (ksk) published. DS needs to wait. +# ZSK: DNSKEY, RRSIG (zsk) published. +key_states "KEY1" "omnipresent" "rumoured" "none" "rumoured" "hidden" +key_states "KEY2" "omnipresent" "rumoured" "rumoured" "none" "none" +key_states "KEY3" "omnipresent" "rumoured" "rumoured" "none" "none" +check_keys +check_apex +check_subdomain +dnssec_verify + # # Zone: dnssec-keygen.kasp. # -zone_properties "ns3" "dnssec-keygen.kasp" "rsasha1" "1234" "3" +zone_properties "ns3" "dnssec-keygen.kasp" "rsasha1" "1234" "3" "10.53.0.3" # key_properties, key_timings and key_states same as above. check_keys check_apex @@ -991,7 +1065,7 @@ dnssec_verify # # Zone: some-keys.kasp. # -zone_properties "ns3" "some-keys.kasp" "rsasha1" "1234" "3" +zone_properties "ns3" "some-keys.kasp" "rsasha1" "1234" "3" "10.53.0.3" # key_properties, key_timings and key_states same as above. check_keys check_apex @@ -1001,7 +1075,7 @@ dnssec_verify # # Zone: legacy-keys.kasp. # -zone_properties "ns3" "legacy-keys.kasp" "rsasha1" "1234" "3" +zone_properties "ns3" "legacy-keys.kasp" "rsasha1" "1234" "3" "10.53.0.3" # key_properties, key_timings and key_states same as above. check_keys check_apex @@ -1013,7 +1087,7 @@ dnssec_verify # # There are more pregenerated keys than needed, hence the number of keys is # six, not three. -zone_properties "ns3" "pregenerated.kasp" "rsasha1" "1234" "6" +zone_properties "ns3" "pregenerated.kasp" "rsasha1" "1234" "6" "10.53.0.3" # key_properties, key_timings and key_states same as above. check_keys check_apex @@ -1023,7 +1097,7 @@ dnssec_verify # # Zone: secondary.kasp. # -zone_properties "ns3" "secondary.kasp" "rsasha1" "1234" "3" +zone_properties "ns3" "secondary.kasp" "rsasha1" "1234" "3" "10.53.0.3" # KSK properties, timings and states same as above. check_keys check_apex @@ -1042,12 +1116,12 @@ while [ $i -lt 5 ] do ret=0 - dig_with_opts "a.${ZONE}" @10.53.0.3 A > dig.out.$DIR.test$n.a || log_error "dig a.${ZONE} A failed" + dig_with_opts "a.${ZONE}" @${SERVER} A > dig.out.$DIR.test$n.a || log_error "dig a.${ZONE} A failed" grep "status: NOERROR" dig.out.$DIR.test$n.a > /dev/null || log_error "mismatch status in DNS response" grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.11" dig.out.$DIR.test$n.a > /dev/null || log_error "missing a.${ZONE} A record in response" check_signatures $_qtype dig.out.$DIR.test$n.a $ZSK - dig_with_opts "d.${ZONE}" @10.53.0.3 A > dig.out.$DIR.test$n.d || log_error "dig d.${ZONE} A failed" + dig_with_opts "d.${ZONE}" @${SERVER} A > dig.out.$DIR.test$n.d || log_error "dig d.${ZONE} A failed" grep "status: NOERROR" dig.out.$DIR.test$n.d > /dev/null || log_error "mismatch status in DNS response" grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.4" dig.out.$DIR.test$n.d > /dev/null || log_error "missing d.${ZONE} A record in response" lines=$(get_keys_which_signed A dig.out.$DIR.test$n.d | wc -l) @@ -1069,7 +1143,7 @@ status=$((status+ret)) # # Zone: rsasha1-nsec3.kasp. # -zone_properties "ns3" "rsasha1-nsec3.kasp" "rsasha1-nsec3" "1234" "3" +zone_properties "ns3" "rsasha1-nsec3.kasp" "rsasha1-nsec3" "1234" "3" "10.53.0.3" key_properties "KEY1" "ksk" "315360000" "7" "NSEC3RSASHA1" "2048" "no" "yes" key_properties "KEY2" "zsk" "157680000" "7" "NSEC3RSASHA1" "1024" "yes" "no" key_properties "KEY3" "zsk" "31536000" "7" "NSEC3RSASHA1" "2000" "yes" "no" @@ -1082,7 +1156,7 @@ dnssec_verify # # Zone: rsasha256.kasp. # -zone_properties "ns3" "rsasha256.kasp" "rsasha256" "1234" "3" +zone_properties "ns3" "rsasha256.kasp" "rsasha256" "1234" "3" "10.53.0.3" key_properties "KEY1" "ksk" "315360000" "8" "RSASHA256" "2048" "no" "yes" key_properties "KEY2" "zsk" "157680000" "8" "RSASHA256" "1024" "yes" "no" key_properties "KEY3" "zsk" "31536000" "8" "RSASHA256" "2000" "yes" "no" @@ -1095,7 +1169,7 @@ dnssec_verify # # Zone: rsasha512.kasp. # -zone_properties "ns3" "rsasha512.kasp" "rsasha512" "1234" "3" +zone_properties "ns3" "rsasha512.kasp" "rsasha512" "1234" "3" "10.53.0.3" key_properties "KEY1" "ksk" "315360000" "10" "RSASHA512" "2048" "no" "yes" key_properties "KEY2" "zsk" "157680000" "10" "RSASHA512" "1024" "yes" "no" key_properties "KEY3" "zsk" "31536000" "10" "RSASHA512" "2000" "yes" "no" @@ -1108,7 +1182,7 @@ dnssec_verify # # Zone: ecdsa256.kasp. # -zone_properties "ns3" "ecdsa256.kasp" "ecdsa256" "1234" "3" +zone_properties "ns3" "ecdsa256.kasp" "ecdsa256" "1234" "3" "10.53.0.3" key_properties "KEY1" "ksk" "315360000" "13" "ECDSAP256SHA256" "256" "no" "yes" key_properties "KEY2" "zsk" "157680000" "13" "ECDSAP256SHA256" "256" "yes" "no" key_properties "KEY3" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "yes" "no" @@ -1121,7 +1195,7 @@ dnssec_verify # # Zone: ecdsa512.kasp. # -zone_properties "ns3" "ecdsa384.kasp" "ecdsa384" "1234" "3" +zone_properties "ns3" "ecdsa384.kasp" "ecdsa384" "1234" "3" "10.53.0.3" key_properties "KEY1" "ksk" "315360000" "14" "ECDSAP384SHA384" "384" "no" "yes" key_properties "KEY2" "zsk" "157680000" "14" "ECDSAP384SHA384" "384" "yes" "no" key_properties "KEY3" "zsk" "31536000" "14" "ECDSAP384SHA384" "384" "yes" "no" @@ -1136,7 +1210,7 @@ dnssec_verify # # Zone: expired-sigs.autosign. # -zone_properties "ns3" "expired-sigs.autosign" "autosign" "300" "2" +zone_properties "ns3" "expired-sigs.autosign" "autosign" "300" "2" "10.53.0.3" # Both KSK and ZSK stay OMNIPRESENT. key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "no" "yes" key_timings "KEY1" "published" "active" "retired" "none" "none" @@ -1161,7 +1235,7 @@ check_rrsig_refresh() { n=$((n+1)) echo_i "check ${_qtype} rrsig is refreshed correctly for zone ${ZONE} ($n)" ret=0 - dig_with_opts $ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + dig_with_opts $ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" # If this exact RRSIG is also in the zone file it is not refreshed. @@ -1181,7 +1255,7 @@ check_rrsig_refresh() { n=$((n+1)) echo_i "check ${_label} ${_qtype} rrsig is refreshed correctly for zone ${ZONE} ($n)" ret=0 - dig_with_opts "${_label}.${ZONE}" @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${_label}.${ZONE} ${_qtype} failed" + dig_with_opts "${_label}.${ZONE}" @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${_label}.${ZONE} ${_qtype} failed" grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" _rrsig=`cat rrsig.out.$ZONE.$_qtype` @@ -1197,7 +1271,7 @@ check_rrsig_refresh # # Zone: fresh-sigs.autosign. # -zone_properties "ns3" "fresh-sigs.autosign" "autosign" "300" "2" +zone_properties "ns3" "fresh-sigs.autosign" "autosign" "300" "2" "10.53.0.3" # key_properties, key_timings and key_states same as above. check_keys check_apex @@ -1213,7 +1287,7 @@ check_rrsig_reuse() { n=$((n+1)) echo_i "check ${_qtype} rrsig is reused correctly for zone ${ZONE} ($n)" ret=0 - dig_with_opts $ZONE @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" + dig_with_opts $ZONE @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${ZONE} ${_qtype} failed" grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" # If this exact RRSIG is also in the zone file it is not refreshed. @@ -1233,7 +1307,7 @@ check_rrsig_reuse() { n=$((n+1)) echo_i "check ${_label} ${_qtype} rrsig is reused correctly for zone ${ZONE} ($n)" ret=0 - dig_with_opts "${_label}.${ZONE}" @10.53.0.3 $_qtype > dig.out.$DIR.test$n || log_error "dig ${_label}.${ZONE} ${_qtype} failed" + dig_with_opts "${_label}.${ZONE}" @${SERVER} $_qtype > dig.out.$DIR.test$n || log_error "dig ${_label}.${ZONE} ${_qtype} failed" grep "status: NOERROR" dig.out.$DIR.test$n > /dev/null || log_error "mismatch status in DNS response" grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" dig.out.$DIR.test$n > rrsig.out.$ZONE.$_qtype || log_error "missing RRSIG (${_qtype}) record in response" _rrsig=$(awk '{print $5, $6, $7, $8, $9, $10, $11, $12, $13, $14;}' < rrsig.out.$ZONE.$_qtype) @@ -1249,7 +1323,7 @@ check_rrsig_reuse # # Zone: unfresh-sigs.autosign. # -zone_properties "ns3" "unfresh-sigs.autosign" "autosign" "300" "2" +zone_properties "ns3" "unfresh-sigs.autosign" "autosign" "300" "2" "10.53.0.3" # key_properties, key_timings and key_states same as above. check_keys check_apex @@ -1260,7 +1334,7 @@ check_rrsig_refresh # # Zone: zsk-missing.autosign. # -zone_properties "ns3" "zsk-missing.autosign" "autosign" "300" "2" +zone_properties "ns3" "zsk-missing.autosign" "autosign" "300" "2" "10.53.0.3" # KSK stays OMNIPRESENT. key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "no" "yes" key_timings "KEY1" "published" "active" "retired" "none" "none" @@ -1271,7 +1345,7 @@ key_states "KEY1" "omnipresent" "omnipresent" "none" "omnipresent" "omnipresent" # # Zone: zsk-retired.autosign. # -zone_properties "ns3" "zsk-retired.autosign" "autosign" "300" "3" +zone_properties "ns3" "zsk-retired.autosign" "autosign" "300" "3" "10.53.0.3" # KSK properties, timings and states same as above. # The ZSK goal is set to HIDDEN but records stay OMNIPRESENT until the new ZSK # is active. @@ -1284,6 +1358,178 @@ key_properties "KEY3" "zsk" "31536000" "13" "ECDSAP256SHA256" "256" "no" "no" key_timings "KEY3" "published" "active" "retired" "none" "none" key_states "KEY3" "omnipresent" "rumoured" "hidden" "none" "none" +# +# Test dnssec-policy inheritance. +# + +# These zones should be unsigned: +# ns2/unsigned.tld +# ns4/none.inherit.signed +# ns4/none.override.signed +# ns4/inherit.none.signed +# ns4/none.none.signed +# ns5/inherit.inherit.unsigned +# ns5/none.inherit.unsigned +# ns5/none.override.unsigned +# ns5/inherit.none.unsigned +# ns5/none.none.unsigned +key_clear "KEY1" +key_clear "KEY2" +key_clear "KEY3" + +zone_properties "ns2" "unsigned.tld" "none" "0" "0" "10.53.0.2" +TSIG="" +check_keys +check_apex +check_subdomain + +zone_properties "ns4" "none.inherit.signed" "none" "0" "0" "10.53.0.4" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain + +zone_properties "ns4" "none.override.signed" "none" "0" "0" "10.53.0.4" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain + +zone_properties "ns4" "inherit.none.signed" "none" "0" "0" "10.53.0.4" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain + +zone_properties "ns4" "none.none.signed" "none" "0" "0" "10.53.0.4" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain + +zone_properties "ns5" "inherit.inherit.unsigned" "none" "0" "0" "10.53.0.5" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain + +zone_properties "ns5" "none.inherit.unsigned" "none" "0" "0" "10.53.0.5" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain + +zone_properties "ns5" "none.override.unsigned" "none" "0" "0" "10.53.0.5" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain + +zone_properties "ns5" "inherit.none.unsigned" "none" "0" "0" "10.53.0.5" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain + +zone_properties "ns5" "none.none.unsigned" "none" "0" "0" "10.53.0.5" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain + +# These zones should be signed with the default policy: +# ns2/signed.tld +# ns4/override.inherit.signed +# ns4/inherit.override.signed +# ns5/override.inherit.signed +# ns5/inherit.override.signed +key_properties "KEY1" "csk" "0" "13" "ECDSAP256SHA256" "256" "yes" "yes" +key_timings "KEY1" "published" "active" "none" "none" "none" "none" +key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" + +zone_properties "ns2" "signed.tld" "default" "3600" "1" "10.53.0.2" +TSIG="" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns4" "override.inherit.signed" "default" "3600" "1" "10.53.0.4" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns4" "inherit.override.signed" "default" "3600" "1" "10.53.0.4" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns5" "override.inherit.unsigned" "default" "3600" "1" "10.53.0.5" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns5" "inherit.override.unsigned" "default" "3600" "1" "10.53.0.5" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain +dnssec_verify + +# These zones should be signed with the test policy: +# ns4/inherit.inherit.signed +# ns4/override.override.signed +# ns4/override.none.signed +# ns5/override.override.unsigned +# ns5/override.none.unsigned +key_properties "KEY1" "csk" "0" "14" "ECDSAP384SHA384" "384" "yes" "yes" +key_timings "KEY1" "published" "active" "none" "none" "none" "none" +key_states "KEY1" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" + +zone_properties "ns4" "inherit.inherit.signed" "test" "3600" "1" "10.53.0.4" +TSIG="hmac-sha1:sha1:$SHA1" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns4" "override.override.signed" "test" "3600" "1" "10.53.0.4" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns4" "override.none.signed" "test" "3600" "1" "10.53.0.4" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns5" "override.override.unsigned" "test" "3600" "1" "10.53.0.5" +TSIG="hmac-sha224:sha224:$SHA224" +check_keys +check_apex +check_subdomain +dnssec_verify + +zone_properties "ns5" "override.none.unsigned" "test" "3600" "1" "10.53.0.5" +TSIG="hmac-sha256:sha256:$SHA256" +check_keys +check_apex +check_subdomain +dnssec_verify + +# Clear TSIG. +TSIG="" + # # Testing ZSK Pre-Publication rollover. # @@ -1291,7 +1537,7 @@ key_states "KEY3" "omnipresent" "rumoured" "hidden" "none" "none" # # Zone: step1.zsk-prepub.autosign. # -zone_properties "ns3" "step1.zsk-prepub.autosign" "zsk-prepub" "3600" "2" +zone_properties "ns3" "step1.zsk-prepub.autosign" "zsk-prepub" "3600" "2" "10.53.0.3" # Both KSK (KEY1) and ZSK (KEY2) start in OMNIPRESENT. key_properties "KEY1" "ksk" "63072000" "13" "ECDSAP256SHA256" "256" "no" "yes" key_timings "KEY1" "published" "active" "retired" "none" "none" @@ -1337,7 +1583,7 @@ check_next_key_event 2498400 # # Zone: step2.zsk-prepub.autosign. # -zone_properties "ns3" "step2.zsk-prepub.autosign" "zsk-prepub" "3600" "3" +zone_properties "ns3" "step2.zsk-prepub.autosign" "zsk-prepub" "3600" "3" "10.53.0.3" # KSK (KEY1) doesn't change. # ZSK (KEY2) remains active, no change in properties/timings/states. # New ZSK (KEY3) is prepublished. @@ -1357,7 +1603,7 @@ check_next_key_event 93600 # # Zone: step3.zsk-prepub.autosign. # -zone_properties "ns3" "step3.zsk-prepub.autosign" "zsk-prepub" "3600" "3" +zone_properties "ns3" "step3.zsk-prepub.autosign" "zsk-prepub" "3600" "3" "10.53.0.3" # KSK (KEY1) doesn't change. # ZSK (KEY2) properties and timing metadata same as above. # ZSK (KEY2) no longer is actively signing, RRSIG state in UNRETENTIVE. @@ -1385,7 +1631,7 @@ check_next_key_event 867600 # # Zone: step4.zsk-prepub.autosign. # -zone_properties "ns3" "step4.zsk-prepub.autosign" "zsk-prepub" "3600" "3" +zone_properties "ns3" "step4.zsk-prepub.autosign" "zsk-prepub" "3600" "3" "10.53.0.3" # KSK (KEY1) doesn't change. # ZSK (KEY2) properties and timing metadata same as above. # ZSK (KEY2) DNSKEY is no longer needed. @@ -1407,7 +1653,7 @@ check_next_key_event 7200 # # Zone: step5.zsk-prepub.autosign. # -zone_properties "ns3" "step5.zsk-prepub.autosign" "zsk-prepub" "3600" "3" +zone_properties "ns3" "step5.zsk-prepub.autosign" "zsk-prepub" "3600" "3" "10.53.0.3" # KSK (KEY1) doesn't change. # ZSK (KEY2) properties and timing metadata same as above. # ZSK (KEY3) DNSKEY is now completely HIDDEN and removed. @@ -1431,7 +1677,7 @@ check_next_key_event 1627200 # # Zone: step1.ksk-doubleksk.autosign. # -zone_properties "ns3" "step1.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "2" +zone_properties "ns3" "step1.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "2" "10.53.0.3" # Both KSK (KEY1) and ZSK (KEY2) start in OMNIPRESENT. key_properties "KEY1" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "no" "yes" key_timings "KEY1" "published" "active" "retired" "none" "none" @@ -1456,7 +1702,7 @@ check_next_key_event 5000400 # # Zone: step2.ksk-doubleksk.autosign. # -zone_properties "ns3" "step2.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" +zone_properties "ns3" "step2.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" "10.53.0.3" # ZSK (KEY2) doesn't change. # KSK (KEY1) remains active, no change in properties/timings/states. # New KSK (KEY3) is prepublished (and signs DNSKEY RRset). @@ -1476,7 +1722,7 @@ check_next_key_event 97200 # # Zone: step3.ksk-doubleksk.autosign. # -zone_properties "ns3" "step3.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" +zone_properties "ns3" "step3.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" "10.53.0.3" # ZSK (KEY2) doesn't change. # KSK (KEY1) DS will be removed, so it is UNRETENTIVE. key_states "KEY1" "hidden" "omnipresent" "none" "omnipresent" "unretentive" @@ -1499,7 +1745,7 @@ check_next_key_event 266400 # # Zone: step4.ksk-doubleksk.autosign. # -zone_properties "ns3" "step4.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" +zone_properties "ns3" "step4.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" "10.53.0.3" # ZSK (KEY2) doesn't change. # KSK (KEY1) DNSKEY can be removed. key_properties "KEY1" "ksk" "5184000" "13" "ECDSAP256SHA256" "256" "no" "no" @@ -1519,7 +1765,7 @@ check_next_key_event 10800 # # Zone: step5.ksk-doubleksk.autosign. # -zone_properties "ns3" "step5.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" +zone_properties "ns3" "step5.ksk-doubleksk.autosign" "ksk-doubleksk" "7200" "3" "10.53.0.3" # ZSK (KEY2) doesn't change. # KSK (KEY1) DNSKEY is now HIDDEN. key_states "KEY1" "hidden" "hidden" "none" "hidden" "hidden" @@ -1542,7 +1788,7 @@ check_next_key_event 4813200 # # Zone: step1.csk-roll.autosign. # -zone_properties "ns3" "step1.csk-roll.autosign" "csk-roll" "3600" "1" +zone_properties "ns3" "step1.csk-roll.autosign" "csk-roll" "3600" "1" "10.53.0.3" # The CSK (KEY1) starts in OMNIPRESENT. key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" key_timings "KEY1" "published" "active" "retired" "none" "none" @@ -1566,7 +1812,7 @@ check_next_key_event 15973200 # Zone: step2.csk-roll.autosign. # # Set key properties for testing keys. -zone_properties "ns3" "step2.csk-roll.autosign" "csk-roll" "3600" "2" +zone_properties "ns3" "step2.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" # CSK (KEY1) remains active, no change in properties/timings/states. # New CSK (KEY2) is prepublished (and signs DNSKEY RRset). key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" @@ -1586,7 +1832,7 @@ check_next_key_event 10800 # Zone: step3.csk-roll.autosign. # # Set key properties for testing keys. -zone_properties "ns3" "step3.csk-roll.autosign" "csk-roll" "3600" "2" +zone_properties "ns3" "step3.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" # CSK (KEY1) DS and ZRRSIG will be removed, so it is UNRETENTIVE. key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" key_states "KEY1" "hidden" "omnipresent" "unretentive" "omnipresent" "unretentive" @@ -1613,7 +1859,7 @@ check_next_key_event 100800 # # Zone: step4.csk-roll.autosign. # -zone_properties "ns3" "step4.csk-roll.autosign" "csk-roll" "3600" "2" +zone_properties "ns3" "step4.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" # The old CSK (KEY1) DS is hidden. We still need to keep the DNSKEY public # but can remove the KRRSIG records. key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" @@ -1634,7 +1880,7 @@ check_next_key_event 7200 # # Zone: step5.csk-roll.autosign. # -zone_properties "ns3" "step5.csk-roll.autosign" "csk-roll" "3600" "2" +zone_properties "ns3" "step5.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" # The old CSK (KEY1) KRRSIG records are now all hidden. key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" key_states "KEY1" "hidden" "omnipresent" "unretentive" "hidden" "hidden" @@ -1654,7 +1900,7 @@ check_next_key_event 2149200 # # Zone: step6.csk-roll.autosign. # -zone_properties "ns3" "step6.csk-roll.autosign" "csk-roll" "3600" "2" +zone_properties "ns3" "step6.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" # The old CSK (KEY1) DNSKEY can be removed. key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" key_states "KEY1" "hidden" "unretentive" "hidden" "hidden" "hidden" @@ -1674,7 +1920,7 @@ check_next_key_event 7200 # # Zone: step7.csk-roll.autosign. # -zone_properties "ns3" "step7.csk-roll.autosign" "csk-roll" "3600" "2" +zone_properties "ns3" "step7.csk-roll.autosign" "csk-roll" "3600" "2" "10.53.0.3" # The old CSK (KEY1) is now completely HIDDEN. key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" key_states "KEY1" "hidden" "hidden" "hidden" "hidden" "hidden" @@ -1699,7 +1945,7 @@ check_next_key_event 13708800 # # Zone: step1.csk-roll2.autosign. # -zone_properties "ns3" "step1.csk-roll2.autosign" "csk-roll2" "3600" "1" +zone_properties "ns3" "step1.csk-roll2.autosign" "csk-roll2" "3600" "1" "10.53.0.3" # The CSK (KEY1) starts in OMNIPRESENT. key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "yes" "yes" key_timings "KEY1" "published" "active" "retired" "none" "none" @@ -1723,7 +1969,7 @@ check_next_key_event 15454800 # Zone: step2.csk-roll2.autosign. # # Set key properties for testing keys. -zone_properties "ns3" "step2.csk-roll2.autosign" "csk-roll2" "3600" "2" +zone_properties "ns3" "step2.csk-roll2.autosign" "csk-roll2" "3600" "2" "10.53.0.3" # CSK (KEY1) remains active, no change in properties/timings/states. # New CSK (KEY2) is prepublished (and signs DNSKEY RRset). key_properties "KEY2" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" @@ -1743,7 +1989,7 @@ check_next_key_event 10800 # Zone: step3.csk-roll2.autosign. # # Set key properties for testing keys. -zone_properties "ns3" "step3.csk-roll2.autosign" "csk-roll2" "3600" "2" +zone_properties "ns3" "step3.csk-roll2.autosign" "csk-roll2" "3600" "2" "10.53.0.3" # CSK (KEY1) DS and ZRRSIG will be removed, so it is UNRETENTIVE. key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" key_states "KEY1" "hidden" "omnipresent" "unretentive" "omnipresent" "unretentive" @@ -1771,7 +2017,7 @@ check_next_key_event 136800 # # Zone: step4.csk-roll2.autosign. # -zone_properties "ns3" "step4.csk-roll2.autosign" "csk-roll2" "3600" "2" +zone_properties "ns3" "step4.csk-roll2.autosign" "csk-roll2" "3600" "2" "10.53.0.3" # The old CSK (KEY1) ZRRSIG is now HIDDEN. key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "yes" key_states "KEY1" "hidden" "omnipresent" "hidden" "omnipresent" "unretentive" @@ -1795,7 +2041,7 @@ check_next_key_event 478800 # # Zone: step5.csk-roll2.autosign. # -zone_properties "ns3" "step5.csk-roll2.autosign" "csk-roll2" "3600" "2" +zone_properties "ns3" "step5.csk-roll2.autosign" "csk-roll2" "3600" "2" "10.53.0.3" # The old CSK (KEY1) DNSKEY can be removed. key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" key_states "KEY1" "hidden" "unretentive" "hidden" "unretentive" "hidden" @@ -1815,7 +2061,7 @@ check_next_key_event 7200 # # Zone: step6.csk-roll2.autosign. # -zone_properties "ns3" "step6.csk-roll2.autosign" "csk-roll" "3600" "2" +zone_properties "ns3" "step6.csk-roll2.autosign" "csk-roll" "3600" "2" "10.53.0.3" # The old CSK (KEY1) is now completely HIDDEN. key_properties "KEY1" "csk" "16070400" "13" "ECDSAP256SHA256" "256" "no" "no" key_states "KEY1" "hidden" "hidden" "hidden" "hidden" "hidden" diff --git a/lib/bind9/check.c b/lib/bind9/check.c index 73fb59bf82..402a679053 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -842,6 +842,21 @@ check_name(const char *str) { return (dns_name_fromstring(dns_fixedname_name(&fixed), str, 0, NULL)); } +static bool +kasp_name_allowed(const cfg_listelt_t *element) +{ + const char* name = cfg_obj_asstring(cfg_tuple_get( + cfg_listelt_value(element), "name")); + + if (strcmp("none", name) == 0) { + return false; + } + if (strcmp("default", name) == 0) { + return false; + } + return true; +} + static isc_result_t check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, optlevel_t optlevel) @@ -950,14 +965,15 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, } /* - * Check dnssec-policy at the view/options level + * Check dnssec-policy. */ obj = NULL; (void)cfg_map_get(options, "dnssec-policy", &obj); if (obj != NULL) { - bool bad_kasp = true; - if (optlevel == optlevel_zone && cfg_obj_isstring(obj)) { - bad_kasp = false; + bool bad_kasp = false; + bool bad_name = false; + if (optlevel != optlevel_config && !cfg_obj_isstring(obj)) { + bad_kasp = true; } else if (optlevel == optlevel_config) { if (cfg_obj_islist(obj)) { for (element = cfg_list_first(obj); @@ -967,18 +983,29 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, if (!cfg_obj_istuple( cfg_listelt_value(element))) { - break; + bad_kasp = true; + } + if (!kasp_name_allowed(element)) { + bad_name = true; } } - bad_kasp = false; } } if (bad_kasp) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, - "dnssec-policy may only be activated at " - "the top level and referenced to at the " - "zone level"); + "dnssec-policy may only be configured at " + "the top level, please use name reference " + "at the zone level"); + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } + } + + if (bad_name) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-policy name may not be 'none' or " + "'default' (which is the built-in policy)"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } @@ -2135,6 +2162,8 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, if (strcmp(kaspname, "default") == 0) { has_dnssecpolicy = true; + } else if (strcmp(kaspname, "none") == 0) { + has_dnssecpolicy = false; } else { (void)cfg_map_get(config, "dnssec-policy", &kasps); for (element = cfg_list_first(kasps); element != NULL; @@ -2147,15 +2176,16 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, has_dnssecpolicy = true; } } - } - if (!has_dnssecpolicy) { - cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, - "zone '%s': option 'dnssec-policy %s' " - "has no matching dnssec-policy config", - znamestr, kaspname); - if (result == ISC_R_SUCCESS) { - result = ISC_R_FAILURE; + if (!has_dnssecpolicy) { + cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, + "zone '%s': option " + "'dnssec-policy %s' has no " + "matching dnssec-policy config", + znamestr, kaspname); + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } } } } diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index b39eb9c44c..75350ffa68 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -126,6 +126,8 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, isc_mem_t* mctx, cfg_obj_asstring(cfg_tuple_get(config, "name")) : "default"; + REQUIRE(strcmp(kaspname, "none") != 0); + result = dns_kasplist_find(kasplist, kaspname, &kasp); if (result == ISC_R_SUCCESS) { diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 746ee47a23..e0e5217d55 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2165,6 +2165,9 @@ zone_clauses[] = { { "dnssec-loadkeys-interval", &cfg_type_uint32, CFG_ZONE_MASTER | CFG_ZONE_SLAVE }, + { "dnssec-policy", &cfg_type_astring, + CFG_ZONE_MASTER | CFG_ZONE_SLAVE + }, { "dnssec-secure-to-insecure", &cfg_type_boolean, CFG_ZONE_MASTER }, @@ -2335,9 +2338,6 @@ zone_only_clauses[] = { { "dlz", &cfg_type_astring, CFG_ZONE_MASTER | CFG_ZONE_SLAVE | CFG_ZONE_REDIRECT }, - { "dnssec-policy", &cfg_type_astring, - CFG_ZONE_MASTER | CFG_ZONE_SLAVE - }, { "file", &cfg_type_qstring, CFG_ZONE_MASTER | CFG_ZONE_SLAVE | CFG_ZONE_MIRROR | CFG_ZONE_STUB | CFG_ZONE_HINT | CFG_ZONE_REDIRECT diff --git a/util/copyrights b/util/copyrights index 3608a3a60e..9d5aa28c46 100644 --- a/util/copyrights +++ b/util/copyrights @@ -698,6 +698,8 @@ ./bin/tests/system/kasp/clean.sh SH 2019 ./bin/tests/system/kasp/ns2/setup.sh SH 2019 ./bin/tests/system/kasp/ns3/setup.sh SH 2019 +./bin/tests/system/kasp/ns4/setup.sh SH 2019 +./bin/tests/system/kasp/ns5/setup.sh SH 2019 ./bin/tests/system/kasp/setup.sh SH 2019 ./bin/tests/system/kasp/tests.sh SH 2019 ./bin/tests/system/keepalive/clean.sh SH 2017,2018,2019 From bae0edbf02ef065daf19c2c8b8e096e1e33c33bc Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 5 Nov 2019 17:49:46 +0100 Subject: [PATCH 43/43] Fix checkconf test --- bin/tests/system/checkconf/good-kasp.conf | 3 --- 1 file changed, 3 deletions(-) diff --git a/bin/tests/system/checkconf/good-kasp.conf b/bin/tests/system/checkconf/good-kasp.conf index 04c1cef199..35abe1e6ca 100644 --- a/bin/tests/system/checkconf/good-kasp.conf +++ b/bin/tests/system/checkconf/good-kasp.conf @@ -35,9 +35,6 @@ dnssec-policy "test" { options { dnssec-policy "default"; }; -options { - dnssec-policy "default"; -}; zone "example1" { type master; file "example1.db";