Annotation of src/usr.bin/signify/signify.c, Revision 1.15
1.15 ! espie 1: /* $OpenBSD: signify.c,v 1.14 2014/01/08 05:00:01 tedu Exp $ */
1.1 tedu 2: /*
3: * Copyright (c) 2013 Ted Unangst <tedu@openbsd.org>
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: */
17: #include <sys/stat.h>
18:
19: #include <netinet/in.h>
20: #include <resolv.h>
21:
22: #include <stdint.h>
23: #include <fcntl.h>
24: #include <string.h>
25: #include <stdio.h>
26: #include <err.h>
27: #include <unistd.h>
28: #include <readpassphrase.h>
29: #include <util.h>
30: #include <sha2.h>
31:
32: #include "crypto_api.h"
33:
34: #define SIGBYTES crypto_sign_ed25519_BYTES
35: #define SECRETBYTES crypto_sign_ed25519_SECRETKEYBYTES
36: #define PUBLICBYTES crypto_sign_ed25519_PUBLICKEYBYTES
37:
38: #define PKALG "Ed"
39: #define KDFALG "BK"
1.13 tedu 40: #define FPLEN 8
41:
42: #define COMMENTHDR "untrusted comment:"
43: #define COMMENTHDRLEN 18
1.1 tedu 44:
45: struct enckey {
46: uint8_t pkalg[2];
47: uint8_t kdfalg[2];
48: uint32_t kdfrounds;
49: uint8_t salt[16];
50: uint8_t checksum[8];
1.13 tedu 51: uint8_t fingerprint[FPLEN];
1.1 tedu 52: uint8_t seckey[SECRETBYTES];
53: };
54:
55: struct pubkey {
56: uint8_t pkalg[2];
1.13 tedu 57: uint8_t fingerprint[FPLEN];
1.1 tedu 58: uint8_t pubkey[PUBLICBYTES];
59: };
60:
61: struct sig {
62: uint8_t pkalg[2];
1.13 tedu 63: uint8_t fingerprint[FPLEN];
1.1 tedu 64: uint8_t sig[SIGBYTES];
65: };
66:
67: extern char *__progname;
68:
69: static void
70: usage(void)
71: {
1.9 espie 72: fprintf(stderr, "usage:"
1.15 ! espie 73: #ifndef VERIFYONLY
1.9 espie 74: "\t%s [-n] -p pubkey -s seckey -G\n"
75: "\t%s [-o output] -s seckey -S input\n"
1.15 ! espie 76: #endif
1.9 espie 77: "\t%s [-o output] -p pubkey -V input\n",
1.15 ! espie 78: #ifndef VERIFYONLY
! 79: __progname, __progname,
! 80: #endif
! 81: __progname);
1.1 tedu 82: exit(1);
83: }
84:
85: static int
86: xopen(const char *fname, int flags, mode_t mode)
87: {
88: int fd;
89:
90: fd = open(fname, flags, mode);
91: if (fd == -1)
92: err(1, "open %s", fname);
93: return fd;
94: }
95:
96: static void *
97: xmalloc(size_t len)
98: {
99: void *p;
100:
101: p = malloc(len);
102: if (!p)
103: err(1, "malloc %zu", len);
104: return p;
105: }
106:
107: static void
1.7 espie 108: readall(int fd, void *buf, size_t len, const char *filename)
1.1 tedu 109: {
1.11 tedu 110: ssize_t x;
111:
112: x = read(fd, buf, len);
1.7 espie 113: if (x == -1) {
114: err(1, "read from %s", filename);
115: } else if (x != len) {
116: errx(1, "short read from %s", filename);
117: }
1.1 tedu 118: }
119:
120: static void
121: readb64file(const char *filename, void *buf, size_t len)
122: {
123: char b64[2048];
1.10 tedu 124: int rv, fd;
125: char *commentend;
1.1 tedu 126:
127: fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
128: memset(b64, 0, sizeof(b64));
129: rv = read(fd, b64, sizeof(b64) - 1);
130: if (rv == -1)
1.7 espie 131: err(1, "read from %s", filename);
1.10 tedu 132: commentend = strchr(b64, '\n');
1.13 tedu 133: if (!commentend || commentend - b64 <= COMMENTHDRLEN ||
134: memcmp(b64, COMMENTHDR, COMMENTHDRLEN))
135: errx(1, "invalid comment in %s; must start with '%s'",
136: filename, COMMENTHDR);
1.10 tedu 137: rv = b64_pton(commentend + 1, buf, len);
1.1 tedu 138: if (rv != len)
139: errx(1, "invalid b64 encoding in %s", filename);
140: memset(b64, 0, sizeof(b64));
141: close(fd);
142: if (memcmp(buf, PKALG, 2))
143: errx(1, "unsupported file %s", filename);
144: }
145:
146: uint8_t *
147: readmsg(const char *filename, unsigned long long *msglenp)
148: {
149: unsigned long long msglen;
150: uint8_t *msg;
151: struct stat sb;
152: int fd;
153:
154: fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
155: fstat(fd, &sb);
156: msglen = sb.st_size;
157: if (msglen > (1UL << 30))
158: errx(1, "msg too large in %s", filename);
159: msg = xmalloc(msglen);
1.7 espie 160: readall(fd, msg, msglen, filename);
1.1 tedu 161: close(fd);
162:
163: *msglenp = msglen;
164: return msg;
165: }
166:
1.14 tedu 167: #ifndef VERIFYONLY
1.1 tedu 168: static void
1.7 espie 169: writeall(int fd, const void *buf, size_t len, const char *filename)
1.1 tedu 170: {
1.11 tedu 171: ssize_t x;
172:
173: x = write(fd, buf, len);
1.7 espie 174: if (x == -1) {
175: err(1, "write to %s", filename);
176: } else if (x != len) {
177: errx(1, "short write to %s", filename);
178: }
1.1 tedu 179: }
180:
181: static void
182: writeb64file(const char *filename, const char *comment, const void *buf,
183: size_t len, mode_t mode)
184: {
185: char header[1024];
186: char b64[1024];
187: int fd, rv;
188:
189: fd = xopen(filename, O_CREAT|O_EXCL|O_NOFOLLOW|O_RDWR, mode);
1.13 tedu 190: snprintf(header, sizeof(header), "%s signify %s\n", COMMENTHDR,
191: comment);
1.7 espie 192: writeall(fd, header, strlen(header), filename);
1.8 espie 193: if ((rv = b64_ntop(buf, len, b64, sizeof(b64)-1)) == -1)
1.1 tedu 194: errx(1, "b64 encode failed");
1.8 espie 195: b64[rv++] = '\n';
1.7 espie 196: writeall(fd, b64, rv, filename);
1.1 tedu 197: memset(b64, 0, sizeof(b64));
198: close(fd);
199: }
200:
201: static void
202: kdf(uint8_t *salt, size_t saltlen, int rounds, uint8_t *key, size_t keylen)
203: {
204: char pass[1024];
205:
206: if (rounds == 0) {
207: memset(key, 0, keylen);
208: return;
209: }
210:
211: if (!readpassphrase("passphrase: ", pass, sizeof(pass), 0))
212: errx(1, "readpassphrase");
213: if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key,
214: keylen, rounds) == -1)
215: errx(1, "bcrypt pbkdf");
216: memset(pass, 0, sizeof(pass));
217: }
218:
219: static void
220: signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen,
221: uint8_t *sig)
222: {
223: unsigned long long siglen;
224: uint8_t *sigbuf;
225:
226: sigbuf = xmalloc(msglen + SIGBYTES);
227: crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey);
228: memcpy(sig, sigbuf, SIGBYTES);
229: free(sigbuf);
230: }
231:
232: static void
233: generate(const char *pubkeyfile, const char *seckeyfile, int rounds)
234: {
235: uint8_t digest[SHA512_DIGEST_LENGTH];
236: struct pubkey pubkey;
237: struct enckey enckey;
238: uint8_t xorkey[sizeof(enckey.seckey)];
1.13 tedu 239: uint8_t fingerprint[FPLEN];
1.1 tedu 240: SHA2_CTX ctx;
241: int i;
242:
243: crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey);
1.13 tedu 244: arc4random_buf(fingerprint, sizeof(fingerprint));
1.1 tedu 245:
246: SHA512Init(&ctx);
247: SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
248: SHA512Final(digest, &ctx);
249:
250: memcpy(enckey.pkalg, PKALG, 2);
251: memcpy(enckey.kdfalg, KDFALG, 2);
252: enckey.kdfrounds = htonl(rounds);
1.13 tedu 253: memcpy(enckey.fingerprint, fingerprint, FPLEN);
1.1 tedu 254: arc4random_buf(enckey.salt, sizeof(enckey.salt));
255: kdf(enckey.salt, sizeof(enckey.salt), rounds, xorkey, sizeof(xorkey));
256: memcpy(enckey.checksum, digest, sizeof(enckey.checksum));
257: for (i = 0; i < sizeof(enckey.seckey); i++)
258: enckey.seckey[i] ^= xorkey[i];
259: memset(digest, 0, sizeof(digest));
260: memset(xorkey, 0, sizeof(xorkey));
261:
262: writeb64file(seckeyfile, "secret key", &enckey,
263: sizeof(enckey), 0600);
264: memset(&enckey, 0, sizeof(enckey));
265:
266: memcpy(pubkey.pkalg, PKALG, 2);
1.13 tedu 267: memcpy(pubkey.fingerprint, fingerprint, FPLEN);
1.1 tedu 268: writeb64file(pubkeyfile, "public key", &pubkey,
269: sizeof(pubkey), 0666);
270: }
271:
272: static void
273: sign(const char *seckeyfile, const char *inputfile, const char *sigfile)
274: {
275: struct sig sig;
276: uint8_t digest[SHA512_DIGEST_LENGTH];
277: struct enckey enckey;
278: uint8_t xorkey[sizeof(enckey.seckey)];
279: uint8_t *msg;
280: unsigned long long msglen;
281: int i, rounds;
282: SHA2_CTX ctx;
283:
284: readb64file(seckeyfile, &enckey, sizeof(enckey));
285:
286: if (memcmp(enckey.kdfalg, KDFALG, 2))
287: errx(1, "unsupported KDF");
288: rounds = ntohl(enckey.kdfrounds);
289: kdf(enckey.salt, sizeof(enckey.salt), rounds, xorkey, sizeof(xorkey));
290: for (i = 0; i < sizeof(enckey.seckey); i++)
291: enckey.seckey[i] ^= xorkey[i];
292: memset(xorkey, 0, sizeof(xorkey));
293: SHA512Init(&ctx);
294: SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
295: SHA512Final(digest, &ctx);
296: if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)))
297: errx(1, "incorrect passphrase");
298: memset(digest, 0, sizeof(digest));
299:
300: msg = readmsg(inputfile, &msglen);
301:
302: signmsg(enckey.seckey, msg, msglen, sig.sig);
1.13 tedu 303: memcpy(sig.fingerprint, enckey.fingerprint, FPLEN);
1.1 tedu 304: memset(&enckey, 0, sizeof(enckey));
305:
306: memcpy(sig.pkalg, PKALG, 2);
307: writeb64file(sigfile, "signature", &sig, sizeof(sig), 0666);
308:
309: free(msg);
310: }
1.14 tedu 311: #endif
1.1 tedu 312:
313: static void
1.13 tedu 314: verifymsg(uint8_t *pubkey, uint8_t *msg, unsigned long long msglen,
315: uint8_t *sig)
316: {
317: uint8_t *sigbuf, *dummybuf;
318: unsigned long long siglen, dummylen;
319:
320: siglen = SIGBYTES + msglen;
321: sigbuf = xmalloc(siglen);
322: dummybuf = xmalloc(siglen);
323: memcpy(sigbuf, sig, SIGBYTES);
324: memcpy(sigbuf + SIGBYTES, msg, msglen);
325: if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen,
326: pubkey) == -1)
327: errx(1, "signature verification failed");
328: free(sigbuf);
329: free(dummybuf);
330: }
331:
332:
333: static void
1.1 tedu 334: verify(const char *pubkeyfile, const char *inputfile, const char *sigfile)
335: {
336: struct sig sig;
337: struct pubkey pubkey;
338: unsigned long long msglen;
339: uint8_t *msg;
340:
341: readb64file(pubkeyfile, &pubkey, sizeof(pubkey));
342: readb64file(sigfile, &sig, sizeof(sig));
1.13 tedu 343:
344: if (memcmp(pubkey.fingerprint, sig.fingerprint, FPLEN))
345: errx(1, "verification failed: checked against wrong key");
1.1 tedu 346:
347: msg = readmsg(inputfile, &msglen);
348:
349: verifymsg(pubkey.pubkey, msg, msglen, sig.sig);
350: printf("verified\n");
351:
352: free(msg);
353: }
354:
355: int
356: main(int argc, char **argv)
357: {
358: const char *pubkeyfile = NULL, *seckeyfile = NULL, *inputfile = NULL,
359: *sigfile = NULL;
360: char sigfilebuf[1024];
361: int ch, rounds;
1.6 tedu 362: enum {
363: NONE,
364: GENERATE,
365: SIGN,
366: VERIFY
367: } verb = NONE;
368:
1.1 tedu 369:
370: rounds = 42;
371:
1.9 espie 372: while ((ch = getopt(argc, argv, "GSVno:p:s:")) != -1) {
1.1 tedu 373: switch (ch) {
1.14 tedu 374: #ifndef VERIFYONLY
1.6 tedu 375: case 'G':
376: if (verb)
377: usage();
378: verb = GENERATE;
379: break;
380: case 'S':
381: if (verb)
382: usage();
383: verb = SIGN;
384: break;
1.14 tedu 385: #endif
1.6 tedu 386: case 'V':
387: if (verb)
388: usage();
389: verb = VERIFY;
390: break;
391: case 'n':
1.1 tedu 392: rounds = 0;
393: break;
1.6 tedu 394: case 'o':
1.1 tedu 395: sigfile = optarg;
396: break;
1.6 tedu 397: case 'p':
1.1 tedu 398: pubkeyfile = optarg;
399: break;
1.6 tedu 400: case 's':
1.1 tedu 401: seckeyfile = optarg;
402: break;
403: default:
404: usage();
405: break;
406: }
407: }
1.2 tedu 408: argc -= optind;
1.9 espie 409: argv += optind;
410:
1.15 ! espie 411: #ifdef VERIFYONLY
! 412: if (verb != VERIFY)
! 413: #else
1.9 espie 414: if (verb == NONE)
1.15 ! espie 415: #endif
1.1 tedu 416: usage();
417:
1.14 tedu 418: #ifndef VERIFYONLY
1.6 tedu 419: if (verb == GENERATE) {
1.9 espie 420: if (!pubkeyfile || !seckeyfile || argc != 0)
1.1 tedu 421: usage();
422: generate(pubkeyfile, seckeyfile, rounds);
1.14 tedu 423: } else
424: #endif
425: {
1.9 espie 426: if (argc != 1)
1.1 tedu 427: usage();
1.9 espie 428:
429: inputfile = argv[0];
430:
431: if (!sigfile) {
432: if (snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig",
433: inputfile) >= sizeof(sigfilebuf))
434: errx(1, "path too long");
435: sigfile = sigfilebuf;
436: }
1.14 tedu 437: #ifndef VERIFYONLY
1.9 espie 438: if (verb == SIGN) {
439: if (!seckeyfile)
440: usage();
441: sign(seckeyfile, inputfile, sigfile);
1.14 tedu 442: } else
443: #endif
444: if (verb == VERIFY) {
1.9 espie 445: if (!pubkeyfile)
446: usage();
447: verify(pubkeyfile, inputfile, sigfile);
448: }
1.1 tedu 449: }
1.9 espie 450:
1.1 tedu 451: return 0;
452: }