Annotation of src/usr.bin/signify/signify.c, Revision 1.18
1.18 ! tedu 1: /* $OpenBSD: signify.c,v 1.17 2014/01/09 17:13:36 deraadt 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:
1.18 ! tedu 42: #define COMMENTHDR "untrusted comment: "
! 43: #define COMMENTHDRLEN 19
! 44: #define COMMENTMAXLEN 1024
1.1 tedu 45:
46: struct enckey {
47: uint8_t pkalg[2];
48: uint8_t kdfalg[2];
49: uint32_t kdfrounds;
50: uint8_t salt[16];
51: uint8_t checksum[8];
1.13 tedu 52: uint8_t fingerprint[FPLEN];
1.1 tedu 53: uint8_t seckey[SECRETBYTES];
54: };
55:
56: struct pubkey {
57: uint8_t pkalg[2];
1.13 tedu 58: uint8_t fingerprint[FPLEN];
1.1 tedu 59: uint8_t pubkey[PUBLICBYTES];
60: };
61:
62: struct sig {
63: uint8_t pkalg[2];
1.13 tedu 64: uint8_t fingerprint[FPLEN];
1.1 tedu 65: uint8_t sig[SIGBYTES];
66: };
67:
68: extern char *__progname;
69:
70: static void
71: usage(void)
72: {
1.9 espie 73: fprintf(stderr, "usage:"
1.15 espie 74: #ifndef VERIFYONLY
1.9 espie 75: "\t%s [-n] -p pubkey -s seckey -G\n"
1.16 tedu 76: "\t%s [-e] [-o output] -s seckey -S input\n"
1.15 espie 77: #endif
1.16 tedu 78: "\t%s [-e] [-o output] -p pubkey -V input\n",
1.15 espie 79: #ifndef VERIFYONLY
80: __progname, __progname,
81: #endif
82: __progname);
1.1 tedu 83: exit(1);
84: }
85:
86: static int
87: xopen(const char *fname, int flags, mode_t mode)
88: {
89: int fd;
90:
91: fd = open(fname, flags, mode);
92: if (fd == -1)
93: err(1, "open %s", fname);
94: return fd;
95: }
96:
97: static void *
98: xmalloc(size_t len)
99: {
100: void *p;
101:
102: p = malloc(len);
103: if (!p)
104: err(1, "malloc %zu", len);
105: return p;
106: }
107:
108: static void
1.7 espie 109: readall(int fd, void *buf, size_t len, const char *filename)
1.1 tedu 110: {
1.11 tedu 111: ssize_t x;
112:
113: x = read(fd, buf, len);
1.7 espie 114: if (x == -1) {
115: err(1, "read from %s", filename);
116: } else if (x != len) {
117: errx(1, "short read from %s", filename);
118: }
1.1 tedu 119: }
120:
1.16 tedu 121: static size_t
1.18 ! tedu 122: parseb64file(const char *filename, char *b64, void *buf, size_t len,
! 123: char *comment)
1.16 tedu 124: {
125: int rv;
126: char *commentend, *b64end;
127:
128: commentend = strchr(b64, '\n');
129: if (!commentend || commentend - b64 <= COMMENTHDRLEN ||
130: memcmp(b64, COMMENTHDR, COMMENTHDRLEN))
131: errx(1, "invalid comment in %s; must start with '%s'",
132: filename, COMMENTHDR);
1.18 ! tedu 133: *commentend = 0;
! 134: if (comment)
! 135: strlcpy(comment, b64 + COMMENTHDRLEN, COMMENTMAXLEN);
1.16 tedu 136: b64end = strchr(commentend + 1, '\n');
137: if (!b64end)
138: errx(1, "missing new line after b64 in %s", filename);
139: *b64end = 0;
140: rv = b64_pton(commentend + 1, buf, len);
141: if (rv != len)
142: errx(1, "invalid b64 encoding in %s", filename);
143: if (memcmp(buf, PKALG, 2))
144: errx(1, "unsupported file %s", filename);
145: return b64end - b64 + 1;
146: }
147:
1.1 tedu 148: static void
1.18 ! tedu 149: readb64file(const char *filename, void *buf, size_t len, char *comment)
1.1 tedu 150: {
151: char b64[2048];
1.10 tedu 152: int rv, fd;
1.1 tedu 153:
154: fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
155: memset(b64, 0, sizeof(b64));
156: rv = read(fd, b64, sizeof(b64) - 1);
157: if (rv == -1)
1.7 espie 158: err(1, "read from %s", filename);
1.18 ! tedu 159: parseb64file(filename, b64, buf, len, comment);
1.1 tedu 160: memset(b64, 0, sizeof(b64));
161: close(fd);
162: }
163:
164: uint8_t *
165: readmsg(const char *filename, unsigned long long *msglenp)
166: {
167: unsigned long long msglen;
168: uint8_t *msg;
169: struct stat sb;
170: int fd;
171:
172: fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
173: fstat(fd, &sb);
174: msglen = sb.st_size;
175: if (msglen > (1UL << 30))
176: errx(1, "msg too large in %s", filename);
177: msg = xmalloc(msglen);
1.7 espie 178: readall(fd, msg, msglen, filename);
1.1 tedu 179: close(fd);
180:
181: *msglenp = msglen;
182: return msg;
183: }
184:
185: static void
1.7 espie 186: writeall(int fd, const void *buf, size_t len, const char *filename)
1.1 tedu 187: {
1.11 tedu 188: ssize_t x;
189:
190: x = write(fd, buf, len);
1.7 espie 191: if (x == -1) {
192: err(1, "write to %s", filename);
193: } else if (x != len) {
194: errx(1, "short write to %s", filename);
195: }
1.1 tedu 196: }
197:
1.17 deraadt 198: #ifndef VERIFYONLY
1.1 tedu 199: static void
1.16 tedu 200: appendall(const char *filename, const void *buf, size_t len)
201: {
202: int fd;
203:
204: fd = xopen(filename, O_NOFOLLOW | O_RDWR | O_APPEND, 0);
205: writeall(fd, buf, len, filename);
206: close(fd);
207: }
208:
209: static void
1.1 tedu 210: writeb64file(const char *filename, const char *comment, const void *buf,
211: size_t len, mode_t mode)
212: {
213: char header[1024];
214: char b64[1024];
215: int fd, rv;
216:
217: fd = xopen(filename, O_CREAT|O_EXCL|O_NOFOLLOW|O_RDWR, mode);
1.18 ! tedu 218: snprintf(header, sizeof(header), "%ssignify %s\n", COMMENTHDR,
1.13 tedu 219: comment);
1.7 espie 220: writeall(fd, header, strlen(header), filename);
1.8 espie 221: if ((rv = b64_ntop(buf, len, b64, sizeof(b64)-1)) == -1)
1.1 tedu 222: errx(1, "b64 encode failed");
1.8 espie 223: b64[rv++] = '\n';
1.7 espie 224: writeall(fd, b64, rv, filename);
1.1 tedu 225: memset(b64, 0, sizeof(b64));
226: close(fd);
227: }
228:
229: static void
230: kdf(uint8_t *salt, size_t saltlen, int rounds, uint8_t *key, size_t keylen)
231: {
232: char pass[1024];
233:
234: if (rounds == 0) {
235: memset(key, 0, keylen);
236: return;
237: }
238:
239: if (!readpassphrase("passphrase: ", pass, sizeof(pass), 0))
240: errx(1, "readpassphrase");
241: if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key,
242: keylen, rounds) == -1)
243: errx(1, "bcrypt pbkdf");
244: memset(pass, 0, sizeof(pass));
245: }
246:
247: static void
248: signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen,
249: uint8_t *sig)
250: {
251: unsigned long long siglen;
252: uint8_t *sigbuf;
253:
254: sigbuf = xmalloc(msglen + SIGBYTES);
255: crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey);
256: memcpy(sig, sigbuf, SIGBYTES);
257: free(sigbuf);
258: }
259:
260: static void
261: generate(const char *pubkeyfile, const char *seckeyfile, int rounds)
262: {
263: uint8_t digest[SHA512_DIGEST_LENGTH];
264: struct pubkey pubkey;
265: struct enckey enckey;
266: uint8_t xorkey[sizeof(enckey.seckey)];
1.13 tedu 267: uint8_t fingerprint[FPLEN];
1.1 tedu 268: SHA2_CTX ctx;
269: int i;
270:
271: crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey);
1.13 tedu 272: arc4random_buf(fingerprint, sizeof(fingerprint));
1.1 tedu 273:
274: SHA512Init(&ctx);
275: SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
276: SHA512Final(digest, &ctx);
277:
278: memcpy(enckey.pkalg, PKALG, 2);
279: memcpy(enckey.kdfalg, KDFALG, 2);
280: enckey.kdfrounds = htonl(rounds);
1.13 tedu 281: memcpy(enckey.fingerprint, fingerprint, FPLEN);
1.1 tedu 282: arc4random_buf(enckey.salt, sizeof(enckey.salt));
283: kdf(enckey.salt, sizeof(enckey.salt), rounds, xorkey, sizeof(xorkey));
284: memcpy(enckey.checksum, digest, sizeof(enckey.checksum));
285: for (i = 0; i < sizeof(enckey.seckey); i++)
286: enckey.seckey[i] ^= xorkey[i];
287: memset(digest, 0, sizeof(digest));
288: memset(xorkey, 0, sizeof(xorkey));
289:
290: writeb64file(seckeyfile, "secret key", &enckey,
291: sizeof(enckey), 0600);
292: memset(&enckey, 0, sizeof(enckey));
293:
294: memcpy(pubkey.pkalg, PKALG, 2);
1.13 tedu 295: memcpy(pubkey.fingerprint, fingerprint, FPLEN);
1.1 tedu 296: writeb64file(pubkeyfile, "public key", &pubkey,
297: sizeof(pubkey), 0666);
298: }
299:
300: static void
1.16 tedu 301: sign(const char *seckeyfile, const char *msgfile, const char *sigfile,
302: int embedded)
1.1 tedu 303: {
304: struct sig sig;
305: uint8_t digest[SHA512_DIGEST_LENGTH];
306: struct enckey enckey;
307: uint8_t xorkey[sizeof(enckey.seckey)];
308: uint8_t *msg;
1.18 ! tedu 309: char comment[COMMENTMAXLEN], sigcomment[1024];
1.1 tedu 310: unsigned long long msglen;
311: int i, rounds;
312: SHA2_CTX ctx;
313:
1.18 ! tedu 314: readb64file(seckeyfile, &enckey, sizeof(enckey), comment);
1.1 tedu 315:
316: if (memcmp(enckey.kdfalg, KDFALG, 2))
317: errx(1, "unsupported KDF");
318: rounds = ntohl(enckey.kdfrounds);
319: kdf(enckey.salt, sizeof(enckey.salt), rounds, xorkey, sizeof(xorkey));
320: for (i = 0; i < sizeof(enckey.seckey); i++)
321: enckey.seckey[i] ^= xorkey[i];
322: memset(xorkey, 0, sizeof(xorkey));
323: SHA512Init(&ctx);
324: SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
325: SHA512Final(digest, &ctx);
326: if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)))
327: errx(1, "incorrect passphrase");
328: memset(digest, 0, sizeof(digest));
329:
1.16 tedu 330: msg = readmsg(msgfile, &msglen);
1.1 tedu 331:
332: signmsg(enckey.seckey, msg, msglen, sig.sig);
1.13 tedu 333: memcpy(sig.fingerprint, enckey.fingerprint, FPLEN);
1.1 tedu 334: memset(&enckey, 0, sizeof(enckey));
335:
336: memcpy(sig.pkalg, PKALG, 2);
1.18 ! tedu 337: snprintf(sigcomment, sizeof(sigcomment), "signature from %s", comment);
! 338: writeb64file(sigfile, sigcomment, &sig, sizeof(sig), 0666);
1.16 tedu 339: if (embedded)
340: appendall(sigfile, msg, msglen);
1.1 tedu 341:
342: free(msg);
343: }
1.14 tedu 344: #endif
1.1 tedu 345:
346: static void
1.13 tedu 347: verifymsg(uint8_t *pubkey, uint8_t *msg, unsigned long long msglen,
348: uint8_t *sig)
349: {
350: uint8_t *sigbuf, *dummybuf;
351: unsigned long long siglen, dummylen;
352:
353: siglen = SIGBYTES + msglen;
354: sigbuf = xmalloc(siglen);
355: dummybuf = xmalloc(siglen);
356: memcpy(sigbuf, sig, SIGBYTES);
357: memcpy(sigbuf + SIGBYTES, msg, msglen);
358: if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen,
359: pubkey) == -1)
360: errx(1, "signature verification failed");
361: free(sigbuf);
362: free(dummybuf);
363: }
364:
365:
366: static void
1.16 tedu 367: verify(const char *pubkeyfile, const char *msgfile, const char *sigfile,
368: int embedded)
1.1 tedu 369: {
370: struct sig sig;
371: struct pubkey pubkey;
1.16 tedu 372: unsigned long long msglen, siglen = 0;
1.1 tedu 373: uint8_t *msg;
1.16 tedu 374: int fd;
375:
376: msg = readmsg(embedded ? sigfile : msgfile, &msglen);
1.1 tedu 377:
1.18 ! tedu 378: readb64file(pubkeyfile, &pubkey, sizeof(pubkey), NULL);
1.16 tedu 379: if (embedded) {
1.18 ! tedu 380: siglen = parseb64file(sigfile, msg, &sig, sizeof(sig), NULL);
1.16 tedu 381: msg += siglen;
382: msglen -= siglen;
383: } else {
1.18 ! tedu 384: readb64file(sigfile, &sig, sizeof(sig), NULL);
1.16 tedu 385: }
1.13 tedu 386:
387: if (memcmp(pubkey.fingerprint, sig.fingerprint, FPLEN))
388: errx(1, "verification failed: checked against wrong key");
1.1 tedu 389:
1.16 tedu 390: verifymsg(pubkey.pubkey, msg, msglen, sig.sig);
391: if (embedded) {
392: fd = xopen(msgfile, O_CREAT|O_EXCL|O_NOFOLLOW|O_RDWR, 0666);
393: writeall(fd, msg, msglen, msgfile);
394: close(fd);
395: }
1.1 tedu 396:
397: printf("verified\n");
398:
1.16 tedu 399: free(msg - siglen);
1.1 tedu 400: }
401:
402: int
403: main(int argc, char **argv)
404: {
1.16 tedu 405: const char *pubkeyfile = NULL, *seckeyfile = NULL, *msgfile = NULL,
1.1 tedu 406: *sigfile = NULL;
407: char sigfilebuf[1024];
408: int ch, rounds;
1.16 tedu 409: int embedded = 0;
1.6 tedu 410: enum {
411: NONE,
412: GENERATE,
413: SIGN,
414: VERIFY
415: } verb = NONE;
416:
1.1 tedu 417:
418: rounds = 42;
419:
1.16 tedu 420: while ((ch = getopt(argc, argv, "GSVeno:p:s:")) != -1) {
1.1 tedu 421: switch (ch) {
1.14 tedu 422: #ifndef VERIFYONLY
1.6 tedu 423: case 'G':
424: if (verb)
425: usage();
426: verb = GENERATE;
427: break;
428: case 'S':
429: if (verb)
430: usage();
431: verb = SIGN;
432: break;
1.14 tedu 433: #endif
1.6 tedu 434: case 'V':
435: if (verb)
436: usage();
437: verb = VERIFY;
438: break;
1.16 tedu 439: case 'e':
440: embedded = 1;
441: break;
1.6 tedu 442: case 'n':
1.1 tedu 443: rounds = 0;
444: break;
1.6 tedu 445: case 'o':
1.1 tedu 446: sigfile = optarg;
447: break;
1.6 tedu 448: case 'p':
1.1 tedu 449: pubkeyfile = optarg;
450: break;
1.6 tedu 451: case 's':
1.1 tedu 452: seckeyfile = optarg;
453: break;
454: default:
455: usage();
456: break;
457: }
458: }
1.2 tedu 459: argc -= optind;
1.9 espie 460: argv += optind;
461:
1.15 espie 462: #ifdef VERIFYONLY
463: if (verb != VERIFY)
464: #else
1.9 espie 465: if (verb == NONE)
1.15 espie 466: #endif
1.1 tedu 467: usage();
468:
1.14 tedu 469: #ifndef VERIFYONLY
1.6 tedu 470: if (verb == GENERATE) {
1.9 espie 471: if (!pubkeyfile || !seckeyfile || argc != 0)
1.1 tedu 472: usage();
473: generate(pubkeyfile, seckeyfile, rounds);
1.14 tedu 474: } else
475: #endif
476: {
1.9 espie 477: if (argc != 1)
1.1 tedu 478: usage();
1.9 espie 479:
1.16 tedu 480: msgfile = argv[0];
1.9 espie 481:
482: if (!sigfile) {
483: if (snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig",
1.16 tedu 484: msgfile) >= sizeof(sigfilebuf))
1.9 espie 485: errx(1, "path too long");
486: sigfile = sigfilebuf;
487: }
1.14 tedu 488: #ifndef VERIFYONLY
1.9 espie 489: if (verb == SIGN) {
490: if (!seckeyfile)
491: usage();
1.16 tedu 492: sign(seckeyfile, msgfile, sigfile, embedded);
1.14 tedu 493: } else
494: #endif
495: if (verb == VERIFY) {
1.9 espie 496: if (!pubkeyfile)
497: usage();
1.16 tedu 498: verify(pubkeyfile, msgfile, sigfile, embedded);
1.9 espie 499: }
1.1 tedu 500: }
1.9 espie 501:
1.1 tedu 502: return 0;
503: }