Files: 975560aa4a197bc7aeb4a8952ed8a764b8a90e3a / tls.c
10330 bytesRaw
1 | /* |
2 | * Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net> |
3 | * All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions are met: |
7 | * |
8 | * 1. Redistributions of source code must retain the above copyright notice, |
9 | * this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * |
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
24 | * POSSIBILITY OF SUCH DAMAGE. |
25 | */ |
26 | /* |
27 | * This is a minimal TLS implementation intended only to parse the server name |
28 | * extension. This was created based primarily on Wireshark dissection of a |
29 | * TLS handshake and RFC4366. |
30 | */ |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
41 | |
42 | |
43 | |
44 | struct TLSProtocol { |
45 | int use_alpn; |
46 | char** sni_hostname_list; |
47 | char** alpn_protocol_list; |
48 | }; |
49 | |
50 | static int parse_extensions(const struct TLSProtocol *, const char *, size_t); |
51 | static int parse_server_name_extension(const struct TLSProtocol *, const char *, size_t); |
52 | static int parse_alpn_extension(const struct TLSProtocol *, const char *, size_t); |
53 | static int has_match(char**, const char*, size_t); |
54 | |
55 | /* Parse a TLS packet for the Server Name Indication and ALPN extension in the client |
56 | * hello handshake, returning a status code |
57 | * |
58 | * Returns: |
59 | * >=0 - length of the hostname and updates *hostname |
60 | * caller is responsible for freeing *hostname |
61 | * -1 - Incomplete request |
62 | * -2 - No Host header included in this request |
63 | * -3 - Invalid hostname pointer |
64 | * < -4 - Invalid TLS client hello |
65 | */ |
66 | int |
67 | parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { |
68 | char tls_content_type; |
69 | char tls_version_major; |
70 | char tls_version_minor; |
71 | size_t pos = TLS_HEADER_LEN; |
72 | size_t len; |
73 | |
74 | /* Check that our TCP payload is at least large enough for a TLS header */ |
75 | if (data_len < TLS_HEADER_LEN) |
76 | return -1; |
77 | |
78 | tls_content_type = data[0]; |
79 | if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) { |
80 | if (verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n"); |
81 | return -5; |
82 | } |
83 | |
84 | tls_version_major = data[1]; |
85 | tls_version_minor = data[2]; |
86 | if (tls_version_major < 3) { |
87 | if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which cannot be parsed.\n", |
88 | tls_version_major, tls_version_minor); |
89 | |
90 | return -2; |
91 | } |
92 | |
93 | /* TLS record length */ |
94 | len = ((unsigned char)data[3] << 8) + |
95 | (unsigned char)data[4] + TLS_HEADER_LEN; |
96 | data_len = MIN(data_len, len); |
97 | |
98 | /* Check we received entire TLS record length */ |
99 | if (data_len < len) |
100 | return -1; |
101 | |
102 | /* |
103 | * Handshake |
104 | */ |
105 | if (pos + 1 > data_len) { |
106 | return -5; |
107 | } |
108 | if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) { |
109 | if (verbose) fprintf(stderr, "Not a client hello\n"); |
110 | |
111 | return -5; |
112 | } |
113 | |
114 | /* Skip past fixed length records: |
115 | 1 Handshake Type |
116 | 3 Length |
117 | 2 Version (again) |
118 | 32 Random |
119 | to Session ID Length |
120 | */ |
121 | pos += 38; |
122 | |
123 | /* Session ID */ |
124 | if (pos + 1 > data_len) |
125 | return -5; |
126 | len = (unsigned char)data[pos]; |
127 | pos += 1 + len; |
128 | |
129 | /* Cipher Suites */ |
130 | if (pos + 2 > data_len) |
131 | return -5; |
132 | len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; |
133 | pos += 2 + len; |
134 | |
135 | /* Compression Methods */ |
136 | if (pos + 1 > data_len) |
137 | return -5; |
138 | len = (unsigned char)data[pos]; |
139 | pos += 1 + len; |
140 | |
141 | if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) { |
142 | if (verbose) fprintf(stderr, "Received SSL 3.0 handshake without extensions\n"); |
143 | return -2; |
144 | } |
145 | |
146 | /* Extensions */ |
147 | if (pos + 2 > data_len) |
148 | return -5; |
149 | len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; |
150 | pos += 2; |
151 | |
152 | if (pos + len > data_len) |
153 | return -5; |
154 | return parse_extensions(tls_data, data + pos, len); |
155 | } |
156 | |
157 | static int |
158 | parse_extensions(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { |
159 | size_t pos = 0; |
160 | size_t len; |
161 | int last_matched = 0; |
162 | |
163 | if (tls_data == NULL) |
164 | return -3; |
165 | |
166 | /* Parse each 4 bytes for the extension header */ |
167 | while (pos + 4 <= data_len) { |
168 | /* Extension Length */ |
169 | len = ((unsigned char) data[pos + 2] << 8) + |
170 | (unsigned char) data[pos + 3]; |
171 | |
172 | if (pos + 4 + len > data_len) |
173 | return -5; |
174 | |
175 | size_t extension_type = ((unsigned char) data[pos] << 8) + |
176 | (unsigned char) data[pos + 1]; |
177 | |
178 | |
179 | /* Check if it's a server name extension */ |
180 | /* There can be only one extension of each type, so we break |
181 | our state and move pos to beginning of the extension here */ |
182 | if (tls_data->use_alpn == 2) { |
183 | /* we want BOTH alpn and sni to match */ |
184 | if (extension_type == 0x00) { /* Server Name */ |
185 | if (parse_server_name_extension(tls_data, data + pos + 4, len)) { |
186 | /* SNI matched */ |
187 | if(last_matched) { |
188 | /* this is only true if ALPN matched, so return true */ |
189 | return last_matched; |
190 | } else { |
191 | /* otherwise store that SNI matched */ |
192 | last_matched = 1; |
193 | } |
194 | } else { |
195 | // both can't match |
196 | return -2; |
197 | } |
198 | } else if (extension_type == 0x10) { /* ALPN */ |
199 | if (parse_alpn_extension(tls_data, data + pos + 4, len)) { |
200 | /* ALPN matched */ |
201 | if(last_matched) { |
202 | /* this is only true if SNI matched, so return true */ |
203 | return last_matched; |
204 | } else { |
205 | /* otherwise store that ALPN matched */ |
206 | last_matched = 1; |
207 | } |
208 | } else { |
209 | // both can't match |
210 | return -2; |
211 | } |
212 | } |
213 | |
214 | } else if (extension_type == 0x00 && tls_data->use_alpn == 0) { /* Server Name */ |
215 | return parse_server_name_extension(tls_data, data + pos + 4, len); |
216 | } else if (extension_type == 0x10 && tls_data->use_alpn == 1) { /* ALPN */ |
217 | return parse_alpn_extension(tls_data, data + pos + 4, len); |
218 | } |
219 | |
220 | pos += 4 + len; /* Advance to the next extension header */ |
221 | } |
222 | |
223 | /* Check we ended where we expected to */ |
224 | if (pos != data_len) |
225 | return -5; |
226 | |
227 | return -2; |
228 | } |
229 | |
230 | static int |
231 | parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { |
232 | size_t pos = 2; /* skip server name list length */ |
233 | size_t len; |
234 | |
235 | while (pos + 3 < data_len) { |
236 | len = ((unsigned char)data[pos + 1] << 8) + |
237 | (unsigned char)data[pos + 2]; |
238 | |
239 | if (pos + 3 + len > data_len) |
240 | return -5; |
241 | |
242 | switch (data[pos]) { /* name type */ |
243 | case 0x00: /* host_name */ |
244 | if(has_match(tls_data->sni_hostname_list, data + pos + 3, len)) { |
245 | return len; |
246 | } else { |
247 | return -2; |
248 | } |
249 | default: |
250 | if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n", |
251 | data[pos]); |
252 | } |
253 | pos += 3 + len; |
254 | } |
255 | /* Check we ended where we expected to */ |
256 | if (pos != data_len) |
257 | return -5; |
258 | |
259 | return -2; |
260 | } |
261 | |
262 | static int |
263 | parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { |
264 | size_t pos = 2; |
265 | size_t len; |
266 | |
267 | while (pos + 1 < data_len) { |
268 | len = (unsigned char)data[pos]; |
269 | |
270 | if (pos + 1 + len > data_len) |
271 | return -5; |
272 | |
273 | if (len > 0 && has_match(tls_data->alpn_protocol_list, data + pos + 1, len)) { |
274 | return len; |
275 | } else if (len > 0) { |
276 | if (verbose) fprintf(stderr, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1); |
277 | } |
278 | pos += 1 + len; |
279 | } |
280 | /* Check we ended where we expected to */ |
281 | if (pos != data_len) |
282 | return -5; |
283 | |
284 | return -2; |
285 | } |
286 | |
287 | static int |
288 | has_match(char** list, const char* name, size_t name_len) { |
289 | char **item; |
290 | |
291 | for (item = list; *item; item++) { |
292 | if (verbose) fprintf(stderr, "matching [%.*s] with [%s]\n", (int)name_len, name, *item); |
293 | if(!strncmp(*item, name, name_len)) { |
294 | return 1; |
295 | } |
296 | } |
297 | return 0; |
298 | } |
299 | |
300 | struct TLSProtocol * |
301 | new_tls_data() { |
302 | struct TLSProtocol *tls_data = malloc(sizeof(struct TLSProtocol)); |
303 | if (tls_data != NULL) { |
304 | tls_data->use_alpn = -1; |
305 | } |
306 | |
307 | return tls_data; |
308 | } |
309 | |
310 | struct TLSProtocol * |
311 | tls_data_set_list(struct TLSProtocol *tls_data, int alpn, char** list) { |
312 | if (alpn) { |
313 | tls_data->alpn_protocol_list = list; |
314 | if(tls_data->use_alpn == 0) |
315 | tls_data->use_alpn = 2; |
316 | else |
317 | tls_data->use_alpn = 1; |
318 | } else { |
319 | tls_data->sni_hostname_list = list; |
320 | if(tls_data->use_alpn == 1) |
321 | tls_data->use_alpn = 2; |
322 | else |
323 | tls_data->use_alpn = 0; |
324 | } |
325 | |
326 | return tls_data; |
327 | } |
328 |
Built with git-ssb-web