xref: /openbsd/usr.bin/signify/signify.c (revision dba2cf70)
1*dba2cf70Stedu /* $OpenBSD: signify.c,v 1.89 2014/05/30 21:17:42 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"
34e96025a1Sespie #ifndef VERIFY_ONLY
35e96025a1Sespie #include <stdint.h>
36e96025a1Sespie #include <stddef.h>
37e96025a1Sespie #include <ohash.h>
38e96025a1Sespie #endif
394215a5deStedu 
404215a5deStedu #define SIGBYTES crypto_sign_ed25519_BYTES
414215a5deStedu #define SECRETBYTES crypto_sign_ed25519_SECRETKEYBYTES
424215a5deStedu #define PUBLICBYTES crypto_sign_ed25519_PUBLICKEYBYTES
434215a5deStedu 
444215a5deStedu #define PKALG "Ed"
454215a5deStedu #define KDFALG "BK"
461c9c770cStedu #define FPLEN 8
471c9c770cStedu 
481c9c770cStedu #define COMMENTHDR "untrusted comment: "
491453d2a0Stedu #define COMMENTHDRLEN 19
501453d2a0Stedu #define COMMENTMAXLEN 1024
510f212b48Stedu #define VERIFYWITH "verify with "
524215a5deStedu 
534215a5deStedu struct enckey {
544215a5deStedu 	uint8_t pkalg[2];
554215a5deStedu 	uint8_t kdfalg[2];
564215a5deStedu 	uint32_t kdfrounds;
574215a5deStedu 	uint8_t salt[16];
584215a5deStedu 	uint8_t checksum[8];
591c9c770cStedu 	uint8_t fingerprint[FPLEN];
604215a5deStedu 	uint8_t seckey[SECRETBYTES];
614215a5deStedu };
624215a5deStedu 
634215a5deStedu struct pubkey {
644215a5deStedu 	uint8_t pkalg[2];
651c9c770cStedu 	uint8_t fingerprint[FPLEN];
664215a5deStedu 	uint8_t pubkey[PUBLICBYTES];
674215a5deStedu };
684215a5deStedu 
694215a5deStedu struct sig {
704215a5deStedu 	uint8_t pkalg[2];
711c9c770cStedu 	uint8_t fingerprint[FPLEN];
724215a5deStedu 	uint8_t sig[SIGBYTES];
734215a5deStedu };
744215a5deStedu 
754215a5deStedu extern char *__progname;
764215a5deStedu 
774215a5deStedu static void
78f2adbe28Stedu usage(const char *error)
794215a5deStedu {
80f2adbe28Stedu 	if (error)
81f2adbe28Stedu 		fprintf(stderr, "%s\n", error);
8242efb9f2Sespie 	fprintf(stderr, "usage:"
8335e4c3d2Sespie #ifndef VERIFYONLY
84c374df80Snaddy 	    "\t%1$s -C [-q] -p pubkey -x sigfile [file ...]\n"
85f2adbe28Stedu 	    "\t%1$s -G [-n] [-c comment] -p pubkey -s seckey\n"
86f2adbe28Stedu 	    "\t%1$s -I [-p pubkey] [-s seckey] [-x sigfile]\n"
87f2adbe28Stedu 	    "\t%1$s -S [-e] [-x sigfile] -s seckey -m message\n"
8835e4c3d2Sespie #endif
8958559f60Stedu 	    "\t%1$s -V [-eq] [-x sigfile] -p pubkey -m message\n",
9035e4c3d2Sespie 	    __progname);
914215a5deStedu 	exit(1);
924215a5deStedu }
934215a5deStedu 
944215a5deStedu static int
958e162516Sderaadt xopen(const char *fname, int oflags, mode_t mode)
964215a5deStedu {
97ead0b14bStedu 	struct stat sb;
984215a5deStedu 	int fd;
994215a5deStedu 
100f2adbe28Stedu 	if (strcmp(fname, "-") == 0) {
1018e162516Sderaadt 		if ((oflags & O_WRONLY))
102f2adbe28Stedu 			fd = dup(STDOUT_FILENO);
103f2adbe28Stedu 		else
104f2adbe28Stedu 			fd = dup(STDIN_FILENO);
105f2adbe28Stedu 		if (fd == -1)
106f2adbe28Stedu 			err(1, "dup failed");
107f2adbe28Stedu 	} else {
1088e162516Sderaadt 		fd = open(fname, oflags, mode);
1094215a5deStedu 		if (fd == -1)
110f2adbe28Stedu 			err(1, "can't open %s for %s", fname,
1118e162516Sderaadt 			    (oflags & O_WRONLY) ? "writing" : "reading");
112f2adbe28Stedu 	}
113ead0b14bStedu 	if (fstat(fd, &sb) == -1 || S_ISDIR(sb.st_mode))
1145d586c2bStedu 		errx(1, "not a valid file: %s", fname);
1154215a5deStedu 	return fd;
1164215a5deStedu }
1174215a5deStedu 
1184215a5deStedu static void *
1194215a5deStedu xmalloc(size_t len)
1204215a5deStedu {
1214215a5deStedu 	void *p;
1224215a5deStedu 
123*dba2cf70Stedu 	if (!(p = malloc(len)))
1244215a5deStedu 		err(1, "malloc %zu", len);
1254215a5deStedu 	return p;
1264215a5deStedu }
1274215a5deStedu 
12827f66874Stedu static size_t
1290ce08f52Stedu parseb64file(const char *filename, char *b64, void *buf, size_t buflen,
1301453d2a0Stedu     char *comment)
13127f66874Stedu {
13227f66874Stedu 	char *commentend, *b64end;
13327f66874Stedu 
13427f66874Stedu 	commentend = strchr(b64, '\n');
13527f66874Stedu 	if (!commentend || commentend - b64 <= COMMENTHDRLEN ||
1367d7c2057Stedu 	    memcmp(b64, COMMENTHDR, COMMENTHDRLEN) != 0)
13727f66874Stedu 		errx(1, "invalid comment in %s; must start with '%s'",
13827f66874Stedu 		    filename, COMMENTHDR);
139d502e9a0Stedu 	*commentend = '\0';
140ce3e40b1Sderaadt 	if (comment) {
141ce3e40b1Sderaadt 		if (strlcpy(comment, b64 + COMMENTHDRLEN,
142ce3e40b1Sderaadt 		    COMMENTMAXLEN) >= COMMENTMAXLEN)
14341b393a4Stedu 			errx(1, "comment too long");
144ce3e40b1Sderaadt 	}
145*dba2cf70Stedu 	if (!(b64end = strchr(commentend + 1, '\n')))
1462c706440Stedu 		errx(1, "missing new line after base64 in %s", filename);
147d502e9a0Stedu 	*b64end = '\0';
148e67d6036Stedu 	if (b64_pton(commentend + 1, buf, buflen) != buflen)
1492c706440Stedu 		errx(1, "invalid base64 encoding in %s", filename);
1507d7c2057Stedu 	if (memcmp(buf, PKALG, 2) != 0)
15127f66874Stedu 		errx(1, "unsupported file %s", filename);
15227f66874Stedu 	return b64end - b64 + 1;
15327f66874Stedu }
15427f66874Stedu 
1554215a5deStedu static void
1560ce08f52Stedu readb64file(const char *filename, void *buf, size_t buflen, char *comment)
1574215a5deStedu {
1584215a5deStedu 	char b64[2048];
159f030c3d3Stedu 	int rv, fd;
1604215a5deStedu 
1614215a5deStedu 	fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
162*dba2cf70Stedu 	if ((rv = read(fd, b64, sizeof(b64) - 1)) == -1)
163d59d433dSespie 		err(1, "read from %s", filename);
164d502e9a0Stedu 	b64[rv] = '\0';
1650ce08f52Stedu 	parseb64file(filename, b64, buf, buflen, comment);
166c1ca80caStedu 	explicit_bzero(b64, sizeof(b64));
1674215a5deStedu 	close(fd);
1684215a5deStedu }
1694215a5deStedu 
170eee7f9deStedu static uint8_t *
1714215a5deStedu readmsg(const char *filename, unsigned long long *msglenp)
1724215a5deStedu {
173a30f80dbStedu 	unsigned long long msglen = 0;
174a30f80dbStedu 	uint8_t *msg = NULL;
1754215a5deStedu 	struct stat sb;
176a30f80dbStedu 	ssize_t x, space;
1774215a5deStedu 	int fd;
178ff9a6787Stedu 	const unsigned long long maxmsgsize = 1UL << 30;
1794215a5deStedu 
1804215a5deStedu 	fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
181a30f80dbStedu 	if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode)) {
182ff9a6787Stedu 		if (sb.st_size > maxmsgsize)
183a30f80dbStedu 			errx(1, "msg too large in %s", filename);
184a30f80dbStedu 		space = sb.st_size + 1;
185a30f80dbStedu 	} else {
1869083d0fcStedu 		space = 64 * 1024;
187a30f80dbStedu 	}
188a30f80dbStedu 
1899083d0fcStedu 	msg = xmalloc(space + 1);
190a30f80dbStedu 	while (1) {
191a30f80dbStedu 		if (space == 0) {
192ff9a6787Stedu 			if (msglen * 2 > maxmsgsize)
1939083d0fcStedu 				errx(1, "msg too large in %s", filename);
1949083d0fcStedu 			space = msglen;
195a30f80dbStedu 			if (!(msg = realloc(msg, msglen + space + 1)))
196a30f80dbStedu 				errx(1, "realloc");
197a30f80dbStedu 		}
198a30f80dbStedu 		if ((x = read(fd, msg + msglen, space)) == -1)
199a30f80dbStedu 			err(1, "read from %s", filename);
200a30f80dbStedu 		if (x == 0)
201a30f80dbStedu 			break;
202a30f80dbStedu 		space -= x;
203a30f80dbStedu 		msglen += x;
204a30f80dbStedu 	}
205a30f80dbStedu 
206d502e9a0Stedu 	msg[msglen] = '\0';
2074215a5deStedu 	close(fd);
2084215a5deStedu 
2094215a5deStedu 	*msglenp = msglen;
2104215a5deStedu 	return msg;
2114215a5deStedu }
2124215a5deStedu 
2134215a5deStedu static void
2140ce08f52Stedu writeall(int fd, const void *buf, size_t buflen, const char *filename)
2154215a5deStedu {
2167dec58f2Stedu 	ssize_t x;
2177dec58f2Stedu 
2180ce08f52Stedu 	while (buflen != 0) {
219*dba2cf70Stedu 		if ((x = write(fd, buf, buflen)) == -1)
220d59d433dSespie 			err(1, "write to %s", filename);
2210ce08f52Stedu 		buflen -= x;
222bcd4d29fSespie 		buf = (char *)buf + x;
223bcd4d29fSespie 	}
224d59d433dSespie }
2254215a5deStedu 
2266ffce13fSderaadt #ifndef VERIFYONLY
2274215a5deStedu static void
2284215a5deStedu writeb64file(const char *filename, const char *comment, const void *buf,
2298e162516Sderaadt     size_t buflen, const void *msg, size_t msglen, int oflags, mode_t mode)
2304215a5deStedu {
2314215a5deStedu 	char header[1024];
2324215a5deStedu 	char b64[1024];
2334215a5deStedu 	int fd, rv;
2344215a5deStedu 
2358e162516Sderaadt 	fd = xopen(filename, O_CREAT|oflags|O_NOFOLLOW|O_WRONLY, mode);
236ce3e40b1Sderaadt 	if (snprintf(header, sizeof(header), "%s%s\n",
237ce3e40b1Sderaadt 	    COMMENTHDR, comment) >= sizeof(header))
23841b393a4Stedu 		errx(1, "comment too long");
239d59d433dSespie 	writeall(fd, header, strlen(header), filename);
2400ce08f52Stedu 	if ((rv = b64_ntop(buf, buflen, b64, sizeof(b64)-1)) == -1)
2412c706440Stedu 		errx(1, "base64 encode failed");
242e01c72c4Sespie 	b64[rv++] = '\n';
243d59d433dSespie 	writeall(fd, b64, rv, filename);
244c1ca80caStedu 	explicit_bzero(b64, sizeof(b64));
2450e8a2786Stedu 	if (msg)
2460e8a2786Stedu 		writeall(fd, msg, msglen, filename);
2474215a5deStedu 	close(fd);
2484215a5deStedu }
2494215a5deStedu 
2504215a5deStedu static void
251fdf669dbStedu kdf(uint8_t *salt, size_t saltlen, int rounds, int allowstdin, int confirm,
2524333161aStedu     uint8_t *key, size_t keylen)
2534215a5deStedu {
2544215a5deStedu 	char pass[1024];
2552adf8afcStedu 	int rppflags = RPP_ECHO_OFF;
2564215a5deStedu 
2574215a5deStedu 	if (rounds == 0) {
2584215a5deStedu 		memset(key, 0, keylen);
2594215a5deStedu 		return;
2604215a5deStedu 	}
2614215a5deStedu 
2624333161aStedu 	if (allowstdin && !isatty(STDIN_FILENO))
2632adf8afcStedu 		rppflags |= RPP_STDIN;
2642adf8afcStedu 	if (!readpassphrase("passphrase: ", pass, sizeof(pass), rppflags))
26558ac87a3Stedu 		errx(1, "unable to read passphrase");
2660e5a52c1Stedu 	if (strlen(pass) == 0)
2670e5a52c1Stedu 		errx(1, "please provide a password");
268fdf669dbStedu 	if (confirm && !(rppflags & RPP_STDIN)) {
269fdf669dbStedu 		char pass2[1024];
270fdf669dbStedu 		if (!readpassphrase("confirm passphrase: ", pass2,
271fdf669dbStedu 		    sizeof(pass2), rppflags))
272fdf669dbStedu 			errx(1, "unable to read passphrase");
273fdf669dbStedu 		if (strcmp(pass, pass2) != 0)
274fdf669dbStedu 			errx(1, "passwords don't match");
275fdf669dbStedu 		explicit_bzero(pass2, sizeof(pass2));
276fdf669dbStedu 	}
2774215a5deStedu 	if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key,
2784215a5deStedu 	    keylen, rounds) == -1)
2794215a5deStedu 		errx(1, "bcrypt pbkdf");
280c1ca80caStedu 	explicit_bzero(pass, sizeof(pass));
2814215a5deStedu }
2824215a5deStedu 
2834215a5deStedu static void
2844215a5deStedu signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen,
2854215a5deStedu     uint8_t *sig)
2864215a5deStedu {
2874215a5deStedu 	unsigned long long siglen;
2884215a5deStedu 	uint8_t *sigbuf;
2894215a5deStedu 
2904215a5deStedu 	sigbuf = xmalloc(msglen + SIGBYTES);
2914215a5deStedu 	crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey);
2924215a5deStedu 	memcpy(sig, sigbuf, SIGBYTES);
2934215a5deStedu 	free(sigbuf);
2944215a5deStedu }
2954215a5deStedu 
2964215a5deStedu static void
297bd7b638bStedu generate(const char *pubkeyfile, const char *seckeyfile, int rounds,
298bd7b638bStedu     const char *comment)
2994215a5deStedu {
3004215a5deStedu 	uint8_t digest[SHA512_DIGEST_LENGTH];
3014215a5deStedu 	struct pubkey pubkey;
3024215a5deStedu 	struct enckey enckey;
3034215a5deStedu 	uint8_t xorkey[sizeof(enckey.seckey)];
3041c9c770cStedu 	uint8_t fingerprint[FPLEN];
305bd7b638bStedu 	char commentbuf[COMMENTMAXLEN];
3064215a5deStedu 	SHA2_CTX ctx;
3074215a5deStedu 	int i;
3084215a5deStedu 
3094215a5deStedu 	crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey);
3101c9c770cStedu 	arc4random_buf(fingerprint, sizeof(fingerprint));
3114215a5deStedu 
3124215a5deStedu 	SHA512Init(&ctx);
3134215a5deStedu 	SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
3144215a5deStedu 	SHA512Final(digest, &ctx);
3154215a5deStedu 
3164215a5deStedu 	memcpy(enckey.pkalg, PKALG, 2);
3174215a5deStedu 	memcpy(enckey.kdfalg, KDFALG, 2);
3184215a5deStedu 	enckey.kdfrounds = htonl(rounds);
3191c9c770cStedu 	memcpy(enckey.fingerprint, fingerprint, FPLEN);
3204215a5deStedu 	arc4random_buf(enckey.salt, sizeof(enckey.salt));
321fdf669dbStedu 	kdf(enckey.salt, sizeof(enckey.salt), rounds, 1, 1, xorkey, sizeof(xorkey));
3224215a5deStedu 	memcpy(enckey.checksum, digest, sizeof(enckey.checksum));
3234215a5deStedu 	for (i = 0; i < sizeof(enckey.seckey); i++)
3244215a5deStedu 		enckey.seckey[i] ^= xorkey[i];
325c1ca80caStedu 	explicit_bzero(digest, sizeof(digest));
326c1ca80caStedu 	explicit_bzero(xorkey, sizeof(xorkey));
3274215a5deStedu 
328ce3e40b1Sderaadt 	if (snprintf(commentbuf, sizeof(commentbuf), "%s secret key",
329ce3e40b1Sderaadt 	    comment) >= sizeof(commentbuf))
33041b393a4Stedu 		errx(1, "comment too long");
331bd7b638bStedu 	writeb64file(seckeyfile, commentbuf, &enckey,
3320e8a2786Stedu 	    sizeof(enckey), NULL, 0, O_EXCL, 0600);
333c1ca80caStedu 	explicit_bzero(&enckey, sizeof(enckey));
3344215a5deStedu 
3354215a5deStedu 	memcpy(pubkey.pkalg, PKALG, 2);
3361c9c770cStedu 	memcpy(pubkey.fingerprint, fingerprint, FPLEN);
337ce3e40b1Sderaadt 	if (snprintf(commentbuf, sizeof(commentbuf), "%s public key",
338ce3e40b1Sderaadt 	    comment) >= sizeof(commentbuf))
33941b393a4Stedu 		errx(1, "comment too long");
340bd7b638bStedu 	writeb64file(pubkeyfile, commentbuf, &pubkey,
3410e8a2786Stedu 	    sizeof(pubkey), NULL, 0, O_EXCL, 0666);
3424215a5deStedu }
3434215a5deStedu 
3444215a5deStedu static void
34527f66874Stedu sign(const char *seckeyfile, const char *msgfile, const char *sigfile,
34627f66874Stedu     int embedded)
3474215a5deStedu {
3484215a5deStedu 	struct sig sig;
3494215a5deStedu 	uint8_t digest[SHA512_DIGEST_LENGTH];
3504215a5deStedu 	struct enckey enckey;
3514215a5deStedu 	uint8_t xorkey[sizeof(enckey.seckey)];
3524215a5deStedu 	uint8_t *msg;
3530f212b48Stedu 	char comment[COMMENTMAXLEN], sigcomment[COMMENTMAXLEN];
3540f212b48Stedu 	char *secname;
3554215a5deStedu 	unsigned long long msglen;
3564215a5deStedu 	int i, rounds;
3574215a5deStedu 	SHA2_CTX ctx;
3584215a5deStedu 
3591453d2a0Stedu 	readb64file(seckeyfile, &enckey, sizeof(enckey), comment);
3604215a5deStedu 
3617d7c2057Stedu 	if (memcmp(enckey.kdfalg, KDFALG, 2) != 0)
3624215a5deStedu 		errx(1, "unsupported KDF");
3634215a5deStedu 	rounds = ntohl(enckey.kdfrounds);
3644333161aStedu 	kdf(enckey.salt, sizeof(enckey.salt), rounds, strcmp(msgfile, "-") != 0,
365fdf669dbStedu 	    0, xorkey, sizeof(xorkey));
3664215a5deStedu 	for (i = 0; i < sizeof(enckey.seckey); i++)
3674215a5deStedu 		enckey.seckey[i] ^= xorkey[i];
368c1ca80caStedu 	explicit_bzero(xorkey, sizeof(xorkey));
3694215a5deStedu 	SHA512Init(&ctx);
3704215a5deStedu 	SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
3714215a5deStedu 	SHA512Final(digest, &ctx);
3727d7c2057Stedu 	if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)) != 0)
3734215a5deStedu 	    errx(1, "incorrect passphrase");
374c1ca80caStedu 	explicit_bzero(digest, sizeof(digest));
3754215a5deStedu 
37627f66874Stedu 	msg = readmsg(msgfile, &msglen);
3774215a5deStedu 
3784215a5deStedu 	signmsg(enckey.seckey, msg, msglen, sig.sig);
3791c9c770cStedu 	memcpy(sig.fingerprint, enckey.fingerprint, FPLEN);
380c1ca80caStedu 	explicit_bzero(&enckey, sizeof(enckey));
3814215a5deStedu 
3824215a5deStedu 	memcpy(sig.pkalg, PKALG, 2);
3830f212b48Stedu 	if ((secname = strstr(seckeyfile, ".sec")) && strlen(secname) == 4) {
3840f212b48Stedu 		if (snprintf(sigcomment, sizeof(sigcomment), VERIFYWITH "%.*s.pub",
385265537e5Stedu 		    (int)strlen(seckeyfile) - 4, seckeyfile) >= sizeof(sigcomment))
38641b393a4Stedu 			errx(1, "comment too long");
3870f212b48Stedu 	} else {
388ce3e40b1Sderaadt 		if (snprintf(sigcomment, sizeof(sigcomment), "signature from %s",
389ce3e40b1Sderaadt 		    comment) >= sizeof(sigcomment))
39041b393a4Stedu 			errx(1, "comment too long");
3910f212b48Stedu 	}
39227f66874Stedu 	if (embedded)
3930e8a2786Stedu 		writeb64file(sigfile, sigcomment, &sig, sizeof(sig), msg,
3940e8a2786Stedu 		    msglen, O_TRUNC, 0666);
3950e8a2786Stedu 	else
3960e8a2786Stedu 		writeb64file(sigfile, sigcomment, &sig, sizeof(sig), NULL,
3970e8a2786Stedu 		    0, O_TRUNC, 0666);
3984215a5deStedu 
3994215a5deStedu 	free(msg);
4004215a5deStedu }
40137f70c32Stedu 
40237f70c32Stedu static void
40337f70c32Stedu inspect(const char *seckeyfile, const char *pubkeyfile, const char *sigfile)
40437f70c32Stedu {
40537f70c32Stedu 	struct sig sig;
40637f70c32Stedu 	struct enckey enckey;
40737f70c32Stedu 	struct pubkey pubkey;
40837f70c32Stedu 	char fp[(FPLEN + 2) / 3 * 4 + 1];
40937f70c32Stedu 
41037f70c32Stedu 	if (seckeyfile) {
41137f70c32Stedu 		readb64file(seckeyfile, &enckey, sizeof(enckey), NULL);
41237f70c32Stedu 		b64_ntop(enckey.fingerprint, FPLEN, fp, sizeof(fp));
41337f70c32Stedu 		printf("sec fp: %s\n", fp);
41437f70c32Stedu 	}
41537f70c32Stedu 	if (pubkeyfile) {
41637f70c32Stedu 		readb64file(pubkeyfile, &pubkey, sizeof(pubkey), NULL);
41737f70c32Stedu 		b64_ntop(pubkey.fingerprint, FPLEN, fp, sizeof(fp));
41837f70c32Stedu 		printf("pub fp: %s\n", fp);
41937f70c32Stedu 	}
42037f70c32Stedu 	if (sigfile) {
42137f70c32Stedu 		readb64file(sigfile, &sig, sizeof(sig), NULL);
42237f70c32Stedu 		b64_ntop(sig.fingerprint, FPLEN, fp, sizeof(fp));
42337f70c32Stedu 		printf("sig fp: %s\n", fp);
42437f70c32Stedu 	}
42537f70c32Stedu }
426665ab7d9Stedu #endif
4274215a5deStedu 
4284215a5deStedu static void
42948336e31Stedu verifymsg(struct pubkey *pubkey, uint8_t *msg, unsigned long long msglen,
43048336e31Stedu     struct sig *sig, int quiet)
4311c9c770cStedu {
4321c9c770cStedu 	uint8_t *sigbuf, *dummybuf;
4331c9c770cStedu 	unsigned long long siglen, dummylen;
4341c9c770cStedu 
4357d7c2057Stedu 	if (memcmp(pubkey->fingerprint, sig->fingerprint, FPLEN) != 0)
43648336e31Stedu 		errx(1, "verification failed: checked against wrong key");
43748336e31Stedu 
4381c9c770cStedu 	siglen = SIGBYTES + msglen;
4391c9c770cStedu 	sigbuf = xmalloc(siglen);
4401c9c770cStedu 	dummybuf = xmalloc(siglen);
44148336e31Stedu 	memcpy(sigbuf, sig->sig, SIGBYTES);
4421c9c770cStedu 	memcpy(sigbuf + SIGBYTES, msg, msglen);
4431c9c770cStedu 	if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen,
44448336e31Stedu 	    pubkey->pubkey) == -1)
4451c9c770cStedu 		errx(1, "signature verification failed");
44658559f60Stedu 	if (!quiet)
44758559f60Stedu 		printf("Signature Verified\n");
4481c9c770cStedu 	free(sigbuf);
4491c9c770cStedu 	free(dummybuf);
4501c9c770cStedu }
4511c9c770cStedu 
4521c9c770cStedu static void
45326d9395eStedu readpubkey(const char *pubkeyfile, struct pubkey *pubkey,
45426d9395eStedu     const char *sigcomment)
4554215a5deStedu {
456f65d31e6Stedu 	const char *safepath = "/etc/signify/";
45727f66874Stedu 
4580f212b48Stedu 	if (!pubkeyfile) {
45926d9395eStedu 		if ((pubkeyfile = strstr(sigcomment, VERIFYWITH))) {
4600f212b48Stedu 			pubkeyfile += strlen(VERIFYWITH);
461f65d31e6Stedu 			if (strncmp(pubkeyfile, safepath, strlen(safepath)) != 0 ||
462febc8181Stedu 			    strstr(pubkeyfile, "/../") != NULL)
463b0b02d10Stedu 				errx(1, "untrusted path %s", pubkeyfile);
464b0b02d10Stedu 		} else
4655d586c2bStedu 			usage("must specify pubkey");
4660f212b48Stedu 	}
46726d9395eStedu 	readb64file(pubkeyfile, pubkey, sizeof(*pubkey), NULL);
46826d9395eStedu }
46926d9395eStedu 
47026d9395eStedu static void
47126d9395eStedu verifysimple(const char *pubkeyfile, const char *msgfile, const char *sigfile,
47226d9395eStedu     int quiet)
47326d9395eStedu {
47426d9395eStedu 	char sigcomment[COMMENTMAXLEN];
47526d9395eStedu 	struct sig sig;
47626d9395eStedu 	struct pubkey pubkey;
47726d9395eStedu 	unsigned long long msglen;
47826d9395eStedu 	uint8_t *msg;
47926d9395eStedu 
48026d9395eStedu 	msg = readmsg(msgfile, &msglen);
48126d9395eStedu 
48226d9395eStedu 	readb64file(sigfile, &sig, sizeof(sig), sigcomment);
48326d9395eStedu 	readpubkey(pubkeyfile, &pubkey, sigcomment);
4844215a5deStedu 
48548336e31Stedu 	verifymsg(&pubkey, msg, msglen, &sig, quiet);
486ffebbc61Stedu 
487ffebbc61Stedu 	free(msg);
488ffebbc61Stedu }
489ffebbc61Stedu 
490ffebbc61Stedu static uint8_t *
491ffebbc61Stedu verifyembedded(const char *pubkeyfile, const char *sigfile,
492ffebbc61Stedu     int quiet, unsigned long long *msglenp)
493ffebbc61Stedu {
49426d9395eStedu 	char sigcomment[COMMENTMAXLEN];
495ffebbc61Stedu 	struct sig sig;
496ffebbc61Stedu 	struct pubkey pubkey;
497ffebbc61Stedu 	unsigned long long msglen, siglen;
498ffebbc61Stedu 	uint8_t *msg;
499ffebbc61Stedu 
500ffebbc61Stedu 	msg = readmsg(sigfile, &msglen);
501ffebbc61Stedu 
50226d9395eStedu 	siglen = parseb64file(sigfile, msg, &sig, sizeof(sig), sigcomment);
50326d9395eStedu 	readpubkey(pubkeyfile, &pubkey, sigcomment);
50426d9395eStedu 
505ffebbc61Stedu 	msglen -= siglen;
506ffebbc61Stedu 	memmove(msg, msg + siglen, msglen);
507ffebbc61Stedu 	msg[msglen] = 0;
508ffebbc61Stedu 
509ffebbc61Stedu 	verifymsg(&pubkey, msg, msglen, &sig, quiet);
510ffebbc61Stedu 
511ffebbc61Stedu 	*msglenp = msglen;
512ffebbc61Stedu 	return msg;
513ffebbc61Stedu }
514ffebbc61Stedu 
515ffebbc61Stedu static void
516ffebbc61Stedu verify(const char *pubkeyfile, const char *msgfile, const char *sigfile,
517ffebbc61Stedu     int embedded, int quiet)
518ffebbc61Stedu {
519ffebbc61Stedu 	unsigned long long msglen;
520ffebbc61Stedu 	uint8_t *msg;
521ffebbc61Stedu 	int fd;
522ffebbc61Stedu 
52327f66874Stedu 	if (embedded) {
524ffebbc61Stedu 		msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen);
5259831e76dStedu 		fd = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
52627f66874Stedu 		writeall(fd, msg, msglen, msgfile);
52787e034adSespie 		free(msg);
52827f66874Stedu 		close(fd);
529ffebbc61Stedu 	} else {
530ffebbc61Stedu 		verifysimple(pubkeyfile, msgfile, sigfile, quiet);
53127f66874Stedu 	}
5324215a5deStedu }
5334215a5deStedu 
53458559f60Stedu #ifndef VERIFYONLY
535ce955b95Stedu #define HASHBUFSIZE 224
53658559f60Stedu struct checksum {
53758559f60Stedu 	char file[1024];
538ce955b95Stedu 	char hash[HASHBUFSIZE];
539032a32d9Stedu 	char algo[32];
54058559f60Stedu };
54158559f60Stedu 
542e96025a1Sespie static void *
543e96025a1Sespie ecalloc(size_t s1, size_t s2, void *data)
544e96025a1Sespie {
545db6a8127Stedu 	void *p;
546db6a8127Stedu 
547*dba2cf70Stedu 	if (!(p = calloc(s1, s2)))
548e96025a1Sespie 		err(1, "calloc");
549e96025a1Sespie 	return p;
550e96025a1Sespie }
551e96025a1Sespie 
55258559f60Stedu static void
553e96025a1Sespie efree(void *p, void *data)
554e96025a1Sespie {
555e96025a1Sespie 	free(p);
556e96025a1Sespie }
557e96025a1Sespie 
558e96025a1Sespie static void
559e96025a1Sespie recodehash(char *hash, size_t len)
5600050332bStedu {
561ce955b95Stedu 	uint8_t data[HASHBUFSIZE / 2];
5620050332bStedu 	int i, rv;
5630050332bStedu 
564e96025a1Sespie 	if (strlen(hash) == len)
5650050332bStedu 		return;
5660050332bStedu 	if ((rv = b64_pton(hash, data, sizeof(data))) == -1)
5670050332bStedu 		errx(1, "invalid base64 encoding");
5680050332bStedu 	for (i = 0; i < rv; i++)
569ce955b95Stedu 		snprintf(hash + i * 2, HASHBUFSIZE - i * 2, "%2.2x", data[i]);
5700050332bStedu }
5710050332bStedu 
572e96025a1Sespie static int
573e96025a1Sespie verifychecksum(struct checksum *c, int quiet)
57458559f60Stedu {
575e96025a1Sespie 	char buf[HASHBUFSIZE];
576db6a8127Stedu 
57758559f60Stedu 	if (strcmp(c->algo, "SHA256") == 0) {
578e96025a1Sespie 		recodehash(c->hash, SHA256_DIGEST_STRING_LENGTH-1);
579e96025a1Sespie 		if (!SHA256File(c->file, buf))
580e96025a1Sespie 			return 0;
58158559f60Stedu 	} else if (strcmp(c->algo, "SHA512") == 0) {
582e96025a1Sespie 		recodehash(c->hash, SHA512_DIGEST_STRING_LENGTH-1);
583e96025a1Sespie 		if (!SHA512File(c->file, buf))
584e96025a1Sespie 			return 0;
58558559f60Stedu 	} else {
58658559f60Stedu 		errx(1, "can't handle algorithm %s", c->algo);
58758559f60Stedu 	}
58858559f60Stedu 	if (strcmp(c->hash, buf) != 0) {
589e96025a1Sespie 		return 0;
59058559f60Stedu 	}
59158559f60Stedu 	if (!quiet)
59258559f60Stedu 		printf("%s: OK\n", c->file);
593e96025a1Sespie 	return 1;
59458559f60Stedu }
595e96025a1Sespie 
596e96025a1Sespie static void
597e96025a1Sespie verifychecksums(char *msg, int argc, char **argv, int quiet)
598e96025a1Sespie {
599e96025a1Sespie 	struct ohash_info info = { 0, NULL, ecalloc, efree, NULL };
600e96025a1Sespie 	struct ohash myh;
601e96025a1Sespie 	struct checksum c;
602a5b6fcd7Stedu 	char *e, *line, *endline;
603db6a8127Stedu 	int hasfailed = 0;
604db6a8127Stedu 	int i, rv;
605e96025a1Sespie 	unsigned int slot;
606e96025a1Sespie 
607e96025a1Sespie 	ohash_init(&myh, 6, &info);
608a5b6fcd7Stedu 	if (argc) {
609e96025a1Sespie 		for (i = 0; i < argc; i++) {
610e96025a1Sespie 			slot = ohash_qlookup(&myh, argv[i]);
611e96025a1Sespie 			e = ohash_find(&myh, slot);
612e96025a1Sespie 			if (e == NULL)
613e96025a1Sespie 				ohash_insert(&myh, slot, argv[i]);
614e96025a1Sespie 		}
615e96025a1Sespie 	}
616e96025a1Sespie 
617e96025a1Sespie 	line = msg;
618e96025a1Sespie 	while (line && *line) {
619e96025a1Sespie 		if ((endline = strchr(line, '\n')))
620e96025a1Sespie 			*endline++ = '\0';
621e96025a1Sespie 		rv = sscanf(line, "%31s (%1023s = %223s",
622e96025a1Sespie 		    c.algo, c.file, c.hash);
623e96025a1Sespie 		if (rv != 3 || c.file[0] == 0 || c.file[strlen(c.file)-1] != ')')
624e96025a1Sespie 			errx(1, "unable to parse checksum line %s", line);
625db6a8127Stedu 		c.file[strlen(c.file) - 1] = '\0';
626e96025a1Sespie 		line = endline;
627e96025a1Sespie 		if (argc) {
628e96025a1Sespie 			slot = ohash_qlookup(&myh, c.file);
629e96025a1Sespie 			e = ohash_find(&myh, slot);
630e96025a1Sespie 			if (e != NULL) {
631db6a8127Stedu 				if (verifychecksum(&c, quiet) != 0)
632e96025a1Sespie 					ohash_remove(&myh, slot);
633e96025a1Sespie 			}
634e96025a1Sespie 		} else {
635db6a8127Stedu 			if (verifychecksum(&c, quiet) == 0) {
636a5b6fcd7Stedu 				slot = ohash_qlookup(&myh, c.file);
637a5b6fcd7Stedu 				e = ohash_find(&myh, slot);
638a5b6fcd7Stedu 				if (e == NULL) {
639a5b6fcd7Stedu 					if (!(e = strdup(c.file)))
640a5b6fcd7Stedu 						err(1, "strdup");
641a5b6fcd7Stedu 					ohash_insert(&myh, slot, e);
642a5b6fcd7Stedu 				}
64358559f60Stedu 			}
64458559f60Stedu 		}
645e96025a1Sespie 	}
646e96025a1Sespie 
647a5b6fcd7Stedu 	for (e = ohash_first(&myh, &slot); e != NULL; e = ohash_next(&myh, &slot)) {
648a5b6fcd7Stedu 		fprintf(stderr, "%s: FAIL\n", e);
649e96025a1Sespie 		hasfailed = 1;
650a5b6fcd7Stedu 		if (argc == 0)
651a5b6fcd7Stedu 			free(e);
652e96025a1Sespie 	}
653e96025a1Sespie 	ohash_delete(&myh);
6548972cca3Stedu 	if (hasfailed)
65558559f60Stedu 		exit(1);
65658559f60Stedu }
65758559f60Stedu 
65858559f60Stedu static void
65958559f60Stedu check(const char *pubkeyfile, const char *sigfile, int quiet, int argc,
66058559f60Stedu     char **argv)
66158559f60Stedu {
662ffebbc61Stedu 	unsigned long long msglen;
66358559f60Stedu 	uint8_t *msg;
66458559f60Stedu 
665ffebbc61Stedu 	msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen);
666a40f4206Stedu 	verifychecksums((char *)msg, argc, argv, quiet);
66758559f60Stedu 
668ffebbc61Stedu 	free(msg);
66958559f60Stedu }
67058559f60Stedu #endif
67158559f60Stedu 
6724215a5deStedu int
6734215a5deStedu main(int argc, char **argv)
6744215a5deStedu {
67527f66874Stedu 	const char *pubkeyfile = NULL, *seckeyfile = NULL, *msgfile = NULL,
6764215a5deStedu 	    *sigfile = NULL;
6774215a5deStedu 	char sigfilebuf[1024];
678bd7b638bStedu 	const char *comment = "signify";
6794215a5deStedu 	int ch, rounds;
68027f66874Stedu 	int embedded = 0;
68158559f60Stedu 	int quiet = 0;
682a6bade58Stedu 	enum {
683a6bade58Stedu 		NONE,
68458559f60Stedu 		CHECK,
685a6bade58Stedu 		GENERATE,
68637f70c32Stedu 		INSPECT,
687a6bade58Stedu 		SIGN,
688a6bade58Stedu 		VERIFY
689a6bade58Stedu 	} verb = NONE;
690a6bade58Stedu 
6914215a5deStedu 
6924215a5deStedu 	rounds = 42;
6934215a5deStedu 
69458559f60Stedu 	while ((ch = getopt(argc, argv, "CGISVc:em:np:qs:x:")) != -1) {
6954215a5deStedu 		switch (ch) {
696665ab7d9Stedu #ifndef VERIFYONLY
69758559f60Stedu 		case 'C':
69858559f60Stedu 			if (verb)
69958559f60Stedu 				usage(NULL);
70058559f60Stedu 			verb = CHECK;
70158559f60Stedu 			break;
702a6bade58Stedu 		case 'G':
703a6bade58Stedu 			if (verb)
704f2adbe28Stedu 				usage(NULL);
705a6bade58Stedu 			verb = GENERATE;
7064215a5deStedu 			break;
70737f70c32Stedu 		case 'I':
70837f70c32Stedu 			if (verb)
709f2adbe28Stedu 				usage(NULL);
71037f70c32Stedu 			verb = INSPECT;
71137f70c32Stedu 			break;
7124215a5deStedu 		case 'S':
713a6bade58Stedu 			if (verb)
714f2adbe28Stedu 				usage(NULL);
715a6bade58Stedu 			verb = SIGN;
7164215a5deStedu 			break;
717665ab7d9Stedu #endif
7184215a5deStedu 		case 'V':
719a6bade58Stedu 			if (verb)
720f2adbe28Stedu 				usage(NULL);
721a6bade58Stedu 			verb = VERIFY;
722a6bade58Stedu 			break;
723bd7b638bStedu 		case 'c':
724bd7b638bStedu 			comment = optarg;
725bd7b638bStedu 			break;
72627f66874Stedu 		case 'e':
72727f66874Stedu 			embedded = 1;
72827f66874Stedu 			break;
729f2adbe28Stedu 		case 'm':
730f2adbe28Stedu 			msgfile = optarg;
731f2adbe28Stedu 			break;
732a6bade58Stedu 		case 'n':
733a6bade58Stedu 			rounds = 0;
734a6bade58Stedu 			break;
735a6bade58Stedu 		case 'p':
736a6bade58Stedu 			pubkeyfile = optarg;
737a6bade58Stedu 			break;
73858559f60Stedu 		case 'q':
73958559f60Stedu 			quiet = 1;
74058559f60Stedu 			break;
741a6bade58Stedu 		case 's':
742a6bade58Stedu 			seckeyfile = optarg;
7434215a5deStedu 			break;
744f2adbe28Stedu 		case 'x':
745f2adbe28Stedu 			sigfile = optarg;
746f2adbe28Stedu 			break;
7474215a5deStedu 		default:
748f2adbe28Stedu 			usage(NULL);
7494215a5deStedu 			break;
7504215a5deStedu 		}
7514215a5deStedu 	}
752bcc39c47Stedu 	argc -= optind;
75342efb9f2Sespie 	argv += optind;
75442efb9f2Sespie 
75558559f60Stedu #ifndef VERIFYONLY
75658559f60Stedu 	if (verb == CHECK) {
7579fbe12ffStedu 		if (!sigfile)
7589fbe12ffStedu 			usage("must specify sigfile");
75958559f60Stedu 		check(pubkeyfile, sigfile, quiet, argc, argv);
76058559f60Stedu 		return 0;
76158559f60Stedu 	}
76258559f60Stedu #endif
76358559f60Stedu 
764f2adbe28Stedu 	if (argc != 0)
765f2adbe28Stedu 		usage(NULL);
766f2adbe28Stedu 
7679127dd95Stedu 	if (!sigfile && msgfile) {
768f2adbe28Stedu 		if (strcmp(msgfile, "-") == 0)
7695d586c2bStedu 			usage("must specify sigfile with - message");
7704215a5deStedu 		if (snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig",
77127f66874Stedu 		    msgfile) >= sizeof(sigfilebuf))
7724215a5deStedu 			errx(1, "path too long");
7734215a5deStedu 		sigfile = sigfilebuf;
7744215a5deStedu 	}
7759127dd95Stedu 
7769127dd95Stedu 	switch (verb) {
777665ab7d9Stedu #ifndef VERIFYONLY
7789127dd95Stedu 	case GENERATE:
7799127dd95Stedu 		if (!pubkeyfile || !seckeyfile)
7805d586c2bStedu 			usage("must specify pubkey and seckey");
7819127dd95Stedu 		generate(pubkeyfile, seckeyfile, rounds, comment);
7829127dd95Stedu 		break;
7839127dd95Stedu 	case INSPECT:
7849127dd95Stedu 		inspect(seckeyfile, pubkeyfile, sigfile);
7859127dd95Stedu 		break;
7869127dd95Stedu 	case SIGN:
7879127dd95Stedu 		if (!msgfile || !seckeyfile)
7885d586c2bStedu 			usage("must specify message and seckey");
78927f66874Stedu 		sign(seckeyfile, msgfile, sigfile, embedded);
7909127dd95Stedu 		break;
791665ab7d9Stedu #endif
7929127dd95Stedu 	case VERIFY:
7930f212b48Stedu 		if (!msgfile)
7945d586c2bStedu 			usage("must specify message");
79558559f60Stedu 		verify(pubkeyfile, msgfile, sigfile, embedded, quiet);
7969127dd95Stedu 		break;
7979127dd95Stedu 	default:
7989127dd95Stedu 		usage(NULL);
7999127dd95Stedu 		break;
80042efb9f2Sespie 	}
80142efb9f2Sespie 
8024215a5deStedu 	return 0;
8034215a5deStedu }
804