xref: /openbsd/usr.bin/signify/signify.c (revision d59d433d)
1*d59d433dSespie /* $OpenBSD: signify.c,v 1.7 2014/01/02 16:34:02 espie Exp $ */
24215a5deStedu /*
34215a5deStedu  * Copyright (c) 2013 Ted Unangst <tedu@openbsd.org>
44215a5deStedu  *
54215a5deStedu  * Permission to use, copy, modify, and distribute this software for any
64215a5deStedu  * purpose with or without fee is hereby granted, provided that the above
74215a5deStedu  * copyright notice and this permission notice appear in all copies.
84215a5deStedu  *
94215a5deStedu  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
104215a5deStedu  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
114215a5deStedu  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
124215a5deStedu  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
134215a5deStedu  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
144215a5deStedu  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
154215a5deStedu  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
164215a5deStedu  */
174215a5deStedu #include <sys/stat.h>
184215a5deStedu 
194215a5deStedu #include <netinet/in.h>
204215a5deStedu #include <resolv.h>
214215a5deStedu 
224215a5deStedu #include <stdint.h>
234215a5deStedu #include <fcntl.h>
244215a5deStedu #include <string.h>
254215a5deStedu #include <stdio.h>
264215a5deStedu #include <err.h>
274215a5deStedu #include <unistd.h>
284215a5deStedu #include <readpassphrase.h>
294215a5deStedu #include <util.h>
304215a5deStedu #include <sha2.h>
314215a5deStedu 
324215a5deStedu #include "crypto_api.h"
334215a5deStedu 
344215a5deStedu #define streq(a, b) (strcmp(a, b) == 0)
354215a5deStedu 
364215a5deStedu #define SIGBYTES crypto_sign_ed25519_BYTES
374215a5deStedu #define SECRETBYTES crypto_sign_ed25519_SECRETKEYBYTES
384215a5deStedu #define PUBLICBYTES crypto_sign_ed25519_PUBLICKEYBYTES
394215a5deStedu 
404215a5deStedu #define PKALG "Ed"
414215a5deStedu #define KDFALG "BK"
424215a5deStedu 
434215a5deStedu struct enckey {
444215a5deStedu 	uint8_t pkalg[2];
454215a5deStedu 	uint8_t kdfalg[2];
464215a5deStedu 	uint32_t kdfrounds;
474215a5deStedu 	uint8_t salt[16];
484215a5deStedu 	uint8_t checksum[8];
494215a5deStedu 	uint8_t seckey[SECRETBYTES];
504215a5deStedu };
514215a5deStedu 
524215a5deStedu struct pubkey {
534215a5deStedu 	uint8_t pkalg[2];
544215a5deStedu 	uint8_t pubkey[PUBLICBYTES];
554215a5deStedu };
564215a5deStedu 
574215a5deStedu struct sig {
584215a5deStedu 	uint8_t pkalg[2];
594215a5deStedu 	uint8_t sig[SIGBYTES];
604215a5deStedu };
614215a5deStedu 
624215a5deStedu extern char *__progname;
634215a5deStedu 
644215a5deStedu static void
654215a5deStedu usage(void)
664215a5deStedu {
67a6bade58Stedu 	fprintf(stderr, "usage: %s [-n] [-i input] [-o output] [-p pubkey] [-s seckey] "
68a6bade58Stedu 	    "-G | -S | -V\n", __progname);
694215a5deStedu 	exit(1);
704215a5deStedu }
714215a5deStedu 
724215a5deStedu static int
734215a5deStedu xopen(const char *fname, int flags, mode_t mode)
744215a5deStedu {
754215a5deStedu 	int fd;
764215a5deStedu 
774215a5deStedu 	fd = open(fname, flags, mode);
784215a5deStedu 	if (fd == -1)
794215a5deStedu 		err(1, "open %s", fname);
804215a5deStedu 	return fd;
814215a5deStedu }
824215a5deStedu 
834215a5deStedu static void *
844215a5deStedu xmalloc(size_t len)
854215a5deStedu {
864215a5deStedu 	void *p;
874215a5deStedu 
884215a5deStedu 	p = malloc(len);
894215a5deStedu 	if (!p)
904215a5deStedu 		err(1, "malloc %zu", len);
914215a5deStedu 	return p;
924215a5deStedu }
934215a5deStedu 
944215a5deStedu static void
95*d59d433dSespie readall(int fd, void *buf, size_t len, const char *filename)
964215a5deStedu {
97*d59d433dSespie 	ssize_t x = read(fd, buf, len);
98*d59d433dSespie 	if (x == -1) {
99*d59d433dSespie 		err(1, "read from %s", filename);
100*d59d433dSespie 	} else if (x != len) {
101*d59d433dSespie 		errx(1, "short read from %s", filename);
102*d59d433dSespie 	}
1034215a5deStedu }
1044215a5deStedu 
1054215a5deStedu static void
1064215a5deStedu readb64file(const char *filename, void *buf, size_t len)
1074215a5deStedu {
1084215a5deStedu 	char b64[2048];
1094215a5deStedu 	int i, rv, fd;
1104215a5deStedu 
1114215a5deStedu 	fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
1124215a5deStedu 	memset(b64, 0, sizeof(b64));
1134215a5deStedu 	rv = read(fd, b64, sizeof(b64) - 1);
1144215a5deStedu 	if (rv == -1)
115*d59d433dSespie 		err(1, "read from %s", filename);
1164215a5deStedu 	for (i = 0; i < rv; i++)
1174215a5deStedu 		if (b64[i] == '\n')
1184215a5deStedu 			break;
1194215a5deStedu 	if (i == rv)
1204215a5deStedu 		errx(1, "no newline in %s", filename);
1214215a5deStedu 	rv = b64_pton(b64 + i, buf, len);
1224215a5deStedu 	if (rv != len)
1234215a5deStedu 		errx(1, "invalid b64 encoding in %s", filename);
1244215a5deStedu 	memset(b64, 0, sizeof(b64));
1254215a5deStedu 	close(fd);
1264215a5deStedu 	if (memcmp(buf, PKALG, 2))
1274215a5deStedu 		errx(1, "unsupported file %s", filename);
1284215a5deStedu }
1294215a5deStedu 
1304215a5deStedu uint8_t *
1314215a5deStedu readmsg(const char *filename, unsigned long long *msglenp)
1324215a5deStedu {
1334215a5deStedu 	unsigned long long msglen;
1344215a5deStedu 	uint8_t *msg;
1354215a5deStedu 	struct stat sb;
1364215a5deStedu 	int fd;
1374215a5deStedu 
1384215a5deStedu 	fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
1394215a5deStedu 	fstat(fd, &sb);
1404215a5deStedu 	msglen = sb.st_size;
1414215a5deStedu 	if (msglen > (1UL << 30))
1424215a5deStedu 		errx(1, "msg too large in %s", filename);
1434215a5deStedu 	msg = xmalloc(msglen);
144*d59d433dSespie 	readall(fd, msg, msglen, filename);
1454215a5deStedu 	close(fd);
1464215a5deStedu 
1474215a5deStedu 	*msglenp = msglen;
1484215a5deStedu 	return msg;
1494215a5deStedu }
1504215a5deStedu 
1514215a5deStedu static void
152*d59d433dSespie writeall(int fd, const void *buf, size_t len, const char *filename)
1534215a5deStedu {
154*d59d433dSespie 	ssize_t x = write(fd, buf, len);
155*d59d433dSespie 	if (x == -1) {
156*d59d433dSespie 		err(1, "write to %s", filename);
157*d59d433dSespie 	} else if (x != len) {
158*d59d433dSespie 		errx(1, "short write to %s", filename);
159*d59d433dSespie 	}
1604215a5deStedu }
1614215a5deStedu 
1624215a5deStedu static void
1634215a5deStedu writeb64file(const char *filename, const char *comment, const void *buf,
1644215a5deStedu     size_t len, mode_t mode)
1654215a5deStedu {
1664215a5deStedu 	char header[1024];
1674215a5deStedu 	char b64[1024];
1684215a5deStedu 	int fd, rv;
1694215a5deStedu 
1704215a5deStedu 	fd = xopen(filename, O_CREAT|O_EXCL|O_NOFOLLOW|O_RDWR, mode);
1714215a5deStedu 	snprintf(header, sizeof(header), "signify -- %s\n", comment);
172*d59d433dSespie 	writeall(fd, header, strlen(header), filename);
1734215a5deStedu 	if ((rv = b64_ntop(buf, len, b64, sizeof(b64))) == -1)
1744215a5deStedu 		errx(1, "b64 encode failed");
175*d59d433dSespie 	writeall(fd, b64, rv, filename);
1764215a5deStedu 	memset(b64, 0, sizeof(b64));
1774215a5deStedu 	close(fd);
1784215a5deStedu }
1794215a5deStedu 
1804215a5deStedu static void
1814215a5deStedu kdf(uint8_t *salt, size_t saltlen, int rounds, uint8_t *key, size_t keylen)
1824215a5deStedu {
1834215a5deStedu 	char pass[1024];
1844215a5deStedu 
1854215a5deStedu 	if (rounds == 0) {
1864215a5deStedu 		memset(key, 0, keylen);
1874215a5deStedu 		return;
1884215a5deStedu 	}
1894215a5deStedu 
1904215a5deStedu 	if (!readpassphrase("passphrase: ", pass, sizeof(pass), 0))
1914215a5deStedu 		errx(1, "readpassphrase");
1924215a5deStedu 	if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key,
1934215a5deStedu 	    keylen, rounds) == -1)
1944215a5deStedu 		errx(1, "bcrypt pbkdf");
1954215a5deStedu 	memset(pass, 0, sizeof(pass));
1964215a5deStedu }
1974215a5deStedu 
1984215a5deStedu static void
1994215a5deStedu signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen,
2004215a5deStedu     uint8_t *sig)
2014215a5deStedu {
2024215a5deStedu 	unsigned long long siglen;
2034215a5deStedu 	uint8_t *sigbuf;
2044215a5deStedu 
2054215a5deStedu 	sigbuf = xmalloc(msglen + SIGBYTES);
2064215a5deStedu 	crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey);
2074215a5deStedu 	memcpy(sig, sigbuf, SIGBYTES);
2084215a5deStedu 	free(sigbuf);
2094215a5deStedu }
2104215a5deStedu 
2114215a5deStedu static void
2124215a5deStedu verifymsg(uint8_t *pubkey, uint8_t *msg, unsigned long long msglen,
2134215a5deStedu     uint8_t *sig)
2144215a5deStedu {
2154215a5deStedu 	uint8_t *sigbuf, *dummybuf;
2164215a5deStedu 	unsigned long long siglen, dummylen;
2174215a5deStedu 
2184215a5deStedu 	siglen = SIGBYTES + msglen;
2194215a5deStedu 	sigbuf = xmalloc(siglen);
2204215a5deStedu 	dummybuf = xmalloc(siglen);
2214215a5deStedu 	memcpy(sigbuf, sig, SIGBYTES);
2224215a5deStedu 	memcpy(sigbuf + SIGBYTES, msg, msglen);
2234215a5deStedu 	if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen,
2244215a5deStedu 	    pubkey) == -1)
2254215a5deStedu 		errx(1, "signature failed");
2264215a5deStedu 	free(sigbuf);
2274215a5deStedu 	free(dummybuf);
2284215a5deStedu }
2294215a5deStedu 
2304215a5deStedu static void
2314215a5deStedu generate(const char *pubkeyfile, const char *seckeyfile, int rounds)
2324215a5deStedu {
2334215a5deStedu 	uint8_t digest[SHA512_DIGEST_LENGTH];
2344215a5deStedu 	struct pubkey pubkey;
2354215a5deStedu 	struct enckey enckey;
2364215a5deStedu 	uint8_t xorkey[sizeof(enckey.seckey)];
2374215a5deStedu 	SHA2_CTX ctx;
2384215a5deStedu 	int i;
2394215a5deStedu 
2404215a5deStedu 	crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey);
2414215a5deStedu 
2424215a5deStedu 	SHA512Init(&ctx);
2434215a5deStedu 	SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
2444215a5deStedu 	SHA512Final(digest, &ctx);
2454215a5deStedu 
2464215a5deStedu 	memcpy(enckey.pkalg, PKALG, 2);
2474215a5deStedu 	memcpy(enckey.kdfalg, KDFALG, 2);
2484215a5deStedu 	enckey.kdfrounds = htonl(rounds);
2494215a5deStedu 	arc4random_buf(enckey.salt, sizeof(enckey.salt));
2504215a5deStedu 	kdf(enckey.salt, sizeof(enckey.salt), rounds, xorkey, sizeof(xorkey));
2514215a5deStedu 	memcpy(enckey.checksum, digest, sizeof(enckey.checksum));
2524215a5deStedu 	for (i = 0; i < sizeof(enckey.seckey); i++)
2534215a5deStedu 		enckey.seckey[i] ^= xorkey[i];
2544215a5deStedu 	memset(digest, 0, sizeof(digest));
2554215a5deStedu 	memset(xorkey, 0, sizeof(xorkey));
2564215a5deStedu 
2574215a5deStedu 	writeb64file(seckeyfile, "secret key", &enckey,
2584215a5deStedu 	    sizeof(enckey), 0600);
2594215a5deStedu 	memset(&enckey, 0, sizeof(enckey));
2604215a5deStedu 
2614215a5deStedu 	memcpy(pubkey.pkalg, PKALG, 2);
2624215a5deStedu 	writeb64file(pubkeyfile, "public key", &pubkey,
2634215a5deStedu 	    sizeof(pubkey), 0666);
2644215a5deStedu }
2654215a5deStedu 
2664215a5deStedu static void
2674215a5deStedu sign(const char *seckeyfile, const char *inputfile, const char *sigfile)
2684215a5deStedu {
2694215a5deStedu 	struct sig sig;
2704215a5deStedu 	uint8_t digest[SHA512_DIGEST_LENGTH];
2714215a5deStedu 	struct enckey enckey;
2724215a5deStedu 	uint8_t xorkey[sizeof(enckey.seckey)];
2734215a5deStedu 	uint8_t *msg;
2744215a5deStedu 	unsigned long long msglen;
2754215a5deStedu 	int i, rounds;
2764215a5deStedu 	SHA2_CTX ctx;
2774215a5deStedu 
2784215a5deStedu 	readb64file(seckeyfile, &enckey, sizeof(enckey));
2794215a5deStedu 
2804215a5deStedu 	if (memcmp(enckey.kdfalg, KDFALG, 2))
2814215a5deStedu 		errx(1, "unsupported KDF");
2824215a5deStedu 	rounds = ntohl(enckey.kdfrounds);
2834215a5deStedu 	kdf(enckey.salt, sizeof(enckey.salt), rounds, xorkey, sizeof(xorkey));
2844215a5deStedu 	for (i = 0; i < sizeof(enckey.seckey); i++)
2854215a5deStedu 		enckey.seckey[i] ^= xorkey[i];
2864215a5deStedu 	memset(xorkey, 0, sizeof(xorkey));
2874215a5deStedu 	SHA512Init(&ctx);
2884215a5deStedu 	SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
2894215a5deStedu 	SHA512Final(digest, &ctx);
2904215a5deStedu 	if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)))
2914215a5deStedu 	    errx(1, "incorrect passphrase");
2924215a5deStedu 	memset(digest, 0, sizeof(digest));
2934215a5deStedu 
2944215a5deStedu 	msg = readmsg(inputfile, &msglen);
2954215a5deStedu 
2964215a5deStedu 	signmsg(enckey.seckey, msg, msglen, sig.sig);
2974215a5deStedu 	memset(&enckey, 0, sizeof(enckey));
2984215a5deStedu 
2994215a5deStedu 	memcpy(sig.pkalg, PKALG, 2);
3004215a5deStedu 	writeb64file(sigfile, "signature", &sig, sizeof(sig), 0666);
3014215a5deStedu 
3024215a5deStedu 	free(msg);
3034215a5deStedu }
3044215a5deStedu 
3054215a5deStedu static void
3064215a5deStedu verify(const char *pubkeyfile, const char *inputfile, const char *sigfile)
3074215a5deStedu {
3084215a5deStedu 	struct sig sig;
3094215a5deStedu 	struct pubkey pubkey;
3104215a5deStedu 	unsigned long long msglen;
3114215a5deStedu 	uint8_t *msg;
3124215a5deStedu 
3134215a5deStedu 	readb64file(pubkeyfile, &pubkey, sizeof(pubkey));
3144215a5deStedu 	readb64file(sigfile, &sig, sizeof(sig));
3154215a5deStedu 
3164215a5deStedu 	msg = readmsg(inputfile, &msglen);
3174215a5deStedu 
3184215a5deStedu 	verifymsg(pubkey.pubkey, msg, msglen, sig.sig);
3194215a5deStedu 	printf("verified\n");
3204215a5deStedu 
3214215a5deStedu 	free(msg);
3224215a5deStedu }
3234215a5deStedu 
3244215a5deStedu int
3254215a5deStedu main(int argc, char **argv)
3264215a5deStedu {
3274215a5deStedu 	const char *pubkeyfile = NULL, *seckeyfile = NULL, *inputfile = NULL,
3284215a5deStedu 	    *sigfile = NULL;
3294215a5deStedu 	char sigfilebuf[1024];
3304215a5deStedu 	int ch, rounds;
331a6bade58Stedu 	enum {
332a6bade58Stedu 		NONE,
333a6bade58Stedu 		GENERATE,
334a6bade58Stedu 		SIGN,
335a6bade58Stedu 		VERIFY
336a6bade58Stedu 	} verb = NONE;
337a6bade58Stedu 
3384215a5deStedu 
3394215a5deStedu 	rounds = 42;
3404215a5deStedu 
341a6bade58Stedu 	while ((ch = getopt(argc, argv, "GSVi:no:p:s:")) != -1) {
3424215a5deStedu 		switch (ch) {
343a6bade58Stedu 		case 'G':
344a6bade58Stedu 			if (verb)
345a6bade58Stedu 				usage();
346a6bade58Stedu 			verb = GENERATE;
3474215a5deStedu 			break;
3484215a5deStedu 		case 'S':
349a6bade58Stedu 			if (verb)
350a6bade58Stedu 				usage();
351a6bade58Stedu 			verb = SIGN;
3524215a5deStedu 			break;
3534215a5deStedu 		case 'V':
354a6bade58Stedu 			if (verb)
355a6bade58Stedu 				usage();
356a6bade58Stedu 			verb = VERIFY;
357a6bade58Stedu 			break;
358a6bade58Stedu 		case 'i':
359a6bade58Stedu 			inputfile = optarg;
360a6bade58Stedu 			break;
361a6bade58Stedu 		case 'n':
362a6bade58Stedu 			rounds = 0;
363a6bade58Stedu 			break;
364a6bade58Stedu 		case 'o':
365a6bade58Stedu 			sigfile = optarg;
366a6bade58Stedu 			break;
367a6bade58Stedu 		case 'p':
368a6bade58Stedu 			pubkeyfile = optarg;
369a6bade58Stedu 			break;
370a6bade58Stedu 		case 's':
371a6bade58Stedu 			seckeyfile = optarg;
3724215a5deStedu 			break;
3734215a5deStedu 		default:
3744215a5deStedu 			usage();
3754215a5deStedu 			break;
3764215a5deStedu 		}
3774215a5deStedu 	}
378bcc39c47Stedu 	argc -= optind;
379a6bade58Stedu 	if (argc != 0)
3804215a5deStedu 		usage();
3814215a5deStedu 
3824215a5deStedu 	if (inputfile && !sigfile) {
3834215a5deStedu 		if (snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig",
384bcc39c47Stedu 		    inputfile) >= sizeof(sigfilebuf))
3854215a5deStedu 			errx(1, "path too long");
3864215a5deStedu 		sigfile = sigfilebuf;
3874215a5deStedu 	}
3884215a5deStedu 
389a6bade58Stedu 	if (verb == GENERATE) {
3904215a5deStedu 		if (!pubkeyfile || !seckeyfile)
3914215a5deStedu 			usage();
3924215a5deStedu 		generate(pubkeyfile, seckeyfile, rounds);
393a6bade58Stedu 	} else if (verb == SIGN) {
3944215a5deStedu 		if (!seckeyfile || !inputfile)
3954215a5deStedu 			usage();
3964215a5deStedu 		sign(seckeyfile, inputfile, sigfile);
397a6bade58Stedu 	} else if (verb == VERIFY) {
3984215a5deStedu 		if (!pubkeyfile || !inputfile)
3994215a5deStedu 			usage();
4004215a5deStedu 		verify(pubkeyfile, inputfile, sigfile);
4014215a5deStedu 	} else {
4024215a5deStedu 		usage();
4034215a5deStedu 	}
4044215a5deStedu 	return 0;
4054215a5deStedu }
406