xref: /openbsd-src/usr.bin/signify/zsig.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /* $OpenBSD: zsig.c,v 1.12 2016/09/10 12:23:16 deraadt 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 /* XXX no static there, confuses the hell out of gcc which displays
56  * non-existent warnings.
57  */
58 uint8_t *
59 readgz_header(struct gzheader *h, int fd)
60 {
61 	size_t sz = 1024;
62 	uint8_t *p;
63 	size_t pos = 0;
64 	size_t len = 0;
65 	int state = 0;
66 	ssize_t n;
67 	uint8_t *buf;
68 
69 	buf = xmalloc(sz);
70 
71 	while (1) {
72 		if (len == sz) {
73 			sz *= 2;
74 			buf = realloc(buf, sz);
75 			if (!buf)
76 				err(1, "realloc");
77 		}
78 		n = read(fd, buf+len, sz-len);
79 		if (n == -1)
80 			err(1, "read");
81 		/* incomplete info */
82 		if (n == 0)
83 			errx(1, "gzheader truncated");
84 		len += n;
85 		h->comment = NULL;
86 		h->name = NULL;
87 
88 		switch(state) {
89 		case 0: /* check header proper */
90 			/* need ten bytes */
91 			if (len < GZHEADERLENGTH)
92 				continue;
93 			h->flg = buf[3];
94 			h->mtime = buf[4] | (buf[5] << 8U) | (buf[6] << 16U) |
95 			    (buf[7] << 24U);
96 			h->xflg = buf[8];
97 			h->os = buf[9];
98 			/* magic gzip header */
99 			if (buf[0] != 0x1f || buf[1] != 0x8b || buf[2] != 8)
100 				err(1, "invalud magic in gzheader");
101 			/* XXX special code that only caters to our needs */
102 			if (h->flg & ~ (FCOMMENT_FLAG | FNAME_FLAG))
103 				err(1, "invalid flags in gzheader");
104 			pos = GZHEADERLENGTH;
105 			state++;
106 			/*FALLTHRU*/
107 		case 1:
108 			if (h->flg & FNAME_FLAG) {
109 				p = memchr(buf+pos, 0, len - pos);
110 				if (!p)
111 					continue;
112 				pos = (p - buf) + 1;
113 			}
114 			state++;
115 			/*FALLTHRU*/
116 		case 2:
117 			if (h->flg & FCOMMENT_FLAG) {
118 				p = memchr(buf+pos, 0, len - pos);
119 				if (!p)
120 					continue;
121 				h->comment = buf + pos;
122 				h->endcomment = p;
123 				pos = (p - buf) + 1;
124 			}
125 			if (h->flg & FNAME_FLAG)
126 				h->name = buf + GZHEADERLENGTH;
127 			h->headerlength = pos;
128 			h->buffer = buf;
129 			return buf + len;
130 		}
131 
132 	}
133 }
134 
135 static void
136 copy_blocks(int fdout, int fdin, const char *sha, const char *endsha,
137     size_t bufsize, uint8_t *bufend)
138 {
139 	uint8_t *buffer;
140 	uint8_t *residual;
141 	uint8_t output[SHA512_256_DIGEST_STRING_LENGTH];
142 
143 	buffer = xmalloc(bufsize);
144 	residual = (uint8_t *)endsha + 1;
145 
146 	while (1) {
147 		/* get the next block */
148 		size_t n = 0;
149 		/* if we have residual data, we use it */
150 		if (residual != bufend) {
151 			/* how much can we copy */
152 			size_t len = bufend - residual;
153 			n = len >= bufsize ? bufsize : len;
154 			memcpy(buffer, residual, n);
155 			residual += n;
156 		}
157 		/* if we're not done yet, try to obtain more until EOF */
158 		while (n != bufsize) {
159 			ssize_t more = read(fdin, buffer+n, bufsize-n);
160 			if (more == -1)
161 				err(1, "read");
162 			n += more;
163 			if (more == 0)
164 				break;
165 		}
166 		SHA512_256Data(buffer, n, output);
167 		if (endsha - sha < SHA512_256_DIGEST_STRING_LENGTH-1)
168 			errx(4, "signature truncated");
169 		if (memcmp(output, sha, SHA512_256_DIGEST_STRING_LENGTH-1) != 0)
170 			errx(4, "signature mismatch");
171 		if (sha[SHA512_256_DIGEST_STRING_LENGTH-1] != '\n')
172 			errx(4, "signature mismatch");
173 		sha += SHA512_256_DIGEST_STRING_LENGTH;
174 		writeall(fdout, buffer, n, "stdout");
175 		if (n != bufsize)
176 			break;
177 	}
178 	free(buffer);
179 }
180 
181 void
182 zverify(const char *pubkeyfile, const char *msgfile, const char *sigfile,
183     const char *keytype)
184 {
185 	struct gzheader h;
186 	size_t bufsize;
187 	char *p, *meta;
188 	uint8_t *bufend;
189 	int fdin, fdout;
190 
191 	/* by default, verification will love pipes */
192 	if (!sigfile)
193 		sigfile = "-";
194 	if (!msgfile)
195 		msgfile = "-";
196 
197 	fdin = xopen(sigfile, O_RDONLY | O_NOFOLLOW, 0);
198 
199 	bufend = readgz_header(&h, fdin);
200 	if (!(h.flg & FCOMMENT_FLAG))
201 		errx(1, "unsigned gzip archive");
202 	fake[8] = h.xflg;
203 
204 	p = verifyzdata(h.comment, h.endcomment-h.comment, sigfile,
205 	    pubkeyfile, keytype);
206 
207 	bufsize = MYBUFSIZE;
208 
209 	meta = p;
210 #define BEGINS_WITH(x, y) memcmp((x), (y), sizeof(y)-1) == 0
211 
212 	while (BEGINS_WITH(p, "algorithm=SHA512/256") ||
213 	    BEGINS_WITH(p, "date=") ||
214 	    BEGINS_WITH(p, "key=") ||
215 	    sscanf(p, "blocksize=%zu\n", &bufsize) > 0) {
216 		while (*(p++) != '\n')
217 			continue;
218 	}
219 
220 	if (*p != '\n')
221 		errx(1, "invalid signature");
222 	*(p++) = 0;
223 
224 	fdout = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
225 	/* we don't actually copy the header, but put in a fake one with about
226 	 * zero useful information.
227 	 */
228 	writeall(fdout, fake, sizeof fake, msgfile);
229 	writeall(fdout, meta, p - meta, msgfile);
230 	copy_blocks(fdout, fdin, p, h.endcomment, bufsize, bufend);
231 	free(h.buffer);
232 	close(fdout);
233 	close(fdin);
234 }
235 
236 void
237 zsign(const char *seckeyfile, const char *msgfile, const char *sigfile)
238 {
239 	size_t bufsize = MYBUFSIZE;
240 	int fdin, fdout;
241 	struct gzheader h;
242 	struct stat sb;
243 	size_t space;
244 	char *msg;
245 	char *p;
246 	uint8_t *buffer;
247 	uint8_t *sighdr;
248 	char date[80];
249 	time_t clock;
250 
251 	fdin = xopen(msgfile, O_RDONLY, 0);
252 	if (fstat(fdin, &sb) == -1 || !S_ISREG(sb.st_mode))
253 		errx(1, "Sorry can only sign regular files");
254 
255 	readgz_header(&h, fdin);
256 	/* we don't care about the header, actually */
257 	free(h.buffer);
258 
259 	if (lseek(fdin, h.headerlength, SEEK_SET) == -1)
260 		err(1, "seek in %s", msgfile);
261 
262 	space = (sb.st_size / MYBUFSIZE+1) * SHA512_256_DIGEST_STRING_LENGTH +
263 		1024; /* long enough for extra header information */
264 
265 	msg = xmalloc(space);
266 	buffer = xmalloc(bufsize);
267 	time(&clock);
268 	strftime(date, sizeof date, "%Y-%m-%dT%H:%M:%SZ", gmtime(&clock));
269 	snprintf(msg, space,
270 	    "date=%s\n"
271 	    "key=%s\n"
272 	    "algorithm=SHA512/256\n"
273 	    "blocksize=%zu\n\n",
274 	    date, seckeyfile, bufsize);
275 	p = strchr(msg, 0);
276 
277 	while (1) {
278 		size_t n = read(fdin, buffer, bufsize);
279 		if (n == -1)
280 			err(1, "read from %s", msgfile);
281 		if (n == 0)
282 			break;
283 		SHA512_256Data(buffer, n, p);
284 		p += SHA512_256_DIGEST_STRING_LENGTH;
285 		p[-1] = '\n';
286 		if (msg + space < p)
287 			errx(1, "file too long %s", msgfile);
288 	}
289 	*p = 0;
290 
291 	fdout = xopen(sigfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
292 	sighdr = createsig(seckeyfile, msgfile, msg, p-msg);
293 	fake[8] = h.xflg;
294 
295 	writeall(fdout, fake, sizeof fake, sigfile);
296 	writeall(fdout, sighdr, strlen(sighdr), sigfile);
297 	free(sighdr);
298 	/* need the 0 ! */
299 	writeall(fdout, msg, p - msg + 1, sigfile);
300 	free(msg);
301 
302 	if (lseek(fdin, h.headerlength, SEEK_SET) == -1)
303 		err(1, "seek in %s", msgfile);
304 
305 	while (1) {
306 		size_t n = read(fdin, buffer, bufsize);
307 		if (n == -1)
308 			err(1, "read from %s", msgfile);
309 		if (n == 0)
310 			break;
311 		writeall(fdout, buffer, n, sigfile);
312 	}
313 	free(buffer);
314 	close(fdout);
315 }
316 #endif
317