Add readahead to UDP socket, to avoid throwing away ends of packets.
[platform/upstream/openconnect.git] / dtls.c
1 /*
2  * Open AnyConnect (SSL + DTLS) client
3  *
4  * © 2008 David Woodhouse <dwmw2@infradead.org>
5  *
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
9  * in all copies.
10  *
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.
19  */
20
21 #include <errno.h>
22 #include <sys/types.h>
23 #include <sys/socket.h>
24 #include <netdb.h>
25 #include <unistd.h>
26 #include <openssl/err.h>
27
28 #include "anyconnect.h"
29
30 /*
31  * The master-secret is generated randomly by the client. The server
32  * responds with a DTLS Session-ID. These, done over the HTTPS
33  * connection, are enough to 'resume' a DTLS session, bypassing all
34  * the normal setup of a normal DTLS connection.
35  * 
36  * Cisco's own client uses an old version of OpenSSL, which implements
37  * the pre-RFC version of DTLS. I haven't been able to get it working 
38  * when I force it to link against any of my own builds of OpenSSL.
39  *
40  * Hopefully, it'll just work when I get round to implementing it
41  * here, either with the system OpenSSL, or linking against their
42  * library (which will at least be progress, and make it a little
43  * easier to debug.
44  */   
45
46 static unsigned char nybble(unsigned char n)
47 {
48         if      (n >= '0' && n <= '9') return n - '0';
49         else if (n >= 'A' && n <= 'F') return n - ('A' - 10);
50         else if (n >= 'a' && n <= 'f') return n - ('a' - 10);
51         return 0;
52 }
53
54 static unsigned char hex(const char *data)
55 {
56         return (nybble(data[0]) << 4) | nybble(data[1]);
57 }
58
59 static int connect_dtls_socket(struct anyconnect_info *vpninfo, int dtls_port)
60 {
61         SSL_METHOD *dtls_method;
62         SSL_CTX *dtls_ctx;
63         SSL_SESSION *dtls_session;
64         SSL_CIPHER *https_cipher;
65         SSL *dtls_ssl;
66         BIO *dtls_bio;
67         int dtls_fd;
68
69         if (vpninfo->peer_addr->sa_family == AF_INET) {
70                 struct sockaddr_in *sin = (void *)vpninfo->peer_addr;
71                 sin->sin_port = htons(dtls_port);
72         } else if (vpninfo->peer_addr->sa_family == AF_INET6) {
73                 struct sockaddr_in6 *sin = (void *)vpninfo->peer_addr;
74                 sin->sin6_port = htons(dtls_port);
75         } else {
76                 fprintf(stderr, "Unknown protocol family %d. Cannot do DTLS\n",
77                         vpninfo->peer_addr->sa_family);
78                 return -EINVAL;
79         }
80
81         dtls_fd = socket(vpninfo->peer_addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
82         if (dtls_fd < 0) {
83                 perror("Open UDP socket for DTLS:");
84                 return -EINVAL;
85         }
86         
87         if (connect(dtls_fd, vpninfo->peer_addr, vpninfo->peer_addrlen)) {
88                 perror("UDP (DTLS) connect:\n");
89                 close(dtls_fd);
90                 return -EINVAL;
91         }
92
93         dtls_method = DTLSv1_client_method();
94         dtls_ctx = SSL_CTX_new(dtls_method);
95         SSL_CTX_set_read_ahead(dtls_ctx, 1);
96         https_cipher = SSL_get_current_cipher(vpninfo->https_ssl);
97
98         dtls_ssl = SSL_new(dtls_ctx);
99         SSL_set_connect_state(dtls_ssl);
100         SSL_set_cipher_list(dtls_ssl, SSL_CIPHER_get_name(https_cipher));
101         
102         /* We're going to "resume" a session which never existed. Fake it... */
103         dtls_session = SSL_SESSION_new();
104
105         dtls_session->ssl_version = DTLS1_VERSION;
106
107         dtls_session->master_key_length = sizeof(vpninfo->dtls_secret);
108         memcpy(dtls_session->master_key, vpninfo->dtls_secret,
109                sizeof(vpninfo->dtls_secret));
110
111         dtls_session->session_id_length = sizeof(vpninfo->dtls_session_id);
112         memcpy(dtls_session->session_id, vpninfo->dtls_session_id,
113                sizeof(vpninfo->dtls_session_id));
114
115         dtls_session->cipher = https_cipher;
116         dtls_session->cipher_id = https_cipher->id;
117
118         /* Having faked a session, add it to the CTX and the SSL */
119         if (!SSL_CTX_add_session(dtls_ctx, dtls_session))
120                 printf("SSL_CTX_add_session() failed\n");
121
122         if (!SSL_set_session(dtls_ssl, dtls_session))
123                 printf("SSL_set_session() failed\n");
124
125         /* Go Go Go! */
126         dtls_bio = BIO_new_socket(dtls_fd, BIO_NOCLOSE);
127         SSL_set_bio(dtls_ssl, dtls_bio, dtls_bio);
128
129         if (SSL_do_handshake(dtls_ssl)) {
130                 fprintf(stderr, "DTLS connection failure\n");
131                 ERR_print_errors_fp(stderr);
132                 SSL_free(dtls_ssl);
133                 SSL_CTX_free(dtls_ctx);
134                 close(dtls_fd);
135                 return -EINVAL;
136         }
137
138         vpninfo->dtls_fd = dtls_fd;
139         return 0;
140 }
141
142 int setup_dtls(struct anyconnect_info *vpninfo)
143 {
144         struct vpn_option *dtls_opt = vpninfo->dtls_options;
145         int sessid_found = 0;
146         int dtls_port = 0;
147         int i;
148
149         while (dtls_opt) {
150                 if (verbose)
151                         printf("DTLS option %s : %s\n", dtls_opt->option, dtls_opt->value);
152
153                 if (!strcmp(dtls_opt->option, "X-DTLS-Session-ID")) {
154                         if (strlen(dtls_opt->value) != 64) {
155                                 fprintf(stderr, "X-DTLS-Session-ID not 64 characters\n");
156                                 fprintf(stderr, "Is: %s\n", dtls_opt->value);
157                                 return -EINVAL;
158                         }
159                         for (i = 0; i < 64; i += 2)
160                                 vpninfo->dtls_session_id[i/2] = hex(dtls_opt->value + i);
161                         sessid_found = 1;
162                 } else if (!strcmp(dtls_opt->option, "X-DTLS-Port")) {
163                         dtls_port = atol(dtls_opt->value);
164                 } else if (!strcmp(dtls_opt->option, "X-DTLS-Keepalive")) {
165                         vpninfo->dtls_keepalive = atol(dtls_opt->value);
166                 }
167                         
168                 dtls_opt = dtls_opt->next;
169         }
170         if (!sessid_found || !dtls_port)
171                 return -EINVAL;
172
173         if (connect_dtls_socket(vpninfo, dtls_port))
174                 return -EINVAL;
175
176         /* No idea how to do this yet */
177         close(vpninfo->dtls_fd);
178         vpninfo->dtls_fd = -1;
179         return -EINVAL;
180 }
181
182 int dtls_mainloop(struct anyconnect_info *vpninfo, int *timeout)
183 {
184         return 0;
185 }
186
187