xref: /openbsd-src/usr.bin/signify/zsig.c (revision 2f4de9035df010caaba6d026af61d7deb8a5d5e3)
1*2f4de903Sespie /* $OpenBSD: zsig.c,v 1.19 2023/04/29 10:08:18 espie Exp $ */
23ccd7401Sespie /*
33ccd7401Sespie  * Copyright (c) 2016 Marc Espie <espie@openbsd.org>
43ccd7401Sespie  *
53ccd7401Sespie  * Permission to use, copy, modify, and distribute this software for any
63ccd7401Sespie  * purpose with or without fee is hereby granted, provided that the above
73ccd7401Sespie  * copyright notice and this permission notice appear in all copies.
83ccd7401Sespie  *
93ccd7401Sespie  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
103ccd7401Sespie  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
113ccd7401Sespie  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
123ccd7401Sespie  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
133ccd7401Sespie  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
143ccd7401Sespie  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
153ccd7401Sespie  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
163ccd7401Sespie  */
173ccd7401Sespie 
183ccd7401Sespie #ifndef VERIFYONLY
193ccd7401Sespie #include <stdint.h>
203ccd7401Sespie #include <err.h>
213ccd7401Sespie #include <stdio.h>
223ccd7401Sespie #include <stdlib.h>
233ccd7401Sespie #include <unistd.h>
243ccd7401Sespie #include <sha2.h>
253ccd7401Sespie #include <string.h>
263ccd7401Sespie #include <sys/stat.h>
27f41b8f7eSespie #include <time.h>
283ccd7401Sespie #include <fcntl.h>
293ccd7401Sespie #include "signify.h"
303ccd7401Sespie 
313ccd7401Sespie struct gzheader {
323ccd7401Sespie 	uint8_t flg;
333ccd7401Sespie 	uint32_t mtime;
343ccd7401Sespie 	uint8_t xflg;
353ccd7401Sespie 	uint8_t os;
363ccd7401Sespie 	uint8_t *name;
373ccd7401Sespie 	uint8_t *comment;
383ccd7401Sespie 	uint8_t *endcomment;
393ccd7401Sespie 	unsigned long long headerlength;
4030c37a3dSespie 	uint8_t *buffer;
413ccd7401Sespie };
423ccd7401Sespie 
433ccd7401Sespie #define FTEXT_FLAG 1
443ccd7401Sespie #define FHCRC_FLAG 2
453ccd7401Sespie #define FEXTRA_FLAG 4
463ccd7401Sespie #define FNAME_FLAG 8
473ccd7401Sespie #define FCOMMENT_FLAG 16
483ccd7401Sespie 
4930c37a3dSespie #define GZHEADERLENGTH 10
503ccd7401Sespie #define MYBUFSIZE 65536LU
513ccd7401Sespie 
523ccd7401Sespie 
53f41b8f7eSespie static uint8_t fake[10] = { 0x1f, 0x8b, 8, FCOMMENT_FLAG, 0, 0, 0, 0, 0, 3 };
543ccd7401Sespie 
5548f5b1d4Stedu static uint8_t *
readgz_header(struct gzheader * h,int fd)563ccd7401Sespie readgz_header(struct gzheader *h, int fd)
573ccd7401Sespie {
5837fab01bStedu 	size_t sz = 1023;
593ccd7401Sespie 	uint8_t *p;
603ccd7401Sespie 	size_t pos = 0;
613ccd7401Sespie 	size_t len = 0;
623ccd7401Sespie 	int state = 0;
633ccd7401Sespie 	ssize_t n;
645667e1f1Stedu 	uint8_t *buf;
653ccd7401Sespie 
665667e1f1Stedu 	buf = xmalloc(sz);
673ccd7401Sespie 
683ccd7401Sespie 	while (1) {
693ccd7401Sespie 		if (len == sz) {
703ccd7401Sespie 			sz *= 2;
713ccd7401Sespie 			buf = realloc(buf, sz);
723ccd7401Sespie 			if (!buf)
73f7e01b1cStedu 				err(1, "realloc");
743ccd7401Sespie 		}
753ccd7401Sespie 		n = read(fd, buf+len, sz-len);
763ccd7401Sespie 		if (n == -1)
77f7e01b1cStedu 			err(1, "read");
783ccd7401Sespie 		/* incomplete info */
793ccd7401Sespie 		if (n == 0)
80f7e01b1cStedu 			errx(1, "gzheader truncated");
813ccd7401Sespie 		len += n;
823ccd7401Sespie 		h->comment = NULL;
833ccd7401Sespie 		h->name = NULL;
843ccd7401Sespie 
853ccd7401Sespie 		switch(state) {
863ccd7401Sespie 		case 0: /* check header proper */
873ccd7401Sespie 			/* need ten bytes */
8830c37a3dSespie 			if (len < GZHEADERLENGTH)
893ccd7401Sespie 				continue;
903ccd7401Sespie 			h->flg = buf[3];
913ccd7401Sespie 			h->mtime = buf[4] | (buf[5] << 8U) | (buf[6] << 16U) |
923ccd7401Sespie 			    (buf[7] << 24U);
933ccd7401Sespie 			h->xflg = buf[8];
943ccd7401Sespie 			h->os = buf[9];
953ccd7401Sespie 			/* magic gzip header */
963ccd7401Sespie 			if (buf[0] != 0x1f || buf[1] != 0x8b || buf[2] != 8)
972bc623c2Sespie 				err(1, "invalid magic in gzheader");
983ccd7401Sespie 			/* XXX special code that only caters to our needs */
993ccd7401Sespie 			if (h->flg & ~ (FCOMMENT_FLAG | FNAME_FLAG))
100f7e01b1cStedu 				err(1, "invalid flags in gzheader");
10130c37a3dSespie 			pos = GZHEADERLENGTH;
1023ccd7401Sespie 			state++;
1033ccd7401Sespie 			/*FALLTHRU*/
1043ccd7401Sespie 		case 1:
1053ccd7401Sespie 			if (h->flg & FNAME_FLAG) {
1063ccd7401Sespie 				p = memchr(buf+pos, 0, len - pos);
1073ccd7401Sespie 				if (!p)
1083ccd7401Sespie 					continue;
1093ccd7401Sespie 				pos = (p - buf) + 1;
1103ccd7401Sespie 			}
1113ccd7401Sespie 			state++;
1123ccd7401Sespie 			/*FALLTHRU*/
1133ccd7401Sespie 		case 2:
1143ccd7401Sespie 			if (h->flg & FCOMMENT_FLAG) {
1153ccd7401Sespie 				p = memchr(buf+pos, 0, len - pos);
1163ccd7401Sespie 				if (!p)
1173ccd7401Sespie 					continue;
1183ccd7401Sespie 				h->comment = buf + pos;
1193ccd7401Sespie 				h->endcomment = p;
1203ccd7401Sespie 				pos = (p - buf) + 1;
1213ccd7401Sespie 			}
12230c37a3dSespie 			if (h->flg & FNAME_FLAG)
12330c37a3dSespie 				h->name = buf + GZHEADERLENGTH;
1243ccd7401Sespie 			h->headerlength = pos;
12530c37a3dSespie 			h->buffer = buf;
1263ccd7401Sespie 			return buf + len;
1273ccd7401Sespie 		}
1283ccd7401Sespie 
1293ccd7401Sespie 	}
1303ccd7401Sespie }
1313ccd7401Sespie 
1323ccd7401Sespie static void
copy_blocks(int fdout,int fdin,const char * sha,const char * endsha,size_t bufsize,uint8_t * bufend)1333ccd7401Sespie copy_blocks(int fdout, int fdin, const char *sha, const char *endsha,
1343ccd7401Sespie     size_t bufsize, uint8_t *bufend)
1353ccd7401Sespie {
1365667e1f1Stedu 	uint8_t *buffer;
1375667e1f1Stedu 	uint8_t *residual;
13893394f69Stedu 	uint8_t output[SHA512_256_DIGEST_STRING_LENGTH];
1395667e1f1Stedu 
1405667e1f1Stedu 	buffer = xmalloc(bufsize);
1415667e1f1Stedu 	residual = (uint8_t *)endsha + 1;
1425667e1f1Stedu 
1433ccd7401Sespie 	while (1) {
1443ccd7401Sespie 		/* get the next block */
1453ccd7401Sespie 		size_t n = 0;
1463ccd7401Sespie 		/* if we have residual data, we use it */
1473ccd7401Sespie 		if (residual != bufend) {
1483ccd7401Sespie 			/* how much can we copy */
1493ccd7401Sespie 			size_t len = bufend - residual;
1509a29604dSespie 			n = len >= bufsize ? bufsize : len;
1519a29604dSespie 			memcpy(buffer, residual, n);
1529a29604dSespie 			residual += n;
1533ccd7401Sespie 		}
1543ccd7401Sespie 		/* if we're not done yet, try to obtain more until EOF */
1553ccd7401Sespie 		while (n != bufsize) {
1563ccd7401Sespie 			ssize_t more = read(fdin, buffer+n, bufsize-n);
1573ccd7401Sespie 			if (more == -1)
158f7e01b1cStedu 				err(1, "read");
1593ccd7401Sespie 			n += more;
1603ccd7401Sespie 			if (more == 0)
1613ccd7401Sespie 				break;
1623ccd7401Sespie 		}
163*2f4de903Sespie 		if (n == 0)
164*2f4de903Sespie 			break;
16593394f69Stedu 		SHA512_256Data(buffer, n, output);
16693394f69Stedu 		if (endsha - sha < SHA512_256_DIGEST_STRING_LENGTH-1)
1673ccd7401Sespie 			errx(4, "signature truncated");
16893394f69Stedu 		if (memcmp(output, sha, SHA512_256_DIGEST_STRING_LENGTH-1) != 0)
1693ccd7401Sespie 			errx(4, "signature mismatch");
17093394f69Stedu 		if (sha[SHA512_256_DIGEST_STRING_LENGTH-1] != '\n')
1713ccd7401Sespie 			errx(4, "signature mismatch");
17293394f69Stedu 		sha += SHA512_256_DIGEST_STRING_LENGTH;
1733ccd7401Sespie 		writeall(fdout, buffer, n, "stdout");
1743ccd7401Sespie 		if (n != bufsize)
1753ccd7401Sespie 			break;
1763ccd7401Sespie 	}
177*2f4de903Sespie 	if (endsha != sha)
178*2f4de903Sespie 		errx(4, "file truncated");
1793ccd7401Sespie 	free(buffer);
1803ccd7401Sespie }
1813ccd7401Sespie 
1823ccd7401Sespie void
zverify(const char * pubkeyfile,const char * msgfile,const char * sigfile,const char * keytype)1833ccd7401Sespie zverify(const char *pubkeyfile, const char *msgfile, const char *sigfile,
1843ccd7401Sespie     const char *keytype)
1853ccd7401Sespie {
1863ccd7401Sespie 	struct gzheader h;
1877518afafSespie 	size_t bufsize, len;
1885294c223Sespie 	char *p;
1893ccd7401Sespie 	uint8_t *bufend;
1903ccd7401Sespie 	int fdin, fdout;
1915667e1f1Stedu 
1923ccd7401Sespie 	/* by default, verification will love pipes */
1933ccd7401Sespie 	if (!sigfile)
1943ccd7401Sespie 		sigfile = "-";
1953ccd7401Sespie 	if (!msgfile)
1963ccd7401Sespie 		msgfile = "-";
1973ccd7401Sespie 
1983ccd7401Sespie 	fdin = xopen(sigfile, O_RDONLY | O_NOFOLLOW, 0);
1993ccd7401Sespie 
2003ccd7401Sespie 	bufend = readgz_header(&h, fdin);
2013ccd7401Sespie 	if (!(h.flg & FCOMMENT_FLAG))
20210cec5a2Sespie 		errx(1, "unsigned gzip archive");
2033ccd7401Sespie 	fake[8] = h.xflg;
2047518afafSespie 	len = h.endcomment-h.comment;
2053ccd7401Sespie 
2067518afafSespie 	p = verifyzdata(h.comment, len, sigfile,
2073ccd7401Sespie 	    pubkeyfile, keytype);
2083ccd7401Sespie 
2093ccd7401Sespie 	bufsize = MYBUFSIZE;
2103ccd7401Sespie 
211f41b8f7eSespie #define BEGINS_WITH(x, y) memcmp((x), (y), sizeof(y)-1) == 0
212f41b8f7eSespie 
21393394f69Stedu 	while (BEGINS_WITH(p, "algorithm=SHA512/256") ||
214f41b8f7eSespie 	    BEGINS_WITH(p, "date=") ||
21565fb4872Sespie 	    BEGINS_WITH(p, "key=") ||
216f41b8f7eSespie 	    sscanf(p, "blocksize=%zu\n", &bufsize) > 0) {
2173ccd7401Sespie 		while (*(p++) != '\n')
2183ccd7401Sespie 			continue;
2193ccd7401Sespie 	}
220f41b8f7eSespie 
221f41b8f7eSespie 	if (*p != '\n')
222f41b8f7eSespie 		errx(1, "invalid signature");
223f41b8f7eSespie 
2243ccd7401Sespie 	fdout = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
2253ccd7401Sespie 	writeall(fdout, fake, sizeof fake, msgfile);
2265294c223Sespie 	writeall(fdout, h.comment, len+1, msgfile);
2275294c223Sespie 	*(p++) = 0;
2283ccd7401Sespie 	copy_blocks(fdout, fdin, p, h.endcomment, bufsize, bufend);
22930c37a3dSespie 	free(h.buffer);
2303ccd7401Sespie 	close(fdout);
2313ccd7401Sespie 	close(fdin);
2323ccd7401Sespie }
2333ccd7401Sespie 
2343ccd7401Sespie void
zsign(const char * seckeyfile,const char * msgfile,const char * sigfile,int skipdate)235c610539bStedu zsign(const char *seckeyfile, const char *msgfile, const char *sigfile,
236c610539bStedu     int skipdate)
2373ccd7401Sespie {
2383ccd7401Sespie 	size_t bufsize = MYBUFSIZE;
2393ccd7401Sespie 	int fdin, fdout;
2403ccd7401Sespie 	struct gzheader h;
2413ccd7401Sespie 	struct stat sb;
2423ccd7401Sespie 	size_t space;
2433ccd7401Sespie 	char *msg;
2443ccd7401Sespie 	char *p;
2453ccd7401Sespie 	uint8_t *buffer;
2463ccd7401Sespie 	uint8_t *sighdr;
247f41b8f7eSespie 	char date[80];
248f41b8f7eSespie 	time_t clock;
2493ccd7401Sespie 
2503ccd7401Sespie 	fdin = xopen(msgfile, O_RDONLY, 0);
2513ccd7401Sespie 	if (fstat(fdin, &sb) == -1 || !S_ISREG(sb.st_mode))
2523ccd7401Sespie 		errx(1, "Sorry can only sign regular files");
2533ccd7401Sespie 
2543ccd7401Sespie 	readgz_header(&h, fdin);
25530c37a3dSespie 	/* we don't care about the header, actually */
25630c37a3dSespie 	free(h.buffer);
2573ccd7401Sespie 
2583ccd7401Sespie 	if (lseek(fdin, h.headerlength, SEEK_SET) == -1)
2593ccd7401Sespie 		err(1, "seek in %s", msgfile);
2603ccd7401Sespie 
26193394f69Stedu 	space = (sb.st_size / MYBUFSIZE+1) * SHA512_256_DIGEST_STRING_LENGTH +
262f41b8f7eSespie 		1024; /* long enough for extra header information */
2633ccd7401Sespie 
2643ccd7401Sespie 	msg = xmalloc(space);
2653ccd7401Sespie 	buffer = xmalloc(bufsize);
266c610539bStedu 	if (skipdate) {
267c610539bStedu 		clock = 0;
268c610539bStedu 	} else {
269f41b8f7eSespie 		time(&clock);
270c610539bStedu 	}
271f41b8f7eSespie 	strftime(date, sizeof date, "%Y-%m-%dT%H:%M:%SZ", gmtime(&clock));
272f41b8f7eSespie 	snprintf(msg, space,
273f41b8f7eSespie 	    "date=%s\n"
27465fb4872Sespie 	    "key=%s\n"
27593394f69Stedu 	    "algorithm=SHA512/256\n"
276f41b8f7eSespie 	    "blocksize=%zu\n\n",
27765fb4872Sespie 	    date, seckeyfile, bufsize);
2783ccd7401Sespie 	p = strchr(msg, 0);
2793ccd7401Sespie 
2803ccd7401Sespie 	while (1) {
2813ccd7401Sespie 		size_t n = read(fdin, buffer, bufsize);
2823ccd7401Sespie 		if (n == -1)
2833ccd7401Sespie 			err(1, "read from %s", msgfile);
2843ccd7401Sespie 		if (n == 0)
2853ccd7401Sespie 			break;
28693394f69Stedu 		SHA512_256Data(buffer, n, p);
28793394f69Stedu 		p += SHA512_256_DIGEST_STRING_LENGTH;
2883ccd7401Sespie 		p[-1] = '\n';
2893ccd7401Sespie 		if (msg + space < p)
2903ccd7401Sespie 			errx(1, "file too long %s", msgfile);
2913ccd7401Sespie 	}
2923ccd7401Sespie 	*p = 0;
2933ccd7401Sespie 
2943ccd7401Sespie 	fdout = xopen(sigfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
2953ccd7401Sespie 	sighdr = createsig(seckeyfile, msgfile, msg, p-msg);
2963ccd7401Sespie 	fake[8] = h.xflg;
2973ccd7401Sespie 
2983ccd7401Sespie 	writeall(fdout, fake, sizeof fake, sigfile);
2993ccd7401Sespie 	writeall(fdout, sighdr, strlen(sighdr), sigfile);
3003ccd7401Sespie 	free(sighdr);
3013ccd7401Sespie 	/* need the 0 ! */
3023ccd7401Sespie 	writeall(fdout, msg, p - msg + 1, sigfile);
3033ccd7401Sespie 	free(msg);
3043ccd7401Sespie 
3053ccd7401Sespie 	if (lseek(fdin, h.headerlength, SEEK_SET) == -1)
3063ccd7401Sespie 		err(1, "seek in %s", msgfile);
3073ccd7401Sespie 
3083ccd7401Sespie 	while (1) {
3093ccd7401Sespie 		size_t n = read(fdin, buffer, bufsize);
3103ccd7401Sespie 		if (n == -1)
3113ccd7401Sespie 			err(1, "read from %s", msgfile);
3123ccd7401Sespie 		if (n == 0)
3133ccd7401Sespie 			break;
3143ccd7401Sespie 		writeall(fdout, buffer, n, sigfile);
3153ccd7401Sespie 	}
3163ccd7401Sespie 	free(buffer);
3173ccd7401Sespie 	close(fdout);
3183ccd7401Sespie }
3193ccd7401Sespie #endif
320