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 * 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 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 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 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