blob: ad34557b9c9c1f73cbb72f92aa9a161fc893d07e [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
11#define countof(x) (long)(sizeof(x) / sizeof(x[0]))
12#define MIN(a,b) ((a) < (b) ? (a) : (b))
13
Giovanni Bajo970ce222012-04-22 15:22:07 +020014/* Updated registry that merges various RFCs:
15 https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml */
16static const VerifyAlg valgs[] =
Giovanni Bajoe292e932012-04-22 14:32:02 +020017{
Giovanni Bajod322de02012-04-23 00:30:00 +020018 {0,0,0,0,0}, /* 0: reserved */
19 {0,0,0,0,0}, /* 1: RSAMD5 */
20 {0,0,0,0,0}, /* 2: DH */
21 {0,0,0,0,0}, /* 3: DSA */
22 {0,0,0,0,0}, /* 4: ECC */
23 VALG_VTABLE(rsasha1), /* 5: RSASHA1 */
24 {0,0,0,0,0}, /* 6: DSA-NSEC3-SHA1 */
25 {0,0,0,0,0}, /* 7: RSASHA1-NSEC3-SHA1 */
26 {0,0,0,0,0}, /* 8: RSASHA256 */
27 {0,0,0,0,0}, /* 9: unassigned */
28 {0,0,0,0,0}, /* 10: RSASHA512 */
29 {0,0,0,0,0}, /* 11: unassigned */
30 {0,0,0,0,0}, /* 12: ECC-GOST */
31 {0,0,0,0,0}, /* 13: ECDSAP256SHA256 */
32 {0,0,0,0,0}, /* 14: ECDSAP384SHA384 */
Giovanni Bajoe292e932012-04-22 14:32:02 +020033};
34
35/* Implement RFC1982 wrapped compare for 32-bit numbers */
36static int serial_compare_32(unsigned long s1, unsigned long s2)
37{
38 if (s1 == s2)
39 return SERIAL_EQ;
40
41 if ((s1 < s2 && (s2 - s1) < (1UL<<31)) ||
42 (s1 > s2 && (s1 - s2) > (1UL<<31)))
43 return SERIAL_LT;
44 if ((s1 < s2 && (s2 - s1) > (1UL<<31)) ||
45 (s1 > s2 && (s1 - s2) < (1UL<<31)))
46 return SERIAL_GT;
47 return SERIAL_UNDEF;
48}
49
50/* Extract a DNS name from wire format, without handling compression. This is
51 faster than extract_name() and does not require access to the full dns
52 packet. */
53static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf)
54{
55 unsigned char *start=rr, *end = rr+maxlen;
56 int count;
57
58 while (rr < end && *rr != 0)
59 {
60 count = *rr++;
61 while (count-- >= 0 && rr < end)
62 {
63 *buf = *rr++;
Giovanni Bajob98f7712012-04-22 15:59:27 +020064 if (!isascii(*buf) || iscntrl(*buf) || *buf == '.')
65 return 0;
Giovanni Bajoe292e932012-04-22 14:32:02 +020066 if (*buf >= 'A' && *buf <= 'Z')
67 *buf += 'a' - 'A';
68 buf++;
69 }
70 *buf++ = '.';
71 }
Giovanni Bajoc7a93f62012-04-22 15:53:52 +020072 rr++;
Giovanni Bajoe292e932012-04-22 14:32:02 +020073 *buf = 0;
74 if (rr == end)
75 return 0;
76 return rr-start;
77}
78
79/* Check whether today/now is between date_start and date_end */
80static int check_date_range(unsigned long date_start, unsigned long date_end)
81{
82 /* TODO: double-check that time(0) is the correct time we are looking for */
83 /* TODO: dnssec requires correct timing; implement SNTP in dnsmasq? */
84 unsigned long curtime = time(0);
85
86 /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
87 if (serial_compare_32(curtime, date_start) != SERIAL_GT)
88 return 0;
89 if (serial_compare_32(curtime, date_end) != SERIAL_LT)
90 return 0;
91 return 1;
92}
93
94/* Sort RRs within a RRset in canonical order, according to RFC4034, ยง6.3
95 Notice that the RRDATA sections have been already normalized, so a memcpy
96 is sufficient.
97 NOTE: r1/r2 point immediately after the owner name. */
98static int rrset_canonical_order(const void *r1, const void *r2)
99{
100 int r1len, r2len, res;
101 const unsigned char *pr1=r1, *pr2=r2;
102
103 pr1 += 8; pr2 += 8;
104 GETSHORT(r1len, pr1); GETSHORT(r2len, pr2);
105
106 /* Lexicographically compare RDATA (thus, if equal, smaller length wins) */
107 res = memcmp(pr1, pr2, MIN(r1len, r2len));
108 if (res == 0)
109 {
110 if (r1len < r2len)
111 return -1;
112 else
113 /* NOTE: RFC2181 says that an RRset is not allowed to contain duplicate
114 records. If it happens, it is a protocol error and anything goes. */
115 return 1;
116 }
117
118 return res;
119}
120
121static int validate_rrsig(struct dns_header *header, size_t pktlen,
122 unsigned char *reply, int count, char *owner,
123 int sigclass, int sigrdlen, unsigned char *sig)
124{
125 int i, res;
126 int sigtype, sigalg, siglbl;
127 unsigned char *sigrdata = sig;
128 unsigned long sigttl, date_end, date_start;
129 unsigned char* p = reply;
130 char* signer_name = daemon->namebuff;
131 int keytag;
132 void *rrset[16]; /* TODO: max RRset size? */
133 int rrsetidx = 0;
134
135 if (sigrdlen < 18)
136 return 0;
137 GETSHORT(sigtype, sig);
138 sigalg = *sig++;
139 siglbl = *sig++;
140 GETLONG(sigttl, sig);
141 GETLONG(date_end, sig);
142 GETLONG(date_start, sig);
143 GETSHORT(keytag, sig);
144 sigrdlen -= 18;
145
146 if (sigalg >= countof(valgs) || !valgs[sigalg].set_signature)
147 {
148 printf("RRSIG algorithm not supported: %d\n", sigalg);
149 return 0;
150 }
151
152 if (!check_date_range(ntohl(date_start), ntohl(date_end)))
153 {
154 printf("RRSIG outside date range\n");
155 return 0;
156 }
157
158 /* Iterate within the answer and find the RRsets matching the current RRsig */
159 for (i = 0; i < count; ++i)
160 {
161 int qtype, qclass, rdlen;
162 if (!(res = extract_name(header, pktlen, &p, owner, 0, 10)))
163 return 0;
164 rrset[rrsetidx] = p;
165 GETSHORT(qtype, p);
166 GETSHORT(qclass, p);
167 p += 4; /* skip ttl */
168 GETSHORT(rdlen, p);
169 if (res == 1 && qtype == sigtype && qclass == sigclass)
170 {
171 ++rrsetidx;
Giovanni Bajo382e38f2012-04-24 01:46:47 +0200172 if (rrsetidx == countof(rrset))
173 {
174 /* Internal buffer too small */
175 printf("internal buffer too small for this RRset\n");
176 return 0;
177 }
Giovanni Bajoe292e932012-04-22 14:32:02 +0200178 }
179 p += rdlen;
180 }
181
182 /* Sort RRset records in canonical order. */
183 qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
184
185 /* Extract the signer name (we need to query DNSKEY of this name) */
186 if (!(res = extract_name_no_compression(sig, sigrdlen, signer_name)))
187 return 0;
188 sig += res; sigrdlen -= res;
189
190 /* Now initialize the signature verification algorithm and process the whole
191 RRset */
192 const VerifyAlg *alg = &valgs[sigalg];
193 if (!alg->set_signature(sig, sigrdlen))
194 return 0;
195
196 alg->begin_data();
197 alg->add_data(sigrdata, 18);
198 alg->add_data(signer_name, strlen(signer_name)-1); /* remove trailing dot */
199 for (i = 0; i < rrsetidx; ++i)
200 {
201 int rdlen;
202
203 alg->add_data(owner, strlen(owner));
204 alg->add_data(&sigtype, 2);
205 alg->add_data(&sigclass, 2);
206 alg->add_data(&sigttl, 4);
207
208 p = (unsigned char*)(rrset[i]);
209 p += 8;
210 GETSHORT(rdlen, p);
Giovanni Bajo382e38f2012-04-24 01:46:47 +0200211 /* TODO: instead of a direct add_data(), we must call a RRtype-specific
212 function, that extract and canonicalizes domain names within RDATA. */
Giovanni Bajoe292e932012-04-22 14:32:02 +0200213 alg->add_data(p-2, rdlen+2);
214 }
215 alg->end_data();
216
217 /* TODO: now we need to fetch the DNSKEY of signer_name with the specified
218 keytag, and check whether it validates with the current algorithm. */
219 /*
220 pseudo-code:
221
222 char *key; int keylen;
223 if (!fetch_dnskey(signer_name, keytag, &key, &keylen))
224 return 0;
225 return alg->verify(key, keylen);
226 */
227 return 0;
228}
229
230
231int dnssec_validate(struct dns_header *header, size_t pktlen)
232{
233 unsigned char *p, *reply;
234 char *owner = daemon->namebuff;
235 int i, qtype, qclass, rdlen;
236 unsigned long ttl;
237
238 if (header->ancount == 0)
239 return 0;
240 if (!(reply = p = skip_questions(header, pktlen)))
241 return 0;
242 for (i = 0; i < ntohs(header->ancount); i++)
243 {
244 if (!extract_name(header, pktlen, &p, owner, 1, 10))
245 return 0;
246 GETSHORT(qtype, p);
247 GETSHORT(qclass, p);
248 GETLONG(ttl, p);
249 GETSHORT(rdlen, p);
250 if (qtype == T_RRSIG)
251 {
252 printf("RRSIG found\n");
253 /* TODO: missing logic. We should only validate RRSIGs for which we
254 have a valid DNSKEY that is referenced by a DS record upstream.
255 There is a memory vs CPU conflict here; should we validate everything
256 to save memory and thus waste CPU, or better first acquire all information
257 (wasting memory) and then doing the minimum CPU computations required? */
258 validate_rrsig(header, pktlen, reply, ntohs(header->ancount), owner, qclass, rdlen, p);
259 }
260 p += rdlen;
261 }
262 return 1;
263}