]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
4127. [protocol] CDS and CDNSKEY need to be signed by the key signing
authorMark Andrews <marka@isc.org>
Wed, 27 May 2015 05:25:45 +0000 (15:25 +1000)
committerMark Andrews <marka@isc.org>
Wed, 27 May 2015 05:25:45 +0000 (15:25 +1000)
                        key as per RFC 7344, Section 4.1. [RT #37215]

21 files changed:
CHANGES
bin/dnssec/dnssec-dsfromkey.c
bin/dnssec/dnssec-dsfromkey.docbook
bin/dnssec/dnssec-signzone.c
bin/named/update.c
bin/tests/system/dnssec/clean.sh
bin/tests/system/dnssec/ns2/cdnskey-auto.secure.db.in [new file with mode: 0644]
bin/tests/system/dnssec/ns2/cdnskey-update.secure.db.in [new file with mode: 0644]
bin/tests/system/dnssec/ns2/cdnskey.secure.db.in [new file with mode: 0644]
bin/tests/system/dnssec/ns2/cds-auto.secure.db.in [new file with mode: 0644]
bin/tests/system/dnssec/ns2/cds-update.secure.db.in [new file with mode: 0644]
bin/tests/system/dnssec/ns2/cds.secure.db.in [new file with mode: 0644]
bin/tests/system/dnssec/ns2/named.conf
bin/tests/system/dnssec/ns2/sign.sh
bin/tests/system/dnssec/tests.sh
doc/design/cds-child [new file with mode: 0644]
lib/dns/include/dns/result.h
lib/dns/include/dns/zone.h
lib/dns/result.c
lib/dns/update.c
lib/dns/zone.c

diff --git a/CHANGES b/CHANGES
index a8df64d59e156bc8c9667ce1be76b6d7c14f8da1..6d84ca3328354d112086d8500515414f0bceb817 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,6 @@
+4127.  [protocol]      CDS and CDNSKEY need to be signed by the key signing
+                       key as per RFC 7344, Section 4.1. [RT #37215]
+
 4126.  [bug]           Addressed a regression introduced in change #4121.
                        [RT #39611]
 
index 2ec0f017f29a7fdaccfde2ba64cb6b294f7a4069..0a83d82b051db3e7da63c6dfc1a5d555e113c8fa 100644 (file)
@@ -242,7 +242,7 @@ logkey(dns_rdata_t *rdata)
 
 static void
 emit(unsigned int dtype, isc_boolean_t showall, char *lookaside,
-     dns_rdata_t *rdata)
+     isc_boolean_t cds, dns_rdata_t *rdata)
 {
        isc_result_t result;
        unsigned char buf[DNS_DS_BUFFERSIZE];
@@ -306,9 +306,12 @@ emit(unsigned int dtype, isc_boolean_t showall, char *lookaside,
        isc_buffer_usedregion(&classb, &r);
        printf("%.*s", (int)r.length, r.base);
 
-       if (lookaside == NULL)
-               printf(" DS ");
-       else
+       if (lookaside == NULL) {
+               if (cds)
+                       printf(" CDS ");
+               else
+                       printf(" DS ");
+       } else
                printf(" DLV ");
 
        isc_buffer_usedregion(&textb, &r);
@@ -336,6 +339,7 @@ usage(void) {
                        "(SHA-1, SHA-256, GOST or SHA-384)\n");
        fprintf(stderr, "    -1: use SHA-1\n");
        fprintf(stderr, "    -2: use SHA-256\n");
+       fprintf(stderr, "    -C: print CDS record\n");
        fprintf(stderr, "    -l: add lookaside zone and print DLV records\n");
        fprintf(stderr, "    -s: read keyset from keyset-<dnsname> file\n");
        fprintf(stderr, "    -c class: rdata class for DS set (default: IN)\n");
@@ -356,6 +360,7 @@ main(int argc, char **argv) {
        char            *endp;
        int             ch;
        unsigned int    dtype = DNS_DSDIGEST_SHA1;
+       isc_boolean_t   cds = ISC_FALSE;
        isc_boolean_t   both = ISC_TRUE;
        isc_boolean_t   usekeyset = ISC_FALSE;
        isc_boolean_t   showall = ISC_FALSE;
@@ -381,8 +386,8 @@ main(int argc, char **argv) {
 
        isc_commandline_errprint = ISC_FALSE;
 
-       while ((ch = isc_commandline_parse(argc, argv,
-                                          "12Aa:c:d:Ff:K:l:sT:v:hV")) != -1) {
+#define OPTIONS "12Aa:Cc:d:Ff:K:l:sT:v:hV"
+       while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) {
                switch (ch) {
                case '1':
                        dtype = DNS_DSDIGEST_SHA1;
@@ -399,6 +404,12 @@ main(int argc, char **argv) {
                        algname = isc_commandline_argument;
                        both = ISC_FALSE;
                        break;
+               case 'C':
+                       if (lookaside != NULL)
+                               fatal("lookaside and CDS are mutually"
+                                     " exclusive");
+                       cds = ISC_TRUE;
+                       break;
                case 'c':
                        classname = isc_commandline_argument;
                        break;
@@ -415,6 +426,9 @@ main(int argc, char **argv) {
                        filename = isc_commandline_argument;
                        break;
                case 'l':
+                       if (cds)
+                               fatal("lookaside and CDS are mutually"
+                                     " exclusive");
                        lookaside = isc_commandline_argument;
                        if (strlen(lookaside) == 0U)
                                fatal("lookaside must be a non-empty string");
@@ -533,11 +547,11 @@ main(int argc, char **argv) {
 
                        if (both) {
                                emit(DNS_DSDIGEST_SHA1, showall, lookaside,
-                                    &rdata);
+                                    cds, &rdata);
                                emit(DNS_DSDIGEST_SHA256, showall, lookaside,
-                                    &rdata);
+                                    cds, &rdata);
                        } else
-                               emit(dtype, showall, lookaside, &rdata);
+                               emit(dtype, showall, lookaside, cds, &rdata);
                }
        } else {
                unsigned char key_buf[DST_KEY_MAXSIZE];
@@ -546,10 +560,12 @@ main(int argc, char **argv) {
                        DST_KEY_MAXSIZE, &rdata);
 
                if (both) {
-                       emit(DNS_DSDIGEST_SHA1, showall, lookaside, &rdata);
-                       emit(DNS_DSDIGEST_SHA256, showall, lookaside, &rdata);
+                       emit(DNS_DSDIGEST_SHA1, showall, lookaside, cds,
+                            &rdata);
+                       emit(DNS_DSDIGEST_SHA256, showall, lookaside, cds,
+                            &rdata);
                } else
-                       emit(dtype, showall, lookaside, &rdata);
+                       emit(dtype, showall, lookaside, cds, &rdata);
        }
 
        if (dns_rdataset_isassociated(&rdataset))
index 08175baf26e5bd02a9e856a7a1ef3d04a7925aa7..905ff2158bd40ec9c15a1213cca36dc81f27a32b 100644 (file)
@@ -41,6 +41,7 @@
       <year>2011</year>
       <year>2012</year>
       <year>2014</year>
+      <year>2015</year>
       <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
     </copyright>
   </docinfo>
@@ -52,6 +53,7 @@
       <arg><option>-1</option></arg>
       <arg><option>-2</option></arg>
       <arg><option>-a <replaceable class="parameter">alg</replaceable></option></arg>
+      <arg><option>-C</option></arg>
       <arg><option>-l <replaceable class="parameter">domain</replaceable></option></arg>
       <arg><option>-T <replaceable class="parameter">TTL</replaceable></option></arg>
       <arg choice="req">keyfile</arg>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term>-C</term>
+        <listitem>
+          <para>
+            Generate CDS records rather than DS records.  This is mutually
+           exclusive with generating lookaside records.
+          </para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term>-T <replaceable class="parameter">TTL</replaceable></term>
         <listitem>
             <option>domain</option> is appended to the name for each
             record in the set.
             The DNSSEC Lookaside Validation (DLV) RR is described
-            in RFC 4431.
+            in RFC 4431.  This is mutually exclusive with generating
+           CDS records.
           </para>
         </listitem>
       </varlistentry>
index ae5c9234dad08210441380b82fdbe9f6dbe5b2ad..f60719c5d37ab9fd12b03900e6ceb1b424ca5a26 100644 (file)
@@ -688,7 +688,9 @@ signset(dns_diff_t *del, dns_diff_t *add, dns_dbnode_t *node, dns_name_t *name,
                            (iszsk(key) && !keyset_kskonly))
                                signwithkey(name, set, key->key, ttl, add,
                                            "signing with dnskey");
-               } else if (iszsk(key)) {
+               } else if (set->type == dns_rdatatype_cds ||
+                          set->type == dns_rdatatype_cdnskey ||
+                          iszsk(key)) {
                        signwithkey(name, set, key->key, ttl, add,
                                    "signing with dnskey");
                }
index 57fd6f35295866aa2c460ea11f17fdf9df467304..a0d7531cd1e1c5d2ab6e55107bbd5a52ec06f763 100644 (file)
@@ -3098,6 +3098,19 @@ update_action(isc_task_t *task, isc_event_t *event) {
                        goto failure;
                }
        }
+       if (! ISC_LIST_EMPTY(diff.tuples)) {
+               result = dns_zone_cdscheck(zone, db, ver);
+               if (result == DNS_R_BADCDS || result == DNS_R_BADCDNSKEY) {
+                       update_log(client, zone, LOGLEVEL_PROTOCOL,
+                                  "update rejected: bad %s RRset",
+                                  result == DNS_R_BADCDS ? "CDS" : "CDNSKEY");
+                       result = DNS_R_REFUSED;
+                       goto failure;
+               }
+               if (result != ISC_R_SUCCESS)
+                       goto failure;
+               
+       }
 
        /*
         * If any changes were made, increment the SOA serial number,
index c6d1262d12d0573b7af12b7ffcfb7c959e53e944..24e21cb195945117948921d85c68a0c06aa8c48c 100644 (file)
 # PERFORMANCE OF THIS SOFTWARE.
 
 rm -f */K* */keyset-* */dsset-* */dlvset-* */signedkey-* */*.signed
-rm -f */trusted.conf */managed.conf */revoked.conf
+rm -f */example.bk
+rm -f */named.memstats
+rm -f */named.run
+rm -f */named.secroots
 rm -f */tmp* */*.jnl */*.bk */*.jbk
+rm -f */trusted.conf */managed.conf */revoked.conf
+rm -f Kexample.*
+rm -f canonical?.*
+rm -f delv.out*
+rm -f delve.out*
+rm -f dig.out.*
+rm -f keygen.err
+rm -f named.secroots.test*
+rm -f nosign.before
+rm -f ns*/*.nta
+rm -f ns*/named.lock
+rm -f ns1/managed.key.id
 rm -f ns1/root.db ns2/example.db ns3/secure.example.db
-rm -f ns3/unsecure.example.db ns3/bogus.example.db ns3/keyless.example.db
-rm -f ns3/dynamic.example.db ns3/dynamic.example.db.signed.jnl
-rm -f ns3/rsasha256.example.db ns3/rsasha512.example.db
-rm -f ns3/split-dnssec.example.db
-rm -f ns3/expiring.example.db ns3/nosign.example.db
-rm -f ns2/private.secure.example.db
+rm -f ns2/algroll.db
 rm -f ns2/badparam.db ns2/badparam.db.bad
-rm -f ns2/single-nsec3.db
-rm -f ns2/nsec3chain-test.db
-rm -f ns2/in-addr.arpa.db
-rm -f */example.bk
-rm -f dig.out.*
-rm -f rndc.out.*
-rm -f delv.out*
+rm -f ns2/cdnskey-update.secure.db
+rm -f ns2/cdnskey.secure.db
+rm -f ns2/cds-auto.secure.db ns2/cds-auto.secure.db.jnl
+rm -f ns2/cds-update.secure.db ns2/cds-update.secure.db.jnl
+rm -f ns2/cds.secure.db
 rm -f ns2/dlv.db
-rm -f ns3/multiple.example.db ns3/nsec3-unknown.example.db ns3/nsec3.example.db
-rm -f ns3/optout-unknown.example.db ns3/optout.example.db
+rm -f ns2/in-addr.arpa.db
+rm -f ns2/nsec3chain-test.db
+rm -f ns2/private.secure.example.db
+rm -f ns2/single-nsec3.db
+rm -f ns3/auto-nsec.example.db ns3/auto-nsec3.example.db
+rm -f ns3/badds.example.db
+rm -f ns3/dnskey-nsec3-unknown.example.db
+rm -f ns3/dnskey-nsec3-unknown.example.db.tmp
+rm -f ns3/dnskey-unknown.example.db
+rm -f ns3/dnskey-unknown.example.db.tmp
+rm -f ns3/dynamic.example.db ns3/dynamic.example.db.signed.jnl
 rm -f ns3/expired.example.db ns3/update-nsec3.example.db
-rm -f ns7/multiple.example.bk ns7/nsec3.example.bk ns7/optout.example.bk
-rm -f */named.memstats
-rm -f */named.run
+rm -f ns3/expiring.example.db ns3/nosign.example.db
+rm -f ns3/future.example.db ns3/trusted-future.key
+rm -f ns3/inline.example.db.signed
+rm -f ns3/kskonly.example.db
+rm -f ns3/lower.example.db ns3/upper.example.db ns3/upper.example.db.lower
+rm -f ns3/multiple.example.db ns3/nsec3-unknown.example.db ns3/nsec3.example.db
 rm -f ns3/nsec3.nsec3.example.db
 rm -f ns3/nsec3.optout.example.db
+rm -f ns3/optout-unknown.example.db ns3/optout.example.db
 rm -f ns3/optout.nsec3.example.db
 rm -f ns3/optout.optout.example.db
+rm -f ns3/publish-inactive.example.db
+rm -f ns3/rsasha256.example.db ns3/rsasha512.example.db
+rm -f ns3/secure.below-cname.example.db
 rm -f ns3/secure.nsec3.example.db
 rm -f ns3/secure.optout.example.db
-rm -f */named.secroots
-rm -f ns1/managed.key.id
-rm -f signer/*.db
-rm -f signer/signer.out.*
-rm -f ns2/algroll.db
-rm -f ns3/kskonly.example.db
-rm -f ns4/named.conf ns5/named.conf
+rm -f ns3/siginterval.conf
+rm -f ns3/siginterval.example.db
+rm -f ns3/split-dnssec.example.db
+rm -f ns3/split-smart.example.db
+rm -f ns3/ttlpatch.example.db ns3/ttlpatch.example.db.signed
+rm -f ns3/ttlpatch.example.db.patched
+rm -f ns3/unsecure.example.db ns3/bogus.example.db ns3/keyless.example.db
 rm -f ns4/managed-keys.bind*
-rm -f ns3/auto-nsec.example.db ns3/auto-nsec3.example.db
-rm -f ns3/secure.below-cname.example.db
-rm -f ns3/publish-inactive.example.db
+rm -f ns4/named.conf
+rm -f ns4/named.conf ns5/named.conf
+rm -f ns4/named_dump.db
+rm -f ns6/optout-tld.db
+rm -f ns7/multiple.example.bk ns7/nsec3.example.bk ns7/optout.example.bk
+rm -f ns7/split-rrsig.db ns7/split-rrsig.db.unsplit
+rm -f nsupdate.out*
+rm -f rndc.out.*
+rm -f signer/*.db
 rm -f signer/example.db.after signer/example.db.before
 rm -f signer/example.db.changed
 rm -f signer/nsec3param.out
-rm -f ns3/ttlpatch.example.db ns3/ttlpatch.example.db.signed
-rm -f ns3/ttlpatch.example.db.patched
-rm -f ns3/split-smart.example.db
-rm -f ns3/siginterval.example.db
-rm -f ns3/inline.example.db.signed
-rm -f ns3/lower.example.db ns3/upper.example.db ns3/upper.example.db.lower
-rm -f ns6/optout-tld.db
-rm -f nosign.before
+rm -f signer/signer.out.*
 rm -f signing.out*
-rm -f canonical?.*
-rm -f ns3/siginterval.conf
-rm -f ns4/named_dump.db
-rm -f ns3/badds.example.db
-rm -f delve.out*
-rm -f ns7/split-rrsig.db ns7/split-rrsig.db.unsplit
-rm -f Kexample.*
-rm -f keygen.err
-rm -f ns3/future.example.db ns3/trusted-future.key
-rm -f ns3/dnskey-nsec3-unknown.example.db
-rm -f ns3/dnskey-nsec3-unknown.example.db.tmp
-rm -f ns3/dnskey-unknown.example.db
-rm -f ns3/dnskey-unknown.example.db.tmp
-rm -f ns*/named.lock
-rm -f ns*/*.nta
-rm -f named.secroots.test*
diff --git a/bin/tests/system/dnssec/ns2/cdnskey-auto.secure.db.in b/bin/tests/system/dnssec/ns2/cdnskey-auto.secure.db.in
new file mode 100644 (file)
index 0000000..69dd9de
--- /dev/null
@@ -0,0 +1,17 @@
+; Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+$TTL 3600
+@      SOA     ns2.example. . 1 3600 1200 86400 1200
+@      NS      ns2.example.
diff --git a/bin/tests/system/dnssec/ns2/cdnskey-update.secure.db.in b/bin/tests/system/dnssec/ns2/cdnskey-update.secure.db.in
new file mode 100644 (file)
index 0000000..69dd9de
--- /dev/null
@@ -0,0 +1,17 @@
+; Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+$TTL 3600
+@      SOA     ns2.example. . 1 3600 1200 86400 1200
+@      NS      ns2.example.
diff --git a/bin/tests/system/dnssec/ns2/cdnskey.secure.db.in b/bin/tests/system/dnssec/ns2/cdnskey.secure.db.in
new file mode 100644 (file)
index 0000000..69dd9de
--- /dev/null
@@ -0,0 +1,17 @@
+; Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+$TTL 3600
+@      SOA     ns2.example. . 1 3600 1200 86400 1200
+@      NS      ns2.example.
diff --git a/bin/tests/system/dnssec/ns2/cds-auto.secure.db.in b/bin/tests/system/dnssec/ns2/cds-auto.secure.db.in
new file mode 100644 (file)
index 0000000..69dd9de
--- /dev/null
@@ -0,0 +1,17 @@
+; Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+$TTL 3600
+@      SOA     ns2.example. . 1 3600 1200 86400 1200
+@      NS      ns2.example.
diff --git a/bin/tests/system/dnssec/ns2/cds-update.secure.db.in b/bin/tests/system/dnssec/ns2/cds-update.secure.db.in
new file mode 100644 (file)
index 0000000..69dd9de
--- /dev/null
@@ -0,0 +1,17 @@
+; Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+$TTL 3600
+@      SOA     ns2.example. . 1 3600 1200 86400 1200
+@      NS      ns2.example.
diff --git a/bin/tests/system/dnssec/ns2/cds.secure.db.in b/bin/tests/system/dnssec/ns2/cds.secure.db.in
new file mode 100644 (file)
index 0000000..69dd9de
--- /dev/null
@@ -0,0 +1,17 @@
+; Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+$TTL 3600
+@      SOA     ns2.example. . 1 3600 1200 86400 1200
+@      NS      ns2.example.
index baeba5997d831ff8ad9212932e7e52d149aa4d77..40fe25984af8bd40eb43c59fe4f04de4aa2d5c50 100644 (file)
@@ -107,4 +107,40 @@ zone "in-addr.arpa" {
        file "in-addr.arpa.db.signed";
 };
 
+zone "cds.secure" {
+       type master;
+       file "cds.secure.db.signed";
+};
+
+zone "cds-update.secure" {
+       type master;
+       file "cds-update.secure.db.signed";
+       allow-update { any; };
+};
+
+zone "cds-auto.secure" {
+       type master;
+       file "cds-auto.secure.db.signed";
+       auto-dnssec maintain;
+       allow-update { any; };
+};
+
+zone "cdnskey.secure" {
+       type master;
+       file "cdnskey.secure.db.signed";
+};
+
+zone "cdnskey-update.secure" {
+       type master;
+       file "cdnskey-update.secure.db.signed";
+       allow-update { any; };
+};
+
+zone "cdnskey-auto.secure" {
+       type master;
+       file "cdnskey-auto.secure.db.signed";
+       auto-dnssec maintain;
+       allow-update { any; };
+};
+
 include "trusted.conf";
index 8bf8921d5b696ced4258a891ebc2a91ffd14a694..43869e25adb2cdd744270b70bc596bb0f64555a5 100644 (file)
@@ -192,3 +192,53 @@ key1=`$KEYGEN -q -r $RANDFILE -a RSASHA256 -b 1024 -n zone -fk $zone`
 key2=`$KEYGEN -q -r $RANDFILE -a RSASHA256 -b 1024 -n zone $zone`
 cat $key1.key $key2.key >> $zonefile
 $SIGNER -P -3 - -A -H 1 -g -r $RANDFILE -o $zone -k $key1 $zonefile $key2 > /dev/null
+
+zone=cds.secure
+infile=cds.secure.db.in
+zonefile=cds.secure.db
+key1=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone -fk $zone`
+key2=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone $zone`
+$DSFROMKEY -C $key1.key > $key1.cds
+cat $infile $key1.key $key2.key $key1.cds >$zonefile
+$SIGNER -P -g -r $RANDFILE -o $zone $zonefile > /dev/null
+
+zone=cds-update.secure
+infile=cds-update.secure.db.in
+zonefile=cds-update.secure.db
+key1=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone -fk $zone`
+key2=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone $zone`
+cat $infile $key1.key $key2.key > $zonefile
+$SIGNER -P -g -r $RANDFILE -o $zone $zonefile > /dev/null
+
+zone=cds-auto.secure
+infile=cds-auto.secure.db.in
+zonefile=cds-auto.secure.db
+key1=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone -fk $zone`
+key2=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone $zone`
+$DSFROMKEY -C $key1.key > $key1.cds
+cat $infile $key1.cds > $zonefile.signed
+
+zone=cdnskey.secure
+infile=cdnskey.secure.db.in
+zonefile=cdnskey.secure.db
+key1=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone -fk $zone`
+key2=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone $zone`
+sed 's/DNSKEY/CDNSKEY/' $key1.key > $key1.cds
+cat $infile $key1.key $key2.key $key1.cds >$zonefile
+$SIGNER -P -g -r $RANDFILE -o $zone $zonefile > /dev/null
+
+zone=cdnskey-update.secure
+infile=cdnskey-update.secure.db.in
+zonefile=cdnskey-update.secure.db
+key1=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone -fk $zone`
+key2=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone $zone`
+cat $infile $key1.key $key2.key > $zonefile
+$SIGNER -P -g -r $RANDFILE -o $zone $zonefile > /dev/null
+
+zone=cdnskey-auto.secure
+infile=cdnskey-auto.secure.db.in
+zonefile=cdnskey-auto.secure.db
+key1=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone -fk $zone`
+key2=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone $zone`
+sed 's/DNSKEY/CDNSKEY/' $key1.key > $key1.cds
+cat $infile $key1.cds > $zonefile.signed
index 154dcfe6bcf96733f218faec399587d00ad3135b..624b7b94dc24944c0118ad57eb8486cb8617081b 100644 (file)
@@ -2950,6 +2950,15 @@ n=`expr $n + 1`
 if [ $ret != 0 ]; then echo "I:failed"; fi
 status=`expr $status + $ret`
 
+echo "I:check that CDS records are signed using KSK by dnssec-signzone ($n)"
+ret=0
+$DIG $DIGOPTS +noall +answer @10.53.0.2 cds cds.secure > dig.out.test$n
+lines=`awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 2 || ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
 #
 # Test for +sigchase with a null set of trusted keys.
 #
@@ -2985,6 +2994,57 @@ n=`expr $n + 1`
 if [ $ret != 0 ]; then echo "I:failed"; fi
 status=`expr $status + $ret`
 
+echo "I:check that CDS records are signed using KSK by with dnssec-auto ($n)"
+ret=0
+$DIG $DIGOPTS +noall +answer @10.53.0.2 cds cds-auto.secure > dig.out.test$n
+lines=`awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 2 || ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
+echo "I:check that a lone non matching CDS record is rejected ($n)"
+ret=0
+(
+echo zone cds-update.secure
+echo server 10.53.0.2 5300
+echo update delete cds-update.secure CDS
+$DIG $DIGOPTS +noall +answer @10.53.0.2 dnskey cds-update.secure |
+grep "DNSKEY.257" | sed 's/DNSKEY.257/DNSKEY 258/' |
+$DSFROMKEY -C -A -f - -T 1 cds-update.secure |
+sed "s/^/update add /"
+echo send
+) | $NSUPDATE > nsupdate.out.test$n 2>&1
+grep "update failed: REFUSED" nsupdate.out.test$n > /dev/null || ret=1
+$DIG $DIGOPTS +noall +answer @10.53.0.2 cds cds-update.secure > dig.out.test$n
+lines=`awk '$4 == "CDS" {print}' dig.out.test$n | wc -l`
+test ${lines:-10} -eq 0 || ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
+echo "I:check that CDS records are signed using KSK when added by nsupdate ($n)"
+ret=0
+(
+echo zone cds-update.secure
+echo server 10.53.0.2 5300
+echo update delete cds-update.secure CDS
+echo send
+$DIG $DIGOPTS +noall +answer @10.53.0.2 dnskey cds-update.secure |
+grep "DNSKEY.257" |
+$DSFROMKEY -C -f - -T 1 cds-update.secure |
+sed "s/^/update add /"
+echo send
+) | $NSUPDATE
+$DIG $DIGOPTS +noall +answer @10.53.0.2 cds cds-update.secure > dig.out.test$n
+lines=`awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 2 || ret=1
+lines=`awk '$4 == "CDS" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 2 || ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
 echo "I:checking that positive unknown NSEC3 hash algorithm with OPTOUT does validate ($n)"
 ret=0
 $DIG $DIGOPTS +noauth +noadd +nodnssec +adflag -p 5300 @10.53.0.3 optout-unknown.example SOA > dig.out.ns3.test$n
@@ -2997,6 +3057,32 @@ n=`expr $n + 1`
 if [ $ret != 0 ]; then echo "I:failed"; fi
 status=`expr $status + $ret`
 
+echo "I:check that a non matching CDS record is accepted with a matching CDS record ($n)"
+ret=0
+(
+echo zone cds-update.secure
+echo server 10.53.0.2 5300
+echo update delete cds-update.secure CDS
+echo send
+$DIG $DIGOPTS +noall +answer @10.53.0.2 dnskey cds-update.secure |
+grep "DNSKEY.257" |
+$DSFROMKEY -C -f - -T 1 cds-update.secure |
+sed "s/^/update add /"
+$DIG $DIGOPTS +noall +answer @10.53.0.2 dnskey cds-update.secure |
+grep "DNSKEY.257" | sed 's/DNSKEY.257/DNSKEY 258/' |
+$DSFROMKEY -C -A -f - -T 1 cds-update.secure |
+sed "s/^/update add /"
+echo send
+) | $NSUPDATE
+$DIG $DIGOPTS +noall +answer @10.53.0.2 cds cds-update.secure > dig.out.test$n
+lines=`awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 2 || ret=1
+lines=`awk '$4 == "CDS" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 4 || ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
 echo "I:checking that negative unknown NSEC3 hash algorithm does not validate ($n)"
 ret=0
 $DIG $DIGOPTS +noauth +noadd +nodnssec +adflag -p 5300 @10.53.0.3 nsec3-unknown.example A > dig.out.ns3.test$n
@@ -3007,6 +3093,15 @@ n=`expr $n + 1`
 if [ $ret != 0 ]; then echo "I:failed"; fi
 status=`expr $status + $ret`
 
+echo "I:check that CDNSKEY records are signed using KSK by dnssec-signzone ($n)"
+ret=0
+$DIG $DIGOPTS +noall +answer @10.53.0.2 cdnskey cdnskey.secure > dig.out.test$n
+lines=`awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 2 || ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
 echo "I:checking that negative unknown NSEC3 hash algorithm with OPTOUT does not validate ($n)"
 ret=0
 $DIG $DIGOPTS +noauth +noadd +nodnssec +adflag -p 5300 @10.53.0.3 optout-unknown.example A > dig.out.ns3.test$n
@@ -3017,6 +3112,15 @@ n=`expr $n + 1`
 if [ $ret != 0 ]; then echo "I:failed"; fi
 status=`expr $status + $ret`
 
+echo "I:check that CDNSKEY records are signed using KSK by with dnssec-auto ($n)"
+ret=0
+$DIG $DIGOPTS +noall +answer @10.53.0.2 cdnskey cdnskey-auto.secure > dig.out.test$n
+lines=`awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 2 || ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
 echo "I:checking that unknown DNSKEY algorithm validates as insecure ($n)"
 ret=0
 $DIG $DIGOPTS +noauth +noadd +nodnssec +adflag -p 5300 @10.53.0.3 dnskey-unknown.example A > dig.out.ns3.test$n
@@ -3028,6 +3132,25 @@ n=`expr $n + 1`
 if [ $ret != 0 ]; then echo "I:failed"; fi
 status=`expr $status + $ret`
 
+echo "I:check that a lone non matching CDNSKEY record is rejected ($n)"
+ret=0
+(
+echo zone cdnskey-update.secure
+echo server 10.53.0.2 5300
+echo update delete cdnskey-update.secure CDNSKEY
+echo send
+$DIG $DIGOPTS +noall +answer @10.53.0.2 dnskey cdnskey-update.secure |
+sed -n -e "s/^/update add /" -e 's/DNSKEY.257/CDNSKEY 258/p'
+echo send
+) | $NSUPDATE > nsupdate.out.test$n 2>&1
+grep "update failed: REFUSED" nsupdate.out.test$n > /dev/null || ret=1
+$DIG $DIGOPTS +noall +answer @10.53.0.2 cdnskey cdnskey-update.secure > dig.out.test$n
+lines=`awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l`
+test ${lines:-10} -eq 0 || ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
 echo "I:checking that unknown DNSKEY algorithm + unknown NSEC3 has algorithm validates as insecure ($n)"
 ret=0
 $DIG $DIGOPTS +noauth +noadd +nodnssec +adflag -p 5300 @10.53.0.3 dnskey-nsec3-unknown.example A > dig.out.ns3.test$n
@@ -3039,6 +3162,25 @@ n=`expr $n + 1`
 if [ $ret != 0 ]; then echo "I:failed"; fi
 status=`expr $status + $ret`
 
+echo "I:check that CDNSKEY records are signed using KSK when added by nsupdate ($n)"
+ret=0
+(
+echo zone cdnskey-update.secure
+echo server 10.53.0.2 5300
+echo update delete cdnskey-update.secure CDNSKEY
+$DIG $DIGOPTS +noall +answer @10.53.0.2 dnskey cdnskey-update.secure |
+sed -n -e "s/^/update add /" -e 's/DNSKEY.257/CDNSKEY 257/p'
+echo send
+) | $NSUPDATE
+$DIG $DIGOPTS +noall +answer @10.53.0.2 cdnskey cdnskey-update.secure > dig.out.test$n
+lines=`awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 2 || ret=1
+lines=`awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 1 || ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
 echo "I:checking initialization with a revoked managed key ($n)"
 ret=0
 cp ns5/named2.conf ns5/named.conf
@@ -3050,5 +3192,26 @@ n=`expr $n + 1`
 if [ $ret != 0 ]; then echo "I:failed"; fi
 status=`expr $status + $ret`
 
+echo "I:check that a non matching CDNSKEY record is accepted with a matching CDNSKEY record ($n)"
+ret=0
+(
+echo zone cdnskey-update.secure
+echo server 10.53.0.2 5300
+echo update delete cdnskey-update.secure CDNSKEY
+$DIG $DIGOPTS +noall +answer @10.53.0.2 dnskey cdnskey-update.secure |
+sed -n -e "s/^/update add /" -e 's/DNSKEY.257/CDNSKEY 257/p'
+$DIG $DIGOPTS +noall +answer @10.53.0.2 dnskey cdnskey-update.secure |
+sed -n -e "s/^/update add /" -e 's/DNSKEY.257/CDNSKEY 258/p'
+echo send
+) | $NSUPDATE
+$DIG $DIGOPTS +noall +answer @10.53.0.2 cdnskey cdnskey-update.secure > dig.out.test$n
+lines=`awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 2 || ret=1
+lines=`awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l`
+test ${lines:-0} -eq 2 || ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
 echo "I:exit status: $status"
 exit $status
diff --git a/doc/design/cds-child b/doc/design/cds-child
new file mode 100644 (file)
index 0000000..d95afbc
--- /dev/null
@@ -0,0 +1,51 @@
+
+         CDS / CDNSKEY Child side processing.
+
+* We need a mechanism to say that key should have a cds publish
+  start/end dates.
+
+* We need a mechanism to say that key should have a cdnskey publish
+  start/end dates
+
+  - update dnssec-settime, dnssec-keygen, dnssec-keyfromlabel
+  - update K* files
+
+* dnssec-signzone should add cds and/or cdnskey to zone apex iff the
+  DNSKEY is published and is signing the DNSKEY RRset.  CDS and CDNSKEY
+  records are only removed if there is a deletion date set (implicit on
+  matching DNSKEY going inactive / unpublished or explict).
+
+  Non-matching CDS and CDNSKEY are removed.
+
+* auto-dnssec maintain should cds and/or cdnskey to zone apex iff the 
+  DNSKEY is published and is signing the DNSKEY RRset.   CDS and CDNSKEY
+  records are only removed if there is a deletion date set (implicit on
+  matching DNSKEY going inactive / unpublished or explict).
+
+* UPDATE should check that CDS and CDNSKEY match a active DNSKEY that
+  is signing the DNSKEY RRset and ignore otherwise.  This should be
+  done after all the update section records have been processed.
+
+  ? how will this tie in with CDS/CDNSKEY sanity checks?  Only on fail?
+
+* UPDATE should remove CDS and CDNSKEY records that match a DNSKEY
+  that is being removed. This should be done after all the update
+  section records have been processed.
+
+  ? how will this tie in with CDS/CDNSKEY sanity checks?  Only on fail?
+
+* Zone loading should perform sanity checks on CDS and CDNSKEY
+  records against the DNSKEY records.  This will flow through into
+  dnssec-checkzone and "dnssec-checkconf -z".  ignore/warn/fail
+
+* rndc add the ability to say generate CDS / CDNSKEY along with a key list /
+  all / all SEP
+
+* rndc add the ability to say remove CDS / CDNSKEY.
+
+* inline zones need to check CDS and CDNSKEY records in the raw zone and
+  filter non matching.
+
+* CDS and CDNSKEY must be signed by a DNSKEY which matches parent DS record.
+  This is is different to how non DNSKEY RRsets are usually signed
+  RFC 7344, 4.1.
index 0511c637113e354af7fc9b871d509dafb6362f4e..d10c7f3c08902458b71ca56b94bcb1d5de4534d7 100644 (file)
 #define DNS_R_NOTDYNAMIC               (ISC_RESULTCLASS_DNS + 108)
 #define DNS_R_BADEUI                   (ISC_RESULTCLASS_DNS + 109)
 #define DNS_R_NTACOVERED               (ISC_RESULTCLASS_DNS + 110)
+#define DNS_R_BADCDS                   (ISC_RESULTCLASS_DNS + 111)
+#define DNS_R_BADCDNSKEY               (ISC_RESULTCLASS_DNS + 112)
 
-#define DNS_R_NRESULTS                 111     /*%< Number of results */
+#define DNS_R_NRESULTS                 113     /*%< Number of results */
 
 /*
  * DNS wire format rcodes.
index 058a47ddbe3a1912cc43f8eb722eba63c57d1e88..8bdcf6c19e31c0b80468943006be84bd37083daa 100644 (file)
@@ -2160,6 +2160,23 @@ dns_zone_nscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
  *     ISC_R_SUCCESS if there were no errors examining the zone contents.
  */
 
+isc_result_t
+dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version);
+/*%
+ * Check if CSD, CDNSKEY and DNSKEY are consistent.
+ *
+ * Requires:
+ * \li 'zone' to be valid.
+ * \li 'db' to be valid.
+ * \li 'version' to be valid or NULL.
+ *
+ * Returns:
+ *\li  #ISC_R_SUCCESS
+ *\li  #DNS_R_BADCDS
+ *\li  #DNS_R_BADCDNSKEY
+ *     Others
+ */
+
 void
 dns_zone_setadded(dns_zone_t *zone, isc_boolean_t added);
 /*%
index f88c4b38227c03d658813fe4dbb3b8c7fb22ba48..d4a87339a9c323f959343d375c3c3f2bf9bc6da6 100644 (file)
@@ -162,7 +162,9 @@ static const char *text[DNS_R_NRESULTS] = {
        "not dynamic",                         /*%< 108 DNS_R_NOTDYNAMIC */
        "bad EUI",                             /*%< 109 DNS_R_BADEUI */
 
-       "covered by negative trust anchor"     /*%< 110 DNS_R_NTACOVERED */
+       "covered by negative trust anchor",     /*%< 110 DNS_R_NTACOVERED */
+       "bad CDS",                             /*%< 111 DNS_R_BADCSD */
+       "bad CDNSKEY"                          /*%< 112 DNS_R_BADCDNSKEY */
 };
 
 static const char *ids[DNS_R_NRESULTS] = {
index 72397c1f64164f4b71d752f726720c448828af85..351fe4fdce234b25ffc89f7f950a594f1e629232 100644 (file)
@@ -1140,8 +1140,15 @@ add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db,
                        if (type == dns_rdatatype_dnskey) {
                                if (!KSK(keys[i]) && keyset_kskonly)
                                        continue;
-                       } else if (KSK(keys[i]))
-                               continue;
+                       } else if (KSK(keys[i])) {
+                               /*
+                                * CDS and CDNSKEY are signed with KSK
+                                * (RFC 7344, 4.1).
+                               */
+                               if (type != dns_rdatatype_cds &&
+                                   type != dns_rdatatype_cdnskey)
+                                       continue;
+                       }
                } else if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey)
                        continue;
 
index fb3616780f4ec4435dff6db350c9fd597763d53c..0dcc25b5f1304d8999fafe4cf97d004b1990cae8 100644 (file)
@@ -6579,8 +6579,14 @@ sign_a_node(dns_db_t *db, dns_name_t *name, dns_dbnode_t *node,
                if (rdataset.type == dns_rdatatype_dnskey) {
                        if (!is_ksk && keyset_kskonly)
                                goto next_rdataset;
-               } else if (is_ksk)
-                       goto next_rdataset;
+               } else if (is_ksk) {
+                       /*
+                        * CDS and CDNSKEY are signed with KSK (RFC 7344, 4.1).
+                        */
+                       if (rdataset.type != dns_rdatatype_cds &&
+                           rdataset.type != dns_rdatatype_cdnskey)
+                               goto next_rdataset;
+               }
                if (*delegation &&
                    rdataset.type != dns_rdatatype_ds &&
                    rdataset.type != dns_rdatatype_nsec)
@@ -17635,6 +17641,148 @@ dns_zone_nscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
        return (result);
 }
 
