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