cli.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
  4. */
  5. #include <sys/stat.h>
  6. #include <sys/time.h>
  7. #include <sys/mman.h>
  8. #include <sys/types.h>
  9. #include <sys/socket.h>
  10. #include <arpa/inet.h>
  11. #include <stdio.h>
  12. #include <stdint.h>
  13. #include <stdlib.h>
  14. #include <fcntl.h>
  15. #include <errno.h>
  16. #include <libubox/utils.h>
  17. #include <libubox/uloop.h>
  18. #include <libubox/blobmsg.h>
  19. #include <libubox/blobmsg_json.h>
  20. #include "edsign.h"
  21. #include "ed25519.h"
  22. #include "curve25519.h"
  23. #include "auth-data.h"
  24. #include "pex-msg.h"
  25. static uint8_t peerkey[EDSIGN_PUBLIC_KEY_SIZE];
  26. static uint8_t pubkey[EDSIGN_PUBLIC_KEY_SIZE];
  27. static uint8_t seckey[EDSIGN_PUBLIC_KEY_SIZE];
  28. static void *net_data;
  29. static size_t net_data_len;
  30. static uint64_t net_data_version;
  31. static struct blob_attr *net_data_hosts;
  32. static uint64_t req_id;
  33. static struct blob_buf b;
  34. static FILE *out_file;
  35. static bool quiet;
  36. static bool sync_done;
  37. static enum {
  38. CMD_UNKNOWN,
  39. CMD_GENERATE,
  40. CMD_PUBKEY,
  41. CMD_HOST_PUBKEY,
  42. CMD_VERIFY,
  43. CMD_SIGN,
  44. CMD_DOWNLOAD,
  45. CMD_UPLOAD,
  46. } cmd;
  47. #define INFO(...) \
  48. do { \
  49. if (quiet) \
  50. break; \
  51. fprintf(stderr, ##__VA_ARGS__); \
  52. } while (0)
  53. static void print_key(const uint8_t *key)
  54. {
  55. char keystr[B64_ENCODE_LEN(EDSIGN_PUBLIC_KEY_SIZE)];
  56. if (b64_encode(key, EDSIGN_PUBLIC_KEY_SIZE, keystr, sizeof(keystr)) < 0)
  57. return;
  58. fprintf(out_file, "%s\n", keystr);
  59. }
  60. static int usage(const char *progname)
  61. {
  62. fprintf(stderr, "Usage: %s [command|options] [<file>]\n"
  63. "Commands:\n"
  64. " -S Sign file\n"
  65. " -V Verify file\n"
  66. " -P Get public signing key from secret key\n"
  67. " -H Get public host key from secret key\n"
  68. " -G Generate new private key\n"
  69. " -D <host>[:<port>] Download network data from unetd\n"
  70. " -U <host>[:<port>] Upload network data to unetd\n"
  71. "\n"
  72. "Options:\n"
  73. " -q: Quiet mode - suppress error/info messages\n"
  74. " -o <file>: Set output file to <file> (defaults to stdout)\n"
  75. " -k <keyfile>|-: Set public key from file or stdin\n"
  76. " -K <keyfile>|-: Set secret key from file or stdin\n"
  77. " -h <keyfile>|- Set peer private key from file or stdin\n"
  78. " (for network data down-/upload)\n"
  79. "\n", progname);
  80. return 1;
  81. }
  82. static void pex_timeout(struct uloop_timeout *timeout)
  83. {
  84. uloop_end();
  85. }
  86. static void
  87. pex_recv_update_response(const uint8_t *data, size_t len, enum pex_opcode op)
  88. {
  89. int net_data_len = 0;
  90. void *net_data;
  91. net_data = pex_msg_update_response_recv(data, len, op, &net_data_len, NULL);
  92. if (net_data_len < 0)
  93. goto out;
  94. if (!net_data)
  95. return;
  96. if (cmd == CMD_DOWNLOAD) {
  97. fwrite(net_data, net_data_len, 1, out_file);
  98. sync_done = true;
  99. }
  100. free(net_data);
  101. out:
  102. if (cmd == CMD_DOWNLOAD)
  103. uloop_end();
  104. }
  105. static bool
  106. pex_get_pubkey(uint8_t *pubkey, const uint8_t *id)
  107. {
  108. static const struct blobmsg_policy policy = { "key", BLOBMSG_TYPE_STRING };
  109. struct blob_attr *cur, *key;
  110. int rem;
  111. blobmsg_for_each_attr(cur, net_data_hosts, rem) {
  112. const char *keystr;
  113. blobmsg_parse(&policy, 1, &key, blobmsg_data(cur), blobmsg_len(cur));
  114. if (!key)
  115. continue;
  116. keystr = blobmsg_get_string(key);
  117. if (b64_decode(keystr, pubkey, CURVE25519_KEY_SIZE) != CURVE25519_KEY_SIZE)
  118. continue;
  119. if (!memcmp(pubkey, id, PEX_ID_LEN))
  120. return true;
  121. }
  122. return false;
  123. }
  124. static void
  125. pex_handle_update_request(struct sockaddr_in6 *addr, const uint8_t *id, void *data, size_t len)
  126. {
  127. struct pex_msg_update_send_ctx ctx = {};
  128. static uint8_t empty_key[EDSIGN_PUBLIC_KEY_SIZE] = {};
  129. uint8_t peerpubkey[EDSIGN_PUBLIC_KEY_SIZE];
  130. bool done = false;
  131. if (!pex_get_pubkey(peerpubkey, id)) {
  132. INFO("Could not find public key\n");
  133. return;
  134. }
  135. pex_msg_update_response_init(&ctx, empty_key, pubkey,
  136. peerpubkey, true, data, net_data, net_data_len);
  137. while (!done) {
  138. __pex_msg_send(-1, NULL, NULL, 0);
  139. done = !pex_msg_update_response_continue(&ctx);
  140. }
  141. sync_done = true;
  142. uloop_end();
  143. }
  144. static void pex_recv(void *msg, size_t msg_len, struct sockaddr_in6 *addr)
  145. {
  146. struct pex_hdr *hdr;
  147. struct pex_ext_hdr *ehdr;
  148. uint64_t *msg_req_id;
  149. void *data;
  150. hdr = pex_rx_accept(msg, msg_len, true);
  151. if (!hdr)
  152. return;
  153. ehdr = (void *)(hdr + 1);
  154. data = (void *)(ehdr + 1);
  155. msg_req_id = data;
  156. if (hdr->version != 0)
  157. return;
  158. if (memcmp(ehdr->auth_id, pubkey, sizeof(ehdr->auth_id)) != 0)
  159. return;
  160. *(uint64_t *)hdr->id ^= pex_network_hash(pubkey, ehdr->nonce);
  161. switch (hdr->opcode) {
  162. case PEX_MSG_UPDATE_REQUEST:
  163. if (cmd != CMD_UPLOAD)
  164. break;
  165. pex_handle_update_request(addr, hdr->id, data, hdr->len);
  166. break;
  167. case PEX_MSG_UPDATE_RESPONSE:
  168. case PEX_MSG_UPDATE_RESPONSE_DATA:
  169. case PEX_MSG_UPDATE_RESPONSE_NO_DATA:
  170. if (hdr->len < sizeof(*msg_req_id) || *msg_req_id != req_id)
  171. break;
  172. if (cmd == CMD_DOWNLOAD &&
  173. hdr->opcode == PEX_MSG_UPDATE_RESPONSE_NO_DATA) {
  174. INFO("No network data available\n");
  175. uloop_end();
  176. }
  177. if (cmd == CMD_UPLOAD &&
  178. hdr->opcode != PEX_MSG_UPDATE_RESPONSE_NO_DATA) {
  179. INFO("Server has newer network data\n");
  180. uloop_end();
  181. }
  182. pex_recv_update_response(data, hdr->len, hdr->opcode);
  183. break;
  184. }
  185. }
  186. static int load_network_data(const char *file)
  187. {
  188. static const struct blobmsg_policy policy = { "hosts", BLOBMSG_TYPE_TABLE };
  189. struct unet_auth_hdr *hdr;
  190. struct unet_auth_data *data;
  191. const char *json;
  192. net_data_len = UNETD_NET_DATA_SIZE_MAX;
  193. net_data = unet_read_file(file, &net_data_len);
  194. if (!net_data) {
  195. INFO("failed to read input file %s\n", file);
  196. return 1;
  197. }
  198. if (unet_auth_data_validate(NULL, net_data, net_data_len, &net_data_version, &json) < 0) {
  199. INFO("input data validation failed\n");
  200. return 1;
  201. }
  202. hdr = net_data;
  203. data = (struct unet_auth_data *)(hdr + 1);
  204. memcpy(pubkey, data->pubkey, sizeof(pubkey));
  205. blob_buf_init(&b, 0);
  206. blobmsg_add_json_from_string(&b, json);
  207. blobmsg_parse(&policy, 1, &net_data_hosts, blobmsg_data(b.head), blobmsg_len(b.head));
  208. if (!net_data_hosts) {
  209. INFO("network data is missing the hosts attribute\n");
  210. return 1;
  211. }
  212. return 0;
  213. }
  214. static int cmd_sync(const char *endpoint, int argc, char **argv)
  215. {
  216. uint8_t peerpubkey[EDSIGN_PUBLIC_KEY_SIZE];
  217. struct uloop_timeout timeout = {
  218. .cb = pex_timeout
  219. };
  220. struct pex_update_request *req;
  221. union network_endpoint ep = {};
  222. int len;
  223. if (cmd == CMD_UPLOAD) {
  224. if (argc < 1) {
  225. INFO("missing file argument\n");
  226. return 1;
  227. }
  228. if (load_network_data(argv[0]))
  229. return 1;
  230. }
  231. if (network_get_endpoint(&ep, AF_UNSPEC, endpoint, UNETD_GLOBAL_PEX_PORT, 0) < 0) {
  232. INFO("Invalid hostname/port %s\n", endpoint);
  233. return 1;
  234. }
  235. len = ep.sa.sa_family == AF_INET6 ? sizeof(ep.in6) : sizeof(ep.in);
  236. uloop_init();
  237. if (pex_open(&ep, len, pex_recv, false) < 0)
  238. return 1;
  239. uloop_timeout_set(&timeout, 5000);
  240. curve25519_generate_public(peerpubkey, peerkey);
  241. req = pex_msg_update_request_init(peerpubkey, peerkey, pubkey, &ep,
  242. net_data_version, true);
  243. if (!req)
  244. return 1;
  245. req_id = req->req_id;
  246. if (__pex_msg_send(-1, NULL, NULL, 0) < 0) {
  247. if (!quiet)
  248. perror("send");
  249. return 1;
  250. }
  251. uloop_run();
  252. return !sync_done;
  253. }
  254. static int cmd_sign(int argc, char **argv)
  255. {
  256. struct unet_auth_hdr hdr = {
  257. .magic = cpu_to_be32(UNET_AUTH_MAGIC),
  258. };
  259. struct unet_auth_data *data;
  260. struct timeval tv;
  261. struct stat st;
  262. off_t len;
  263. FILE *f;
  264. if (argc != 1) {
  265. INFO("Missing filename\n");
  266. return 1;
  267. }
  268. if (gettimeofday(&tv, NULL)) {
  269. if (!quiet)
  270. perror("gettimeofday");
  271. return 1;
  272. }
  273. if (stat(argv[0], &st) ||
  274. (f = fopen(argv[0], "r")) == NULL) {
  275. INFO("Input file not found\n");
  276. return 1;
  277. }
  278. data = calloc(1, sizeof(*data) + st.st_size + 1);
  279. data->timestamp = cpu_to_be64(tv.tv_sec);
  280. len = fread(data + 1, 1, st.st_size, f);
  281. fclose(f);
  282. if (len != st.st_size) {
  283. INFO("Error reading from input file\n");
  284. return 1;
  285. }
  286. len += sizeof(*data) + 1;
  287. memcpy(data->pubkey, pubkey, sizeof(pubkey));
  288. edsign_sign(hdr.signature, pubkey, seckey, (const void *)data, len);
  289. fwrite(&hdr, sizeof(hdr), 1, out_file);
  290. fwrite(data, len, 1, out_file);
  291. free(data);
  292. return 0;
  293. }
  294. static int cmd_verify(int argc, char **argv)
  295. {
  296. struct unet_auth_data *data;
  297. struct unet_auth_hdr *hdr;
  298. struct stat st;
  299. off_t len;
  300. FILE *f;
  301. int ret = 1;
  302. if (argc != 1) {
  303. INFO("Missing filename\n");
  304. return 1;
  305. }
  306. if (stat(argv[0], &st) ||
  307. (f = fopen(argv[0], "r")) == NULL) {
  308. INFO("Input file not found\n");
  309. return 1;
  310. }
  311. if (st.st_size <= sizeof(*hdr) + sizeof(*data)) {
  312. INFO("Input file too small\n");
  313. fclose(f);
  314. return 1;
  315. }
  316. hdr = calloc(1, st.st_size);
  317. len = fread(hdr, 1, st.st_size, f);
  318. fclose(f);
  319. if (len != st.st_size) {
  320. INFO("Error reading from input file\n");
  321. return 1;
  322. }
  323. ret = unet_auth_data_validate(pubkey, hdr, len, NULL, NULL);
  324. switch (ret) {
  325. case -1:
  326. INFO("Invalid input data\n");
  327. break;
  328. case -2:
  329. INFO("Public key does not match\n");
  330. break;
  331. case -3:
  332. INFO("Signature verification failed\n");
  333. break;
  334. }
  335. free(hdr);
  336. return ret;
  337. }
  338. static int cmd_host_pubkey(int argc, char **argv)
  339. {
  340. curve25519_generate_public(pubkey, seckey);
  341. print_key(pubkey);
  342. return 0;
  343. }
  344. static int cmd_pubkey(int argc, char **argv)
  345. {
  346. print_key(pubkey);
  347. return 0;
  348. }
  349. static int cmd_generate(int argc, char **argv)
  350. {
  351. FILE *f;
  352. int ret;
  353. f = fopen("/dev/urandom", "r");
  354. if (!f) {
  355. INFO("Can't open /dev/urandom\n");
  356. return 1;
  357. }
  358. ret = fread(seckey, sizeof(seckey), 1, f);
  359. fclose(f);
  360. if (ret != 1) {
  361. INFO("Can't read data from /dev/urandom\n");
  362. return 1;
  363. }
  364. ed25519_prepare(seckey);
  365. print_key(seckey);
  366. return 0;
  367. }
  368. static bool parse_key(uint8_t *dest, const char *str)
  369. {
  370. char keystr[B64_ENCODE_LEN(EDSIGN_PUBLIC_KEY_SIZE) + 2];
  371. FILE *f;
  372. int len;
  373. if (!strcmp(str, "-"))
  374. f = stdin;
  375. else
  376. f = fopen(str, "r");
  377. if (!f) {
  378. INFO("Can't open key file for reading\n");
  379. return false;
  380. }
  381. len = fread(keystr, 1, sizeof(keystr) - 1, f);
  382. if (f != stdin)
  383. fclose(f);
  384. keystr[len] = 0;
  385. if (b64_decode(keystr, dest, EDSIGN_PUBLIC_KEY_SIZE) != EDSIGN_PUBLIC_KEY_SIZE) {
  386. INFO("Failed to parse key data\n");
  387. return false;
  388. }
  389. return true;
  390. }
  391. static bool cmd_needs_peerkey(void)
  392. {
  393. switch (cmd) {
  394. case CMD_DOWNLOAD:
  395. return true;
  396. default:
  397. return false;
  398. }
  399. }
  400. static bool cmd_needs_pubkey(void)
  401. {
  402. switch (cmd) {
  403. case CMD_DOWNLOAD:
  404. case CMD_VERIFY:
  405. return true;
  406. default:
  407. return false;
  408. }
  409. }
  410. static bool cmd_needs_key(void)
  411. {
  412. switch (cmd) {
  413. case CMD_SIGN:
  414. case CMD_PUBKEY:
  415. case CMD_HOST_PUBKEY:
  416. return true;
  417. default:
  418. return false;
  419. }
  420. }
  421. static bool cmd_needs_outfile(void)
  422. {
  423. switch (cmd) {
  424. case CMD_SIGN:
  425. case CMD_PUBKEY:
  426. case CMD_GENERATE:
  427. case CMD_DOWNLOAD:
  428. return true;
  429. default:
  430. return false;
  431. }
  432. }
  433. int main(int argc, char **argv)
  434. {
  435. const char *progname = argv[0];
  436. const char *out_filename = NULL;
  437. const char *cmd_arg = NULL;
  438. bool has_key = false, has_pubkey = false;
  439. bool has_peerkey = false;
  440. int ret, ch;
  441. while ((ch = getopt(argc, argv, "h:k:K:o:qD:GHPSU:V")) != -1) {
  442. switch (ch) {
  443. case 'D':
  444. case 'U':
  445. case 'G':
  446. case 'H':
  447. case 'S':
  448. case 'P':
  449. case 'V':
  450. if (cmd != CMD_UNKNOWN)
  451. return usage(progname);
  452. break;
  453. default:
  454. break;
  455. }
  456. switch (ch) {
  457. case 'q':
  458. quiet = true;
  459. break;
  460. case 'o':
  461. out_filename = optarg;
  462. break;
  463. case 'h':
  464. if (has_peerkey)
  465. return usage(progname);
  466. if (!parse_key(peerkey, optarg)) {
  467. return 1;
  468. }
  469. has_peerkey = true;
  470. break;
  471. case 'k':
  472. if (has_pubkey)
  473. return usage(progname);
  474. if (!parse_key(pubkey, optarg)) {
  475. return 1;
  476. }
  477. has_pubkey = true;
  478. break;
  479. case 'K':
  480. if (has_pubkey)
  481. return usage(progname);
  482. if (!parse_key(seckey, optarg)) {
  483. return 1;
  484. }
  485. has_key = true;
  486. edsign_sec_to_pub(pubkey, seckey);
  487. has_pubkey = true;
  488. break;
  489. case 'U':
  490. cmd = CMD_UPLOAD;
  491. cmd_arg = optarg;
  492. break;
  493. case 'D':
  494. cmd = CMD_DOWNLOAD;
  495. cmd_arg = optarg;
  496. break;
  497. case 'G':
  498. cmd = CMD_GENERATE;
  499. break;
  500. case 'S':
  501. cmd = CMD_SIGN;
  502. break;
  503. case 'P':
  504. cmd = CMD_PUBKEY;
  505. break;
  506. case 'H':
  507. cmd = CMD_HOST_PUBKEY;
  508. break;
  509. case 'V':
  510. cmd = CMD_VERIFY;
  511. break;
  512. default:
  513. return usage(progname);
  514. }
  515. }
  516. if (!has_peerkey && cmd_needs_peerkey()) {
  517. INFO("Missing -h <key> argument\n");
  518. return 1;
  519. }
  520. if (!has_key && cmd_needs_key()) {
  521. INFO("Missing -K <key> argument\n");
  522. return 1;
  523. }
  524. if (!has_pubkey && cmd_needs_pubkey()) {
  525. INFO("Missing -k <key> argument\n");
  526. return 1;
  527. }
  528. argc -= optind;
  529. argv += optind;
  530. if (out_filename && cmd_needs_outfile()) {
  531. out_file = fopen(out_filename, "w");
  532. if (!out_file) {
  533. INFO("Failed to open output file\n");
  534. return 1;
  535. }
  536. } else {
  537. out_file = stdout;
  538. }
  539. ret = -1;
  540. switch (cmd) {
  541. case CMD_UPLOAD:
  542. case CMD_DOWNLOAD:
  543. ret = cmd_sync(cmd_arg, argc, argv);
  544. break;
  545. case CMD_GENERATE:
  546. ret = cmd_generate(argc, argv);
  547. break;
  548. case CMD_SIGN:
  549. ret = cmd_sign(argc, argv);
  550. break;
  551. case CMD_PUBKEY:
  552. ret = cmd_pubkey(argc, argv);
  553. break;
  554. case CMD_HOST_PUBKEY:
  555. ret = cmd_host_pubkey(argc, argv);
  556. break;
  557. case CMD_VERIFY:
  558. ret = cmd_verify(argc, argv);
  559. break;
  560. case CMD_UNKNOWN:
  561. ret = usage(progname);
  562. break;
  563. }
  564. if (net_data)
  565. free(net_data);
  566. blob_buf_free(&b);
  567. if (out_file != stdout) {
  568. fclose(out_file);
  569. if (ret)
  570. unlink(out_filename);
  571. }
  572. return ret;
  573. }