xref: /openbsd/usr.bin/signify/signify.c (revision 2c706440)
1*2c706440Stedu /* $OpenBSD: signify.c,v 1.82 2014/05/14 15:56:41 tedu 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>
26ebde6afdStedu #include <stdlib.h>
274215a5deStedu #include <err.h>
284215a5deStedu #include <unistd.h>
294215a5deStedu #include <readpassphrase.h>
304215a5deStedu #include <util.h>
314215a5deStedu #include <sha2.h>
324215a5deStedu 
334215a5deStedu #include "crypto_api.h"
344215a5deStedu 
354215a5deStedu #define SIGBYTES crypto_sign_ed25519_BYTES
364215a5deStedu #define SECRETBYTES crypto_sign_ed25519_SECRETKEYBYTES
374215a5deStedu #define PUBLICBYTES crypto_sign_ed25519_PUBLICKEYBYTES
384215a5deStedu 
394215a5deStedu #define PKALG "Ed"
404215a5deStedu #define KDFALG "BK"
411c9c770cStedu #define FPLEN 8
421c9c770cStedu 
431c9c770cStedu #define COMMENTHDR "untrusted comment: "
441453d2a0Stedu #define COMMENTHDRLEN 19
451453d2a0Stedu #define COMMENTMAXLEN 1024
460f212b48Stedu #define VERIFYWITH "verify with "
474215a5deStedu 
484215a5deStedu struct enckey {
494215a5deStedu 	uint8_t pkalg[2];
504215a5deStedu 	uint8_t kdfalg[2];
514215a5deStedu 	uint32_t kdfrounds;
524215a5deStedu 	uint8_t salt[16];
534215a5deStedu 	uint8_t checksum[8];
541c9c770cStedu 	uint8_t fingerprint[FPLEN];
554215a5deStedu 	uint8_t seckey[SECRETBYTES];
564215a5deStedu };
574215a5deStedu 
584215a5deStedu struct pubkey {
594215a5deStedu 	uint8_t pkalg[2];
601c9c770cStedu 	uint8_t fingerprint[FPLEN];
614215a5deStedu 	uint8_t pubkey[PUBLICBYTES];
624215a5deStedu };
634215a5deStedu 
644215a5deStedu struct sig {
654215a5deStedu 	uint8_t pkalg[2];
661c9c770cStedu 	uint8_t fingerprint[FPLEN];
674215a5deStedu 	uint8_t sig[SIGBYTES];
684215a5deStedu };
694215a5deStedu 
704215a5deStedu extern char *__progname;
714215a5deStedu 
724215a5deStedu static void
73f2adbe28Stedu usage(const char *error)
744215a5deStedu {
75f2adbe28Stedu 	if (error)
76f2adbe28Stedu 		fprintf(stderr, "%s\n", error);
7742efb9f2Sespie 	fprintf(stderr, "usage:"
7835e4c3d2Sespie #ifndef VERIFYONLY
79c374df80Snaddy 	    "\t%1$s -C [-q] -p pubkey -x sigfile [file ...]\n"
80f2adbe28Stedu 	    "\t%1$s -G [-n] [-c comment] -p pubkey -s seckey\n"
81f2adbe28Stedu 	    "\t%1$s -I [-p pubkey] [-s seckey] [-x sigfile]\n"
82f2adbe28Stedu 	    "\t%1$s -S [-e] [-x sigfile] -s seckey -m message\n"
8335e4c3d2Sespie #endif
8458559f60Stedu 	    "\t%1$s -V [-eq] [-x sigfile] -p pubkey -m message\n",
8535e4c3d2Sespie 	    __progname);
864215a5deStedu 	exit(1);
874215a5deStedu }
884215a5deStedu 
894215a5deStedu static int
908e162516Sderaadt xopen(const char *fname, int oflags, mode_t mode)
914215a5deStedu {
92ead0b14bStedu 	struct stat sb;
934215a5deStedu 	int fd;
944215a5deStedu 
95f2adbe28Stedu 	if (strcmp(fname, "-") == 0) {
968e162516Sderaadt 		if ((oflags & O_WRONLY))
97f2adbe28Stedu 			fd = dup(STDOUT_FILENO);
98f2adbe28Stedu 		else
99f2adbe28Stedu 			fd = dup(STDIN_FILENO);
100f2adbe28Stedu 		if (fd == -1)
101f2adbe28Stedu 			err(1, "dup failed");
102f2adbe28Stedu 	} else {
1038e162516Sderaadt 		fd = open(fname, oflags, mode);
1044215a5deStedu 		if (fd == -1)
105f2adbe28Stedu 			err(1, "can't open %s for %s", fname,
1068e162516Sderaadt 			    (oflags & O_WRONLY) ? "writing" : "reading");
107f2adbe28Stedu 	}
108ead0b14bStedu 	if (fstat(fd, &sb) == -1 || S_ISDIR(sb.st_mode))
1095d586c2bStedu 		errx(1, "not a valid file: %s", fname);
1104215a5deStedu 	return fd;
1114215a5deStedu }
1124215a5deStedu 
1134215a5deStedu static void *
1144215a5deStedu xmalloc(size_t len)
1154215a5deStedu {
1164215a5deStedu 	void *p;
1174215a5deStedu 
1184215a5deStedu 	p = malloc(len);
1194215a5deStedu 	if (!p)
1204215a5deStedu 		err(1, "malloc %zu", len);
1214215a5deStedu 	return p;
1224215a5deStedu }
1234215a5deStedu 
12427f66874Stedu static size_t
1250ce08f52Stedu parseb64file(const char *filename, char *b64, void *buf, size_t buflen,
1261453d2a0Stedu     char *comment)
12727f66874Stedu {
12827f66874Stedu 	char *commentend, *b64end;
12927f66874Stedu 
13027f66874Stedu 	commentend = strchr(b64, '\n');
13127f66874Stedu 	if (!commentend || commentend - b64 <= COMMENTHDRLEN ||
1327d7c2057Stedu 	    memcmp(b64, COMMENTHDR, COMMENTHDRLEN) != 0)
13327f66874Stedu 		errx(1, "invalid comment in %s; must start with '%s'",
13427f66874Stedu 		    filename, COMMENTHDR);
135d502e9a0Stedu 	*commentend = '\0';
136ce3e40b1Sderaadt 	if (comment) {
137ce3e40b1Sderaadt 		if (strlcpy(comment, b64 + COMMENTHDRLEN,
138ce3e40b1Sderaadt 		    COMMENTMAXLEN) >= COMMENTMAXLEN)
13941b393a4Stedu 			errx(1, "comment too long");
140ce3e40b1Sderaadt 	}
14127f66874Stedu 	b64end = strchr(commentend + 1, '\n');
14227f66874Stedu 	if (!b64end)
143*2c706440Stedu 		errx(1, "missing new line after base64 in %s", filename);
144d502e9a0Stedu 	*b64end = '\0';
145e67d6036Stedu 	if (b64_pton(commentend + 1, buf, buflen) != buflen)
146*2c706440Stedu 		errx(1, "invalid base64 encoding in %s", filename);
1477d7c2057Stedu 	if (memcmp(buf, PKALG, 2) != 0)
14827f66874Stedu 		errx(1, "unsupported file %s", filename);
14927f66874Stedu 	return b64end - b64 + 1;
15027f66874Stedu }
15127f66874Stedu 
1524215a5deStedu static void
1530ce08f52Stedu readb64file(const char *filename, void *buf, size_t buflen, char *comment)
1544215a5deStedu {
1554215a5deStedu 	char b64[2048];
156f030c3d3Stedu 	int rv, fd;
1574215a5deStedu 
1584215a5deStedu 	fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
1594215a5deStedu 	rv = read(fd, b64, sizeof(b64) - 1);
1604215a5deStedu 	if (rv == -1)
161d59d433dSespie 		err(1, "read from %s", filename);
162d502e9a0Stedu 	b64[rv] = '\0';
1630ce08f52Stedu 	parseb64file(filename, b64, buf, buflen, comment);
164c1ca80caStedu 	explicit_bzero(b64, sizeof(b64));
1654215a5deStedu 	close(fd);
1664215a5deStedu }
1674215a5deStedu 
168eee7f9deStedu static uint8_t *
1694215a5deStedu readmsg(const char *filename, unsigned long long *msglenp)
1704215a5deStedu {
171a30f80dbStedu 	unsigned long long msglen = 0;
172a30f80dbStedu 	uint8_t *msg = NULL;
1734215a5deStedu 	struct stat sb;
174a30f80dbStedu 	ssize_t x, space;
1754215a5deStedu 	int fd;
176ff9a6787Stedu 	const unsigned long long maxmsgsize = 1UL << 30;
1774215a5deStedu 
1784215a5deStedu 	fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
179a30f80dbStedu 	if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode)) {
180ff9a6787Stedu 		if (sb.st_size > maxmsgsize)
181a30f80dbStedu 			errx(1, "msg too large in %s", filename);
182a30f80dbStedu 		space = sb.st_size + 1;
183a30f80dbStedu 	} else {
1849083d0fcStedu 		space = 64 * 1024;
185a30f80dbStedu 	}
186a30f80dbStedu 
1879083d0fcStedu 	msg = xmalloc(space + 1);
188a30f80dbStedu 	while (1) {
189a30f80dbStedu 		if (space == 0) {
190ff9a6787Stedu 			if (msglen * 2 > maxmsgsize)
1919083d0fcStedu 				errx(1, "msg too large in %s", filename);
1929083d0fcStedu 			space = msglen;
193a30f80dbStedu 			if (!(msg = realloc(msg, msglen + space + 1)))
194a30f80dbStedu 				errx(1, "realloc");
195a30f80dbStedu 		}
196a30f80dbStedu 		if ((x = read(fd, msg + msglen, space)) == -1)
197a30f80dbStedu 			err(1, "read from %s", filename);
198a30f80dbStedu 		if (x == 0)
199a30f80dbStedu 			break;
200a30f80dbStedu 		space -= x;
201a30f80dbStedu 		msglen += x;
202a30f80dbStedu 	}
203a30f80dbStedu 
204d502e9a0Stedu 	msg[msglen] = '\0';
2054215a5deStedu 	close(fd);
2064215a5deStedu 
2074215a5deStedu 	*msglenp = msglen;
2084215a5deStedu 	return msg;
2094215a5deStedu }
2104215a5deStedu 
2114215a5deStedu static void
2120ce08f52Stedu writeall(int fd, const void *buf, size_t buflen, const char *filename)
2134215a5deStedu {
2147dec58f2Stedu 	ssize_t x;
2157dec58f2Stedu 
2160ce08f52Stedu 	while (buflen != 0) {
2170ce08f52Stedu 		x = write(fd, buf, buflen);
218bcd4d29fSespie 		if (x == -1)
219d59d433dSespie 			err(1, "write to %s", filename);
2200ce08f52Stedu 		buflen -= x;
221bcd4d29fSespie 		buf = (char *)buf + x;
222bcd4d29fSespie 	}
223d59d433dSespie }
2244215a5deStedu 
2256ffce13fSderaadt #ifndef VERIFYONLY
2264215a5deStedu static void
2274215a5deStedu writeb64file(const char *filename, const char *comment, const void *buf,
2288e162516Sderaadt     size_t buflen, const void *msg, size_t msglen, int oflags, mode_t mode)
2294215a5deStedu {
2304215a5deStedu 	char header[1024];
2314215a5deStedu 	char b64[1024];
2324215a5deStedu 	int fd, rv;
2334215a5deStedu 
2348e162516Sderaadt 	fd = xopen(filename, O_CREAT|oflags|O_NOFOLLOW|O_WRONLY, mode);
235ce3e40b1Sderaadt 	if (snprintf(header, sizeof(header), "%s%s\n",
236ce3e40b1Sderaadt 	    COMMENTHDR, comment) >= sizeof(header))
23741b393a4Stedu 		errx(1, "comment too long");
238d59d433dSespie 	writeall(fd, header, strlen(header), filename);
2390ce08f52Stedu 	if ((rv = b64_ntop(buf, buflen, b64, sizeof(b64)-1)) == -1)
240*2c706440Stedu 		errx(1, "base64 encode failed");
241e01c72c4Sespie 	b64[rv++] = '\n';
242d59d433dSespie 	writeall(fd, b64, rv, filename);
243c1ca80caStedu 	explicit_bzero(b64, sizeof(b64));
2440e8a2786Stedu 	if (msg)
2450e8a2786Stedu 		writeall(fd, msg, msglen, filename);
2464215a5deStedu 	close(fd);
2474215a5deStedu }
2484215a5deStedu 
2494215a5deStedu static void
250fdf669dbStedu kdf(uint8_t *salt, size_t saltlen, int rounds, int allowstdin, int confirm,
2514333161aStedu     uint8_t *key, size_t keylen)
2524215a5deStedu {
2534215a5deStedu 	char pass[1024];
2542adf8afcStedu 	int rppflags = RPP_ECHO_OFF;
2554215a5deStedu 
2564215a5deStedu 	if (rounds == 0) {
2574215a5deStedu 		memset(key, 0, keylen);
2584215a5deStedu 		return;
2594215a5deStedu 	}
2604215a5deStedu 
2614333161aStedu 	if (allowstdin && !isatty(STDIN_FILENO))
2622adf8afcStedu 		rppflags |= RPP_STDIN;
2632adf8afcStedu 	if (!readpassphrase("passphrase: ", pass, sizeof(pass), rppflags))
26458ac87a3Stedu 		errx(1, "unable to read passphrase");
2650e5a52c1Stedu 	if (strlen(pass) == 0)
2660e5a52c1Stedu 		errx(1, "please provide a password");
267fdf669dbStedu 	if (confirm && !(rppflags & RPP_STDIN)) {
268fdf669dbStedu 		char pass2[1024];
269fdf669dbStedu 		if (!readpassphrase("confirm passphrase: ", pass2,
270fdf669dbStedu 		    sizeof(pass2), rppflags))
271fdf669dbStedu 			errx(1, "unable to read passphrase");
272fdf669dbStedu 		if (strcmp(pass, pass2) != 0)
273fdf669dbStedu 			errx(1, "passwords don't match");
274fdf669dbStedu 		explicit_bzero(pass2, sizeof(pass2));
275fdf669dbStedu 	}
2764215a5deStedu 	if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key,
2774215a5deStedu 	    keylen, rounds) == -1)
2784215a5deStedu 		errx(1, "bcrypt pbkdf");
279c1ca80caStedu 	explicit_bzero(pass, sizeof(pass));
2804215a5deStedu }
2814215a5deStedu 
2824215a5deStedu static void
2834215a5deStedu signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen,
2844215a5deStedu     uint8_t *sig)
2854215a5deStedu {
2864215a5deStedu 	unsigned long long siglen;
2874215a5deStedu 	uint8_t *sigbuf;
2884215a5deStedu 
2894215a5deStedu 	sigbuf = xmalloc(msglen + SIGBYTES);
2904215a5deStedu 	crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey);
2914215a5deStedu 	memcpy(sig, sigbuf, SIGBYTES);
2924215a5deStedu 	free(sigbuf);
2934215a5deStedu }
2944215a5deStedu 
2954215a5deStedu static void
296bd7b638bStedu generate(const char *pubkeyfile, const char *seckeyfile, int rounds,
297bd7b638bStedu     const char *comment)
2984215a5deStedu {
2994215a5deStedu 	uint8_t digest[SHA512_DIGEST_LENGTH];
3004215a5deStedu 	struct pubkey pubkey;
3014215a5deStedu 	struct enckey enckey;
3024215a5deStedu 	uint8_t xorkey[sizeof(enckey.seckey)];
3031c9c770cStedu 	uint8_t fingerprint[FPLEN];
304bd7b638bStedu 	char commentbuf[COMMENTMAXLEN];
3054215a5deStedu 	SHA2_CTX ctx;
3064215a5deStedu 	int i;
3074215a5deStedu 
3084215a5deStedu 	crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey);
3091c9c770cStedu 	arc4random_buf(fingerprint, sizeof(fingerprint));
3104215a5deStedu 
3114215a5deStedu 	SHA512Init(&ctx);
3124215a5deStedu 	SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
3134215a5deStedu 	SHA512Final(digest, &ctx);
3144215a5deStedu 
3154215a5deStedu 	memcpy(enckey.pkalg, PKALG, 2);
3164215a5deStedu 	memcpy(enckey.kdfalg, KDFALG, 2);
3174215a5deStedu 	enckey.kdfrounds = htonl(rounds);
3181c9c770cStedu 	memcpy(enckey.fingerprint, fingerprint, FPLEN);
3194215a5deStedu 	arc4random_buf(enckey.salt, sizeof(enckey.salt));
320fdf669dbStedu 	kdf(enckey.salt, sizeof(enckey.salt), rounds, 1, 1, xorkey, sizeof(xorkey));
3214215a5deStedu 	memcpy(enckey.checksum, digest, sizeof(enckey.checksum));
3224215a5deStedu 	for (i = 0; i < sizeof(enckey.seckey); i++)
3234215a5deStedu 		enckey.seckey[i] ^= xorkey[i];
324c1ca80caStedu 	explicit_bzero(digest, sizeof(digest));
325c1ca80caStedu 	explicit_bzero(xorkey, sizeof(xorkey));
3264215a5deStedu 
327ce3e40b1Sderaadt 	if (snprintf(commentbuf, sizeof(commentbuf), "%s secret key",
328ce3e40b1Sderaadt 	    comment) >= sizeof(commentbuf))
32941b393a4Stedu 		errx(1, "comment too long");
330bd7b638bStedu 	writeb64file(seckeyfile, commentbuf, &enckey,
3310e8a2786Stedu 	    sizeof(enckey), NULL, 0, O_EXCL, 0600);
332c1ca80caStedu 	explicit_bzero(&enckey, sizeof(enckey));
3334215a5deStedu 
3344215a5deStedu 	memcpy(pubkey.pkalg, PKALG, 2);
3351c9c770cStedu 	memcpy(pubkey.fingerprint, fingerprint, FPLEN);
336ce3e40b1Sderaadt 	if (snprintf(commentbuf, sizeof(commentbuf), "%s public key",
337ce3e40b1Sderaadt 	    comment) >= sizeof(commentbuf))
33841b393a4Stedu 		errx(1, "comment too long");
339bd7b638bStedu 	writeb64file(pubkeyfile, commentbuf, &pubkey,
3400e8a2786Stedu 	    sizeof(pubkey), NULL, 0, O_EXCL, 0666);
3414215a5deStedu }
3424215a5deStedu 
3434215a5deStedu static void
34427f66874Stedu sign(const char *seckeyfile, const char *msgfile, const char *sigfile,
34527f66874Stedu     int embedded)
3464215a5deStedu {
3474215a5deStedu 	struct sig sig;
3484215a5deStedu 	uint8_t digest[SHA512_DIGEST_LENGTH];
3494215a5deStedu 	struct enckey enckey;
3504215a5deStedu 	uint8_t xorkey[sizeof(enckey.seckey)];
3514215a5deStedu 	uint8_t *msg;
3520f212b48Stedu 	char comment[COMMENTMAXLEN], sigcomment[COMMENTMAXLEN];
3530f212b48Stedu 	char *secname;
3544215a5deStedu 	unsigned long long msglen;
3554215a5deStedu 	int i, rounds;
3564215a5deStedu 	SHA2_CTX ctx;
3574215a5deStedu 
3581453d2a0Stedu 	readb64file(seckeyfile, &enckey, sizeof(enckey), comment);
3594215a5deStedu 
3607d7c2057Stedu 	if (memcmp(enckey.kdfalg, KDFALG, 2) != 0)
3614215a5deStedu 		errx(1, "unsupported KDF");
3624215a5deStedu 	rounds = ntohl(enckey.kdfrounds);
3634333161aStedu 	kdf(enckey.salt, sizeof(enckey.salt), rounds, strcmp(msgfile, "-") != 0,
364fdf669dbStedu 	    0, xorkey, sizeof(xorkey));
3654215a5deStedu 	for (i = 0; i < sizeof(enckey.seckey); i++)
3664215a5deStedu 		enckey.seckey[i] ^= xorkey[i];
367c1ca80caStedu 	explicit_bzero(xorkey, sizeof(xorkey));
3684215a5deStedu 	SHA512Init(&ctx);
3694215a5deStedu 	SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
3704215a5deStedu 	SHA512Final(digest, &ctx);
3717d7c2057Stedu 	if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)) != 0)
3724215a5deStedu 	    errx(1, "incorrect passphrase");
373c1ca80caStedu 	explicit_bzero(digest, sizeof(digest));
3744215a5deStedu 
37527f66874Stedu 	msg = readmsg(msgfile, &msglen);
3764215a5deStedu 
3774215a5deStedu 	signmsg(enckey.seckey, msg, msglen, sig.sig);
3781c9c770cStedu 	memcpy(sig.fingerprint, enckey.fingerprint, FPLEN);
379c1ca80caStedu 	explicit_bzero(&enckey, sizeof(enckey));
3804215a5deStedu 
3814215a5deStedu 	memcpy(sig.pkalg, PKALG, 2);
3820f212b48Stedu 	if ((secname = strstr(seckeyfile, ".sec")) && strlen(secname) == 4) {
3830f212b48Stedu 		if (snprintf(sigcomment, sizeof(sigcomment), VERIFYWITH "%.*s.pub",
384265537e5Stedu 		    (int)strlen(seckeyfile) - 4, seckeyfile) >= sizeof(sigcomment))
38541b393a4Stedu 			errx(1, "comment too long");
3860f212b48Stedu 	} else {
387ce3e40b1Sderaadt 		if (snprintf(sigcomment, sizeof(sigcomment), "signature from %s",
388ce3e40b1Sderaadt 		    comment) >= sizeof(sigcomment))
38941b393a4Stedu 			errx(1, "comment too long");
3900f212b48Stedu 	}
39127f66874Stedu 	if (embedded)
3920e8a2786Stedu 		writeb64file(sigfile, sigcomment, &sig, sizeof(sig), msg,
3930e8a2786Stedu 		    msglen, O_TRUNC, 0666);
3940e8a2786Stedu 	else
3950e8a2786Stedu 		writeb64file(sigfile, sigcomment, &sig, sizeof(sig), NULL,
3960e8a2786Stedu 		    0, O_TRUNC, 0666);
3974215a5deStedu 
3984215a5deStedu 	free(msg);
3994215a5deStedu }
40037f70c32Stedu 
40137f70c32Stedu static void
40237f70c32Stedu inspect(const char *seckeyfile, const char *pubkeyfile, const char *sigfile)
40337f70c32Stedu {
40437f70c32Stedu 	struct sig sig;
40537f70c32Stedu 	struct enckey enckey;
40637f70c32Stedu 	struct pubkey pubkey;
40737f70c32Stedu 	char fp[(FPLEN + 2) / 3 * 4 + 1];
40837f70c32Stedu 
40937f70c32Stedu 	if (seckeyfile) {
41037f70c32Stedu 		readb64file(seckeyfile, &enckey, sizeof(enckey), NULL);
41137f70c32Stedu 		b64_ntop(enckey.fingerprint, FPLEN, fp, sizeof(fp));
41237f70c32Stedu 		printf("sec fp: %s\n", fp);
41337f70c32Stedu 	}
41437f70c32Stedu 	if (pubkeyfile) {
41537f70c32Stedu 		readb64file(pubkeyfile, &pubkey, sizeof(pubkey), NULL);
41637f70c32Stedu 		b64_ntop(pubkey.fingerprint, FPLEN, fp, sizeof(fp));
41737f70c32Stedu 		printf("pub fp: %s\n", fp);
41837f70c32Stedu 	}
41937f70c32Stedu 	if (sigfile) {
42037f70c32Stedu 		readb64file(sigfile, &sig, sizeof(sig), NULL);
42137f70c32Stedu 		b64_ntop(sig.fingerprint, FPLEN, fp, sizeof(fp));
42237f70c32Stedu 		printf("sig fp: %s\n", fp);
42337f70c32Stedu 	}
42437f70c32Stedu }
425665ab7d9Stedu #endif
4264215a5deStedu 
4274215a5deStedu static void
42848336e31Stedu verifymsg(struct pubkey *pubkey, uint8_t *msg, unsigned long long msglen,
42948336e31Stedu     struct sig *sig, int quiet)
4301c9c770cStedu {
4311c9c770cStedu 	uint8_t *sigbuf, *dummybuf;
4321c9c770cStedu 	unsigned long long siglen, dummylen;
4331c9c770cStedu 
4347d7c2057Stedu 	if (memcmp(pubkey->fingerprint, sig->fingerprint, FPLEN) != 0)
43548336e31Stedu 		errx(1, "verification failed: checked against wrong key");
43648336e31Stedu 
4371c9c770cStedu 	siglen = SIGBYTES + msglen;
4381c9c770cStedu 	sigbuf = xmalloc(siglen);
4391c9c770cStedu 	dummybuf = xmalloc(siglen);
44048336e31Stedu 	memcpy(sigbuf, sig->sig, SIGBYTES);
4411c9c770cStedu 	memcpy(sigbuf + SIGBYTES, msg, msglen);
4421c9c770cStedu 	if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen,
44348336e31Stedu 	    pubkey->pubkey) == -1)
4441c9c770cStedu 		errx(1, "signature verification failed");
44558559f60Stedu 	if (!quiet)
44658559f60Stedu 		printf("Signature Verified\n");
4471c9c770cStedu 	free(sigbuf);
4481c9c770cStedu 	free(dummybuf);
4491c9c770cStedu }
4501c9c770cStedu 
4511c9c770cStedu static void
45226d9395eStedu readpubkey(const char *pubkeyfile, struct pubkey *pubkey,
45326d9395eStedu     const char *sigcomment)
4544215a5deStedu {
455f65d31e6Stedu 	const char *safepath = "/etc/signify/";
45627f66874Stedu 
4570f212b48Stedu 	if (!pubkeyfile) {
45826d9395eStedu 		if ((pubkeyfile = strstr(sigcomment, VERIFYWITH))) {
4590f212b48Stedu 			pubkeyfile += strlen(VERIFYWITH);
460f65d31e6Stedu 			if (strncmp(pubkeyfile, safepath, strlen(safepath)) != 0 ||
461febc8181Stedu 			    strstr(pubkeyfile, "/../") != NULL)
462b0b02d10Stedu 				errx(1, "untrusted path %s", pubkeyfile);
463b0b02d10Stedu 		} else
4645d586c2bStedu 			usage("must specify pubkey");
4650f212b48Stedu 	}
46626d9395eStedu 	readb64file(pubkeyfile, pubkey, sizeof(*pubkey), NULL);
46726d9395eStedu }
46826d9395eStedu 
46926d9395eStedu static void
47026d9395eStedu verifysimple(const char *pubkeyfile, const char *msgfile, const char *sigfile,
47126d9395eStedu     int quiet)
47226d9395eStedu {
47326d9395eStedu 	char sigcomment[COMMENTMAXLEN];
47426d9395eStedu 	struct sig sig;
47526d9395eStedu 	struct pubkey pubkey;
47626d9395eStedu 	unsigned long long msglen;
47726d9395eStedu 	uint8_t *msg;
47826d9395eStedu 
47926d9395eStedu 	msg = readmsg(msgfile, &msglen);
48026d9395eStedu 
48126d9395eStedu 	readb64file(sigfile, &sig, sizeof(sig), sigcomment);
48226d9395eStedu 	readpubkey(pubkeyfile, &pubkey, sigcomment);
4834215a5deStedu 
48448336e31Stedu 	verifymsg(&pubkey, msg, msglen, &sig, quiet);
485ffebbc61Stedu 
486ffebbc61Stedu 	free(msg);
487ffebbc61Stedu }
488ffebbc61Stedu 
489ffebbc61Stedu static uint8_t *
490ffebbc61Stedu verifyembedded(const char *pubkeyfile, const char *sigfile,
491ffebbc61Stedu     int quiet, unsigned long long *msglenp)
492ffebbc61Stedu {
49326d9395eStedu 	char sigcomment[COMMENTMAXLEN];
494ffebbc61Stedu 	struct sig sig;
495ffebbc61Stedu 	struct pubkey pubkey;
496ffebbc61Stedu 	unsigned long long msglen, siglen;
497ffebbc61Stedu 	uint8_t *msg;
498ffebbc61Stedu 
499ffebbc61Stedu 	msg = readmsg(sigfile, &msglen);
500ffebbc61Stedu 
50126d9395eStedu 	siglen = parseb64file(sigfile, msg, &sig, sizeof(sig), sigcomment);
50226d9395eStedu 	readpubkey(pubkeyfile, &pubkey, sigcomment);
50326d9395eStedu 
504ffebbc61Stedu 	msglen -= siglen;
505ffebbc61Stedu 	memmove(msg, msg + siglen, msglen);
506ffebbc61Stedu 	msg[msglen] = 0;
507ffebbc61Stedu 
508ffebbc61Stedu 	verifymsg(&pubkey, msg, msglen, &sig, quiet);
509ffebbc61Stedu 
510ffebbc61Stedu 	*msglenp = msglen;
511ffebbc61Stedu 	return msg;
512ffebbc61Stedu }
513ffebbc61Stedu 
514ffebbc61Stedu static void
515ffebbc61Stedu verify(const char *pubkeyfile, const char *msgfile, const char *sigfile,
516ffebbc61Stedu     int embedded, int quiet)
517ffebbc61Stedu {
518ffebbc61Stedu 	unsigned long long msglen;
519ffebbc61Stedu 	uint8_t *msg;
520ffebbc61Stedu 	int fd;
521ffebbc61Stedu 
52227f66874Stedu 	if (embedded) {
523ffebbc61Stedu 		msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen);
5249831e76dStedu 		fd = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
52527f66874Stedu 		writeall(fd, msg, msglen, msgfile);
52687e034adSespie 		free(msg);
52727f66874Stedu 		close(fd);
528ffebbc61Stedu 	} else {
529ffebbc61Stedu 		verifysimple(pubkeyfile, msgfile, sigfile, quiet);
53027f66874Stedu 	}
5314215a5deStedu }
5324215a5deStedu 
53358559f60Stedu #ifndef VERIFYONLY
53458559f60Stedu struct checksum {
53558559f60Stedu 	char file[1024];
536032a32d9Stedu 	char hash[224];
537032a32d9Stedu 	char algo[32];
53858559f60Stedu };
53958559f60Stedu 
54058559f60Stedu static void
5410050332bStedu recodehash(char *hash)
5420050332bStedu {
543032a32d9Stedu 	uint8_t data[112];
5440050332bStedu 	int i, rv;
5450050332bStedu 
5460050332bStedu 	if (strlen(hash) == SHA256_DIGEST_STRING_LENGTH ||
5470050332bStedu 	    strlen(hash) == SHA512_DIGEST_STRING_LENGTH)
5480050332bStedu 		return;
5490050332bStedu 	if ((rv = b64_pton(hash, data, sizeof(data))) == -1)
5500050332bStedu 		errx(1, "invalid base64 encoding");
5510050332bStedu 	for (i = 0; i < rv; i++)
5520050332bStedu 		snprintf(hash + i * 2, 1024 - i * 2, "%2.2x", data[i]);
5530050332bStedu }
5540050332bStedu 
5550050332bStedu static void
556a40f4206Stedu verifychecksums(char *msg, int argc, char **argv, int quiet)
55758559f60Stedu {
55858559f60Stedu 	char buf[1024];
559a40f4206Stedu 	char *line, *endline;
56058559f60Stedu 	struct checksum *checksums = NULL, *c = NULL;
561bfc490c6Stedu 	int nchecksums = 0, checksumspace = 0;
5622dd83db0Stedu 	int i, j, rv, uselist, count, hasfailed;
56358559f60Stedu 	int *failures;
56458559f60Stedu 
565a40f4206Stedu 	line = msg;
56658559f60Stedu 	while (line && *line) {
567bfc490c6Stedu 		if (nchecksums == checksumspace) {
568bfc490c6Stedu 			checksumspace = 2 * (nchecksums + 1);
569f46a86e7Stedu 			if (!(checksums = reallocarray(checksums,
570bfc490c6Stedu 			    checksumspace, sizeof(*checksums))))
57158559f60Stedu 				err(1, "realloc");
572bfc490c6Stedu 		}
57358559f60Stedu 		c = &checksums[nchecksums++];
57458559f60Stedu 		if ((endline = strchr(line, '\n')))
5752dd83db0Stedu 			*endline++ = '\0';
576032a32d9Stedu 		rv = sscanf(line, "%31s %1023s = %223s",
5772dd83db0Stedu 		    c->algo, buf, c->hash);
5782dd83db0Stedu 		if (rv != 3 || buf[0] != '(' || buf[strlen(buf) - 1] != ')')
57958559f60Stedu 			errx(1, "unable to parse checksum line %s", line);
5800050332bStedu 		recodehash(c->hash);
58158559f60Stedu 		buf[strlen(buf) - 1] = 0;
58258559f60Stedu 		strlcpy(c->file, buf + 1, sizeof(c->file));
58358559f60Stedu 		line = endline;
58458559f60Stedu 	}
58558559f60Stedu 
58658559f60Stedu 	if (argc) {
58758559f60Stedu 		uselist = 0;
58858559f60Stedu 		count = argc;
58958559f60Stedu 	} else {
59058559f60Stedu 		uselist = 1;
59158559f60Stedu 		count = nchecksums;
59258559f60Stedu 	}
593c8d42ad7Stedu 	if (!(failures = calloc(count, sizeof(*failures))))
5948972cca3Stedu 		err(1, "calloc");
59558559f60Stedu 	for (i = 0; i < count; i++) {
59658559f60Stedu 		if (uselist) {
59758559f60Stedu 			c = &checksums[i];
59858559f60Stedu 		} else {
59958559f60Stedu 			for (j = 0; j < nchecksums; j++) {
60058559f60Stedu 				c = &checksums[j];
60158559f60Stedu 				if (strcmp(c->file, argv[i]) == 0)
60258559f60Stedu 					break;
60358559f60Stedu 			}
60458559f60Stedu 			if (j == nchecksums) {
60558559f60Stedu 				failures[i] = 1;
60658559f60Stedu 				continue;
60758559f60Stedu 			}
60858559f60Stedu 		}
60958559f60Stedu 
61058559f60Stedu 		if (strcmp(c->algo, "SHA256") == 0) {
61158559f60Stedu 			if (!SHA256File(c->file, buf)) {
61258559f60Stedu 				failures[i] = 1;
61358559f60Stedu 				continue;
61458559f60Stedu 			}
61558559f60Stedu 		} else if (strcmp(c->algo, "SHA512") == 0) {
61658559f60Stedu 			if (!SHA512File(c->file, buf)) {
61758559f60Stedu 				failures[i] = 1;
61858559f60Stedu 				continue;
61958559f60Stedu 			}
62058559f60Stedu 		} else {
62158559f60Stedu 			errx(1, "can't handle algorithm %s", c->algo);
62258559f60Stedu 		}
62358559f60Stedu 		if (strcmp(c->hash, buf) != 0) {
62458559f60Stedu 			failures[i] = 1;
62558559f60Stedu 			continue;
62658559f60Stedu 		}
62758559f60Stedu 		if (!quiet)
62858559f60Stedu 			printf("%s: OK\n", c->file);
62958559f60Stedu 	}
6308972cca3Stedu 	hasfailed = 0;
63158559f60Stedu 	for (i = 0; i < count; i++) {
63258559f60Stedu 		if (failures[i]) {
63358559f60Stedu 			fprintf(stderr, "%s: FAIL\n",
63458559f60Stedu 			    uselist ? checksums[i].file : argv[i]);
6358972cca3Stedu 			hasfailed = 1;
63658559f60Stedu 		}
63758559f60Stedu 	}
6388972cca3Stedu 	if (hasfailed)
63958559f60Stedu 		exit(1);
64058559f60Stedu 	free(checksums);
6418972cca3Stedu 	free(failures);
64258559f60Stedu }
64358559f60Stedu 
64458559f60Stedu static void
64558559f60Stedu check(const char *pubkeyfile, const char *sigfile, int quiet, int argc,
64658559f60Stedu     char **argv)
64758559f60Stedu {
648ffebbc61Stedu 	unsigned long long msglen;
64958559f60Stedu 	uint8_t *msg;
65058559f60Stedu 
651ffebbc61Stedu 	msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen);
652a40f4206Stedu 	verifychecksums((char *)msg, argc, argv, quiet);
65358559f60Stedu 
654ffebbc61Stedu 	free(msg);
65558559f60Stedu }
65658559f60Stedu #endif
65758559f60Stedu 
6584215a5deStedu int
6594215a5deStedu main(int argc, char **argv)
6604215a5deStedu {
66127f66874Stedu 	const char *pubkeyfile = NULL, *seckeyfile = NULL, *msgfile = NULL,
6624215a5deStedu 	    *sigfile = NULL;
6634215a5deStedu 	char sigfilebuf[1024];
664bd7b638bStedu 	const char *comment = "signify";
6654215a5deStedu 	int ch, rounds;
66627f66874Stedu 	int embedded = 0;
66758559f60Stedu 	int quiet = 0;
668a6bade58Stedu 	enum {
669a6bade58Stedu 		NONE,
67058559f60Stedu 		CHECK,
671a6bade58Stedu 		GENERATE,
67237f70c32Stedu 		INSPECT,
673a6bade58Stedu 		SIGN,
674a6bade58Stedu 		VERIFY
675a6bade58Stedu 	} verb = NONE;
676a6bade58Stedu 
6774215a5deStedu 
6784215a5deStedu 	rounds = 42;
6794215a5deStedu 
68058559f60Stedu 	while ((ch = getopt(argc, argv, "CGISVc:em:np:qs:x:")) != -1) {
6814215a5deStedu 		switch (ch) {
682665ab7d9Stedu #ifndef VERIFYONLY
68358559f60Stedu 		case 'C':
68458559f60Stedu 			if (verb)
68558559f60Stedu 				usage(NULL);
68658559f60Stedu 			verb = CHECK;
68758559f60Stedu 			break;
688a6bade58Stedu 		case 'G':
689a6bade58Stedu 			if (verb)
690f2adbe28Stedu 				usage(NULL);
691a6bade58Stedu 			verb = GENERATE;
6924215a5deStedu 			break;
69337f70c32Stedu 		case 'I':
69437f70c32Stedu 			if (verb)
695f2adbe28Stedu 				usage(NULL);
69637f70c32Stedu 			verb = INSPECT;
69737f70c32Stedu 			break;
6984215a5deStedu 		case 'S':
699a6bade58Stedu 			if (verb)
700f2adbe28Stedu 				usage(NULL);
701a6bade58Stedu 			verb = SIGN;
7024215a5deStedu 			break;
703665ab7d9Stedu #endif
7044215a5deStedu 		case 'V':
705a6bade58Stedu 			if (verb)
706f2adbe28Stedu 				usage(NULL);
707a6bade58Stedu 			verb = VERIFY;
708a6bade58Stedu 			break;
709bd7b638bStedu 		case 'c':
710bd7b638bStedu 			comment = optarg;
711bd7b638bStedu 			break;
71227f66874Stedu 		case 'e':
71327f66874Stedu 			embedded = 1;
71427f66874Stedu 			break;
715f2adbe28Stedu 		case 'm':
716f2adbe28Stedu 			msgfile = optarg;
717f2adbe28Stedu 			break;
718a6bade58Stedu 		case 'n':
719a6bade58Stedu 			rounds = 0;
720a6bade58Stedu 			break;
721a6bade58Stedu 		case 'p':
722a6bade58Stedu 			pubkeyfile = optarg;
723a6bade58Stedu 			break;
72458559f60Stedu 		case 'q':
72558559f60Stedu 			quiet = 1;
72658559f60Stedu 			break;
727a6bade58Stedu 		case 's':
728a6bade58Stedu 			seckeyfile = optarg;
7294215a5deStedu 			break;
730f2adbe28Stedu 		case 'x':
731f2adbe28Stedu 			sigfile = optarg;
732f2adbe28Stedu 			break;
7334215a5deStedu 		default:
734f2adbe28Stedu 			usage(NULL);
7354215a5deStedu 			break;
7364215a5deStedu 		}
7374215a5deStedu 	}
738bcc39c47Stedu 	argc -= optind;
73942efb9f2Sespie 	argv += optind;
74042efb9f2Sespie 
74158559f60Stedu #ifndef VERIFYONLY
74258559f60Stedu 	if (verb == CHECK) {
74358559f60Stedu 		if (!pubkeyfile || !sigfile)
7445d586c2bStedu 			usage("must specify pubkey and sigfile");
74558559f60Stedu 		check(pubkeyfile, sigfile, quiet, argc, argv);
74658559f60Stedu 		return 0;
74758559f60Stedu 	}
74858559f60Stedu #endif
74958559f60Stedu 
750f2adbe28Stedu 	if (argc != 0)
751f2adbe28Stedu 		usage(NULL);
752f2adbe28Stedu 
7539127dd95Stedu 	if (!sigfile && msgfile) {
754f2adbe28Stedu 		if (strcmp(msgfile, "-") == 0)
7555d586c2bStedu 			usage("must specify sigfile with - message");
7564215a5deStedu 		if (snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig",
75727f66874Stedu 		    msgfile) >= sizeof(sigfilebuf))
7584215a5deStedu 			errx(1, "path too long");
7594215a5deStedu 		sigfile = sigfilebuf;
7604215a5deStedu 	}
7619127dd95Stedu 
7629127dd95Stedu 	switch (verb) {
763665ab7d9Stedu #ifndef VERIFYONLY
7649127dd95Stedu 	case GENERATE:
7659127dd95Stedu 		if (!pubkeyfile || !seckeyfile)
7665d586c2bStedu 			usage("must specify pubkey and seckey");
7679127dd95Stedu 		generate(pubkeyfile, seckeyfile, rounds, comment);
7689127dd95Stedu 		break;
7699127dd95Stedu 	case INSPECT:
7709127dd95Stedu 		inspect(seckeyfile, pubkeyfile, sigfile);
7719127dd95Stedu 		break;
7729127dd95Stedu 	case SIGN:
7739127dd95Stedu 		if (!msgfile || !seckeyfile)
7745d586c2bStedu 			usage("must specify message and seckey");
77527f66874Stedu 		sign(seckeyfile, msgfile, sigfile, embedded);
7769127dd95Stedu 		break;
777665ab7d9Stedu #endif
7789127dd95Stedu 	case VERIFY:
7790f212b48Stedu 		if (!msgfile)
7805d586c2bStedu 			usage("must specify message");
78158559f60Stedu 		verify(pubkeyfile, msgfile, sigfile, embedded, quiet);
7829127dd95Stedu 		break;
7839127dd95Stedu 	default:
7849127dd95Stedu 		usage(NULL);
7859127dd95Stedu 		break;
78642efb9f2Sespie 	}
78742efb9f2Sespie 
7884215a5deStedu 	return 0;
7894215a5deStedu }
790