README: replace unicode character
[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 }
133
134 static bool
135 get_base64_file(const char *file, void *dest, int size, void *buf, int buflen)
136 {
137         get_file(file, buf, buflen - 1);
138         return b64_decode(buf, dest, size) == size;
139 }
140
141 static void write_file(const char *name, const uint8_t *fingerprint,
142                        const char *prefix, char *buf)
143 {
144         FILE *f;
145
146         f = open_file(name, false);
147         fputs("untrusted comment: ", f);
148         if (comment)
149                 fputs(comment, f);
150         else
151                 fprintf(f, "%s %"PRIx64, prefix,
152                         fingerprint_u64(fingerprint));
153         fprintf(f, "\n%s\n", buf);
154         fclose(f);
155 }
156
157 static int verify(const char *msgfile)
158 {
159         struct pubkey pkey;
160         struct sig sig;
161         struct edsign_verify_state vst;
162         FILE *f;
163         char buf[512];
164
165         f = open_file(msgfile, true);
166         if (!f) {
167                 fprintf(stderr, "Cannot open message file\n");
168                 return 1;
169         }
170
171         if (!get_base64_file(sigfile, &sig, sizeof(sig), buf, sizeof(buf)) ||
172             memcmp(sig.pkalg, "Ed", 2) != 0) {
173                 fprintf(stderr, "Failed to decode signature\n");
174                 return 1;
175         }
176
177         if (!pubkeyfile) {
178                 snprintf(buf, sizeof(buf), "%s/%"PRIx64, pubkeydir,
179                          fingerprint_u64(sig.fingerprint));
180                 pubkeyfile = buf;
181         }
182
183         if (!get_base64_file(pubkeyfile, &pkey, sizeof(pkey), buf, sizeof(buf)) ||
184             memcmp(pkey.pkalg, "Ed", 2) != 0) {
185                 fprintf(stderr, "Failed to decode public key\n");
186                 return 1;
187         }
188
189         edsign_verify_init(&vst, sig.sig, pkey.pubkey);
190
191         while (!feof(f)) {
192                 int len = fread(buf, 1, sizeof(buf), f);
193                 edsign_verify_add(&vst, buf, len);
194         }
195         fclose(f);
196
197         if (!edsign_verify(&vst, sig.sig, pkey.pubkey)) {
198                 if (!quiet)
199                         fprintf(stderr, "verification failed\n");
200                 return 1;
201         }
202
203         if (!quiet)
204                 fprintf(stderr, "OK\n");
205         return 0;
206 }
207
208 static int sign(const char *msgfile)
209 {
210         struct seckey skey;
211         struct sig sig = {
212                 .pkalg = "Ed",
213         };
214         struct stat st;
215         char buf[512];
216         void *pubkey = buf;
217         long mlen;
218         void *m;
219         int mfd;
220
221         if (!get_base64_file(seckeyfile, &skey, sizeof(skey), buf, sizeof(buf)) ||
222             memcmp(skey.pkalg, "Ed", 2) != 0) {
223                 fprintf(stderr, "Failed to decode secret key\n");
224                 return 1;
225         }
226
227         if (skey.kdfrounds) {
228                 fprintf(stderr, "Password protected secret keys are not supported\n");
229                 return 1;
230         }
231
232         mfd = open(msgfile, O_RDONLY, 0);
233         if (mfd < 0 || fstat(mfd, &st) < 0 ||
234                 (m = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, mfd, 0)) == MAP_FAILED) {
235                 if (mfd >= 0)
236                         close(mfd);
237                 perror("Cannot open message file");
238                 return 1;
239         }
240         mlen = st.st_size;
241
242         memcpy(sig.fingerprint, skey.fingerprint, sizeof(sig.fingerprint));
243         edsign_sec_to_pub(pubkey, skey.seckey);
244         edsign_sign(sig.sig, pubkey, skey.seckey, m, mlen);
245         munmap(m, mlen);
246         close(mfd);
247
248         if (b64_encode(&sig, sizeof(sig), buf, sizeof(buf)) < 0)
249                 return 1;
250
251         write_file(sigfile, sig.fingerprint, "signed by key", buf);
252
253         return 0;
254 }
255
256 static int fingerprint(void)
257 {
258         struct seckey skey;
259         struct pubkey pkey;
260         struct sig sig;
261         char buf[512];
262         uint8_t *fp;
263
264         if (seckeyfile &&
265             get_base64_file(seckeyfile, &skey, sizeof(skey), buf, sizeof(buf)))
266                 fp = skey.fingerprint;
267         else if (pubkeyfile &&
268                  get_base64_file(pubkeyfile, &pkey, sizeof(pkey), buf, sizeof(buf)))
269                 fp = pkey.fingerprint;
270         else if (sigfile &&
271                  get_base64_file(sigfile, &sig, sizeof(sig), buf, sizeof(buf)))
272                 fp = sig.fingerprint;
273         else
274                 return 1;
275
276         fprintf(stdout, "%"PRIx64"\n", fingerprint_u64(fp));
277         return 0;
278 }
279
280 static int generate(void)
281 {
282         struct seckey skey = {
283                 .pkalg = "Ed",
284                 .kdfalg = "BK",
285                 .kdfrounds = 0,
286         };
287         struct pubkey pkey = {
288                 .pkalg = "Ed",
289         };
290         struct sha512_state s;
291         char buf[512];
292         FILE *f;
293
294         f = fopen("/dev/urandom", "r");
295         if (!f ||
296             fread(skey.fingerprint, sizeof(skey.fingerprint), 1, f) != 1 ||
297             fread(skey.seckey, EDSIGN_SECRET_KEY_SIZE, 1, f) != 1 ||
298             fread(skey.salt, sizeof(skey.salt), 1, f) != 1) {
299                 fprintf(stderr, "Can't read data from /dev/urandom\n");
300                 return 1;
301         }
302         if (f)
303                 fclose(f);
304
305         ed25519_prepare(skey.seckey);
306         edsign_sec_to_pub(skey.seckey + 32, skey.seckey);
307
308         sha512_init(&s);
309         sha512_add(&s, skey.seckey, sizeof(skey.seckey));
310         memcpy(skey.checksum, sha512_final_get(&s), sizeof(skey.checksum));
311
312         if (b64_encode(&skey, sizeof(skey), buf, sizeof(buf)) < 0)
313                 return 1;
314
315         write_file(seckeyfile, skey.fingerprint, "private key", buf);
316
317         memcpy(pkey.fingerprint, skey.fingerprint, sizeof(pkey.fingerprint));
318         memcpy(pkey.pubkey, skey.seckey + 32, sizeof(pkey.pubkey));
319
320         if (b64_encode(&pkey, sizeof(pkey), buf, sizeof(buf)) < 0)
321                 return 1;
322
323         write_file(pubkeyfile, pkey.fingerprint, "public key", buf);
324
325         return 0;
326 }
327
328 static int usage(const char *cmd)
329 {
330         fprintf(stderr,
331                 "Usage: %s <command> <options>\n"
332                 "Commands:\n"
333                 "  -V:                  verify (needs at least -m and -p|-P)\n"
334                 "  -S:                  sign (needs at least -m and -s)\n"
335                 "  -F:                  print key fingerprint of public/secret key or signature\n"
336                 "  -G:                  generate a new keypair (needs at least -p and -s)\n"
337                 "Options:\n"
338                 "  -c <comment>:        add comment to keys\n"
339                 "  -m <file>:           message file\n"
340                 "  -p <file>:           public key file (verify/fingerprint only)\n"
341                 "  -P <path>:           public key directory (verify only)\n"
342                 "  -q:                  quiet (do not print verification result, use return code only)\n"
343                 "  -s <file>:           secret key file (sign/fingerprint only)\n"
344                 "  -x <file>:           signature file (defaults to <message file>.sig)\n"
345                 "\n",
346                 cmd);
347         return 1;
348 }
349
350 static void set_cmd(const char *prog, int val)
351 {
352         if (cmd != CMD_NONE)
353                 exit(usage(prog));
354
355         cmd = val;
356 }
357
358 int main(int argc, char **argv)
359 {
360         const char *msgfile = NULL;
361         int ch;
362
363         while ((ch = getopt(argc, argv, "FGSVc:m:P:p:qs:x:")) != -1) {
364                 switch (ch) {
365                 case 'V':
366                         set_cmd(argv[0], CMD_VERIFY);
367                         break;
368                 case 'S':
369                         set_cmd(argv[0], CMD_SIGN);
370                         break;
371                 case 'F':
372                         set_cmd(argv[0], CMD_FINGERPRINT);
373                         break;
374                 case 'G':
375                         set_cmd(argv[0], CMD_GENERATE);
376                         break;
377                 case 'c':
378                         comment = optarg;
379                         break;
380                 case 'm':
381                         msgfile = optarg;
382                         break;
383                 case 'P':
384                         pubkeydir = optarg;
385                         break;
386                 case 'p':
387                         pubkeyfile = optarg;
388                         break;
389                 case 's':
390                         seckeyfile = optarg;
391                         break;
392                 case 'x':
393                         sigfile = optarg;
394                         break;
395                 case 'q':
396                         quiet = true;
397                         break;
398                 default:
399                         return usage(argv[0]);
400                 }
401         }
402
403         if (!sigfile && msgfile) {
404                 char *buf = alloca(strlen(msgfile) + 5);
405
406                 if (!strcmp(msgfile, "-")) {
407                         fprintf(stderr, "Need signature file when reading message from stdin\n");
408                         return 1;
409                 }
410
411                 sprintf(buf, "%s.sig", msgfile);
412                 sigfile = buf;
413         }
414
415         switch (cmd) {
416         case CMD_VERIFY:
417                 if ((!pubkeyfile && !pubkeydir) || !msgfile)
418                         return usage(argv[0]);
419                 return verify(msgfile);
420         case CMD_SIGN:
421                 if (!seckeyfile || !msgfile || !sigfile)
422                         return usage(argv[0]);
423                 return sign(msgfile);
424         case CMD_FINGERPRINT:
425                 if (!!seckeyfile + !!pubkeyfile + !!sigfile != 1) {
426                         fprintf(stderr, "Need one secret/public key or signature\n");
427                         return usage(argv[0]);
428                 }
429                 return fingerprint();
430         case CMD_GENERATE:
431                 if (!seckeyfile || !pubkeyfile)
432                         return usage(argv[0]);
433                 return generate();
434         default:
435                 return usage(argv[0]);
436         }
437 }