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