blob: 2991fe23409a1227e8289ffa317b8675d8e0897a [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
6#define SERIAL_UNDEF -100
7#define SERIAL_EQ 0
8#define SERIAL_LT -1
9#define SERIAL_GT 1
10
Giovanni Bajoe292e932012-04-22 14:32:02 +020011/* Implement RFC1982 wrapped compare for 32-bit numbers */
12static 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. */
29static 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++;
Giovanni Bajo6445c8e2012-04-24 02:02:29 +020037 while (count-- > 0 && rr < end)
Giovanni Bajoe292e932012-04-22 14:32:02 +020038 {
39 *buf = *rr++;
Giovanni Bajob98f7712012-04-22 15:59:27 +020040 if (!isascii(*buf) || iscntrl(*buf) || *buf == '.')
41 return 0;
Giovanni Bajoe292e932012-04-22 14:32:02 +020042 if (*buf >= 'A' && *buf <= 'Z')
43 *buf += 'a' - 'A';
44 buf++;
Giovanni Bajo79333a22012-04-28 01:03:10 +020045 }
Giovanni Bajoe292e932012-04-22 14:32:02 +020046 *buf++ = '.';
47 }
Giovanni Bajo2ef843d2012-04-25 17:48:40 +020048 // Remove trailing dot (if any)
49 if (rr != start)
50 *(--buf) = 0;
Giovanni Bajoe292e932012-04-22 14:32:02 +020051 if (rr == end)
52 return 0;
Giovanni Bajo79333a22012-04-28 01:03:10 +020053 // Trailing \0 in source data must be consumed
54 return rr-start+1;
Giovanni Bajoe292e932012-04-22 14:32:02 +020055}
56
57/* Check whether today/now is between date_start and date_end */
58static 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. */
76static int rrset_canonical_order(const void *r1, const void *r2)
77{
78 int r1len, r2len, res;
Giovanni Bajo0decc862012-04-24 02:23:11 +020079 const unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2;
Giovanni Bajoe292e932012-04-22 14:32:02 +020080
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
Giovanni Bajoadca3e92012-04-25 17:46:53 +020099typedef struct PendingRRSIGValidation
100{
101 VerifyAlgCtx *alg;
102 char *signer_name;
103 int keytag;
104} PendingRRSIGValidation;
105
Giovanni Bajo13e435e2012-04-27 03:19:40 +0200106/* 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. */
111static 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
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200127static 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)
Giovanni Bajoe292e932012-04-22 14:32:02 +0200131{
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;
Giovanni Bajo13e435e2012-04-27 03:19:40 +0200138 int signer_name_rdlen;
Giovanni Bajoe292e932012-04-22 14:32:02 +0200139 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
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200154 if (!verifyalg_supported(sigalg))
Giovanni Bajoe292e932012-04-22 14:32:02 +0200155 {
156 printf("RRSIG algorithm not supported: %d\n", sigalg);
157 return 0;
158 }
159
Giovanni Bajod31d0572012-04-24 02:02:55 +0200160 if (!check_date_range(date_start, date_end))
Giovanni Bajoe292e932012-04-22 14:32:02 +0200161 {
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;
Giovanni Bajo382e38f2012-04-24 01:46:47 +0200180 if (rrsetidx == countof(rrset))
181 {
182 /* Internal buffer too small */
183 printf("internal buffer too small for this RRset\n");
184 return 0;
185 }
Giovanni Bajoe292e932012-04-22 14:32:02 +0200186 }
187 p += rdlen;
188 }
189
190 /* Sort RRset records in canonical order. */
191 qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
192
Giovanni Bajo50a96b62012-04-28 01:04:56 +0200193 /* 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)))
Giovanni Bajoe292e932012-04-22 14:32:02 +0200197 return 0;
Giovanni Bajo50a96b62012-04-28 01:04:56 +0200198 signer_name_rdlen = p - sig;
199 sig = p; sigrdlen -= signer_name_rdlen;
Giovanni Bajo13e435e2012-04-27 03:19:40 +0200200
Giovanni Bajoe292e932012-04-22 14:32:02 +0200201 /* Now initialize the signature verification algorithm and process the whole
202 RRset */
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200203 VerifyAlgCtx *alg = verifyalg_alloc(sigalg);
204 if (!alg)
205 return 0;
206 if (!alg->vtbl->set_signature(alg, sig, sigrdlen))
Giovanni Bajoe292e932012-04-22 14:32:02 +0200207 return 0;
208
Giovanni Bajo4b0eecb2012-04-27 03:18:52 +0200209 sigtype = htons(sigtype);
210 sigclass = htons(sigclass);
211 sigttl = htonl(sigttl);
212
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200213 alg->vtbl->begin_data(alg);
Giovanni Bajo13e435e2012-04-27 03:19:40 +0200214 alg->vtbl->add_data(alg, sigrdata, 18+signer_name_rdlen);
Giovanni Bajoe292e932012-04-22 14:32:02 +0200215 for (i = 0; i < rrsetidx; ++i)
216 {
217 int rdlen;
Giovanni Bajo13e435e2012-04-27 03:19:40 +0200218 p = (unsigned char*)(rrset[i]);
219
220 verifyalg_add_data_domain(alg, owner);
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200221 alg->vtbl->add_data(alg, &sigtype, 2);
222 alg->vtbl->add_data(alg, &sigclass, 2);
223 alg->vtbl->add_data(alg, &sigttl, 4);
Giovanni Bajoe292e932012-04-22 14:32:02 +0200224
Giovanni Bajoe292e932012-04-22 14:32:02 +0200225 p += 8;
226 GETSHORT(rdlen, p);
Giovanni Bajo382e38f2012-04-24 01:46:47 +0200227 /* TODO: instead of a direct add_data(), we must call a RRtype-specific
228 function, that extract and canonicalizes domain names within RDATA. */
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200229 alg->vtbl->add_data(alg, p-2, rdlen+2);
Giovanni Bajoe292e932012-04-22 14:32:02 +0200230 }
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200231 alg->vtbl->end_data(alg);
Giovanni Bajoe292e932012-04-22 14:32:02 +0200232
Giovanni Bajo50a96b62012-04-28 01:04:56 +0200233 /* 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
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200237 out->alg = alg;
238 out->keytag = keytag;
239 out->signer_name = signer_name;
240 return 1;
241}
242
243static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_dnskey)
244{
Giovanni Bajoa7338642012-04-26 14:37:22 +0200245 /* FIXME: keydata is non-contiguous */
Giovanni Bajo708bcd22012-04-25 20:19:07 +0200246 return val->alg->vtbl->verify(val->alg, crec_dnskey->addr.key.keydata, crec_dnskey->uid);
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200247}
248
249static 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);
Giovanni Bajo20bccd42012-04-25 20:22:16 +0200260
261 /* Look in the cache for *all* the DNSKEYs with matching signer_name and keytag */
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200262 char onekey = 0;
263 struct crec *crecp = NULL;
264 while (crecp = cache_find_by_name(crecp, val.signer_name, time(0), F_DNSKEY)) /* TODO: time(0) */
265 {
266 onekey = 1;
267
Giovanni Bajo20bccd42012-04-25 20:22:16 +0200268 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);
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200272
Giovanni Bajo20bccd42012-04-25 20:22:16 +0200273 if (end_rrsig_validation(&val, crecp))
274 printf("Validation OK\n");
275 else
276 printf("Validation FAILED\n");
277 }
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200278 }
279
280 if (!onekey)
281 {
Giovanni Bajoccca70c2012-04-25 20:15:35 +0200282 printf("DNSKEY not found, need to fetch it\n");
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200283 /* TODO: store PendingRRSIGValidation in routing table,
284 fetch key (and make it go through dnssec_parskey), then complete validation. */
285 }
Giovanni Bajoe292e932012-04-22 14:32:02 +0200286}
287
Giovanni Bajo3471f182012-04-25 17:49:16 +0200288/* Compute keytag (checksum to quickly index a key). See RFC4034 */
289static 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
301int 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;
306 int explen, keytag;
307 unsigned long exp;
308 unsigned char *ordata = rdata; int ordlen = rdlen;
309
310 CHECKED_GETSHORT(flags, rdata, rdlen);
311 CHECKED_GETCHAR(proto, rdata, rdlen);
312 CHECKED_GETCHAR(alg, rdata, rdlen);
313
314 if (proto != 3)
315 return 0;
Giovanni Bajo0d829eb2012-04-25 18:17:50 +0200316 /* Skip non-signing keys (as specified in RFC4034 */
317 if (!(flags & 0x100))
318 return 0;
Giovanni Bajo3471f182012-04-25 17:49:16 +0200319
Giovanni Bajo262ac852012-04-27 03:13:34 +0200320 key = keydata_alloc(rdata, rdlen);
Giovanni Bajo3471f182012-04-25 17:49:16 +0200321
Giovanni Bajo3471f182012-04-25 17:49:16 +0200322 /* TODO: time(0) is correct here? */
323 crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY);
324 if (crecp)
325 {
326 /* TODO: improve union not to name "uid" this field */
327 crecp->uid = rdlen;
328 crecp->addr.key.keydata = key;
329 crecp->addr.key.algo = alg;
330 crecp->addr.key.keytag = dnskey_keytag(ordata, ordlen);
331 printf("DNSKEY: storing key for %s (keytag: %d)\n", owner, crecp->addr.key.keytag);
332 }
333 else
334 {
335 keydata_free(key);
336 /* TODO: if insertion really might fail, verify we don't depend on cache
337 insertion success for validation workflow correctness */
338 printf("DNSKEY: cache insertion failure\n");
339 return 0;
340 }
Giovanni Bajo3471f182012-04-25 17:49:16 +0200341 return 1;
342}
343
344int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl,
345 int rdlen, unsigned char *rdata)
346{
347 return 0;
348}
Giovanni Bajoe292e932012-04-22 14:32:02 +0200349
350int dnssec_validate(struct dns_header *header, size_t pktlen)
351{
352 unsigned char *p, *reply;
353 char *owner = daemon->namebuff;
354 int i, qtype, qclass, rdlen;
355 unsigned long ttl;
356
357 if (header->ancount == 0)
358 return 0;
359 if (!(reply = p = skip_questions(header, pktlen)))
360 return 0;
Giovanni Bajod0edff72012-04-25 20:16:22 +0200361
362 /* First, process DNSKEY/DS records and add them to the cache. */
363 cache_start_insert();
Giovanni Bajoe292e932012-04-22 14:32:02 +0200364 for (i = 0; i < ntohs(header->ancount); i++)
365 {
366 if (!extract_name(header, pktlen, &p, owner, 1, 10))
367 return 0;
368 GETSHORT(qtype, p);
369 GETSHORT(qclass, p);
370 GETLONG(ttl, p);
371 GETSHORT(rdlen, p);
Giovanni Bajo3471f182012-04-25 17:49:16 +0200372 if (qtype == T_DS)
373 {
374 printf("DS found\n");
375 dnssec_parseds(header, pktlen, owner, ttl, rdlen, p);
376 }
377 else if (qtype == T_DNSKEY)
378 {
379 printf("DNSKEY found\n");
Giovanni Bajo47f99dd2012-04-25 18:03:52 +0200380 dnssec_parsekey(header, pktlen, owner, ttl, rdlen, p);
Giovanni Bajo3471f182012-04-25 17:49:16 +0200381 }
Giovanni Bajo4137b842012-04-25 18:13:41 +0200382 p += rdlen;
383 }
Giovanni Bajod0edff72012-04-25 20:16:22 +0200384 cache_end_insert();
Giovanni Bajo4137b842012-04-25 18:13:41 +0200385
Giovanni Bajod0edff72012-04-25 20:16:22 +0200386 /* After we have cached DNSKEY/DS records, start looking for RRSIGs.
Giovanni Bajo4137b842012-04-25 18:13:41 +0200387 We want to do this in a separate step because we want the cache
388 to be already populated with DNSKEYs before parsing signatures. */
389 p = reply;
390 for (i = 0; i < ntohs(header->ancount); i++)
391 {
392 if (!extract_name(header, pktlen, &p, owner, 1, 10))
393 return 0;
394 GETSHORT(qtype, p);
395 GETSHORT(qclass, p);
396 GETLONG(ttl, p);
397 GETSHORT(rdlen, p);
398 if (qtype == T_RRSIG)
Giovanni Bajoe292e932012-04-22 14:32:02 +0200399 {
Giovanni Bajo00b963a2012-04-28 01:03:22 +0200400 printf("RRSIG found (owner: %s)\n", owner);
Giovanni Bajoe292e932012-04-22 14:32:02 +0200401 /* TODO: missing logic. We should only validate RRSIGs for which we
Giovanni Bajo4137b842012-04-25 18:13:41 +0200402 have a valid DNSKEY that is referenced by a DS record upstream.
Giovanni Bajoe292e932012-04-22 14:32:02 +0200403 There is a memory vs CPU conflict here; should we validate everything
404 to save memory and thus waste CPU, or better first acquire all information
405 (wasting memory) and then doing the minimum CPU computations required? */
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200406 dnssec_parserrsig(header, pktlen, reply, ntohs(header->ancount), owner, qclass, rdlen, p);
Giovanni Bajo4137b842012-04-25 18:13:41 +0200407 }
Giovanni Bajoe292e932012-04-22 14:32:02 +0200408 p += rdlen;
409 }
Giovanni Bajo4137b842012-04-25 18:13:41 +0200410
Giovanni Bajoe292e932012-04-22 14:32:02 +0200411 return 1;
412}