Initial import
[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 bool quiet;
61 static enum {
62         CMD_NONE,
63         CMD_VERIFY,
64         CMD_SIGN,
65         CMD_FINGERPRINT,
66         CMD_GENERATE,
67 } cmd = CMD_NONE;
68
69 static uint64_t fingerprint_u64(uint8_t *data)
70 {
71         uint64_t val = 0;
72
73 #define ADD(_v) val = (val << 8) | _v
74         ADD(data[0]);
75         ADD(data[1]);
76         ADD(data[2]);
77         ADD(data[3]);
78         ADD(data[4]);
79         ADD(data[5]);
80         ADD(data[6]);
81         ADD(data[7]);
82 #undef ADD
83
84         return val;
85 }
86
87 static void
88 file_error(const char *filename, bool _read)
89 {
90         if (!quiet || cmd != CMD_VERIFY)
91                 fprintf(stderr, "Cannot open file '%s' for %s\n", filename,
92                         _read ? "reading" : "writing");
93         exit(1);
94 }
95
96 static FILE *
97 open_file(const char *filename, bool _read)
98 {
99         FILE *f;
100
101         if (!strcmp(filename, "-"))
102                 return _read ? stdin : stdout;
103
104         f = fopen(filename, _read ? "r" : "w");
105         if (!f)
106                 file_error(filename, _read);
107
108         return f;
109 }
110
111 static void
112 get_file(const char *filename, char *buf, int buflen)
113 {
114         FILE *f = open_file(filename, true);
115         int len;
116
117         while (1) {
118                 char *cur = fgets(buf, buflen, f);
119
120                 if (!cur) {
121                         fprintf(stderr, "Premature end of file\n");
122                         exit(1);
123                 }
124
125                 if (strchr(buf, '\n'))
126                         break;
127         }
128
129         len = fread(buf, 1, buflen - 1, f);
130         buf[len] = 0;
131 }
132
133 static bool
134 get_base64_file(const char *file, void *dest, int size, void *buf, int buflen)
135 {
136         get_file(file, buf, buflen - 1);
137         return b64_pton(buf, dest, size) == size;
138 }
139
140 static int verify(const char *msgfile)
141 {
142         struct pubkey pkey;
143         struct sig sig;
144         struct edsign_verify_state vst;
145         FILE *f;
146         char buf[512];
147
148         f = open_file(msgfile, true);
149         if (!f) {
150                 fprintf(stderr, "Cannot open message file\n");
151                 return 1;
152         }
153
154         if (!get_base64_file(sigfile, &sig, sizeof(sig), buf, sizeof(buf)) ||
155             memcmp(sig.pkalg, "Ed", 2) != 0) {
156                 fprintf(stderr, "Failed to decode signature\n");
157                 return 1;
158         }
159
160         if (!pubkeyfile) {
161                 snprintf(buf, sizeof(buf), "%s/%"PRIx64, pubkeydir,
162                          fingerprint_u64(sig.fingerprint));
163                 pubkeyfile = buf;
164         }
165
166         if (!get_base64_file(pubkeyfile, &pkey, sizeof(pkey), buf, sizeof(buf)) ||
167             memcmp(pkey.pkalg, "Ed", 2) != 0) {
168                 fprintf(stderr, "Failed to decode public key\n");
169                 return 1;
170         }
171
172         edsign_verify_init(&vst, sig.sig, pkey.pubkey);
173
174         while (!feof(f)) {
175                 int len = fread(buf, 1, sizeof(buf), f);
176                 edsign_verify_add(&vst, buf, len);
177         }
178         fclose(f);
179
180         if (!edsign_verify(&vst, sig.sig, pkey.pubkey)) {
181                 if (!quiet)
182                         fprintf(stderr, "verification failed\n");
183                 return 1;
184         }
185
186         if (!quiet)
187                 fprintf(stderr, "OK\n");
188         return 0;
189 }
190
191 static int sign(const char *msgfile)
192 {
193         struct seckey skey;
194         struct sig sig = {
195                 .pkalg = "Ed",
196         };
197         struct stat st;
198         char buf[512];
199         void *pubkey = buf;
200         long mlen;
201         void *m;
202         int mfd;
203         FILE *out;
204
205         if (!get_base64_file(seckeyfile, &skey, sizeof(skey), buf, sizeof(buf)) ||
206             memcmp(skey.pkalg, "Ed", 2) != 0) {
207                 fprintf(stderr, "Failed to decode secret key\n");
208                 return 1;
209         }
210
211         if (skey.kdfrounds) {
212                 fprintf(stderr, "Password protected secret keys are not supported\n");
213                 return 1;
214         }
215
216         mfd = open(msgfile, O_RDONLY, 0);
217         if (mfd < 0 || fstat(mfd, &st) < 0 ||
218                 (m = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, mfd, 0)) == MAP_FAILED) {
219                 if (mfd >= 0)
220                         close(mfd);
221                 perror("Cannot open message file");
222                 return 1;
223         }
224         mlen = st.st_size;
225
226         memcpy(sig.fingerprint, skey.fingerprint, sizeof(sig.fingerprint));
227         edsign_sec_to_pub(pubkey, skey.seckey);
228         edsign_sign(sig.sig, pubkey, skey.seckey, m, mlen);
229         munmap(m, mlen);
230         close(mfd);
231
232         if (b64_ntop(&sig, sizeof(sig), buf, sizeof(buf)) < 0)
233                 return 1;
234
235         out = open_file(sigfile, false);
236         fprintf(out, "untrusted comment: signed by key %"PRIx64"\n%s\n", fingerprint_u64(sig.fingerprint), buf);
237         fclose(out);
238
239         return 0;
240 }
241
242 static int fingerprint(void)
243 {
244         struct seckey skey;
245         struct pubkey pkey;
246         struct sig sig;
247         char buf[512];
248         uint8_t *fp;
249
250         if (seckeyfile &&
251             get_base64_file(seckeyfile, &skey, sizeof(skey), buf, sizeof(buf)))
252                 fp = skey.fingerprint;
253         else if (pubkeyfile &&
254                  get_base64_file(pubkeyfile, &pkey, sizeof(pkey), buf, sizeof(buf)))
255                 fp = pkey.fingerprint;
256         else if (sigfile &&
257                  get_base64_file(sigfile, &sig, sizeof(sig), buf, sizeof(buf)))
258                 fp = sig.fingerprint;
259         else
260                 return 1;
261
262         fprintf(stdout, "%"PRIx64"\n", fingerprint_u64(fp));
263         return 0;
264 }
265
266 static int generate(void)
267 {
268         struct seckey skey = {
269                 .pkalg = "Ed",
270                 .kdfalg = "BK",
271                 .kdfrounds = 0,
272         };
273         struct pubkey pkey = {
274                 .pkalg = "Ed",
275         };
276         struct sha512_state s;
277         char buf[512];
278         FILE *f;
279
280         f = fopen("/dev/urandom", "r");
281         if (!f ||
282             fread(skey.fingerprint, sizeof(skey.fingerprint), 1, f) != 1 ||
283             fread(skey.seckey, EDSIGN_SECRET_KEY_SIZE, 1, f) != 1 ||
284             fread(skey.salt, sizeof(skey.salt), 1, f) != 1) {
285                 fprintf(stderr, "Can't read data from /dev/urandom\n");
286                 return 1;
287         }
288         if (f)
289                 fclose(f);
290
291         ed25519_prepare(skey.seckey);
292         edsign_sec_to_pub(skey.seckey + 32, skey.seckey);
293
294         sha512_init(&s);
295         sha512_add(&s, skey.seckey, sizeof(skey.seckey));
296         memcpy(skey.checksum, sha512_final_get(&s), sizeof(skey.checksum));
297
298         if (b64_ntop(&skey, sizeof(skey), buf, sizeof(buf)) < 0)
299                 return 1;
300
301         f = open_file(seckeyfile, false);
302         fprintf(f, "untrusted comment: secret key %"PRIx64"\n%s\n", fingerprint_u64(skey.fingerprint), buf);
303         fclose(f);
304
305         memcpy(pkey.fingerprint, skey.fingerprint, sizeof(pkey.fingerprint));
306         memcpy(pkey.pubkey, skey.seckey + 32, sizeof(pkey.pubkey));
307
308         if (b64_ntop(&pkey, sizeof(pkey), buf, sizeof(buf)) < 0)
309                 return 1;
310
311         f = open_file(pubkeyfile, false);
312         fprintf(f, "untrusted comment: public key %"PRIx64"\n%s\n", fingerprint_u64(pkey.fingerprint), buf);
313         fclose(f);
314
315         return 0;
316 }
317
318 static int usage(const char *cmd)
319 {
320         fprintf(stderr,
321                 "Usage: %s <command> <options>\n"
322                 "Commands:\n"
323                 "  -V:          verify (needs at least -m and -p|-P)\n"
324                 "  -S:          sign (needs at least -m and -s)\n"
325                 "  -F:          print key fingerprint of public/secret key or signature\n"
326                 "  -G:          generate a new keypair\n"
327                 "Options:\n"
328                 "  -m <file>:   message file\n"
329                 "  -p <file>:   public key file (verify/fingerprint only)\n"
330                 "  -P <path>:   public key directory (verify only)\n"
331                 "  -q:                  quiet (do not print verification result, use return code only)\n"
332                 "  -s <file>:   secret key file (sign/fingerprint only)\n"
333                 "  -x <file>:   signature file (defaults to <message file>.sig)\n"
334                 "\n",
335                 cmd);
336         return 1;
337 }
338
339 static void set_cmd(const char *prog, int val)
340 {
341         if (cmd != CMD_NONE)
342                 exit(usage(prog));
343
344         cmd = val;
345 }
346
347 int main(int argc, char **argv)
348 {
349         const char *msgfile = NULL;
350         int ch;
351
352         while ((ch = getopt(argc, argv, "FGSVm:P:p:qs:x:")) != -1) {
353                 switch (ch) {
354                 case 'V':
355                         set_cmd(argv[0], CMD_VERIFY);
356                         break;
357                 case 'S':
358                         set_cmd(argv[0], CMD_SIGN);
359                         break;
360                 case 'F':
361                         set_cmd(argv[0], CMD_FINGERPRINT);
362                         break;
363                 case 'G':
364                         set_cmd(argv[0], CMD_GENERATE);
365                         break;
366                 case 'm':
367                         msgfile = optarg;
368                         break;
369                 case 'P':
370                         pubkeydir = optarg;
371                         break;
372                 case 'p':
373                         pubkeyfile = optarg;
374                         break;
375                 case 's':
376                         seckeyfile = optarg;
377                         break;
378                 case 'x':
379                         sigfile = optarg;
380                         break;
381                 case 'q':
382                         quiet = true;
383                         break;
384                 default:
385                         return usage(argv[0]);
386                 }
387         }
388
389         if (!sigfile && msgfile) {
390                 char *buf = alloca(strlen(msgfile) + 5);
391
392                 if (!strcmp(msgfile, "-")) {
393                         fprintf(stderr, "Need signature file when reading message from stdin\n");
394                         return 1;
395                 }
396
397                 sprintf(buf, "%s.sig", msgfile);
398                 sigfile = buf;
399         }
400
401         switch (cmd) {
402         case CMD_VERIFY:
403                 if ((!pubkeyfile && !pubkeydir) || !msgfile)
404                         return usage(argv[0]);
405                 return verify(msgfile);
406         case CMD_SIGN:
407                 if (!seckeyfile || !msgfile || !sigfile)
408                         return usage(argv[0]);
409                 return sign(msgfile);
410         case CMD_FINGERPRINT:
411                 if (!!seckeyfile + !!pubkeyfile + !!sigfile != 1) {
412                         fprintf(stderr, "Need one secret/public key or signature\n");
413                         return usage(argv[0]);
414                 }
415                 return fingerprint();
416         case CMD_GENERATE:
417                 if (!seckeyfile || !pubkeyfile)
418                         return usage(argv[0]);
419                 return generate();
420         default:
421                 return usage(argv[0]);
422         }
423 }