Mercurial > hg > quicktun-tcp
comparison protocol.c @ 0:17cb7cdbb8be draft default tip
Working prototype
author | Ivo Smits <Ivo@UCIS.nl> |
---|---|
date | Fri, 07 Feb 2014 23:28:39 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:17cb7cdbb8be |
---|---|
1 /* Copyright 2014 Ivo Smits <Ivo@UCIS.nl>. All rights reserved. | |
2 Redistribution and use in source and binary forms, with or without modification, are | |
3 permitted provided that the following conditions are met: | |
4 | |
5 1. Redistributions of source code must retain the above copyright notice, this list of | |
6 conditions and the following disclaimer. | |
7 | |
8 2. Redistributions in binary form must reproduce the above copyright notice, this list | |
9 of conditions and the following disclaimer in the documentation and/or other materials | |
10 provided with the distribution. | |
11 | |
12 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
13 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
14 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR | |
15 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
16 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
17 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
18 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
19 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
20 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
21 | |
22 The views and conclusions contained in the software and documentation are those of the | |
23 authors and should not be interpreted as representing official policies, either expressed | |
24 or implied, of Ivo Smits.*/ | |
25 | |
26 #include <stdio.h> | |
27 #include <stdbool.h> | |
28 #include <unistd.h> | |
29 #include <fcntl.h> | |
30 #include <string.h> | |
31 #include <sys/types.h> | |
32 #include <sodium/crypto_box_curve25519xsalsa20poly1305.h> | |
33 #include <sodium/crypto_scalarmult_curve25519.h> | |
34 #include <sodium/crypto_hash_sha512.h> | |
35 #include "include.h" | |
36 | |
37 static unsigned char workbuffer[2000]; | |
38 | |
39 static int devurandomfd = -1; | |
40 static bool randombytes(unsigned char* buffer, int len) { | |
41 if (devurandomfd == -1) devurandomfd = open("/dev/urandom", O_RDONLY); | |
42 if (devurandomfd == -1) return errorexit("could not open /dev/urandom"); | |
43 while (len > 0) { | |
44 int got = read(devurandomfd, buffer, len); | |
45 if (got < 0) return errorexitp("could not read from /dev/urandom"); | |
46 buffer += got; | |
47 len -= got; | |
48 } | |
49 return true; | |
50 } | |
51 | |
52 static void dumphex(char* lbl, unsigned char* buffer, int len) { | |
53 fprintf(stderr, "%s: ", lbl); | |
54 for (; len > 0; len--, buffer++) fprintf(stderr, "%02x", *buffer); | |
55 fprintf(stderr, "\n"); | |
56 } | |
57 | |
58 static void nonceinc(unsigned char* nonce) { | |
59 int i; | |
60 for (i = 23; i >= 0 && (++nonce[i] == 0); i--); | |
61 } | |
62 | |
63 static bool send_all(int socket, unsigned char* buffer, int length) { | |
64 while (length) { | |
65 int sent = write(socket, buffer, length); | |
66 if (sent <= 0) return errorexitp("socket write failed"); | |
67 buffer += sent; | |
68 length -= sent; | |
69 } | |
70 return true; | |
71 } | |
72 static bool send_packet(connection_context* context, unsigned char* buffer, int length, bool controlflag) { | |
73 fprintf(stderr, "Send packet (size=%d type=%d control=%d encrypt=%d)\n", length, buffer[0], controlflag, context->send_encrypted); | |
74 if (context->send_encrypted) { | |
75 if (length + 32 > sizeof(workbuffer)) return errorexit("packet too big for encryption buffer"); | |
76 memmove(workbuffer + 32, buffer, length); | |
77 memset(workbuffer, 0, 32); | |
78 crypto_box_curve25519xsalsa20poly1305_afternm(workbuffer, workbuffer, length + 32, context->send_nonce, context->send_key); | |
79 nonceinc(context->send_nonce); | |
80 buffer = workbuffer + 16; | |
81 length += 16; | |
82 } | |
83 if (length > 0x7FFF) return errorexit("packet too big for framing"); | |
84 unsigned char lbuf[2]; | |
85 length = (length & 0x7FFF) | (controlflag ? 0x8000 : 0); | |
86 lbuf[0] = (length >> 8) & 0xFF; | |
87 lbuf[1] = length & 0xFF; | |
88 if (!send_all(context->send_socket, lbuf, 2)) return false; | |
89 if (!send_all(context->send_socket, buffer, length & 0x7FFF)) return false; | |
90 return true; | |
91 } | |
92 static bool send_command_ack(connection_context* context, unsigned char code) { | |
93 workbuffer[0] = code; | |
94 return send_packet(context, workbuffer, 1, true); | |
95 } | |
96 | |
97 | |
98 static bool check_tunnel_ready(connection_context* context) { | |
99 if (context->local_tunnelready) return true; | |
100 if (context->require_key_authentication) return true; | |
101 if (context->require_password_authentication) return true; | |
102 if (context->require_encryption && !context->recv_encrypted) return true; | |
103 if (!send_command_ack(context, 0)) return false; | |
104 context->local_tunnelready = true; | |
105 return true; | |
106 } | |
107 | |
108 static bool process_network_packet(connection_context* context, unsigned char* buffer, int pktlen, int controlflag); | |
109 bool connection_read(connection_context* context) { | |
110 int got = read(context->recv_socket, context->recv_buffer + context->recv_offset, sizeof(context->recv_buffer) - context->recv_offset); | |
111 if (got < 0) return errorexitp("read failure on socket"); | |
112 context->recv_offset += got; | |
113 while (context->recv_offset >= 2) { | |
114 int pktlen = ((unsigned char)context->recv_buffer[0] << 8) | (unsigned char)context->recv_buffer[1]; | |
115 int controlflag = pktlen & 0x8000; | |
116 pktlen &= 0x7FFF; | |
117 if (pktlen > sizeof(context->recv_buffer)) return errorexit("received packet too big for buffer"); | |
118 if (context->recv_offset < pktlen + 2) break; | |
119 if (!process_network_packet(context, context->recv_buffer + 2, pktlen, !!controlflag)) return false; | |
120 context->recv_offset -= 2 + pktlen; | |
121 memmove(context->recv_buffer, context->recv_buffer + 2 + pktlen, context->recv_offset); | |
122 } | |
123 if (!context->local_tunnelready) if (!check_tunnel_ready(context)) return false; | |
124 return true; | |
125 } | |
126 static bool send_start_crypt_auth(connection_context* context, const unsigned char* key) { | |
127 if (!context->key_updated) return context->startcryptauthsent && !key; | |
128 if (key) { | |
129 memcpy(context->local_seckey_next, key, 32); | |
130 } else { | |
131 randombytes(context->local_seckey_next, 32); | |
132 } | |
133 workbuffer[0] = 1; | |
134 crypto_scalarmult_curve25519_base(workbuffer + 1, context->local_seckey_next); | |
135 memcpy(workbuffer + 1 + 32, context->nonce_next, 24); | |
136 if (!send_packet(context, workbuffer, 1 + 32 + 24, true)) return false; | |
137 crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next); | |
138 memcpy(context->send_nonce + 12, context->nonce_next + 12, 12); | |
139 context->send_encrypted = true; | |
140 context->startcryptauthsent = true; | |
141 context->key_updated = false; | |
142 return true; | |
143 } | |
144 static bool begin_update_key(connection_context* context, char* seckey) { | |
145 if (!context->key_updated) return !seckey; | |
146 if (seckey) { | |
147 memcpy(context->local_seckey_next, seckey, 32); | |
148 } else { | |
149 randombytes(context->local_seckey_next, 32); | |
150 } | |
151 workbuffer[0] = 3; | |
152 crypto_scalarmult_curve25519_base(workbuffer + 1, context->local_seckey_next); | |
153 if (!send_packet(context, workbuffer, 1 + 32, true)) return false; | |
154 crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next); | |
155 context->send_encrypted = true; | |
156 context->key_updated = false; | |
157 return true; | |
158 } | |
159 static bool password_hash(unsigned char* output, unsigned char* salt, int saltlength, char* password) { | |
160 int pwlen = password ? strlen(password) : 0; | |
161 if (saltlength + pwlen > sizeof(workbuffer)) return errorexit("password and salt too big for buffer"); | |
162 if (saltlength) memmove(workbuffer, salt, saltlength); | |
163 if (pwlen) memcpy(workbuffer + saltlength, password, pwlen); | |
164 crypto_hash_sha512(output, workbuffer, saltlength + pwlen); | |
165 return true; | |
166 } | |
167 static bool process_network_packet(connection_context* context, unsigned char* buffer, int pktlen, int controlflag) { | |
168 if (context->recv_encrypted) { | |
169 if (pktlen + 16 > sizeof(workbuffer)) return errorexit("received packet too big for decrypt buffer"); | |
170 memmove(workbuffer + 16, buffer, pktlen); | |
171 memset(workbuffer, 0, 16); | |
172 if (crypto_box_curve25519xsalsa20poly1305_open_afternm(workbuffer, workbuffer, pktlen + 16, context->recv_nonce, context->recv_key)) return false; | |
173 nonceinc(context->recv_nonce); | |
174 buffer = workbuffer + 32; | |
175 pktlen -= 16; | |
176 } | |
177 if (controlflag) { | |
178 fprintf(stderr, "Received packet (size=%d type=%d control=%d encrypt=%d)\n", pktlen, buffer[0], controlflag, context->recv_encrypted); | |
179 if (pktlen < 1) return errorexit("zero length control packet"); | |
180 switch (buffer[0]) { | |
181 case 0: | |
182 fprintf(stderr, "Control: tunnel ready\n"); | |
183 context->remote_tunnelready = true; | |
184 break; | |
185 case 1: | |
186 fprintf(stderr, "Control: crypto auth req\n"); | |
187 if (pktlen < 1 + 32 + 24) return errorexit("short control packet 1"); | |
188 if (context->require_key_authentication) { | |
189 if (memcmp(buffer + 1, context->remote_pubkey_expect, 32)) return errorexit("incorrect crypto auth key"); | |
190 context->require_key_authentication = true; | |
191 } | |
192 if (!context->startcryptauthsent) send_start_crypt_auth(context, NULL); | |
193 if (!send_command_ack(context, 2)) return false; | |
194 memcpy(context->remote_pubkey, buffer + 1, 32); | |
195 memcpy(context->send_nonce, buffer + 1 + 32, 12); | |
196 memcpy(context->recv_nonce + 12, buffer + 1 + 32 + 12, 12); | |
197 crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current); | |
198 crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next); | |
199 context->recv_encrypted = context->send_encrypted = true; | |
200 break; | |
201 case 2: | |
202 fprintf(stderr, "Control: crypto auth ack\n"); | |
203 memcpy(context->local_seckey_current, context->local_seckey_next, 32); | |
204 memcpy(context->recv_nonce, context->nonce_next, 12); | |
205 crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current); | |
206 context->recv_encrypted = true; | |
207 context->key_updated = true; | |
208 begin_update_key(context, NULL); | |
209 break; | |
210 case 3: | |
211 fprintf(stderr, "Control: key update req\n"); | |
212 if (pktlen < 1 + 32) return errorexit("short control packet 3"); | |
213 if (!send_command_ack(context, 4)) return false; | |
214 memcpy(context->remote_pubkey, buffer + 1, 32); | |
215 crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current); | |
216 crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next); | |
217 context->recv_encrypted = context->send_encrypted = true; | |
218 break; | |
219 case 4: | |
220 fprintf(stderr, "Control: key update ack\n"); | |
221 memcpy(context->local_seckey_current, context->local_seckey_next, 32); | |
222 crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current); | |
223 context->recv_encrypted = true; | |
224 context->key_updated = true; | |
225 break; | |
226 case 5: | |
227 fprintf(stderr, "Control: password authentication req\n"); | |
228 unsigned char pwhash[64]; | |
229 if (!password_hash(pwhash, buffer + 1, pktlen - 1, context->password)) return false; | |
230 workbuffer[0] = 6; | |
231 memcpy(workbuffer + 1, pwhash, 64); | |
232 if (!send_packet(context, workbuffer, 1 + 64, true)) return false; | |
233 break; | |
234 case 6: | |
235 fprintf(stderr, "Control: password authentication ack\n"); | |
236 if (pktlen < 1 + 64) return errorexit("short control packet 6");; | |
237 char pwhashcheck[64]; | |
238 memcpy(pwhashcheck, buffer + 1, 64); | |
239 if (!password_hash(pwhash, context->nonce_next, 12, context->password)) return false; | |
240 if (memcmp(pwhash, pwhashcheck, 64)) return errorexit("incorrect password auth");; | |
241 context->require_password_authentication = false; | |
242 break; | |
243 case 7: | |
244 fprintf(stderr, "Control: disable encryption req\n"); | |
245 context->recv_encrypted = context->send_encrypted = false; | |
246 if (!send_command_ack(context, 8)) return false; | |
247 break; | |
248 case 8: | |
249 fprintf(stderr, "Control: disable encryption ack\n"); | |
250 context->recv_encrypted = false; | |
251 break; | |
252 case 9: | |
253 fprintf(stderr, "Control: echo req\n"); | |
254 if (pktlen > sizeof(workbuffer)) return errorexit("echo request too big");; | |
255 memmove(workbuffer, buffer, pktlen); | |
256 workbuffer[0] = 10; | |
257 if (!send_packet(context, workbuffer, pktlen, true)) return false; | |
258 break; | |
259 case 10: | |
260 fprintf(stderr, "Control: echo resp\n"); | |
261 context->pong = true; | |
262 break; | |
263 case 13: | |
264 if (!context->local_tunnelready || !context->remote_tunnelready) return errorexit("received data packet while tunnel not ready");; | |
265 if (context->tunnel && !tunnel_write_data(context->tunnel, buffer + 1, pktlen - 1)) return false; | |
266 break; | |
267 case 11: | |
268 case 12: | |
269 case 17: | |
270 case 81: | |
271 break; | |
272 default: | |
273 fprintf(stderr, "Unknown control type %d\n", buffer[0]); | |
274 break; | |
275 } | |
276 } else { | |
277 if (!context->local_tunnelready || !context->remote_tunnelready) return errorexit("received data packet while tunnel not ready");; | |
278 fprintf(stderr, "Tunnel data %d\n", pktlen); | |
279 if (context->tunnel && !tunnel_write_data(context->tunnel, buffer, pktlen)) return false; | |
280 } | |
281 return true; | |
282 } | |
283 | |
284 bool connection_ping(connection_context* context) { | |
285 if (!context->pong) return false; | |
286 context->pong = false; | |
287 send_command_ack(context, 9); | |
288 return true; | |
289 } | |
290 | |
291 bool connection_init(connection_context* context) { | |
292 memset(context, 0, sizeof(connection_context)); | |
293 char localpubkey[32]; | |
294 crypto_scalarmult_curve25519_base(context->remote_pubkey, context->local_seckey_current); | |
295 randombytes(context->nonce_next, 24); | |
296 context->key_updated = true; | |
297 context->pong = true; | |
298 return true; | |
299 } | |
300 bool connection_init_socket(connection_context* context, const int recvsocket, const int sendsocket) { | |
301 context->recv_socket = recvsocket; | |
302 context->send_socket = sendsocket; | |
303 if (!send_packet(context, (unsigned char*)"QUICKTUN", 8, true)) return false; | |
304 return true; | |
305 } | |
306 bool connection_init_encryption(connection_context* context, const unsigned char* localseckey, const unsigned char* remotepubkey) { | |
307 if (remotepubkey) { | |
308 memcpy(context->remote_pubkey_expect, remotepubkey, 32); | |
309 context->require_key_authentication = true; | |
310 } | |
311 context->require_encryption = true; | |
312 if (!send_start_crypt_auth(context, localseckey)) return false; | |
313 return true; | |
314 } | |
315 bool connection_init_passwordauth(connection_context* context, char* password) { | |
316 context->password = password; | |
317 context->require_password_authentication = true; | |
318 workbuffer[0] = 5; | |
319 memcpy(workbuffer + 1, context->nonce_next, 12); | |
320 if (!send_packet(context, workbuffer, 1 + 12, true)) return false; | |
321 return true; | |
322 } | |
323 | |
324 bool connection_write_data(connection_context* context, unsigned char* buffer, int len) { | |
325 return send_packet(context, buffer, len, false); | |
326 } | |
327 | |
328 bool connection_init_done(connection_context* context) { | |
329 return check_tunnel_ready(context); | |
330 } | |
331 | |
332 bool connection_update_key(connection_context* context) { | |
333 return begin_update_key(context, NULL); | |
334 } | |
335 |