]>
Commit | Line | Data |
---|---|---|
e292e93d GB |
1 | |
2 | #include "dnsmasq.h" | |
3 | #include <assert.h> | |
4 | ||
5 | #define SERIAL_UNDEF -100 | |
6 | #define SERIAL_EQ 0 | |
7 | #define SERIAL_LT -1 | |
8 | #define SERIAL_GT 1 | |
9 | ||
10 | #define countof(x) (long)(sizeof(x) / sizeof(x[0])) | |
11 | #define MIN(a,b) ((a) < (b) ? (a) : (b)) | |
12 | ||
13 | /* | |
14 | * vtable for a signature verification algorithm. | |
15 | * | |
16 | * Each algorithm verifies that a certain signature over a (possibly non-contigous) | |
17 | * array of data has been made with the specified key. | |
18 | * | |
19 | * Sample of usage: | |
20 | * | |
21 | * // First, set the signature we need to check. Notice: data is not copied | |
22 | * // nor consumed, so the pointer must stay valid. | |
23 | * alg->set_signature(sig, 16); | |
24 | * | |
25 | * // Second, push the data in; data is consumed immediately, so the buffer | |
26 | * // can be freed or modified. | |
27 | * alg->begin_data(); | |
28 | * alg->add_data(buf1, 123); | |
29 | * alg->add_data(buf2, 45); | |
30 | * alg->add_data(buf3, 678); | |
31 | * alg->end_data(); | |
32 | * | |
33 | * // Third, verify if we got the correct key for this signature. | |
34 | * alg->verify(key1, 16); | |
35 | * alg->verify(key2, 16); | |
36 | */ | |
37 | typedef struct | |
38 | { | |
39 | int (*set_signature)(unsigned char *data, unsigned len); | |
40 | void (*begin_data)(void); | |
41 | void (*add_data)(void *data, unsigned len); | |
42 | void (*end_data)(void); | |
43 | int (*verify)(unsigned char *key, unsigned key_len); | |
44 | } VerifyAlg; | |
45 | ||
46 | /* RFC4034, Appendix A.1: only algorithm 3 (DSA/SHA1) and 5 (RSA/SHA1) are | |
47 | currently valid for zone-signing. */ | |
48 | static const VerifyAlg valgs[6] = | |
49 | { | |
50 | {0,0,0,0,0}, /* 0: reserved */ | |
51 | {0,0,0,0,0}, /* 1: RSA/MD5 */ | |
52 | {0,0,0,0,0}, /* 2: DH */ | |
53 | {0,0,0,0,0}, /* 3: DSA/SHA1 */ | |
54 | {0,0,0,0,0}, /* 4: ECC */ | |
55 | {0,0,0,0,0}, /* 5: RSA/SHA1 */ | |
56 | }; | |
57 | ||
58 | /* Implement RFC1982 wrapped compare for 32-bit numbers */ | |
59 | static int serial_compare_32(unsigned long s1, unsigned long s2) | |
60 | { | |
61 | if (s1 == s2) | |
62 | return SERIAL_EQ; | |
63 | ||
64 | if ((s1 < s2 && (s2 - s1) < (1UL<<31)) || | |
65 | (s1 > s2 && (s1 - s2) > (1UL<<31))) | |
66 | return SERIAL_LT; | |
67 | if ((s1 < s2 && (s2 - s1) > (1UL<<31)) || | |
68 | (s1 > s2 && (s1 - s2) < (1UL<<31))) | |
69 | return SERIAL_GT; | |
70 | return SERIAL_UNDEF; | |
71 | } | |
72 | ||
73 | /* Extract a DNS name from wire format, without handling compression. This is | |
74 | faster than extract_name() and does not require access to the full dns | |
75 | packet. */ | |
76 | static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf) | |
77 | { | |
78 | unsigned char *start=rr, *end = rr+maxlen; | |
79 | int count; | |
80 | ||
81 | while (rr < end && *rr != 0) | |
82 | { | |
83 | count = *rr++; | |
84 | while (count-- >= 0 && rr < end) | |
85 | { | |
86 | *buf = *rr++; | |
87 | if (*buf >= 'A' && *buf <= 'Z') | |
88 | *buf += 'a' - 'A'; | |
89 | buf++; | |
90 | } | |
91 | *buf++ = '.'; | |
92 | } | |
93 | *buf = 0; | |
94 | if (rr == end) | |
95 | return 0; | |
96 | return rr-start; | |
97 | } | |
98 | ||
99 | /* Check whether today/now is between date_start and date_end */ | |
100 | static int check_date_range(unsigned long date_start, unsigned long date_end) | |
101 | { | |
102 | /* TODO: double-check that time(0) is the correct time we are looking for */ | |
103 | /* TODO: dnssec requires correct timing; implement SNTP in dnsmasq? */ | |
104 | unsigned long curtime = time(0); | |
105 | ||
106 | /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ | |
107 | if (serial_compare_32(curtime, date_start) != SERIAL_GT) | |
108 | return 0; | |
109 | if (serial_compare_32(curtime, date_end) != SERIAL_LT) | |
110 | return 0; | |
111 | return 1; | |
112 | } | |
113 | ||
114 | /* Sort RRs within a RRset in canonical order, according to RFC4034, ยง6.3 | |
115 | Notice that the RRDATA sections have been already normalized, so a memcpy | |
116 | is sufficient. | |
117 | NOTE: r1/r2 point immediately after the owner name. */ | |
118 | static int rrset_canonical_order(const void *r1, const void *r2) | |
119 | { | |
120 | int r1len, r2len, res; | |
121 | const unsigned char *pr1=r1, *pr2=r2; | |
122 | ||
123 | pr1 += 8; pr2 += 8; | |
124 | GETSHORT(r1len, pr1); GETSHORT(r2len, pr2); | |
125 | ||
126 | /* Lexicographically compare RDATA (thus, if equal, smaller length wins) */ | |
127 | res = memcmp(pr1, pr2, MIN(r1len, r2len)); | |
128 | if (res == 0) | |
129 | { | |
130 | if (r1len < r2len) | |
131 | return -1; | |
132 | else | |
133 | /* NOTE: RFC2181 says that an RRset is not allowed to contain duplicate | |
134 | records. If it happens, it is a protocol error and anything goes. */ | |
135 | return 1; | |
136 | } | |
137 | ||
138 | return res; | |
139 | } | |
140 | ||
141 | static int validate_rrsig(struct dns_header *header, size_t pktlen, | |
142 | unsigned char *reply, int count, char *owner, | |
143 | int sigclass, int sigrdlen, unsigned char *sig) | |
144 | { | |
145 | int i, res; | |
146 | int sigtype, sigalg, siglbl; | |
147 | unsigned char *sigrdata = sig; | |
148 | unsigned long sigttl, date_end, date_start; | |
149 | unsigned char* p = reply; | |
150 | char* signer_name = daemon->namebuff; | |
151 | int keytag; | |
152 | void *rrset[16]; /* TODO: max RRset size? */ | |
153 | int rrsetidx = 0; | |
154 | ||
155 | if (sigrdlen < 18) | |
156 | return 0; | |
157 | GETSHORT(sigtype, sig); | |
158 | sigalg = *sig++; | |
159 | siglbl = *sig++; | |
160 | GETLONG(sigttl, sig); | |
161 | GETLONG(date_end, sig); | |
162 | GETLONG(date_start, sig); | |
163 | GETSHORT(keytag, sig); | |
164 | sigrdlen -= 18; | |
165 | ||
166 | if (sigalg >= countof(valgs) || !valgs[sigalg].set_signature) | |
167 | { | |
168 | printf("RRSIG algorithm not supported: %d\n", sigalg); | |
169 | return 0; | |
170 | } | |
171 | ||
172 | if (!check_date_range(ntohl(date_start), ntohl(date_end))) | |
173 | { | |
174 | printf("RRSIG outside date range\n"); | |
175 | return 0; | |
176 | } | |
177 | ||
178 | /* Iterate within the answer and find the RRsets matching the current RRsig */ | |
179 | for (i = 0; i < count; ++i) | |
180 | { | |
181 | int qtype, qclass, rdlen; | |
182 | if (!(res = extract_name(header, pktlen, &p, owner, 0, 10))) | |
183 | return 0; | |
184 | rrset[rrsetidx] = p; | |
185 | GETSHORT(qtype, p); | |
186 | GETSHORT(qclass, p); | |
187 | p += 4; /* skip ttl */ | |
188 | GETSHORT(rdlen, p); | |
189 | if (res == 1 && qtype == sigtype && qclass == sigclass) | |
190 | { | |
191 | ++rrsetidx; | |
192 | assert(rrsetidx < countof(rrset)); | |
193 | /* TODO: here we should convert to lowercase domain names within | |
194 | RDATA. We can do it in place. */ | |
195 | } | |
196 | p += rdlen; | |
197 | } | |
198 | ||
199 | /* Sort RRset records in canonical order. */ | |
200 | qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order); | |
201 | ||
202 | /* Extract the signer name (we need to query DNSKEY of this name) */ | |
203 | if (!(res = extract_name_no_compression(sig, sigrdlen, signer_name))) | |
204 | return 0; | |
205 | sig += res; sigrdlen -= res; | |
206 | ||
207 | /* Now initialize the signature verification algorithm and process the whole | |
208 | RRset */ | |
209 | const VerifyAlg *alg = &valgs[sigalg]; | |
210 | if (!alg->set_signature(sig, sigrdlen)) | |
211 | return 0; | |
212 | ||
213 | alg->begin_data(); | |
214 | alg->add_data(sigrdata, 18); | |
215 | alg->add_data(signer_name, strlen(signer_name)-1); /* remove trailing dot */ | |
216 | for (i = 0; i < rrsetidx; ++i) | |
217 | { | |
218 | int rdlen; | |
219 | ||
220 | alg->add_data(owner, strlen(owner)); | |
221 | alg->add_data(&sigtype, 2); | |
222 | alg->add_data(&sigclass, 2); | |
223 | alg->add_data(&sigttl, 4); | |
224 | ||
225 | p = (unsigned char*)(rrset[i]); | |
226 | p += 8; | |
227 | GETSHORT(rdlen, p); | |
228 | alg->add_data(p-2, rdlen+2); | |
229 | } | |
230 | alg->end_data(); | |
231 | ||
232 | /* TODO: now we need to fetch the DNSKEY of signer_name with the specified | |
233 | keytag, and check whether it validates with the current algorithm. */ | |
234 | /* | |
235 | pseudo-code: | |
236 | ||
237 | char *key; int keylen; | |
238 | if (!fetch_dnskey(signer_name, keytag, &key, &keylen)) | |
239 | return 0; | |
240 | return alg->verify(key, keylen); | |
241 | */ | |
242 | return 0; | |
243 | } | |
244 | ||
245 | ||
246 | int dnssec_validate(struct dns_header *header, size_t pktlen) | |
247 | { | |
248 | unsigned char *p, *reply; | |
249 | char *owner = daemon->namebuff; | |
250 | int i, qtype, qclass, rdlen; | |
251 | unsigned long ttl; | |
252 | ||
253 | if (header->ancount == 0) | |
254 | return 0; | |
255 | if (!(reply = p = skip_questions(header, pktlen))) | |
256 | return 0; | |
257 | for (i = 0; i < ntohs(header->ancount); i++) | |
258 | { | |
259 | if (!extract_name(header, pktlen, &p, owner, 1, 10)) | |
260 | return 0; | |
261 | GETSHORT(qtype, p); | |
262 | GETSHORT(qclass, p); | |
263 | GETLONG(ttl, p); | |
264 | GETSHORT(rdlen, p); | |
265 | if (qtype == T_RRSIG) | |
266 | { | |
267 | printf("RRSIG found\n"); | |
268 | /* TODO: missing logic. We should only validate RRSIGs for which we | |
269 | have a valid DNSKEY that is referenced by a DS record upstream. | |
270 | There is a memory vs CPU conflict here; should we validate everything | |
271 | to save memory and thus waste CPU, or better first acquire all information | |
272 | (wasting memory) and then doing the minimum CPU computations required? */ | |
273 | validate_rrsig(header, pktlen, reply, ntohs(header->ancount), owner, qclass, rdlen, p); | |
274 | } | |
275 | p += rdlen; | |
276 | } | |
277 | return 1; | |
278 | } |