1 /* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2
3 This program is free software: you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation, either version 3 of the License, or
6 (at your option) any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <https://www.gnu.org/licenses/>.
15 */
16
17 #include <stdio.h>
18
19 #include "knot/zone/digest.h"
20 #include "knot/dnssec/rrset-sign.h"
21 #include "knot/updates/zone-update.h"
22 #include "contrib/wire_ctx.h"
23 #include "libdnssec/digest.h"
24 #include "libknot/libknot.h"
25
26 #define DIGEST_BUF_MIN 4096
27 #define DIGEST_BUF_MAX (40 * 1024 * 1024)
28
29 typedef struct {
30 size_t buf_size;
31 uint8_t *buf;
32 struct dnssec_digest_ctx *digest_ctx;
33 const zone_node_t *apex;
34 } contents_digest_ctx_t;
35
digest_rrset(knot_rrset_t * rrset,const zone_node_t * node,void * vctx)36 static int digest_rrset(knot_rrset_t *rrset, const zone_node_t *node, void *vctx)
37 {
38 contents_digest_ctx_t *ctx = vctx;
39
40 // ignore apex ZONEMD
41 if (node == ctx->apex && rrset->type == KNOT_RRTYPE_ZONEMD) {
42 return KNOT_EOK;
43 }
44
45 // ignore RRSIGs of apex ZONEMD
46 if (node == ctx->apex && rrset->type == KNOT_RRTYPE_RRSIG) {
47 knot_rdataset_t cpy = rrset->rrs, zonemd_rrsig = { 0 };
48 int ret = knot_rdataset_copy(&rrset->rrs, &cpy, NULL);
49 if (ret != KNOT_EOK) {
50 return ret;
51 }
52
53 ret = knot_synth_rrsig(KNOT_RRTYPE_ZONEMD, &rrset->rrs, &zonemd_rrsig, NULL);
54 if (ret == KNOT_EOK) {
55 ret = knot_rdataset_subtract(&rrset->rrs, &zonemd_rrsig, NULL);
56 knot_rdataset_clear(&zonemd_rrsig, NULL);
57 }
58 if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
59 knot_rdataset_clear(&rrset->rrs, NULL);
60 return ret;
61 }
62 }
63
64 // serialize RRSet, expand buf as needed
65 int ret = knot_rrset_to_wire_extra(rrset, ctx->buf, ctx->buf_size, 0,
66 NULL, KNOT_PF_ORIGTTL);
67 while (ret == KNOT_ESPACE && ctx->buf_size < DIGEST_BUF_MAX) {
68 free(ctx->buf);
69 ctx->buf_size *= 2;
70 ctx->buf = malloc(ctx->buf_size);
71 if (ctx->buf == NULL) {
72 return KNOT_ENOMEM;
73 }
74 ret = knot_rrset_to_wire_extra(rrset, ctx->buf, ctx->buf_size, 0,
75 NULL, KNOT_PF_ORIGTTL);
76 }
77
78 // cleanup apex RRSIGs mess
79 if (node == ctx->apex && rrset->type == KNOT_RRTYPE_RRSIG) {
80 knot_rdataset_clear(&rrset->rrs, NULL);
81 }
82
83 if (ret < 0) {
84 return ret;
85 }
86
87 // digest serialized RRSet
88 dnssec_binary_t bufbin = { ret, ctx->buf };
89 return dnssec_digest(ctx->digest_ctx, &bufbin);
90 }
91
digest_node(zone_node_t * node,void * ctx)92 static int digest_node(zone_node_t *node, void *ctx)
93 {
94 int i = 0, ret = KNOT_EOK;
95 for ( ; i < node->rrset_count && ret == KNOT_EOK; i++) {
96 knot_rrset_t rrset = node_rrset_at(node, i);
97 ret = digest_rrset(&rrset, node, ctx);
98 }
99 return ret;
100 }
101
zone_contents_digest(const zone_contents_t * contents,int algorithm,uint8_t ** out_digest,size_t * out_size)102 int zone_contents_digest(const zone_contents_t *contents, int algorithm,
103 uint8_t **out_digest, size_t *out_size)
104 {
105 if (out_digest == NULL || out_size == NULL) {
106 return KNOT_EINVAL;
107 }
108
109 if (contents == NULL) {
110 return KNOT_EEMPTYZONE;
111 }
112
113 contents_digest_ctx_t ctx = {
114 .buf_size = DIGEST_BUF_MIN,
115 .buf = malloc(DIGEST_BUF_MIN),
116 .apex = contents->apex,
117 };
118 if (ctx.buf == NULL) {
119 return KNOT_ENOMEM;
120 }
121
122 int ret = dnssec_digest_init(algorithm, &ctx.digest_ctx);
123 if (ret != DNSSEC_EOK) {
124 free(ctx.buf);
125 return knot_error_from_libdnssec(ret);
126 }
127
128 zone_tree_t *conts = contents->nodes;
129 if (!zone_tree_is_empty(contents->nsec3_nodes)) {
130 conts = zone_tree_shallow_copy(conts);
131 if (conts == NULL) {
132 ret = KNOT_ENOMEM;;
133 }
134 if (ret == KNOT_EOK) {
135 ret = zone_tree_merge(conts, contents->nsec3_nodes);
136 }
137 }
138
139 if (ret == KNOT_EOK) {
140 ret = zone_tree_apply(conts, digest_node, &ctx);
141 }
142
143 if (conts != contents->nodes) {
144 zone_tree_free(&conts);
145 }
146
147 dnssec_binary_t res = { 0 };
148 if (ret == KNOT_EOK) {
149 ret = dnssec_digest_finish(ctx.digest_ctx, &res);
150 }
151 free(ctx.buf);
152 *out_digest = res.data;
153 *out_size = res.size;
154 return ret;
155 }
156
verify_zonemd(const knot_rdata_t * zonemd,const zone_contents_t * contents)157 static int verify_zonemd(const knot_rdata_t *zonemd, const zone_contents_t *contents)
158 {
159 uint8_t *computed = NULL;
160 size_t comp_size = 0;
161 int ret = zone_contents_digest(contents, knot_zonemd_algorithm(zonemd),
162 &computed, &comp_size);
163 if (ret != KNOT_EOK) {
164 return ret;
165 }
166
167 if (comp_size != knot_zonemd_digest_size(zonemd)) {
168 ret = KNOT_EFEWDATA;
169 } else if (memcmp(knot_zonemd_digest(zonemd), computed, comp_size) != 0) {
170 ret = KNOT_EMALF;
171 }
172 free(computed);
173 return ret;
174 }
175
zone_contents_digest_exists(const zone_contents_t * contents,int alg,bool no_verify)176 bool zone_contents_digest_exists(const zone_contents_t *contents, int alg, bool no_verify)
177 {
178 if (alg == 0) {
179 return true;
180 }
181
182 knot_rdataset_t *zonemd = node_rdataset(contents->apex, KNOT_RRTYPE_ZONEMD);
183
184 if (alg == ZONE_DIGEST_REMOVE) {
185 return (zonemd == NULL || zonemd->count == 0);
186 }
187
188 if (zonemd == NULL || zonemd->count != 1 || knot_zonemd_algorithm(zonemd->rdata) != alg) {
189 return false;
190 }
191
192 if (no_verify) {
193 return true;
194 }
195
196 return verify_zonemd(zonemd->rdata, contents) == KNOT_EOK;
197 }
198
check_duplicate_schalg(const knot_rdataset_t * zonemd,int check_upto,uint8_t scheme,uint8_t alg)199 static bool check_duplicate_schalg(const knot_rdataset_t *zonemd, int check_upto,
200 uint8_t scheme, uint8_t alg)
201 {
202 knot_rdata_t *check = zonemd->rdata;
203 assert(check_upto <= zonemd->count);
204 for (int i = 0; i < check_upto; i++) {
205 if (knot_zonemd_scheme(check) == scheme &&
206 knot_zonemd_algorithm(check) == alg) {
207 return false;
208 }
209 check = knot_rdataset_next(check);
210 }
211 return true;
212 }
213
zone_contents_digest_verify(const zone_contents_t * contents)214 int zone_contents_digest_verify(const zone_contents_t *contents)
215 {
216 if (contents == NULL) {
217 return KNOT_EEMPTYZONE;
218 }
219
220 knot_rdataset_t *zonemd = node_rdataset(contents->apex, KNOT_RRTYPE_ZONEMD);
221 if (zonemd == NULL) {
222 return KNOT_ENOENT;
223 }
224
225 uint32_t soa_serial = zone_contents_serial(contents);
226
227 knot_rdata_t *rr = zonemd->rdata, *supported = NULL;
228 for (int i = 0; i < zonemd->count; i++) {
229 if (knot_zonemd_scheme(rr) == KNOT_ZONEMD_SCHEME_SIMPLE &&
230 knot_zonemd_digest_size(rr) > 0 &&
231 knot_zonemd_soa_serial(rr) == soa_serial) {
232 supported = rr;
233 }
234 if (!check_duplicate_schalg(zonemd, i, knot_zonemd_scheme(rr),
235 knot_zonemd_algorithm(rr))) {
236 return KNOT_ESEMCHECK;
237 }
238 rr = knot_rdataset_next(rr);
239 }
240
241 return supported == NULL ? KNOT_ENOTSUP : verify_zonemd(supported, contents);
242 }
243
zonemd_hash_offs(void)244 static ptrdiff_t zonemd_hash_offs(void)
245 {
246 knot_rdata_t fake = { 0 };
247 return knot_zonemd_digest(&fake) - fake.data;
248 }
249
zone_update_add_digest(struct zone_update * update,int algorithm,bool placeholder)250 int zone_update_add_digest(struct zone_update *update, int algorithm, bool placeholder)
251 {
252 if (update == NULL) {
253 return KNOT_EINVAL;
254 }
255
256 uint8_t *digest = NULL;
257 size_t dsize = 0;
258
259 knot_rrset_t exists = node_rrset(update->new_cont->apex, KNOT_RRTYPE_ZONEMD);
260 if (algorithm == ZONE_DIGEST_REMOVE) {
261 return zone_update_remove(update, &exists);
262 }
263 if (placeholder) {
264 if (!knot_rrset_empty(&exists) &&
265 !check_duplicate_schalg(&exists.rrs, exists.rrs.count,
266 KNOT_ZONEMD_SCHEME_SIMPLE, algorithm)) {
267 return KNOT_EOK;
268 }
269 } else {
270 int ret = zone_contents_digest(update->new_cont, algorithm, &digest, &dsize);
271 if (ret != KNOT_EOK) {
272 return ret;
273 }
274
275 ret = zone_update_remove(update, &exists);
276 if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
277 free(digest);
278 return ret;
279 }
280 }
281
282 knot_rrset_t zonemd, soa = node_rrset(update->new_cont->apex, KNOT_RRTYPE_SOA);
283
284 uint8_t rdata[zonemd_hash_offs() + dsize];
285 wire_ctx_t wire = wire_ctx_init(rdata, sizeof(rdata));
286 wire_ctx_write_u32(&wire, knot_soa_serial(soa.rrs.rdata));
287 wire_ctx_write_u8(&wire, KNOT_ZONEMD_SCHEME_SIMPLE);
288 wire_ctx_write_u8(&wire, algorithm);
289 wire_ctx_write(&wire, digest, dsize);
290 assert(wire.error == KNOT_EOK && wire_ctx_available(&wire) == 0);
291
292 free(digest);
293
294 knot_rrset_init(&zonemd, update->new_cont->apex->owner, KNOT_RRTYPE_ZONEMD,
295 KNOT_CLASS_IN, soa.ttl);
296 int ret = knot_rrset_add_rdata(&zonemd, rdata, sizeof(rdata), NULL);
297 if (ret != KNOT_EOK) {
298 return ret;
299 }
300
301 ret = zone_update_add(update, &zonemd);
302 knot_rdataset_clear(&zonemd.rrs, NULL);
303 return ret;
304 }
305