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