blob: 6b73e3e1e56568207364a016d89c6ccc0c62ca68 [file] [log] [blame]
Giovanni Bajoe292e932012-04-22 14:32:02 +02001
2#include "dnsmasq.h"
Giovanni Bajod322de02012-04-23 00:30:00 +02003#include "dnssec-crypto.h"
Giovanni Bajoe292e932012-04-22 14:32:02 +02004#include <assert.h>
5
Giovanni Bajoce2a0f52012-04-25 17:47:56 +02006#define CHECKED_GETCHAR(var, ptr, len) do { \
7 if ((len) < 1) return 0; \
8 var = *ptr++; \
9 (len) -= 1; \
10 } while (0)
11
12#define CHECKED_GETSHORT(var, ptr, len) do { \
13 if ((len) < 2) return 0; \
14 GETSHORT(var, ptr); \
15 (len) -= 2; \
16 } while (0)
17
18#define CHECKED_GETLONG(var, ptr, len) do { \
19 if ((len) < 4) return 0; \
20 GETLONG(var, ptr); \
21 (len) -= 4; \
22 } while (0)
23
24
Giovanni Bajoe292e932012-04-22 14:32:02 +020025#define SERIAL_UNDEF -100
26#define SERIAL_EQ 0
27#define SERIAL_LT -1
28#define SERIAL_GT 1
29
Giovanni Bajoe292e932012-04-22 14:32:02 +020030/* Implement RFC1982 wrapped compare for 32-bit numbers */
31static int serial_compare_32(unsigned long s1, unsigned long s2)
32{
33 if (s1 == s2)
34 return SERIAL_EQ;
35
36 if ((s1 < s2 && (s2 - s1) < (1UL<<31)) ||
37 (s1 > s2 && (s1 - s2) > (1UL<<31)))
38 return SERIAL_LT;
39 if ((s1 < s2 && (s2 - s1) > (1UL<<31)) ||
40 (s1 > s2 && (s1 - s2) < (1UL<<31)))
41 return SERIAL_GT;
42 return SERIAL_UNDEF;
43}
44
45/* Extract a DNS name from wire format, without handling compression. This is
46 faster than extract_name() and does not require access to the full dns
47 packet. */
48static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf)
49{
50 unsigned char *start=rr, *end = rr+maxlen;
51 int count;
52
53 while (rr < end && *rr != 0)
54 {
55 count = *rr++;
Giovanni Bajo6445c8e2012-04-24 02:02:29 +020056 while (count-- > 0 && rr < end)
Giovanni Bajoe292e932012-04-22 14:32:02 +020057 {
58 *buf = *rr++;
Giovanni Bajob98f7712012-04-22 15:59:27 +020059 if (!isascii(*buf) || iscntrl(*buf) || *buf == '.')
60 return 0;
Giovanni Bajoe292e932012-04-22 14:32:02 +020061 if (*buf >= 'A' && *buf <= 'Z')
62 *buf += 'a' - 'A';
63 buf++;
64 }
65 *buf++ = '.';
66 }
Giovanni Bajo2ef843d2012-04-25 17:48:40 +020067 // Remove trailing dot (if any)
68 if (rr != start)
69 *(--buf) = 0;
Giovanni Bajoc7a93f62012-04-22 15:53:52 +020070 rr++;
Giovanni Bajoe292e932012-04-22 14:32:02 +020071 if (rr == end)
72 return 0;
73 return rr-start;
74}
75
76/* Check whether today/now is between date_start and date_end */
77static int check_date_range(unsigned long date_start, unsigned long date_end)
78{
79 /* TODO: double-check that time(0) is the correct time we are looking for */
80 /* TODO: dnssec requires correct timing; implement SNTP in dnsmasq? */
81 unsigned long curtime = time(0);
82
83 /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
84 if (serial_compare_32(curtime, date_start) != SERIAL_GT)
85 return 0;
86 if (serial_compare_32(curtime, date_end) != SERIAL_LT)
87 return 0;
88 return 1;
89}
90
91/* Sort RRs within a RRset in canonical order, according to RFC4034, ยง6.3
92 Notice that the RRDATA sections have been already normalized, so a memcpy
93 is sufficient.
94 NOTE: r1/r2 point immediately after the owner name. */
95static int rrset_canonical_order(const void *r1, const void *r2)
96{
97 int r1len, r2len, res;
Giovanni Bajo0decc862012-04-24 02:23:11 +020098 const unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2;
Giovanni Bajoe292e932012-04-22 14:32:02 +020099
100 pr1 += 8; pr2 += 8;
101 GETSHORT(r1len, pr1); GETSHORT(r2len, pr2);
102
103 /* Lexicographically compare RDATA (thus, if equal, smaller length wins) */
104 res = memcmp(pr1, pr2, MIN(r1len, r2len));
105 if (res == 0)
106 {
107 if (r1len < r2len)
108 return -1;
109 else
110 /* NOTE: RFC2181 says that an RRset is not allowed to contain duplicate
111 records. If it happens, it is a protocol error and anything goes. */
112 return 1;
113 }
114
115 return res;
116}
117
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200118typedef struct PendingRRSIGValidation
119{
120 VerifyAlgCtx *alg;
121 char *signer_name;
122 int keytag;
123} PendingRRSIGValidation;
124
125static int begin_rrsig_validation(struct dns_header *header, size_t pktlen,
126 unsigned char *reply, int count, char *owner,
127 int sigclass, int sigrdlen, unsigned char *sig,
128 PendingRRSIGValidation *out)
Giovanni Bajoe292e932012-04-22 14:32:02 +0200129{
130 int i, res;
131 int sigtype, sigalg, siglbl;
132 unsigned char *sigrdata = sig;
133 unsigned long sigttl, date_end, date_start;
134 unsigned char* p = reply;
135 char* signer_name = daemon->namebuff;
136 int keytag;
137 void *rrset[16]; /* TODO: max RRset size? */
138 int rrsetidx = 0;
139
140 if (sigrdlen < 18)
141 return 0;
142 GETSHORT(sigtype, sig);
143 sigalg = *sig++;
144 siglbl = *sig++;
145 GETLONG(sigttl, sig);
146 GETLONG(date_end, sig);
147 GETLONG(date_start, sig);
148 GETSHORT(keytag, sig);
149 sigrdlen -= 18;
150
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200151 if (!verifyalg_supported(sigalg))
Giovanni Bajoe292e932012-04-22 14:32:02 +0200152 {
153 printf("RRSIG algorithm not supported: %d\n", sigalg);
154 return 0;
155 }
156
Giovanni Bajod31d0572012-04-24 02:02:55 +0200157 if (!check_date_range(date_start, date_end))
Giovanni Bajoe292e932012-04-22 14:32:02 +0200158 {
159 printf("RRSIG outside date range\n");
160 return 0;
161 }
162
163 /* Iterate within the answer and find the RRsets matching the current RRsig */
164 for (i = 0; i < count; ++i)
165 {
166 int qtype, qclass, rdlen;
167 if (!(res = extract_name(header, pktlen, &p, owner, 0, 10)))
168 return 0;
169 rrset[rrsetidx] = p;
170 GETSHORT(qtype, p);
171 GETSHORT(qclass, p);
172 p += 4; /* skip ttl */
173 GETSHORT(rdlen, p);
174 if (res == 1 && qtype == sigtype && qclass == sigclass)
175 {
176 ++rrsetidx;
Giovanni Bajo382e38f2012-04-24 01:46:47 +0200177 if (rrsetidx == countof(rrset))
178 {
179 /* Internal buffer too small */
180 printf("internal buffer too small for this RRset\n");
181 return 0;
182 }
Giovanni Bajoe292e932012-04-22 14:32:02 +0200183 }
184 p += rdlen;
185 }
186
187 /* Sort RRset records in canonical order. */
188 qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
189
190 /* Extract the signer name (we need to query DNSKEY of this name) */
191 if (!(res = extract_name_no_compression(sig, sigrdlen, signer_name)))
192 return 0;
193 sig += res; sigrdlen -= res;
194
195 /* Now initialize the signature verification algorithm and process the whole
196 RRset */
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200197 VerifyAlgCtx *alg = verifyalg_alloc(sigalg);
198 if (!alg)
199 return 0;
200 if (!alg->vtbl->set_signature(alg, sig, sigrdlen))
Giovanni Bajoe292e932012-04-22 14:32:02 +0200201 return 0;
202
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200203 alg->vtbl->begin_data(alg);
204 alg->vtbl->add_data(alg, sigrdata, 18);
Giovanni Bajo2ef843d2012-04-25 17:48:40 +0200205 alg->vtbl->add_data(alg, signer_name, strlen(signer_name));
Giovanni Bajoe292e932012-04-22 14:32:02 +0200206 for (i = 0; i < rrsetidx; ++i)
207 {
208 int rdlen;
209
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200210 alg->vtbl->add_data(alg, owner, strlen(owner));
211 alg->vtbl->add_data(alg, &sigtype, 2);
212 alg->vtbl->add_data(alg, &sigclass, 2);
213 alg->vtbl->add_data(alg, &sigttl, 4);
Giovanni Bajoe292e932012-04-22 14:32:02 +0200214
215 p = (unsigned char*)(rrset[i]);
216 p += 8;
217 GETSHORT(rdlen, p);
Giovanni Bajo382e38f2012-04-24 01:46:47 +0200218 /* TODO: instead of a direct add_data(), we must call a RRtype-specific
219 function, that extract and canonicalizes domain names within RDATA. */
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200220 alg->vtbl->add_data(alg, p-2, rdlen+2);
Giovanni Bajoe292e932012-04-22 14:32:02 +0200221 }
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200222 alg->vtbl->end_data(alg);
Giovanni Bajoe292e932012-04-22 14:32:02 +0200223
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200224 out->alg = alg;
225 out->keytag = keytag;
226 out->signer_name = signer_name;
227 return 1;
228}
229
230static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_dnskey)
231{
232
233}
234
235static void dnssec_parserrsig(struct dns_header *header, size_t pktlen,
236 unsigned char *reply, int count, char *owner,
237 int sigclass, int sigrdlen, unsigned char *sig)
238{
239 PendingRRSIGValidation val;
240
241 /* Initiate the RRSIG validation process. The pending state is returned into val. */
242 if (!begin_rrsig_validation(header, pktlen, reply, count, owner, sigclass, sigrdlen, sig, &val))
243 return;
244
245 printf("RRSIG: querying cache for DNSKEY %s (keytag: %d)\n", val.signer_name, val.keytag);
246 /* Look in the cache for all the DNSKEYs with matching signer_name and keytag */
247 char onekey = 0;
248 struct crec *crecp = NULL;
249 while (crecp = cache_find_by_name(crecp, val.signer_name, time(0), F_DNSKEY)) /* TODO: time(0) */
250 {
251 onekey = 1;
252
253 if (crecp->addr.key.keytag != val.keytag)
254 continue;
Giovanni Bajoe6c2a672012-04-25 18:13:20 +0200255 if (crecp->addr.key.algo != verifyalg_algonum(val.alg))
256 continue;
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200257
258 printf("RRSIG: found DNSKEY %d in cache, attempting validation\n", val.keytag);
259
260 if (end_rrsig_validation(&val, crecp))
261 printf("Validation OK\n");
262 else
263 printf("Validation FAILED\n");
264 }
265
266 if (!onekey)
267 {
268 printf("DNSKEY not found, need to fetch it");
269 /* TODO: store PendingRRSIGValidation in routing table,
270 fetch key (and make it go through dnssec_parskey), then complete validation. */
271 }
Giovanni Bajoe292e932012-04-22 14:32:02 +0200272}
273
Giovanni Bajo3471f182012-04-25 17:49:16 +0200274/* Compute keytag (checksum to quickly index a key). See RFC4034 */
275static int dnskey_keytag(unsigned char *rdata, int rdlen)
276{
277 unsigned long ac;
278 int i;
279
280 ac = 0;
281 for (i = 0; i < rdlen; ++i)
282 ac += (i & 1) ? rdata[i] : rdata[i] << 8;
283 ac += (ac >> 16) & 0xFFFF;
284 return ac & 0xFFFF;
285}
286
287int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl,
288 int rdlen, unsigned char *rdata)
289{
290 int flags, proto, alg;
291 struct keydata *key; struct crec *crecp;
292 int explen, keytag;
293 unsigned long exp;
294 unsigned char *ordata = rdata; int ordlen = rdlen;
295
296 CHECKED_GETSHORT(flags, rdata, rdlen);
297 CHECKED_GETCHAR(proto, rdata, rdlen);
298 CHECKED_GETCHAR(alg, rdata, rdlen);
299
300 if (proto != 3)
301 return 0;
Giovanni Bajo0d829eb2012-04-25 18:17:50 +0200302 /* Skip non-signing keys (as specified in RFC4034 */
303 if (!(flags & 0x100))
304 return 0;
Giovanni Bajo3471f182012-04-25 17:49:16 +0200305
306 switch (alg)
307 {
308 case 5: /* RSASHA1 */
309 CHECKED_GETCHAR(explen, rdata, rdlen);
310 if (explen == 0)
311 {
312 printf("DNSKEY: RSASHA1: Unsupported huge exponents\n");
313 return 0;
314 }
315
316 if (rdlen < explen)
317 return 0;
318 printf("Alloc'ing: %d bytes\n", rdlen);
319 key = keydata_alloc(rdata, rdlen);
320 printf("Done\n");
321 break;
322
323 default:
324 printf("DNSKEY: Unsupported algorithm: %d\n", alg);
325 return 0;
326 }
327
328 cache_start_insert();
329 /* TODO: time(0) is correct here? */
330 crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY);
331 if (crecp)
332 {
333 /* TODO: improve union not to name "uid" this field */
334 crecp->uid = rdlen;
335 crecp->addr.key.keydata = key;
336 crecp->addr.key.algo = alg;
337 crecp->addr.key.keytag = dnskey_keytag(ordata, ordlen);
338 printf("DNSKEY: storing key for %s (keytag: %d)\n", owner, crecp->addr.key.keytag);
339 }
340 else
341 {
342 keydata_free(key);
343 /* TODO: if insertion really might fail, verify we don't depend on cache
344 insertion success for validation workflow correctness */
345 printf("DNSKEY: cache insertion failure\n");
346 return 0;
347 }
348 cache_end_insert();
349 printf("DNSKEY record inserted\n");
350 return 1;
351}
352
353int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl,
354 int rdlen, unsigned char *rdata)
355{
356 return 0;
357}
Giovanni Bajoe292e932012-04-22 14:32:02 +0200358
359int dnssec_validate(struct dns_header *header, size_t pktlen)
360{
361 unsigned char *p, *reply;
362 char *owner = daemon->namebuff;
363 int i, qtype, qclass, rdlen;
364 unsigned long ttl;
365
366 if (header->ancount == 0)
367 return 0;
368 if (!(reply = p = skip_questions(header, pktlen)))
369 return 0;
370 for (i = 0; i < ntohs(header->ancount); i++)
371 {
372 if (!extract_name(header, pktlen, &p, owner, 1, 10))
373 return 0;
374 GETSHORT(qtype, p);
375 GETSHORT(qclass, p);
376 GETLONG(ttl, p);
377 GETSHORT(rdlen, p);
Giovanni Bajo3471f182012-04-25 17:49:16 +0200378 if (qtype == T_DS)
379 {
380 printf("DS found\n");
381 dnssec_parseds(header, pktlen, owner, ttl, rdlen, p);
382 }
383 else if (qtype == T_DNSKEY)
384 {
385 printf("DNSKEY found\n");
Giovanni Bajo47f99dd2012-04-25 18:03:52 +0200386 dnssec_parsekey(header, pktlen, owner, ttl, rdlen, p);
Giovanni Bajo3471f182012-04-25 17:49:16 +0200387 }
Giovanni Bajo4137b842012-04-25 18:13:41 +0200388 p += rdlen;
389 }
390
391 /* After we have parsed DNSKEY/DS records, start looking for RRSIGs.
392 We want to do this in a separate step because we want the cache
393 to be already populated with DNSKEYs before parsing signatures. */
394 p = reply;
395 for (i = 0; i < ntohs(header->ancount); i++)
396 {
397 if (!extract_name(header, pktlen, &p, owner, 1, 10))
398 return 0;
399 GETSHORT(qtype, p);
400 GETSHORT(qclass, p);
401 GETLONG(ttl, p);
402 GETSHORT(rdlen, p);
403 if (qtype == T_RRSIG)
Giovanni Bajoe292e932012-04-22 14:32:02 +0200404 {
Giovanni Bajo4137b842012-04-25 18:13:41 +0200405 printf("RRSIG found\n");
Giovanni Bajoe292e932012-04-22 14:32:02 +0200406 /* TODO: missing logic. We should only validate RRSIGs for which we
Giovanni Bajo4137b842012-04-25 18:13:41 +0200407 have a valid DNSKEY that is referenced by a DS record upstream.
Giovanni Bajoe292e932012-04-22 14:32:02 +0200408 There is a memory vs CPU conflict here; should we validate everything
409 to save memory and thus waste CPU, or better first acquire all information
410 (wasting memory) and then doing the minimum CPU computations required? */
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200411 dnssec_parserrsig(header, pktlen, reply, ntohs(header->ancount), owner, qclass, rdlen, p);
Giovanni Bajo4137b842012-04-25 18:13:41 +0200412 }
Giovanni Bajoe292e932012-04-22 14:32:02 +0200413 p += rdlen;
414 }
Giovanni Bajo4137b842012-04-25 18:13:41 +0200415
Giovanni Bajoe292e932012-04-22 14:32:02 +0200416 return 1;
417}