1 /* $OpenBSD: mft.c,v 1.16 2020/09/12 15:46:48 claudio Exp $ */ 2 /* 3 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 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 #include <assert.h> 19 #include <err.h> 20 #include <stdarg.h> 21 #include <stdint.h> 22 #include <fcntl.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #include <openssl/sha.h> 28 29 #include "extern.h" 30 31 /* 32 * Parse results and data of the manifest file. 33 */ 34 struct parse { 35 const char *fn; /* manifest file name */ 36 struct mft *res; /* result object */ 37 }; 38 39 static const char * 40 gentime2str(const ASN1_GENERALIZEDTIME *time) 41 { 42 static char buf[64]; 43 BIO *mem; 44 45 if ((mem = BIO_new(BIO_s_mem())) == NULL) 46 cryptoerrx("BIO_new"); 47 if (!ASN1_GENERALIZEDTIME_print(mem, time)) 48 cryptoerrx("ASN1_GENERALIZEDTIME_print"); 49 if (BIO_gets(mem, buf, sizeof(buf)) < 0) 50 cryptoerrx("BIO_gets"); 51 52 BIO_free(mem); 53 return buf; 54 } 55 56 /* 57 * Validate and verify the time validity of the mft. 58 * Returns 1 if all is good, 0 if mft is stale, any other case -1. 59 * XXX should use ASN1_time_tm_cmp() once libressl is used. 60 */ 61 static time_t 62 check_validity(const ASN1_GENERALIZEDTIME *from, 63 const ASN1_GENERALIZEDTIME *until, const char *fn) 64 { 65 time_t now = time(NULL); 66 67 if (!ASN1_GENERALIZEDTIME_check(from) || 68 !ASN1_GENERALIZEDTIME_check(until)) { 69 warnx("%s: embedded time format invalid", fn); 70 return -1; 71 } 72 /* check that until is not before from */ 73 if (ASN1_STRING_cmp(until, from) < 0) { 74 warnx("%s: bad update interval", fn); 75 return -1; 76 } 77 /* check that now is not before from */ 78 if (X509_cmp_time(from, &now) > 0) { 79 warnx("%s: mft not yet valid %s", fn, gentime2str(from)); 80 return -1; 81 } 82 /* check that now is not after until */ 83 if (X509_cmp_time(until, &now) < 0) { 84 warnx("%s: mft expired on %s", fn, gentime2str(until)); 85 return 0; 86 } 87 88 return 1; 89 } 90 91 /* 92 * Parse an individual "FileAndHash", RFC 6486, sec. 4.2. 93 * Return zero on failure, non-zero on success. 94 */ 95 static int 96 mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os) 97 { 98 ASN1_SEQUENCE_ANY *seq; 99 const ASN1_TYPE *file, *hash; 100 char *fn = NULL; 101 const unsigned char *d = os->data; 102 size_t dsz = os->length, sz; 103 int rc = 0; 104 struct mftfile *fent; 105 106 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 107 cryptowarnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 108 "failed ASN.1 sequence parse", p->fn); 109 goto out; 110 } else if (sk_ASN1_TYPE_num(seq) != 2) { 111 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 112 "want 2 elements, have %d", p->fn, 113 sk_ASN1_TYPE_num(seq)); 114 goto out; 115 } 116 117 /* First is the filename itself. */ 118 119 file = sk_ASN1_TYPE_value(seq, 0); 120 if (file->type != V_ASN1_IA5STRING) { 121 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 122 "want ASN.1 IA5 string, have %s (NID %d)", 123 p->fn, ASN1_tag2str(file->type), file->type); 124 goto out; 125 } 126 fn = strndup((const char *)file->value.ia5string->data, 127 file->value.ia5string->length); 128 if (fn == NULL) 129 err(1, NULL); 130 131 /* 132 * Make sure we're just a pathname and either an ROA or CER. 133 * I don't think that the RFC specifically mentions this, but 134 * it's in practical use and would really screw things up 135 * (arbitrary filenames) otherwise. 136 */ 137 138 if (strchr(fn, '/') != NULL) { 139 warnx("%s: path components disallowed in filename: %s", 140 p->fn, fn); 141 goto out; 142 } else if ((sz = strlen(fn)) <= 4) { 143 warnx("%s: filename must be large enough for suffix part: %s", 144 p->fn, fn); 145 goto out; 146 } 147 148 if (strcasecmp(fn + sz - 4, ".roa") && 149 strcasecmp(fn + sz - 4, ".crl") && 150 strcasecmp(fn + sz - 4, ".cer")) { 151 /* ignore unknown files */ 152 free(fn); 153 fn = NULL; 154 rc = 1; 155 goto out; 156 } 157 158 /* Now hash value. */ 159 160 hash = sk_ASN1_TYPE_value(seq, 1); 161 if (hash->type != V_ASN1_BIT_STRING) { 162 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 163 "want ASN.1 bit string, have %s (NID %d)", 164 p->fn, ASN1_tag2str(hash->type), hash->type); 165 goto out; 166 } 167 168 if (hash->value.bit_string->length != SHA256_DIGEST_LENGTH) { 169 warnx("%s: RFC 6486 section 4.2.1: hash: " 170 "invalid SHA256 length, have %d", 171 p->fn, hash->value.bit_string->length); 172 goto out; 173 } 174 175 /* Insert the filename and hash value. */ 176 177 p->res->files = reallocarray(p->res->files, p->res->filesz + 1, 178 sizeof(struct mftfile)); 179 if (p->res->files == NULL) 180 err(1, NULL); 181 182 fent = &p->res->files[p->res->filesz++]; 183 memset(fent, 0, sizeof(struct mftfile)); 184 185 fent->file = fn; 186 fn = NULL; 187 memcpy(fent->hash, hash->value.bit_string->data, SHA256_DIGEST_LENGTH); 188 189 rc = 1; 190 out: 191 free(fn); 192 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 193 return rc; 194 } 195 196 /* 197 * Parse the "FileAndHash" sequence, RFC 6486, sec. 4.2. 198 * Return zero on failure, non-zero on success. 199 */ 200 static int 201 mft_parse_flist(struct parse *p, const ASN1_OCTET_STRING *os) 202 { 203 ASN1_SEQUENCE_ANY *seq; 204 const ASN1_TYPE *t; 205 const unsigned char *d = os->data; 206 size_t dsz = os->length; 207 int i, rc = 0; 208 209 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 210 cryptowarnx("%s: RFC 6486 section 4.2: fileList: " 211 "failed ASN.1 sequence parse", p->fn); 212 goto out; 213 } 214 215 for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { 216 t = sk_ASN1_TYPE_value(seq, i); 217 if (t->type != V_ASN1_SEQUENCE) { 218 warnx("%s: RFC 6486 section 4.2: fileList: " 219 "want ASN.1 sequence, have %s (NID %d)", 220 p->fn, ASN1_tag2str(t->type), t->type); 221 goto out; 222 } else if (!mft_parse_filehash(p, t->value.octet_string)) 223 goto out; 224 } 225 226 rc = 1; 227 out: 228 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 229 return rc; 230 } 231 232 /* 233 * Handle the eContent of the manifest object, RFC 6486 sec. 4.2. 234 * Returns <0 on failure, 0 on stale, >0 on success. 235 */ 236 static int 237 mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p) 238 { 239 ASN1_SEQUENCE_ANY *seq; 240 const ASN1_TYPE *t; 241 const ASN1_GENERALIZEDTIME *from, *until; 242 int i, rc = -1, validity; 243 244 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 245 cryptowarnx("%s: RFC 6486 section 4.2: Manifest: " 246 "failed ASN.1 sequence parse", p->fn); 247 goto out; 248 } 249 250 /* The version is optional. */ 251 252 if (sk_ASN1_TYPE_num(seq) != 5 && 253 sk_ASN1_TYPE_num(seq) != 6) { 254 warnx("%s: RFC 6486 section 4.2: Manifest: " 255 "want 5 or 6 elements, have %d", p->fn, 256 sk_ASN1_TYPE_num(seq)); 257 goto out; 258 } 259 260 /* Start with optional version. */ 261 262 i = 0; 263 if (sk_ASN1_TYPE_num(seq) == 6) { 264 t = sk_ASN1_TYPE_value(seq, i++); 265 if (t->type != V_ASN1_INTEGER) { 266 warnx("%s: RFC 6486 section 4.2.1: version: " 267 "want ASN.1 integer, have %s (NID %d)", 268 p->fn, ASN1_tag2str(t->type), t->type); 269 goto out; 270 } 271 } 272 273 /* Now the manifest sequence number. */ 274 275 t = sk_ASN1_TYPE_value(seq, i++); 276 if (t->type != V_ASN1_INTEGER) { 277 warnx("%s: RFC 6486 section 4.2.1: manifestNumber: " 278 "want ASN.1 integer, have %s (NID %d)", 279 p->fn, ASN1_tag2str(t->type), t->type); 280 goto out; 281 } 282 283 /* 284 * Timestamps: this and next update time. 285 * Validate that the current date falls into this interval. 286 * This is required by section 4.4, (3). 287 * If we're after the given date, then the MFT is stale. 288 * This is made super complicated because it usees OpenSSL's 289 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could 290 * compare against the current time trivially. 291 */ 292 293 t = sk_ASN1_TYPE_value(seq, i++); 294 if (t->type != V_ASN1_GENERALIZEDTIME) { 295 warnx("%s: RFC 6486 section 4.2.1: thisUpdate: " 296 "want ASN.1 generalised time, have %s (NID %d)", 297 p->fn, ASN1_tag2str(t->type), t->type); 298 goto out; 299 } 300 from = t->value.generalizedtime; 301 302 t = sk_ASN1_TYPE_value(seq, i++); 303 if (t->type != V_ASN1_GENERALIZEDTIME) { 304 warnx("%s: RFC 6486 section 4.2.1: nextUpdate: " 305 "want ASN.1 generalised time, have %s (NID %d)", 306 p->fn, ASN1_tag2str(t->type), t->type); 307 goto out; 308 } 309 until = t->value.generalizedtime; 310 311 validity = check_validity(from, until, p->fn); 312 if (validity != 1) 313 goto out; 314 315 /* File list algorithm. */ 316 317 t = sk_ASN1_TYPE_value(seq, i++); 318 if (t->type != V_ASN1_OBJECT) { 319 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 320 "want ASN.1 object time, have %s (NID %d)", 321 p->fn, ASN1_tag2str(t->type), t->type); 322 goto out; 323 } else if (OBJ_obj2nid(t->value.object) != NID_sha256) { 324 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 325 "want SHA256 object, have %s (NID %d)", p->fn, 326 ASN1_tag2str(OBJ_obj2nid(t->value.object)), 327 OBJ_obj2nid(t->value.object)); 328 goto out; 329 } 330 331 /* Now the sequence. */ 332 333 t = sk_ASN1_TYPE_value(seq, i++); 334 if (t->type != V_ASN1_SEQUENCE) { 335 warnx("%s: RFC 6486 section 4.2.1: fileList: " 336 "want ASN.1 sequence, have %s (NID %d)", 337 p->fn, ASN1_tag2str(t->type), t->type); 338 goto out; 339 } else if (!mft_parse_flist(p, t->value.octet_string)) 340 goto out; 341 342 rc = validity; 343 out: 344 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 345 return rc; 346 } 347 348 /* 349 * Parse the objects that have been published in the manifest. 350 * This conforms to RFC 6486. 351 * Note that if the MFT is stale, all referenced objects are stripped 352 * from the parsed content. 353 * The MFT content is otherwise returned. 354 */ 355 struct mft * 356 mft_parse(X509 **x509, const char *fn) 357 { 358 struct parse p; 359 int c, rc = 0; 360 size_t i, cmsz; 361 unsigned char *cms; 362 363 memset(&p, 0, sizeof(struct parse)); 364 p.fn = fn; 365 366 cms = cms_parse_validate(x509, fn, "1.2.840.113549.1.9.16.1.26", 367 NULL, &cmsz); 368 if (cms == NULL) 369 return NULL; 370 assert(*x509 != NULL); 371 372 if ((p.res = calloc(1, sizeof(struct mft))) == NULL) 373 err(1, NULL); 374 if ((p.res->file = strdup(fn)) == NULL) 375 err(1, NULL); 376 if (!x509_get_ski_aki(*x509, fn, &p.res->ski, &p.res->aki)) 377 goto out; 378 379 /* 380 * If we're stale, then remove all of the files that the MFT 381 * references as well as marking it as stale. 382 */ 383 384 if ((c = mft_parse_econtent(cms, cmsz, &p)) == 0) { 385 /* 386 * FIXME: it should suffice to just mark this as stale 387 * and have the logic around mft_read() simply ignore 388 * the contents of stale entries, just like it does for 389 * invalid ROAs or certificates. 390 */ 391 392 p.res->stale = 1; 393 if (p.res->files != NULL) 394 for (i = 0; i < p.res->filesz; i++) 395 free(p.res->files[i].file); 396 free(p.res->files); 397 p.res->filesz = 0; 398 p.res->files = NULL; 399 } else if (c == -1) 400 goto out; 401 402 rc = 1; 403 out: 404 if (rc == 0) { 405 mft_free(p.res); 406 p.res = NULL; 407 X509_free(*x509); 408 *x509 = NULL; 409 } 410 free(cms); 411 return p.res; 412 } 413 414 /* 415 * Check the hash value of a file. 416 * Return zero on failure, non-zero on success. 417 */ 418 static int 419 mft_validfilehash(const char *fn, const struct mftfile *m) 420 { 421 char filehash[SHA256_DIGEST_LENGTH]; 422 char buffer[8192]; 423 char *cp, *path = NULL; 424 SHA256_CTX ctx; 425 ssize_t nr; 426 int fd; 427 428 /* Check hash of file now, but first build path for it */ 429 cp = strrchr(fn, '/'); 430 assert(cp != NULL); 431 if (asprintf(&path, "%.*s/%s", (int)(cp - fn), fn, m->file) == -1) 432 err(1, "asprintf"); 433 434 if ((fd = open(path, O_RDONLY)) == -1) { 435 warn("%s: referenced file %s", fn, m->file); 436 free(path); 437 return 0; 438 } 439 free(path); 440 441 SHA256_Init(&ctx); 442 while ((nr = read(fd, buffer, sizeof(buffer))) > 0) { 443 SHA256_Update(&ctx, buffer, nr); 444 } 445 close(fd); 446 447 SHA256_Final(filehash, &ctx); 448 if (memcmp(m->hash, filehash, SHA256_DIGEST_LENGTH) != 0) { 449 warnx("%s: bad message digest for %s", fn, m->file); 450 return 0; 451 } 452 453 return 1; 454 } 455 456 /* 457 * Check all files and their hashes in a MFT structure. 458 * Return zero on failure, non-zero on success. 459 */ 460 int 461 mft_check(const char *fn, struct mft *p) 462 { 463 size_t i; 464 int rc = 1; 465 466 for (i = 0; i < p->filesz; i++) 467 if (!mft_validfilehash(fn, &p->files[i])) 468 rc = 0; 469 470 return rc; 471 } 472 473 /* 474 * Free an MFT pointer. 475 * Safe to call with NULL. 476 */ 477 void 478 mft_free(struct mft *p) 479 { 480 size_t i; 481 482 if (p == NULL) 483 return; 484 485 if (p->files != NULL) 486 for (i = 0; i < p->filesz; i++) 487 free(p->files[i].file); 488 489 free(p->aki); 490 free(p->ski); 491 free(p->file); 492 free(p->files); 493 free(p); 494 } 495 496 /* 497 * Serialise MFT parsed content into the given buffer. 498 * See mft_read() for the other side of the pipe. 499 */ 500 void 501 mft_buffer(char **b, size_t *bsz, size_t *bmax, const struct mft *p) 502 { 503 size_t i; 504 505 io_simple_buffer(b, bsz, bmax, &p->stale, sizeof(int)); 506 io_str_buffer(b, bsz, bmax, p->file); 507 io_simple_buffer(b, bsz, bmax, &p->filesz, sizeof(size_t)); 508 509 for (i = 0; i < p->filesz; i++) { 510 io_str_buffer(b, bsz, bmax, p->files[i].file); 511 io_simple_buffer(b, bsz, bmax, 512 p->files[i].hash, SHA256_DIGEST_LENGTH); 513 } 514 515 io_str_buffer(b, bsz, bmax, p->aki); 516 io_str_buffer(b, bsz, bmax, p->ski); 517 } 518 519 /* 520 * Read an MFT structure from the file descriptor. 521 * Result must be passed to mft_free(). 522 */ 523 struct mft * 524 mft_read(int fd) 525 { 526 struct mft *p = NULL; 527 size_t i; 528 529 if ((p = calloc(1, sizeof(struct mft))) == NULL) 530 err(1, NULL); 531 532 io_simple_read(fd, &p->stale, sizeof(int)); 533 io_str_read(fd, &p->file); 534 io_simple_read(fd, &p->filesz, sizeof(size_t)); 535 536 if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL) 537 err(1, NULL); 538 539 for (i = 0; i < p->filesz; i++) { 540 io_str_read(fd, &p->files[i].file); 541 io_simple_read(fd, p->files[i].hash, SHA256_DIGEST_LENGTH); 542 } 543 544 io_str_read(fd, &p->aki); 545 io_str_read(fd, &p->ski); 546 return p; 547 } 548