Annotation of src/usr.bin/signify/zsig.c, Revision 1.4
1.4 ! tedu 1: /* $OpenBSD: zsig.c,v 1.3 2016/09/02 21:48:03 tedu Exp $ */
1.1 espie 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 <fcntl.h>
28: #include "signify.h"
29:
30: struct gzheader {
31: uint8_t flg;
32: uint32_t mtime;
33: uint8_t xflg;
34: uint8_t os;
35: uint8_t *name;
36: uint8_t *comment;
37: uint8_t *endcomment;
38: unsigned long long headerlength;
39: };
40:
41: #define FTEXT_FLAG 1
42: #define FHCRC_FLAG 2
43: #define FEXTRA_FLAG 4
44: #define FNAME_FLAG 8
45: #define FCOMMENT_FLAG 16
46:
47: #define MYBUFSIZE 65536LU
48:
49:
50: static uint8_t fake[10] = { 0x1f, 0x8b, 8, 0, 0, 0, 0, 0, 0, 3 };
51:
52: /* XXX no static there, confuses the hell out of gcc which displays
53: * non-existent warnings.
54: */
55: uint8_t *
56: readgz_header(struct gzheader *h, int fd)
57: {
58: size_t sz = 1024;
59: uint8_t *p;
60: size_t pos = 0;
61: size_t len = 0;
62: int state = 0;
63: ssize_t n;
1.4 ! tedu 64: uint8_t *buf;
1.1 espie 65:
1.4 ! tedu 66: buf = xmalloc(sz);
1.1 espie 67:
68: while (1) {
69: if (len == sz) {
70: sz *= 2;
71: buf = realloc(buf, sz);
72: if (!buf)
1.3 tedu 73: err(1, "realloc");
1.1 espie 74: }
75: n = read(fd, buf+len, sz-len);
76: if (n == -1)
1.3 tedu 77: err(1, "read");
1.1 espie 78: /* incomplete info */
79: if (n == 0)
1.3 tedu 80: errx(1, "gzheader truncated");
1.1 espie 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 < 10)
89: continue;
90: h->flg = buf[3];
1.4 ! tedu 91: h->mtime = buf[4] | (buf[5] << 8U) | (buf[6] << 16U) |
1.1 espie 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)
1.3 tedu 97: err(1, "invalud magic in gzheader");
1.1 espie 98: /* XXX special code that only caters to our needs */
99: if (h->flg & ~ (FCOMMENT_FLAG | FNAME_FLAG))
1.3 tedu 100: err(1, "invalid flags in gzheader");
1.1 espie 101: pos = 10;
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: h->name = buf + pos;
110: pos = (p - buf) + 1;
111: }
112: state++;
113: /*FALLTHRU*/
114: case 2:
115: if (h->flg & FCOMMENT_FLAG) {
116: p = memchr(buf+pos, 0, len - pos);
117: if (!p)
118: continue;
119: h->comment = buf + pos;
120: h->endcomment = p;
121: pos = (p - buf) + 1;
122: }
123: h->headerlength = pos;
124: return buf + len;
125: }
126:
127: }
128: }
129:
1.4 ! tedu 130: static void
1.1 espie 131: copy_blocks(int fdout, int fdin, const char *sha, const char *endsha,
1.4 ! tedu 132: size_t bufsize, uint8_t *bufend)
1.1 espie 133: {
1.4 ! tedu 134: uint8_t *buffer;
! 135: uint8_t *residual;
! 136: uint8_t output[SHA256_DIGEST_STRING_LENGTH];
! 137:
! 138: buffer = xmalloc(bufsize);
! 139: residual = (uint8_t *)endsha + 1;
1.1 espie 140:
141: while (1) {
142: /* get the next block */
143: size_t n = 0;
144: /* if we have residual data, we use it */
145: if (residual != bufend) {
146: /* how much can we copy */
147: size_t len = bufend - residual;
148: if (len >= bufsize) {
149: memcpy(buffer, residual, bufsize);
150: n = bufsize;
151: residual += bufsize;
152: } else {
153: memcpy(buffer, residual, len);
154: residual += len;
155: n = len;
156: }
1.4 ! tedu 157: }
1.1 espie 158: /* if we're not done yet, try to obtain more until EOF */
159: while (n != bufsize) {
160: ssize_t more = read(fdin, buffer+n, bufsize-n);
161: if (more == -1)
1.3 tedu 162: err(1, "read");
1.1 espie 163: n += more;
164: if (more == 0)
165: break;
166: }
167: SHA256Data(buffer, n, output);
168: if (endsha - sha < SHA256_DIGEST_STRING_LENGTH-1)
169: errx(4, "signature truncated");
170: if (memcmp(output, sha, SHA256_DIGEST_STRING_LENGTH-1) != 0)
171: errx(4, "signature mismatch");
172: if (sha[SHA256_DIGEST_STRING_LENGTH-1] != '\n')
173: errx(4, "signature mismatch");
174: sha += SHA256_DIGEST_STRING_LENGTH;
175: writeall(fdout, buffer, n, "stdout");
176: if (n != bufsize)
177: break;
1.4 ! tedu 178: }
1.1 espie 179: free(buffer);
180: }
181:
182: void
183: zverify(const char *pubkeyfile, const char *msgfile, const char *sigfile,
184: const char *keytype)
185: {
186: struct gzheader h;
187: size_t bufsize;
188: char *p;
189: uint8_t *bufend;
190: int fdin, fdout;
1.4 ! tedu 191:
1.1 espie 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, "%s is an unsigned archive", sigfile);
203: fake[8] = h.xflg;
204:
1.4 ! tedu 205: p = verifyzdata(h.comment, h.endcomment-h.comment, sigfile,
1.1 espie 206: pubkeyfile, keytype);
207:
208: bufsize = MYBUFSIZE;
209:
210: /* allow for arbitrary blocksize */
211: if (sscanf(p, "blocksize=%zu\n", &bufsize)) {
212: while (*(p++) != '\n')
213: continue;
214: }
215: fdout = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
216: /* we don't actually copy the header, but put in a fake one with about
217: * zero useful information.
218: */
219: writeall(fdout, fake, sizeof fake, msgfile);
220: copy_blocks(fdout, fdin, p, h.endcomment, bufsize, bufend);
221: close(fdout);
222: close(fdin);
223: }
224:
225: void
226: zsign(const char *seckeyfile, const char *msgfile, const char *sigfile)
227: {
228: size_t bufsize = MYBUFSIZE;
229: int fdin, fdout;
230: struct gzheader h;
231: struct stat sb;
232: size_t space;
233: char *msg;
234: char *p;
235: uint8_t *buffer;
236: uint8_t *sighdr;
237:
238: fdin = xopen(msgfile, O_RDONLY, 0);
239: if (fstat(fdin, &sb) == -1 || !S_ISREG(sb.st_mode))
240: errx(1, "Sorry can only sign regular files");
241:
242: readgz_header(&h, fdin);
243:
244: if (lseek(fdin, h.headerlength, SEEK_SET) == -1)
245: err(1, "seek in %s", msgfile);
246:
1.4 ! tedu 247: space = (sb.st_size / MYBUFSIZE) * SHA256_DIGEST_STRING_LENGTH +
1.1 espie 248: 80; /* long enough for blocksize=.... */
249:
250: msg = xmalloc(space);
251: buffer = xmalloc(bufsize);
252: snprintf(msg, space, "blocksize=%zu\n", bufsize);
253: p = strchr(msg, 0);
254:
255: while (1) {
256: size_t n = read(fdin, buffer, bufsize);
257: if (n == -1)
258: err(1, "read from %s", msgfile);
259: if (n == 0)
260: break;
261: SHA256Data(buffer, n, p);
262: p += SHA256_DIGEST_STRING_LENGTH;
263: p[-1] = '\n';
264: if (msg + space < p)
265: errx(1, "file too long %s", msgfile);
266: }
267: *p = 0;
268:
269: fdout = xopen(sigfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666);
270: sighdr = createsig(seckeyfile, msgfile, msg, p-msg);
271: fake[3] = FCOMMENT_FLAG;
272: fake[8] = h.xflg;
273:
274: writeall(fdout, fake, sizeof fake, sigfile);
275: writeall(fdout, sighdr, strlen(sighdr), sigfile);
276: free(sighdr);
277: /* need the 0 ! */
278: writeall(fdout, msg, p - msg + 1, sigfile);
279: free(msg);
280:
281: if (lseek(fdin, h.headerlength, SEEK_SET) == -1)
282: err(1, "seek in %s", msgfile);
283:
284: while (1) {
285: size_t n = read(fdin, buffer, bufsize);
286: if (n == -1)
287: err(1, "read from %s", msgfile);
288: if (n == 0)
289: break;
290: writeall(fdout, buffer, n, sigfile);
291: }
292: free(buffer);
293: close(fdout);
294: }
295: #endif