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