]>
Commit | Line | Data |
---|---|---|
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 */ |
12 | static 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. */ | |
29 | static 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 */ | |
56 | static 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. */ | |
74 | static 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 | ||
97 | static 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 | ||
207 | int 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 | } |