]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
implemented zonemd calculation/verification
authorLibor Peltan <libor.peltan@nic.cz>
Tue, 23 Mar 2021 17:15:26 +0000 (18:15 +0100)
committerDaniel Salzman <daniel.salzman@nic.cz>
Thu, 3 Jun 2021 08:51:58 +0000 (10:51 +0200)
17 files changed:
Knot.files
Knot.includes
doc/doxygen/Doxy.page.h
src/knot/Makefile.inc
src/knot/zone/digest.c [new file with mode: 0644]
src/knot/zone/digest.h [new file with mode: 0644]
src/libdnssec/Makefile.inc
src/libdnssec/digest.c [new file with mode: 0644]
src/libdnssec/digest.h [new file with mode: 0644]
src/libdnssec/error.c
src/libdnssec/error.h
src/libknot/Makefile.inc
src/libknot/libknot.h.in
src/libknot/rrtype/zonemd.h [new file with mode: 0644]
tests/.gitignore
tests/Makefile.am
tests/knot/test_digest.c [new file with mode: 0644]

index 936ee05475ac56554f2b92e17f9c5f1f5de19511..ad3777df30b284cd00e3fa96a8cb89822161752f 100644 (file)
@@ -281,6 +281,8 @@ src/knot/zone/backup.c
 src/knot/zone/backup.h
 src/knot/zone/contents.c
 src/knot/zone/contents.h
+src/knot/zone/digest.c
+src/knot/zone/digest.h
 src/knot/zone/measure.c
 src/knot/zone/measure.h
 src/knot/zone/node.c
@@ -311,6 +313,8 @@ src/libdnssec/binary.c
 src/libdnssec/binary.h
 src/libdnssec/crypto.c
 src/libdnssec/crypto.h
+src/libdnssec/digest.c
+src/libdnssec/digest.h
 src/libdnssec/dnssec.h
 src/libdnssec/error.c
 src/libdnssec/error.h
@@ -415,6 +419,7 @@ src/libknot/rrtype/rrsig.h
 src/libknot/rrtype/soa.h
 src/libknot/rrtype/tsig.c
 src/libknot/rrtype/tsig.h
+src/libknot/rrtype/zonemd.h
 src/libknot/tsig-op.c
 src/libknot/tsig-op.h
 src/libknot/tsig.c
@@ -547,6 +552,7 @@ tests/knot/test_conf.h
 tests/knot/test_conf_tools.c
 tests/knot/test_confdb.c
 tests/knot/test_confio.c
+tests/knot/test_digest.c
 tests/knot/test_dthreads.c
 tests/knot/test_fdset.c
 tests/knot/test_journal.c
index 7b61db7ba4f8c3e48b5899be663b6c66e9d8abdb..c1a976e2eaba2298556212ef7da027eddf90f855 100644 (file)
@@ -6,3 +6,6 @@ src/libzscanner
 tests
 tests-fuzz
 tests-fuzz/knotd_wrap
+src/knot/zone
+src/libknot/rrtype
+tests/knot
index 32906b78bba61b8ccf34dd5c7c34a09af484fca9..e4d0242617d587f025c4ea2c5cd559b313f40a70 100644 (file)
@@ -52,6 +52,7 @@
 \section libdnssec-content Sections
  - \ref binary   — Universal binary data container
  - \ref crypto   — Cryptographic backend
+ - \ref digest   — Data hashing operations
  - \ref error    — Error codes and error reporting
  - \ref key      — DNSSEC key manipulation
  - \ref keyid    — DNSSEC key ID manipulation
@@ -65,6 +66,7 @@
 
 \defgroup binary   binary
 \defgroup crypto   crypto
+\defgroup digest   digest
 \defgroup error    error
 \defgroup key      key
 \defgroup keyid    keyid
index bf0a0855899724f7da213cb79c1a9b42cdb758f5..9f9400d117ef7cc72d087400eeac246496d5f989 100644 (file)
@@ -174,6 +174,8 @@ libknotd_la_SOURCES = \
        knot/zone/backup.h                      \
        knot/zone/contents.c                    \
        knot/zone/contents.h                    \
+       knot/zone/digest.c                      \
+       knot/zone/digest.h                      \
        knot/zone/measure.h                     \
        knot/zone/measure.c                     \
        knot/zone/node.c                        \