+isc_result_t
+dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) {
+       isc_result_t result;
+       dns_dbnode_t *node = NULL;
+       dns_rdataset_t dnskey, cds, cdnskey;
+       unsigned char buffer[DNS_DS_BUFFERSIZE];
+       unsigned char algorithms[256];
+       unsigned int i;
+
+       REQUIRE(DNS_ZONE_VALID(zone));
+
+       result = dns_db_getoriginnode(db, &node);
+       if (result != ISC_R_SUCCESS)
+               return (result);
+
+       dns_rdataset_init(&cds);
+       dns_rdataset_init(&dnskey);
+       dns_rdataset_init(&cdnskey);
+       
+       result = dns_db_findrdataset(db, node, version, dns_rdatatype_cds,
+                                    dns_rdatatype_none, 0, &cds, NULL);
+       if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND)
+               goto failure;
+
+       result = dns_db_findrdataset(db, node, version, dns_rdatatype_cdnskey,
+                                    dns_rdatatype_none, 0, &cdnskey, NULL);
+       if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND)
+               goto failure;
+
+       if (!dns_rdataset_isassociated(&cds) &&
+           !dns_rdataset_isassociated(&cdnskey)) {
+               result = ISC_R_SUCCESS;
+               goto failure;
+       }
+
+       result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey,
+                                    dns_rdatatype_none, 0, &dnskey, NULL);
+       if (result == ISC_R_NOTFOUND) {
+               if (dns_rdataset_isassociated(&cds))
+                       result = DNS_R_BADCDS;
+               else
+                       result = DNS_R_BADCDNSKEY;
+               goto failure;
+       }
+       if (result != ISC_R_SUCCESS)
+               goto failure;
+
+       /*
+        * For each DNSSEC algorithm in the CDS RRset there must be
+        * a matching DNSKEY record.
+        */
+       if (dns_rdataset_isassociated(&cds)) {
+               memset(algorithms, 0, sizeof(algorithms));
+               for (result = dns_rdataset_first(&cds);
+                    result == ISC_R_SUCCESS;
+                    result = dns_rdataset_next(&cds)) {
+                       dns_rdata_t crdata = DNS_RDATA_INIT;
+                       dns_rdata_cds_t structcds;
+
+                       dns_rdataset_current(&cds, &crdata);
+                       CHECK(dns_rdata_tostruct(&crdata, &structcds, NULL));
+                       if (algorithms[structcds.algorithm] == 0)
+                               algorithms[structcds.algorithm] = 1;
+                       for (result = dns_rdataset_first(&dnskey);
+                            result == ISC_R_SUCCESS;
+                            result = dns_rdataset_next(&dnskey)) {
+                               dns_rdata_t rdata = DNS_RDATA_INIT;
+                               dns_rdata_t dsrdata = DNS_RDATA_INIT;
+
+                               dns_rdataset_current(&dnskey, &rdata);
+                               CHECK(dns_ds_buildrdata(&zone->origin, &rdata,
+                                                       structcds.digest_type,
+                                                       buffer, &dsrdata));
+                               if (crdata.length == dsrdata.length &&
+                                   memcmp(crdata.data, dsrdata.data,
+                                          dsrdata.length) == 0) {
+                                       algorithms[structcds.algorithm] = 2;
+                               }
+                       }
+                       if (result != ISC_R_NOMORE)
+                               goto failure;
+               }
+               for (i = 0; i < sizeof(algorithms); i++) {
+                       if (algorithms[i] == 1) {
+                               result = DNS_R_BADCDNSKEY;
+                               goto failure;
+                       }
+               }
+       }
+
+       /*
+        * For each DNSSEC algorithm in the CDNSKEY RRset there must be
+        * a matching DNSKEY record.
+        */
+       if (dns_rdataset_isassociated(&cdnskey)) {
+               memset(algorithms, 0, sizeof(algorithms));
+               for (result = dns_rdataset_first(&cdnskey);
+                    result == ISC_R_SUCCESS;
+                    result = dns_rdataset_next(&cdnskey)) {
+                       dns_rdata_t crdata = DNS_RDATA_INIT;
+                       dns_rdata_cdnskey_t structcdnskey;
+
+                       dns_rdataset_current(&cdnskey, &crdata);
+                       CHECK(dns_rdata_tostruct(&crdata, &structcdnskey,
+                                                NULL));
+                       if (algorithms[structcdnskey.algorithm] == 0)
+                               algorithms[structcdnskey.algorithm] = 1;
+                       for (result = dns_rdataset_first(&dnskey);
+                            result == ISC_R_SUCCESS;
+                            result = dns_rdataset_next(&dnskey)) {
+                               dns_rdata_t rdata = DNS_RDATA_INIT;
+
+                               dns_rdataset_current(&dnskey, &rdata);
+                               if (crdata.length == rdata.length &&
+                                   memcmp(crdata.data, rdata.data,
+                                          rdata.length) == 0) {
+                                       algorithms[structcdnskey.algorithm] = 2;
+                               }
+                       }
+                       if (result != ISC_R_NOMORE)
+                               goto failure;
+               }
+               for (i = 0; i < sizeof(algorithms); i++) {
+                       if (algorithms[i] == 1) {
+                               result = DNS_R_BADCDS;
+                               goto failure;
+                       }
+               }
+       }
+       result = ISC_R_SUCCESS;
+       
+ failure:
+       if (dns_rdataset_isassociated(&cds))
+               dns_rdataset_disassociate(&cds);
+       if (dns_rdataset_isassociated(&dnskey))
+               dns_rdataset_disassociate(&dnskey);
+       if (dns_rdataset_isassociated(&cdnskey))
+               dns_rdataset_disassociate(&cdnskey);
+       dns_db_detachnode(db, &node);
+       return (result);
+}
+
 void
 dns_zone_setautomatic(dns_zone_t *zone, isc_boolean_t automatic) {
        REQUIRE(DNS_ZONE_VALID(zone));