fix SPDX tag
[project/unetd.git] / cli.c
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 <stdio.h>
9 #include <stdint.h>
10 #include <stdlib.h>
11 #include <fcntl.h>
12 #include <libubox/utils.h>
13 #include "edsign.h"
14 #include "ed25519.h"
15 #include "curve25519.h"
16 #include "auth-data.h"
17
18 static uint8_t pubkey[EDSIGN_PUBLIC_KEY_SIZE];
19 static uint8_t seckey[EDSIGN_PUBLIC_KEY_SIZE];
20 static FILE *out_file;
21 static enum {
22 CMD_UNKNOWN,
23 CMD_GENERATE,
24 CMD_PUBKEY,
25 CMD_HOST_PUBKEY,
26 CMD_VERIFY,
27 CMD_SIGN,
28 } cmd;
29
30 static void print_key(const uint8_t *key)
31 {
32 char keystr[B64_ENCODE_LEN(EDSIGN_PUBLIC_KEY_SIZE)];
33
34 if (b64_encode(key, EDSIGN_PUBLIC_KEY_SIZE, keystr, sizeof(keystr)) < 0)
35 return;
36
37 fprintf(out_file, "%s\n", keystr);
38 }
39
40 static int usage(const char *progname)
41 {
42 fprintf(stderr, "Usage: %s [command|options] [<file>]\n"
43 "Commands:\n"
44 " -S Sign file\n"
45 " -V Verify file\n"
46 " -P Get pulic signing key from secret key\n"
47 " -H Get pulic host key from secret key\n"
48 " -G Generate new private key\n"
49 "\n"
50 "Options:\n"
51 " -o <file>: Set output file to <file> (defaults to stdout)\n"
52 " -k <keyfile>|-: Set public key from file or stdin\n"
53 " -K <keyfile>|-: Set secret key from file or stdin\n"
54 "\n", progname);
55 return 1;
56 }
57
58 static int cmd_sign(int argc, char **argv)
59 {
60 struct unet_auth_hdr hdr = {
61 .magic = cpu_to_be32(UNET_AUTH_MAGIC),
62 };
63 struct unet_auth_data *data;
64 struct timeval tv;
65 struct stat st;
66 off_t len;
67 FILE *f;
68
69 if (argc != 1) {
70 fprintf(stderr, "Missing filename\n");
71 return 1;
72 }
73
74 if (gettimeofday(&tv, NULL)) {
75 perror("gettimeofday");
76 return 1;
77 }
78
79 if (stat(argv[0], &st) ||
80 (f = fopen(argv[0], "r")) == NULL) {
81 fprintf(stderr, "Input file not found\n");
82 return 1;
83 }
84
85 data = calloc(1, sizeof(*data) + st.st_size + 1);
86 data->timestamp = cpu_to_be64(tv.tv_sec);
87 len = fread(data + 1, 1, st.st_size, f);
88 fclose(f);
89
90 if (len != st.st_size) {
91 fprintf(stderr, "Error reading from input file\n");
92 return 1;
93 }
94
95 len += sizeof(*data);
96
97 memcpy(data->pubkey, pubkey, sizeof(pubkey));
98 edsign_sign(hdr.signature, pubkey, seckey, (const void *)data, len);
99
100 fwrite(&hdr, sizeof(hdr), 1, out_file);
101 fwrite(data, len, 1, out_file);
102
103 free(data);
104
105 return 0;
106 }
107
108 static int cmd_verify(int argc, char **argv)
109 {
110 struct unet_auth_data *data;
111 struct unet_auth_hdr *hdr;
112 struct stat st;
113 off_t len;
114 FILE *f;
115 int ret = 1;
116
117 if (argc != 1) {
118 fprintf(stderr, "Missing filename\n");
119 return 1;
120 }
121
122 if (stat(argv[0], &st) ||
123 (f = fopen(argv[0], "r")) == NULL) {
124 fprintf(stderr, "Input file not found\n");
125 return 1;
126 }
127
128 if (st.st_size <= sizeof(*hdr) + sizeof(*data)) {
129 fprintf(stderr, "Input file too small\n");
130 fclose(f);
131 return 1;
132 }
133
134 hdr = calloc(1, st.st_size);
135 len = fread(hdr, 1, st.st_size, f);
136 fclose(f);
137
138 if (len != st.st_size) {
139 fprintf(stderr, "Error reading from input file\n");
140 return 1;
141 }
142
143 ret = unet_auth_data_validate(pubkey, hdr, len, NULL);
144 switch (ret) {
145 case -1:
146 fprintf(stderr, "Invalid input data\n");
147 break;
148 case -2:
149 fprintf(stderr, "Public key does not match\n");
150 break;
151 case -3:
152 fprintf(stderr, "Signature verification failed\n");
153 break;
154 }
155
156 free(hdr);
157 return ret;
158 }
159
160 static int cmd_host_pubkey(int argc, char **argv)
161 {
162 curve25519_generate_public(pubkey, seckey);
163 print_key(pubkey);
164
165 return 0;
166 }
167
168 static int cmd_pubkey(int argc, char **argv)
169 {
170 print_key(pubkey);
171
172 return 0;
173 }
174
175 static int cmd_generate(int argc, char **argv)
176 {
177 FILE *f;
178 int ret;
179
180 f = fopen("/dev/urandom", "r");
181 if (!f) {
182 fprintf(stderr, "Can't open /dev/urandom\n");
183 return 1;
184 }
185
186 ret = fread(seckey, sizeof(seckey), 1, f);
187 fclose(f);
188
189 if (ret != 1) {
190 fprintf(stderr, "Can't read data from /dev/urandom\n");
191 return 1;
192 }
193
194 ed25519_prepare(seckey);
195 print_key(seckey);
196
197 return 0;
198 }
199
200 static bool parse_key(uint8_t *dest, const char *str)
201 {
202 char keystr[B64_ENCODE_LEN(EDSIGN_PUBLIC_KEY_SIZE) + 2];
203 FILE *f;
204 int len;
205
206 if (!strcmp(str, "-"))
207 f = stdin;
208 else
209 f = fopen(str, "r");
210
211 if (!f) {
212 fprintf(stderr, "Can't open key file for reading\n");
213 return false;
214 }
215
216 len = fread(keystr, 1, sizeof(keystr) - 1, f);
217 if (f != stdin)
218 fclose(f);
219
220 keystr[len] = 0;
221
222 if (b64_decode(keystr, dest, EDSIGN_PUBLIC_KEY_SIZE) != EDSIGN_PUBLIC_KEY_SIZE) {
223 fprintf(stderr, "Failed to parse key data\n");
224 return false;
225 }
226
227 return true;
228 }
229
230 static bool cmd_needs_pubkey(void)
231 {
232 switch (cmd) {
233 case CMD_VERIFY:
234 return true;
235 default:
236 return false;
237 }
238 }
239
240 static bool cmd_needs_key(void)
241 {
242 switch (cmd) {
243 case CMD_SIGN:
244 case CMD_PUBKEY:
245 case CMD_HOST_PUBKEY:
246 return true;
247 default:
248 return false;
249 }
250 }
251
252 int main(int argc, char **argv)
253 {
254 const char *progname = argv[0];
255 const char *out_filename = NULL;
256 bool has_key = false, has_pubkey = false;
257 int ret, ch;
258
259 while ((ch = getopt(argc, argv, "o:k:K:GHPSV")) != -1) {
260 switch (ch) {
261 case 'o':
262 out_filename = optarg;
263 break;
264 case 'k':
265 if (has_pubkey)
266 return usage(progname);
267
268 if (!parse_key(pubkey, optarg)) {
269 return 1;
270 }
271
272 has_pubkey = true;
273 break;
274 case 'K':
275 if (has_pubkey)
276 return usage(progname);
277
278 if (!parse_key(seckey, optarg)) {
279 return 1;
280 }
281
282 has_key = true;
283
284 edsign_sec_to_pub(pubkey, seckey);
285 has_pubkey = true;
286 break;
287 case 'G':
288 if (cmd != CMD_UNKNOWN)
289 return usage(progname);
290
291 cmd = CMD_GENERATE;
292 break;
293 case 'S':
294 if (cmd != CMD_UNKNOWN)
295 return usage(progname);
296
297 cmd = CMD_SIGN;
298 break;
299 case 'P':
300 if (cmd != CMD_UNKNOWN)
301 return usage(progname);
302
303 cmd = CMD_PUBKEY;
304 break;
305 case 'H':
306 if (cmd != CMD_UNKNOWN)
307 return usage(progname);
308
309 cmd = CMD_HOST_PUBKEY;
310 break;
311 case 'V':
312 if (cmd != CMD_UNKNOWN)
313 return usage(progname);
314
315 cmd = CMD_VERIFY;
316 break;
317 default:
318 return usage(progname);
319 }
320 }
321
322 if (!has_key && cmd_needs_key()) {
323 fprintf(stderr, "Missing -K <key> argument\n");
324 return 1;
325 }
326
327 if (!has_pubkey && cmd_needs_pubkey()) {
328 fprintf(stderr, "Missing -k <key> argument\n");
329 return 1;
330 }
331
332 argc -= optind;
333 argv += optind;
334
335 if (out_filename) {
336 out_file = fopen(out_filename, "w");
337 if (!out_file) {
338 fprintf(stderr, "Failed to open output file\n");
339 return 1;
340 }
341 } else {
342 out_file = stdout;
343 }
344
345 ret = -1;
346 switch (cmd) {
347 case CMD_GENERATE:
348 ret = cmd_generate(argc, argv);
349 break;
350 case CMD_SIGN:
351 ret = cmd_sign(argc, argv);
352 break;
353 case CMD_PUBKEY:
354 ret = cmd_pubkey(argc, argv);
355 break;
356 case CMD_HOST_PUBKEY:
357 ret = cmd_host_pubkey(argc, argv);
358 break;
359 case CMD_VERIFY:
360 ret = cmd_verify(argc, argv);
361 break;
362 case CMD_UNKNOWN:
363 ret = usage(progname);
364 break;
365 }
366
367 if (out_file != stdout) {
368 fclose(out_file);
369 if (ret)
370 unlink(out_filename);
371 }
372
373 return ret;
374 }