1 /* $OpenBSD: zsig.c,v 1.18 2019/12/22 06:37:25 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 		SHA512_256Data(buffer, n, output);
164 		if (endsha - sha < SHA512_256_DIGEST_STRING_LENGTH-1)
165 			errx(4, "signature truncated");
166 		if (memcmp(output, sha, SHA512_256_DIGEST_STRING_LENGTH-1) != 0)
167 			errx(4, "signature mismatch");
168 		if (sha[SHA512_256_DIGEST_STRING_LENGTH-1] != '\n')
169 			errx(4, "signature mismatch");
170 		sha += SHA512_256_DIGEST_STRING_LENGTH;
171 		writeall(fdout, buffer, n, "stdout");
172 		if (n != bufsize)
173 			break;
174 	}
175 	free(buffer);
176 }
177 
178 void
zverify(const char * pubkeyfile,const char * msgfile,const char * sigfile,const char * keytype)179 zverify(const char *pubkeyfile, const char *msgfile, const char *sigfile,
180     const char *keytype)
181 {
182 	struct gzheader h;
183 	size_t bufsize, len;
184 	char *p;
185 	uint8_t *bufend;
186 	int fdin, fdout;
187 
188 	/* by default, verification will love pipes */
189 	if (!sigfile)
190 		sigfile = "-";
191 	if (!msgfile)
192 		msgfile = "-";
193 
194 	fdin = xopen(sigfile, O_RDONLY | O_NOFOLLOW, 0);
195 
196 	bufend = readgz_header(&h, fdin);
197 	if (!(h.flg & FCOMMENT_FLAG))
198 		errx(1, "unsigned gzip archive");
199 	fake[8] = h.xflg;
200 	len = h.endcomment-h.comment;
201 
202 	p = verifyzdata(h.comment, len, sigfile,
203 	    pubkeyfile, keytype);
204 
205 	bufsize = MYBUFSIZE;
206 
207 #define BEGINS_WITH(x, y) memcmp((x), (y), sizeof(y)-1) == 0
208 
209 	while (BEGINS_WITH(p, "algorithm=SHA512/256") ||
210 	    BEGINS_WITH(p, "date=") ||
211 	    BEGINS_WITH(p, "key=") ||
212 	    sscanf(p, "blocksize=%zu\n", &bufsize) > 0) {
213 		while (*(p++) != '\n')
214 			continue;
215 	}
216 
217 	if (*p != '\n')
218 		errx(1, "invalid signature");
219 
220 	fdout = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
221 	writeall(fdout, fake, sizeof fake, msgfile);
222 	writeall(fdout, h.comment, len+1, msgfile);
223 	*(p++) = 0;
224 	copy_blocks(fdout, fdin, p, h.endcomment, bufsize, bufend);
225 	free(h.buffer);
226 	close(fdout);
227 	close(fdin);
228 }
229 
230 void
zsign(const char * seckeyfile,const char * msgfile,const char * sigfile,int skipdate)231 zsign(const char *seckeyfile, const char *msgfile, const char *sigfile,
232     int skipdate)
233 {
234 	size_t bufsize = MYBUFSIZE;
235 	int fdin, fdout;
236 	struct gzheader h;
237 	struct stat sb;
238 	size_t space;
239 	char *msg;
240 	char *p;
241 	uint8_t *buffer;
242 	uint8_t *sighdr;
243 	char date[80];
244 	time_t clock;
245 
246 	fdin = xopen(msgfile, O_RDONLY, 0);
247 	if (fstat(fdin, &sb) == -1 || !S_ISREG(sb.st_mode))
248 		errx(1, "Sorry can only sign regular files");
249 
250 	readgz_header(&h, fdin);
251 	/* we don't care about the header, actually */
252 	free(h.buffer);
253 
254 	if (lseek(fdin, h.headerlength, SEEK_SET) == -1)
255 		err(1, "seek in %s", msgfile);
256 
257 	space = (sb.st_size / MYBUFSIZE+1) * SHA512_256_DIGEST_STRING_LENGTH +
258 		1024; /* long enough for extra header information */
259 
260 	msg = xmalloc(space);
261 	buffer = xmalloc(bufsize);
262 	if (skipdate) {
263 		clock = 0;
264 	} else {
265 		time(&clock);
266 	}
267 	strftime(date, sizeof date, "%Y-%m-%dT%H:%M:%SZ", gmtime(&clock));
268 	snprintf(msg, space,
269 	    "date=%s\n"
270 	    "key=%s\n"
271 	    "algorithm=SHA512/256\n"
272 	    "blocksize=%zu\n\n",
273 	    date, seckeyfile, bufsize);
274 	p = strchr(msg, 0);
275 
276 	while (1) {
277 		size_t n = read(fdin, buffer, bufsize);
278 		if (n == -1)
279 			err(1, "read from %s", msgfile);
280 		if (n == 0)
281 			break;
282 		SHA512_256Data(buffer, n, p);
283 		p += SHA512_256_DIGEST_STRING_LENGTH;
284 		p[-1] = '\n';
285 		if (msg + space < p)
286 			errx(1, "file too long %s", msgfile);
287 	}
288 	*p = 0;
289 
290 	fdout = xopen(sigfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
291 	sighdr = createsig(seckeyfile, msgfile, msg, p-msg);
292 	fake[8] = h.xflg;
293 
294 	writeall(fdout, fake, sizeof fake, sigfile);
295 	writeall(fdout, sighdr, strlen(sighdr), sigfile);
296 	free(sighdr);
297 	/* need the 0 ! */
298 	writeall(fdout, msg, p - msg + 1, sigfile);
299 	free(msg);
300 
301 	if (lseek(fdin, h.headerlength, SEEK_SET) == -1)
302 		err(1, "seek in %s", msgfile);
303 
304 	while (1) {
305 		size_t n = read(fdin, buffer, bufsize);
306 		if (n == -1)
307 			err(1, "read from %s", msgfile);
308 		if (n == 0)
309 			break;
310 		writeall(fdout, buffer, n, sigfile);
311 	}
312 	free(buffer);
313 	close(fdout);
314 }
315 #endif
316