Always pad fingerprints to 16 characters
[project/usign.git] / main.c
1 /*
2 * usign - tiny signify replacement
3 *
4 * Copyright (C) 2015 Felix Fietkau <nbd@openwrt.org>
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #include <sys/mman.h>
19 #include <sys/stat.h>
20 #include <stdio.h>
21 #include <stdbool.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <getopt.h>
25 #include <stdint.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <inttypes.h>
29
30 #include "base64.h"
31 #include "edsign.h"
32 #include "ed25519.h"
33
34 struct pubkey {
35 char pkalg[2];
36 uint8_t fingerprint[8];
37 uint8_t pubkey[EDSIGN_PUBLIC_KEY_SIZE];
38 };
39
40 struct seckey {
41 char pkalg[2];
42 char kdfalg[2];
43 uint32_t kdfrounds;
44 uint8_t salt[16];
45 uint8_t checksum[8];
46 uint8_t fingerprint[8];
47 uint8_t seckey[64];
48 };
49
50 struct sig {
51 char pkalg[2];
52 uint8_t fingerprint[8];
53 uint8_t sig[EDSIGN_SIGNATURE_SIZE];
54 };
55
56 static const char *pubkeyfile;
57 static const char *pubkeydir;
58 static const char *sigfile;
59 static const char *seckeyfile;
60 static const char *comment;
61 static bool quiet;
62 static enum {
63 CMD_NONE,
64 CMD_VERIFY,
65 CMD_SIGN,
66 CMD_FINGERPRINT,
67 CMD_GENERATE,
68 } cmd = CMD_NONE;
69
70 static uint64_t fingerprint_u64(const uint8_t *data)
71 {
72 uint64_t val = 0;
73
74 #define ADD(_v) val = (val << 8) | _v
75 ADD(data[0]);
76 ADD(data[1]);
77 ADD(data[2]);
78 ADD(data[3]);
79 ADD(data[4]);
80 ADD(data[5]);
81 ADD(data[6]);
82 ADD(data[7]);
83 #undef ADD
84
85 return val;
86 }
87
88 static void
89 file_error(const char *filename, bool _read)
90 {
91 if (!quiet || cmd != CMD_VERIFY)
92 fprintf(stderr, "Cannot open file '%s' for %s\n", filename,
93 _read ? "reading" : "writing");
94 exit(1);
95 }
96
97 static FILE *
98 open_file(const char *filename, bool _read)
99 {
100 FILE *f;
101
102 if (!strcmp(filename, "-"))
103 return _read ? stdin : stdout;
104
105 f = fopen(filename, _read ? "r" : "w");
106 if (!f)
107 file_error(filename, _read);
108
109 return f;
110 }
111
112 static void
113 get_file(const char *filename, char *buf, int buflen)
114 {
115 FILE *f = open_file(filename, true);
116 int len;
117
118 while (1) {
119 char *cur = fgets(buf, buflen, f);
120
121 if (!cur) {
122 fprintf(stderr, "Premature end of file\n");
123 exit(1);
124 }
125
126 if (strchr(buf, '\n'))
127 break;
128 }
129
130 len = fread(buf, 1, buflen - 1, f);
131 buf[len] = 0;
132 fclose(f);
133 }
134
135 static bool
136 get_base64_file(const char *file, void *dest, int size, void *buf, int buflen)
137 {
138 get_file(file, buf, buflen - 1);
139 return b64_decode(buf, dest, size) == size;
140 }
141
142 static void write_file(const char *name, const uint8_t *fingerprint,
143 const char *prefix, char *buf)
144 {
145 FILE *f;
146
147 f = open_file(name, false);
148 fputs("untrusted comment: ", f);
149 if (comment)
150 fputs(comment, f);
151 else
152 fprintf(f, "%s %016"PRIx64, prefix,
153 fingerprint_u64(fingerprint));
154 fprintf(f, "\n%s\n", buf);
155 fclose(f);
156 }
157
158 static int verify(const char *msgfile)
159 {
160 struct pubkey pkey;
161 struct sig sig;
162 struct edsign_verify_state vst;
163 FILE *f;
164 char buf[512];
165
166 f = open_file(msgfile, true);
167 if (!f) {
168 fprintf(stderr, "Cannot open message file\n");
169 return 1;
170 }
171
172 if (!get_base64_file(sigfile, &sig, sizeof(sig), buf, sizeof(buf)) ||
173 memcmp(sig.pkalg, "Ed", 2) != 0) {
174 fprintf(stderr, "Failed to decode signature\n");
175 fclose(f);
176 return 1;
177 }
178
179 if (!pubkeyfile) {
180 snprintf(buf, sizeof(buf), "%s/%016"PRIx64, pubkeydir,
181 fingerprint_u64(sig.fingerprint));
182 pubkeyfile = buf;
183 }
184
185 if (!get_base64_file(pubkeyfile, &pkey, sizeof(pkey), buf, sizeof(buf)) ||
186 memcmp(pkey.pkalg, "Ed", 2) != 0) {
187 fprintf(stderr, "Failed to decode public key\n");
188 fclose(f);
189 return 1;
190 }
191
192 edsign_verify_init(&vst, sig.sig, pkey.pubkey);
193
194 while (!feof(f)) {
195 int len = fread(buf, 1, sizeof(buf), f);
196 edsign_verify_add(&vst, buf, len);
197 }
198 fclose(f);
199
200 if (!edsign_verify(&vst, sig.sig, pkey.pubkey)) {
201 if (!quiet)
202 fprintf(stderr, "verification failed\n");
203 return 1;
204 }
205
206 if (!quiet)
207 fprintf(stderr, "OK\n");
208 return 0;
209 }
210
211 static int sign(const char *msgfile)
212 {
213 struct seckey skey;
214 struct sig sig = {
215 .pkalg = "Ed",
216 };
217 struct stat st;
218 char buf[512];
219 void *pubkey = buf;
220 long mlen;
221 void *m;
222 int mfd;
223
224 if (!get_base64_file(seckeyfile, &skey, sizeof(skey), buf, sizeof(buf)) ||
225 memcmp(skey.pkalg, "Ed", 2) != 0) {
226 fprintf(stderr, "Failed to decode secret key\n");
227 return 1;
228 }
229
230 if (skey.kdfrounds) {
231 fprintf(stderr, "Password protected secret keys are not supported\n");
232 return 1;
233 }
234
235 mfd = open(msgfile, O_RDONLY, 0);
236 if (mfd < 0 || fstat(mfd, &st) < 0 ||
237 (m = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, mfd, 0)) == MAP_FAILED) {
238 if (mfd >= 0)
239 close(mfd);
240 perror("Cannot open message file");
241 return 1;
242 }
243 mlen = st.st_size;
244
245 memcpy(sig.fingerprint, skey.fingerprint, sizeof(sig.fingerprint));
246 edsign_sec_to_pub(pubkey, skey.seckey);
247 edsign_sign(sig.sig, pubkey, skey.seckey, m, mlen);
248 munmap(m, mlen);
249 close(mfd);
250
251 if (b64_encode(&sig, sizeof(sig), buf, sizeof(buf)) < 0)
252 return 1;
253
254 write_file(sigfile, sig.fingerprint, "signed by key", buf);
255
256 return 0;
257 }
258
259 static int fingerprint(void)
260 {
261 struct seckey skey;
262 struct pubkey pkey;
263 struct sig sig;
264 char buf[512];
265 uint8_t *fp;
266
267 if (seckeyfile &&
268 get_base64_file(seckeyfile, &skey, sizeof(skey), buf, sizeof(buf)))
269 fp = skey.fingerprint;
270 else if (pubkeyfile &&
271 get_base64_file(pubkeyfile, &pkey, sizeof(pkey), buf, sizeof(buf)))
272 fp = pkey.fingerprint;
273 else if (sigfile &&
274 get_base64_file(sigfile, &sig, sizeof(sig), buf, sizeof(buf)))
275 fp = sig.fingerprint;
276 else
277 return 1;
278
279 fprintf(stdout, "%016"PRIx64"\n", fingerprint_u64(fp));
280 return 0;
281 }
282
283 static int generate(void)
284 {
285 struct seckey skey = {
286 .pkalg = "Ed",
287 .kdfalg = "BK",
288 .kdfrounds = 0,
289 };
290 struct pubkey pkey = {
291 .pkalg = "Ed",
292 };
293 struct sha512_state s;
294 char buf[512];
295 FILE *f;
296
297 f = fopen("/dev/urandom", "r");
298 if (!f) {
299 fprintf(stderr, "Can't open /dev/urandom\n");
300 return 1;
301 }
302
303 if (fread(skey.fingerprint, sizeof(skey.fingerprint), 1, f) != 1 ||
304 fread(skey.seckey, EDSIGN_SECRET_KEY_SIZE, 1, f) != 1 ||
305 fread(skey.salt, sizeof(skey.salt), 1, f) != 1) {
306 fprintf(stderr, "Can't read data from /dev/urandom\n");
307 fclose(f);
308 return 1;
309 }
310 if (f)
311 fclose(f);
312
313 ed25519_prepare(skey.seckey);
314 edsign_sec_to_pub(skey.seckey + 32, skey.seckey);
315
316 sha512_init(&s);
317 sha512_add(&s, skey.seckey, sizeof(skey.seckey));
318 memcpy(skey.checksum, sha512_final_get(&s), sizeof(skey.checksum));
319
320 if (b64_encode(&skey, sizeof(skey), buf, sizeof(buf)) < 0)
321 return 1;
322
323 write_file(seckeyfile, skey.fingerprint, "private key", buf);
324
325 memcpy(pkey.fingerprint, skey.fingerprint, sizeof(pkey.fingerprint));
326 memcpy(pkey.pubkey, skey.seckey + 32, sizeof(pkey.pubkey));
327
328 if (b64_encode(&pkey, sizeof(pkey), buf, sizeof(buf)) < 0)
329 return 1;
330
331 write_file(pubkeyfile, pkey.fingerprint, "public key", buf);
332
333 return 0;
334 }
335
336 static int usage(const char *cmd)
337 {
338 fprintf(stderr,
339 "Usage: %s <command> <options>\n"
340 "Commands:\n"
341 " -V: verify (needs at least -m and -p|-P)\n"
342 " -S: sign (needs at least -m and -s)\n"
343 " -F: print key fingerprint of public/secret key or signature\n"
344 " -G: generate a new keypair (needs at least -p and -s)\n"
345 "Options:\n"
346 " -c <comment>: add comment to keys\n"
347 " -m <file>: message file\n"
348 " -p <file>: public key file (verify/fingerprint only)\n"
349 " -P <path>: public key directory (verify only)\n"
350 " -q: quiet (do not print verification result, use return code only)\n"
351 " -s <file>: secret key file (sign/fingerprint only)\n"
352 " -x <file>: signature file (defaults to <message file>.sig)\n"
353 "\n",
354 cmd);
355 return 1;
356 }
357
358 static void set_cmd(const char *prog, int val)
359 {
360 if (cmd != CMD_NONE)
361 exit(usage(prog));
362
363 cmd = val;
364 }
365
366 int main(int argc, char **argv)
367 {
368 const char *msgfile = NULL;
369 int ch;
370
371 while ((ch = getopt(argc, argv, "FGSVc:m:P:p:qs:x:")) != -1) {
372 switch (ch) {
373 case 'V':
374 set_cmd(argv[0], CMD_VERIFY);
375 break;
376 case 'S':
377 set_cmd(argv[0], CMD_SIGN);
378 break;
379 case 'F':
380 set_cmd(argv[0], CMD_FINGERPRINT);
381 break;
382 case 'G':
383 set_cmd(argv[0], CMD_GENERATE);
384 break;
385 case 'c':
386 comment = optarg;
387 break;
388 case 'm':
389 msgfile = optarg;
390 break;
391 case 'P':
392 pubkeydir = optarg;
393 break;
394 case 'p':
395 pubkeyfile = optarg;
396 break;
397 case 's':
398 seckeyfile = optarg;
399 break;
400 case 'x':
401 sigfile = optarg;
402 break;
403 case 'q':
404 quiet = true;
405 break;
406 default:
407 return usage(argv[0]);
408 }
409 }
410
411 if (!sigfile && msgfile) {
412 char *buf = alloca(strlen(msgfile) + 5);
413
414 if (!strcmp(msgfile, "-")) {
415 fprintf(stderr, "Need signature file when reading message from stdin\n");
416 return 1;
417 }
418
419 sprintf(buf, "%s.sig", msgfile);
420 sigfile = buf;
421 }
422
423 switch (cmd) {
424 case CMD_VERIFY:
425 if ((!pubkeyfile && !pubkeydir) || !msgfile)
426 return usage(argv[0]);
427 return verify(msgfile);
428 case CMD_SIGN:
429 if (!seckeyfile || !msgfile || !sigfile)
430 return usage(argv[0]);
431 return sign(msgfile);
432 case CMD_FINGERPRINT:
433 if (!!seckeyfile + !!pubkeyfile + !!sigfile != 1) {
434 fprintf(stderr, "Need one secret/public key or signature\n");
435 return usage(argv[0]);
436 }
437 return fingerprint();
438 case CMD_GENERATE:
439 if (!seckeyfile || !pubkeyfile)
440 return usage(argv[0]);
441 return generate();
442 default:
443 return usage(argv[0]);
444 }
445 }