diff --git a/src/knot/zone/digest.c b/src/knot/zone/digest.c
new file mode 100644 (file)
index 0000000..536bc37
--- /dev/null
@@ -0,0 +1,202 @@
+/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "knot/zone/digest.h"
+
+#include <stdio.h>
+
+#include "libdnssec/digest.h"
+#include "libknot/error.h"
+#include "libknot/packet/rrset-wire.h"
+#include "libknot/rrtype/rrsig.h"
+#include "libknot/rrtype/zonemd.h"
+#include "knot/dnssec/rrset-sign.h" // only knot_synth_rrsig()
+
+#define DIGEST_BUF_MIN 4096
+#define DIGEST_BUF_MAX (40 * 1024 * 1024)
+
+typedef struct {
+       size_t buf_size;
+       uint8_t *buf;
+       struct dnssec_digest_ctx *digest_ctx;
+       const zone_node_t *apex;
+} contents_digest_ctx_t;
+
+static int digest_rrset(knot_rrset_t *rrset, const zone_node_t *node, void *vctx)
+{
+       contents_digest_ctx_t *ctx = vctx;
+
+       // ignore apex ZONEMD
+       if (node == ctx->apex && rrset->type == KNOT_RRTYPE_ZONEMD) {
+               return KNOT_EOK;
+       }
+
+       // ignore RRSIGs of apex ZONEMD
+       if (node == ctx->apex && rrset->type == KNOT_RRTYPE_RRSIG) {
+               knot_rdataset_t cpy = rrset->rrs, zonemd_rrsig = { 0 };
+               int ret = knot_rdataset_copy(&rrset->rrs, &cpy, NULL);
+               if (ret != KNOT_EOK) {
+                       return ret;
+               }
+
+               ret = knot_synth_rrsig(KNOT_RRTYPE_ZONEMD, &rrset->rrs, &zonemd_rrsig, NULL);
+               if (ret == KNOT_EOK) {
+                       ret = knot_rdataset_subtract(&rrset->rrs, &zonemd_rrsig, NULL);
+                       knot_rdataset_clear(&zonemd_rrsig, NULL);
+               }
+               if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
+                       knot_rdataset_clear(&rrset->rrs, NULL);
+                       return ret;
+               }
+       }
+
+       // serialize RRSet, expand buf as needed
+       int ret = knot_rrset_to_wire_extra(rrset, ctx->buf, ctx->buf_size, 0,
+                                          NULL, KNOT_PF_ORIGTTL);
+       while (ret == KNOT_ESPACE && ctx->buf_size < DIGEST_BUF_MAX) {
+               free(ctx->buf);
+               ctx->buf_size *= 2;
+               ctx->buf = malloc(ctx->buf_size);
+               if (ctx->buf == NULL) {
+                       return KNOT_ENOMEM;
+               }
+               ret = knot_rrset_to_wire_extra(rrset, ctx->buf, ctx->buf_size, 0,
+                                              NULL, KNOT_PF_ORIGTTL);
+       }
+
+       // cleanup apex RRSIGs mess
+       if (node == ctx->apex && rrset->type == KNOT_RRTYPE_RRSIG) {
+               knot_rdataset_clear(&rrset->rrs, NULL);
+       }
+
+       if (ret < 0) {
+               return ret;
+       }
+
+       // digest serialized RRSet
+       dnssec_binary_t bufbin = { ret, ctx->buf };
+       return dnssec_digest(ctx->digest_ctx, &bufbin);
+}
+
+static int digest_node(zone_node_t *node, void *ctx)
+{
+       int i = 0, ret = KNOT_EOK;
+       for ( ; i < node->rrset_count && ret == KNOT_EOK; i++) {
+               knot_rrset_t rrset = node_rrset_at(node, i);
+               ret = digest_rrset(&rrset, node, ctx);
+       }
+       return ret;
+}
+
+int zone_contents_digest(const zone_contents_t *contents, int algorithm, uint8_t **out_digest, size_t *out_size)
+{
+       if (contents == NULL || out_digest == NULL || out_size == NULL) {
+               return KNOT_EINVAL;
+       }
+
+       contents_digest_ctx_t ctx = {
+               .buf_size = DIGEST_BUF_MIN,
+               .buf = malloc(DIGEST_BUF_MIN),
+               .apex = contents->apex,
+       };
+       if (ctx.buf == NULL) {
+               return KNOT_ENOMEM;
+       }
+
+       int ret = dnssec_digest_init(algorithm, &ctx.digest_ctx);
+       if (ret != DNSSEC_EOK) {
+               free(ctx.buf);
+               return knot_error_from_libdnssec(ret);
+       }
+
+       ret = zone_contents_apply((zone_contents_t *)contents, digest_node, &ctx);
+       if (ret == KNOT_EOK) {
+               ret = zone_contents_nsec3_apply((zone_contents_t *)contents, digest_node, &ctx);
+       }
+
+       dnssec_binary_t res = { 0 };
+       if (ret == KNOT_EOK) {
+               ret = dnssec_digest_finish(ctx.digest_ctx, &res);
+       }
+       free(ctx.buf);
+       *out_digest = res.data;
+       *out_size = res.size;
+       return ret;
+}
+
+static int verify_zonemd(const knot_rdata_t *zonemd, const zone_contents_t *contents)
+{
+       uint8_t *computed = NULL;
+       size_t comp_size = 0;
+       int ret = zone_contents_digest(contents, knot_zonemd_algorithm(zonemd),
+                                      &computed, &comp_size);
+       if (ret != KNOT_EOK) {
+               return ret;
+       }
+
+       if (comp_size != knot_zonemd_digest_size(zonemd)) {
+               ret = KNOT_EFEWDATA;
+       } else if (memcmp(knot_zonemd_digest(zonemd), computed, comp_size) != 0) {
+               ret = KNOT_EMALF;
+       }
+       free(computed);
+       return ret;
+}
+
+static bool check_duplicate_schalg(const knot_rdataset_t *zonemd, int check_upto,
+                                   uint8_t scheme, uint8_t alg)
+{
+       knot_rdata_t *check = zonemd->rdata;
+       assert(check_upto <= zonemd->count);
+       for (int i = 0; i < check_upto; i++) {
+               if (knot_zonemd_scheme(check) == scheme &&
+                   knot_zonemd_algorithm(check) == alg) {
+                       return false;
+               }
+               check = knot_rdataset_next(check);
+       }
+       return true;
+}
+
+int zone_contents_digest_verify(const zone_contents_t *contents)
+{
+       if (contents == NULL) {
+               return KNOT_EINVAL;
+       }
+
+       knot_rdataset_t *zonemd = node_rdataset(contents->apex, KNOT_RRTYPE_ZONEMD);
+       if (zonemd == NULL) {
+               return KNOT_ENOENT;
+       }
+
+       uint32_t soa_serial = zone_contents_serial(contents);
+
+       knot_rdata_t *rr = zonemd->rdata, *supported = NULL;
+       for (int i = 0; i < zonemd->count; i++) {
+               if (knot_zonemd_scheme(rr) == KNOT_ZONEMD_SCHEME_SIMPLE &&
+                   knot_zonemd_digest_size(rr) > 0 &&
+                   knot_zonemd_soa_serial(rr) == soa_serial) {
+                       supported = rr;
+               }
+               if (!check_duplicate_schalg(zonemd, i, knot_zonemd_scheme(rr),
+                                           knot_zonemd_algorithm(rr))) {
+                       return KNOT_ESEMCHECK;
+               }
+               rr = knot_rdataset_next(rr);
+       }
+
+       return supported == NULL ? KNOT_ENOTSUP : verify_zonemd(supported, contents);
+}
diff --git a/src/knot/zone/digest.h b/src/knot/zone/digest.h
new file mode 100644 (file)
index 0000000..f72342d
--- /dev/null
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "knot/zone/contents.h"
+
+/*!
+ * \brief Compute hash over whole zone by concatenating RRSets in wire format.
+ *
+ * \param contents     Zone contents to digest.
+ * \param algorithm    Algorithm to use.
+ * \param out_digest   Output: buffer with computed hash (to be freed).
+ * \param out_size     Output: size of the resulting hash.
+ *
+ * \return KNOT_E*
+ */
+int zone_contents_digest(const zone_contents_t *contents, int algorithm, uint8_t **out_digest, size_t *out_size);
+
+/*!
+ * \brief Verify zone dgest in ZONEMD record.
+ *
+ * \param contents   Zone contents ot be verified.
+ *
+ * \retval KNOT_ENOENT      There is no ZONEMD in contents' apex.
+ * \retval KNOT_ENOTSUP     None of present ZONEMD is supported (scheme+algrithm+SOAserial).
+ * \retval KNOT_ESEMCHECK   Duplicate ZONEMD with identical scheme+algorithm pair.
+ * \retval KNOT_EFEWDATA    Error in hash length.
+ * \retval KNOT_EMALF       The computed hash differs from ZONEMD.
+ * \return KNOT_E*
+ */
+int zone_contents_digest_verify(const zone_contents_t *contents);
index 4cc25fc7e60cf2cc3d09eb0c1ad95e115da91768..981d84102df1dd04cec790c4f10c46d38347df17 100644 (file)
@@ -12,6 +12,7 @@ include_libdnssecdir = $(includedir)/libdnssec
 include_libdnssec_HEADERS = \
        libdnssec/binary.h                      \
        libdnssec/crypto.h                      \
