From: Libor Peltan Date: Tue, 23 Mar 2021 17:15:26 +0000 (+0100) Subject: implemented zonemd calculation/verification X-Git-Tag: v3.1.0~75^2~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ebd79e7630d31a4b9ff0cefd560de12bf7c07235;p=thirdparty%2Fknot-dns.git implemented zonemd calculation/verification --- diff --git a/Knot.files b/Knot.files index 936ee05475..ad3777df30 100644 --- a/Knot.files +++ b/Knot.files @@ -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 diff --git a/Knot.includes b/Knot.includes index 7b61db7ba4..c1a976e2ea 100644 --- a/Knot.includes +++ b/Knot.includes @@ -6,3 +6,6 @@ src/libzscanner tests tests-fuzz tests-fuzz/knotd_wrap +src/knot/zone +src/libknot/rrtype +tests/knot diff --git a/doc/doxygen/Doxy.page.h b/doc/doxygen/Doxy.page.h index 32906b78bb..e4d0242617 100644 --- a/doc/doxygen/Doxy.page.h +++ b/doc/doxygen/Doxy.page.h @@ -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 diff --git a/src/knot/Makefile.inc b/src/knot/Makefile.inc index bf0a085589..9f9400d117 100644 --- a/src/knot/Makefile.inc +++ b/src/knot/Makefile.inc @@ -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 index 0000000000..536bc37c2f --- /dev/null +++ b/src/knot/zone/digest.c @@ -0,0 +1,202 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. + + 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 . + */ + +#include "knot/zone/digest.h" + +#include + +#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 index 0000000000..f72342ddd2 --- /dev/null +++ b/src/knot/zone/digest.h @@ -0,0 +1,45 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. + + 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 . + */ + +#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); diff --git a/src/libdnssec/Makefile.inc b/src/libdnssec/Makefile.inc index 4cc25fc7e6..981d84102d 100644 --- a/src/libdnssec/Makefile.inc +++ b/src/libdnssec/Makefile.inc @@ -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 index 0000000000..83a4fcbd46 --- /dev/null +++ b/src/libdnssec/digest.c @@ -0,0 +1,105 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. + + 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 . + */ + +#include "libdnssec/digest.h" + +#include +#include + +#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 index 0000000000..76709aa534 --- /dev/null +++ b/src/libdnssec/digest.h @@ -0,0 +1,76 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. + + 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 . + */ + +/*! + * \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); + +/*! @} */ diff --git a/src/libdnssec/error.c b/src/libdnssec/error.c index a0f5e05f4e..d4f9a81e7f 100644 --- a/src/libdnssec/error.c +++ b/src/libdnssec/error.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 CZ.NIC, z.s.p.o. +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. 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 } }; diff --git a/src/libdnssec/error.h b/src/libdnssec/error.h index f998fcba8c..aee87cb1e1 100644 --- a/src/libdnssec/error.h +++ b/src/libdnssec/error.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 CZ.NIC, z.s.p.o. +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. 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 }; diff --git a/src/libknot/Makefile.inc b/src/libknot/Makefile.inc index 07b60691df..0c5f1eff3b 100644 --- a/src/libknot/Makefile.inc +++ b/src/libknot/Makefile.inc @@ -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 \ diff --git a/src/libknot/libknot.h.in b/src/libknot/libknot.h.in index 3e0c92fb3c..85ec7cdf48 100644 --- a/src/libknot/libknot.h.in +++ b/src/libknot/libknot.h.in @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 CZ.NIC, z.s.p.o. +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. 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 index 0000000000..ba1c838778 --- /dev/null +++ b/src/libknot/rrtype/zonemd.h @@ -0,0 +1,71 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. + + 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 . + */ + +/*! + * \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; +} + +/*! @} */ diff --git a/tests/.gitignore b/tests/.gitignore index eed2ccbe1a..3718cbfced 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -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 diff --git a/tests/Makefile.am b/tests/Makefile.am index 0c9cf152b6..93c97e5ea4 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 index 0000000000..03c453e433 --- /dev/null +++ b/tests/knot/test_digest.c @@ -0,0 +1,317 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. + + 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 . + */ + +#include "knot/zone/digest.h" + +#include +#include + +#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; +}