diff options
Diffstat (limited to 'nacl/curvecp/curvecpclient.c')
-rw-r--r-- | nacl/curvecp/curvecpclient.c | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/nacl/curvecp/curvecpclient.c b/nacl/curvecp/curvecpclient.c new file mode 100644 index 00000000..00793f00 --- /dev/null +++ b/nacl/curvecp/curvecpclient.c | |||
@@ -0,0 +1,476 @@ | |||
1 | #include <signal.h> | ||
2 | #include <sys/types.h> | ||
3 | #include <sys/stat.h> | ||
4 | #include <sys/wait.h> | ||
5 | #include <fcntl.h> | ||
6 | #include <poll.h> | ||
7 | #include <unistd.h> | ||
8 | #include "e.h" | ||
9 | #include "die.h" | ||
10 | #include "load.h" | ||
11 | #include "open.h" | ||
12 | #include "byte.h" | ||
13 | #include "socket.h" | ||
14 | #include "uint64_pack.h" | ||
15 | #include "uint64_unpack.h" | ||
16 | #include "nanoseconds.h" | ||
17 | #include "hexparse.h" | ||
18 | #include "nameparse.h" | ||
19 | #include "portparse.h" | ||
20 | #include "writeall.h" | ||
21 | #include "safenonce.h" | ||
22 | #include "randommod.h" | ||
23 | |||
24 | long long recent = 0; | ||
25 | |||
26 | #define NUMIP 8 | ||
27 | long long hellowait[NUMIP] = { | ||
28 | 1000000000 | ||
29 | , 1500000000 | ||
30 | , 2250000000 | ||
31 | , 3375000000 | ||
32 | , 5062500000 | ||
33 | , 7593750000 | ||
34 | , 11390625000 | ||
35 | , 17085937500 | ||
36 | } ; | ||
37 | |||
38 | #include "crypto_box.h" | ||
39 | #include "randombytes.h" | ||
40 | #if crypto_box_PUBLICKEYBYTES != 32 | ||
41 | error! | ||
42 | #endif | ||
43 | #if crypto_box_NONCEBYTES != 24 | ||
44 | error! | ||
45 | #endif | ||
46 | #if crypto_box_BOXZEROBYTES != 16 | ||
47 | error! | ||
48 | #endif | ||
49 | #if crypto_box_ZEROBYTES != 32 | ||
50 | error! | ||
51 | #endif | ||
52 | #if crypto_box_BEFORENMBYTES != 32 | ||
53 | error! | ||
54 | #endif | ||
55 | |||
56 | int flagverbose = 1; | ||
57 | |||
58 | #define USAGE "\ | ||
59 | curvecpclient: how to use:\n\ | ||
60 | curvecpclient: -q (optional): no error messages\n\ | ||
61 | curvecpclient: -Q (optional): print error messages (default)\n\ | ||
62 | curvecpclient: -v (optional): print extra information\n\ | ||
63 | curvecpclient: -c keydir (optional): use this public-key directory\n\ | ||
64 | curvecpclient: sname: server's name\n\ | ||
65 | curvecpclient: pk: server's public key\n\ | ||
66 | curvecpclient: ip: server's IP address\n\ | ||
67 | curvecpclient: port: server's UDP port\n\ | ||
68 | curvecpclient: ext: server's extension\n\ | ||
69 | curvecpclient: prog: run this client\n\ | ||
70 | " | ||
71 | |||
72 | void die_usage(const char *s) | ||
73 | { | ||
74 | if (s) die_4(100,USAGE,"curvecpclient: fatal: ",s,"\n"); | ||
75 | die_1(100,USAGE); | ||
76 | } | ||
77 | |||
78 | void die_fatal(const char *trouble,const char *d,const char *fn) | ||
79 | { | ||
80 | /* XXX: clean up? OS can do it much more reliably */ | ||
81 | if (!flagverbose) die_0(111); | ||
82 | if (d) { | ||
83 | if (fn) die_9(111,"curvecpclient: fatal: ",trouble," ",d,"/",fn,": ",e_str(errno),"\n"); | ||
84 | die_7(111,"curvecpclient: fatal: ",trouble," ",d,": ",e_str(errno),"\n"); | ||
85 | } | ||
86 | if (errno) die_5(111,"curvecpclient: fatal: ",trouble,": ",e_str(errno),"\n"); | ||
87 | die_3(111,"curvecpclient: fatal: ",trouble,"\n"); | ||
88 | } | ||
89 | |||
90 | int multiipparse(unsigned char *y,const char *x) | ||
91 | { | ||
92 | long long pos; | ||
93 | long long pos2; | ||
94 | long long ynum; | ||
95 | long long ypos; | ||
96 | long long j; | ||
97 | long long k; | ||
98 | long long d; | ||
99 | for (j = 0;j < 4 * NUMIP;++j) y[j] = 0; | ||
100 | ynum = 0; | ||
101 | while (ynum < 1000) { | ||
102 | ++ynum; | ||
103 | ypos = randommod(ynum); | ||
104 | for (k = 0;k < 4;++k) { | ||
105 | pos = ypos * 4 + k; | ||
106 | pos2 = (ynum - 1) * 4 + k; | ||
107 | if (pos >= 0 && pos < 4 * NUMIP && pos2 >= 0 && pos2 < 4 * NUMIP) y[pos2] = y[pos]; | ||
108 | d = 0; | ||
109 | for (j = 0;j < 3 && x[j] >= '0' && x[j] <= '9';++j) d = d * 10 + (x[j] - '0'); | ||
110 | if (j == 0) return 0; | ||
111 | x += j; | ||
112 | if (pos >= 0 && pos < 4 * NUMIP) y[pos] = d; | ||
113 | if (k < 3) { | ||
114 | if (*x != '.') return 0; | ||
115 | ++x; | ||
116 | } | ||
117 | } | ||
118 | if (!*x) break; | ||
119 | if (*x != ',') return 0; | ||
120 | ++x; | ||
121 | } | ||
122 | /* if fewer than 8 IP addresses, cycle through them: */ | ||
123 | pos = 0; | ||
124 | pos2 = ynum * 4; | ||
125 | while (pos2 < 4 * NUMIP) { | ||
126 | if (pos >= 0 && pos < 4 * NUMIP && pos2 >= 0 && pos2 < 4 * NUMIP) y[pos2] = y[pos]; | ||
127 | ++pos2; | ||
128 | ++pos; | ||
129 | } | ||
130 | return 1; | ||
131 | } | ||
132 | |||
133 | |||
134 | /* routing to the client: */ | ||
135 | unsigned char clientextension[16]; | ||
136 | long long clientextensionloadtime = 0; | ||
137 | int udpfd = -1; | ||
138 | |||
139 | void clientextension_init(void) | ||
140 | { | ||
141 | if (recent >= clientextensionloadtime) { | ||
142 | clientextensionloadtime = recent + 30000000000LL; | ||
143 | if (load("/etc/curvecpextension",clientextension,16) == -1) | ||
144 | if (errno == ENOENT || errno == ENAMETOOLONG) | ||
145 | byte_zero(clientextension,16); | ||
146 | } | ||
147 | } | ||
148 | |||
149 | |||
150 | /* client security: */ | ||
151 | char *keydir = 0; | ||
152 | unsigned char clientlongtermpk[32]; | ||
153 | unsigned char clientlongtermsk[32]; | ||
154 | unsigned char clientshorttermpk[32]; | ||
155 | unsigned char clientshorttermsk[32]; | ||
156 | crypto_uint64 clientshorttermnonce; | ||
157 | unsigned char vouch[64]; | ||
158 | |||
159 | void clientshorttermnonce_update(void) | ||
160 | { | ||
161 | ++clientshorttermnonce; | ||
162 | if (clientshorttermnonce) return; | ||
163 | errno = EPROTO; | ||
164 | die_fatal("nonce space expired",0,0); | ||
165 | } | ||
166 | |||
167 | /* routing to the server: */ | ||
168 | unsigned char serverip[4 * NUMIP]; | ||
169 | unsigned char serverport[2]; | ||
170 | unsigned char serverextension[16]; | ||
171 | |||
172 | /* server security: */ | ||
173 | unsigned char servername[256]; | ||
174 | unsigned char serverlongtermpk[32]; | ||
175 | unsigned char servershorttermpk[32]; | ||
176 | unsigned char servercookie[96]; | ||
177 | |||
178 | /* shared secrets: */ | ||
179 | unsigned char clientshortserverlong[32]; | ||
180 | unsigned char clientshortservershort[32]; | ||
181 | unsigned char clientlongserverlong[32]; | ||
182 | |||
183 | unsigned char allzero[128] = {0}; | ||
184 | |||
185 | unsigned char nonce[24]; | ||
186 | unsigned char text[2048]; | ||
187 | |||
188 | unsigned char packet[4096]; | ||
189 | unsigned char packetip[4]; | ||
190 | unsigned char packetport[2]; | ||
191 | crypto_uint64 packetnonce; | ||
192 | int flagreceivedmessage = 0; | ||
193 | crypto_uint64 receivednonce = 0; | ||
194 | |||
195 | struct pollfd p[3]; | ||
196 | |||
197 | int fdwd = -1; | ||
198 | |||
199 | int tochild[2] = {-1,-1}; | ||
200 | int fromchild[2] = {-1,-1}; | ||
201 | pid_t child = -1; | ||
202 | int childstatus = 0; | ||
203 | |||
204 | unsigned char childbuf[4096]; | ||
205 | long long childbuflen = 0; | ||
206 | unsigned char childmessage[2048]; | ||
207 | long long childmessagelen = 0; | ||
208 | |||
209 | int main(int argc,char **argv) | ||
210 | { | ||
211 | long long hellopackets; | ||
212 | long long r; | ||
213 | long long nextaction; | ||
214 | |||
215 | signal(SIGPIPE,SIG_IGN); | ||
216 | |||
217 | if (!argv[0]) die_usage(0); | ||
218 | for (;;) { | ||
219 | char *x; | ||
220 | if (!argv[1]) break; | ||
221 | if (argv[1][0] != '-') break; | ||
222 | x = *++argv; | ||
223 | if (x[0] == '-' && x[1] == 0) break; | ||
224 | if (x[0] == '-' && x[1] == '-' && x[2] == 0) break; | ||
225 | while (*++x) { | ||
226 | if (*x == 'q') { flagverbose = 0; continue; } | ||
227 | if (*x == 'Q') { flagverbose = 1; continue; } | ||
228 | if (*x == 'v') { if (flagverbose == 2) flagverbose = 3; else flagverbose = 2; continue; } | ||
229 | if (*x == 'c') { | ||
230 | if (x[1]) { keydir = x + 1; break; } | ||
231 | if (argv[1]) { keydir = *++argv; break; } | ||
232 | } | ||
233 | die_usage(0); | ||
234 | } | ||
235 | } | ||
236 | if (!nameparse(servername,*++argv)) die_usage("sname must be at most 255 bytes, at most 63 bytes between dots"); | ||
237 | if (!hexparse(serverlongtermpk,32,*++argv)) die_usage("pk must be exactly 64 hex characters"); | ||
238 | if (!multiipparse(serverip,*++argv)) die_usage("ip must be a comma-separated series of IPv4 addresses"); | ||
239 | if (!portparse(serverport,*++argv)) die_usage("port must be an integer between 0 and 65535"); | ||
240 | if (!hexparse(serverextension,16,*++argv)) die_usage("ext must be exactly 32 hex characters"); | ||
241 | if (!*++argv) die_usage("missing prog"); | ||
242 | |||
243 | for (;;) { | ||
244 | r = open_read("/dev/null"); | ||
245 | if (r == -1) die_fatal("unable to open /dev/null",0,0); | ||
246 | if (r > 9) { close(r); break; } | ||
247 | } | ||
248 | |||
249 | if (keydir) { | ||
250 | fdwd = open_cwd(); | ||
251 | if (fdwd == -1) die_fatal("unable to open current working directory",0,0); | ||
252 | if (chdir(keydir) == -1) die_fatal("unable to change to directory",keydir,0); | ||
253 | if (load("publickey",clientlongtermpk,sizeof clientlongtermpk) == -1) die_fatal("unable to read public key from",keydir,0); | ||
254 | if (load(".expertsonly/secretkey",clientlongtermsk,sizeof clientlongtermsk) == -1) die_fatal("unable to read secret key from",keydir,0); | ||
255 | } else { | ||
256 | crypto_box_keypair(clientlongtermpk,clientlongtermsk); | ||
257 | } | ||
258 | |||
259 | crypto_box_keypair(clientshorttermpk,clientshorttermsk); | ||
260 | clientshorttermnonce = randommod(281474976710656LL); | ||
261 | crypto_box_beforenm(clientshortserverlong,serverlongtermpk,clientshorttermsk); | ||
262 | crypto_box_beforenm(clientlongserverlong,serverlongtermpk,clientlongtermsk); | ||
263 | |||
264 | udpfd = socket_udp(); | ||
265 | if (udpfd == -1) die_fatal("unable to create socket",0,0); | ||
266 | |||
267 | for (hellopackets = 0;hellopackets < NUMIP;++hellopackets) { | ||
268 | recent = nanoseconds(); | ||
269 | |||
270 | /* send a Hello packet: */ | ||
271 | |||
272 | clientextension_init(); | ||
273 | |||
274 | clientshorttermnonce_update(); | ||
275 | byte_copy(nonce,16,"CurveCP-client-H"); | ||
276 | uint64_pack(nonce + 16,clientshorttermnonce); | ||
277 | |||
278 | byte_copy(packet,8,"QvnQ5XlH"); | ||
279 | byte_copy(packet + 8,16,serverextension); | ||
280 | byte_copy(packet + 24,16,clientextension); | ||
281 | byte_copy(packet + 40,32,clientshorttermpk); | ||
282 | byte_copy(packet + 72,64,allzero); | ||
283 | byte_copy(packet + 136,8,nonce + 16); | ||
284 | crypto_box_afternm(text,allzero,96,nonce,clientshortserverlong); | ||
285 | byte_copy(packet + 144,80,text + 16); | ||
286 | |||
287 | socket_send(udpfd,packet,224,serverip + 4 * hellopackets,serverport); | ||
288 | |||
289 | nextaction = recent + hellowait[hellopackets] + randommod(hellowait[hellopackets]); | ||
290 | |||
291 | for (;;) { | ||
292 | long long timeout = nextaction - recent; | ||
293 | if (timeout <= 0) break; | ||
294 | p[0].fd = udpfd; | ||
295 | p[0].events = POLLIN; | ||
296 | if (poll(p,1,timeout / 1000000 + 1) < 0) p[0].revents = 0; | ||
297 | |||
298 | do { /* try receiving a Cookie packet: */ | ||
299 | if (!p[0].revents) break; | ||
300 | r = socket_recv(udpfd,packet,sizeof packet,packetip,packetport); | ||
301 | if (r != 200) break; | ||
302 | if (!(byte_isequal(packetip,4,serverip + 4 * hellopackets) & | ||
303 | byte_isequal(packetport,2,serverport) & | ||
304 | byte_isequal(packet,8,"RL3aNMXK") & | ||
305 | byte_isequal(packet + 8,16,clientextension) & | ||
306 | byte_isequal(packet + 24,16,serverextension) | ||
307 | )) break; | ||
308 | byte_copy(nonce,8,"CurveCPK"); | ||
309 | byte_copy(nonce + 8,16,packet + 40); | ||
310 | byte_zero(text,16); | ||
311 | byte_copy(text + 16,144,packet + 56); | ||
312 | if (crypto_box_open_afternm(text,text,160,nonce,clientshortserverlong)) break; | ||
313 | byte_copy(servershorttermpk,32,text + 32); | ||
314 | byte_copy(servercookie,96,text + 64); | ||
315 | byte_copy(serverip,4,serverip + 4 * hellopackets); | ||
316 | goto receivedcookie; | ||
317 | } while (0); | ||
318 | |||
319 | recent = nanoseconds(); | ||
320 | } | ||
321 | } | ||
322 | |||
323 | errno = ETIMEDOUT; die_fatal("no response from server",0,0); | ||
324 | |||
325 | receivedcookie: | ||
326 | |||
327 | crypto_box_beforenm(clientshortservershort,servershorttermpk,clientshorttermsk); | ||
328 | |||
329 | byte_copy(nonce,8,"CurveCPV"); | ||
330 | if (keydir) { | ||
331 | if (safenonce(nonce + 8,0) == -1) die_fatal("nonce-generation disaster",0,0); | ||
332 | } else { | ||
333 | randombytes(nonce + 8,16); | ||
334 | } | ||
335 | |||
336 | byte_zero(text,32); | ||
337 | byte_copy(text + 32,32,clientshorttermpk); | ||
338 | crypto_box_afternm(text,text,64,nonce,clientlongserverlong); | ||
339 | byte_copy(vouch,16,nonce + 8); | ||
340 | byte_copy(vouch + 16,48,text + 16); | ||
341 | |||
342 | /* server is responding, so start child: */ | ||
343 | |||
344 | if (open_pipe(tochild) == -1) die_fatal("unable to create pipe",0,0); | ||
345 | if (open_pipe(fromchild) == -1) die_fatal("unable to create pipe",0,0); | ||
346 | |||
347 | child = fork(); | ||
348 | if (child == -1) die_fatal("unable to fork",0,0); | ||
349 | if (child == 0) { | ||
350 | if (keydir) if (fchdir(fdwd) == -1) die_fatal("unable to chdir to original directory",0,0); | ||
351 | close(8); | ||
352 | if (dup(tochild[0]) != 8) die_fatal("unable to dup",0,0); | ||
353 | close(9); | ||
354 | if (dup(fromchild[1]) != 9) die_fatal("unable to dup",0,0); | ||
355 | /* XXX: set up environment variables */ | ||
356 | signal(SIGPIPE,SIG_DFL); | ||
357 | execvp(*argv,argv); | ||
358 | die_fatal("unable to run",*argv,0); | ||
359 | } | ||
360 | |||
361 | close(fromchild[1]); | ||
362 | close(tochild[0]); | ||
363 | |||
364 | |||
365 | for (;;) { | ||
366 | p[0].fd = udpfd; | ||
367 | p[0].events = POLLIN; | ||
368 | p[1].fd = fromchild[0]; | ||
369 | p[1].events = POLLIN; | ||
370 | |||
371 | if (poll(p,2,-1) < 0) { | ||
372 | p[0].revents = 0; | ||
373 | p[1].revents = 0; | ||
374 | } | ||
375 | |||
376 | do { /* try receiving a Message packet: */ | ||
377 | if (!p[0].revents) break; | ||
378 | r = socket_recv(udpfd,packet,sizeof packet,packetip,packetport); | ||
379 | if (r < 80) break; | ||
380 | if (r > 1152) break; | ||
381 | if (r & 15) break; | ||
382 | packetnonce = uint64_unpack(packet + 40); | ||
383 | if (flagreceivedmessage && packetnonce <= receivednonce) break; | ||
384 | if (!(byte_isequal(packetip,4,serverip + 4 * hellopackets) & | ||
385 | byte_isequal(packetport,2,serverport) & | ||
386 | byte_isequal(packet,8,"RL3aNMXM") & | ||
387 | byte_isequal(packet + 8,16,clientextension) & | ||
388 | byte_isequal(packet + 24,16,serverextension) | ||
389 | )) break; | ||
390 | byte_copy(nonce,16,"CurveCP-server-M"); | ||
391 | byte_copy(nonce + 16,8,packet + 40); | ||
392 | byte_zero(text,16); | ||
393 | byte_copy(text + 16,r - 48,packet + 48); | ||
394 | if (crypto_box_open_afternm(text,text,r - 32,nonce,clientshortservershort)) break; | ||
395 | |||
396 | if (!flagreceivedmessage) { | ||
397 | flagreceivedmessage = 1; | ||
398 | randombytes(clientlongtermpk,sizeof clientlongtermpk); | ||
399 | randombytes(vouch,sizeof vouch); | ||
400 | randombytes(servername,sizeof servername); | ||
401 | randombytes(servercookie,sizeof servercookie); | ||
402 | } | ||
403 | |||
404 | receivednonce = packetnonce; | ||
405 | text[31] = (r - 64) >> 4; | ||
406 | /* child is responsible for reading all data immediately, so we won't block: */ | ||
407 | if (writeall(tochild[1],text + 31,r - 63) == -1) goto done; | ||
408 | } while (0); | ||
409 | |||
410 | do { /* try receiving data from child: */ | ||
411 | long long i; | ||
412 | if (!p[1].revents) break; | ||
413 | r = read(fromchild[0],childbuf,sizeof childbuf); | ||
414 | if (r == -1) if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) break; | ||
415 | if (r <= 0) goto done; | ||
416 | childbuflen = r; | ||
417 | for (i = 0;i < childbuflen;++i) { | ||
418 | if (childmessagelen < 0) goto done; | ||
419 | if (childmessagelen >= sizeof childmessage) goto done; | ||
420 | childmessage[childmessagelen++] = childbuf[i]; | ||
421 | if (childmessage[0] & 128) goto done; | ||
422 | if (childmessagelen == 1 + 16 * (unsigned long long) childmessage[0]) { | ||
423 | clientextension_init(); | ||
424 | clientshorttermnonce_update(); | ||
425 | uint64_pack(nonce + 16,clientshorttermnonce); | ||
426 | if (flagreceivedmessage) { | ||
427 | r = childmessagelen - 1; | ||
428 | if (r < 16) goto done; | ||
429 | if (r > 1088) goto done; | ||
430 | byte_copy(nonce,16,"CurveCP-client-M"); | ||
431 | byte_zero(text,32); | ||
432 | byte_copy(text + 32,r,childmessage + 1); | ||
433 | crypto_box_afternm(text,text,r + 32,nonce,clientshortservershort); | ||
434 | byte_copy(packet,8,"QvnQ5XlM"); | ||
435 | byte_copy(packet + 8,16,serverextension); | ||
436 | byte_copy(packet + 24,16,clientextension); | ||
437 | byte_copy(packet + 40,32,clientshorttermpk); | ||
438 | byte_copy(packet + 72,8,nonce + 16); | ||
439 | byte_copy(packet + 80,r + 16,text + 16); | ||
440 | socket_send(udpfd,packet,r + 96,serverip,serverport); | ||
441 | } else { | ||
442 | r = childmessagelen - 1; | ||
443 | if (r < 16) goto done; | ||
444 | if (r > 640) goto done; | ||
445 | byte_copy(nonce,16,"CurveCP-client-I"); | ||
446 | byte_zero(text,32); | ||
447 | byte_copy(text + 32,32,clientlongtermpk); | ||
448 | byte_copy(text + 64,64,vouch); | ||
449 | byte_copy(text + 128,256,servername); | ||
450 | byte_copy(text + 384,r,childmessage + 1); | ||
451 | crypto_box_afternm(text,text,r + 384,nonce,clientshortservershort); | ||
452 | byte_copy(packet,8,"QvnQ5XlI"); | ||
453 | byte_copy(packet + 8,16,serverextension); | ||
454 | byte_copy(packet + 24,16,clientextension); | ||
455 | byte_copy(packet + 40,32,clientshorttermpk); | ||
456 | byte_copy(packet + 72,96,servercookie); | ||
457 | byte_copy(packet + 168,8,nonce + 16); | ||
458 | byte_copy(packet + 176,r + 368,text + 16); | ||
459 | socket_send(udpfd,packet,r + 544,serverip,serverport); | ||
460 | } | ||
461 | childmessagelen = 0; | ||
462 | } | ||
463 | } | ||
464 | } while (0); | ||
465 | } | ||
466 | |||
467 | |||
468 | done: | ||
469 | |||
470 | do { | ||
471 | r = waitpid(child,&childstatus,0); | ||
472 | } while (r == -1 && errno == EINTR); | ||
473 | |||
474 | if (!WIFEXITED(childstatus)) { errno = 0; die_fatal("process killed by signal",0,0); } | ||
475 | return WEXITSTATUS(childstatus); | ||
476 | } | ||