2 * Open AnyConnect (SSL + DTLS) client
4 * © 2008 David Woodhouse <dwmw2@infradead.org>
6 * Permission to use, copy, modify, and/or distribute this software
7 * for any purpose with or without fee is hereby granted, provided
8 * that the above copyright notice and this permission notice appear
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
12 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
13 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
14 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
15 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
16 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
17 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 #include <sys/types.h>
23 #include <sys/socket.h>
26 #include <openssl/err.h>
29 #include "anyconnect.h"
32 * The master-secret is generated randomly by the client. The server
33 * responds with a DTLS Session-ID. These, done over the HTTPS
34 * connection, are enough to 'resume' a DTLS session, bypassing all
35 * the normal setup of a normal DTLS connection.
37 * Cisco use a version of the protocol which predates RFC4347, but
38 * isn't quite the same as the pre-RFC version of the protocol which
39 * was in OpenSSL 0.9.8e -- it includes backports of some later
42 * The openssl/ directory of this source tree should contain both a
43 * small patch against OpenSSL 0.9.8e to make it support Cisco's
44 * snapshot of the protocol, and a larger patch against newer OpenSSL
45 * which gives us an option to use the old protocol again.
47 * Cisco's server also seems to respond to the official version of the
48 * protocol, with a change in the ChangeCipherSpec packet which implies
49 * that it does know the difference and isn't just repeating the version
50 * number seen in the ClientHello. But although I can make the handshake
51 * complete by hacking tls1_mac() to use the _old_ protocol version
52 * number when calculating the MAC, the server still seems to be ignoring
53 * my subsequent data packets.
56 static unsigned char nybble(unsigned char n)
58 if (n >= '0' && n <= '9') return n - '0';
59 else if (n >= 'A' && n <= 'F') return n - ('A' - 10);
60 else if (n >= 'a' && n <= 'f') return n - ('a' - 10);
64 static unsigned char hex(const char *data)
66 return (nybble(data[0]) << 4) | nybble(data[1]);
69 static int connect_dtls_socket(struct anyconnect_info *vpninfo, SSL **ret_ssl,
72 SSL_METHOD *dtls_method;
74 SSL_SESSION *dtls_session;
75 SSL_CIPHER *https_cipher;
81 dtls_fd = socket(vpninfo->peer_addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
83 perror("Open UDP socket for DTLS:");
87 if (connect(dtls_fd, vpninfo->peer_addr, vpninfo->peer_addrlen)) {
88 perror("UDP (DTLS) connect:\n");
93 fcntl(dtls_fd, F_SETFD, FD_CLOEXEC);
95 dtls_method = DTLSv1_client_method();
96 dtls_ctx = SSL_CTX_new(dtls_method);
97 SSL_CTX_set_read_ahead(dtls_ctx, 1);
98 https_cipher = SSL_get_current_cipher(vpninfo->https_ssl);
100 dtls_ssl = SSL_new(dtls_ctx);
101 SSL_set_connect_state(dtls_ssl);
102 SSL_set_cipher_list(dtls_ssl, SSL_CIPHER_get_name(https_cipher));
104 /* We're going to "resume" a session which never existed. Fake it... */
105 dtls_session = SSL_SESSION_new();
107 dtls_session->ssl_version = 0x0100; // DTLS1_BAD_VER
109 dtls_session->master_key_length = sizeof(vpninfo->dtls_secret);
110 memcpy(dtls_session->master_key, vpninfo->dtls_secret,
111 sizeof(vpninfo->dtls_secret));
113 dtls_session->session_id_length = sizeof(vpninfo->dtls_session_id);
114 memcpy(dtls_session->session_id, vpninfo->dtls_session_id,
115 sizeof(vpninfo->dtls_session_id));
117 dtls_session->cipher = https_cipher;
118 dtls_session->cipher_id = https_cipher->id;
120 /* Having faked a session, add it to the CTX and the SSL */
121 if (!SSL_set_session(dtls_ssl, dtls_session)) {
122 printf("SSL_set_session() failed with old protocol version 0x%x\n",
123 dtls_session->ssl_version);
124 printf("Your OpenSSL may lack Cisco compatibility support\n");
125 printf("See http://rt.openssl.org/Ticket/Display.html?id=1751\n");
126 printf("Use the --no-dtls command line option to avoid this message\n");
129 if (!SSL_CTX_add_session(dtls_ctx, dtls_session))
130 printf("SSL_CTX_add_session() failed\n");
134 dtls_bio = BIO_new_socket(dtls_fd, BIO_NOCLOSE);
135 SSL_set_bio(dtls_ssl, dtls_bio, dtls_bio);
137 /* XXX Cargo cult programming. Other DTLS code does this, and it might
138 avoid http://rt.openssl.org/Ticket/Display.html?id=1703 */
139 BIO_ctrl(dtls_bio, BIO_CTRL_DGRAM_MTU_DISCOVER, 0, NULL);
141 #ifndef SSL_OP_CISCO_ANYCONNECT
142 #define SSL_OP_CISCO_ANYCONNECT 0x8000
144 SSL_set_options(dtls_ssl, SSL_OP_CISCO_ANYCONNECT);
145 ret = SSL_do_handshake(dtls_ssl);
148 fprintf(stderr, "DTLS connection returned %d\n", ret);
150 fprintf(stderr, "DTLS handshake error: %d\n", SSL_get_error(dtls_ssl, ret));
151 ERR_print_errors_fp(stderr);
153 SSL_CTX_free(dtls_ctx);
158 BIO_set_nbio(SSL_get_rbio(dtls_ssl),1);
159 BIO_set_nbio(SSL_get_wbio(dtls_ssl),1);
161 fcntl(dtls_fd, F_SETFL, fcntl(dtls_fd, F_GETFL) | O_NONBLOCK);
169 static int dtls_rekey(struct anyconnect_info *vpninfo)
174 /* To rekey, we just 'resume' the session again */
175 if (connect_dtls_socket(vpninfo, &dtls_ssl, &dtls_fd))
178 vpninfo->pfds[vpninfo->dtls_pfd].fd = dtls_fd;
180 SSL_free(vpninfo->dtls_ssl);
181 close(vpninfo->dtls_fd);
183 vpninfo->dtls_ssl = dtls_ssl;
184 vpninfo->dtls_fd = dtls_fd;
189 int setup_dtls(struct anyconnect_info *vpninfo)
191 struct vpn_option *dtls_opt = vpninfo->dtls_options;
192 int sessid_found = 0;
198 printf("DTLS option %s : %s\n", dtls_opt->option, dtls_opt->value);
200 if (!strcmp(dtls_opt->option, "X-DTLS-Session-ID")) {
201 if (strlen(dtls_opt->value) != 64) {
202 fprintf(stderr, "X-DTLS-Session-ID not 64 characters\n");
203 fprintf(stderr, "Is: %s\n", dtls_opt->value);
206 for (i = 0; i < 64; i += 2)
207 vpninfo->dtls_session_id[i/2] = hex(dtls_opt->value + i);
209 } else if (!strcmp(dtls_opt->option + 7, "Port")) {
210 dtls_port = atol(dtls_opt->value);
211 } else if (!strcmp(dtls_opt->option + 7, "Keepalive")) {
212 vpninfo->dtls_keepalive = atol(dtls_opt->value);
213 } else if (!strcmp(dtls_opt->option + 7, "DPD")) {
214 vpninfo->dtls_dpd = atol(dtls_opt->value);
215 } else if (!strcmp(dtls_opt->option + 7, "Rekey-Time")) {
216 vpninfo->dtls_rekey = atol(dtls_opt->value);
219 dtls_opt = dtls_opt->next;
221 if (!sessid_found || !dtls_port)
224 if (vpninfo->peer_addr->sa_family == AF_INET) {
225 struct sockaddr_in *sin = (void *)vpninfo->peer_addr;
226 sin->sin_port = htons(dtls_port);
227 } else if (vpninfo->peer_addr->sa_family == AF_INET6) {
228 struct sockaddr_in6 *sin = (void *)vpninfo->peer_addr;
229 sin->sin6_port = htons(dtls_port);
231 fprintf(stderr, "Unknown protocol family %d. Cannot do DTLS\n",
232 vpninfo->peer_addr->sa_family);
236 if (connect_dtls_socket(vpninfo, &vpninfo->dtls_ssl, &vpninfo->dtls_fd))
239 vpninfo->dtls_pfd = vpn_add_pollfd(vpninfo, vpninfo->dtls_fd,
240 POLLIN|POLLHUP|POLLERR);
241 vpninfo->last_dtls_rekey = vpninfo->last_dtls_rx =
242 vpninfo->last_dtls_tx = time(NULL);
245 printf("DTLS connected. DPD %d, Keepalive %d\n",
246 vpninfo->dtls_dpd, vpninfo->dtls_keepalive);
251 int dtls_mainloop(struct anyconnect_info *vpninfo, int *timeout)
253 unsigned char buf[2000];
257 while ( (len = SSL_read(vpninfo->dtls_ssl, buf, sizeof(buf))) > 0 ) {
259 printf("Received DTLS packet 0x%02x of %d bytes\n",
262 vpninfo->last_dtls_rx = time(NULL);
266 queue_new_packet(&vpninfo->incoming_queue, AF_INET, buf+1, len-1);
270 case 4: /* DPD response */
272 printf("Got DTLS DPD response\n");
276 fprintf(stderr, "Unknown DTLS packet type %02x\n", buf[0]);
277 vpninfo->quit_reason = "Unknown packet received";
281 while (vpninfo->outgoing_queue) {
282 struct pkt *this = vpninfo->outgoing_queue;
285 vpninfo->outgoing_queue = this->next;
287 /* One byte of header */
288 this->hdr[7] = AC_PKT_DATA;
290 ret = SSL_write(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
291 /* There's not a lot we can do if the write fails. If the link is
292 really dead, DPD will kick in and we should fall back to SSL,
293 if that's still working */
294 vpninfo->last_dtls_tx = time(NULL);
296 printf("Sent DTLS packet of %d bytes; SSL_write() returned %d\n",
301 /* DPD is bidirectional -- PKT 3 out, PKT 4 back */
302 if (vpninfo->dtls_dpd) {
303 time_t now = time(NULL);
304 time_t due = vpninfo->last_dtls_rx + vpninfo->dtls_dpd;
305 time_t overdue = vpninfo->last_dtls_rx + (2 * vpninfo->dtls_dpd);
307 /* If we already have DPD outstanding, don't flood */
308 if (vpninfo->last_dtls_dpd > vpninfo->last_dtls_rx) {
310 printf("DTLS DPD outstanding. Will kill in %ld seconds\n",
313 due = vpninfo->last_dtls_dpd + vpninfo->dtls_dpd;
316 fprintf(stderr, "DTLS Dead Peer Detection detected dead peer!\n");
317 /* Fall back to SSL */
318 SSL_free(vpninfo->dtls_ssl);
319 close(vpninfo->dtls_fd);
320 vpninfo->pfds[vpninfo->dtls_pfd].fd = -1;
321 vpninfo->dtls_ssl = NULL;
322 vpninfo->dtls_fd = -1;
326 static unsigned char dtls_dpd_pkt[1] = { 3 };
327 /* Haven't heard anything from the other end for a while.
328 Check if it's still there */
329 /* FIXME: If isn't, we should act on that */
330 SSL_write(vpninfo->dtls_ssl, dtls_dpd_pkt, 1);
331 vpninfo->last_dtls_dpd = vpninfo->last_dtls_tx = now;
333 due = now + vpninfo->dtls_dpd;
335 printf("Sent DTLS DPD\n");
338 if (verbose && due < overdue)
339 printf("Next DTLS DPD due in %ld seconds\n", (due - now));
340 if (*timeout > (due - now) * 1000)
341 *timeout = (due - now) * 1000;
344 /* Keepalive is just client -> server */
345 if (vpninfo->dtls_keepalive) {
346 time_t now = time(NULL);
347 time_t due = vpninfo->last_dtls_tx + vpninfo->dtls_keepalive;
350 static unsigned char dtls_keepalive_pkt[1] = { 7 };
352 /* Send something (which is discarded), to keep
353 the connection alive. */
354 SSL_write(vpninfo->dtls_ssl, dtls_keepalive_pkt, 1);
355 vpninfo->last_dtls_tx = now;
357 due = now + vpninfo->dtls_keepalive;
359 printf("Sent DTLS Keepalive\n");
363 printf("Next DTLS Keepalive due in %ld seconds\n", (due - now));
364 if (*timeout > (due - now) * 1000)
365 *timeout = (due - now) * 1000;
368 if (vpninfo->dtls_rekey) {
369 time_t now = time(NULL);
370 time_t due = vpninfo->last_dtls_rekey + vpninfo->dtls_rekey;
374 printf("DTLS rekey due\n");
375 if (dtls_rekey(vpninfo)) {
376 fprintf(stderr, "DTLS rekey failed\n");
377 /* Fall back to SSL */
378 SSL_free(vpninfo->dtls_ssl);
379 close(vpninfo->dtls_fd);
380 vpninfo->pfds[vpninfo->dtls_pfd].fd = -1;
381 vpninfo->dtls_ssl = NULL;
382 vpninfo->dtls_fd = -1;
385 vpninfo->last_dtls_rekey = time(NULL);
386 due = vpninfo->last_dtls_rekey + vpninfo->dtls_rekey;
389 printf("Next DTLS rekey due in %ld seconds\n", (due - now));
390 if (*timeout > (due - now) * 1000)
391 *timeout = (due - now) * 1000;