]>
Commit | Line | Data |
---|---|---|
e292e93d GB |
1 | |
2 | #include "dnsmasq.h" | |
d322de06 | 3 | #include "dnssec-crypto.h" |
e292e93d GB |
4 | #include <assert.h> |
5 | ||
6 | #define SERIAL_UNDEF -100 | |
7 | #define SERIAL_EQ 0 | |
8 | #define SERIAL_LT -1 | |
9 | #define SERIAL_GT 1 | |
10 | ||
e292e93d GB |
11 | /* Implement RFC1982 wrapped compare for 32-bit numbers */ |
12 | static int serial_compare_32(unsigned long s1, unsigned long s2) | |
13 | { | |
14 | if (s1 == s2) | |
15 | return SERIAL_EQ; | |
16 | ||
17 | if ((s1 < s2 && (s2 - s1) < (1UL<<31)) || | |
18 | (s1 > s2 && (s1 - s2) > (1UL<<31))) | |
19 | return SERIAL_LT; | |
20 | if ((s1 < s2 && (s2 - s1) > (1UL<<31)) || | |
21 | (s1 > s2 && (s1 - s2) < (1UL<<31))) | |
22 | return SERIAL_GT; | |
23 | return SERIAL_UNDEF; | |
24 | } | |
25 | ||
26 | /* Extract a DNS name from wire format, without handling compression. This is | |
27 | faster than extract_name() and does not require access to the full dns | |
28 | packet. */ | |
29 | static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf) | |
30 | { | |
31 | unsigned char *start=rr, *end = rr+maxlen; | |
32 | int count; | |
33 | ||
34 | while (rr < end && *rr != 0) | |
35 | { | |
36 | count = *rr++; | |
6445c8ed | 37 | while (count-- > 0 && rr < end) |
e292e93d GB |
38 | { |
39 | *buf = *rr++; | |
b98f7715 GB |
40 | if (!isascii(*buf) || iscntrl(*buf) || *buf == '.') |
41 | return 0; | |
e292e93d GB |
42 | if (*buf >= 'A' && *buf <= 'Z') |
43 | *buf += 'a' - 'A'; | |
44 | buf++; | |
79333a24 | 45 | } |
e292e93d GB |
46 | *buf++ = '.'; |
47 | } | |
dd090561 | 48 | /* Remove trailing dot (if any) */ |
2ef843dd GB |
49 | if (rr != start) |
50 | *(--buf) = 0; | |
e292e93d GB |
51 | if (rr == end) |
52 | return 0; | |
dd090561 | 53 | /* Trailing \0 in source data must be consumed */ |
79333a24 | 54 | return rr-start+1; |
e292e93d GB |
55 | } |
56 | ||
57 | /* Check whether today/now is between date_start and date_end */ | |
58 | static int check_date_range(unsigned long date_start, unsigned long date_end) | |
59 | { | |
60 | /* TODO: double-check that time(0) is the correct time we are looking for */ | |
61 | /* TODO: dnssec requires correct timing; implement SNTP in dnsmasq? */ | |
62 | unsigned long curtime = time(0); | |
63 | ||
64 | /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ | |
65 | if (serial_compare_32(curtime, date_start) != SERIAL_GT) | |
66 | return 0; | |
67 | if (serial_compare_32(curtime, date_end) != SERIAL_LT) | |
68 | return 0; | |
69 | return 1; | |
70 | } | |
71 | ||
72 | /* Sort RRs within a RRset in canonical order, according to RFC4034, ยง6.3 | |
73 | Notice that the RRDATA sections have been already normalized, so a memcpy | |
74 | is sufficient. | |
75 | NOTE: r1/r2 point immediately after the owner name. */ | |
76 | static int rrset_canonical_order(const void *r1, const void *r2) | |
77 | { | |
78 | int r1len, r2len, res; | |
0decc869 | 79 | const unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2; |
e292e93d GB |
80 | |
81 | pr1 += 8; pr2 += 8; | |
82 | GETSHORT(r1len, pr1); GETSHORT(r2len, pr2); | |
83 | ||
84 | /* Lexicographically compare RDATA (thus, if equal, smaller length wins) */ | |
85 | res = memcmp(pr1, pr2, MIN(r1len, r2len)); | |
86 | if (res == 0) | |
87 | { | |
88 | if (r1len < r2len) | |
89 | return -1; | |
90 | else | |
91 | /* NOTE: RFC2181 says that an RRset is not allowed to contain duplicate | |
92 | records. If it happens, it is a protocol error and anything goes. */ | |
93 | return 1; | |
94 | } | |
95 | ||
96 | return res; | |
97 | } | |
98 | ||
adca3e9c GB |
99 | typedef struct PendingRRSIGValidation |
100 | { | |
101 | VerifyAlgCtx *alg; | |
102 | char *signer_name; | |
103 | int keytag; | |
104 | } PendingRRSIGValidation; | |
105 | ||
13e435eb GB |
106 | /* Pass a domain name through a verification hash function. |
107 | ||
108 | We must pass domain names in DNS wire format, but uncompressed. | |
109 | This means that we cannot directly use raw data from the original | |
110 | message since it might be compressed. */ | |
111 | static void verifyalg_add_data_domain(VerifyAlgCtx *alg, char* name) | |
112 | { | |
113 | unsigned char len; char *p; | |
114 | ||
115 | while ((p = strchr(name, '.'))) | |
116 | { | |
117 | len = p-name; | |
118 | alg->vtbl->add_data(alg, &len, 1); | |
119 | alg->vtbl->add_data(alg, name, len); | |
120 | name = p+1; | |
121 | } | |
122 | len = strlen(name); | |
123 | alg->vtbl->add_data(alg, &len, 1); | |
124 | alg->vtbl->add_data(alg, name, len+1); | |
125 | } | |
126 | ||
adca3e9c GB |
127 | static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, |
128 | unsigned char *reply, int count, char *owner, | |
129 | int sigclass, int sigrdlen, unsigned char *sig, | |
130 | PendingRRSIGValidation *out) | |
e292e93d GB |
131 | { |
132 | int i, res; | |
133 | int sigtype, sigalg, siglbl; | |
134 | unsigned char *sigrdata = sig; | |
135 | unsigned long sigttl, date_end, date_start; | |
136 | unsigned char* p = reply; | |
137 | char* signer_name = daemon->namebuff; | |
13e435eb | 138 | int signer_name_rdlen; |
e292e93d GB |
139 | int keytag; |
140 | void *rrset[16]; /* TODO: max RRset size? */ | |
141 | int rrsetidx = 0; | |
142 | ||
143 | if (sigrdlen < 18) | |
144 | return 0; | |
145 | GETSHORT(sigtype, sig); | |
146 | sigalg = *sig++; | |
147 | siglbl = *sig++; | |
148 | GETLONG(sigttl, sig); | |
149 | GETLONG(date_end, sig); | |
150 | GETLONG(date_start, sig); | |
151 | GETSHORT(keytag, sig); | |
152 | sigrdlen -= 18; | |
153 | ||
adca3e9c | 154 | if (!verifyalg_supported(sigalg)) |
e292e93d GB |
155 | { |
156 | printf("RRSIG algorithm not supported: %d\n", sigalg); | |
157 | return 0; | |
158 | } | |
159 | ||
d31d057a | 160 | if (!check_date_range(date_start, date_end)) |
e292e93d GB |
161 | { |
162 | printf("RRSIG outside date range\n"); | |
163 | return 0; | |
164 | } | |
165 | ||
166 | /* Iterate within the answer and find the RRsets matching the current RRsig */ | |
167 | for (i = 0; i < count; ++i) | |
168 | { | |
169 | int qtype, qclass, rdlen; | |
170 | if (!(res = extract_name(header, pktlen, &p, owner, 0, 10))) | |
171 | return 0; | |
172 | rrset[rrsetidx] = p; | |
173 | GETSHORT(qtype, p); | |
174 | GETSHORT(qclass, p); | |
175 | p += 4; /* skip ttl */ | |
176 | GETSHORT(rdlen, p); | |
177 | if (res == 1 && qtype == sigtype && qclass == sigclass) | |
178 | { | |
179 | ++rrsetidx; | |
382e38f4 GB |
180 | if (rrsetidx == countof(rrset)) |
181 | { | |
182 | /* Internal buffer too small */ | |
183 | printf("internal buffer too small for this RRset\n"); | |
184 | return 0; | |
185 | } | |
e292e93d GB |
186 | } |
187 | p += rdlen; | |
188 | } | |
189 | ||
190 | /* Sort RRset records in canonical order. */ | |
191 | qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order); | |
192 | ||
50a96b62 GB |
193 | /* Skip through the signer name; we don't extract it right now because |
194 | we don't want to overwrite the single daemon->namebuff which contains | |
195 | the owner name. We'll get to this later. */ | |
196 | if (!(p = skip_name(sig, header, pktlen, 0))) | |
e292e93d | 197 | return 0; |
50a96b62 GB |
198 | signer_name_rdlen = p - sig; |
199 | sig = p; sigrdlen -= signer_name_rdlen; | |
13e435eb | 200 | |
e292e93d GB |
201 | /* Now initialize the signature verification algorithm and process the whole |
202 | RRset */ | |
adca3e9c GB |
203 | VerifyAlgCtx *alg = verifyalg_alloc(sigalg); |
204 | if (!alg) | |
205 | return 0; | |
206 | if (!alg->vtbl->set_signature(alg, sig, sigrdlen)) | |
e292e93d GB |
207 | return 0; |
208 | ||
4b0eecbb GB |
209 | sigtype = htons(sigtype); |
210 | sigclass = htons(sigclass); | |
211 | sigttl = htonl(sigttl); | |
212 | ||
adca3e9c | 213 | alg->vtbl->begin_data(alg); |
13e435eb | 214 | alg->vtbl->add_data(alg, sigrdata, 18+signer_name_rdlen); |
e292e93d GB |
215 | for (i = 0; i < rrsetidx; ++i) |
216 | { | |
217 | int rdlen; | |
13e435eb GB |
218 | p = (unsigned char*)(rrset[i]); |
219 | ||
220 | verifyalg_add_data_domain(alg, owner); | |
adca3e9c GB |
221 | alg->vtbl->add_data(alg, &sigtype, 2); |
222 | alg->vtbl->add_data(alg, &sigclass, 2); | |
223 | alg->vtbl->add_data(alg, &sigttl, 4); | |
e292e93d | 224 | |
e292e93d GB |
225 | p += 8; |
226 | GETSHORT(rdlen, p); | |
382e38f4 GB |
227 | /* TODO: instead of a direct add_data(), we must call a RRtype-specific |
228 | function, that extract and canonicalizes domain names within RDATA. */ | |
adca3e9c | 229 | alg->vtbl->add_data(alg, p-2, rdlen+2); |
e292e93d | 230 | } |
adca3e9c | 231 | alg->vtbl->end_data(alg); |
e292e93d | 232 | |
50a96b62 GB |
233 | /* We don't need the owner name anymore; now extract the signer name */ |
234 | if (!extract_name_no_compression(sigrdata+18, signer_name_rdlen, signer_name)) | |
235 | return 0; | |
236 | ||
adca3e9c GB |
237 | out->alg = alg; |
238 | out->keytag = keytag; | |
239 | out->signer_name = signer_name; | |
240 | return 1; | |
241 | } | |
242 | ||
243 | static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_dnskey) | |
244 | { | |
a7338645 | 245 | /* FIXME: keydata is non-contiguous */ |
708bcd2d | 246 | return val->alg->vtbl->verify(val->alg, crec_dnskey->addr.key.keydata, crec_dnskey->uid); |
adca3e9c GB |
247 | } |
248 | ||
249 | static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, | |
250 | unsigned char *reply, int count, char *owner, | |
251 | int sigclass, int sigrdlen, unsigned char *sig) | |
252 | { | |
253 | PendingRRSIGValidation val; | |
254 | ||
255 | /* Initiate the RRSIG validation process. The pending state is returned into val. */ | |
256 | if (!begin_rrsig_validation(header, pktlen, reply, count, owner, sigclass, sigrdlen, sig, &val)) | |
257 | return; | |
258 | ||
259 | printf("RRSIG: querying cache for DNSKEY %s (keytag: %d)\n", val.signer_name, val.keytag); | |
20bccd49 GB |
260 | |
261 | /* Look in the cache for *all* the DNSKEYs with matching signer_name and keytag */ | |
adca3e9c GB |
262 | char onekey = 0; |
263 | struct crec *crecp = NULL; | |
a55ce08c | 264 | while ((crecp = cache_find_by_name(crecp, val.signer_name, time(0), F_DNSKEY))) /* TODO: time(0) */ |
adca3e9c GB |
265 | { |
266 | onekey = 1; | |
267 | ||
20bccd49 GB |
268 | if (crecp->addr.key.keytag == val.keytag |
269 | && crecp->addr.key.algo == verifyalg_algonum(val.alg)) | |
270 | { | |
271 | printf("RRSIG: found DNSKEY %d in cache, attempting validation\n", val.keytag); | |
adca3e9c | 272 | |
20bccd49 GB |
273 | if (end_rrsig_validation(&val, crecp)) |
274 | printf("Validation OK\n"); | |
275 | else | |
276 | printf("Validation FAILED\n"); | |
277 | } | |
adca3e9c GB |
278 | } |
279 | ||
280 | if (!onekey) | |
281 | { | |
ccca70cb | 282 | printf("DNSKEY not found, need to fetch it\n"); |
adca3e9c GB |
283 | /* TODO: store PendingRRSIGValidation in routing table, |
284 | fetch key (and make it go through dnssec_parskey), then complete validation. */ | |
285 | } | |
e292e93d GB |
286 | } |
287 | ||
3471f181 GB |
288 | /* Compute keytag (checksum to quickly index a key). See RFC4034 */ |
289 | static int dnskey_keytag(unsigned char *rdata, int rdlen) | |
290 | { | |
291 | unsigned long ac; | |
292 | int i; | |
293 | ||
294 | ac = 0; | |
295 | for (i = 0; i < rdlen; ++i) | |
296 | ac += (i & 1) ? rdata[i] : rdata[i] << 8; | |
297 | ac += (ac >> 16) & 0xFFFF; | |
298 | return ac & 0xFFFF; | |
299 | } | |
300 | ||
301 | int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl, | |
302 | int rdlen, unsigned char *rdata) | |
303 | { | |
304 | int flags, proto, alg; | |
305 | struct keydata *key; struct crec *crecp; | |
3471f181 GB |
306 | unsigned char *ordata = rdata; int ordlen = rdlen; |
307 | ||
308 | CHECKED_GETSHORT(flags, rdata, rdlen); | |
309 | CHECKED_GETCHAR(proto, rdata, rdlen); | |
310 | CHECKED_GETCHAR(alg, rdata, rdlen); | |
311 | ||
312 | if (proto != 3) | |
313 | return 0; | |
0d829ebc GB |
314 | /* Skip non-signing keys (as specified in RFC4034 */ |
315 | if (!(flags & 0x100)) | |
316 | return 0; | |
3471f181 | 317 | |
a55ce08c | 318 | key = keydata_alloc((char*)rdata, rdlen); |
3471f181 | 319 | |
3471f181 GB |
320 | /* TODO: time(0) is correct here? */ |
321 | crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY); | |
322 | if (crecp) | |
323 | { | |
324 | /* TODO: improve union not to name "uid" this field */ | |
325 | crecp->uid = rdlen; | |
326 | crecp->addr.key.keydata = key; | |
327 | crecp->addr.key.algo = alg; | |
328 | crecp->addr.key.keytag = dnskey_keytag(ordata, ordlen); | |
329 | printf("DNSKEY: storing key for %s (keytag: %d)\n", owner, crecp->addr.key.keytag); | |
330 | } | |
331 | else | |
332 | { | |
333 | keydata_free(key); | |
334 | /* TODO: if insertion really might fail, verify we don't depend on cache | |
335 | insertion success for validation workflow correctness */ | |
336 | printf("DNSKEY: cache insertion failure\n"); | |
337 | return 0; | |
338 | } | |
3471f181 GB |
339 | return 1; |
340 | } | |
341 | ||
342 | int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl, | |
343 | int rdlen, unsigned char *rdata) | |
344 | { | |
345 | return 0; | |
346 | } | |
e292e93d GB |
347 | |
348 | int dnssec_validate(struct dns_header *header, size_t pktlen) | |
349 | { | |
350 | unsigned char *p, *reply; | |
351 | char *owner = daemon->namebuff; | |
352 | int i, qtype, qclass, rdlen; | |
353 | unsigned long ttl; | |
354 | ||
355 | if (header->ancount == 0) | |
356 | return 0; | |
357 | if (!(reply = p = skip_questions(header, pktlen))) | |
358 | return 0; | |
d0edff7d GB |
359 | |
360 | /* First, process DNSKEY/DS records and add them to the cache. */ | |
361 | cache_start_insert(); | |
e292e93d GB |
362 | for (i = 0; i < ntohs(header->ancount); i++) |
363 | { | |
364 | if (!extract_name(header, pktlen, &p, owner, 1, 10)) | |
365 | return 0; | |
366 | GETSHORT(qtype, p); | |
367 | GETSHORT(qclass, p); | |
368 | GETLONG(ttl, p); | |
369 | GETSHORT(rdlen, p); | |
3471f181 GB |
370 | if (qtype == T_DS) |
371 | { | |
372 | printf("DS found\n"); | |
373 | dnssec_parseds(header, pktlen, owner, ttl, rdlen, p); | |
374 | } | |
375 | else if (qtype == T_DNSKEY) | |
376 | { | |
377 | printf("DNSKEY found\n"); | |
47f99dd2 | 378 | dnssec_parsekey(header, pktlen, owner, ttl, rdlen, p); |
3471f181 | 379 | } |
4137b84e GB |
380 | p += rdlen; |
381 | } | |
d0edff7d | 382 | cache_end_insert(); |
4137b84e | 383 | |
d0edff7d | 384 | /* After we have cached DNSKEY/DS records, start looking for RRSIGs. |
4137b84e GB |
385 | We want to do this in a separate step because we want the cache |
386 | to be already populated with DNSKEYs before parsing signatures. */ | |
387 | p = reply; | |
388 | for (i = 0; i < ntohs(header->ancount); i++) | |
389 | { | |
390 | if (!extract_name(header, pktlen, &p, owner, 1, 10)) | |
391 | return 0; | |
392 | GETSHORT(qtype, p); | |
393 | GETSHORT(qclass, p); | |
394 | GETLONG(ttl, p); | |
395 | GETSHORT(rdlen, p); | |
396 | if (qtype == T_RRSIG) | |
e292e93d | 397 | { |
00b963ab | 398 | printf("RRSIG found (owner: %s)\n", owner); |
e292e93d | 399 | /* TODO: missing logic. We should only validate RRSIGs for which we |
4137b84e | 400 | have a valid DNSKEY that is referenced by a DS record upstream. |
e292e93d GB |
401 | There is a memory vs CPU conflict here; should we validate everything |
402 | to save memory and thus waste CPU, or better first acquire all information | |
403 | (wasting memory) and then doing the minimum CPU computations required? */ | |
adca3e9c | 404 | dnssec_parserrsig(header, pktlen, reply, ntohs(header->ancount), owner, qclass, rdlen, p); |
4137b84e | 405 | } |
e292e93d GB |
406 | p += rdlen; |
407 | } | |
4137b84e | 408 | |
e292e93d GB |
409 | return 1; |
410 | } |