1 /* $OpenBSD: mft.c,v 1.121 2024/12/24 10:03:59 tb Exp $ */ 2 /* 3 * Copyright (c) 2022 Theo Buehler <tb@openbsd.org> 4 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <assert.h> 20 #include <err.h> 21 #include <limits.h> 22 #include <stdint.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #include <openssl/bn.h> 28 #include <openssl/asn1.h> 29 #include <openssl/asn1t.h> 30 #include <openssl/safestack.h> 31 #include <openssl/sha.h> 32 #include <openssl/stack.h> 33 #include <openssl/x509.h> 34 35 #include "extern.h" 36 37 extern ASN1_OBJECT *mft_oid; 38 BN_CTX *bn_ctx; 39 40 /* 41 * Types and templates for the Manifest eContent, RFC 6486, section 4.2. 42 */ 43 44 ASN1_ITEM_EXP FileAndHash_it; 45 ASN1_ITEM_EXP Manifest_it; 46 47 typedef struct { 48 ASN1_IA5STRING *file; 49 ASN1_BIT_STRING *hash; 50 } FileAndHash; 51 52 DECLARE_STACK_OF(FileAndHash); 53 54 #ifndef DEFINE_STACK_OF 55 #define sk_FileAndHash_dup(sk) SKM_sk_dup(FileAndHash, (sk)) 56 #define sk_FileAndHash_free(sk) SKM_sk_free(FileAndHash, (sk)) 57 #define sk_FileAndHash_num(sk) SKM_sk_num(FileAndHash, (sk)) 58 #define sk_FileAndHash_value(sk, i) SKM_sk_value(FileAndHash, (sk), (i)) 59 #define sk_FileAndHash_sort(sk) SKM_sk_sort(FileAndHash, (sk)) 60 #define sk_FileAndHash_set_cmp_func(sk, cmp) \ 61 SKM_sk_set_cmp_func(FileAndHash, (sk), (cmp)) 62 #endif 63 64 typedef struct { 65 ASN1_INTEGER *version; 66 ASN1_INTEGER *manifestNumber; 67 ASN1_GENERALIZEDTIME *thisUpdate; 68 ASN1_GENERALIZEDTIME *nextUpdate; 69 ASN1_OBJECT *fileHashAlg; 70 STACK_OF(FileAndHash) *fileList; 71 } Manifest; 72 73 ASN1_SEQUENCE(FileAndHash) = { 74 ASN1_SIMPLE(FileAndHash, file, ASN1_IA5STRING), 75 ASN1_SIMPLE(FileAndHash, hash, ASN1_BIT_STRING), 76 } ASN1_SEQUENCE_END(FileAndHash); 77 78 ASN1_SEQUENCE(Manifest) = { 79 ASN1_EXP_OPT(Manifest, version, ASN1_INTEGER, 0), 80 ASN1_SIMPLE(Manifest, manifestNumber, ASN1_INTEGER), 81 ASN1_SIMPLE(Manifest, thisUpdate, ASN1_GENERALIZEDTIME), 82 ASN1_SIMPLE(Manifest, nextUpdate, ASN1_GENERALIZEDTIME), 83 ASN1_SIMPLE(Manifest, fileHashAlg, ASN1_OBJECT), 84 ASN1_SEQUENCE_OF(Manifest, fileList, FileAndHash), 85 } ASN1_SEQUENCE_END(Manifest); 86 87 DECLARE_ASN1_FUNCTIONS(Manifest); 88 IMPLEMENT_ASN1_FUNCTIONS(Manifest); 89 90 #define GENTIME_LENGTH 15 91 92 /* 93 * Determine rtype corresponding to file extension. Returns RTYPE_INVALID 94 * on error or unknown extension. 95 */ 96 enum rtype 97 rtype_from_file_extension(const char *fn) 98 { 99 size_t sz; 100 101 sz = strlen(fn); 102 if (sz < 5) 103 return RTYPE_INVALID; 104 105 if (strcasecmp(fn + sz - 4, ".tal") == 0) 106 return RTYPE_TAL; 107 if (strcasecmp(fn + sz - 4, ".cer") == 0) 108 return RTYPE_CER; 109 if (strcasecmp(fn + sz - 4, ".crl") == 0) 110 return RTYPE_CRL; 111 if (strcasecmp(fn + sz - 4, ".mft") == 0) 112 return RTYPE_MFT; 113 if (strcasecmp(fn + sz - 4, ".roa") == 0) 114 return RTYPE_ROA; 115 if (strcasecmp(fn + sz - 4, ".gbr") == 0) 116 return RTYPE_GBR; 117 if (strcasecmp(fn + sz - 4, ".sig") == 0) 118 return RTYPE_RSC; 119 if (strcasecmp(fn + sz - 4, ".asa") == 0) 120 return RTYPE_ASPA; 121 if (strcasecmp(fn + sz - 4, ".tak") == 0) 122 return RTYPE_TAK; 123 if (strcasecmp(fn + sz - 4, ".csv") == 0) 124 return RTYPE_GEOFEED; 125 if (strcasecmp(fn + sz - 4, ".spl") == 0) 126 return RTYPE_SPL; 127 128 return RTYPE_INVALID; 129 } 130 131 /* 132 * Validate that a filename listed on a Manifest only contains characters 133 * permitted in RFC 9286 section 4.2.2. 134 * Also ensure that there is exactly one '.'. 135 */ 136 static int 137 valid_mft_filename(const char *fn, size_t len) 138 { 139 const unsigned char *c; 140 141 if (!valid_filename(fn, len)) 142 return 0; 143 144 c = memchr(fn, '.', len); 145 if (c == NULL || c != memrchr(fn, '.', len)) 146 return 0; 147 148 return 1; 149 } 150 151 /* 152 * Check that the file is allowed to be part of a manifest and the parser 153 * for this type is implemented in rpki-client. 154 * Returns corresponding rtype or RTYPE_INVALID to mark the file as unknown. 155 */ 156 static enum rtype 157 rtype_from_mftfile(const char *fn) 158 { 159 enum rtype type; 160 161 type = rtype_from_file_extension(fn); 162 switch (type) { 163 case RTYPE_CER: 164 case RTYPE_CRL: 165 case RTYPE_GBR: 166 case RTYPE_ROA: 167 case RTYPE_ASPA: 168 case RTYPE_SPL: 169 case RTYPE_TAK: 170 return type; 171 default: 172 return RTYPE_INVALID; 173 } 174 } 175 176 /* 177 * Parse an individual "FileAndHash", RFC 6486, sec. 4.2. 178 * Return zero on failure, non-zero on success. 179 */ 180 static int 181 mft_parse_filehash(const char *fn, struct mft *mft, const FileAndHash *fh, 182 int *found_crl) 183 { 184 char *file = NULL; 185 int rc = 0; 186 struct mftfile *fent; 187 enum rtype type; 188 size_t new_idx = 0; 189 190 if (!valid_mft_filename(fh->file->data, fh->file->length)) { 191 warnx("%s: RFC 6486 section 4.2.2: bad filename", fn); 192 goto out; 193 } 194 file = strndup(fh->file->data, fh->file->length); 195 if (file == NULL) 196 err(1, NULL); 197 198 if (fh->hash->length != SHA256_DIGEST_LENGTH) { 199 warnx("%s: RFC 6486 section 4.2.1: hash: " 200 "invalid SHA256 length, have %d", fn, fh->hash->length); 201 goto out; 202 } 203 204 type = rtype_from_mftfile(file); 205 if (type == RTYPE_CRL) { 206 if (*found_crl == 1) { 207 warnx("%s: RFC 6487: too many CRLs listed on MFT", fn); 208 goto out; 209 } 210 if (strcmp(file, mft->crl) != 0) { 211 warnx("%s: RFC 6487: name (%s) doesn't match CRLDP " 212 "(%s)", fn, file, mft->crl); 213 goto out; 214 } 215 /* remember the filehash for the CRL in struct mft */ 216 memcpy(mft->crlhash, fh->hash->data, SHA256_DIGEST_LENGTH); 217 *found_crl = 1; 218 } 219 220 if (filemode) 221 fent = &mft->files[mft->filesz++]; 222 else { 223 /* Fisher-Yates shuffle */ 224 new_idx = arc4random_uniform(mft->filesz + 1); 225 mft->files[mft->filesz++] = mft->files[new_idx]; 226 fent = &mft->files[new_idx]; 227 } 228 229 fent->type = type; 230 fent->file = file; 231 file = NULL; 232 memcpy(fent->hash, fh->hash->data, SHA256_DIGEST_LENGTH); 233 234 rc = 1; 235 out: 236 free(file); 237 return rc; 238 } 239 240 static int 241 mft_fh_cmp_name(const FileAndHash *const *a, const FileAndHash *const *b) 242 { 243 if ((*a)->file->length < (*b)->file->length) 244 return -1; 245 if ((*a)->file->length > (*b)->file->length) 246 return 1; 247 248 return memcmp((*a)->file->data, (*b)->file->data, (*b)->file->length); 249 } 250 251 static int 252 mft_fh_cmp_hash(const FileAndHash *const *a, const FileAndHash *const *b) 253 { 254 assert((*a)->hash->length == SHA256_DIGEST_LENGTH); 255 assert((*b)->hash->length == SHA256_DIGEST_LENGTH); 256 257 return memcmp((*a)->hash->data, (*b)->hash->data, (*b)->hash->length); 258 } 259 260 /* 261 * Assuming that the hash lengths are validated, this checks that all file names 262 * and hashes in a manifest are unique. Returns 1 on success, 0 on failure. 263 */ 264 static int 265 mft_has_unique_names_and_hashes(const char *fn, const Manifest *mft) 266 { 267 STACK_OF(FileAndHash) *fhs; 268 int i, ret = 0; 269 270 if ((fhs = sk_FileAndHash_dup(mft->fileList)) == NULL) 271 err(1, NULL); 272 273 (void)sk_FileAndHash_set_cmp_func(fhs, mft_fh_cmp_name); 274 sk_FileAndHash_sort(fhs); 275 276 for (i = 0; i < sk_FileAndHash_num(fhs) - 1; i++) { 277 const FileAndHash *curr = sk_FileAndHash_value(fhs, i); 278 const FileAndHash *next = sk_FileAndHash_value(fhs, i + 1); 279 280 if (mft_fh_cmp_name(&curr, &next) == 0) { 281 warnx("%s: duplicate name: %.*s", fn, 282 curr->file->length, curr->file->data); 283 goto err; 284 } 285 } 286 287 (void)sk_FileAndHash_set_cmp_func(fhs, mft_fh_cmp_hash); 288 sk_FileAndHash_sort(fhs); 289 290 for (i = 0; i < sk_FileAndHash_num(fhs) - 1; i++) { 291 const FileAndHash *curr = sk_FileAndHash_value(fhs, i); 292 const FileAndHash *next = sk_FileAndHash_value(fhs, i + 1); 293 294 if (mft_fh_cmp_hash(&curr, &next) == 0) { 295 warnx("%s: duplicate hash for %.*s and %.*s", fn, 296 curr->file->length, curr->file->data, 297 next->file->length, next->file->data); 298 goto err; 299 } 300 } 301 302 ret = 1; 303 304 err: 305 sk_FileAndHash_free(fhs); 306 307 return ret; 308 } 309 310 /* 311 * Handle the eContent of the manifest object, RFC 6486 sec. 4.2. 312 * Returns 0 on failure and 1 on success. 313 */ 314 static int 315 mft_parse_econtent(const char *fn, struct mft *mft, const unsigned char *d, 316 size_t dsz) 317 { 318 const unsigned char *oder; 319 Manifest *mft_asn1; 320 FileAndHash *fh; 321 int found_crl, i, rc = 0; 322 323 oder = d; 324 if ((mft_asn1 = d2i_Manifest(NULL, &d, dsz)) == NULL) { 325 warnx("%s: RFC 6486 section 4: failed to parse Manifest", fn); 326 goto out; 327 } 328 if (d != oder + dsz) { 329 warnx("%s: %td bytes trailing garbage in eContent", fn, 330 oder + dsz - d); 331 goto out; 332 } 333 334 if (!valid_econtent_version(fn, mft_asn1->version, 0)) 335 goto out; 336 337 mft->seqnum = x509_convert_seqnum(fn, "manifest number", 338 mft_asn1->manifestNumber); 339 if (mft->seqnum == NULL) 340 goto out; 341 342 /* 343 * OpenSSL's DER decoder implementation will accept a GeneralizedTime 344 * which doesn't conform to RFC 5280. So, double check. 345 */ 346 if (ASN1_STRING_length(mft_asn1->thisUpdate) != GENTIME_LENGTH) { 347 warnx("%s: embedded from time format invalid", fn); 348 goto out; 349 } 350 if (ASN1_STRING_length(mft_asn1->nextUpdate) != GENTIME_LENGTH) { 351 warnx("%s: embedded until time format invalid", fn); 352 goto out; 353 } 354 355 if (!x509_get_time(mft_asn1->thisUpdate, &mft->thisupdate)) { 356 warn("%s: parsing manifest thisUpdate failed", fn); 357 goto out; 358 } 359 if (!x509_get_time(mft_asn1->nextUpdate, &mft->nextupdate)) { 360 warn("%s: parsing manifest nextUpdate failed", fn); 361 goto out; 362 } 363 364 if (mft->thisupdate > mft->nextupdate) { 365 warnx("%s: bad update interval", fn); 366 goto out; 367 } 368 369 if (OBJ_obj2nid(mft_asn1->fileHashAlg) != NID_sha256) { 370 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 371 "want SHA256 object, have %s", fn, 372 nid2str(OBJ_obj2nid(mft_asn1->fileHashAlg))); 373 goto out; 374 } 375 376 if (sk_FileAndHash_num(mft_asn1->fileList) <= 0) { 377 warnx("%s: no files in manifest fileList", fn); 378 goto out; 379 } 380 if (sk_FileAndHash_num(mft_asn1->fileList) >= MAX_MANIFEST_ENTRIES) { 381 warnx("%s: %d exceeds manifest entry limit (%d)", fn, 382 sk_FileAndHash_num(mft_asn1->fileList), 383 MAX_MANIFEST_ENTRIES); 384 goto out; 385 } 386 387 mft->files = calloc(sk_FileAndHash_num(mft_asn1->fileList), 388 sizeof(struct mftfile)); 389 if (mft->files == NULL) 390 err(1, NULL); 391 392 found_crl = 0; 393 for (i = 0; i < sk_FileAndHash_num(mft_asn1->fileList); i++) { 394 fh = sk_FileAndHash_value(mft_asn1->fileList, i); 395 if (!mft_parse_filehash(fn, mft, fh, &found_crl)) 396 goto out; 397 } 398 399 if (!found_crl) { 400 warnx("%s: CRL not part of MFT fileList", fn); 401 goto out; 402 } 403 404 if (!mft_has_unique_names_and_hashes(fn, mft_asn1)) 405 goto out; 406 407 rc = 1; 408 out: 409 Manifest_free(mft_asn1); 410 return rc; 411 } 412 413 /* 414 * Parse the objects that have been published in the manifest. 415 * Return mft if it conforms to RFC 6486, otherwise NULL. 416 */ 417 struct mft * 418 mft_parse(X509 **x509, const char *fn, int talid, const unsigned char *der, 419 size_t len) 420 { 421 struct mft *mft; 422 struct cert *cert = NULL; 423 int rc = 0; 424 size_t cmsz; 425 unsigned char *cms; 426 char *crldp = NULL, *crlfile; 427 time_t signtime = 0; 428 429 cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz, &signtime); 430 if (cms == NULL) 431 return NULL; 432 assert(*x509 != NULL); 433 434 if ((mft = calloc(1, sizeof(*mft))) == NULL) 435 err(1, NULL); 436 mft->signtime = signtime; 437 438 if (!x509_get_aia(*x509, fn, &mft->aia)) 439 goto out; 440 if (!x509_get_aki(*x509, fn, &mft->aki)) 441 goto out; 442 if (!x509_get_sia(*x509, fn, &mft->sia)) 443 goto out; 444 if (!x509_get_ski(*x509, fn, &mft->ski)) 445 goto out; 446 if (mft->aia == NULL || mft->aki == NULL || mft->sia == NULL || 447 mft->ski == NULL) { 448 warnx("%s: RFC 6487 section 4.8: " 449 "missing AIA, AKI, SIA, or SKI X509 extension", fn); 450 goto out; 451 } 452 453 if (!x509_inherits(*x509)) { 454 warnx("%s: RFC 3779 extension not set to inherit", fn); 455 goto out; 456 } 457 458 /* get CRL info for later */ 459 if (!x509_get_crl(*x509, fn, &crldp)) 460 goto out; 461 if (crldp == NULL) { 462 warnx("%s: RFC 6487 section 4.8.6: CRL: " 463 "missing CRL distribution point extension", fn); 464 goto out; 465 } 466 crlfile = strrchr(crldp, '/'); 467 if (crlfile == NULL) { 468 warnx("%s: RFC 6487 section 4.8.6: " 469 "invalid CRL distribution point", fn); 470 goto out; 471 } 472 crlfile++; 473 if (!valid_mft_filename(crlfile, strlen(crlfile)) || 474 rtype_from_file_extension(crlfile) != RTYPE_CRL) { 475 warnx("%s: RFC 6487 section 4.8.6: CRL: " 476 "bad CRL distribution point extension", fn); 477 goto out; 478 } 479 if ((mft->crl = strdup(crlfile)) == NULL) 480 err(1, NULL); 481 482 if (mft_parse_econtent(fn, mft, cms, cmsz) == 0) 483 goto out; 484 485 if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL) 486 goto out; 487 488 if (mft->signtime > mft->nextupdate) { 489 warnx("%s: dating issue: CMS signing-time after MFT nextUpdate", 490 fn); 491 goto out; 492 } 493 494 rc = 1; 495 out: 496 if (rc == 0) { 497 mft_free(mft); 498 mft = NULL; 499 X509_free(*x509); 500 *x509 = NULL; 501 } 502 free(crldp); 503 cert_free(cert); 504 free(cms); 505 return mft; 506 } 507 508 /* 509 * Free an MFT pointer. 510 * Safe to call with NULL. 511 */ 512 void 513 mft_free(struct mft *p) 514 { 515 size_t i; 516 517 if (p == NULL) 518 return; 519 520 for (i = 0; i < p->filesz; i++) 521 free(p->files[i].file); 522 523 free(p->path); 524 free(p->files); 525 free(p->seqnum); 526 free(p->aia); 527 free(p->aki); 528 free(p->sia); 529 free(p->ski); 530 free(p->crl); 531 free(p); 532 } 533 534 /* 535 * Serialise MFT parsed content into the given buffer. 536 * See mft_read() for the other side of the pipe. 537 */ 538 void 539 mft_buffer(struct ibuf *b, const struct mft *p) 540 { 541 size_t i; 542 543 io_simple_buffer(b, &p->repoid, sizeof(p->repoid)); 544 io_simple_buffer(b, &p->talid, sizeof(p->talid)); 545 io_simple_buffer(b, &p->certid, sizeof(p->certid)); 546 io_simple_buffer(b, &p->seqnum_gap, sizeof(p->seqnum_gap)); 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 */ 568 struct mft * 569 mft_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->repoid, sizeof(p->repoid)); 578 io_read_buf(b, &p->talid, sizeof(p->talid)); 579 io_read_buf(b, &p->certid, sizeof(p->certid)); 580 io_read_buf(b, &p->seqnum_gap, sizeof(p->seqnum_gap)); 581 io_read_str(b, &p->path); 582 583 io_read_str(b, &p->aia); 584 io_read_str(b, &p->aki); 585 io_read_str(b, &p->ski); 586 assert(p->aia && p->aki && p->ski); 587 588 io_read_buf(b, &p->filesz, sizeof(size_t)); 589 if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL) 590 err(1, NULL); 591 592 for (i = 0; i < p->filesz; i++) { 593 io_read_str(b, &p->files[i].file); 594 io_read_buf(b, &p->files[i].type, sizeof(p->files[i].type)); 595 io_read_buf(b, &p->files[i].location, 596 sizeof(p->files[i].location)); 597 io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 598 } 599 600 return p; 601 } 602 603 /* 604 * Compare the thisupdate time of two mft files. 605 */ 606 int 607 mft_compare_issued(const struct mft *a, const struct mft *b) 608 { 609 if (a->thisupdate > b->thisupdate) 610 return 1; 611 if (a->thisupdate < b->thisupdate) 612 return -1; 613 return 0; 614 } 615 616 /* 617 * Compare the manifestNumber of two mft files. 618 */ 619 int 620 mft_compare_seqnum(const struct mft *a, const struct mft *b) 621 { 622 int r; 623 624 r = strlen(a->seqnum) - strlen(b->seqnum); 625 if (r > 0) /* seqnum in a is longer -> higher */ 626 return 1; 627 if (r < 0) /* seqnum in a is shorter -> smaller */ 628 return -1; 629 630 r = strcmp(a->seqnum, b->seqnum); 631 if (r > 0) /* a is greater, prefer a */ 632 return 1; 633 if (r < 0) /* b is greater, prefer b */ 634 return -1; 635 636 return 0; 637 } 638 639 /* 640 * Test if there is a gap in the sequence numbers of two MFTs. 641 * Return 1 if a gap is detected. 642 */ 643 int 644 mft_seqnum_gap_present(const struct mft *a, const struct mft *b) 645 { 646 BIGNUM *diff, *seqnum_a, *seqnum_b; 647 int ret = 0; 648 649 BN_CTX_start(bn_ctx); 650 if ((diff = BN_CTX_get(bn_ctx)) == NULL || 651 (seqnum_a = BN_CTX_get(bn_ctx)) == NULL || 652 (seqnum_b = BN_CTX_get(bn_ctx)) == NULL) 653 errx(1, "BN_CTX_get"); 654 655 if (!BN_hex2bn(&seqnum_a, a->seqnum)) 656 errx(1, "BN_hex2bn"); 657 658 if (!BN_hex2bn(&seqnum_b, b->seqnum)) 659 errx(1, "BN_hex2bn"); 660 661 if (!BN_sub(diff, seqnum_a, seqnum_b)) 662 errx(1, "BN_sub"); 663 664 ret = !BN_is_one(diff); 665 666 BN_CTX_end(bn_ctx); 667 668 return ret; 669 } 670