xref: /openbsd/usr.bin/signify/zsig.c (revision 2f4de903)
1 /* $OpenBSD: zsig.c,v 1.19 2023/04/29 10:08:18 espie Exp $ */
2 /*
3  * Copyright (c) 2016 Marc Espie <espie@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #ifndef VERIFYONLY
19 #include <stdint.h>
20 #include <err.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <sha2.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <time.h>
28 #include <fcntl.h>
29 #include "signify.h"
30 
31 struct gzheader {
32 	uint8_t flg;
33 	uint32_t mtime;
34 	uint8_t xflg;
35 	uint8_t os;
36 	uint8_t *name;
37 	uint8_t *comment;
38 	uint8_t *endcomment;
39 	unsigned long long headerlength;
40 	uint8_t *buffer;
41 };
42 
43 #define FTEXT_FLAG 1
44 #define FHCRC_FLAG 2
45 #define FEXTRA_FLAG 4
46 #define FNAME_FLAG 8
47 #define FCOMMENT_FLAG 16
48 
49 #define GZHEADERLENGTH 10
50 #define MYBUFSIZE 65536LU
51 
52 
53 static uint8_t fake[10] = { 0x1f, 0x8b, 8, FCOMMENT_FLAG, 0, 0, 0, 0, 0, 3 };
54 
55 static uint8_t *
readgz_header(struct gzheader * h,int fd)56 readgz_header(struct gzheader *h, int fd)
57 {
58 	size_t sz = 1023;
59 	uint8_t *p;
60 	size_t pos = 0;
61 	size_t len = 0;
62 	int state = 0;
63 	ssize_t n;
64 	uint8_t *buf;
65 
66 	buf = xmalloc(sz);
67 
68 	while (1) {
69 		if (len == sz) {
70 			sz *= 2;
71 			buf = realloc(buf, sz);
72 			if (!buf)
73 				err(1, "realloc");
74 		}
75 		n = read(fd, buf+len, sz-len);
76 		if (n == -1)
77 			err(1, "read");
78 		/* incomplete info */
79 		if (n == 0)
80 			errx(1, "gzheader truncated");
81 		len += n;
82 		h->comment = NULL;
83 		h->name = NULL;
84 
85 		switch(state) {
86 		case 0: /* check header proper */
87 			/* need ten bytes */
88 			if (len < GZHEADERLENGTH)
89 				continue;
90 			h->flg = buf[3];
91 			h->mtime = buf[4] | (buf[5] << 8U) | (buf[6] << 16U) |
92 			    (buf[7] << 24U);
93 			h->xflg = buf[8];
94 			h->os = buf[9];
95 			/* magic gzip header */
96 			if (buf[0] != 0x1f || buf[1] != 0x8b || buf[2] != 8)
97 				err(1, "invalid magic in gzheader");
98 			/* XXX special code that only caters to our needs */
99 			if (h->flg & ~ (FCOMMENT_FLAG | FNAME_FLAG))
100 				err(1, "invalid flags in gzheader");
101 			pos = GZHEADERLENGTH;
102 			state++;
103 			/*FALLTHRU*/
104 		case 1:
105 			if (h->flg & FNAME_FLAG) {
106 				p = memchr(buf+pos, 0, len - pos);
107 				if (!p)
108 					continue;
109 				pos = (p - buf) + 1;
110 			}
111 			state++;
112 			/*FALLTHRU*/
113 		case 2:
114 			if (h->flg & FCOMMENT_FLAG) {
115 				p = memchr(buf+pos, 0, len - pos);
116 				if (!p)
117 					continue;
118 				h->comment = buf + pos;
119 				h->endcomment = p;
120 				pos = (p - buf) + 1;
121 			}
122 			if (h->flg & FNAME_FLAG)
123 				h->name = buf + GZHEADERLENGTH;
124 			h->headerlength = pos;
125 			h->buffer = buf;
126 			return buf + len;
127 		}
128 
129 	}
130 }
131 
132 static void
copy_blocks(int fdout,int fdin,const char * sha,const char * endsha,size_t bufsize,uint8_t * bufend)133 copy_blocks(int fdout, int fdin, const char *sha, const char *endsha,
134     size_t bufsize, uint8_t *bufend)
135 {
136 	uint8_t *buffer;
137 	uint8_t *residual;
138 	uint8_t output[SHA512_256_DIGEST_STRING_LENGTH];
139 
140 	buffer = xmalloc(bufsize);
141 	residual = (uint8_t *)endsha + 1;
142 
143 	while (1) {
144 		/* get the next block */
145 		size_t n = 0;
146 		/* if we have residual data, we use it */
147 		if (residual != bufend) {
148 			/* how much can we copy */
149 			size_t len = bufend - residual;
150 			n = len >= bufsize ? bufsize : len;
151 			memcpy(buffer, residual, n);
152 			residual += n;
153 		}
154 		/* if we're not done yet, try to obtain more until EOF */
155 		while (n != bufsize) {
156 			ssize_t more = read(fdin, buffer+n, bufsize-n);
157 			if (more == -1)
158 				err(1, "read");
159 			n += more;
160 			if (more == 0)
161 				break;
162 		}
163 		if (n == 0)
164 			break;
165 		SHA512_256Data(buffer, n, output);
166 		if (endsha - sha < SHA512_256_DIGEST_STRING_LENGTH-1)
167 			errx(4, "signature truncated");
168 		if (memcmp(output, sha, SHA512_256_DIGEST_STRING_LENGTH-1) != 0)
169 			errx(4, "signature mismatch");
170 		if (sha[SHA512_256_DIGEST_STRING_LENGTH-1] != '\n')
171 			errx(4, "signature mismatch");
172 		sha += SHA512_256_DIGEST_STRING_LENGTH;
173 		writeall(fdout, buffer, n, "stdout");
174 		if (n != bufsize)
175 			break;
176 	}
177 	if (endsha != sha)
178 		errx(4, "file truncated");
179 	free(buffer);
180 }
181 
182 void
zverify(const char * pubkeyfile,const char * msgfile,const char * sigfile,const char * keytype)183 zverify(const char *pubkeyfile, const char *msgfile, const char *sigfile,
184     const char *keytype)
185 {
186 	struct gzheader h;
187 	size_t bufsize, len;
188 	char *p;
189 	uint8_t *bufend;
190 	int fdin, fdout;
191 
192 	/* by default, verification will love pipes */
193 	if (!sigfile)
194 		sigfile = "-";
195 	if (!msgfile)
196 		msgfile = "-";
197 
198 	fdin = xopen(sigfile, O_RDONLY | O_NOFOLLOW, 0);
199 
200 	bufend = readgz_header(&h, fdin);
201 	if (!(h.flg & FCOMMENT_FLAG))
202 		errx(1, "unsigned gzip archive");
203 	fake[8] = h.xflg;
204 	len = h.endcomment-h.comment;
205 
206 	p = verifyzdata(h.comment, len, sigfile,
207 	    pubkeyfile, keytype);
208 
209 	bufsize = MYBUFSIZE;
210 
211 #define BEGINS_WITH(x, y) memcmp((x), (y), sizeof(y)-1) == 0
212 
213 	while (BEGINS_WITH(p, "algorithm=SHA512/256") ||
214 	    BEGINS_WITH(p, "date=") ||
215 	    BEGINS_WITH(p, "key=") ||
216 	    sscanf(p, "blocksize=%zu\n", &bufsize) > 0) {
217 		while (*(p++) != '\n')
218 			continue;
219 	}
220 
221 	if (*p != '\n')
222 		errx(1, "invalid signature");
223 
224 	fdout = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
225 	writeall(fdout, fake, sizeof fake, msgfile);
226 	writeall(fdout, h.comment, len+1, msgfile);
227 	*(p++) = 0;
228 	copy_blocks(fdout, fdin, p, h.endcomment, bufsize, bufend);
229 	free(h.buffer);
230 	close(fdout);
231 	close(fdin);
232 }
233 
234 void
zsign(const char * seckeyfile,const char * msgfile,const char * sigfile,int skipdate)235 zsign(const char *seckeyfile, const char *msgfile, const char *sigfile,
236     int skipdate)
237 {
238 	size_t bufsize = MYBUFSIZE;
239 	int fdin, fdout;
240 	struct gzheader h;
241 	struct stat sb;
242 	size_t space;
243 	char *msg;
244 	char *p;
245 	uint8_t *buffer;
246 	uint8_t *sighdr;
247 	char date[80];
248 	time_t clock;
249 
250 	fdin = xopen(msgfile, O_RDONLY, 0);
251 	if (fstat(fdin, &sb) == -1 || !S_ISREG(sb.st_mode))
252 		errx(1, "Sorry can only sign regular files");
253 
254 	readgz_header(&h, fdin);
255 	/* we don't care about the header, actually */
256 	free(h.buffer);
257 
258 	if (lseek(fdin, h.headerlength, SEEK_SET) == -1)
259 		err(1, "seek in %s", msgfile);
260 
261 	space = (sb.st_size / MYBUFSIZE+1) * SHA512_256_DIGEST_STRING_LENGTH +
262 		1024; /* long enough for extra header information */
263 
264 	msg = xmalloc(space);
265 	buffer = xmalloc(bufsize);
266 	if (skipdate) {
267 		clock = 0;
268 	} else {
269 		time(&clock);
270 	}
271 	strftime(date, sizeof date, "%Y-%m-%dT%H:%M:%SZ", gmtime(&clock));
272 	snprintf(msg, space,
273 	    "date=%s\n"
274 	    "key=%s\n"
275 	    "algorithm=SHA512/256\n"
276 	    "blocksize=%zu\n\n",
277 	    date, seckeyfile, bufsize);
278 	p = strchr(msg, 0);
279 
280 	while (1) {
281 		size_t n = read(fdin, buffer, bufsize);
282 		if (n == -1)
283 			err(1, "read from %s", msgfile);
284 		if (n == 0)
285 			break;
286 		SHA512_256Data(buffer, n, p);
287 		p += SHA512_256_DIGEST_STRING_LENGTH;
288 		p[-1] = '\n';
289 		if (msg + space < p)
290 			errx(1, "file too long %s", msgfile);
291 	}
292 	*p = 0;
293 
294 	fdout = xopen(sigfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
295 	sighdr = createsig(seckeyfile, msgfile, msg, p-msg);
296 	fake[8] = h.xflg;
297 
298 	writeall(fdout, fake, sizeof fake, sigfile);
299 	writeall(fdout, sighdr, strlen(sighdr), sigfile);
300 	free(sighdr);
301 	/* need the 0 ! */
302 	writeall(fdout, msg, p - msg + 1, sigfile);
303 	free(msg);
304 
305 	if (lseek(fdin, h.headerlength, SEEK_SET) == -1)
306 		err(1, "seek in %s", msgfile);
307 
308 	while (1) {
309 		size_t n = read(fdin, buffer, bufsize);
310 		if (n == -1)
311 			err(1, "read from %s", msgfile);
312 		if (n == 0)
313 			break;
314 		writeall(fdout, buffer, n, sigfile);
315 	}
316 	free(buffer);
317 	close(fdout);
318 }
319 #endif
320