]> git.ipfire.org Git - people/ms/dnsmasq.git/blame - src/dnssec.c
Silence a few warnings.
[people/ms/dnsmasq.git] / src / dnssec.c
CommitLineData
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 */
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++;
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 */
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;
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
99typedef 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. */
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
adca3e9c
GB
127static 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
243static 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
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);
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 */
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;
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
342int 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
348int 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}