]> git.ipfire.org Git - people/ms/dnsmasq.git/blame - src/dnssec.c
Explicitize the context of verification algorithm.
[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++;
45 }
46 *buf++ = '.';
47 }
c7a93f6e 48 rr++;
e292e93d
GB
49 *buf = 0;
50 if (rr == end)
51 return 0;
52 return rr-start;
53}
54
55/* Check whether today/now is between date_start and date_end */
56static int check_date_range(unsigned long date_start, unsigned long date_end)
57{
58 /* TODO: double-check that time(0) is the correct time we are looking for */
59 /* TODO: dnssec requires correct timing; implement SNTP in dnsmasq? */
60 unsigned long curtime = time(0);
61
62 /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
63 if (serial_compare_32(curtime, date_start) != SERIAL_GT)
64 return 0;
65 if (serial_compare_32(curtime, date_end) != SERIAL_LT)
66 return 0;
67 return 1;
68}
69
70/* Sort RRs within a RRset in canonical order, according to RFC4034, ยง6.3
71 Notice that the RRDATA sections have been already normalized, so a memcpy
72 is sufficient.
73 NOTE: r1/r2 point immediately after the owner name. */
74static int rrset_canonical_order(const void *r1, const void *r2)
75{
76 int r1len, r2len, res;
0decc869 77 const unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2;
e292e93d
GB
78
79 pr1 += 8; pr2 += 8;
80 GETSHORT(r1len, pr1); GETSHORT(r2len, pr2);
81
82 /* Lexicographically compare RDATA (thus, if equal, smaller length wins) */
83 res = memcmp(pr1, pr2, MIN(r1len, r2len));
84 if (res == 0)
85 {
86 if (r1len < r2len)
87 return -1;
88 else
89 /* NOTE: RFC2181 says that an RRset is not allowed to contain duplicate
90 records. If it happens, it is a protocol error and anything goes. */
91 return 1;
92 }
93
94 return res;
95}
96
97static int validate_rrsig(struct dns_header *header, size_t pktlen,
98 unsigned char *reply, int count, char *owner,
99 int sigclass, int sigrdlen, unsigned char *sig)
100{
101 int i, res;
102 int sigtype, sigalg, siglbl;
103 unsigned char *sigrdata = sig;
104 unsigned long sigttl, date_end, date_start;
105 unsigned char* p = reply;
106 char* signer_name = daemon->namebuff;
107 int keytag;
108 void *rrset[16]; /* TODO: max RRset size? */
109 int rrsetidx = 0;
110
111 if (sigrdlen < 18)
112 return 0;
113 GETSHORT(sigtype, sig);
114 sigalg = *sig++;
115 siglbl = *sig++;
116 GETLONG(sigttl, sig);
117 GETLONG(date_end, sig);
118 GETLONG(date_start, sig);
119 GETSHORT(keytag, sig);
120 sigrdlen -= 18;
121
122 if (sigalg >= countof(valgs) || !valgs[sigalg].set_signature)
123 {
124 printf("RRSIG algorithm not supported: %d\n", sigalg);
125 return 0;
126 }
127
d31d057a 128 if (!check_date_range(date_start, date_end))
e292e93d
GB
129 {
130 printf("RRSIG outside date range\n");
131 return 0;
132 }
133
134 /* Iterate within the answer and find the RRsets matching the current RRsig */
135 for (i = 0; i < count; ++i)
136 {
137 int qtype, qclass, rdlen;
138 if (!(res = extract_name(header, pktlen, &p, owner, 0, 10)))
139 return 0;
140 rrset[rrsetidx] = p;
141 GETSHORT(qtype, p);
142 GETSHORT(qclass, p);
143 p += 4; /* skip ttl */
144 GETSHORT(rdlen, p);
145 if (res == 1 && qtype == sigtype && qclass == sigclass)
146 {
147 ++rrsetidx;
382e38f4
GB
148 if (rrsetidx == countof(rrset))
149 {
150 /* Internal buffer too small */
151 printf("internal buffer too small for this RRset\n");
152 return 0;
153 }
e292e93d
GB
154 }
155 p += rdlen;
156 }
157
158 /* Sort RRset records in canonical order. */
159 qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
160
161 /* Extract the signer name (we need to query DNSKEY of this name) */
162 if (!(res = extract_name_no_compression(sig, sigrdlen, signer_name)))
163 return 0;
164 sig += res; sigrdlen -= res;
165
166 /* Now initialize the signature verification algorithm and process the whole
167 RRset */
168 const VerifyAlg *alg = &valgs[sigalg];
169 if (!alg->set_signature(sig, sigrdlen))
170 return 0;
171
172 alg->begin_data();
173 alg->add_data(sigrdata, 18);
174 alg->add_data(signer_name, strlen(signer_name)-1); /* remove trailing dot */
175 for (i = 0; i < rrsetidx; ++i)
176 {
177 int rdlen;
178
179 alg->add_data(owner, strlen(owner));
180 alg->add_data(&sigtype, 2);
181 alg->add_data(&sigclass, 2);
182 alg->add_data(&sigttl, 4);
183
184 p = (unsigned char*)(rrset[i]);
185 p += 8;
186 GETSHORT(rdlen, p);
382e38f4
GB
187 /* TODO: instead of a direct add_data(), we must call a RRtype-specific
188 function, that extract and canonicalizes domain names within RDATA. */
e292e93d
GB
189 alg->add_data(p-2, rdlen+2);
190 }
191 alg->end_data();
192
193 /* TODO: now we need to fetch the DNSKEY of signer_name with the specified
194 keytag, and check whether it validates with the current algorithm. */
195 /*
196 pseudo-code:
197
198 char *key; int keylen;
199 if (!fetch_dnskey(signer_name, keytag, &key, &keylen))
200 return 0;
201 return alg->verify(key, keylen);
202 */
203 return 0;
204}
205
206
207int dnssec_validate(struct dns_header *header, size_t pktlen)
208{
209 unsigned char *p, *reply;
210 char *owner = daemon->namebuff;
211 int i, qtype, qclass, rdlen;
212 unsigned long ttl;
213
214 if (header->ancount == 0)
215 return 0;
216 if (!(reply = p = skip_questions(header, pktlen)))
217 return 0;
218 for (i = 0; i < ntohs(header->ancount); i++)
219 {
220 if (!extract_name(header, pktlen, &p, owner, 1, 10))
221 return 0;
222 GETSHORT(qtype, p);
223 GETSHORT(qclass, p);
224 GETLONG(ttl, p);
225 GETSHORT(rdlen, p);
226 if (qtype == T_RRSIG)
227 {
228 printf("RRSIG found\n");
229 /* TODO: missing logic. We should only validate RRSIGs for which we
230 have a valid DNSKEY that is referenced by a DS record upstream.
231 There is a memory vs CPU conflict here; should we validate everything
232 to save memory and thus waste CPU, or better first acquire all information
233 (wasting memory) and then doing the minimum CPU computations required? */
234 validate_rrsig(header, pktlen, reply, ntohs(header->ancount), owner, qclass, rdlen, p);
235 }
236 p += rdlen;
237 }
238 return 1;
239}