--- /dev/null
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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 https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file dns/skr.h
+ * \brief
+ * A skr is a convenience type representing a Signed Key Response (SKR),
+ * determining which DNSKEY, CDS, CDNSKEY and corresponding signatures
+ * should be published at a given time. It is based on terminology used in
+ * https://www.iana.org/dnssec/archive/files/draft-icann-dnssec-keymgmt-01.txt
+ */
+
+#include <isc/stdtime.h>
+
+#include <dns/diff.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+#define DNS_SKR_MAGIC ISC_MAGIC('S', 'K', 'R', '-')
+#define DNS_SKR_VALID(t) ISC_MAGIC_VALID(t, DNS_SKR_MAGIC)
+
+#define DNS_SKRBUNDLE_MAGIC ISC_MAGIC('S', 'K', 'R', 'B')
+#define DNS_SKRBUNDLE_VALID(t) ISC_MAGIC_VALID(t, DNS_SKRBUNDLE_MAGIC)
+
+typedef struct dns_skrbundle dns_skrbundle_t;
+typedef ISC_LIST(dns_skrbundle_t) dns_skrbundlelist_t;
+
+/* Stores a Signed Key Response (SKR) */
+struct dns_skr {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ char *filename;
+ isc_time_t loadtime;
+ dns_skrbundlelist_t bundles;
+ isc_refcount_t references;
+};
+
+struct dns_skrbundle {
+ unsigned int magic;
+ isc_stdtime_t inception;
+ dns_diff_t diff;
+ ISC_LINK(dns_skrbundle_t) link;
+};
+
+void
+dns_skrbundle_create(isc_mem_t *mctx, dns_name_t *name,
+ dns_rdataclass_t rdclass, isc_stdtime_t inception,
+ dns_skrbundle_t **bp);
+/*%<
+ * Create a single bundle.
+ *
+ * Requires:
+ * \li *bp != NULL && *bp == NULL
+ */
+
+void
+dns_skrbundle_addtuple(dns_skrbundle_t *bundle, dns_difftuple_t **tuple);
+/*%<
+ * Add a single tuple to a key bundle.
+ *
+ * \li 'bundle' is a valid bundle
+ * \li '*tuple' is a valid tuple
+ */
+
+isc_result_t
+dns_skrbundle_getsig(dns_skrbundle_t *bundle, dst_key_t *key,
+ dns_rdatatype_t covering_type, dns_rdata_t *sigrdata);
+/*%<
+ * Retrieve the RRSIG rdata for 'covering_type' generated by 'key' from the
+ * given 'bundle'.
+ *
+ * Requires:
+ * \li 'bundle' is a valid bundle
+ *
+ * Returns:
+ * \li a possible error if we fail to convert the rdata to a struct
+ * \li ISC_R_SUCCESS if the signature is found
+ * \li ISC_R_NOTFOUND otherwise
+ */
+
+void
+dns_skr_create(isc_mem_t *mctx, const char *filename, dns_name_t *origin,
+ dns_rdataclass_t rdclass, dns_skr_t **skrp);
+/*%<
+ * Create a SKR.
+ *
+ * Requires:
+ * \li mctx != NULL
+ * \li *skrp != NULL && *skrp == NULL
+ */
+
+void
+dns_skr_addbundle(dns_skr_t *skr, dns_skrbundle_t **bundlep);
+/*%<
+ * Add a single bundle to a SKR.
+ *
+ * Requires:
+ * \li 'skr' is a valid SKR
+ * \li 'bundle' is a valid bundle
+ */
+
+dns_skrbundle_t *
+dns_skr_lookup(dns_skr_t *skr, isc_stdtime_t time, uint32_t sigval);
+/*%<
+ * Look up the currently active bundle. The active bundle is the one which
+ * inception time is prior to 'time' and the next bundle inception is after
+ " 'time'. In case of the last bundle in the SKR, 'time' is expected to be
+ * lower than the last bundle inception time plus 'sigval'.
+ *
+ * Requires:
+ * \li 'skr' is a valid SKR
+ *
+ * Returns:
+ * \li The currently active bundle, or NULL if no such bundle is found.
+ */
+
+void
+dns_skr_attach(dns_skr_t *source, dns_skr_t **targetp);
+/*%<
+ * Attach '*targetp' to 'source'.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid SKR.
+ *
+ *\li 'targetp' points to a NULL dns_skr_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ */
+
+void
+dns_skr_detach(dns_skr_t **skrp);
+/*%<
+ * Detach SKR.
+ *
+ * Requires:
+ *
+ *\li 'skrp' points to a valid dns_skr_t *
+ *
+ * Ensures:
+ *
+ *\li *skrp is NULL.
+ */
+
+void
+dns_skr_destroy(dns_skr_t *skr);
+/*%<
+ * Destroy a SKR.
+ *
+ * Requires:
+ * \li 'skr' is a valid SKR
+ */
+
+ISC_LANG_ENDDECLS
--- /dev/null
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * 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 https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <dns/skr.h>
+
+void
+dns_skrbundle_create(isc_mem_t *mctx, dns_name_t *name,
+ dns_rdataclass_t rdclass, isc_stdtime_t inception,
+ dns_skrbundle_t **bp) {
+ dns_skrbundle_t *b;
+
+ REQUIRE(bp != NULL && *bp == NULL);
+
+ UNUSED(name);
+ UNUSED(rdclass);
+
+ b = isc_mem_get(mctx, sizeof(*b));
+ b->magic = DNS_SKRBUNDLE_MAGIC;
+ b->inception = inception;
+ dns_diff_init(mctx, &b->diff);
+
+ ISC_LINK_INIT(b, link);
+
+ *bp = b;
+}
+
+void
+dns_skrbundle_addtuple(dns_skrbundle_t *bundle, dns_difftuple_t **tuple) {
+ REQUIRE(DNS_DIFFTUPLE_VALID(*tuple));
+ REQUIRE(DNS_SKRBUNDLE_VALID(bundle));
+ REQUIRE(DNS_DIFF_VALID(&bundle->diff));
+
+ dns_diff_append(&bundle->diff, tuple);
+}
+
+isc_result_t
+dns_skrbundle_getsig(dns_skrbundle_t *bundle, dst_key_t *key,
+ dns_rdatatype_t covering_type, dns_rdata_t *sigrdata) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(DNS_SKRBUNDLE_VALID(bundle));
+ REQUIRE(DNS_DIFF_VALID(&bundle->diff));
+
+ dns_difftuple_t *tuple = ISC_LIST_HEAD(bundle->diff.tuples);
+ while (tuple != NULL) {
+ dns_rdata_rrsig_t rrsig;
+
+ if (tuple->op != DNS_DIFFOP_ADDRESIGN) {
+ tuple = ISC_LIST_NEXT(tuple, link);
+ continue;
+ }
+ INSIST(tuple->rdata.type == dns_rdatatype_rrsig);
+
+ result = dns_rdata_tostruct(&tuple->rdata, &rrsig, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Check if covering type matches, and if the signature is
+ * generated by 'key'.
+ */
+ if (rrsig.covered == covering_type &&
+ rrsig.keyid == dst_key_id(key))
+ {
+ dns_rdata_clone(&tuple->rdata, sigrdata);
+ return (ISC_R_SUCCESS);
+ }
+
+ tuple = ISC_LIST_NEXT(tuple, link);
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+void
+dns_skr_create(isc_mem_t *mctx, const char *filename, dns_name_t *origin,
+ dns_rdataclass_t rdclass, dns_skr_t **skrp) {
+ isc_time_t now;
+ dns_skr_t *skr = NULL;
+
+ REQUIRE(skrp != NULL && *skrp == NULL);
+ REQUIRE(mctx != NULL);
+
+ UNUSED(origin);
+ UNUSED(rdclass);
+
+ now = isc_time_now();
+ skr = isc_mem_get(mctx, sizeof(*skr));
+ *skr = (dns_skr_t){
+ .magic = DNS_SKR_MAGIC,
+ .filename = isc_mem_strdup(mctx, filename),
+ .loadtime = now,
+ };
+ /*
+ * A list is not the best structure to store bundles that
+ * we need to look up, but we don't expect many bundles
+ * per SKR so it is acceptable for now.
+ */
+ ISC_LIST_INIT(skr->bundles);
+
+ isc_mem_attach(mctx, &skr->mctx);
+ isc_refcount_init(&skr->references, 1);
+ *skrp = skr;
+}
+
+void
+dns_skr_addbundle(dns_skr_t *skr, dns_skrbundle_t **bundlep) {
+ REQUIRE(DNS_SKR_VALID(skr));
+ REQUIRE(DNS_SKRBUNDLE_VALID(*bundlep));
+
+ ISC_LIST_APPEND(skr->bundles, *bundlep, link);
+ *bundlep = NULL;
+}
+
+dns_skrbundle_t *
+dns_skr_lookup(dns_skr_t *skr, isc_stdtime_t time, uint32_t sigval) {
+ dns_skrbundle_t *b, *next;
+
+ REQUIRE(DNS_SKR_VALID(skr));
+
+ for (b = ISC_LIST_HEAD(skr->bundles); b != NULL; b = next) {
+ next = ISC_LIST_NEXT(b, link);
+ if (next == NULL) {
+ isc_stdtime_t expired = b->inception + sigval;
+ if (b->inception <= time && time < expired) {
+ return (b);
+ }
+ return (NULL);
+ }
+ if (b->inception <= time && time < next->inception) {
+ return (b);
+ }
+ }
+
+ return (NULL);
+}
+
+void
+dns_skr_attach(dns_skr_t *source, dns_skr_t **targetp) {
+ REQUIRE(DNS_SKR_VALID(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+ *targetp = source;
+}
+
+void
+dns_skr_detach(dns_skr_t **skrp) {
+ REQUIRE(skrp != NULL && DNS_SKR_VALID(*skrp));
+
+ dns_skr_t *skr = *skrp;
+ *skrp = NULL;
+
+ if (isc_refcount_decrement(&skr->references) == 1) {
+ dns_skr_destroy(skr);
+ }
+}
+
+void
+dns_skr_destroy(dns_skr_t *skr) {
+ dns_skrbundle_t *b, *next;
+
+ REQUIRE(DNS_SKR_VALID(skr));
+
+ for (b = ISC_LIST_HEAD(skr->bundles); b != NULL; b = next) {
+ next = ISC_LIST_NEXT(b, link);
+ ISC_LIST_UNLINK(skr->bundles, b, link);
+ dns_diff_clear(&b->diff);
+ isc_mem_put(skr->mctx, b, sizeof(*b));
+ }
+ INSIST(ISC_LIST_EMPTY(skr->bundles));
+
+ isc_mem_free(skr->mctx, skr->filename);
+ isc_mem_putanddetach(&skr->mctx, skr, sizeof(*skr));
+}