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