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