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