1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
9 #include <sys/socket.h>
10 #include <arpa/inet.h>
16 #include <libubox/utils.h>
17 #include <libubox/uloop.h>
18 #include <libubox/blobmsg.h>
19 #include <libubox/blobmsg_json.h>
22 #include "curve25519.h"
23 #include "auth-data.h"
26 static uint8_t peerkey
[EDSIGN_PUBLIC_KEY_SIZE
];
27 static uint8_t pubkey
[EDSIGN_PUBLIC_KEY_SIZE
];
28 static uint8_t seckey
[EDSIGN_PUBLIC_KEY_SIZE
];
29 static void *net_data
;
30 static size_t net_data_len
;
31 static uint64_t net_data_version
;
32 static struct blob_attr
*net_data_hosts
;
33 static uint64_t req_id
;
34 static struct blob_buf b
;
35 static FILE *out_file
;
37 static bool sync_done
;
53 fprintf(stderr, ##__VA_ARGS__); \
56 static void print_key(const uint8_t *key
)
58 char keystr
[B64_ENCODE_LEN(EDSIGN_PUBLIC_KEY_SIZE
)];
60 if (b64_encode(key
, EDSIGN_PUBLIC_KEY_SIZE
, keystr
, sizeof(keystr
)) < 0)
63 fprintf(out_file
, "%s\n", keystr
);
66 static int usage(const char *progname
)
68 fprintf(stderr
, "Usage: %s [command|options] [<file>]\n"
72 " -P Get public signing key from secret key\n"
73 " -H Get public host key from secret key\n"
74 " -G Generate new private key\n"
75 " -D <host>[:<port>] Download network data from unetd\n"
76 " -U <host>[:<port>] Upload network data to unetd\n"
79 " -q: Quiet mode - suppress error/info messages\n"
80 " -o <file>: Set output file to <file> (defaults to stdout)\n"
81 " -k <keyfile>|-: Set public key from file or stdin\n"
82 " -K <keyfile>|-: Set secret key from file or stdin\n"
83 " -h <keyfile>|- Set peer private key from file or stdin\n"
84 " (for network data down-/upload)\n"
89 static void pex_timeout(struct uloop_timeout
*timeout
)
95 pex_recv_update_response(const uint8_t *data
, size_t len
, enum pex_opcode op
)
100 net_data
= pex_msg_update_response_recv(data
, len
, op
, &net_data_len
, NULL
);
101 if (net_data_len
< 0)
107 if (cmd
== CMD_DOWNLOAD
) {
108 fwrite(net_data
, net_data_len
, 1, out_file
);
115 if (cmd
== CMD_DOWNLOAD
)
120 pex_get_pubkey(uint8_t *pubkey
, const uint8_t *id
)
122 static const struct blobmsg_policy policy
= { "key", BLOBMSG_TYPE_STRING
};
123 struct blob_attr
*cur
, *key
;
126 blobmsg_for_each_attr(cur
, net_data_hosts
, rem
) {
129 blobmsg_parse(&policy
, 1, &key
, blobmsg_data(cur
), blobmsg_len(cur
));
134 keystr
= blobmsg_get_string(key
);
135 if (b64_decode(keystr
, pubkey
, CURVE25519_KEY_SIZE
) != CURVE25519_KEY_SIZE
)
138 if (!memcmp(pubkey
, id
, PEX_ID_LEN
))
146 pex_handle_update_request(struct sockaddr_in6
*addr
, const uint8_t *id
, void *data
, size_t len
)
148 struct pex_msg_update_send_ctx ctx
= {};
149 static uint8_t empty_key
[EDSIGN_PUBLIC_KEY_SIZE
] = {};
150 uint8_t peerpubkey
[EDSIGN_PUBLIC_KEY_SIZE
];
153 if (!pex_get_pubkey(peerpubkey
, id
)) {
154 INFO("Could not find public key\n");
158 pex_msg_update_response_init(&ctx
, empty_key
, pubkey
,
159 peerpubkey
, true, data
, net_data
, net_data_len
);
161 __pex_msg_send(-1, NULL
, NULL
, 0);
162 done
= !pex_msg_update_response_continue(&ctx
);
168 static void pex_recv(void *msg
, size_t msg_len
, struct sockaddr_in6
*addr
)
171 struct pex_ext_hdr
*ehdr
;
172 uint64_t *msg_req_id
;
175 hdr
= pex_rx_accept(msg
, msg_len
, true);
179 ehdr
= (void *)(hdr
+ 1);
180 data
= (void *)(ehdr
+ 1);
183 if (hdr
->version
!= 0)
186 if (memcmp(ehdr
->auth_id
, pubkey
, sizeof(ehdr
->auth_id
)) != 0)
189 *(uint64_t *)hdr
->id
^= pex_network_hash(pubkey
, ehdr
->nonce
);
191 switch (hdr
->opcode
) {
192 case PEX_MSG_UPDATE_REQUEST
:
193 if (cmd
!= CMD_UPLOAD
)
196 pex_handle_update_request(addr
, hdr
->id
, data
, hdr
->len
);
198 case PEX_MSG_UPDATE_RESPONSE
:
199 case PEX_MSG_UPDATE_RESPONSE_DATA
:
200 case PEX_MSG_UPDATE_RESPONSE_NO_DATA
:
201 if (hdr
->len
< sizeof(*msg_req_id
) || *msg_req_id
!= req_id
)
204 if (cmd
== CMD_DOWNLOAD
&&
205 hdr
->opcode
== PEX_MSG_UPDATE_RESPONSE_NO_DATA
) {
206 INFO("No network data available\n");
210 if (cmd
== CMD_UPLOAD
&&
211 hdr
->opcode
!= PEX_MSG_UPDATE_RESPONSE_NO_DATA
) {
212 INFO("Server has newer network data\n");
216 pex_recv_update_response(data
, hdr
->len
, hdr
->opcode
);
221 static int load_network_data(const char *file
)
223 static const struct blobmsg_policy policy
= { "hosts", BLOBMSG_TYPE_TABLE
};
224 struct unet_auth_hdr
*hdr
;
225 struct unet_auth_data
*data
;
228 net_data_len
= UNETD_NET_DATA_SIZE_MAX
;
229 net_data
= unet_read_file(file
, &net_data_len
);
231 INFO("failed to read input file %s\n", file
);
235 if (unet_auth_data_validate(NULL
, net_data
, net_data_len
, &net_data_version
, &json
) < 0) {
236 INFO("input data validation failed\n");
241 data
= (struct unet_auth_data
*)(hdr
+ 1);
242 memcpy(pubkey
, data
->pubkey
, sizeof(pubkey
));
244 blob_buf_init(&b
, 0);
245 blobmsg_add_json_from_string(&b
, json
);
247 blobmsg_parse(&policy
, 1, &net_data_hosts
, blobmsg_data(b
.head
), blobmsg_len(b
.head
));
248 if (!net_data_hosts
) {
249 INFO("network data is missing the hosts attribute\n");
257 static int cmd_sync(const char *endpoint
, int argc
, char **argv
)
259 uint8_t peerpubkey
[EDSIGN_PUBLIC_KEY_SIZE
];
260 struct uloop_timeout timeout
= {
263 struct pex_update_request
*req
;
264 union network_endpoint ep
= {};
267 if (cmd
== CMD_UPLOAD
) {
269 INFO("missing file argument\n");
273 if (load_network_data(argv
[0]))
277 if (network_get_endpoint(&ep
, AF_UNSPEC
, endpoint
, UNETD_GLOBAL_PEX_PORT
, 0) < 0) {
278 INFO("Invalid hostname/port %s\n", endpoint
);
282 len
= ep
.sa
.sa_family
== AF_INET6
? sizeof(ep
.in6
) : sizeof(ep
.in
);
286 if (pex_open(&ep
, len
, pex_recv
, false) < 0)
289 uloop_timeout_set(&timeout
, 5000);
291 curve25519_generate_public(peerpubkey
, peerkey
);
292 req
= pex_msg_update_request_init(peerpubkey
, peerkey
, pubkey
, &ep
,
293 net_data_version
, true);
297 req_id
= req
->req_id
;
298 if (__pex_msg_send(-1, NULL
, NULL
, 0) < 0) {
309 static int cmd_sign(int argc
, char **argv
)
311 struct unet_auth_hdr hdr
= {
312 .magic
= cpu_to_be32(UNET_AUTH_MAGIC
),
314 struct unet_auth_data
*data
;
321 INFO("Missing filename\n");
325 if (gettimeofday(&tv
, NULL
)) {
327 perror("gettimeofday");
331 if (stat(argv
[0], &st
) ||
332 (f
= fopen(argv
[0], "r")) == NULL
) {
333 INFO("Input file not found\n");
337 data
= calloc(1, sizeof(*data
) + st
.st_size
+ 1);
338 data
->timestamp
= cpu_to_be64(tv
.tv_sec
);
339 len
= fread(data
+ 1, 1, st
.st_size
, f
);
342 if (len
!= st
.st_size
) {
343 INFO("Error reading from input file\n");
347 len
+= sizeof(*data
) + 1;
349 memcpy(data
->pubkey
, pubkey
, sizeof(pubkey
));
350 edsign_sign(hdr
.signature
, pubkey
, seckey
, (const void *)data
, len
);
352 fwrite(&hdr
, sizeof(hdr
), 1, out_file
);
353 fwrite(data
, len
, 1, out_file
);
360 static int cmd_verify(int argc
, char **argv
)
362 struct unet_auth_data
*data
;
363 struct unet_auth_hdr
*hdr
;
370 INFO("Missing filename\n");
374 if (stat(argv
[0], &st
) ||
375 (f
= fopen(argv
[0], "r")) == NULL
) {
376 INFO("Input file not found\n");
380 if (st
.st_size
<= sizeof(*hdr
) + sizeof(*data
)) {
381 INFO("Input file too small\n");
386 hdr
= calloc(1, st
.st_size
);
387 len
= fread(hdr
, 1, st
.st_size
, f
);
390 if (len
!= st
.st_size
) {
391 INFO("Error reading from input file\n");
395 ret
= unet_auth_data_validate(pubkey
, hdr
, len
, NULL
, NULL
);
398 INFO("Invalid input data\n");
401 INFO("Public key does not match\n");
404 INFO("Signature verification failed\n");
412 static int cmd_host_pubkey(int argc
, char **argv
)
414 curve25519_generate_public(pubkey
, seckey
);
420 static int cmd_pubkey(int argc
, char **argv
)
427 static int cmd_generate(int argc
, char **argv
)
432 f
= fopen("/dev/urandom", "r");
434 INFO("Can't open /dev/urandom\n");
438 ret
= fread(seckey
, sizeof(seckey
), 1, f
);
442 INFO("Can't read data from /dev/urandom\n");
446 ed25519_prepare(seckey
);
452 static bool parse_key(uint8_t *dest
, const char *str
)
454 char keystr
[B64_ENCODE_LEN(EDSIGN_PUBLIC_KEY_SIZE
) + 2];
458 if (!strcmp(str
, "-"))
464 INFO("Can't open key file for reading\n");
468 len
= fread(keystr
, 1, sizeof(keystr
) - 1, f
);
474 if (b64_decode(keystr
, dest
, EDSIGN_PUBLIC_KEY_SIZE
) != EDSIGN_PUBLIC_KEY_SIZE
) {
475 INFO("Failed to parse key data\n");
482 static bool cmd_needs_peerkey(void)
492 static bool cmd_needs_pubkey(void)
503 static bool cmd_needs_key(void)
508 case CMD_HOST_PUBKEY
:
515 static bool cmd_needs_outfile(void)
528 int main(int argc
, char **argv
)
530 const char *progname
= argv
[0];
531 const char *out_filename
= NULL
;
532 const char *cmd_arg
= NULL
;
533 bool has_key
= false, has_pubkey
= false;
534 bool has_peerkey
= false;
537 while ((ch
= getopt(argc
, argv
, "h:k:K:o:qD:GHPSU:V")) != -1) {
546 if (cmd
!= CMD_UNKNOWN
)
547 return usage(progname
);
558 out_filename
= optarg
;
562 return usage(progname
);
564 if (!parse_key(peerkey
, optarg
)) {
572 return usage(progname
);
574 if (!parse_key(pubkey
, optarg
)) {
582 return usage(progname
);
584 if (!parse_key(seckey
, optarg
)) {
590 edsign_sec_to_pub(pubkey
, seckey
);
611 cmd
= CMD_HOST_PUBKEY
;
617 return usage(progname
);
621 if (!has_peerkey
&& cmd_needs_peerkey()) {
622 INFO("Missing -h <key> argument\n");
626 if (!has_key
&& cmd_needs_key()) {
627 INFO("Missing -K <key> argument\n");
631 if (!has_pubkey
&& cmd_needs_pubkey()) {
632 INFO("Missing -k <key> argument\n");
639 if (out_filename
&& cmd_needs_outfile()) {
640 out_file
= fopen(out_filename
, "w");
642 INFO("Failed to open output file\n");
653 ret
= cmd_sync(cmd_arg
, argc
, argv
);
656 ret
= cmd_generate(argc
, argv
);
659 ret
= cmd_sign(argc
, argv
);
662 ret
= cmd_pubkey(argc
, argv
);
664 case CMD_HOST_PUBKEY
:
665 ret
= cmd_host_pubkey(argc
, argv
);
668 ret
= cmd_verify(argc
, argv
);
671 ret
= usage(progname
);
680 if (out_file
!= stdout
) {
683 unlink(out_filename
);