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