zone-nsec.c 7.24 KB
Newer Older
Jan Včelák's avatar
Jan Včelák committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*  Copyright (C) 2011 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 <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>

23
#include "common/base32hex.h"
24
#include "common/debug.h"
Jan Včelák's avatar
Jan Včelák committed
25
#include "common/descriptor.h"
26
#include "common/hhash.h"
27 28 29
#include "libknot/dnssec/bitmap.h"
#include "libknot/util/utils.h"
#include "libknot/packet/wire.h"
30 31
#include "libknot/rrtype/soa.h"
#include "libknot/rrtype/nsec3.h"
32 33 34 35
#include "knot/dnssec/nsec-chain.h"
#include "knot/dnssec/nsec3-chain.h"
#include "knot/dnssec/zone-nsec.h"
#include "knot/dnssec/zone-sign.h"
36
#include "knot/zone/contents.h"
37
#include "knot/zone/zone-diff.h"
Jan Včelák's avatar
Jan Včelák committed
38

39
/*!
40
 * \brief Deletes NSEC3 chain if NSEC should be used.
41
 *
42 43
 * \param zone       Zone to fix.
 * \param changeset  Changeset to be used.
44 45
 * \return KNOT_E*
 */
46
static int delete_nsec3_chain(const zone_contents_t *zone,
47
                              changeset_t *changeset)
48
{
49 50
	assert(zone);
	assert(changeset);
51

52
	if (zone_tree_is_empty(zone->nsec3_nodes)) {
53
		return KNOT_EOK;
54
	}
55

56
	dbg_dnssec_detail("deleting NSEC3 chain\n");
57
	zone_tree_t *empty_tree = zone_tree_create();
58
	if (!empty_tree) {
59 60 61
		return KNOT_ENOMEM;
	}

62
	int result = zone_tree_add_diff(zone->nsec3_nodes, empty_tree,
63
	                                     changeset);
64

65
	zone_tree_free(&empty_tree);
66

67
	return result;
68 69
}

70
/* - helper functions ------------------------------------------------------ */
71

72
/*!
73
 * \brief Check if NSEC3 is enabled for given zone.
74
 */
75
bool knot_is_nsec3_enabled(const zone_contents_t *zone)
76
{
77
	if (!zone) {
78
		return false;
79 80
	}

81
	return zone->nsec3_params.algorithm != 0;
82 83
}

84
/*!
85 86
 * \brief Get minimum TTL from zone SOA.
 * \note Value should be used for NSEC records.
87
 */
88
static bool get_zone_soa_min_ttl(const zone_contents_t *zone,
89
                                 uint32_t *ttl)
90
{
91 92 93
	assert(zone);
	assert(zone->apex);
	assert(ttl);
94

95 96
	zone_node_t *apex = zone->apex;
	const knot_rdataset_t *soa = node_rdataset(apex, KNOT_RRTYPE_SOA);
97
	if (!soa) {
98 99 100
		return false;
	}

101
	uint32_t result =  knot_soa_minimum(soa);
102 103
	if (result == 0) {
		return false;
104
	}
105

106 107
	*ttl = result;
	return true;
108 109
}

110 111 112 113
/*!
 * \brief Finds a node with the same owner as the given NSEC3 RRSet and marks it
 *        as 'removed'.
 *
114
 * \param data NSEC3 tree to search for the node in. (type zone_tree_t *).
115 116 117 118 119 120
 * \param rrset RRSet whose owner will be sought in the zone tree. non-NSEC3
 *              RRSets are ignored.
 *
 * This function is constructed as a callback for the knot_changeset_apply() f
 * function.
 */
121
static int mark_nsec3(knot_rrset_t *rrset, zone_tree_t *nsec3s)
122 123
{
	assert(rrset != NULL);
124
	assert(nsec3s != NULL);
125

126
	zone_node_t *node = NULL;
127 128
	int ret;

129
	if (rrset->type == KNOT_RRTYPE_NSEC3) {
130
		// Find the name in the NSEC3 tree and mark the node
131
		ret = zone_tree_get(nsec3s, rrset->owner,
132 133 134 135 136 137
		                         &node);
		if (ret != KNOT_EOK) {
			return ret;
		}

		if (node != NULL) {
138
			node->flags |= NODE_FLAGS_REMOVED_NSEC;
139 140 141 142 143 144
		}
	}

	return KNOT_EOK;
}

145 146 147 148 149 150
/*!
 * \brief Marks all NSEC3 nodes in zone from which RRSets are to be removed.
 *
 * For each NSEC3 RRSet in the changeset finds its node and marks it with the
 * 'removed' flag.
 */
151
static int mark_removed_nsec3(changeset_t *out_ch,
152
                              const zone_contents_t *zone)
