diff --git a/sbotc.1 b/sbotc.1 index 63133ab..a707823 100644 --- a/sbotc.1 +++ b/sbotc.1 @@ -8,6 +8,7 @@ .Sh SYNOPSIS .Nm .Op Fl j +.Op Fl T .Op Fl a Ar cap .Op Fl s Ar host .Op Fl p Ar port @@ -22,6 +23,10 @@ standard I/O. .Bl -tag .It Fl j Send stdin data as JSON. +.It Fl T +Test using shs1-testsuite protocol. Instead of connecting to a server and running +a command, connect to stdio. On successful handshake, output concatenation of +the encryption key, encryption nonce, decryption key and decryption nonce. .It Fl c Ar cap Capability key for secret-handshake. Default is SSB's capability key, .Li 1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s= . diff --git a/sbotc.c b/sbotc.c index 88da832..b907aba 100644 --- a/sbotc.c +++ b/sbotc.c @@ -95,7 +95,7 @@ static const unsigned char ssb_cap[] = { }; static void usage() { - fputs("usage: sbotc [-j] [-a ] [-s ] [-p ] [-k ] [-t ] " + fputs("usage: sbotc [-j] [-T] [-a ] [-s ] [-p ] [-k ] [-t ] " " [...]\n", stderr); exit(EXIT_FAILURE); } @@ -153,7 +153,7 @@ static int write_all(int fd, const void *buf, size_t count) { return 0; } -static void shs_connect(int s, const unsigned char pubkey[32], const unsigned char seckey[64], const unsigned char appkey[32], const unsigned char server_pubkey[32], struct boxs *bs) { +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) { int rc; unsigned char local_app_mac[32], remote_app_mac[32]; @@ -168,12 +168,12 @@ static void shs_connect(int s, const unsigned char pubkey[32], const unsigned ch unsigned char buf[64]; memcpy(buf, local_app_mac, 32); memcpy(buf+32, kx_pk, 32); - rc = write_all(s, buf, sizeof(buf)); + rc = write_all(outfd, buf, sizeof(buf)); if (rc < 0) err(1, "failed to send challenge"); // recv challenge unsigned char remote_kx_pk[32]; - rc = read_all(s, buf, sizeof(buf)); + rc = read_all(infd, buf, sizeof(buf)); if (rc < 0) err(1, "challenge not accepted"); memcpy(remote_app_mac, buf, 32); memcpy(remote_kx_pk, buf+32, 32); @@ -224,13 +224,13 @@ static void shs_connect(int s, const unsigned char pubkey[32], const unsigned ch rc = crypto_secretbox_easy(boxed_auth, hello, sizeof(hello), zeros, secret2); if (rc < 0) errx(1, "failed to box hello"); - rc = write_all(s, boxed_auth, sizeof(boxed_auth)); + rc = write_all(outfd, boxed_auth, sizeof(boxed_auth)); if (rc < 0) errx(1, "failed to send auth"); // verify accept unsigned char boxed_okay[80]; - rc = read_all(s, boxed_okay, sizeof(boxed_okay)); + rc = read_all(infd, boxed_okay, sizeof(boxed_okay)); if (rc < 0) err(1, "hello not accepted"); unsigned char local_sk_curve[32]; @@ -283,7 +283,7 @@ static void shs_connect(int s, const unsigned char pubkey[32], const unsigned ch bs->rx_buf_pos = 0; bs->rx_buf_len = 0; - bs->s = s; + bs->s = sfd; } static int pubkey_decode(const char *key_str, unsigned char key[32]) { @@ -797,7 +797,7 @@ static int args_to_json(char *out, size_t outlen, unsigned int argc, char *argv[ } int main(int argc, char *argv[]) { - int i, s, rc; + int i, s, infd, outfd, rc; const char *key = NULL; const char *host = NULL; const char *port = "8008"; @@ -812,7 +812,9 @@ int main(int argc, char *argv[]) { enum pkt_type ptype = pkt_type_buffer; char method[256]; char app_dir[_POSIX_PATH_MAX]; + char argument[argument_len]; ssize_t len; + bool test = false; get_app_dir(app_dir, sizeof(app_dir)); @@ -833,6 +835,7 @@ int main(int argc, char *argv[]) { switch (argv[i][1]) { case 'c': shs_cap_key_str = argv[++i]; break; case 'j': ptype = pkt_type_json; break; + case 'T': test = true; break; case 's': host = argv[++i]; break; case 'k': key = argv[++i]; break; case 'p': port = argv[++i]; break; @@ -840,7 +843,7 @@ int main(int argc, char *argv[]) { default: usage(); } } - if (i < argc) methodstr = argv[i++]; else usage(); + if (i < argc) methodstr = argv[i++]; else if (!test) usage(); if (shs_cap_key_str) { rc = pubkey_decode(shs_cap_key_str, shs_cap_key); @@ -849,33 +852,33 @@ int main(int argc, char *argv[]) { memcpy(shs_cap_key, ssb_cap, 32); } + if (!test) { + argument_len = args_to_json_length(argc-i, argv+i); + rc = args_to_json(argument, sizeof(argument), argc-i, argv+i); + if (rc < 0) errx(0, "unable to collect arguments"); - argument_len = args_to_json_length(argc-i, argv+i); - char argument[argument_len]; - rc = args_to_json(argument, sizeof(argument), argc-i, argv+i); - if (rc < 0) errx(0, "unable to collect arguments"); - - char manifest_buf[8192]; - if (!typestr) { - len = read_file(manifest_buf, sizeof(manifest_buf), - "%s/manifest.json", app_dir); - if (len < 0) err(1, "failed to read manifest file"); - - ssize_t type_len = json_get_value(manifest_buf, methodstr, &typestr); - if (!typestr && errno == ENOMSG) errx(1, - "unable to find method '%s' in manifest", methodstr); - if (!typestr) err(1, "unable to read manifest"); - ((char *)typestr)[type_len] = '\0'; - } - if (strcmp(typestr, "sync") == 0) type = muxrpc_type_async; - else if (strcmp(typestr, "async") == 0) type = muxrpc_type_async; - else if (strcmp(typestr, "sink") == 0) type = muxrpc_type_sink; - else if (strcmp(typestr, "source") == 0) type = muxrpc_type_source; - else if (strcmp(typestr, "duplex") == 0) type = muxrpc_type_duplex; - else errx(1, "type must be one of "); - - rc = method_to_json(method, sizeof(method), methodstr); - if (rc < 0) errx(0, "unable to convert method name"); + char manifest_buf[8192]; + if (!typestr) { + len = read_file(manifest_buf, sizeof(manifest_buf), + "%s/manifest.json", app_dir); + if (len < 0) err(1, "failed to read manifest file"); + + ssize_t type_len = json_get_value(manifest_buf, methodstr, &typestr); + if (!typestr && errno == ENOMSG) errx(1, + "unable to find method '%s' in manifest", methodstr); + if (!typestr) err(1, "unable to read manifest"); + ((char *)typestr)[type_len] = '\0'; + } + if (strcmp(typestr, "sync") == 0) type = muxrpc_type_async; + else if (strcmp(typestr, "async") == 0) type = muxrpc_type_async; + else if (strcmp(typestr, "sink") == 0) type = muxrpc_type_sink; + else if (strcmp(typestr, "source") == 0) type = muxrpc_type_source; + else if (strcmp(typestr, "duplex") == 0) type = muxrpc_type_duplex; + else errx(1, "type must be one of "); + + rc = method_to_json(method, sizeof(method), methodstr); + if (rc < 0) errx(0, "unable to convert method name"); + } read_private_key(app_dir, private_key); @@ -887,11 +890,27 @@ int main(int argc, char *argv[]) { memcpy(remote_key, public_key, 32); } - s = tcp_connect(host, port); - if (s < 0) err(1, "tcp_connect"); + if (test) { + infd = STDIN_FILENO; + outfd = STDOUT_FILENO; + s = -1; + } else { + s = tcp_connect(host, port); + if (s < 0) err(1, "tcp_connect"); + infd = outfd = s; + } struct boxs bs; - shs_connect(s, public_key, private_key, shs_cap_key, remote_key, &bs); + shs_connect(s, infd, outfd, public_key, private_key, shs_cap_key, remote_key, &bs); + + if (test) { + rc = write_all(outfd, bs.encrypt_key, sizeof(bs.encrypt_key)); + rc |= write_all(outfd, bs.nonce1, sizeof(bs.nonce1)); + rc |= write_all(outfd, bs.decrypt_key, sizeof(bs.decrypt_key)); + rc |= write_all(outfd, bs.rx_nonce, sizeof(bs.rx_nonce)); + if (rc < 0) err(1, "failed to write handshake result"); + return 0; + } muxrpc_call(&bs, method, argument, type, typestr, 1); diff --git a/test-shs-inner.sh b/test-shs-inner.sh new file mode 100755 index 0000000..622e241 --- /dev/null +++ b/test-shs-inner.sh @@ -0,0 +1,8 @@ +#!/bin/sh +cap_hex=${1?shs cap key} +pk_hex=${2?server public key} + +cap_b64="$(echo -n "$cap_hex" | xxd -r -p | base64)" +pk_b64="$(echo -n "$pk_hex" | xxd -r -p | base64)" + +exec sbotc -T -c "$cap_b64" -k "$pk_b64" diff --git a/test-shs.sh b/test-shs.sh new file mode 100755 index 0000000..f233f93 --- /dev/null +++ b/test-shs.sh @@ -0,0 +1,3 @@ +#!/bin/sh +# test using [shs1-testsuite](%riikqU1Zc/dgjc80vABMA3DkTzTHzlxEYxGU5NYwje8=.sha256) +exec shs1testclient "$(dirname "$0")"/test-shs-inner.sh "$@"