Commit fbebdaf66c4d724f2b37639a256feb53a634a686
Add support for Server Name Indication (SNI, RFC4366)
Yves Rutschle committed on 7/15/2015, 12:07:16 PMParent: fecfb170c84cd6119165581a419a6545286f203a
Parent: b9885401050ad27d9fa13ffa67d5e43441f495c0
Files changed
Makefile | changed |
example.cfg | changed |
probe.c | changed |
probe.h | changed |
sslh-main.c | changed |
tls.c | added |
tls.h | added |
Makefile | ||
---|---|---|
@@ -22,9 +22,9 @@ | ||
22 | 22 | CC ?= gcc |
23 | 23 | CFLAGS ?=-Wall -g $(CFLAGS_COV) |
24 | 24 | |
25 | 25 | LIBS= |
26 | -OBJS=common.o sslh-main.o probe.o | |
26 | +OBJS=common.o sslh-main.o probe.o tls.o | |
27 | 27 | |
28 | 28 | ifneq ($(strip $(USELIBWRAP)),) |
29 | 29 | LIBS:=$(LIBS) -lwrap |
30 | 30 | CPPFLAGS+=-DLIBWRAP |
@@ -62,9 +62,9 @@ | ||
62 | 62 | $(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS) |
63 | 63 | #strip sslh-select |
64 | 64 | |
65 | 65 | echosrv: $(OBJS) echosrv.o |
66 | - $(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o $(LIBS) | |
66 | + $(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS) | |
67 | 67 | |
68 | 68 | $(MAN): sslh.pod Makefile |
69 | 69 | pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN) |
70 | 70 |
example.cfg | ||
---|---|---|
@@ -35,8 +35,9 @@ | ||
35 | 35 | |
36 | 36 | protocols: |
37 | 37 | ( |
38 | 38 | { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; }, |
39 | + { name: "sni"; host: "localhost"; port: "993"; probe: "builtin"; sni_hostnames: [ "imap.example.org", "imap.example.com" ]; }, | |
39 | 40 | { name: "openvpn"; host: "localhost"; port: "1194"; probe: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; }, |
40 | 41 | { name: "xmpp"; host: "localhost"; port: "5222"; probe: [ "jabber" ]; }, |
41 | 42 | { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; }, |
42 | 43 | { name: "ssl"; host: "localhost"; port: "443"; probe: [ "" ]; }, |
probe.c | ||
---|---|---|
@@ -216,8 +216,35 @@ | ||
216 | 216 | |
217 | 217 | return PROBE_NEXT; |
218 | 218 | } |
219 | 219 | |
220 | +static int is_sni_protocol(const char *p, int len, struct proto *proto) | |
221 | +{ | |
222 | + int valid_tls; | |
223 | + char *hostname; | |
224 | + | |
225 | + valid_tls = parse_tls_header(p, len, &hostname); | |
226 | + | |
227 | + if(valid_tls < 0) | |
228 | + return -1 == valid_tls ? PROBE_AGAIN : PROBE_NEXT; | |
229 | + | |
230 | + if (verbose) fprintf(stderr, "sni hostname: %s\n", hostname); | |
231 | + | |
232 | + /* Assume does not match */ | |
233 | + valid_tls = PROBE_NEXT; | |
234 | + | |
235 | + char **sni_hostname = proto->data; | |
236 | + | |
237 | + for (; *sni_hostname; sni_hostname++) | |
238 | + if(!strcmp(hostname, *sni_hostname)) { | |
239 | + valid_tls = PROBE_MATCH; | |
240 | + break; | |
241 | + } | |
242 | + | |
243 | + free(hostname); | |
244 | + return valid_tls; | |
245 | +} | |
246 | + | |
220 | 247 | static int is_tls_protocol(const char *p, int len, struct proto *proto) |
221 | 248 | { |
222 | 249 | if (len < 3) |
223 | 250 | return PROBE_AGAIN; |
@@ -334,8 +361,12 @@ | ||
334 | 361 | * regexp is not legal on the command line)*/ |
335 | 362 | if (!strcmp(description, "regex")) |
336 | 363 | return regex_probe; |
337 | 364 | |
365 | + /* Special case of "sni" probe for same reason as above*/ | |
366 | + if (!strcmp(description, "sni")) | |
367 | + return is_sni_protocol; | |
368 | + | |
338 | 369 | return NULL; |
339 | 370 | } |
340 | 371 | |
341 | 372 |
probe.h | ||
---|---|---|
@@ -3,8 +3,9 @@ | ||
3 | 3 | |
4 | 4 | |
5 | 5 | |
6 | 6 | |
7 | + | |
7 | 8 | |
8 | 9 | typedef enum { |
9 | 10 | PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */ |
10 | 11 | PROBE_MATCH, /* Enough data, probe successful -- it's the current protocol */ |
@@ -22,9 +23,9 @@ | ||
22 | 23 | |
23 | 24 | /* function to probe that protocol; parameters are buffer and length |
24 | 25 | * containing the data to probe, and a pointer to the protocol structure */ |
25 | 26 | T_PROBE* probe; |
26 | - void* data; /* opaque pointer ; used to pass list of regex to regex probe */ | |
27 | + void* data; /* opaque pointer ; used to pass list of regex to regex probe, or sni hostnames to sni probe */ | |
27 | 28 | struct proto *next; /* pointer to next protocol in list, NULL if last */ |
28 | 29 | }; |
29 | 30 | |
30 | 31 | /* Returns a pointer to the array of builtin protocols */ |
sslh-main.c | ||
---|---|---|
@@ -210,15 +210,47 @@ | ||
210 | 210 | |
211 | 211 | } |
212 | 212 | |
213 | 213 | |
214 | + | |
215 | +static void setup_sni_hostnames(struct proto *p, config_setting_t* sni_hostnames) | |
216 | +{ | |
217 | + int num_probes, i, max_server_name_len, server_name_len; | |
218 | + const char * sni_hostname; | |
219 | + char** sni_hostname_list; | |
220 | + | |
221 | + num_probes = config_setting_length(sni_hostnames); | |
222 | + if (!num_probes) { | |
223 | + fprintf(stderr, "%s: no sni_hostnames specified\n", p->description); | |
224 | + exit(1); | |
225 | + } | |
226 | + | |
227 | + max_server_name_len = 0; | |
228 | + for (i = 0; i < num_probes; i++) { | |
229 | + server_name_len = strlen(config_setting_get_string_elem(sni_hostnames, i)); | |
230 | + if(server_name_len > max_server_name_len) | |
231 | + max_server_name_len = server_name_len; | |
232 | + } | |
233 | + | |
234 | + sni_hostname_list = calloc(num_probes + 1, ++max_server_name_len); | |
235 | + p->data = (void*)sni_hostname_list; | |
236 | + | |
237 | + for (i = 0; i < num_probes; i++) { | |
238 | + sni_hostname = config_setting_get_string_elem(sni_hostnames, i); | |
239 | + sni_hostname_list[i] = malloc(max_server_name_len); | |
240 | + strcpy (sni_hostname_list[i], sni_hostname); | |
241 | + if(verbose) fprintf(stderr, "sni_hostnames[%d]: %s\n", i, sni_hostname_list[i]); | |
242 | + } | |
243 | +} | |
244 | + | |
245 | + | |
214 | 246 | /* Extract configuration for protocols to connect to. |
215 | 247 | * out: newly-allocated list of protocols |
216 | 248 | */ |
217 | 249 | |
218 | 250 | static int config_protocols(config_t *config, struct proto **prots) |
219 | 251 | { |
220 | - config_setting_t *setting, *prot, *probes; | |
252 | + config_setting_t *setting, *prot, *probes, *sni_hostnames; | |
221 | 253 | const char *hostname, *port, *name; |
222 | 254 | int i, num_prots; |
223 | 255 | struct proto *p, *prev = NULL; |
224 | 256 | |
@@ -264,8 +296,13 @@ | ||
264 | 296 | exit(1); |
265 | 297 | } |
266 | 298 | } |
267 | 299 | } |
300 | + | |
301 | + sni_hostnames = config_setting_get_member(prot, "sni_hostnames"); | |
302 | + if (sni_hostnames && config_setting_is_array(sni_hostnames)) { | |
303 | + setup_sni_hostnames(p, sni_hostnames); | |
304 | + } | |
268 | 305 | } |
269 | 306 | } |
270 | 307 | } |
271 | 308 |
tls.c | ||
---|---|---|
@@ -1,0 +1,238 @@ | ||
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 | + | |
45 | +static int parse_extensions(const char *, size_t, char **); | |
46 | +static int parse_server_name_extension(const char *, size_t, char **); | |
47 | + | |
48 | +const char tls_alert[] = { | |
49 | + 0x15, /* TLS Alert */ | |
50 | + 0x03, 0x01, /* TLS version */ | |
51 | + 0x00, 0x02, /* Payload length */ | |
52 | + 0x02, 0x28, /* Fatal, handshake failure */ | |
53 | +}; | |
54 | + | |
55 | +/* Parse a TLS packet for the Server Name Indication extension in the client | |
56 | + * hello handshake, returning the first servername found (pointer to static | |
57 | + * array) | |
58 | + * | |
59 | + * Returns: | |
60 | + * >=0 - length of the hostname and updates *hostname | |
61 | + * caller is responsible for freeing *hostname | |
62 | + * -1 - Incomplete request | |
63 | + * -2 - No Host header included in this request | |
64 | + * -3 - Invalid hostname pointer | |
65 | + * -4 - malloc failure | |
66 | + * < -4 - Invalid TLS client hello | |
67 | + */ | |
68 | +int | |
69 | +parse_tls_header(const char *data, size_t data_len, char **hostname) { | |
70 | + char tls_content_type; | |
71 | + char tls_version_major; | |
72 | + char tls_version_minor; | |
73 | + size_t pos = TLS_HEADER_LEN; | |
74 | + size_t len; | |
75 | + | |
76 | + if (hostname == NULL) | |
77 | + return -3; | |
78 | + | |
79 | + /* Check that our TCP payload is at least large enough for a TLS header */ | |
80 | + if (data_len < TLS_HEADER_LEN) | |
81 | + return -1; | |
82 | + | |
83 | + /* SSL 2.0 compatible Client Hello | |
84 | + * | |
85 | + * High bit of first byte (length) and content type is Client Hello | |
86 | + * | |
87 | + * See RFC5246 Appendix E.2 | |
88 | + */ | |
89 | + if (data[0] & 0x80 && data[2] == 1) { | |
90 | + if (verbose) fprintf(stderr, "Received SSL 2.0 Client Hello which can not support SNI.\n"); | |
91 | + return -2; | |
92 | + } | |
93 | + | |
94 | + tls_content_type = data[0]; | |
95 | + if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) { | |
96 | + if (verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n"); | |
97 | + return -5; | |
98 | + } | |
99 | + | |
100 | + tls_version_major = data[1]; | |
101 | + tls_version_minor = data[2]; | |
102 | + if (tls_version_major < 3) { | |
103 | + if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which which can not support SNI.\n", | |
104 | + tls_version_major, tls_version_minor); | |
105 | + | |
106 | + return -2; | |
107 | + } | |
108 | + | |
109 | + /* TLS record length */ | |
110 | + len = ((unsigned char)data[3] << 8) + | |
111 | + (unsigned char)data[4] + TLS_HEADER_LEN; | |
112 | + data_len = MIN(data_len, len); | |
113 | + | |
114 | + /* Check we received entire TLS record length */ | |
115 | + if (data_len < len) | |
116 | + return -1; | |
117 | + | |
118 | + /* | |
119 | + * Handshake | |
120 | + */ | |
121 | + if (pos + 1 > data_len) { | |
122 | + return -5; | |
123 | + } | |
124 | + if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) { | |
125 | + if (verbose) fprintf(stderr, "Not a client hello\n"); | |
126 | + | |
127 | + return -5; | |
128 | + } | |
129 | + | |
130 | + /* Skip past fixed length records: | |
131 | + 1 Handshake Type | |
132 | + 3 Length | |
133 | + 2 Version (again) | |
134 | + 32 Random | |
135 | + to Session ID Length | |
136 | + */ | |
137 | + pos += 38; | |
138 | + | |
139 | + /* Session ID */ | |
140 | + if (pos + 1 > data_len) | |
141 | + return -5; | |
142 | + len = (unsigned char)data[pos]; | |
143 | + pos += 1 + len; | |
144 | + | |
145 | + /* Cipher Suites */ | |
146 | + if (pos + 2 > data_len) | |
147 | + return -5; | |
148 | + len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; | |
149 | + pos += 2 + len; | |
150 | + | |
151 | + /* Compression Methods */ | |
152 | + if (pos + 1 > data_len) | |
153 | + return -5; | |
154 | + len = (unsigned char)data[pos]; | |
155 | + pos += 1 + len; | |
156 | + | |
157 | + if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) { | |
158 | + if (verbose) fprintf(stderr, "Received SSL 3.0 handshake without extensions\n"); | |
159 | + return -2; | |
160 | + } | |
161 | + | |
162 | + /* Extensions */ | |
163 | + if (pos + 2 > data_len) | |
164 | + return -5; | |
165 | + len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; | |
166 | + pos += 2; | |
167 | + | |
168 | + if (pos + len > data_len) | |
169 | + return -5; | |
170 | + return parse_extensions(data + pos, len, hostname); | |
171 | +} | |
172 | + | |
173 | +int | |
174 | +parse_extensions(const char *data, size_t data_len, char **hostname) { | |
175 | + size_t pos = 0; | |
176 | + size_t len; | |
177 | + | |
178 | + /* Parse each 4 bytes for the extension header */ | |
179 | + while (pos + 4 <= data_len) { | |
180 | + /* Extension Length */ | |
181 | + len = ((unsigned char)data[pos + 2] << 8) + | |
182 | + (unsigned char)data[pos + 3]; | |
183 | + | |
184 | + /* Check if it's a server name extension */ | |
185 | + if (data[pos] == 0x00 && data[pos + 1] == 0x00) { | |
186 | + /* There can be only one extension of each type, so we break | |
187 | + our state and move p to beinnging of the extension here */ | |
188 | + if (pos + 4 + len > data_len) | |
189 | + return -5; | |
190 | + return parse_server_name_extension(data + pos + 4, len, hostname); | |
191 | + } | |
192 | + pos += 4 + len; /* Advance to the next extension header */ | |
193 | + } | |
194 | + /* Check we ended where we expected to */ | |
195 | + if (pos != data_len) | |
196 | + return -5; | |
197 | + | |
198 | + return -2; | |
199 | +} | |
200 | + | |
201 | +int | |
202 | +parse_server_name_extension(const char *data, size_t data_len, | |
203 | + char **hostname) { | |
204 | + size_t pos = 2; /* skip server name list length */ | |
205 | + size_t len; | |
206 | + | |
207 | + while (pos + 3 < data_len) { | |
208 | + len = ((unsigned char)data[pos + 1] << 8) + | |
209 | + (unsigned char)data[pos + 2]; | |
210 | + | |
211 | + if (pos + 3 + len > data_len) | |
212 | + return -5; | |
213 | + | |
214 | + switch (data[pos]) { /* name type */ | |
215 | + case 0x00: /* host_name */ | |
216 | + *hostname = malloc(len + 1); | |
217 | + if (*hostname == NULL) { | |
218 | + if (verbose) fprintf(stderr, "malloc() failure\n"); | |
219 | + return -4; | |
220 | + } | |
221 | + | |
222 | + strncpy(*hostname, data + pos + 3, len); | |
223 | + | |
224 | + (*hostname)[len] = '\0'; | |
225 | + | |
226 | + return len; | |
227 | + default: | |
228 | + if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n", | |
229 | + data[pos]); | |
230 | + } | |
231 | + pos += 3 + len; | |
232 | + } | |
233 | + /* Check we ended where we expected to */ | |
234 | + if (pos != data_len) | |
235 | + return -5; | |
236 | + | |
237 | + return -2; | |
238 | +} |
tls.h | ||
---|---|---|
@@ -1,0 +1,33 @@ | ||
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 | + | |
28 | + | |
29 | + | |
30 | + | |
31 | +int parse_tls_header(const char *data, size_t data_len, char **hostname); | |
32 | + | |
33 | + |
Built with git-ssb-web