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