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 pulic signing key from secret key\n"
73 " -H Get pulic 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
);
162 done
= !pex_msg_update_response_continue(&ctx
);
168 static void pex_recv(struct pex_hdr
*hdr
, struct sockaddr_in6
*addr
)
170 struct pex_ext_hdr
*ehdr
= (void *)(hdr
+ 1);
171 void *data
= (void *)(ehdr
+ 1);
172 uint32_t len
= be32_to_cpu(hdr
->len
);
173 uint64_t *msg_req_id
= data
;
175 if (hdr
->version
!= 0)
178 if (memcmp(ehdr
->auth_id
, pubkey
, sizeof(ehdr
->auth_id
)) != 0)
181 *(uint64_t *)hdr
->id
^= pex_network_hash(pubkey
, ehdr
->nonce
);
183 switch (hdr
->opcode
) {
184 case PEX_MSG_UPDATE_REQUEST
:
185 if (cmd
!= CMD_UPLOAD
)
188 pex_handle_update_request(addr
, hdr
->id
, data
, len
);
190 case PEX_MSG_UPDATE_RESPONSE
:
191 case PEX_MSG_UPDATE_RESPONSE_DATA
:
192 case PEX_MSG_UPDATE_RESPONSE_NO_DATA
:
193 if (len
< sizeof(*msg_req_id
) || *msg_req_id
!= req_id
)
196 if (cmd
== CMD_DOWNLOAD
&&
197 hdr
->opcode
== PEX_MSG_UPDATE_RESPONSE_NO_DATA
) {
198 INFO("No network data available\n");
202 if (cmd
== CMD_UPLOAD
&&
203 hdr
->opcode
!= PEX_MSG_UPDATE_RESPONSE_NO_DATA
) {
204 INFO("Server has newer network data\n");
208 pex_recv_update_response(data
, hdr
->len
, hdr
->opcode
);
213 static int load_network_data(const char *file
)
215 static const struct blobmsg_policy policy
= { "hosts", BLOBMSG_TYPE_TABLE
};
216 struct unet_auth_hdr
*hdr
;
217 struct unet_auth_data
*data
;
220 net_data_len
= UNETD_NET_DATA_SIZE_MAX
;
221 net_data
= unet_read_file(file
, &net_data_len
);
223 INFO("failed to read input file %s\n", file
);
227 if (unet_auth_data_validate(NULL
, net_data
, net_data_len
, &net_data_version
, &json
) < 0) {
228 INFO("input data validation failed\n");
233 data
= (struct unet_auth_data
*)(hdr
+ 1);
234 memcpy(pubkey
, data
->pubkey
, sizeof(pubkey
));
236 blob_buf_init(&b
, 0);
237 blobmsg_add_json_from_string(&b
, json
);
239 blobmsg_parse(&policy
, 1, &net_data_hosts
, blobmsg_data(b
.head
), blobmsg_len(b
.head
));
240 if (!net_data_hosts
) {
241 INFO("network data is missing the hosts attribute\n");
249 static int cmd_sync(const char *endpoint
, int argc
, char **argv
)
251 uint8_t peerpubkey
[EDSIGN_PUBLIC_KEY_SIZE
];
252 struct uloop_timeout timeout
= {
255 struct pex_update_request
*req
;
256 union network_endpoint ep
= {};
259 if (cmd
== CMD_UPLOAD
) {
261 INFO("missing file argument\n");
265 if (load_network_data(argv
[0]))
269 if (network_get_endpoint(&ep
, endpoint
, UNETD_GLOBAL_PEX_PORT
, 0) < 0) {
270 INFO("Invalid hostname/port %s\n", endpoint
);
274 len
= ep
.sa
.sa_family
== AF_INET6
? sizeof(ep
.in6
) : sizeof(ep
.in
);
278 if (pex_open(&ep
, len
, pex_recv
, false) < 0)
281 uloop_timeout_set(&timeout
, 5000);
283 curve25519_generate_public(peerpubkey
, peerkey
);
284 req
= pex_msg_update_request_init(peerpubkey
, peerkey
, pubkey
, &ep
,
285 net_data_version
, true);
289 req_id
= req
->req_id
;
290 if (__pex_msg_send(-1, NULL
) < 0) {
301 static int cmd_sign(int argc
, char **argv
)
303 struct unet_auth_hdr hdr
= {
304 .magic
= cpu_to_be32(UNET_AUTH_MAGIC
),
306 struct unet_auth_data
*data
;
313 INFO("Missing filename\n");
317 if (gettimeofday(&tv
, NULL
)) {
319 perror("gettimeofday");
323 if (stat(argv
[0], &st
) ||
324 (f
= fopen(argv
[0], "r")) == NULL
) {
325 INFO("Input file not found\n");
329 data
= calloc(1, sizeof(*data
) + st
.st_size
+ 1);
330 data
->timestamp
= cpu_to_be64(tv
.tv_sec
);
331 len
= fread(data
+ 1, 1, st
.st_size
, f
);
334 if (len
!= st
.st_size
) {
335 INFO("Error reading from input file\n");
339 len
+= sizeof(*data
) + 1;
341 memcpy(data
->pubkey
, pubkey
, sizeof(pubkey
));
342 edsign_sign(hdr
.signature
, pubkey
, seckey
, (const void *)data
, len
);
344 fwrite(&hdr
, sizeof(hdr
), 1, out_file
);
345 fwrite(data
, len
, 1, out_file
);
352 static int cmd_verify(int argc
, char **argv
)
354 struct unet_auth_data
*data
;
355 struct unet_auth_hdr
*hdr
;
362 INFO("Missing filename\n");
366 if (stat(argv
[0], &st
) ||
367 (f
= fopen(argv
[0], "r")) == NULL
) {
368 INFO("Input file not found\n");
372 if (st
.st_size
<= sizeof(*hdr
) + sizeof(*data
)) {
373 INFO("Input file too small\n");
378 hdr
= calloc(1, st
.st_size
);
379 len
= fread(hdr
, 1, st
.st_size
, f
);
382 if (len
!= st
.st_size
) {
383 INFO("Error reading from input file\n");
387 ret
= unet_auth_data_validate(pubkey
, hdr
, len
, NULL
, NULL
);
390 INFO("Invalid input data\n");
393 INFO("Public key does not match\n");
396 INFO("Signature verification failed\n");
404 static int cmd_host_pubkey(int argc
, char **argv
)
406 curve25519_generate_public(pubkey
, seckey
);
412 static int cmd_pubkey(int argc
, char **argv
)
419 static int cmd_generate(int argc
, char **argv
)
424 f
= fopen("/dev/urandom", "r");
426 INFO("Can't open /dev/urandom\n");
430 ret
= fread(seckey
, sizeof(seckey
), 1, f
);
434 INFO("Can't read data from /dev/urandom\n");
438 ed25519_prepare(seckey
);
444 static bool parse_key(uint8_t *dest
, const char *str
)
446 char keystr
[B64_ENCODE_LEN(EDSIGN_PUBLIC_KEY_SIZE
) + 2];
450 if (!strcmp(str
, "-"))
456 INFO("Can't open key file for reading\n");
460 len
= fread(keystr
, 1, sizeof(keystr
) - 1, f
);
466 if (b64_decode(keystr
, dest
, EDSIGN_PUBLIC_KEY_SIZE
) != EDSIGN_PUBLIC_KEY_SIZE
) {
467 INFO("Failed to parse key data\n");
474 static bool cmd_needs_peerkey(void)
484 static bool cmd_needs_pubkey(void)
495 static bool cmd_needs_key(void)
500 case CMD_HOST_PUBKEY
:
507 static bool cmd_needs_outfile(void)
520 int main(int argc
, char **argv
)
522 const char *progname
= argv
[0];
523 const char *out_filename
= NULL
;
524 const char *cmd_arg
= NULL
;
525 bool has_key
= false, has_pubkey
= false;
526 bool has_peerkey
= false;
529 while ((ch
= getopt(argc
, argv
, "h:k:K:o:qD:GHPSU:V")) != -1) {
538 if (cmd
!= CMD_UNKNOWN
)
539 return usage(progname
);
550 out_filename
= optarg
;
554 return usage(progname
);
556 if (!parse_key(peerkey
, optarg
)) {
564 return usage(progname
);
566 if (!parse_key(pubkey
, optarg
)) {
574 return usage(progname
);
576 if (!parse_key(seckey
, optarg
)) {
582 edsign_sec_to_pub(pubkey
, seckey
);
603 cmd
= CMD_HOST_PUBKEY
;
609 return usage(progname
);
613 if (!has_peerkey
&& cmd_needs_peerkey()) {
614 INFO("Missing -h <key> argument\n");
618 if (!has_key
&& cmd_needs_key()) {
619 INFO("Missing -K <key> argument\n");
623 if (!has_pubkey
&& cmd_needs_pubkey()) {
624 INFO("Missing -k <key> argument\n");
631 if (out_filename
&& cmd_needs_outfile()) {
632 out_file
= fopen(out_filename
, "w");
634 INFO("Failed to open output file\n");
645 ret
= cmd_sync(cmd_arg
, argc
, argv
);
648 ret
= cmd_generate(argc
, argv
);
651 ret
= cmd_sign(argc
, argv
);
654 ret
= cmd_pubkey(argc
, argv
);
656 case CMD_HOST_PUBKEY
:
657 ret
= cmd_host_pubkey(argc
, argv
);
660 ret
= cmd_verify(argc
, argv
);
663 ret
= usage(progname
);
672 if (out_file
!= stdout
) {
675 unlink(out_filename
);