mft.c revision 1.113
1/* $OpenBSD: mft.c,v 1.113 2024/04/20 15:45:41 job 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 draft-ietf-sidrops-6486bis 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 (NID %d)", fn, 370 ASN1_tag2str(OBJ_obj2nid(mft_asn1->fileHashAlg)), 371 OBJ_obj2nid(mft_asn1->fileHashAlg)); 372 goto out; 373 } 374 375 if (sk_FileAndHash_num(mft_asn1->fileList) >= MAX_MANIFEST_ENTRIES) { 376 warnx("%s: %d exceeds manifest entry limit (%d)", fn, 377 sk_FileAndHash_num(mft_asn1->fileList), 378 MAX_MANIFEST_ENTRIES); 379 goto out; 380 } 381 382 mft->files = calloc(sk_FileAndHash_num(mft_asn1->fileList), 383 sizeof(struct mftfile)); 384 if (mft->files == NULL) 385 err(1, NULL); 386 387 found_crl = 0; 388 for (i = 0; i < sk_FileAndHash_num(mft_asn1->fileList); i++) { 389 fh = sk_FileAndHash_value(mft_asn1->fileList, i); 390 if (!mft_parse_filehash(fn, mft, fh, &found_crl)) 391 goto out; 392 } 393 394 if (!found_crl) { 395 warnx("%s: CRL not part of MFT fileList", fn); 396 goto out; 397 } 398 399 if (!mft_has_unique_names_and_hashes(fn, mft_asn1)) 400 goto out; 401 402 rc = 1; 403 out: 404 Manifest_free(mft_asn1); 405 return rc; 406} 407 408/* 409 * Parse the objects that have been published in the manifest. 410 * Return mft if it conforms to RFC 6486, otherwise NULL. 411 */ 412struct mft * 413mft_parse(X509 **x509, const char *fn, int talid, const unsigned char *der, 414 size_t len) 415{ 416 struct mft *mft; 417 struct cert *cert = NULL; 418 int rc = 0; 419 size_t cmsz; 420 unsigned char *cms; 421 char *crldp = NULL, *crlfile; 422 time_t signtime = 0; 423 424 cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz, &signtime); 425 if (cms == NULL) 426 return NULL; 427 assert(*x509 != NULL); 428 429 if ((mft = calloc(1, sizeof(*mft))) == NULL) 430 err(1, NULL); 431 mft->signtime = signtime; 432 433 if (!x509_get_aia(*x509, fn, &mft->aia)) 434 goto out; 435 if (!x509_get_aki(*x509, fn, &mft->aki)) 436 goto out; 437 if (!x509_get_sia(*x509, fn, &mft->sia)) 438 goto out; 439 if (!x509_get_ski(*x509, fn, &mft->ski)) 440 goto out; 441 if (mft->aia == NULL || mft->aki == NULL || mft->sia == NULL || 442 mft->ski == NULL) { 443 warnx("%s: RFC 6487 section 4.8: " 444 "missing AIA, AKI, SIA, or SKI X509 extension", fn); 445 goto out; 446 } 447 448 if (!x509_inherits(*x509)) { 449 warnx("%s: RFC 3779 extension not set to inherit", fn); 450 goto out; 451 } 452 453 /* get CRL info for later */ 454 if (!x509_get_crl(*x509, fn, &crldp)) 455 goto out; 456 if (crldp == NULL) { 457 warnx("%s: RFC 6487 section 4.8.6: CRL: " 458 "missing CRL distribution point extension", fn); 459 goto out; 460 } 461 crlfile = strrchr(crldp, '/'); 462 if (crlfile == NULL) { 463 warnx("%s: RFC 6487 section 4.8.6: " 464 "invalid CRL distribution point", fn); 465 goto out; 466 } 467 crlfile++; 468 if (!valid_mft_filename(crlfile, strlen(crlfile)) || 469 rtype_from_file_extension(crlfile) != RTYPE_CRL) { 470 warnx("%s: RFC 6487 section 4.8.6: CRL: " 471 "bad CRL distribution point extension", fn); 472 goto out; 473 } 474 if ((mft->crl = strdup(crlfile)) == NULL) 475 err(1, NULL); 476 477 if (mft_parse_econtent(fn, mft, cms, cmsz) == 0) 478 goto out; 479 480 if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL) 481 goto out; 482 483 if (mft->signtime > mft->nextupdate) { 484 warnx("%s: dating issue: CMS signing-time after MFT nextUpdate", 485 fn); 486 goto out; 487 } 488 489 rc = 1; 490out: 491 if (rc == 0) { 492 mft_free(mft); 493 mft = NULL; 494 X509_free(*x509); 495 *x509 = NULL; 496 } 497 free(crldp); 498 cert_free(cert); 499 free(cms); 500 return mft; 501} 502 503/* 504 * Free an MFT pointer. 505 * Safe to call with NULL. 506 */ 507void 508mft_free(struct mft *p) 509{ 510 size_t i; 511 512 if (p == NULL) 513 return; 514 515 if (p->files != NULL) 516 for (i = 0; i < p->filesz; i++) 517 free(p->files[i].file); 518 519 free(p->aia); 520 free(p->aki); 521 free(p->sia); 522 free(p->ski); 523 free(p->path); 524 free(p->files); 525 free(p->seqnum); 526 free(p); 527} 528 529/* 530 * Serialise MFT parsed content into the given buffer. 531 * See mft_read() for the other side of the pipe. 532 */ 533void 534mft_buffer(struct ibuf *b, const struct mft *p) 535{ 536 size_t i; 537 538 io_simple_buffer(b, &p->repoid, sizeof(p->repoid)); 539 io_simple_buffer(b, &p->talid, sizeof(p->talid)); 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_str(b, &p->path); 573 574 io_read_str(b, &p->aia); 575 io_read_str(b, &p->aki); 576 io_read_str(b, &p->ski); 577 assert(p->aia && p->aki && p->ski); 578 579 io_read_buf(b, &p->filesz, sizeof(size_t)); 580 if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL) 581 err(1, NULL); 582 583 for (i = 0; i < p->filesz; i++) { 584 io_read_str(b, &p->files[i].file); 585 io_read_buf(b, &p->files[i].type, sizeof(p->files[i].type)); 586 io_read_buf(b, &p->files[i].location, 587 sizeof(p->files[i].location)); 588 io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 589 } 590 591 return p; 592} 593 594/* 595 * Compare the thisupdate time of two mft files. 596 */ 597int 598mft_compare_issued(const struct mft *a, const struct mft *b) 599{ 600 if (a->thisupdate > b->thisupdate) 601 return 1; 602 if (a->thisupdate < b->thisupdate) 603 return -1; 604 return 0; 605} 606 607/* 608 * Compare the manifestNumber of two mft files. 609 */ 610int 611mft_compare_seqnum(const struct mft *a, const struct mft *b) 612{ 613 int r; 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 -1; 620 621 r = strcmp(a->seqnum, b->seqnum); 622 if (r > 0) /* a is greater, prefer a */ 623 return 1; 624 if (r < 0) /* b is greater, prefer b */ 625 return -1; 626 627 return 0; 628} 629