xref: /openbsd/usr.bin/signify/signify.c (revision a40f4206)
1*a40f4206Stedu /* $OpenBSD: signify.c,v 1.45 2014/03/05 23:03:19 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
464215a5deStedu 
474215a5deStedu struct enckey {
484215a5deStedu 	uint8_t pkalg[2];
494215a5deStedu 	uint8_t kdfalg[2];
504215a5deStedu 	uint32_t kdfrounds;
514215a5deStedu 	uint8_t salt[16];
524215a5deStedu 	uint8_t checksum[8];
531c9c770cStedu 	uint8_t fingerprint[FPLEN];
544215a5deStedu 	uint8_t seckey[SECRETBYTES];
554215a5deStedu };
564215a5deStedu 
574215a5deStedu struct pubkey {
584215a5deStedu 	uint8_t pkalg[2];
591c9c770cStedu 	uint8_t fingerprint[FPLEN];
604215a5deStedu 	uint8_t pubkey[PUBLICBYTES];
614215a5deStedu };
624215a5deStedu 
634215a5deStedu struct sig {
644215a5deStedu 	uint8_t pkalg[2];
651c9c770cStedu 	uint8_t fingerprint[FPLEN];
664215a5deStedu 	uint8_t sig[SIGBYTES];
674215a5deStedu };
684215a5deStedu 
694215a5deStedu extern char *__progname;
704215a5deStedu 
714215a5deStedu static void
72f2adbe28Stedu usage(const char *error)
734215a5deStedu {
74f2adbe28Stedu 	if (error)
75f2adbe28Stedu 		fprintf(stderr, "%s\n", error);
7642efb9f2Sespie 	fprintf(stderr, "usage:"
7735e4c3d2Sespie #ifndef VERIFYONLY
7858559f60Stedu 	    "\t%1$s -C [-q] -p pubkey -x sigfile [files...]\n"
79f2adbe28Stedu 	    "\t%1$s -G [-n] [-c comment] -p pubkey -s seckey\n"
80f2adbe28Stedu 	    "\t%1$s -I [-p pubkey] [-s seckey] [-x sigfile]\n"
81f2adbe28Stedu 	    "\t%1$s -S [-e] [-x sigfile] -s seckey -m message\n"
8235e4c3d2Sespie #endif
8358559f60Stedu 	    "\t%1$s -V [-eq] [-x sigfile] -p pubkey -m message\n",
8435e4c3d2Sespie 	    __progname);
854215a5deStedu 	exit(1);
864215a5deStedu }
874215a5deStedu 
884215a5deStedu static int
894215a5deStedu xopen(const char *fname, int flags, mode_t mode)
904215a5deStedu {
914215a5deStedu 	int fd;
924215a5deStedu 
93f2adbe28Stedu 	if (strcmp(fname, "-") == 0) {
94f2adbe28Stedu 		if ((flags & O_WRONLY))
95f2adbe28Stedu 			fd = dup(STDOUT_FILENO);
96f2adbe28Stedu 		else
97f2adbe28Stedu 			fd = dup(STDIN_FILENO);
98f2adbe28Stedu 		if (fd == -1)
99f2adbe28Stedu 			err(1, "dup failed");
100f2adbe28Stedu 	} else {
1014215a5deStedu 		fd = open(fname, flags, mode);
1024215a5deStedu 		if (fd == -1)
103f2adbe28Stedu 			err(1, "can't open %s for %s", fname,
104f2adbe28Stedu 			    (flags & O_WRONLY) ? "writing" : "reading");
105f2adbe28Stedu 	}
1064215a5deStedu 	return fd;
1074215a5deStedu }
1084215a5deStedu 
1094215a5deStedu static void *
1104215a5deStedu xmalloc(size_t len)
1114215a5deStedu {
1124215a5deStedu 	void *p;
1134215a5deStedu 
1144215a5deStedu 	p = malloc(len);
1154215a5deStedu 	if (!p)
1164215a5deStedu 		err(1, "malloc %zu", len);
1174215a5deStedu 	return p;
1184215a5deStedu }
1194215a5deStedu 
1204215a5deStedu static void
121d59d433dSespie readall(int fd, void *buf, size_t len, const char *filename)
1224215a5deStedu {
1237dec58f2Stedu 	ssize_t x;
1247dec58f2Stedu 
125bcd4d29fSespie 	while (len != 0) {
1267dec58f2Stedu 		x = read(fd, buf, len);
127bcd4d29fSespie 		if (x == -1)
128d59d433dSespie 			err(1, "read from %s", filename);
129bcd4d29fSespie 		len -= x;
130bcd4d29fSespie 		buf = (char*)buf + x;
131bcd4d29fSespie 	}
132d59d433dSespie }
1334215a5deStedu 
13427f66874Stedu static size_t
1351453d2a0Stedu parseb64file(const char *filename, char *b64, void *buf, size_t len,
1361453d2a0Stedu     char *comment)
13727f66874Stedu {
13827f66874Stedu 	int rv;
13927f66874Stedu 	char *commentend, *b64end;
14027f66874Stedu 
14127f66874Stedu 	commentend = strchr(b64, '\n');
14227f66874Stedu 	if (!commentend || commentend - b64 <= COMMENTHDRLEN ||
14327f66874Stedu 	    memcmp(b64, COMMENTHDR, COMMENTHDRLEN))
14427f66874Stedu 		errx(1, "invalid comment in %s; must start with '%s'",
14527f66874Stedu 		    filename, COMMENTHDR);
1461453d2a0Stedu 	*commentend = 0;
147ce3e40b1Sderaadt 	if (comment) {
148ce3e40b1Sderaadt 		if (strlcpy(comment, b64 + COMMENTHDRLEN,
149ce3e40b1Sderaadt 		    COMMENTMAXLEN) >= COMMENTMAXLEN)
150ce3e40b1Sderaadt 			err(1, "comment too long");
151ce3e40b1Sderaadt 	}
15227f66874Stedu 	b64end = strchr(commentend + 1, '\n');
15327f66874Stedu 	if (!b64end)
15427f66874Stedu 		errx(1, "missing new line after b64 in %s", filename);
15527f66874Stedu 	*b64end = 0;
15627f66874Stedu 	rv = b64_pton(commentend + 1, buf, len);
15727f66874Stedu 	if (rv != len)
15827f66874Stedu 		errx(1, "invalid b64 encoding in %s", filename);
15927f66874Stedu 	if (memcmp(buf, PKALG, 2))
16027f66874Stedu 		errx(1, "unsupported file %s", filename);
16127f66874Stedu 	return b64end - b64 + 1;
16227f66874Stedu }
16327f66874Stedu 
1644215a5deStedu static void
1651453d2a0Stedu readb64file(const char *filename, void *buf, size_t len, char *comment)
1664215a5deStedu {
1674215a5deStedu 	char b64[2048];
168f030c3d3Stedu 	int rv, fd;
1694215a5deStedu 
1704215a5deStedu 	fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
1714215a5deStedu 	memset(b64, 0, sizeof(b64));
1724215a5deStedu 	rv = read(fd, b64, sizeof(b64) - 1);
1734215a5deStedu 	if (rv == -1)
174d59d433dSespie 		err(1, "read from %s", filename);
1751453d2a0Stedu 	parseb64file(filename, b64, buf, len, comment);
176c1ca80caStedu 	explicit_bzero(b64, sizeof(b64));
1774215a5deStedu 	close(fd);
1784215a5deStedu }
1794215a5deStedu 
180eee7f9deStedu static uint8_t *
1814215a5deStedu readmsg(const char *filename, unsigned long long *msglenp)
1824215a5deStedu {
1834215a5deStedu 	unsigned long long msglen;
1844215a5deStedu 	uint8_t *msg;
1854215a5deStedu 	struct stat sb;
1864215a5deStedu 	int fd;
1874215a5deStedu 
1884215a5deStedu 	fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0);
1897dcdad2dSlteo 	if (fstat(fd, &sb) == -1)
1907dcdad2dSlteo 		err(1, "fstat on %s", filename);
1919831e76dStedu 	if (!S_ISREG(sb.st_mode))
1929831e76dStedu 		errx(1, "%s must be a regular file", filename);
1934215a5deStedu 	msglen = sb.st_size;
1944215a5deStedu 	if (msglen > (1UL << 30))
1954215a5deStedu 		errx(1, "msg too large in %s", filename);
196*a40f4206Stedu 	msg = xmalloc(msglen + 1);
197d59d433dSespie 	readall(fd, msg, msglen, filename);
198*a40f4206Stedu 	msg[msglen] = 0;
1994215a5deStedu 	close(fd);
2004215a5deStedu 
2014215a5deStedu 	*msglenp = msglen;
2024215a5deStedu 	return msg;
2034215a5deStedu }
2044215a5deStedu 
2054215a5deStedu static void
206d59d433dSespie writeall(int fd, const void *buf, size_t len, const char *filename)
2074215a5deStedu {
2087dec58f2Stedu 	ssize_t x;
2097dec58f2Stedu 
210bcd4d29fSespie 	while (len != 0) {
2117dec58f2Stedu 		x = write(fd, buf, len);
212bcd4d29fSespie 		if (x == -1)
213d59d433dSespie 			err(1, "write to %s", filename);
214bcd4d29fSespie 		len -= x;
215bcd4d29fSespie 		buf = (char*)buf + x;
216bcd4d29fSespie 	}
217d59d433dSespie }
2184215a5deStedu 
2196ffce13fSderaadt #ifndef VERIFYONLY
2204215a5deStedu static void
22127f66874Stedu appendall(const char *filename, const void *buf, size_t len)
22227f66874Stedu {
22327f66874Stedu 	int fd;
22427f66874Stedu 
2259831e76dStedu 	fd = xopen(filename, O_NOFOLLOW | O_WRONLY | O_APPEND, 0);
22627f66874Stedu 	writeall(fd, buf, len, filename);
22727f66874Stedu 	close(fd);
22827f66874Stedu }
22927f66874Stedu 
23027f66874Stedu static void
2314215a5deStedu writeb64file(const char *filename, const char *comment, const void *buf,
232d609a234Sespie     size_t len, int flags, mode_t mode)
2334215a5deStedu {
2344215a5deStedu 	char header[1024];
2354215a5deStedu 	char b64[1024];
2364215a5deStedu 	int fd, rv;
2374215a5deStedu 
2389831e76dStedu 	fd = xopen(filename, O_CREAT|flags|O_NOFOLLOW|O_WRONLY, mode);
239ce3e40b1Sderaadt 	if (snprintf(header, sizeof(header), "%s%s\n",
240ce3e40b1Sderaadt 	    COMMENTHDR, comment) >= sizeof(header))
241ce3e40b1Sderaadt 		err(1, "comment too long");
242d59d433dSespie 	writeall(fd, header, strlen(header), filename);
243e01c72c4Sespie 	if ((rv = b64_ntop(buf, len, b64, sizeof(b64)-1)) == -1)
2444215a5deStedu 		errx(1, "b64 encode failed");
245e01c72c4Sespie 	b64[rv++] = '\n';
246d59d433dSespie 	writeall(fd, b64, rv, filename);
247c1ca80caStedu 	explicit_bzero(b64, sizeof(b64));
2484215a5deStedu 	close(fd);
2494215a5deStedu }
2504215a5deStedu 
2514215a5deStedu static void
2524215a5deStedu kdf(uint8_t *salt, size_t saltlen, int rounds, uint8_t *key, size_t keylen)
2534215a5deStedu {
2544215a5deStedu 	char pass[1024];
2554215a5deStedu 
2564215a5deStedu 	if (rounds == 0) {
2574215a5deStedu 		memset(key, 0, keylen);
2584215a5deStedu 		return;
2594215a5deStedu 	}
2604215a5deStedu 
26158ac87a3Stedu 	if (!readpassphrase("passphrase: ", pass, sizeof(pass), RPP_ECHO_OFF))
26258ac87a3Stedu 		errx(1, "unable to read passphrase");
2630e5a52c1Stedu 	if (strlen(pass) == 0)
2640e5a52c1Stedu 		errx(1, "please provide a password");
2654215a5deStedu 	if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key,
2664215a5deStedu 	    keylen, rounds) == -1)
2674215a5deStedu 		errx(1, "bcrypt pbkdf");
268c1ca80caStedu 	explicit_bzero(pass, sizeof(pass));
2694215a5deStedu }
2704215a5deStedu 
2714215a5deStedu static void
2724215a5deStedu signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen,
2734215a5deStedu     uint8_t *sig)
2744215a5deStedu {
2754215a5deStedu 	unsigned long long siglen;
2764215a5deStedu 	uint8_t *sigbuf;
2774215a5deStedu 
2784215a5deStedu 	sigbuf = xmalloc(msglen + SIGBYTES);
2794215a5deStedu 	crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey);
2804215a5deStedu 	memcpy(sig, sigbuf, SIGBYTES);
2814215a5deStedu 	free(sigbuf);
2824215a5deStedu }
2834215a5deStedu 
2844215a5deStedu static void
285bd7b638bStedu generate(const char *pubkeyfile, const char *seckeyfile, int rounds,
286bd7b638bStedu     const char *comment)
2874215a5deStedu {
2884215a5deStedu 	uint8_t digest[SHA512_DIGEST_LENGTH];
2894215a5deStedu 	struct pubkey pubkey;
2904215a5deStedu 	struct enckey enckey;
2914215a5deStedu 	uint8_t xorkey[sizeof(enckey.seckey)];
2921c9c770cStedu 	uint8_t fingerprint[FPLEN];
293bd7b638bStedu 	char commentbuf[COMMENTMAXLEN];
2944215a5deStedu 	SHA2_CTX ctx;
2954215a5deStedu 	int i;
2964215a5deStedu 
2974215a5deStedu 	crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey);
2981c9c770cStedu 	arc4random_buf(fingerprint, sizeof(fingerprint));
2994215a5deStedu 
3004215a5deStedu 	SHA512Init(&ctx);
3014215a5deStedu 	SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
3024215a5deStedu 	SHA512Final(digest, &ctx);
3034215a5deStedu 
3044215a5deStedu 	memcpy(enckey.pkalg, PKALG, 2);
3054215a5deStedu 	memcpy(enckey.kdfalg, KDFALG, 2);
3064215a5deStedu 	enckey.kdfrounds = htonl(rounds);
3071c9c770cStedu 	memcpy(enckey.fingerprint, fingerprint, FPLEN);
3084215a5deStedu 	arc4random_buf(enckey.salt, sizeof(enckey.salt));
3094215a5deStedu 	kdf(enckey.salt, sizeof(enckey.salt), rounds, xorkey, sizeof(xorkey));
3104215a5deStedu 	memcpy(enckey.checksum, digest, sizeof(enckey.checksum));
3114215a5deStedu 	for (i = 0; i < sizeof(enckey.seckey); i++)
3124215a5deStedu 		enckey.seckey[i] ^= xorkey[i];
313c1ca80caStedu 	explicit_bzero(digest, sizeof(digest));
314c1ca80caStedu 	explicit_bzero(xorkey, sizeof(xorkey));
3154215a5deStedu 
316ce3e40b1Sderaadt 	if (snprintf(commentbuf, sizeof(commentbuf), "%s secret key",
317ce3e40b1Sderaadt 	    comment) >= sizeof(commentbuf))
318ce3e40b1Sderaadt 		err(1, "comment too long");
319bd7b638bStedu 	writeb64file(seckeyfile, commentbuf, &enckey,
320d609a234Sespie 	    sizeof(enckey), O_EXCL, 0600);
321c1ca80caStedu 	explicit_bzero(&enckey, sizeof(enckey));
3224215a5deStedu 
3234215a5deStedu 	memcpy(pubkey.pkalg, PKALG, 2);
3241c9c770cStedu 	memcpy(pubkey.fingerprint, fingerprint, FPLEN);
325ce3e40b1Sderaadt 	if (snprintf(commentbuf, sizeof(commentbuf), "%s public key",
326ce3e40b1Sderaadt 	    comment) >= sizeof(commentbuf))
327ce3e40b1Sderaadt 		err(1, "comment too long");
328bd7b638bStedu 	writeb64file(pubkeyfile, commentbuf, &pubkey,
329d609a234Sespie 	    sizeof(pubkey), O_EXCL, 0666);
3304215a5deStedu }
3314215a5deStedu 
3324215a5deStedu static void
33327f66874Stedu sign(const char *seckeyfile, const char *msgfile, const char *sigfile,
33427f66874Stedu     int embedded)
3354215a5deStedu {
3364215a5deStedu 	struct sig sig;
3374215a5deStedu 	uint8_t digest[SHA512_DIGEST_LENGTH];
3384215a5deStedu 	struct enckey enckey;
3394215a5deStedu 	uint8_t xorkey[sizeof(enckey.seckey)];
3404215a5deStedu 	uint8_t *msg;
3411453d2a0Stedu 	char comment[COMMENTMAXLEN], sigcomment[1024];
3424215a5deStedu 	unsigned long long msglen;
3434215a5deStedu 	int i, rounds;
3444215a5deStedu 	SHA2_CTX ctx;
3454215a5deStedu 
3461453d2a0Stedu 	readb64file(seckeyfile, &enckey, sizeof(enckey), comment);
3474215a5deStedu 
3484215a5deStedu 	if (memcmp(enckey.kdfalg, KDFALG, 2))
3494215a5deStedu 		errx(1, "unsupported KDF");
3504215a5deStedu 	rounds = ntohl(enckey.kdfrounds);
3514215a5deStedu 	kdf(enckey.salt, sizeof(enckey.salt), rounds, xorkey, sizeof(xorkey));
3524215a5deStedu 	for (i = 0; i < sizeof(enckey.seckey); i++)
3534215a5deStedu 		enckey.seckey[i] ^= xorkey[i];
354c1ca80caStedu 	explicit_bzero(xorkey, sizeof(xorkey));
3554215a5deStedu 	SHA512Init(&ctx);
3564215a5deStedu 	SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey));
3574215a5deStedu 	SHA512Final(digest, &ctx);
3584215a5deStedu 	if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)))
3594215a5deStedu 	    errx(1, "incorrect passphrase");
360c1ca80caStedu 	explicit_bzero(digest, sizeof(digest));
3614215a5deStedu 
36227f66874Stedu 	msg = readmsg(msgfile, &msglen);
3634215a5deStedu 
3644215a5deStedu 	signmsg(enckey.seckey, msg, msglen, sig.sig);
3651c9c770cStedu 	memcpy(sig.fingerprint, enckey.fingerprint, FPLEN);
366c1ca80caStedu 	explicit_bzero(&enckey, sizeof(enckey));
3674215a5deStedu 
3684215a5deStedu 	memcpy(sig.pkalg, PKALG, 2);
369ce3e40b1Sderaadt 	if (snprintf(sigcomment, sizeof(sigcomment), "signature from %s",
370ce3e40b1Sderaadt 	    comment) >= sizeof(sigcomment))
371ce3e40b1Sderaadt 		err(1, "comment too long");
372d609a234Sespie 	writeb64file(sigfile, sigcomment, &sig, sizeof(sig), O_TRUNC, 0666);
37327f66874Stedu 	if (embedded)
37427f66874Stedu 		appendall(sigfile, msg, msglen);
3754215a5deStedu 
3764215a5deStedu 	free(msg);
3774215a5deStedu }
37837f70c32Stedu 
37937f70c32Stedu static void
38037f70c32Stedu inspect(const char *seckeyfile, const char *pubkeyfile, const char *sigfile)
38137f70c32Stedu {
38237f70c32Stedu 	struct sig sig;
38337f70c32Stedu 	struct enckey enckey;
38437f70c32Stedu 	struct pubkey pubkey;
38537f70c32Stedu 	char fp[(FPLEN + 2) / 3 * 4 + 1];
38637f70c32Stedu 
38737f70c32Stedu 	if (seckeyfile) {
38837f70c32Stedu 		readb64file(seckeyfile, &enckey, sizeof(enckey), NULL);
38937f70c32Stedu 		b64_ntop(enckey.fingerprint, FPLEN, fp, sizeof(fp));
39037f70c32Stedu 		printf("sec fp: %s\n", fp);
39137f70c32Stedu 	}
39237f70c32Stedu 	if (pubkeyfile) {
39337f70c32Stedu 		readb64file(pubkeyfile, &pubkey, sizeof(pubkey), NULL);
39437f70c32Stedu 		b64_ntop(pubkey.fingerprint, FPLEN, fp, sizeof(fp));
39537f70c32Stedu 		printf("pub fp: %s\n", fp);
39637f70c32Stedu 	}
39737f70c32Stedu 	if (sigfile) {
39837f70c32Stedu 		readb64file(sigfile, &sig, sizeof(sig), NULL);
39937f70c32Stedu 		b64_ntop(sig.fingerprint, FPLEN, fp, sizeof(fp));
40037f70c32Stedu 		printf("sig fp: %s\n", fp);
40137f70c32Stedu 	}
40237f70c32Stedu }
403665ab7d9Stedu #endif
4044215a5deStedu 
4054215a5deStedu static void
4061c9c770cStedu verifymsg(uint8_t *pubkey, uint8_t *msg, unsigned long long msglen,
40758559f60Stedu     uint8_t *sig, int quiet)
4081c9c770cStedu {
4091c9c770cStedu 	uint8_t *sigbuf, *dummybuf;
4101c9c770cStedu 	unsigned long long siglen, dummylen;
4111c9c770cStedu 
4121c9c770cStedu 	siglen = SIGBYTES + msglen;
4131c9c770cStedu 	sigbuf = xmalloc(siglen);
4141c9c770cStedu 	dummybuf = xmalloc(siglen);
4151c9c770cStedu 	memcpy(sigbuf, sig, SIGBYTES);
4161c9c770cStedu 	memcpy(sigbuf + SIGBYTES, msg, msglen);
4171c9c770cStedu 	if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen,
4181c9c770cStedu 	    pubkey) == -1)
4191c9c770cStedu 		errx(1, "signature verification failed");
42058559f60Stedu 	if (!quiet)
42158559f60Stedu 		printf("Signature Verified\n");
4221c9c770cStedu 	free(sigbuf);
4231c9c770cStedu 	free(dummybuf);
4241c9c770cStedu }
4251c9c770cStedu 
4261c9c770cStedu 
4271c9c770cStedu static void
42827f66874Stedu verify(const char *pubkeyfile, const char *msgfile, const char *sigfile,
42958559f60Stedu     int embedded, int quiet)
4304215a5deStedu {
4314215a5deStedu 	struct sig sig;
4324215a5deStedu 	struct pubkey pubkey;
43327f66874Stedu 	unsigned long long msglen, siglen = 0;
4344215a5deStedu 	uint8_t *msg;
43527f66874Stedu 	int fd;
43627f66874Stedu 
43727f66874Stedu 	msg = readmsg(embedded ? sigfile : msgfile, &msglen);
4384215a5deStedu 
4391453d2a0Stedu 	readb64file(pubkeyfile, &pubkey, sizeof(pubkey), NULL);
44027f66874Stedu 	if (embedded) {
4411453d2a0Stedu 		siglen = parseb64file(sigfile, msg, &sig, sizeof(sig), NULL);
44227f66874Stedu 		msg += siglen;
44327f66874Stedu 		msglen -= siglen;
44427f66874Stedu 	} else {
4451453d2a0Stedu 		readb64file(sigfile, &sig, sizeof(sig), NULL);
44627f66874Stedu 	}
4474215a5deStedu 
44837f70c32Stedu 	if (memcmp(pubkey.fingerprint, sig.fingerprint, FPLEN)) {
44937f70c32Stedu #ifndef VERIFYONLY
45037f70c32Stedu 		inspect(NULL, pubkeyfile, sigfile);
45137f70c32Stedu #endif
4521c9c770cStedu 		errx(1, "verification failed: checked against wrong key");
45337f70c32Stedu 	}
4541c9c770cStedu 
45558559f60Stedu 	verifymsg(pubkey.pubkey, msg, msglen, sig.sig, quiet);
45627f66874Stedu 	if (embedded) {
4579831e76dStedu 		fd = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
45827f66874Stedu 		writeall(fd, msg, msglen, msgfile);
45927f66874Stedu 		close(fd);
46027f66874Stedu 	}
46127f66874Stedu 
46227f66874Stedu 	free(msg - siglen);
4634215a5deStedu }
4644215a5deStedu 
46558559f60Stedu #ifndef VERIFYONLY
46658559f60Stedu struct checksum {
46758559f60Stedu 	char file[1024];
46858559f60Stedu 	char hash[1024];
46958559f60Stedu 	char algo[256];
47058559f60Stedu };
47158559f60Stedu 
47258559f60Stedu static void
473*a40f4206Stedu verifychecksums(char *msg, int argc, char **argv, int quiet)
47458559f60Stedu {
47558559f60Stedu 	char buf[1024];
476*a40f4206Stedu 	char *line, *endline;
47758559f60Stedu 	struct checksum *checksums = NULL, *c = NULL;
47858559f60Stedu 	int nchecksums = 0;
4798972cca3Stedu 	int i, j, uselist, count, hasfailed;
48058559f60Stedu 	int *failures;
48158559f60Stedu 
482*a40f4206Stedu 	line = msg;
48358559f60Stedu 	while (line && *line) {
48458559f60Stedu 		if (!(checksums = realloc(checksums,
48558559f60Stedu 		    sizeof(*c) * (nchecksums + 1))))
48658559f60Stedu 			err(1, "realloc");
48758559f60Stedu 		c = &checksums[nchecksums++];
48858559f60Stedu 		if ((endline = strchr(line, '\n')))
48958559f60Stedu 			*endline++ = 0;
49058559f60Stedu 		if (sscanf(line, "%255s %1023s = %1023s",
49158559f60Stedu 		    c->algo, buf, c->hash) != 3 ||
49258559f60Stedu 		    buf[0] != '(' || buf[strlen(buf) - 1] != ')')
49358559f60Stedu 			errx(1, "unable to parse checksum line %s", line);
49458559f60Stedu 		buf[strlen(buf) - 1] = 0;
49558559f60Stedu 		strlcpy(c->file, buf + 1, sizeof(c->file));
49658559f60Stedu 		line = endline;
49758559f60Stedu 	}
49858559f60Stedu 
49958559f60Stedu 	if (argc) {
50058559f60Stedu 		uselist = 0;
50158559f60Stedu 		count = argc;
50258559f60Stedu 	} else {
50358559f60Stedu 		uselist = 1;
50458559f60Stedu 		count = nchecksums;
50558559f60Stedu 	}
5068972cca3Stedu 	if (!(failures = calloc(count, sizeof(int))))
5078972cca3Stedu 		err(1, "calloc");
50858559f60Stedu 	for (i = 0; i < count; i++) {
50958559f60Stedu 		if (uselist) {
51058559f60Stedu 			c = &checksums[i];
51158559f60Stedu 		} else {
51258559f60Stedu 			for (j = 0; j < nchecksums; j++) {
51358559f60Stedu 				c = &checksums[j];
51458559f60Stedu 				if (strcmp(c->file, argv[i]) == 0)
51558559f60Stedu 					break;
51658559f60Stedu 			}
51758559f60Stedu 			if (j == nchecksums) {
51858559f60Stedu 				failures[i] = 1;
51958559f60Stedu 				continue;
52058559f60Stedu 			}
52158559f60Stedu 		}
52258559f60Stedu 
52358559f60Stedu 		if (strcmp(c->algo, "SHA256") == 0) {
52458559f60Stedu 			if (!SHA256File(c->file, buf)) {
52558559f60Stedu 				failures[i] = 1;
52658559f60Stedu 				continue;
52758559f60Stedu 			}
52858559f60Stedu 		} else if (strcmp(c->algo, "SHA512") == 0) {
52958559f60Stedu 			if (!SHA512File(c->file, buf)) {
53058559f60Stedu 				failures[i] = 1;
53158559f60Stedu 				continue;
53258559f60Stedu 			}
53358559f60Stedu 		} else {
53458559f60Stedu 			errx(1, "can't handle algorithm %s", c->algo);
53558559f60Stedu 		}
53658559f60Stedu 		if (strcmp(c->hash, buf) != 0) {
53758559f60Stedu 			failures[i] = 1;
53858559f60Stedu 			continue;
53958559f60Stedu 		}
54058559f60Stedu 		if (!quiet)
54158559f60Stedu 			printf("%s: OK\n", c->file);
54258559f60Stedu 	}
5438972cca3Stedu 	hasfailed = 0;
54458559f60Stedu 	for (i = 0; i < count; i++) {
54558559f60Stedu 		if (failures[i]) {
54658559f60Stedu 			fprintf(stderr, "%s: FAIL\n",
54758559f60Stedu 			    uselist ? checksums[i].file : argv[i]);
5488972cca3Stedu 			hasfailed = 1;
54958559f60Stedu 		}
55058559f60Stedu 	}
5518972cca3Stedu 	if (hasfailed)
55258559f60Stedu 		exit(1);
55358559f60Stedu 	free(checksums);
5548972cca3Stedu 	free(failures);
55558559f60Stedu }
55658559f60Stedu 
55758559f60Stedu static void
55858559f60Stedu check(const char *pubkeyfile, const char *sigfile, int quiet, int argc,
55958559f60Stedu     char **argv)
56058559f60Stedu {
56158559f60Stedu 	struct sig sig;
56258559f60Stedu 	struct pubkey pubkey;
56358559f60Stedu 	unsigned long long msglen, siglen;
56458559f60Stedu 	uint8_t *msg;
56558559f60Stedu 
56658559f60Stedu 	msg = readmsg(sigfile, &msglen);
56758559f60Stedu 
56858559f60Stedu 	readb64file(pubkeyfile, &pubkey, sizeof(pubkey), NULL);
56958559f60Stedu 	siglen = parseb64file(sigfile, msg, &sig, sizeof(sig), NULL);
57058559f60Stedu 	msg += siglen;
57158559f60Stedu 	msglen -= siglen;
57258559f60Stedu 
57358559f60Stedu 	if (memcmp(pubkey.fingerprint, sig.fingerprint, FPLEN)) {
57458559f60Stedu #ifndef VERIFYONLY
57558559f60Stedu 		inspect(NULL, pubkeyfile, sigfile);
57658559f60Stedu #endif
57758559f60Stedu 		errx(1, "verification failed: checked against wrong key");
57858559f60Stedu 	}
57958559f60Stedu 
58058559f60Stedu 	verifymsg(pubkey.pubkey, msg, msglen, sig.sig, quiet);
581*a40f4206Stedu 	verifychecksums((char *)msg, argc, argv, quiet);
58258559f60Stedu 
58358559f60Stedu 	free(msg - siglen);
58458559f60Stedu }
58558559f60Stedu #endif
58658559f60Stedu 
5874215a5deStedu int
5884215a5deStedu main(int argc, char **argv)
5894215a5deStedu {
59027f66874Stedu 	const char *pubkeyfile = NULL, *seckeyfile = NULL, *msgfile = NULL,
5914215a5deStedu 	    *sigfile = NULL;
5924215a5deStedu 	char sigfilebuf[1024];
593bd7b638bStedu 	const char *comment = "signify";
5944215a5deStedu 	int ch, rounds;
59527f66874Stedu 	int embedded = 0;
59658559f60Stedu 	int quiet = 0;
597a6bade58Stedu 	enum {
598a6bade58Stedu 		NONE,
59958559f60Stedu 		CHECK,
600a6bade58Stedu 		GENERATE,
60137f70c32Stedu 		INSPECT,
602a6bade58Stedu 		SIGN,
603a6bade58Stedu 		VERIFY
604a6bade58Stedu 	} verb = NONE;
605a6bade58Stedu 
6064215a5deStedu 
6074215a5deStedu 	rounds = 42;
6084215a5deStedu 
60958559f60Stedu 	while ((ch = getopt(argc, argv, "CGISVc:em:np:qs:x:")) != -1) {
6104215a5deStedu 		switch (ch) {
611665ab7d9Stedu #ifndef VERIFYONLY
61258559f60Stedu 		case 'C':
61358559f60Stedu 			if (verb)
61458559f60Stedu 				usage(NULL);
61558559f60Stedu 			verb = CHECK;
61658559f60Stedu 			break;
617a6bade58Stedu 		case 'G':
618a6bade58Stedu 			if (verb)
619f2adbe28Stedu 				usage(NULL);
620a6bade58Stedu 			verb = GENERATE;
6214215a5deStedu 			break;
62237f70c32Stedu 		case 'I':
62337f70c32Stedu 			if (verb)
624f2adbe28Stedu 				usage(NULL);
62537f70c32Stedu 			verb = INSPECT;
62637f70c32Stedu 			break;
6274215a5deStedu 		case 'S':
628a6bade58Stedu 			if (verb)
629f2adbe28Stedu 				usage(NULL);
630a6bade58Stedu 			verb = SIGN;
6314215a5deStedu 			break;
632665ab7d9Stedu #endif
6334215a5deStedu 		case 'V':
634a6bade58Stedu 			if (verb)
635f2adbe28Stedu 				usage(NULL);
636a6bade58Stedu 			verb = VERIFY;
637a6bade58Stedu 			break;
638bd7b638bStedu 		case 'c':
639bd7b638bStedu 			comment = optarg;
640bd7b638bStedu 			break;
64127f66874Stedu 		case 'e':
64227f66874Stedu 			embedded = 1;
64327f66874Stedu 			break;
644f2adbe28Stedu 		case 'm':
645f2adbe28Stedu 			msgfile = optarg;
646f2adbe28Stedu 			break;
647a6bade58Stedu 		case 'n':
648a6bade58Stedu 			rounds = 0;
649a6bade58Stedu 			break;
650a6bade58Stedu 		case 'p':
651a6bade58Stedu 			pubkeyfile = optarg;
652a6bade58Stedu 			break;
65358559f60Stedu 		case 'q':
65458559f60Stedu 			quiet = 1;
65558559f60Stedu 			break;
656a6bade58Stedu 		case 's':
657a6bade58Stedu 			seckeyfile = optarg;
6584215a5deStedu 			break;
659f2adbe28Stedu 		case 'x':
660f2adbe28Stedu 			sigfile = optarg;
661f2adbe28Stedu 			break;
6624215a5deStedu 		default:
663f2adbe28Stedu 			usage(NULL);
6644215a5deStedu 			break;
6654215a5deStedu 		}
6664215a5deStedu 	}
667bcc39c47Stedu 	argc -= optind;
66842efb9f2Sespie 	argv += optind;
66942efb9f2Sespie 
67058559f60Stedu #ifndef VERIFYONLY
67158559f60Stedu 	if (verb == CHECK) {
67258559f60Stedu 		if (!pubkeyfile || !sigfile)
67358559f60Stedu 			usage("need pubkey and sigfile");
67458559f60Stedu 		check(pubkeyfile, sigfile, quiet, argc, argv);
67558559f60Stedu 		return 0;
67658559f60Stedu 	}
67758559f60Stedu #endif
67858559f60Stedu 
67958559f60Stedu 	quiet = 1; /* retain quiet default for 5.5 release */
68058559f60Stedu 
681f2adbe28Stedu 	if (argc != 0)
682f2adbe28Stedu 		usage(NULL);
683f2adbe28Stedu 
6849127dd95Stedu 	if (!sigfile && msgfile) {
685f2adbe28Stedu 		if (strcmp(msgfile, "-") == 0)
686f2adbe28Stedu 			errx(1, "must specify sigfile with - message");
6874215a5deStedu 		if (snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig",
68827f66874Stedu 		    msgfile) >= sizeof(sigfilebuf))
6894215a5deStedu 			errx(1, "path too long");
6904215a5deStedu 		sigfile = sigfilebuf;
6914215a5deStedu 	}
6929127dd95Stedu 
6939127dd95Stedu 	switch (verb) {
694665ab7d9Stedu #ifndef VERIFYONLY
6959127dd95Stedu 	case GENERATE:
6969127dd95Stedu 		if (!pubkeyfile || !seckeyfile)
6979127dd95Stedu 			usage("need pubkey and seckey");
6989127dd95Stedu 		generate(pubkeyfile, seckeyfile, rounds, comment);
6999127dd95Stedu 		break;
7009127dd95Stedu 	case INSPECT:
7019127dd95Stedu 		inspect(seckeyfile, pubkeyfile, sigfile);
7029127dd95Stedu 		break;
7039127dd95Stedu 	case SIGN:
7049127dd95Stedu 		if (!msgfile || !seckeyfile)
7059127dd95Stedu 			usage("need message and seckey");
70627f66874Stedu 		sign(seckeyfile, msgfile, sigfile, embedded);
7079127dd95Stedu 		break;
708665ab7d9Stedu #endif
7099127dd95Stedu 	case VERIFY:
7109127dd95Stedu 		if (!msgfile || !pubkeyfile)
7119127dd95Stedu 			usage("need message and pubkey");
71258559f60Stedu 		verify(pubkeyfile, msgfile, sigfile, embedded, quiet);
7139127dd95Stedu 		break;
7149127dd95Stedu 	default:
7159127dd95Stedu 		usage(NULL);
7169127dd95Stedu 		break;
71742efb9f2Sespie 	}
71842efb9f2Sespie 
7194215a5deStedu 	return 0;
7204215a5deStedu }
721