153
{
154
	if (zone_tree_is_empty(zone->nsec3_nodes)) {
155 156
		return KNOT_EOK;
	}
157 158 159

	changeset_iter_t itt;
	changeset_iter_rem(&itt, out_ch, false);
160
	
161
	knot_rrset_t rr = changeset_iter_next(&itt);
162 163 164
	while (!knot_rrset_empty(&rr)) {
		int ret = mark_nsec3(&rr, zone->nsec3_nodes);
		if (ret != KNOT_EOK) {
165
			changeset_iter_clear(&itt);
166 167
			return ret;
		}
168
		rr = changeset_iter_next(&itt);
169
	}
170
	changeset_iter_clear(&itt);
171 172
	
	return KNOT_EOK;
173 174
}

175
/* - public API ------------------------------------------------------------ */
Jan Včelák's avatar
Jan Včelák committed
176

177 178
/*!
 * \brief Create NSEC3 owner name from regular owner name.
179 180 181 182 183 184
 *
 * \param owner      Node owner name.
 * \param zone_apex  Zone apex name.
 * \param params     Params for NSEC3 hashing function.
 *
 * \return NSEC3 owner name, NULL in case of error.
185
 */
186 187 188
knot_dname_t *knot_create_nsec3_owner(const knot_dname_t *owner,
                                      const knot_dname_t *zone_apex,
                                      const knot_nsec3_params_t *params)
189
{
190
	if (owner == NULL || zone_apex == NULL || params == NULL) {
191 192 193 194 195
		return NULL;
	}

	uint8_t *hash = NULL;
	size_t hash_size = 0;
196
	int owner_size = knot_dname_size(owner);
197

198
	if (owner_size < 0) {
199 200 201
		return NULL;
	}

202 203
	if (knot_nsec3_hash(params, owner, owner_size, &hash, &hash_size)
	    != KNOT_EOK) {
204 205 206
		return NULL;
	}

207
	knot_dname_t *result = knot_nsec3_hash_to_dname(hash, hash_size, zone_apex);
208 209 210 211 212
	free(hash);

	return result;
}

213 214 215
/*!
 * \brief Create NSEC3 owner name from hash and zone apex.
 */
216 217 218 219 220 221 222
knot_dname_t *knot_nsec3_hash_to_dname(const uint8_t *hash, size_t hash_size,
                                       const knot_dname_t *zone_apex)
{
	assert(zone_apex);

	// encode raw hash to first label

223
	uint8_t label[KNOT_DNAME_MAXLEN];
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
	int32_t label_size;
	label_size = base32hex_encode(hash, hash_size, label, sizeof(label));
	if (label_size <= 0) {
		return NULL;
	}

	// allocate result

	size_t zone_apex_size = knot_dname_size(zone_apex);
	size_t result_size = 1 + label_size + zone_apex_size;
	knot_dname_t *result = malloc(result_size);
	if (!result) {
		return NULL;
	}

	// build the result

	uint8_t *write = result;

	*write = (uint8_t)label_size;
	write += 1;
	memcpy(write, label, label_size);
	write += label_size;
	memcpy(write, zone_apex, zone_apex_size);
	write += zone_apex_size;

	assert(write == result + result_size);
	knot_dname_to_lower(result);

	return result;
}

256 257 258
/*!
 * \brief Create NSEC or NSEC3 chain in the zone.
 */
259
int knot_zone_create_nsec_chain(const zone_contents_t *zone,
260
                                changeset_t *changeset,
261 262
                                const knot_zone_keys_t *zone_keys,
                                const knot_dnssec_policy_t *policy)
Jan Včelák's avatar
Jan Včelák committed
263
{
264
	if (!zone || !changeset) {
Jan Včelák's avatar
Jan Včelák committed
265
		return KNOT_EINVAL;
266
	}
Jan Včelák's avatar
Jan Včelák committed
267 268

	uint32_t nsec_ttl = 0;
269
	if (!get_zone_soa_min_ttl(zone, &nsec_ttl)) {
Jan Kadlec's avatar
Jan Kadlec committed
270
		return KNOT_EINVAL;
271
	}
Jan Včelák's avatar
Jan Včelák committed
272

273
	int result;
274
	bool nsec3_enabled = knot_is_nsec3_enabled(zone);
275

276
	if (nsec3_enabled) {
277
		result = knot_nsec3_create_chain(zone, nsec_ttl, changeset);
278
	} else {
279
		result = knot_nsec_create_chain(zone, nsec_ttl, changeset);
280
	}
Jan Včelák's avatar
Jan Včelák committed
281

282 283 284 285
	if (result == KNOT_EOK && !nsec3_enabled) {
		result = delete_nsec3_chain(zone, changeset);
	}

286 287 288 289 290
	if (result == KNOT_EOK) {
		// Mark removed NSEC3 nodes, so that they are not signed later
		result = mark_removed_nsec3(changeset, zone);
	}

291 292 293 294 295
	if (result != KNOT_EOK) {
		return result;
	}

	// Sign newly created records right away
296
	return knot_zone_sign_nsecs_in_changeset(zone_keys, policy, changeset);
Jan Včelák's avatar
Jan Včelák committed
297
}