]>
Commit | Line | Data |
---|---|---|
6644c1c7 MT |
1 | From fbc5205702c7f6f431d9f1043c553d7fb62ddfdb Mon Sep 17 00:00:00 2001 |
2 | From: Simon Kelley <simon@thekelleys.org.uk> | |
3 | Date: Tue, 23 Dec 2014 15:46:08 +0000 | |
5f206778 | 4 | Subject: [PATCH 19/87] Fix problems validating NSEC3 and wildcards. |
6644c1c7 MT |
5 | |
6 | --- | |
7 | src/dnssec.c | 253 ++++++++++++++++++++++++++++++----------------------------- | |
8 | 1 file changed, 128 insertions(+), 125 deletions(-) | |
9 | ||
10 | diff --git a/src/dnssec.c b/src/dnssec.c | |
11 | index 3208ac701149..9350d3e8c963 100644 | |
12 | --- a/src/dnssec.c | |
13 | +++ b/src/dnssec.c | |
14 | @@ -615,6 +615,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int | |
15 | Return code: | |
16 | STAT_SECURE if it validates. | |
17 | STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion. | |
18 | + (In this case *wildcard_out points to the "body" of the wildcard within name.) | |
19 | STAT_NO_SIG no RRsigs found. | |
20 | STAT_INSECURE RRset empty. | |
21 | STAT_BOGUS signature is wrong, bad packet. | |
22 | @@ -625,8 +626,8 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int | |
23 | ||
24 | name is unchanged on exit. keyname is used as workspace and trashed. | |
25 | */ | |
26 | -static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, | |
27 | - int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in) | |
28 | +static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, | |
29 | + char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in) | |
30 | { | |
31 | static unsigned char **rrset = NULL, **sigs = NULL; | |
32 | static int rrset_sz = 0, sig_sz = 0; | |
33 | @@ -798,8 +799,16 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in | |
34 | { | |
35 | int k; | |
36 | for (k = name_labels - labels; k != 0; k--) | |
37 | - while (*name_start != '.' && *name_start != 0) | |
38 | - name_start++; | |
39 | + { | |
40 | + while (*name_start != '.' && *name_start != 0) | |
41 | + name_start++; | |
42 | + if (k != 1) | |
43 | + name_start++; | |
44 | + } | |
45 | + | |
46 | + if (wildcard_out) | |
47 | + *wildcard_out = name_start+1; | |
48 | + | |
49 | name_start--; | |
50 | *name_start = '*'; | |
51 | } | |
52 | @@ -974,7 +983,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch | |
53 | if (recp1->addr.ds.keylen == (int)hash->digest_size && | |
54 | (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) && | |
55 | memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && | |
56 | - validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag) == STAT_SECURE) | |
57 | + validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) | |
58 | { | |
59 | valid = 1; | |
60 | break; | |
61 | @@ -1443,11 +1452,88 @@ static int base32_decode(char *in, unsigned char *out) | |
62 | return p - out; | |
63 | } | |
64 | ||
65 | +static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type, | |
66 | + char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count) | |
67 | +{ | |
68 | + int i, hash_len, salt_len, base32_len, rdlen; | |
69 | + unsigned char *p, *psave; | |
70 | + | |
71 | + for (i = 0; i < nsec_count; i++) | |
72 | + if ((p = nsecs[i])) | |
73 | + { | |
74 | + if (!extract_name(header, plen, &p, workspace1, 1, 0) || | |
75 | + !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) | |
76 | + return 0; | |
77 | + | |
78 | + p += 8; /* class, type, TTL */ | |
79 | + GETSHORT(rdlen, p); | |
80 | + psave = p; | |
81 | + p += 4; /* algo, flags, iterations */ | |
82 | + salt_len = *p++; /* salt_len */ | |
83 | + p += salt_len; /* salt */ | |
84 | + hash_len = *p++; /* p now points to next hashed name */ | |
85 | + | |
86 | + if (!CHECK_LEN(header, p, plen, hash_len)) | |
87 | + return 0; | |
88 | + | |
89 | + if (digest_len == base32_len && hash_len == base32_len) | |
90 | + { | |
91 | + int rc = memcmp(workspace2, digest, digest_len); | |
92 | + | |
93 | + if (rc == 0) | |
94 | + { | |
95 | + /* We found an NSEC3 whose hashed name exactly matches the query, so | |
96 | + we just need to check the type map. p points to the RR data for the record. */ | |
97 | + | |
98 | + int offset = (type & 0xff) >> 3; | |
99 | + int mask = 0x80 >> (type & 0x07); | |
100 | + | |
101 | + p += hash_len; /* skip next-domain hash */ | |
102 | + rdlen -= p - psave; | |
103 | + | |
104 | + if (!CHECK_LEN(header, p, plen, rdlen)) | |
105 | + return 0; | |
106 | + | |
107 | + while (rdlen >= 2) | |
108 | + { | |
109 | + if (p[0] == type >> 8) | |
110 | + { | |
111 | + /* Does the NSEC3 say our type exists? */ | |
112 | + if (offset < p[1] && (p[offset+2] & mask) != 0) | |
113 | + return STAT_BOGUS; | |
114 | + | |
115 | + break; /* finshed checking */ | |
116 | + } | |
117 | + | |
118 | + rdlen -= p[1]; | |
119 | + p += p[1]; | |
120 | + } | |
121 | + | |
122 | + return 1; | |
123 | + } | |
124 | + else if (rc <= 0) | |
125 | + { | |
126 | + /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, | |
127 | + wrap around case, name-hash falls between NSEC3 name-hash and end */ | |
128 | + if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0) | |
129 | + return 1; | |
130 | + } | |
131 | + else | |
132 | + { | |
133 | + /* wrap around case, name falls between start and next domain name */ | |
134 | + if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0) | |
135 | + return 1; | |
136 | + } | |
137 | + } | |
138 | + } | |
139 | + return 0; | |
140 | +} | |
141 | + | |
142 | static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, | |
143 | - char *workspace1, char *workspace2, char *name, int type) | |
144 | + char *workspace1, char *workspace2, char *name, int type, char *wildname) | |
145 | { | |
146 | unsigned char *salt, *p, *digest; | |
147 | - int digest_len, i, iterations, salt_len, hash_len, base32_len, algo = 0; | |
148 | + int digest_len, i, iterations, salt_len, base32_len, algo = 0; | |
149 | struct nettle_hash const *hash; | |
150 | char *closest_encloser, *next_closest, *wildcard; | |
151 | ||
152 | @@ -1520,7 +1606,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns | |
153 | if (!(hash = hash_find("sha1"))) | |
154 | return STAT_BOGUS; | |
155 | ||
156 | - /* Now, we need the "closest encloser NSEC3" */ | |
157 | + if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0) | |
158 | + return STAT_BOGUS; | |
159 | + | |
160 | + if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count)) | |
161 | + return STAT_SECURE; | |
162 | + | |
163 | + /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" | |
164 | + or an answer inferred from a wildcard record. */ | |
165 | closest_encloser = name; | |
166 | next_closest = NULL; | |
167 | ||
168 | @@ -1529,6 +1622,9 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns | |
169 | if (*closest_encloser == '.') | |
170 | closest_encloser++; | |
171 | ||
172 | + if (wildname && hostname_isequal(closest_encloser, wildname)) | |
173 | + break; | |
174 | + | |
175 | if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0) | |
176 | return STAT_BOGUS; | |
177 | ||
178 | @@ -1551,127 +1647,33 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns | |
179 | } | |
180 | while ((closest_encloser = strchr(closest_encloser, '.'))); | |
181 | ||
182 | - /* No usable NSEC3s */ | |
183 | - if (i == nsec_count) | |
184 | + if (!closest_encloser) | |
185 | return STAT_BOGUS; | |
186 | ||
187 | - if (!next_closest) | |
188 | - { | |
189 | - /* We found an NSEC3 whose hashed name exactly matches the query, so | |
190 | - Now we just need to check the type map. p points to the RR data for the record. */ | |
191 | - int rdlen; | |
192 | - unsigned char *psave; | |
193 | - int offset = (type & 0xff) >> 3; | |
194 | - int mask = 0x80 >> (type & 0x07); | |
195 | - | |
196 | - p += 8; /* class, type, TTL */ | |
197 | - GETSHORT(rdlen, p); | |
198 | - psave = p; | |
199 | - p += 5 + salt_len; /* algo, flags, iterations, salt_len, salt */ | |
200 | - hash_len = *p++; | |
201 | - if (!CHECK_LEN(header, p, plen, hash_len)) | |
202 | - return STAT_BOGUS; /* bad packet */ | |
203 | - p += hash_len; | |
204 | - rdlen -= p - psave; | |
205 | - | |
206 | - while (rdlen >= 2) | |
207 | - { | |
208 | - if (!CHECK_LEN(header, p, plen, rdlen)) | |
209 | - return STAT_BOGUS; | |
210 | - | |
211 | - if (p[0] == type >> 8) | |
212 | - { | |
213 | - /* Does the NSEC3 say our type exists? */ | |
214 | - if (offset < p[1] && (p[offset+2] & mask) != 0) | |
215 | - return STAT_BOGUS; | |
216 | - | |
217 | - break; /* finshed checking */ | |
218 | - } | |
219 | - | |
220 | - rdlen -= p[1]; | |
221 | - p += p[1]; | |
222 | - } | |
223 | - | |
224 | - return STAT_SECURE; | |
225 | - } | |
226 | - | |
227 | /* Look for NSEC3 that proves the non-existence of the next-closest encloser */ | |
228 | if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0) | |
229 | return STAT_BOGUS; | |
230 | ||
231 | - for (i = 0; i < nsec_count; i++) | |
232 | - if ((p = nsecs[i])) | |
233 | - { | |
234 | - if (!extract_name(header, plen, &p, workspace1, 1, 0) || | |
235 | - !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) | |
236 | - return STAT_BOGUS; | |
237 | - | |
238 | - p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */ | |
239 | - hash_len = *p++; /* p now points to next hashed name */ | |
240 | - | |
241 | - if (!CHECK_LEN(header, p, plen, hash_len)) | |
242 | - return STAT_BOGUS; | |
243 | - | |
244 | - if (digest_len == base32_len && hash_len == base32_len) | |
245 | - { | |
246 | - if (memcmp(workspace2, digest, digest_len) <= 0) | |
247 | - { | |
248 | - /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, | |
249 | - wrap around case, name-hash falls between NSEC3 name-hash and end */ | |
250 | - if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0) | |
251 | - return STAT_SECURE; | |
252 | - } | |
253 | - else | |
254 | - { | |
255 | - /* wrap around case, name falls between start and next domain name */ | |
256 | - if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0) | |
257 | - return STAT_SECURE; | |
258 | - } | |
259 | - } | |
260 | - } | |
261 | - | |
262 | - /* Finally, check that there's no seat of wildcard synthesis */ | |
263 | - if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest) | |
264 | - return STAT_BOGUS; | |
265 | - | |
266 | - wildcard--; | |
267 | - *wildcard = '*'; | |
268 | - | |
269 | - if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) | |
270 | + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count)) | |
271 | return STAT_BOGUS; | |
272 | ||
273 | - for (i = 0; i < nsec_count; i++) | |
274 | - if ((p = nsecs[i])) | |
275 | - { | |
276 | - if (!extract_name(header, plen, &p, workspace1, 1, 0) || | |
277 | - !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) | |
278 | - return STAT_BOGUS; | |
279 | - | |
280 | - p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */ | |
281 | - hash_len = *p++; /* p now points to next hashed name */ | |
282 | - | |
283 | - if (!CHECK_LEN(header, p, plen, hash_len)) | |
284 | - return STAT_BOGUS; | |
285 | - | |
286 | - if (digest_len == base32_len && hash_len == base32_len) | |
287 | - { | |
288 | - if (memcmp(workspace2, digest, digest_len) <= 0) | |
289 | - { | |
290 | - /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, | |
291 | - wrap around case, name-hash falls between NSEC3 name-hash and end */ | |
292 | - if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0) | |
293 | - return STAT_SECURE; | |
294 | - } | |
295 | - else | |
296 | - { | |
297 | - /* wrap around case, name falls between start and next domain name */ | |
298 | - if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0) | |
299 | - return STAT_SECURE; | |
300 | - } | |
301 | - } | |
302 | - } | |
303 | + /* Finally, check that there's no seat of wildcard synthesis */ | |
304 | + if (!wildname) | |
305 | + { | |
306 | + if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest) | |
307 | + return STAT_BOGUS; | |
308 | + | |
309 | + wildcard--; | |
310 | + *wildcard = '*'; | |
311 | + | |
312 | + if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) | |
313 | + return STAT_BOGUS; | |
314 | + | |
315 | + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count)) | |
316 | + return STAT_BOGUS; | |
317 | + } | |
318 | ||
319 | - return STAT_BOGUS; | |
320 | + return STAT_SECURE; | |
321 | } | |
322 | ||
323 | /* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */ | |
324 | @@ -1792,8 +1794,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch | |
325 | struct all_addr a; | |
326 | struct blockdata *key; | |
327 | struct crec *crecp; | |
328 | - | |
329 | - rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0); | |
330 | + char *wildname; | |
331 | + | |
332 | + rc = validate_rrset(now, header, plen, class1, type1, name, keyname, &wildname, NULL, 0, 0, 0); | |
333 | ||
334 | if (rc == STAT_SECURE_WILDCARD) | |
335 | { | |
336 | @@ -1807,7 +1810,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch | |
337 | if (nsec_type == T_NSEC) | |
338 | rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1); | |
339 | else | |
340 | - rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1); | |
341 | + rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, wildname); | |
342 | ||
343 | if (rc != STAT_SECURE) | |
344 | return rc; | |
345 | @@ -1933,7 +1936,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch | |
346 | if (nsec_type == T_NSEC) | |
347 | return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype); | |
348 | else | |
349 | - return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype); | |
350 | + return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL); | |
351 | } | |
352 | ||
353 | /* Chase the CNAME chain in the packet until the first record which _doesn't validate. | |
354 | @@ -1980,7 +1983,7 @@ int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char | |
355 | return STAT_INSECURE; | |
356 | ||
357 | /* validate CNAME chain, return if insecure or need more data */ | |
358 | - rc = validate_rrset(now, header, plen, class, type, name, keyname, NULL, 0, 0, 0); | |
359 | + rc = validate_rrset(now, header, plen, class, type, name, keyname, NULL, NULL, 0, 0, 0); | |
360 | if (rc != STAT_SECURE) | |
361 | { | |
362 | if (rc == STAT_NO_SIG) | |
363 | -- | |
364 | 2.1.0 | |
365 |