Files: fd953a1e72b4b16e6e5a74bcf2f893dbf1407ce4 / sbotc.c
28524 bytesRaw
1 | /* |
2 | * sbotc.c |
3 | * Copyright (c) 2017 Secure Scuttlebutt Consortium |
4 | * |
5 | * Usage of the works is permitted provided that this instrument is |
6 | * retained with the works, so that any entity that uses the works is |
7 | * notified of this instrument. |
8 | * |
9 | * DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. |
10 | */ |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | write_all(fd, buf, sizeof(buf)-1) |
38 | |
39 | struct boxs_header { |
40 | uint16_t len; |
41 | uint8_t mac[16]; |
42 | }; |
43 | |
44 | struct boxs { |
45 | int s; |
46 | unsigned char encrypt_key[32]; |
47 | unsigned char decrypt_key[32]; |
48 | unsigned char nonce1[24]; |
49 | unsigned char nonce2[24]; |
50 | unsigned char rx_nonce[24]; |
51 | unsigned char rx_buf[BOXS_MAXLEN]; |
52 | size_t rx_buf_pos; |
53 | size_t rx_buf_len; |
54 | }; |
55 | |
56 | enum pkt_type { |
57 | pkt_type_buffer = 0, |
58 | pkt_type_string = 1, |
59 | pkt_type_json = 2, |
60 | }; |
61 | |
62 | enum pkt_flags { |
63 | pkt_flags_buffer = 0, |
64 | pkt_flags_string = 1, |
65 | pkt_flags_json = 2, |
66 | pkt_flags_end = 4, |
67 | pkt_flags_stream = 8, |
68 | }; |
69 | |
70 | struct pkt_header { |
71 | uint32_t len; |
72 | int32_t req; |
73 | } __attribute__((packed)); |
74 | |
75 | enum muxrpc_type { |
76 | muxrpc_type_async, |
77 | muxrpc_type_source, |
78 | muxrpc_type_sink, |
79 | muxrpc_type_duplex, |
80 | }; |
81 | |
82 | enum stream_state { |
83 | stream_state_open, |
84 | stream_state_ended_ok, |
85 | stream_state_ended_error, |
86 | }; |
87 | |
88 | static unsigned char zeros[24] = {0}; |
89 | |
90 | static const unsigned char ssb_cap[] = { |
91 | 0xd4, 0xa1, 0xcb, 0x88, 0xa6, 0x6f, 0x02, 0xf8, |
92 | 0xdb, 0x63, 0x5c, 0xe2, 0x64, 0x41, 0xcc, 0x5d, |
93 | 0xac, 0x1b, 0x08, 0x42, 0x0c, 0xea, 0xac, 0x23, |
94 | 0x08, 0x39, 0xb7, 0x55, 0x84, 0x5a, 0x9f, 0xfb |
95 | }; |
96 | |
97 | static void usage() { |
98 | fputs("usage: sbotc [-j] [-T] [-a <cap>] [-s <host>] [-p <port>] [-k <key>] [-K <keypair_seed>] \n" |
99 | " [-t <type>] <method> [<argument>...]\n", stderr); |
100 | exit(EXIT_FAILURE); |
101 | } |
102 | |
103 | static int tcp_connect(const char *host, const char *port) { |
104 | struct addrinfo hints; |
105 | struct addrinfo *result, *rp; |
106 | int s; |
107 | int fd; |
108 | int err; |
109 | |
110 | memset(&hints, 0, sizeof(hints)); |
111 | hints.ai_family = AF_UNSPEC; |
112 | hints.ai_protocol = IPPROTO_TCP; |
113 | |
114 | s = getaddrinfo(host, port, &hints, &result); |
115 | if (s < 0) errx(1, "unable to resolve host: %s", gai_strerror(s)); |
116 | |
117 | for (rp = result; rp; rp = rp->ai_next) { |
118 | fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); |
119 | if (fd < 0) continue; |
120 | if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) break; |
121 | err = errno; |
122 | close(fd); |
123 | errno = err; |
124 | } |
125 | if (rp == NULL) fd = -1; |
126 | |
127 | freeaddrinfo(result); |
128 | return fd; |
129 | } |
130 | |
131 | static int read_all(int fd, void *buf, size_t count) { |
132 | ssize_t nbytes; |
133 | while (count > 0) { |
134 | nbytes = read(fd, buf, count); |
135 | if (nbytes == 0) { errno = EPIPE; return -1; } |
136 | if (nbytes < 0 && errno == EINTR) continue; |
137 | if (nbytes < 0) return -1; |
138 | buf += nbytes; |
139 | count -= nbytes; |
140 | } |
141 | return 0; |
142 | } |
143 | |
144 | static int write_all(int fd, const void *buf, size_t count) { |
145 | ssize_t nbytes; |
146 | while (count > 0) { |
147 | nbytes = write(fd, buf, count); |
148 | if (nbytes < 0 && errno == EINTR) continue; |
149 | if (nbytes < 0) return -1; |
150 | buf += nbytes; |
151 | count -= nbytes; |
152 | } |
153 | return 0; |
154 | } |
155 | |
156 | static void shs_connect(int sfd, int infd, int outfd, const unsigned char pubkey[32], const unsigned char seckey[64], const unsigned char appkey[32], const unsigned char server_pubkey[32], struct boxs *bs) { |
157 | int rc; |
158 | unsigned char local_app_mac[32], remote_app_mac[32]; |
159 | |
160 | unsigned char kx_pk[32], kx_sk[32]; |
161 | rc = crypto_box_keypair(kx_pk, kx_sk); |
162 | if (rc < 0) errx(1, "failed to generate auth keypair"); |
163 | |
164 | rc = crypto_auth(local_app_mac, kx_pk, 32, appkey); |
165 | if (rc < 0) err(1, "failed to generate app mac"); |
166 | |
167 | // send challenge |
168 | unsigned char buf[64]; |
169 | memcpy(buf, local_app_mac, 32); |
170 | memcpy(buf+32, kx_pk, 32); |
171 | rc = write_all(outfd, buf, sizeof(buf)); |
172 | if (rc < 0) err(1, "failed to send challenge"); |
173 | |
174 | // recv challenge |
175 | unsigned char remote_kx_pk[32]; |
176 | rc = read_all(infd, buf, sizeof(buf)); |
177 | if (rc < 0) err(1, "challenge not accepted"); |
178 | memcpy(remote_app_mac, buf, 32); |
179 | memcpy(remote_kx_pk, buf+32, 32); |
180 | rc = crypto_auth_verify(buf, remote_kx_pk, 32, appkey); |
181 | if (rc < 0) errx(1, "wrong protocol (version?)"); |
182 | |
183 | // send auth |
184 | |
185 | unsigned char secret[32]; |
186 | rc = crypto_scalarmult(secret, kx_sk, remote_kx_pk); |
187 | if (rc < 0) errx(1, "failed to derive shared secret"); |
188 | |
189 | unsigned char remote_pk_curve[32]; |
190 | rc = crypto_sign_ed25519_pk_to_curve25519(remote_pk_curve, server_pubkey); |
191 | if (rc < 0) errx(1, "failed to curvify remote public key"); |
192 | |
193 | unsigned char a_bob[32]; |
194 | rc = crypto_scalarmult(a_bob, kx_sk, remote_pk_curve); |
195 | if (rc < 0) errx(1, "failed to derive a_bob"); |
196 | |
197 | unsigned char secret2a[96]; |
198 | memcpy(secret2a, appkey, 32); |
199 | memcpy(secret2a+32, secret, 32); |
200 | memcpy(secret2a+64, a_bob, 32); |
201 | |
202 | unsigned char secret2[32]; |
203 | rc = crypto_hash_sha256(secret2, secret2a, sizeof(secret2a)); |
204 | if (rc < 0) errx(1, "failed to hash secret2"); |
205 | |
206 | unsigned char shash[32]; |
207 | rc = crypto_hash_sha256(shash, secret, sizeof(secret)); |
208 | if (rc < 0) errx(1, "failed to hash secret"); |
209 | |
210 | unsigned char signed1[96]; |
211 | memcpy(signed1, appkey, 32); |
212 | memcpy(signed1+32, server_pubkey, 32); |
213 | memcpy(signed1+64, shash, 32); |
214 | |
215 | unsigned char sig[64]; |
216 | rc = crypto_sign_detached(sig, NULL, signed1, sizeof(signed1), seckey); |
217 | if (rc < 0) errx(1, "failed to sign inner hello"); |
218 | |
219 | unsigned char hello[96]; |
220 | memcpy(hello, sig, 64); |
221 | memcpy(hello+64, pubkey, 32); |
222 | |
223 | unsigned char boxed_auth[112]; |
224 | rc = crypto_secretbox_easy(boxed_auth, hello, sizeof(hello), zeros, secret2); |
225 | if (rc < 0) errx(1, "failed to box hello"); |
226 | |
227 | rc = write_all(outfd, boxed_auth, sizeof(boxed_auth)); |
228 | if (rc < 0) errx(1, "failed to send auth"); |
229 | |
230 | // verify accept |
231 | |
232 | unsigned char boxed_okay[80]; |
233 | rc = read_all(infd, boxed_okay, sizeof(boxed_okay)); |
234 | if (rc < 0) err(1, "hello not accepted"); |
235 | |
236 | unsigned char local_sk_curve[32]; |
237 | rc = crypto_sign_ed25519_sk_to_curve25519(local_sk_curve, seckey); |
238 | if (rc < 0) errx(1, "failed to curvify local secret key"); |
239 | |
240 | unsigned char b_alice[32]; |
241 | rc = crypto_scalarmult(b_alice, local_sk_curve, remote_kx_pk); |
242 | if (rc < 0) errx(1, "failed to derive b_alice"); |
243 | |
244 | unsigned char secret3a[128]; |
245 | memcpy(secret3a, appkey, 32); |
246 | memcpy(secret3a+32, secret, 32); |
247 | memcpy(secret3a+64, a_bob, 32); |
248 | memcpy(secret3a+96, b_alice, 32); |
249 | |
250 | unsigned char secret3[32]; |
251 | rc = crypto_hash_sha256(secret3, secret3a, sizeof(secret3a)); |
252 | if (rc < 0) errx(1, "failed to hash secret3"); |
253 | |
254 | rc = crypto_secretbox_open_easy(sig, boxed_okay, sizeof(boxed_okay), zeros, secret3); |
255 | if (rc < 0) errx(1, "failed to unbox the okay"); |
256 | |
257 | unsigned char signed2[160]; |
258 | memcpy(signed2, appkey, 32); |
259 | memcpy(signed2+32, hello, 96); |
260 | memcpy(signed2+128, shash, 32); |
261 | |
262 | rc = crypto_sign_verify_detached(sig, signed2, sizeof(signed2), server_pubkey); |
263 | if (rc < 0) errx(1, "server not authenticated"); |
264 | |
265 | rc = crypto_hash_sha256(secret, secret3, 32); |
266 | if (rc < 0) errx(1, "failed to hash secret3"); |
267 | |
268 | unsigned char enc_key_hashed[64]; |
269 | memcpy(enc_key_hashed, secret, 32); |
270 | memcpy(enc_key_hashed+32, server_pubkey, 32); |
271 | rc = crypto_hash_sha256(bs->encrypt_key, enc_key_hashed, 64); |
272 | if (rc < 0) errx(1, "failed to hash the encrypt key"); |
273 | |
274 | unsigned char dec_key_hashed[64]; |
275 | memcpy(dec_key_hashed, secret, 32); |
276 | memcpy(dec_key_hashed+32, pubkey, 32); |
277 | rc = crypto_hash_sha256(bs->decrypt_key, dec_key_hashed, 64); |
278 | if (rc < 0) errx(1, "failed to hash the decrypt key"); |
279 | |
280 | memcpy(bs->nonce1, remote_app_mac, 24); |
281 | memcpy(bs->nonce2, remote_app_mac, 24); |
282 | memcpy(bs->rx_nonce, local_app_mac, 24); |
283 | |
284 | bs->rx_buf_pos = 0; |
285 | bs->rx_buf_len = 0; |
286 | bs->s = sfd; |
287 | } |
288 | |
289 | static int pubkey_decode(const char *key_str, unsigned char key[32]) { |
290 | if (!key_str) { errno = EPROTO; return -1; } |
291 | if (!*key_str) { errno = EPROTO; return -1; } |
292 | if (*key_str == '@') key_str++; |
293 | size_t len = strlen(key_str); |
294 | if (len == 52 && strcmp(key_str+44, ".ed25519") == 0) {} |
295 | else if (len != 44) { errno = EMSGSIZE; return -1; } |
296 | return base64_decode(key_str, 44, key, 32); |
297 | } |
298 | |
299 | static jsmntok_t *json_lookup(const char *buf, jsmntok_t *tok, const char *prop, size_t prop_len) { |
300 | jsmntok_t *end = tok + tok->size + 1; |
301 | if (tok->type != JSMN_OBJECT) { errno = EPROTO; return NULL; } |
302 | tok++; |
303 | while (tok < end) { |
304 | if (tok + 1 >= end) { errno = EPROTO; return NULL; } |
305 | if (tok->type == JSMN_STRING |
306 | && tok->end - tok->start == (int)prop_len |
307 | && memcmp(buf + tok->start, prop, prop_len) == 0) |
308 | return tok + 1; |
309 | tok += tok->size + 1; |
310 | end += tok->size; |
311 | } |
312 | return NULL; |
313 | } |
314 | |
315 | static ssize_t json_get_value(const char *buf, const char *path, const char **value) { |
316 | static const int num_tokens = 1024; |
317 | jsmntok_t tokens[num_tokens], *tok = tokens; |
318 | jsmn_parser parser; |
319 | |
320 | jsmn_init(&parser); |
321 | switch (jsmn_parse(&parser, buf, tokens, num_tokens)) { |
322 | case JSMN_ERROR_NOMEM: errno = ENOMEM; return -1; |
323 | case JSMN_ERROR_INVAL: errno = EINVAL; return -1; |
324 | case JSMN_ERROR_PART: errno = EMSGSIZE; return -1; |
325 | case JSMN_SUCCESS: break; |
326 | default: errno = EPROTO; return -1; |
327 | } |
328 | |
329 | while (*path) { |
330 | const char *end = strchr(path, '.'); |
331 | size_t part_len = end ? (size_t)end - (size_t)path : strlen(path); |
332 | tok = json_lookup(buf, tok, path, part_len); |
333 | if (!tok) { errno = ENOMSG; return -1; } |
334 | path += part_len; |
335 | if (*path == '.') path++; |
336 | } |
337 | |
338 | *value = buf + tok->start; |
339 | return tok->end - tok->start; |
340 | } |
341 | |
342 | static void get_app_dir(char *dir, size_t len) { |
343 | const char *path, *home, *appname; |
344 | int rc; |
345 | path = getenv("ssb_path"); |
346 | if (path) { |
347 | if (strlen(path) > len) errx(1, "ssb_path too long"); |
348 | strncpy(dir, path, len); |
349 | return; |
350 | } |
351 | home = getenv("HOME"); |
352 | if (!home) home = "."; |
353 | appname = getenv("ssb_appname"); |
354 | if (!appname) appname = "ssb"; |
355 | rc = snprintf(dir, len, "%s/.%s", home, appname); |
356 | if (rc < 0) err(1, "failed to get app dir"); |
357 | if ((size_t)rc >= len) errx(1, "path to app dir too long"); |
358 | } |
359 | |
360 | static ssize_t read_file(char *buf, size_t len, const char *fmt, ...) { |
361 | va_list ap; |
362 | int rc; |
363 | struct stat st; |
364 | int fd; |
365 | |
366 | va_start(ap, fmt); |
367 | rc = vsnprintf(buf, len, fmt, ap); |
368 | va_end(ap); |
369 | if (rc < 0) return -1; |
370 | if ((size_t)rc >= len) { errno = ENAMETOOLONG; return -1; } |
371 | |
372 | rc = stat(buf, &st); |
373 | if (rc < 0) return -1; |
374 | if (st.st_size > (off_t)(len-1)) { errno = EMSGSIZE; return -1; } |
375 | |
376 | fd = open(buf, O_RDONLY); |
377 | if (fd < 0) return -1; |
378 | |
379 | rc = read_all(fd, buf, st.st_size); |
380 | if (rc < 0) return -1; |
381 | buf[st.st_size] = '\0'; |
382 | |
383 | close(fd); |
384 | return st.st_size; |
385 | } |
386 | |
387 | |
388 | static void read_private_key(const char *dir, unsigned char pk[64]) { |
389 | ssize_t len; |
390 | char buf[8192]; |
391 | const char *pk_b64; |
392 | int rc; |
393 | ssize_t key_len; |
394 | char *line; |
395 | |
396 | len = read_file(buf, sizeof(buf), "%s/secret", dir); |
397 | if (len < 0) err(1, "failed to read secret file"); |
398 | |
399 | // strip comments |
400 | for (line = buf; *line; ) { |
401 | if (*line == '#') while (*line && *line != '\n') *line++ = ' '; |
402 | else while (*line && *line++ != '\n'); |
403 | } |
404 | |
405 | key_len = json_get_value(buf, "private", &pk_b64); |
406 | if (key_len < 0) err(1, "unable to read private key"); |
407 | |
408 | if (key_len > 8 && memcmp(pk_b64 + key_len - 8, ".ed25519", 8) == 0) |
409 | key_len -= 8; |
410 | rc = base64_decode(pk_b64, key_len, pk, 64); |
411 | if (rc < 0) err(1, "unable to decode private key"); |
412 | } |
413 | |
414 | static void increment_nonce(uint8_t nonce[24]) { |
415 | int i; |
416 | for (i = 23; i >= 0 && nonce[i] == 0xff; i--) nonce[i] = 0; |
417 | if (i >= 0) nonce[i]++; |
418 | } |
419 | |
420 | static void bs_write_packet(struct boxs *bs, const unsigned char *buf, uint16_t len) { |
421 | size_t boxed_len = len + 34; |
422 | unsigned char boxed[boxed_len]; |
423 | increment_nonce(bs->nonce2); |
424 | int rc = crypto_secretbox_easy(boxed + 18, buf, len, bs->nonce2, bs->encrypt_key); |
425 | if (rc < 0) errx(1, "failed to box packet data"); |
426 | struct boxs_header header; |
427 | header.len = htons(len); |
428 | memcpy(header.mac, boxed + 18, 16); |
429 | rc = crypto_secretbox_easy(boxed, (unsigned char *)&header, 18, bs->nonce1, bs->encrypt_key); |
430 | if (rc < 0) errx(1, "failed to box packet header"); |
431 | increment_nonce(bs->nonce1); |
432 | increment_nonce(bs->nonce1); |
433 | increment_nonce(bs->nonce2); |
434 | rc = write_all(bs->s, boxed, boxed_len); |
435 | if (rc < 0) err(1, "failed to write boxed packet"); |
436 | } |
437 | |
438 | static int bs_read_packet(struct boxs *bs, void *buf, size_t *lenp) { |
439 | unsigned char boxed_header[34]; |
440 | struct boxs_header header; |
441 | int rc = read_all(bs->s, boxed_header, 34); |
442 | if (rc < 0 && errno == EPIPE) errx(1, "unexpected end of parent stream"); |
443 | if (rc < 0) err(1, "failed to read boxed packet header"); |
444 | rc = crypto_secretbox_open_easy((unsigned char *)&header, boxed_header, 34, bs->rx_nonce, bs->decrypt_key); |
445 | if (rc < 0) errx(1, "failed to unbox packet header"); |
446 | increment_nonce(bs->rx_nonce); |
447 | if (header.len == 0 && !memcmp(header.mac, zeros, 16)) { errno = EPIPE; return -1; } |
448 | size_t len = ntohs(header.len); |
449 | if (len > BOXS_MAXLEN) errx(1, "received boxed packet too large"); |
450 | unsigned char boxed_data[len + 16]; |
451 | rc = read_all(bs->s, boxed_data + 16, len); |
452 | if (rc < 0) err(1, "failed to read boxed packet data"); |
453 | memcpy(boxed_data, header.mac, 16); |
454 | rc = crypto_secretbox_open_easy(buf, boxed_data, len+16, bs->rx_nonce, bs->decrypt_key); |
455 | if (rc < 0) errx(1, "failed to unbox packet data"); |
456 | increment_nonce(bs->rx_nonce); |
457 | *lenp = len; |
458 | return 0; |
459 | } |
460 | |
461 | static int bs_read(struct boxs *bs, char *buf, size_t len) { |
462 | size_t remaining; |
463 | while (len > 0) { |
464 | remaining = bs->rx_buf_len > len ? len : bs->rx_buf_len; |
465 | if (buf) memcpy(buf, bs->rx_buf + bs->rx_buf_pos, remaining); |
466 | bs->rx_buf_len -= remaining; |
467 | bs->rx_buf_pos += remaining; |
468 | len -= remaining; |
469 | buf += remaining; |
470 | if (len == 0) return 0; |
471 | if (bs_read_packet(bs, bs->rx_buf, &bs->rx_buf_len) < 0) return -1; |
472 | bs->rx_buf_pos = 0; |
473 | } |
474 | return 0; |
475 | } |
476 | |
477 | static int bs_read_out(struct boxs *bs, int fd, size_t len) { |
478 | size_t chunk; |
479 | char buf[4096]; |
480 | int rc; |
481 | while (len > 0) { |
482 | chunk = len > sizeof(buf) ? sizeof(buf) : len; |
483 | rc = bs_read(bs, buf, chunk); |
484 | if (rc < 0) return -1; |
485 | rc = write_all(fd, buf, chunk); |
486 | if (rc < 0) return -1; |
487 | len -= chunk; |
488 | } |
489 | return 0; |
490 | } |
491 | |
492 | static int bs_read_error(struct boxs *bs, int errfd, enum pkt_flags flags, size_t len) { |
493 | // suppress printing "true" indicating end without error |
494 | if (flags & pkt_flags_json && len == 4) { |
495 | char buf[4]; |
496 | if (bs_read(bs, buf, 4) < 0) return -1; |
497 | if (strncmp(buf, "true", 0) == 0) { |
498 | return 0; |
499 | } |
500 | if (write_all(errfd, buf, 4) < 0) return -1; |
501 | } else { |
502 | if (bs_read_out(bs, errfd, len) < 0) return -1; |
503 | } |
504 | if (flags & (pkt_flags_json | pkt_flags_string)) { |
505 | if (write_buf(errfd, "\n") < 0) return -1; |
506 | } |
507 | return 1; |
508 | } |
509 | |
510 | static void bs_write(struct boxs *bs, const unsigned char *buf, size_t len) { |
511 | while (len > 0) { |
512 | size_t l = len > BOXS_MAXLEN ? BOXS_MAXLEN : len; |
513 | bs_write_packet(bs, buf, l); |
514 | len -= l; |
515 | buf += l; |
516 | } |
517 | } |
518 | |
519 | static void ps_write(struct boxs *bs, const char *data, size_t len, enum pkt_type type, int req_id, bool stream, bool end) { |
520 | size_t out_len = 9 + len; |
521 | unsigned char out_buf[out_len]; |
522 | struct pkt_header header = {htonl(len), htonl(req_id)}; |
523 | out_buf[0] = (stream << 3) | (end << 2) | (type & 3); |
524 | memcpy(out_buf+1, &header, 8); |
525 | memcpy(out_buf+9, data, len); |
526 | bs_write(bs, out_buf, out_len); |
527 | } |
528 | |
529 | static int ps_read_header(struct boxs *bs, size_t *len, int *req_id, enum pkt_flags *flags) { |
530 | char buf[9]; |
531 | struct pkt_header header; |
532 | if (bs_read(bs, buf, sizeof(buf)) < 0) return -1; |
533 | memcpy(&header, buf+1, 8); |
534 | if (len) *len = ntohl(header.len); |
535 | if (req_id) *req_id = ntohl(header.req); |
536 | if (flags) *flags = buf[0]; |
537 | return 0; |
538 | } |
539 | |
540 | static void muxrpc_call(struct boxs *bs, const char *method, const char *argument, enum muxrpc_type type, const char *typestr, int req_id) { |
541 | char req[33792]; // 32768 max message value size + 1024 extra |
542 | ssize_t reqlen; |
543 | bool is_request = type == muxrpc_type_async; |
544 | |
545 | if (is_request) { |
546 | reqlen = snprintf(req, sizeof(req), |
547 | "{\"name\":%s,\"args\":%s}", |
548 | method, argument); |
549 | } else { |
550 | reqlen = snprintf(req, sizeof(req), |
551 | "{\"name\":%s,\"args\":%s,\"type\":\"%s\"}", |
552 | method, argument, typestr); |
553 | } |
554 | if (reqlen < 0) err(1, "failed to construct request"); |
555 | if ((size_t)reqlen >= sizeof(req)) errx(1, "request too large"); |
556 | |
557 | ps_write(bs, req, reqlen, pkt_type_json, req_id, !is_request, false); |
558 | } |
559 | |
560 | static void ps_reject(struct boxs *bs, size_t len, int32_t req, enum pkt_flags flags) { |
561 | // ignore the packet. if this is a request, the substream on the other end |
562 | // will just have to wait until the rpc connection closes. |
563 | (void)req; |
564 | (void)flags; |
565 | write_buf(STDERR_FILENO, "ignoring packet: "); |
566 | int rc = bs_read_out(bs, STDERR_FILENO, len); |
567 | if (rc < 0) err(1, "bs_read_out"); |
568 | write_buf(STDERR_FILENO, "\n"); |
569 | } |
570 | |
571 | static enum stream_state muxrpc_read_source_1(struct boxs *bs, int outfd, int req_id) { |
572 | enum pkt_flags flags; |
573 | size_t len; |
574 | int32_t req; |
575 | int rc = ps_read_header(bs, &len, &req, &flags); |
576 | if (rc < 0) err(1, "ps_read_header"); |
577 | if (req == 0 && len == 0) { |
578 | warnx("unexpected end of parent stream"); |
579 | return stream_state_ended_error; |
580 | } |
581 | if (req != -req_id) { |
582 | ps_reject(bs, len, req, flags); |
583 | return stream_state_open; |
584 | } |
585 | if (flags & pkt_flags_end) { |
586 | rc = bs_read_error(bs, STDERR_FILENO, flags, len); |
587 | if (rc < 0) err(1, "bs_read_error"); |
588 | if (rc == 1) return stream_state_ended_error; |
589 | return stream_state_ended_ok; |
590 | } |
591 | rc = bs_read_out(bs, outfd, len); |
592 | if (rc < 0) err(1, "bs_read_out"); |
593 | if (flags & (pkt_flags_json | pkt_flags_string)) { |
594 | rc = write_buf(outfd, "\n"); |
595 | if (rc < 0) err(1, "write_buf"); |
596 | } |
597 | return stream_state_open; |
598 | } |
599 | |
600 | static int muxrpc_read_source(struct boxs *bs, int outfd, int req_id) { |
601 | enum stream_state state; |
602 | while ((state = muxrpc_read_source_1(bs, outfd, req_id)) == stream_state_open); |
603 | return state == stream_state_ended_ok ? 0 : |
604 | state == stream_state_ended_error ? 2 : 1; |
605 | } |
606 | |
607 | static int muxrpc_read_async(struct boxs *bs, int outfd, int req_id) { |
608 | enum pkt_flags flags; |
609 | size_t len; |
610 | int32_t req; |
611 | int rc; |
612 | |
613 | while (1) { |
614 | rc = ps_read_header(bs, &len, &req, &flags); |
615 | if (rc < 0) err(1, "ps_read_header"); |
616 | if (req == -req_id) break; |
617 | if (req == 0 && len == 0) errx(1, "unexpected end of parent stream"); |
618 | ps_reject(bs, len, req, flags); |
619 | } |
620 | if (flags & pkt_flags_end) { |
621 | rc = bs_read_error(bs, STDERR_FILENO, flags, len); |
622 | if (rc < 0) err(1, "bs_read_error"); |
623 | if (rc == 1) return 2; |
624 | return 1; |
625 | } |
626 | rc = bs_read_out(bs, outfd, len); |
627 | if (rc < 0) err(1, "bs_read_out"); |
628 | if (flags & (pkt_flags_json | pkt_flags_string)) { |
629 | rc = write_buf(outfd, "\n"); |
630 | if (rc < 0) err(1, "write_buf"); |
631 | } |
632 | return 0; |
633 | } |
634 | |
635 | static enum stream_state muxrpc_write_sink_1(struct boxs *bs, int infd, |
636 | enum pkt_type ptype, int req_id) { |
637 | char buf[4096]; |
638 | ssize_t sz = read(infd, buf, sizeof(buf)); |
639 | if (sz < 0) err(1, "read"); |
640 | if (sz == 0) { |
641 | ps_write(bs, "true", 4, pkt_type_json, req_id, true, true); |
642 | return stream_state_ended_ok; |
643 | } |
644 | ps_write(bs, buf, sz, ptype, req_id, true, false); |
645 | return stream_state_open; |
646 | } |
647 | |
648 | static enum stream_state muxrpc_write_sink_1_hashed(struct boxs *bs, int infd, |
649 | crypto_hash_sha256_state *hash_state, int req_id) { |
650 | int rc; |
651 | unsigned char buf[4096]; |
652 | ssize_t sz = read(infd, buf, sizeof(buf)); |
653 | if (sz < 0) err(1, "read"); |
654 | if (sz == 0) { |
655 | ps_write(bs, "true", 4, pkt_type_json, req_id, true, true); |
656 | return stream_state_ended_ok; |
657 | } |
658 | rc = crypto_hash_sha256_update(hash_state, buf, sz); |
659 | if (rc < 0) errx(1, "hash update failed"); |
660 | ps_write(bs, (char *)buf, sz, pkt_type_buffer, req_id, true, false); |
661 | return stream_state_open; |
662 | } |
663 | |
664 | static int muxrpc_write_sink(struct boxs *bs, int infd, enum pkt_type ptype, int req_id) { |
665 | int rc; |
666 | fd_set rd; |
667 | int sfd = bs->s; |
668 | int maxfd = infd > sfd ? infd : sfd; |
669 | enum stream_state in = stream_state_open; |
670 | enum stream_state out = stream_state_open; |
671 | |
672 | while (out == stream_state_open) { |
673 | FD_ZERO(&rd); |
674 | if (in == stream_state_open) FD_SET(infd, &rd); |
675 | if (out == stream_state_open) FD_SET(sfd, &rd); |
676 | rc = select(maxfd + 1, &rd, 0, 0, NULL); |
677 | if (rc < 0) err(1, "select"); |
678 | if (FD_ISSET(infd, &rd)) in = muxrpc_write_sink_1(bs, infd, ptype, req_id); |
679 | if (FD_ISSET(sfd, &rd)) out = muxrpc_read_source_1(bs, -1, req_id); |
680 | } |
681 | |
682 | return in == stream_state_ended_ok && out == stream_state_ended_ok ? 0 : |
683 | in == stream_state_ended_error || out == stream_state_ended_error ? 2 : 1; |
684 | } |
685 | |
686 | static int muxrpc_write_blob_add(struct boxs *bs, int infd, int outfd, int req_id) { |
687 | int rc; |
688 | fd_set rd; |
689 | int sfd = bs->s; |
690 | int maxfd = infd > sfd ? infd : sfd; |
691 | enum stream_state in = stream_state_open; |
692 | enum stream_state out = stream_state_open; |
693 | crypto_hash_sha256_state hash_state; |
694 | unsigned char hash[32]; |
695 | char id[54] = "&"; |
696 | |
697 | rc = crypto_hash_sha256_init(&hash_state); |
698 | if (rc < 0) { errno = EINVAL; return -1; } |
699 | |
700 | while (out == stream_state_open) { |
701 | FD_ZERO(&rd); |
702 | if (in == stream_state_open) FD_SET(infd, &rd); |
703 | if (out == stream_state_open) FD_SET(sfd, &rd); |
704 | rc = select(maxfd + 1, &rd, 0, 0, NULL); |
705 | if (rc < 0) err(1, "select"); |
706 | if (FD_ISSET(infd, &rd)) in = muxrpc_write_sink_1_hashed(bs, infd, &hash_state, req_id); |
707 | if (FD_ISSET(sfd, &rd)) out = muxrpc_read_source_1(bs, -1, req_id); |
708 | } |
709 | |
710 | rc = crypto_hash_sha256_final(&hash_state, hash); |
711 | if (rc < 0) errx(1, "hash finalize failed"); |
712 | |
713 | rc = base64_encode(hash, 32, id+1, sizeof(id)-1); |
714 | if (rc < 0) err(1, "encoding hash failed"); |
715 | strcpy(id + 45, ".sha256\n"); |
716 | rc = write_all(outfd, id, sizeof(id)-1); |
717 | if (rc < 0) err(1, "writing hash failed"); |
718 | |
719 | return in == stream_state_ended_ok && out == stream_state_ended_ok ? 0 : |
720 | in == stream_state_ended_error || out == stream_state_ended_error ? 2 : 1; |
721 | } |
722 | |
723 | static int muxrpc_duplex(struct boxs *bs, int infd, int outfd, enum pkt_type in_ptype, int req_id) { |
724 | int rc; |
725 | fd_set rd; |
726 | int sfd = bs->s; |
727 | int maxfd = infd > sfd ? infd : sfd; |
728 | enum stream_state in = stream_state_open; |
729 | enum stream_state out = stream_state_open; |
730 | |
731 | while (out == stream_state_open |
732 | || (in == stream_state_open && out != stream_state_ended_error)) { |
733 | FD_ZERO(&rd); |
734 | if (in == stream_state_open) FD_SET(infd, &rd); |
735 | if (out == stream_state_open) FD_SET(sfd, &rd); |
736 | rc = select(maxfd + 1, &rd, 0, 0, NULL); |
737 | if (rc < 0) err(1, "select"); |
738 | if (FD_ISSET(infd, &rd)) in = muxrpc_write_sink_1(bs, infd, in_ptype, req_id); |
739 | if (FD_ISSET(sfd, &rd)) out = muxrpc_read_source_1(bs, outfd, req_id); |
740 | } |
741 | |
742 | return in == stream_state_ended_ok && out == stream_state_ended_ok ? 0 : |
743 | in == stream_state_ended_error || out == stream_state_ended_error ? 2 : 1; |
744 | } |
745 | |
746 | static int method_to_json(char *out, size_t outlen, const char *str) { |
747 | // blobs.get => ["blobs", "get"] |
748 | size_t i = 0; |
749 | char c; |
750 | if (i+2 > outlen) return -1; |
751 | out[i++] = '['; |
752 | out[i++] = '"'; |
753 | while ((c = *str++)) { |
754 | if (c == '.') { |
755 | if (i+3 > outlen) return -1; |
756 | out[i++] = '"'; |
757 | out[i++] = ','; |
758 | out[i++] = '"'; |
759 | } else if (c == '"') { |
760 | if (i+2 > outlen) return -1; |
761 | out[i++] = '\\'; |
762 | out[i++] = '"'; |
763 | } else { |
764 | if (i+1 > outlen) return -1; |
765 | out[i++] = c; |
766 | } |
767 | } |
768 | if (i+3 > outlen) return -1; |
769 | out[i++] = '"'; |
770 | out[i++] = ']'; |
771 | out[i++] = '\0'; |
772 | return i; |
773 | } |
774 | |
775 | static int args_to_json_length(int argc, char *argv[]) { |
776 | int i = 0; |
777 | int len = 3; // "[]\0" |
778 | for (i = 0; i < argc; i++) |
779 | len += strlen(argv[i])+1; |
780 | return len; |
781 | } |
782 | |
783 | static int args_to_json(char *out, size_t outlen, unsigned int argc, char *argv[]) { |
784 | size_t i = 0; |
785 | size_t j; |
786 | if (i+1 > outlen) return -1; |
787 | out[i++] = '['; |
788 | for (j = 0; j < argc; j++) { |
789 | size_t len = strlen(argv[j]); |
790 | if (j > 0) out[i++] = ','; |
791 | if (i+len > outlen) return -1; |
792 | strncpy(out+i, argv[j], len); |
793 | i += len; |
794 | } |
795 | if (i+2 > outlen) return -1; |
796 | out[i++] = ']'; |
797 | out[i++] = '\0'; |
798 | return i; |
799 | } |
800 | |
801 | int main(int argc, char *argv[]) { |
802 | int i, s, infd, outfd, rc; |
803 | const char *key = NULL; |
804 | const char *keypair_seed_str = NULL; |
805 | const char *host = NULL; |
806 | const char *port = "8008"; |
807 | const char *typestr = NULL, *methodstr; |
808 | const char *shs_cap_key_str = NULL; |
809 | size_t argument_len; |
810 | unsigned char private_key[64]; |
811 | unsigned char public_key[32]; |
812 | unsigned char remote_key[32]; |
813 | unsigned char shs_cap_key[32]; |
814 | enum muxrpc_type type; |
815 | enum pkt_type ptype = pkt_type_buffer; |
816 | char method[256]; |
817 | char app_dir[_POSIX_PATH_MAX]; |
818 | ssize_t len; |
819 | bool test = false; |
820 | |
821 | get_app_dir(app_dir, sizeof(app_dir)); |
822 | |
823 | char config_buf[8192]; |
824 | len = read_file(config_buf, sizeof(config_buf), "%s/config", app_dir); |
825 | if (len > 0) { |
826 | ssize_t host_len = json_get_value(config_buf, "host", &host); |
827 | ssize_t port_len = json_get_value(config_buf, "port", &port); |
828 | ssize_t shs_cap_len = json_get_value(config_buf, "caps.shs", &shs_cap_key_str); |
829 | if (host_len >= 0) ((char *)host)[host_len] = '\0'; |
830 | if (port_len >= 0) ((char *)port)[port_len] = '\0'; |
831 | if (shs_cap_len >= 0) ((char *)shs_cap_key_str)[shs_cap_len] = '\0'; |
832 | } else if (len < 0 && errno != ENOENT) { |
833 | err(1, "failed to read config"); |
834 | } |
835 | |
836 | for (i = 1; i < argc && (argv[i][0] == '-'); i++) { |
837 | switch (argv[i][1]) { |
838 | case 'c': shs_cap_key_str = argv[++i]; break; |
839 | case 'j': ptype = pkt_type_json; break; |
840 | case 'T': test = true; break; |
841 | case 's': host = argv[++i]; break; |
842 | case 'k': key = argv[++i]; break; |
843 | case 'K': keypair_seed_str = argv[++i]; break; |
844 | case 'p': port = argv[++i]; break; |
845 | case 't': typestr = argv[++i]; break; |
846 | default: usage(); |
847 | } |
848 | } |
849 | if (i < argc) methodstr = argv[i++]; else if (!test) usage(); |
850 | |
851 | if (shs_cap_key_str) { |
852 | rc = pubkey_decode(shs_cap_key_str, shs_cap_key); |
853 | if (rc < 0) err(1, "unable to decode cap key '%s'", shs_cap_key_str); |
854 | } else { |
855 | memcpy(shs_cap_key, ssb_cap, 32); |
856 | } |
857 | |
858 | argument_len = test ? 0 : args_to_json_length(argc-i, argv+i); |
859 | char argument[argument_len]; |
860 | |
861 | if (!test) { |
862 | rc = args_to_json(argument, sizeof(argument), argc-i, argv+i); |
863 | if (rc < 0) errx(0, "unable to collect arguments"); |
864 | |
865 | char manifest_buf[8192]; |
866 | if (!typestr) { |
867 | len = read_file(manifest_buf, sizeof(manifest_buf), |
868 | "%s/manifest.json", app_dir); |
869 | if (len < 0) err(1, "failed to read manifest file"); |
870 | |
871 | ssize_t type_len = json_get_value(manifest_buf, methodstr, &typestr); |
872 | if (!typestr && errno == ENOMSG) errx(1, |
873 | "unable to find method '%s' in manifest", methodstr); |
874 | if (!typestr) err(1, "unable to read manifest"); |
875 | ((char *)typestr)[type_len] = '\0'; |
876 | } |
877 | if (strcmp(typestr, "sync") == 0) type = muxrpc_type_async; |
878 | else if (strcmp(typestr, "async") == 0) type = muxrpc_type_async; |
879 | else if (strcmp(typestr, "sink") == 0) type = muxrpc_type_sink; |
880 | else if (strcmp(typestr, "source") == 0) type = muxrpc_type_source; |
881 | else if (strcmp(typestr, "duplex") == 0) type = muxrpc_type_duplex; |
882 | else errx(1, "type must be one of <async|sink|source|duplex>"); |
883 | |
884 | rc = method_to_json(method, sizeof(method), methodstr); |
885 | if (rc < 0) errx(0, "unable to convert method name"); |
886 | } |
887 | |
888 | if (keypair_seed_str) { |
889 | unsigned char seed[crypto_sign_SEEDBYTES]; |
890 | unsigned char ed25519_skpk[crypto_sign_ed25519_SECRETKEYBYTES]; |
891 | |
892 | rc = pubkey_decode(keypair_seed_str, ed25519_skpk); |
893 | if (rc < 0) err(1, "unable to decode private key"); |
894 | rc = crypto_sign_ed25519_sk_to_seed(seed, ed25519_skpk); |
895 | if (rc < 0) err(1, "unable to convert private key to seed"); |
896 | rc = crypto_sign_seed_keypair(public_key, private_key, seed); |
897 | if (rc < 0) err(1, "unable to generate keypair from seed"); |
898 | } else { |
899 | read_private_key(app_dir, private_key); |
900 | memcpy(public_key, private_key+32, 32); |
901 | } |
902 | |
903 | if (key) { |
904 | rc = pubkey_decode(key, remote_key); |
905 | if (rc < 0) err(1, "unable to decode remote key '%s'", key); |
906 | } else { |
907 | memcpy(remote_key, public_key, 32); |
908 | } |
909 | |
910 | if (test) { |
911 | infd = STDIN_FILENO; |
912 | outfd = STDOUT_FILENO; |
913 | s = -1; |
914 | } else { |
915 | s = tcp_connect(host, port); |
916 | if (s < 0) err(1, "tcp_connect"); |
917 | infd = outfd = s; |
918 | } |
919 | |
920 | struct boxs bs; |
921 | shs_connect(s, infd, outfd, public_key, private_key, shs_cap_key, remote_key, &bs); |
922 | |
923 | if (test) { |
924 | rc = write_all(outfd, bs.encrypt_key, sizeof(bs.encrypt_key)); |
925 | rc |= write_all(outfd, bs.nonce1, sizeof(bs.nonce1)); |
926 | rc |= write_all(outfd, bs.decrypt_key, sizeof(bs.decrypt_key)); |
927 | rc |= write_all(outfd, bs.rx_nonce, sizeof(bs.rx_nonce)); |
928 | if (rc < 0) err(1, "failed to write handshake result"); |
929 | return 0; |
930 | } |
931 | |
932 | muxrpc_call(&bs, method, argument, type, typestr, 1); |
933 | |
934 | switch (type) { |
935 | case muxrpc_type_async: |
936 | rc = muxrpc_read_async(&bs, STDOUT_FILENO, 1); |
937 | break; |
938 | case muxrpc_type_source: |
939 | rc = muxrpc_read_source(&bs, STDOUT_FILENO, 1); |
940 | break; |
941 | case muxrpc_type_sink: |
942 | if (!strcmp(methodstr, "blobs.add")) { |
943 | rc = muxrpc_write_blob_add(&bs, STDIN_FILENO, STDOUT_FILENO, 1); |
944 | } else { |
945 | rc = muxrpc_write_sink(&bs, STDIN_FILENO, ptype, 1); |
946 | } |
947 | break; |
948 | case muxrpc_type_duplex: |
949 | rc = muxrpc_duplex(&bs, STDIN_FILENO, STDOUT_FILENO, ptype, 1); |
950 | break; |
951 | } |
952 | |
953 | close(s); |
954 | return rc; |
955 | } |
956 |
Built with git-ssb-web