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