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