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