]> git.ipfire.org Git - people/ms/dnsmasq.git/blob - src/dnssec.c
Move general macros in dnsmasq.h
[people/ms/dnsmasq.git] / src / dnssec.c
1
2 #include "dnsmasq.h"
3 #include "dnssec-crypto.h"
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
11 /* Updated registry that merges various RFCs:
12 https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml */
13 static const VerifyAlg valgs[] =
14 {
15 {0,0,0,0,0}, /* 0: reserved */
16 {0,0,0,0,0}, /* 1: RSAMD5 */
17 {0,0,0,0,0}, /* 2: DH */
18 {0,0,0,0,0}, /* 3: DSA */
19 {0,0,0,0,0}, /* 4: ECC */
20 VALG_VTABLE(rsasha1), /* 5: RSASHA1 */
21 {0,0,0,0,0}, /* 6: DSA-NSEC3-SHA1 */
22 {0,0,0,0,0}, /* 7: RSASHA1-NSEC3-SHA1 */
23 VALG_VTABLE(rsasha256), /* 8: RSASHA256 */
24 {0,0,0,0,0}, /* 9: unassigned */
25 {0,0,0,0,0}, /* 10: RSASHA512 */
26 {0,0,0,0,0}, /* 11: unassigned */
27 {0,0,0,0,0}, /* 12: ECC-GOST */
28 {0,0,0,0,0}, /* 13: ECDSAP256SHA256 */
29 {0,0,0,0,0}, /* 14: ECDSAP384SHA384 */
30 };
31
32 /* Implement RFC1982 wrapped compare for 32-bit numbers */
33 static int serial_compare_32(unsigned long s1, unsigned long s2)
34 {
35 if (s1 == s2)
36 return SERIAL_EQ;
37
38 if ((s1 < s2 && (s2 - s1) < (1UL<<31)) ||
39 (s1 > s2 && (s1 - s2) > (1UL<<31)))
40 return SERIAL_LT;
41 if ((s1 < s2 && (s2 - s1) > (1UL<<31)) ||
42 (s1 > s2 && (s1 - s2) < (1UL<<31)))
43 return SERIAL_GT;
44 return SERIAL_UNDEF;
45 }
46
47 /* Extract a DNS name from wire format, without handling compression. This is
48 faster than extract_name() and does not require access to the full dns
49 packet. */
50 static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf)
51 {
52 unsigned char *start=rr, *end = rr+maxlen;
53 int count;
54
55 while (rr < end && *rr != 0)
56 {
57 count = *rr++;
58 while (count-- > 0 && rr < end)
59 {
60 *buf = *rr++;
61 if (!isascii(*buf) || iscntrl(*buf) || *buf == '.')
62 return 0;
63 if (*buf >= 'A' && *buf <= 'Z')
64 *buf += 'a' - 'A';
65 buf++;
66 }
67 *buf++ = '.';
68 }
69 rr++;
70 *buf = 0;
71 if (rr == end)
72 return 0;
73 return rr-start;
74 }
75
76 /* Check whether today/now is between date_start and date_end */
77 static 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. */
95 static int rrset_canonical_order(const void *r1, const void *r2)
96 {
97 int r1len, r2len, res;
98 const unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2;
99
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
118 static int validate_rrsig(struct dns_header *header, size_t pktlen,
119 unsigned char *reply, int count, char *owner,
120 int sigclass, int sigrdlen, unsigned char *sig)
121 {
122 int i, res;
123 int sigtype, sigalg, siglbl;
124 unsigned char *sigrdata = sig;
125 unsigned long sigttl, date_end, date_start;
126 unsigned char* p = reply;
127 char* signer_name = daemon->namebuff;
128 int keytag;
129 void *rrset[16]; /* TODO: max RRset size? */
130 int rrsetidx = 0;
131
132 if (sigrdlen < 18)
133 return 0;
134 GETSHORT(sigtype, sig);
135 sigalg = *sig++;
136 siglbl = *sig++;
137 GETLONG(sigttl, sig);
138 GETLONG(date_end, sig);
139 GETLONG(date_start, sig);
140 GETSHORT(keytag, sig);
141 sigrdlen -= 18;
142
143 if (sigalg >= countof(valgs) || !valgs[sigalg].set_signature)
144 {
145 printf("RRSIG algorithm not supported: %d\n", sigalg);
146 return 0;
147 }
148
149 if (!check_date_range(date_start, date_end))
150 {
151 printf("RRSIG outside date range\n");
152 return 0;
153 }
154
155 /* Iterate within the answer and find the RRsets matching the current RRsig */
156 for (i = 0; i < count; ++i)
157 {
158 int qtype, qclass, rdlen;
159 if (!(res = extract_name(header, pktlen, &p, owner, 0, 10)))
160 return 0;
161 rrset[rrsetidx] = p;
162 GETSHORT(qtype, p);
163 GETSHORT(qclass, p);
164 p += 4; /* skip ttl */
165 GETSHORT(rdlen, p);
166 if (res == 1 && qtype == sigtype && qclass == sigclass)
167 {
168 ++rrsetidx;
169 if (rrsetidx == countof(rrset))
170 {
171 /* Internal buffer too small */
172 printf("internal buffer too small for this RRset\n");
173 return 0;
174 }
175 }
176 p += rdlen;
177 }
178
179 /* Sort RRset records in canonical order. */
180 qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
181
182 /* Extract the signer name (we need to query DNSKEY of this name) */
183 if (!(res = extract_name_no_compression(sig, sigrdlen, signer_name)))
184 return 0;
185 sig += res; sigrdlen -= res;
186
187 /* Now initialize the signature verification algorithm and process the whole
188 RRset */
189 const VerifyAlg *alg = &valgs[sigalg];
190 if (!alg->set_signature(sig, sigrdlen))
191 return 0;
192
193 alg->begin_data();
194 alg->add_data(sigrdata, 18);
195 alg->add_data(signer_name, strlen(signer_name)-1); /* remove trailing dot */
196 for (i = 0; i < rrsetidx; ++i)
197 {
198 int rdlen;
199
200 alg->add_data(owner, strlen(owner));
201 alg->add_data(&sigtype, 2);
202 alg->add_data(&sigclass, 2);
203 alg->add_data(&sigttl, 4);
204
205 p = (unsigned char*)(rrset[i]);
206 p += 8;
207 GETSHORT(rdlen, p);
208 /* TODO: instead of a direct add_data(), we must call a RRtype-specific
209 function, that extract and canonicalizes domain names within RDATA. */
210 alg->add_data(p-2, rdlen+2);
211 }
212 alg->end_data();
213
214 /* TODO: now we need to fetch the DNSKEY of signer_name with the specified
215 keytag, and check whether it validates with the current algorithm. */
216 /*
217 pseudo-code:
218
219 char *key; int keylen;
220 if (!fetch_dnskey(signer_name, keytag, &key, &keylen))
221 return 0;
222 return alg->verify(key, keylen);
223 */
224 return 0;
225 }
226
227
228 int dnssec_validate(struct dns_header *header, size_t pktlen)
229 {
230 unsigned char *p, *reply;
231 char *owner = daemon->namebuff;
232 int i, qtype, qclass, rdlen;
233 unsigned long ttl;
234
235 if (header->ancount == 0)
236 return 0;
237 if (!(reply = p = skip_questions(header, pktlen)))
238 return 0;
239 for (i = 0; i < ntohs(header->ancount); i++)
240 {
241 if (!extract_name(header, pktlen, &p, owner, 1, 10))
242 return 0;
243 GETSHORT(qtype, p);
244 GETSHORT(qclass, p);
245 GETLONG(ttl, p);
246 GETSHORT(rdlen, p);
247 if (qtype == T_RRSIG)
248 {
249 printf("RRSIG found\n");
250 /* TODO: missing logic. We should only validate RRSIGs for which we
251 have a valid DNSKEY that is referenced by a DS record upstream.
252 There is a memory vs CPU conflict here; should we validate everything
253 to save memory and thus waste CPU, or better first acquire all information
254 (wasting memory) and then doing the minimum CPU computations required? */
255 validate_rrsig(header, pktlen, reply, ntohs(header->ancount), owner, qclass, rdlen, p);
256 }
257 p += rdlen;
258 }
259 return 1;
260 }