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