mft.c revision 1.63
1/* $OpenBSD: mft.c,v 1.63 2022/05/10 07:41:37 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 <ctype.h> 20#include <err.h> 21#include <limits.h> 22#include <stdarg.h> 23#include <stdint.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 int found_crl; 42}; 43 44extern ASN1_OBJECT *mft_oid; 45 46/* 47 * Convert an ASN1_GENERALIZEDTIME to a struct tm. 48 * Returns 1 on success, 0 on failure. 49 */ 50static int 51generalizedtime_to_tm(const ASN1_GENERALIZEDTIME *gtime, struct tm *tm) 52{ 53 const char *data; 54 size_t len; 55 56 data = ASN1_STRING_get0_data(gtime); 57 len = ASN1_STRING_length(gtime); 58 59 memset(tm, 0, sizeof(*tm)); 60 return ASN1_time_parse(data, len, tm, V_ASN1_GENERALIZEDTIME) == 61 V_ASN1_GENERALIZEDTIME; 62} 63 64/* 65 * Validate and verify the time validity of the mft. 66 * Returns 1 if all is good and for any other case 0. 67 */ 68static int 69mft_parse_time(const ASN1_GENERALIZEDTIME *from, 70 const ASN1_GENERALIZEDTIME *until, struct parse *p) 71{ 72 struct tm tm_from, tm_until; 73 74 if (!generalizedtime_to_tm(from, &tm_from)) { 75 warnx("%s: embedded from time format invalid", p->fn); 76 return 0; 77 } 78 if (!generalizedtime_to_tm(until, &tm_until)) { 79 warnx("%s: embedded until time format invalid", p->fn); 80 return 0; 81 } 82 83 /* check that until is not before from */ 84 if (ASN1_time_tm_cmp(&tm_until, &tm_from) < 0) { 85 warnx("%s: bad update interval", p->fn); 86 return 0; 87 } 88 89 if ((p->res->valid_since = timegm(&tm_from)) == -1 || 90 (p->res->valid_until = timegm(&tm_until)) == -1) 91 errx(1, "%s: timegm failed", p->fn); 92 93 return 1; 94} 95 96/* 97 * Determine rtype corresponding to file extension. Returns RTYPE_INVALID 98 * on error or unkown extension. 99 */ 100enum rtype 101rtype_from_file_extension(const char *fn) 102{ 103 size_t sz; 104 105 sz = strlen(fn); 106 if (sz < 5) 107 return RTYPE_INVALID; 108 109 if (strcasecmp(fn + sz - 4, ".tal") == 0) 110 return RTYPE_TAL; 111 if (strcasecmp(fn + sz - 4, ".cer") == 0) 112 return RTYPE_CER; 113 if (strcasecmp(fn + sz - 4, ".crl") == 0) 114 return RTYPE_CRL; 115 if (strcasecmp(fn + sz - 4, ".mft") == 0) 116 return RTYPE_MFT; 117 if (strcasecmp(fn + sz - 4, ".roa") == 0) 118 return RTYPE_ROA; 119 if (strcasecmp(fn + sz - 4, ".gbr") == 0) 120 return RTYPE_GBR; 121 if (strcasecmp(fn + sz - 4, ".asa") == 0) 122 return RTYPE_ASPA; 123 if (strcasecmp(fn + sz - 4, ".sig") == 0) 124 return RTYPE_RSC; 125 126 return RTYPE_INVALID; 127} 128 129/* 130 * Validate that a filename listed on a Manifest only contains characters 131 * permitted in draft-ietf-sidrops-6486bis section 4.2.2 132 * Also ensure that there is exactly one '.'. 133 */ 134static int 135valid_mft_filename(const char *fn, size_t len) 136{ 137 const unsigned char *c; 138 139 if (!valid_filename(fn, len)) 140 return 0; 141 142 c = memchr(fn, '.', len); 143 if (c == NULL || c != memrchr(fn, '.', len)) 144 return 0; 145 146 return 1; 147} 148 149/* 150 * Check that the file is an ASPA, CER, CRL, GBR or a ROA. 151 * Returns corresponding rtype or RTYPE_INVALID on error. 152 */ 153static enum rtype 154rtype_from_mftfile(const char *fn) 155{ 156 enum rtype type; 157 158 type = rtype_from_file_extension(fn); 159 switch (type) { 160 case RTYPE_ASPA: 161 case RTYPE_CER: 162 case RTYPE_CRL: 163 case RTYPE_GBR: 164 case RTYPE_ROA: 165 return type; 166 default: 167 return RTYPE_INVALID; 168 } 169} 170 171/* 172 * Parse an individual "FileAndHash", RFC 6486, sec. 4.2. 173 * Return zero on failure, non-zero on success. 174 */ 175static int 176mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os) 177{ 178 ASN1_SEQUENCE_ANY *seq; 179 const ASN1_TYPE *file, *hash; 180 char *fn = NULL; 181 const unsigned char *d = os->data; 182 size_t dsz = os->length; 183 int rc = 0; 184 struct mftfile *fent; 185 enum rtype type; 186 187 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 188 cryptowarnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 189 "failed ASN.1 sequence parse", p->fn); 190 goto out; 191 } 192 if (sk_ASN1_TYPE_num(seq) != 2) { 193 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 194 "want 2 elements, have %d", p->fn, 195 sk_ASN1_TYPE_num(seq)); 196 goto out; 197 } 198 199 /* First is the filename itself. */ 200 201 file = sk_ASN1_TYPE_value(seq, 0); 202 if (file->type != V_ASN1_IA5STRING) { 203 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 204 "want ASN.1 IA5 string, have %s (NID %d)", 205 p->fn, ASN1_tag2str(file->type), file->type); 206 goto out; 207 } 208 if (!valid_mft_filename(file->value.ia5string->data, 209 file->value.ia5string->length)) { 210 warnx("%s: RFC 6486 section 4.2.2: bad filename", p->fn); 211 goto out; 212 } 213 fn = strndup((const char *)file->value.ia5string->data, 214 file->value.ia5string->length); 215 if (fn == NULL) 216 err(1, NULL); 217 218 /* Now hash value. */ 219 220 hash = sk_ASN1_TYPE_value(seq, 1); 221 if (hash->type != V_ASN1_BIT_STRING) { 222 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 223 "want ASN.1 bit string, have %s (NID %d)", 224 p->fn, ASN1_tag2str(hash->type), hash->type); 225 goto out; 226 } 227 228 if (hash->value.bit_string->length != SHA256_DIGEST_LENGTH) { 229 warnx("%s: RFC 6486 section 4.2.1: hash: " 230 "invalid SHA256 length, have %d", 231 p->fn, hash->value.bit_string->length); 232 goto out; 233 } 234 235 type = rtype_from_mftfile(fn); 236 /* remember the filehash for the CRL in struct mft */ 237 if (type == RTYPE_CRL && strcmp(fn, p->res->crl) == 0) { 238 memcpy(p->res->crlhash, hash->value.bit_string->data, 239 SHA256_DIGEST_LENGTH); 240 p->found_crl = 1; 241 } 242 243 /* Insert the filename and hash value. */ 244 fent = &p->res->files[p->res->filesz++]; 245 fent->type = type; 246 fent->file = fn; 247 fn = NULL; 248 memcpy(fent->hash, hash->value.bit_string->data, SHA256_DIGEST_LENGTH); 249 250 rc = 1; 251out: 252 free(fn); 253 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 254 return rc; 255} 256 257/* 258 * Parse the "FileAndHash" sequence, RFC 6486, sec. 4.2. 259 * Return zero on failure, non-zero on success. 260 */ 261static int 262mft_parse_flist(struct parse *p, const ASN1_OCTET_STRING *os) 263{ 264 ASN1_SEQUENCE_ANY *seq; 265 const ASN1_TYPE *t; 266 const unsigned char *d = os->data; 267 size_t dsz = os->length; 268 int i, rc = 0; 269 270 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 271 cryptowarnx("%s: RFC 6486 section 4.2: fileList: " 272 "failed ASN.1 sequence parse", p->fn); 273 goto out; 274 } 275 276 if (sk_ASN1_TYPE_num(seq) > MAX_MANIFEST_ENTRIES) { 277 warnx("%s: %d exceeds manifest entry limit (%d)", p->fn, 278 sk_ASN1_TYPE_num(seq), MAX_MANIFEST_ENTRIES); 279 goto out; 280 } 281 282 p->res->files = calloc(sk_ASN1_TYPE_num(seq), sizeof(struct mftfile)); 283 if (p->res->files == NULL) 284 err(1, NULL); 285 286 for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { 287 t = sk_ASN1_TYPE_value(seq, i); 288 if (t->type != V_ASN1_SEQUENCE) { 289 warnx("%s: RFC 6486 section 4.2: fileList: " 290 "want ASN.1 sequence, have %s (NID %d)", 291 p->fn, ASN1_tag2str(t->type), t->type); 292 goto out; 293 } 294 if (!mft_parse_filehash(p, t->value.octet_string)) 295 goto out; 296 } 297 298 if (!p->found_crl) { 299 warnx("%s: CRL not part of MFT fileList", p->fn); 300 goto out; 301 } 302 303 rc = 1; 304 out: 305 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 306 return rc; 307} 308 309/* 310 * Handle the eContent of the manifest object, RFC 6486 sec. 4.2. 311 * Returns 0 on failure and 1 on success. 312 */ 313static int 314mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p) 315{ 316 ASN1_SEQUENCE_ANY *seq; 317 const ASN1_TYPE *t; 318 const ASN1_GENERALIZEDTIME *from, *until; 319 long mft_version; 320 int i = 0, rc = 0; 321 322 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 323 cryptowarnx("%s: RFC 6486 section 4.2: Manifest: " 324 "failed ASN.1 sequence parse", p->fn); 325 goto out; 326 } 327 328 /* Test if the optional profile version field is present. */ 329 if (sk_ASN1_TYPE_num(seq) != 5 && 330 sk_ASN1_TYPE_num(seq) != 6) { 331 warnx("%s: RFC 6486 section 4.2: Manifest: " 332 "want 5 or 6 elements, have %d", p->fn, 333 sk_ASN1_TYPE_num(seq)); 334 goto out; 335 } 336 337 /* Parse the optional version field */ 338 if (sk_ASN1_TYPE_num(seq) == 6) { 339 t = sk_ASN1_TYPE_value(seq, i++); 340 d = t->value.asn1_string->data; 341 dsz = t->value.asn1_string->length; 342 343 if (cms_econtent_version(p->fn, &d, dsz, &mft_version) == -1) 344 goto out; 345 346 switch (mft_version) { 347 case 0: 348 warnx("%s: incorrect encoding for version 0", p->fn); 349 goto out; 350 default: 351 warnx("%s: version %ld not supported (yet)", p->fn, 352 mft_version); 353 goto out; 354 } 355 } 356 357 /* Now the manifest sequence number. */ 358 359 t = sk_ASN1_TYPE_value(seq, i++); 360 if (t->type != V_ASN1_INTEGER) { 361 warnx("%s: RFC 6486 section 4.2.1: manifestNumber: " 362 "want ASN.1 integer, have %s (NID %d)", 363 p->fn, ASN1_tag2str(t->type), t->type); 364 goto out; 365 } 366 367 p->res->seqnum = x509_convert_seqnum(p->fn, t->value.integer); 368 if (p->res->seqnum == NULL) 369 goto out; 370 371 /* 372 * Timestamps: this and next update time. 373 * Validate that the current date falls into this interval. 374 * This is required by section 4.4, (3). 375 * If we're after the given date, then the MFT is stale. 376 * This is made super complicated because it uses OpenSSL's 377 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could 378 * compare against the current time trivially. 379 */ 380 381 t = sk_ASN1_TYPE_value(seq, i++); 382 if (t->type != V_ASN1_GENERALIZEDTIME) { 383 warnx("%s: RFC 6486 section 4.2.1: thisUpdate: " 384 "want ASN.1 generalised time, have %s (NID %d)", 385 p->fn, ASN1_tag2str(t->type), t->type); 386 goto out; 387 } 388 from = t->value.generalizedtime; 389 390 t = sk_ASN1_TYPE_value(seq, i++); 391 if (t->type != V_ASN1_GENERALIZEDTIME) { 392 warnx("%s: RFC 6486 section 4.2.1: nextUpdate: " 393 "want ASN.1 generalised time, have %s (NID %d)", 394 p->fn, ASN1_tag2str(t->type), t->type); 395 goto out; 396 } 397 until = t->value.generalizedtime; 398 399 if (!mft_parse_time(from, until, p)) 400 goto out; 401 402 /* File list algorithm. */ 403 404 t = sk_ASN1_TYPE_value(seq, i++); 405 if (t->type != V_ASN1_OBJECT) { 406 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 407 "want ASN.1 object, have %s (NID %d)", 408 p->fn, ASN1_tag2str(t->type), t->type); 409 goto out; 410 } 411 if (OBJ_obj2nid(t->value.object) != NID_sha256) { 412 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 413 "want SHA256 object, have %s (NID %d)", p->fn, 414 ASN1_tag2str(OBJ_obj2nid(t->value.object)), 415 OBJ_obj2nid(t->value.object)); 416 goto out; 417 } 418 419 /* Now the sequence. */ 420 421 t = sk_ASN1_TYPE_value(seq, i++); 422 if (t->type != V_ASN1_SEQUENCE) { 423 warnx("%s: RFC 6486 section 4.2.1: fileList: " 424 "want ASN.1 sequence, have %s (NID %d)", 425 p->fn, ASN1_tag2str(t->type), t->type); 426 goto out; 427 } 428 429 if (!mft_parse_flist(p, t->value.octet_string)) 430 goto out; 431 432 rc = 1; 433out: 434 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 435 return rc; 436} 437 438/* 439 * Parse the objects that have been published in the manifest. 440 * This conforms to RFC 6486. 441 * Note that if the MFT is stale, all referenced objects are stripped 442 * from the parsed content. 443 * The MFT content is otherwise returned. 444 */ 445struct mft * 446mft_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len) 447{ 448 struct parse p; 449 int rc = 0; 450 size_t cmsz; 451 unsigned char *cms; 452 char *crldp = NULL, *crlfile; 453 454 memset(&p, 0, sizeof(struct parse)); 455 p.fn = fn; 456 457 cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz); 458 if (cms == NULL) 459 return NULL; 460 assert(*x509 != NULL); 461 462 if ((p.res = calloc(1, sizeof(struct mft))) == NULL) 463 err(1, NULL); 464 465 if (!x509_get_aia(*x509, fn, &p.res->aia)) 466 goto out; 467 if (!x509_get_aki(*x509, fn, &p.res->aki)) 468 goto out; 469 if (!x509_get_ski(*x509, fn, &p.res->ski)) 470 goto out; 471 if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) { 472 warnx("%s: RFC 6487 section 4.8: " 473 "missing AIA, AKI or SKI X509 extension", fn); 474 goto out; 475 } 476 477 /* get CRL info for later */ 478 if (!x509_get_crl(*x509, fn, &crldp)) 479 goto out; 480 if (crldp == NULL) { 481 warnx("%s: RFC 6487 section 4.8.6: CRL: " 482 "missing CRL distribution point extension", fn); 483 goto out; 484 } 485 if ((crlfile = strrchr(crldp, '/')) == NULL || 486 !valid_mft_filename(crlfile + 1, strlen(crlfile + 1)) || 487 rtype_from_file_extension(crlfile + 1) != RTYPE_CRL) { 488 warnx("%s: RFC 6487 section 4.8.6: CRL: " 489 "bad CRL distribution point extension", fn); 490 goto out; 491 } 492 if ((p.res->crl = strdup(crlfile + 1)) == NULL) 493 err(1, NULL); 494 495 if (mft_parse_econtent(cms, cmsz, &p) == 0) 496 goto out; 497 498 rc = 1; 499out: 500 if (rc == 0) { 501 mft_free(p.res); 502 p.res = NULL; 503 X509_free(*x509); 504 *x509 = NULL; 505 } 506 free(crldp); 507 free(cms); 508 return p.res; 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->path); 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(p->stale)); 546 io_simple_buffer(b, &p->repoid, sizeof(p->repoid)); 547 io_str_buffer(b, p->path); 548 549 io_str_buffer(b, p->aia); 550 io_str_buffer(b, p->aki); 551 io_str_buffer(b, p->ski); 552 553 io_simple_buffer(b, &p->filesz, sizeof(size_t)); 554 for (i = 0; i < p->filesz; i++) { 555 io_str_buffer(b, p->files[i].file); 556 io_simple_buffer(b, &p->files[i].type, 557 sizeof(p->files[i].type)); 558 io_simple_buffer(b, &p->files[i].location, 559 sizeof(p->files[i].location)); 560 io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 561 } 562} 563 564/* 565 * Read an MFT structure from the file descriptor. 566 * Result must be passed to mft_free(). 567 */ 568struct mft * 569mft_read(struct ibuf *b) 570{ 571 struct mft *p = NULL; 572 size_t i; 573 574 if ((p = calloc(1, sizeof(struct mft))) == NULL) 575 err(1, NULL); 576 577 io_read_buf(b, &p->stale, sizeof(p->stale)); 578 io_read_buf(b, &p->repoid, sizeof(p->repoid)); 579 io_read_str(b, &p->path); 580 581 io_read_str(b, &p->aia); 582 io_read_str(b, &p->aki); 583 io_read_str(b, &p->ski); 584 assert(p->aia && p->aki && p->ski); 585 586 io_read_buf(b, &p->filesz, sizeof(size_t)); 587 if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL) 588 err(1, NULL); 589 590 for (i = 0; i < p->filesz; i++) { 591 io_read_str(b, &p->files[i].file); 592 io_read_buf(b, &p->files[i].type, sizeof(p->files[i].type)); 593 io_read_buf(b, &p->files[i].location, 594 sizeof(p->files[i].location)); 595 io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 596 } 597 598 return p; 599} 600 601/* 602 * Compare two MFT files, returns 1 if first MFT is preferred and 0 if second 603 * MFT should be used. 604 */ 605int 606mft_compare(const struct mft *a, const struct mft *b) 607{ 608 int r; 609 610 if (b == NULL) 611 return 1; 612 if (a == NULL) 613 return 0; 614 615 r = strlen(a->seqnum) - strlen(b->seqnum); 616 if (r > 0) /* seqnum in a is longer -> higher */ 617 return 1; 618 if (r < 0) /* seqnum in a is shorter -> smaller */ 619 return 0; 620 621 r = strcmp(a->seqnum, b->seqnum); 622 if (r >= 0) /* a is greater or equal, prefer a */ 623 return 1; 624 return 0; 625} 626