+       libdnssec/digest.h                      \
        libdnssec/dnssec.h                      \
        libdnssec/error.h                       \
        libdnssec/key.h                         \
@@ -28,6 +29,7 @@ include_libdnssec_HEADERS = \
 libdnssec_la_SOURCES = \
        libdnssec/binary.c                      \
        libdnssec/crypto.c                      \
+       libdnssec/digest.c                      \
        libdnssec/error.c                       \
        libdnssec/key/algorithm.c               \
        libdnssec/key/algorithm.h               \
diff --git a/src/libdnssec/digest.c b/src/libdnssec/digest.c
new file mode 100644 (file)
index 0000000..83a4fcb
--- /dev/null
@@ -0,0 +1,105 @@
+/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "libdnssec/digest.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#include "libdnssec/shared/shared.h"
+
+struct dnssec_digest_ctx {
+       gnutls_hash_hd_t gtctx;
+       unsigned size;
+};
+
+static gnutls_digest_algorithm_t lookup_algorithm(dnssec_digest_t algorithm)
+{
+       switch (algorithm) {
+       case DNSSEC_DIGEST_SHA384: return GNUTLS_DIG_SHA384;
+       case DNSSEC_DIGEST_SHA512: return GNUTLS_DIG_SHA512;
+       default:
+               return GNUTLS_DIG_UNKNOWN;
+       };
+}
+
+_public_
+int dnssec_digest_init(dnssec_digest_t algorithm, dnssec_digest_ctx_t **out_ctx)
+{
+       if (out_ctx == NULL) {
+               return DNSSEC_EINVAL;
+       }
+
+       gnutls_digest_algorithm_t gtalg = lookup_algorithm(algorithm);
+       if (gtalg == GNUTLS_DIG_UNKNOWN) {
+               return DNSSEC_INVALID_DIGEST_ALGORITHM;
+       }
+
+       dnssec_digest_ctx_t *res = malloc(sizeof(*res));
+       if (res == NULL) {
+               return DNSSEC_ENOMEM;
+       }
+
+       res->size = gnutls_hash_get_len(gtalg);
+       if (res->size == 0 || gnutls_hash_init(&res->gtctx, gtalg) < 0) {
+               free(res);
+               return DNSSEC_DIGEST_ERROR;
+       }
+
+       *out_ctx = res;
+       return DNSSEC_EOK;
+}
+
+static void digest_ctx_free(dnssec_digest_ctx_t *ctx)
+{
+       free_gnutls_hash_ptr(&ctx->gtctx);
+       free(ctx);
+}
+
+_public_
+int dnssec_digest(dnssec_digest_ctx_t *ctx, dnssec_binary_t *data)
+{
+       if (ctx == NULL || data == NULL) {
+               return DNSSEC_EINVAL;
+       }
+
+       int r = gnutls_hash(ctx->gtctx, data->data, data->size);
+       if (r != 0) {
+               digest_ctx_free(ctx);
+               return DNSSEC_DIGEST_ERROR;
+       }
+       return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_digest_finish(dnssec_digest_ctx_t *ctx, dnssec_binary_t *out)
+{
+       if (ctx == NULL || out == NULL) {
+               return DNSSEC_EINVAL;
+       }
+
+       int r = dnssec_binary_resize(out, ctx->size);
+       if (r < 0) {
+               dnssec_binary_free(out);
+               digest_ctx_free(ctx);
+               return r;
+       }
+
+       gnutls_hash_output(ctx->gtctx, out->data);
+
+       digest_ctx_free(ctx);
+       return DNSSEC_EOK;
+}
diff --git a/src/libdnssec/digest.h b/src/libdnssec/digest.h
new file mode 100644 (file)
index 0000000..76709aa
--- /dev/null
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \addtogroup digest
+ *
+ * \brief Data hashing operations.
+ *
+ * @{
+ */
+
+#pragma once
+
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+
+typedef enum {
+       DNSSEC_DIGEST_INVALID = 0,
+       DNSSEC_DIGEST_SHA384  = 1,
+       DNSSEC_DIGEST_SHA512  = 2,
+} dnssec_digest_t;
+
+struct dnssec_digest_ctx;
+typedef struct dnssec_digest_ctx dnssec_digest_ctx_t;
+
+/*!
+ * \brief Initialize digest context.
+ *
+ * \param algorithm   Hasing algorithm to be used.
+ * \param out_ctx     Output: context structure to be initialized.
+ *
+ * \return DNSSEC_E*
+ */
+int dnssec_digest_init(dnssec_digest_t algorithm, dnssec_digest_ctx_t **out_ctx);
+
+/*!
+ * \brief Digest data.
+ *
+ * \param ctx    Digest context.
+ * \param data   Data to be hashed.
+ *
+ * \note This function may be invoked repeatedly for single digest context,
+ *       hashing all data as concatenated.
+ *
+ * \return DNSSEC_E*
+ *
+ * \note If error is returned, the digest context is automatically disposed.
+ */
+int dnssec_digest(dnssec_digest_ctx_t *ctx, dnssec_binary_t *data);
+
+/*!
+ * \brief Finalize digest, dispose digest context and return the hash.
+ *
+ * \param ctx   Digest context.
+ * \param out   Output: computed hash.
+ *
+ * \return DNSSEC_E*
+ */
+int dnssec_digest_finish(dnssec_digest_ctx_t *ctx, dnssec_binary_t *out);
+
+/*! @} */
index a0f5e05f4ed05514bb7961f0eb9720ecd807ffb6..d4f9a81e7fc86f0c09674bd957ce9dbf0f9c40b7 100644 (file)
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -68,6 +68,9 @@ static const error_message_t ERROR_MESSAGES[] = {
        { DNSSEC_P11_TOO_MANY_MODULES,      "too many PKCS #11 modules loaded" },
        { DNSSEC_P11_TOKEN_NOT_AVAILABLE,   "PKCS #11 token not available" },
 
+       { DNSSEC_INVALID_DIGEST_ALGORITHM,  "invalid digest algorithm" },
+       { DNSSEC_DIGEST_ERROR,              "digest error" },
+
        { 0 }
 };
 
index f998fcba8cc8e056e2e5db80733d7735791aea80..aee87cb1e13acdfbbf112bf31d8892957570b908 100644 (file)
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -80,6 +80,9 @@ enum dnssec_error {
        DNSSEC_P11_TOO_MANY_MODULES,
        DNSSEC_P11_TOKEN_NOT_AVAILABLE,
 
+       DNSSEC_INVALID_DIGEST_ALGORITHM,
+       DNSSEC_DIGEST_ERROR,
+
        DNSSEC_ERROR_MAX = -1001
 };
 
index 07b60691dffaa33330a5bfe9dd80f08989903a4d..0c5f1eff3b5941270a0b232299bafc97ce817f47 100644 (file)
@@ -44,6 +44,7 @@ nobase_include_libknot_HEADERS = \
        libknot/rrtype/rrsig.h                  \
        libknot/rrtype/soa.h                    \
        libknot/rrtype/tsig.h                   \
+       libknot/rrtype/zonemd.h                 \
        libknot/tsig-op.h                       \
        libknot/tsig.h                          \
        libknot/wire.h                          \
index 3e0c92fb3c94e45d32882c6028d94bbd6521d5d7..85ec7cdf48072b61ae2fd27482ac2ec56c0d242b 100644 (file)
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -63,6 +63,7 @@
 #include "libknot/rrtype/rrsig.h"
 #include "libknot/rrtype/soa.h"
 #include "libknot/rrtype/tsig.h"
+#include "libknot/rrtype/zonemd.h"
 #include "libknot/wire.h"
 #if @XDP_VISIBLE_HEADERS@
 #include "libknot/xdp/xdp.h"
diff --git a/src/libknot/rrtype/zonemd.h b/src/libknot/rrtype/zonemd.h
new file mode 100644 (file)
index 0000000..ba1c838
--- /dev/null
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \addtogroup rrtype
+ * @{
+ */
+
+#pragma once
+
+#include "libknot/rdata.h"
+#include "libknot/wire.h"
+
+#define KNOT_ZONEMD_SCHEME_SIMPLE      1
+#define KNOT_ZONEMD_ALORITHM_SHA384    1
+#define KNOT_ZONEMD_ALORITHM_SHA512    2
+
+static inline
+uint32_t knot_zonemd_soa_serial(const knot_rdata_t *rdata)
+{
+       assert(rdata);
+       return knot_wire_read_u32(rdata->data);
+}
+
+static inline
+uint8_t knot_zonemd_scheme(const knot_rdata_t *rdata)
+{
+       assert(rdata);
+       return *(rdata->data + 4);
+}
+
+static inline
+uint8_t knot_zonemd_algorithm(const knot_rdata_t *rdata)
+{
+       assert(rdata);
+       return *(rdata->data + 5);
+}
+
+static inline
+size_t knot_zonemd_digest_size(const knot_rdata_t *rdata)
+{
+       switch (knot_zonemd_algorithm(rdata)) {
+       case KNOT_ZONEMD_ALORITHM_SHA384: return 48;
+       case KNOT_ZONEMD_ALORITHM_SHA512: return 64;
+       default: return 0;
+       }
+}
+
+static inline
+const uint8_t *knot_zonemd_digest(const knot_rdata_t *rdata)
+{
+       assert(rdata);
+       return rdata->data + 6;
+}
+
+/*! @} */
index eed2ccbe1ab6505e826d5b7545afb5f235e07ab4..3718cbfced92818475d5dd8ef16272ea206fe231 100644 (file)
@@ -23,6 +23,7 @@
 /knot/test_conf_tools
 /knot/test_confdb
 /knot/test_confio
+/knot/test_digest
 /knot/test_dthreads
 /knot/test_fdset
 /knot/test_journal
index 0c9cf152b6576f3b5e364ff4be67aa70cbfd6fa2..93c97e5ea4e32d75c7db3762c055ad15d7b666e5 100644 (file)
@@ -91,6 +91,7 @@ check_PROGRAMS += \
        knot/test_conf_tools                    \
        knot/test_confdb                        \
        knot/test_confio                        \
+       knot/test_digest                        \
        knot/test_dthreads                      \
        knot/test_fdset                         \
        knot/test_journal                       \
diff --git a/tests/knot/test_digest.c b/tests/knot/test_digest.c
new file mode 100644 (file)
index 0000000..03c453e
--- /dev/null
@@ -0,0 +1,317 @@
+/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "knot/zone/digest.h"
+
+#include <string.h>
+#include <tap/basic.h>
+
+#include "knot/zone/zonefile.h"
+#include "libzscanner/scanner.h"
+
+// copy-pasted from knot/zone/zonefile.c
+static void process_data(zs_scanner_t *scanner)
+{
+       zcreator_t *zc = scanner->process.data;
+       if (zc->ret != KNOT_EOK) {
+               scanner->state = ZS_STATE_STOP;
+               return;
+       }
+
+       knot_dname_t *owner = knot_dname_copy(scanner->r_owner, NULL);
+       if (owner == NULL) {
+               zc->ret = KNOT_ENOMEM;
+               return;
+       }
+
+       knot_rrset_t rr;
+       knot_rrset_init(&rr, owner, scanner->r_type, scanner->r_class, scanner->r_ttl);
+
+       int ret = knot_rrset_add_rdata(&rr, scanner->r_data, scanner->r_data_length, NULL);
+       if (ret != KNOT_EOK) {
+               knot_rrset_clear(&rr, NULL);
+               zc->ret = ret;
+               return;
+       }
+
+       ret = knot_rrset_rr_to_canonical(&rr);
+       if (ret != KNOT_EOK) {
+               knot_rrset_clear(&rr, NULL);
+               zc->ret = ret;
+               return;
+       }
+
+       zc->ret = zcreator_step(zc, &rr);
+       knot_rrset_clear(&rr, NULL);
+}
+
+static void process_error(zs_scanner_t *s)
+{
+       (void)s;
+       assert(0);
+}
+
+static zone_contents_t *str2contents(const char *zone_str)
+{
+       char origin_str[KNOT_DNAME_TXT_MAXLEN];
+       sscanf(zone_str, "%s", origin_str); // NOTE assuming that first token in zone_str is origin name!
+
+       knot_dname_t *origin = knot_dname_from_str_alloc(origin_str);
+       assert(origin != NULL);
+
+       zone_contents_t *cont = zone_contents_new(origin, false);
+       assert(cont != NULL);
+       knot_dname_free(origin, NULL);
+
+       zcreator_t zc = { cont, true, KNOT_EOK };
+
+       zs_scanner_t sc;
+       int ret = zs_init(&sc, origin_str, KNOT_CLASS_IN, 3600);
+       assert(ret == 0);
+
+       ret = zs_set_input_string(&sc, zone_str, strlen(zone_str));
+       assert(ret == 0);
+
+       ret = zs_set_processing(&sc, process_data, process_error, &zc);
+       assert(ret == 0);
+
+       ret = zs_parse_all(&sc);
+       assert(ret == 0);
+
+       zs_deinit(&sc);
+
+       return cont;
+}
+
+static int check_contents(const char *zone_str)
+{
+       zone_contents_t *cont = str2contents(zone_str);
+       int ret = zone_contents_digest_verify(cont);
+       zone_contents_deep_free(cont);
+       return ret;
+}
+
+const char *simple_zone = "\
+example.      86400  IN  SOA     ns1 admin 2018031900 (  \n\
+                                 1800 900 604800 86400 ) \n\
+              86400  IN  NS      ns1                     \n\
+              86400  IN  NS      ns2                     \n\
+              86400  IN  ZONEMD  2018031900 1 1 (        \n\
+                                 c68090d90a7aed71        \n\
+                                 6bc459f9340e3d7c        \n\
+                                 1370d4d24b7e2fc3        \n\
+                                 a1ddc0b9a87153b9        \n\
+                                 a9713b3c9ae5cc27        \n\
+                                 777f98b8e730044c )      \n\
+ns1           3600   IN  A       203.0.113.63            \n\
+ns2           3600   IN  AAAA    2001:db8::63";
+
+const char *complex_zone = "\
+example.      86400  IN  SOA     ns1 admin 2018031900 (                 \n\
+                                 1800 900 604800 86400 )                \n\
+              86400  IN  NS      ns1                                    \n\
+              86400  IN  NS      ns2                                    \n\
+              86400  IN  ZONEMD  2018031900 1 1 (                       \n\
+                                 a3b69bad980a3504                       \n\
+                                 e1cffcb0fd6397f9                       \n\
+                                 3848071c93151f55                       \n\
+                                 2ae2f6b1711d4bd2                       \n\
+                                 d8b39808226d7b9d                       \n\
+                                 b71e34b72077f8fe )                     \n\
+ns1           3600   IN  A       203.0.113.63                           \n\
+NS2           3600   IN  AAAA    2001:db8::63                           \n\
+occluded.sub  7200   IN  TXT     \"I'm occluded but must be digested\"  \n\
+sub           7200   IN  NS      ns1                                    \n\
+duplicate     300    IN  TXT     \"I must be digested just once\"       \n\
+duplicate     300    IN  TXT     \"I must be digested just once\"       \n\
+foo.test.     555    IN  TXT     \"out-of-zone data must be excluded\"  \n\
+UPPERCASE     3600   IN  TXT     \"canonicalize uppercase owner names\" \n\
+*             777    IN  PTR     dont-forget-about-wildcards            \n\
+mail          3600   IN  MX      20 MAIL1                               \n\
+mail          3600   IN  MX      10 Mail2.Example.                      \n\
+sortme        3600   IN  AAAA    2001:db8::5:61                         \n\
+sortme        3600   IN  AAAA    2001:db8::3:62                         \n\
+sortme        3600   IN  AAAA    2001:db8::4:63                         \n\
+sortme        3600   IN  AAAA    2001:db8::1:65                         \n\
+sortme        3600   IN  AAAA    2001:db8::2:64                         \n\
+non-apex      900    IN  ZONEMD  2018031900 1 1 (                       \n\
+                                 616c6c6f77656420                       \n\
+                                 6275742069676e6f                       \n\
+                                 7265642e20616c6c                       \n\
+                                 6f77656420627574                       \n\
+                                 2069676e6f726564                       \n\
+                                 2e20616c6c6f7765 )";
+
+const char *multiple_digests = "\
+example.      86400  IN  SOA     ns1 admin 2018031900 (                \n\
+                                 1800 900 604800 86400 )               \n\
+example.      86400  IN  NS      ns1.example.                          \n\
+example.      86400  IN  NS      ns2.example.                          \n\
+example.      86400  IN  ZONEMD  2018031900 1 1 (                      \n\
+                                 62e6cf51b02e54b9                      \n\
+                                 b5f967d547ce4313                      \n\
+                                 6792901f9f88e637                      \n\
+                                 493daaf401c92c27                      \n\
+                                 9dd10f0edb1c56f8                      \n\
+                                 080211f8480ee306 )                    \n\
+example.      86400  IN  ZONEMD  2018031900 1 2 (                      \n\
+                                 08cfa1115c7b948c                      \n\
+                                 4163a901270395ea                      \n\
+                                 226a930cd2cbcf2f                      \n\
+                                 a9a5e6eb85f37c8a                      \n\
+                                 4e114d884e66f176                      \n\
+                                 eab121cb02db7d65                      \n\
+                                 2e0cc4827e7a3204                      \n\
+                                 f166b47e5613fd27 )                    \n\
+example.      86400  IN  ZONEMD  2018031900 1 240 (                    \n\
+                                 e2d523f654b9422a                      \n\
+                                 96c5a8f44607bbee )                    \n\
+example.      86400  IN  ZONEMD  2018031900 241 1 (                    \n\
+                                 e1846540e33a9e41                      \n\
+                                 89792d18d5d131f6                      \n\
+                                 05fc283e )                            \n\
+ns1.example.  3600   IN  A       203.0.113.63                          \n\
+ns2.example.  86400  IN  TXT     \"This example has multiple digests\" \n\
+NS2.EXAMPLE.  3600   IN  AAAA    2001:db8::63";
+
+const char *signed_zone = "\
+uri.arpa.      3600 IN SOA     sns.dns.icann.org. noc.dns.icann.org. 2018100702 10800 3600 1209600 3600 \n\
+uri.arpa.      3600 IN RRSIG   SOA 8 2 3600 20210217232440 20210120232440 37444 uri.arpa. GzQw+QzwLDJr13REPGVmpEChjD1D2XlX0ie1DnWHpgaEw1E/dhs3lCN3 +BmHd4Kx3tffTRgiyq65HxR6feQ5v7VmAifjyXUYB1DZur1eP5q0Ms2y gCB3byoeMgCNsFS1oKZ2LdzNBRpy3oace8xQn1SpmHGfyrsgg+WbHKCT 1dY= \n\
+uri.arpa.      86400 IN NS     a.iana-servers.net. \n\
+uri.arpa.      86400 IN NS     b.iana-servers.net. \n\
+uri.arpa.      86400 IN NS     c.iana-servers.net. \n\
+uri.arpa.      86400 IN NS     ns2.lacnic.net. \n\
+uri.arpa.      86400 IN NS     sec3.apnic.net. \n\
+uri.arpa.      86400 IN RRSIG  NS 8 2 86400 20210217232440 20210120232440 37444 uri.arpa. M+Iei2lcewWGaMtkPlrhM9FpUAHXFkCHTVpeyrjxjEONeNgKtHZor5e4 V4qJBOzNqo8go/qJpWlFBm+T5Hn3asaBZVstFIYky38/C8UeRLPKq1hT THARYUlFrexr5fMtSUAVOgOQPSBfH3xBq/BgSccTdRb9clD+HE7djpqr LS4= \n\
+uri.arpa.      600 IN MX       10 pechora.icann.org. \n\
+uri.arpa.      600 IN RRSIG    MX 8 2 600 20210217232440 20210120232440 37444 uri.arpa. kQAJQivmv6A5hqYBK8h6Z13ESY69gmosXwKI6WE09I8RFetfrxr24ecd nYd0lpnDtgNNSoHkYRSOoB+C4+zuJsoyAAzGo9uoWMWj97/2xeGhf3PT C9meQ9Ohi6hul9By7OR76XYmGhdWX8PBi60RUmZ1guslFBfQ8izwPqzu phs= \n\
+uri.arpa.      3600 IN NSEC    ftp.uri.arpa. NS SOA MX RRSIG NSEC DNSKEY ZONEMD \n\
+uri.arpa.      3600 IN RRSIG   NSEC 8 2 3600 20210217232440 20210120232440 37444 uri.arpa. dU/rXLM/naWd1+1PiWiYVaNJyCkiuyZJSccr91pJI673T8r3685B4ODM YFafZRboVgwnl3ZrXddY6xOhZL3n9V9nxXZwjLJ2HJUojFoKcXTlpnUy YUYvVQ2kj4GHAo6fcGCEp5QFJ2KbCpeJoS+PhKGRRx28icCiNT4/uXQv O2E= \n\
+uri.arpa.      3600 IN DNSKEY  256 3 8 AwEAAbMxuFuLeVDuOwIMzYOTD/bTREjLflo7wOi6ieIJhqltEzgjNzmW Jf9kGwwDmzxU7kbthMEhBNBZNn84zmcyRSCMzuStWveL7xmqqUlE3swL 8kLOvdZvc75XnmpHrk3ndTyEb6eZM7slh2C63Oh6K8VR5VkiZAkEGg0u ZIT3NjsF \n\
+uri.arpa.      3600 IN DNSKEY  257 3 8 AwEAAdkTaWkZtZuRh7/OobBUFxM+ytTst+bCu0r9w+rEwXD7GbDs0pIM hMenrZzoAvmv1fQxw2MGs6Ri6yPKfNULcFOSt9l8i6BVBLI+SKTY6XXe DUQpSEmSaxohHeRPMQFzpysfjxINp/L2rGtZ7yPmxY/XRiFPSO0myqwG Ja9r06Zw9CHM5UDHKWV/E+zxPFq/I7CfPbrrzbUotBX7Z6Vh3Sarllbe 8cGUB2UFNaTRgwB0TwDBPRD5ER3w2Dzbry9NhbElTr7vVfhaGWeOGuqA UXwlXEg6CrNkmJXJ2F1Rzr9WHUzhp7uWxhAbmJREGfi2dEyPAbUAyCjB qhFaqglknvc= \n\
+uri.arpa.      3600 IN DNSKEY  257 3 8 AwEAAenQaBoFmDmvRT+/H5oNbm0Tr5FmNRNDEun0Jpj/ELkzeUrTWhNp QmZeIMC8I0kZ185tEvOnRvn8OvV39B17QIdrvvKGIh2HlgeDRCLolhao jfn2QM0DStjF/WWHpxJOmE6CIuvhqYEU37yoJscGAPpPVPzNvnL1HhYT aao1VRYWQ/maMrJ+bfHg+YX1N6M/8MnRjIKBif1FWjbCKvsn6dnuGGL9 oCWYUFJ3DwofXuhgPyZMkzPc88YkJj5EMvbMH4wtelbCwC+ivx732l0w /rXJn0ciQSOgoeVvDio8dIJmWQITWQAuP+q/ZHFEFHPlrP3gvQh5mcVS 48eLX71Bq7c= \n\
+uri.arpa.      3600 IN RRSIG   DNSKEY 8 2 3600 20210217232440 20210120232440 12670 uri.arpa. DBE2gkKAoxJCfz47KKxzoImN/0AKArhIVHE7TyTwy0DdRPo44V5R+vL6 thUxlQ1CJi2Rw0jwAXymx5Y3Q873pOEllH+4bJoIT4dmoBmPXfYWW7Cl vw9UPKHRP0igKHmCVwIeBYDTU3gfLcMTbR4nEWPDN0GxlL1Mf7ITaC2I oabo79Ip3M/MR8I3Vx/xZ4ZKKPHtLn3xUuJluPNanqJrED2gTslL2xWZ 1tqjsAjJv7JnJo2HJ8XVRB5zBto0IaJ2oBlqcjdcQ/0VlyoM8uOy1pDw HQ2BJl7322gNMHBP9HSiUPIOaIDNUCwW8eUcW6DIUk+s9u3GN1uTqwWz sYB/rA== \n\
+uri.arpa.      3600 IN RRSIG   DNSKEY 8 2 3600 20210217232440 20210120232440 30577 uri.arpa. Kx6HwP4UlkGc1UZ7SERXtQjPajOF4iUvkwDj7MEG1xbQFB1KoJiEb/ei W0qmSWdIhMDv8myhgauejRLyJxwxz8HDRV4xOeHWnRGfWBk4XGYwkejV zOHzoIArVdUVRbr2JKigcTOoyFN+uu52cNB7hRYu7dH5y1hlc6UbOnzR pMtGxcgVyKQ+/ARbIqGG3pegdEOvV49wTPWEiyY65P2urqhvnRg5ok/j zwAdMx4XGshiib7Ojq0sRVl2ZIzj4rFgY/qsSO8SEXEhMo2VuSkoJNio fVzYoqpxEeGnANkIT7Tx2xJL1BWyJxyc7E8Wr2QSgCcc+rYL6IkHDtJG Hy7TaQ== \n\
+uri.arpa.      3600 IN ZONEMD  2018100702 1 1 0DBC3C4DBFD75777C12CA19C337854B1577799901307C482E9D91D5D 15CD934D16319D98E30C4201CF25A1D5A0254960 \n\
+uri.arpa.      3600 IN RRSIG   ZONEMD 8 2 3600 20210217232440 20210120232440 37444 uri.arpa. QDo4XZcL3HMyn8aAHyCUsu/Tqj4Gkth8xY1EqByOb8XOTwVtA4ZNQORE 1siqNqjtJUbeJPtJSbLNqCL7rCq0CzNNnBscv6IIf4gnqJZjlGtHO30o hXtKvEc4z7SU3IASsi6bB3nLmEAyERdYSeU6UBfx8vatQDIRhkgEnnWU Th4= \n\
+ftp.uri.arpa.  604800 IN       NAPTR   0 0 \"\" \"\" \"!^ftp://([^:/?#]*).*$!\\\\1!i\" . \n\
+ftp.uri.arpa.  604800 IN       RRSIG   NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. EygekDgl+Lyyq4NMSEpPyOrOywYf9Y3FAB4v1DT44J3R5QGidaH8l7ZF jHoYFI8sY64iYOCV4sBnX/dh6C1L5NgpY+8l5065Xu3vvjyzbtuJ2k6Y YwJrrCbvl5DDn53zAhhO2hL9uLgyLraZGi9i7TFGd0sm3zNyUF/EVL0C cxU= \n\
+ftp.uri.arpa.  3600 IN NSEC    http.uri.arpa. NAPTR RRSIG NSEC \n\
+ftp.uri.arpa.  3600 IN RRSIG   NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. pbP4KxevPXCu/bDqcvXiuBppXyFEmtHyiy0eAN5gS7mi6mp9Z9bWFjx/ LdH9+6oFGYa5vGmJ5itu/4EDMe8iQeZbI8yrpM4TquB7RR/MGfBnTd8S +sjyQtlRYG7yqEu77Vd78Fme22BKPJ+MVqjS0JHMUE/YUGomPkAjLJJw wGw= \n\
+http.uri.arpa. 604800 IN       NAPTR   0 0 \"\" \"\" \"!^http://([^:/?#]*).*$!\\\\1!i\" . \n\
+http.uri.arpa. 604800 IN       RRSIG   NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. eTqbWvt1GvTeXozuvm4ebaAfkXFQKrtdu0cEiExto80sHIiCbO0WL8UD a/J3cDivtQca7LgUbOb6c17NESsrsVkc6zNPx5RK2tG7ZQYmhYmtqtfg 1oU5BRdHZ5TyqIXcHlw9Blo2pir1Y9IQgshhD7UOGkbkEmvB1Lrd0aHh AAg= \n\
+http.uri.arpa. 3600 IN NSEC    mailto.uri.arpa. NAPTR RRSIG NSEC \n\
+http.uri.arpa. 3600 IN RRSIG   NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. R9rlNzw1CVz2N08q6DhULzcsuUm0UKcPaGAWEU40tr81jEDHsFHNM+kh CdOI8nDstzA42aee4rwCEgijxJpRCcY9hrO1Ysrrr2fdqNz60JikMdar vU5O0p0VXeaaJDfJQT44+o+YXaBwI7Qod3FTMx7aRib8i7istvPm1Rr7 ixA= \n\
+mailto.uri.arpa. 604800 IN     NAPTR   0 0 \"\" \"\" \"!^mailto:(.*)@(.*)$!\\\\2!i\" . \n\
+mailto.uri.arpa. 604800 IN     RRSIG   NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. Ch2zTG2F1plEvQPyIH4Yd80XXLjXOPvMbiqDjpJBcnCJsV8QF7kr0wTL nUT3dB+asQudOjPyzaHGwFlMzmrrAsszN4XAMJ6htDtFJdsgTMP/NkHh YRSmVv6rLeAhd+mVfObY12M//b/GGVTjeUI/gJaLW0fLVZxr1Fp5U5CR jyw= \n\
+mailto.uri.arpa. 3600 IN NSEC  urn.uri.arpa. NAPTR RRSIG NSEC \n\
+mailto.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. fQUbSIE6E7JDi2rosah4SpCOTrKufeszFyj5YEavbQuYlQ5cNFvtm8Ku E2xXMRgRI4RGvM2leVqcoDw5hS3m2pOJLxH8l2WE72YjYvWhvnwc5Rof e/8yB/vaSK9WCnqN8y2q6Vmy73AGP0fuiwmuBra7LlkOiqmyx3amSFiz wms= \n\
+urn.uri.arpa.  604800 IN       NAPTR   0 0 \"\" \"\" \"/urn:([^:]+)/\\\\1/i\" . \n\
+urn.uri.arpa.  604800 IN       RRSIG   NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. CVt2Tgz0e5ZmaSXqRfNys/8OtVCk9nfP0zhezhN8Bo6MDt6yyKZ2kEEW JPjkN7PCYHjO8fGjnUn0AHZI2qBNv7PKHcpR42VY03q927q85a65weOO 1YE0vPYMzACpua9TOtfNnynM2Ws0uN9URxUyvYkXBdqOC81N3sx1dVEL cwc= \n\
+urn.uri.arpa.  3600 IN NSEC    uri.arpa. NAPTR RRSIG NSEC \n\
+urn.uri.arpa.  3600 IN RRSIG   NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. JuKkMiC3/j9iM3V8/izcouXWAVGnSZjkOgEgFPhutMqoylQNRcSkbEZQ zFK8B/PIVdzZF0Y5xkO6zaKQjOzz6OkSaNPIo1a7Vyyl3wDY/uLCRRAH RJfpknuY7O+AUNXvVVIEYJqZggd4kl/Rjh1GTzPYZTRrVi5eQidI1LqC Oeg=";
+
+const char *no_zonemd = "\
+example.      86400  IN  SOA     ns1 admin 2018031900 (  \n\
+                                 1800 900 604800 86400 ) \n\
+              86400  IN  NS      ns1                     \n\
+              86400  IN  NS      ns2                     \n\
+ns1           3600   IN  A       203.0.113.63            \n\
+ns2           3600   IN  AAAA    2001:db8::63";
+
+const char *wrong_soa = "\
+example.      86400  IN  SOA     ns1 admin 2018031900 (  \n\
+                                 1800 900 604800 86400 ) \n\
+              86400  IN  NS      ns1                     \n\
+              86400  IN  NS      ns2                     \n\
+              86400  IN  ZONEMD  2018031901 1 1 (        \n\
+                                 c68090d90a7aed71        \n\
+                                 6bc459f9340e3d7c        \n\
+                                 1370d4d24b7e2fc3        \n\
+                                 a1ddc0b9a87153b9        \n\
+                                 a9713b3c9ae5cc27        \n\
+                                 777f98b8e730044c )      \n\
+ns1           3600   IN  A       203.0.113.63            \n\
+ns2           3600   IN  AAAA    2001:db8::63";
+
+const char *duplicate_schemalg = "\
+example.      86400  IN  SOA     ns1 admin 2018031900 (  \n\
+                                 1800 900 604800 86400 ) \n\
+              86400  IN  NS      ns1                     \n\
+              86400  IN  NS      ns2                     \n\
+              86400  IN  ZONEMD  2018031900 1 1 (        \n\
+                                 c68090d90a7aed71        \n\
+                                 6bc459f9340e3d7c        \n\
+                                 1370d4d24b7e2fc3        \n\
+                                 a1ddc0b9a87153b9        \n\
+                                 a9713b3c9ae5cc27        \n\
+                                 777f98b8e730044c )      \n\
+              86400  IN  ZONEMD  2018031901 1 1 (        \n\
+                                 c68090d90a7aed71        \n\
+                                 6bc459f9340e3d7c        \n\
+                                 1370d4d24b7e2fc3        \n\
+                                 a1ddc0b9a87153b9        \n\
+                                 a9713b3c9ae5cc27        \n\
+                                 777f98b8e730044c )      \n\
+ns1           3600   IN  A       203.0.113.63            \n\
+ns2           3600   IN  AAAA    2001:db8::63";
+
+const char *wrong_hash = "\
+example.      86400  IN  SOA     ns1 admin 2018031900 (  \n\
+                                 1800 900 604800 86400 ) \n\
+              86400  IN  NS      ns1                     \n\
+              86400  IN  NS      ns2                     \n\
+              86400  IN  ZONEMD  2018031900 1 1 (        \n\
+                                 c68090d90a7aed71        \n\
+                                 6bc459f9340e3d7c        \n\
+                                 1370d4d24b7e2fc3        \n\
+                                 a1ddc0b9a87153b9        \n\
+                                 a9713b3c9ae5cc27        \n\
+                                 777f98b8e730044d )      \n\
+ns1           3600   IN  A       203.0.113.63            \n\
+ns2           3600   IN  AAAA    2001:db8::63";
+
+int main(int argc, char *argv[])
+{
+       plan_lazy();
+
+       int ret = check_contents(simple_zone);
+       is_int(KNOT_EOK, ret, "simple zone");
+
+       ret = check_contents(complex_zone);
+       is_int(KNOT_EOK, ret, "complex zone");
+
+       ret = check_contents(multiple_digests);
+       is_int(KNOT_EOK, ret, "multiple digests");
+
+       ret = check_contents(signed_zone);
+       is_int(KNOT_EOK, ret, "signed zone");
+
+       ret = check_contents(no_zonemd);
+       is_int(KNOT_ENOENT, ret, "no zonemd");
+
+       ret = check_contents(wrong_soa);
+       is_int(KNOT_ENOTSUP, ret, "wrong SOA serial");
+       // TODO tests for different scheme / algorithm ?
+
+       ret = check_contents(duplicate_schemalg);
+       is_int(KNOT_ESEMCHECK, ret, "duplicate scheme+algorithm pair");
+
+       ret = check_contents(wrong_hash);
+       is_int(KNOT_EMALF, ret, "wrong hash");
+
+       return 0;
+}