mft.c revision 1.56
1/* $OpenBSD: mft.c,v 1.56 2022/04/08 15:29:59 claudio Exp $ */ 2/* 3 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <assert.h> 19#include <ctype.h> 20#include <err.h> 21#include <limits.h> 22#include <stdarg.h> 23#include <stdint.h> 24#include <stdlib.h> 25#include <string.h> 26#include <unistd.h> 27 28#include <openssl/bn.h> 29#include <openssl/asn1.h> 30#include <openssl/sha.h> 31#include <openssl/x509.h> 32 33#include "extern.h" 34 35/* 36 * Parse results and data of the manifest file. 37 */ 38struct parse { 39 const char *fn; /* manifest file name */ 40 struct mft *res; /* result object */ 41}; 42 43extern ASN1_OBJECT *mft_oid; 44 45/* 46 * Convert an ASN1_GENERALIZEDTIME to a struct tm. 47 * Returns 1 on success, 0 on failure. 48 */ 49static int 50generalizedtime_to_tm(const ASN1_GENERALIZEDTIME *gtime, struct tm *tm) 51{ 52 const char *data; 53 size_t len; 54 55 data = ASN1_STRING_get0_data(gtime); 56 len = ASN1_STRING_length(gtime); 57 58 memset(tm, 0, sizeof(*tm)); 59 return ASN1_time_parse(data, len, tm, V_ASN1_GENERALIZEDTIME) == 60 V_ASN1_GENERALIZEDTIME; 61} 62 63/* 64 * Validate and verify the time validity of the mft. 65 * Returns 1 if all is good and for any other case 0. 66 */ 67static int 68mft_parse_time(const ASN1_GENERALIZEDTIME *from, 69 const ASN1_GENERALIZEDTIME *until, struct parse *p) 70{ 71 struct tm tm_from, tm_until; 72 73 if (!generalizedtime_to_tm(from, &tm_from)) { 74 warnx("%s: embedded from time format invalid", p->fn); 75 return 0; 76 } 77 if (!generalizedtime_to_tm(until, &tm_until)) { 78 warnx("%s: embedded until time format invalid", p->fn); 79 return 0; 80 } 81 82 /* check that until is not before from */ 83 if (ASN1_time_tm_cmp(&tm_until, &tm_from) < 0) { 84 warnx("%s: bad update interval", p->fn); 85 return 0; 86 } 87 88 if ((p->res->valid_from = timegm(&tm_from)) == -1 || 89 (p->res->valid_until = timegm(&tm_until)) == -1) 90 errx(1, "%s: timegm failed", p->fn); 91 92 return 1; 93} 94 95/* 96 * Determine rtype corresponding to file extension. Returns RTYPE_INVALID 97 * on error or unkown extension. 98 */ 99enum rtype 100rtype_from_file_extension(const char *fn) 101{ 102 size_t sz; 103 104 sz = strlen(fn); 105 if (sz < 5) 106 return RTYPE_INVALID; 107 108 if (strcasecmp(fn + sz - 4, ".tal") == 0) 109 return RTYPE_TAL; 110 if (strcasecmp(fn + sz - 4, ".cer") == 0) 111 return RTYPE_CER; 112 if (strcasecmp(fn + sz - 4, ".crl") == 0) 113 return RTYPE_CRL; 114 if (strcasecmp(fn + sz - 4, ".mft") == 0) 115 return RTYPE_MFT; 116 if (strcasecmp(fn + sz - 4, ".roa") == 0) 117 return RTYPE_ROA; 118 if (strcasecmp(fn + sz - 4, ".gbr") == 0) 119 return RTYPE_GBR; 120 if (strcasecmp(fn + sz - 4, ".asa") == 0) 121 return RTYPE_ASPA; 122 123 return RTYPE_INVALID; 124} 125 126/* 127 * Validate that a filename listed on a Manifest only contains characters 128 * permitted in draft-ietf-sidrops-6486bis section 4.2.2 129 */ 130static int 131valid_filename(const char *fn, size_t len) 132{ 133 const unsigned char *c; 134 size_t i; 135 136 for (c = fn, i = 0; i < len; i++, c++) 137 if (!isalnum(*c) && *c != '-' && *c != '_' && *c != '.') 138 return 0; 139 140 c = memchr(fn, '.', len); 141 if (c == NULL || c != memrchr(fn, '.', len)) 142 return 0; 143 144 return 1; 145} 146 147/* 148 * Check that the file is a CER, CRL, GBR or a ROA. 149 * Returns corresponding rtype or RTYPE_INVALID on error. 150 */ 151static enum rtype 152rtype_from_mftfile(const char *fn) 153{ 154 enum rtype type; 155 156 type = rtype_from_file_extension(fn); 157 switch (type) { 158 case RTYPE_CER: 159 case RTYPE_CRL: 160 case RTYPE_GBR: 161 case RTYPE_ROA: 162 case RTYPE_ASPA: 163 return type; 164 default: 165 return RTYPE_INVALID; 166 } 167} 168 169/* 170 * Parse an individual "FileAndHash", RFC 6486, sec. 4.2. 171 * Return zero on failure, non-zero on success. 172 */ 173static int 174mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os) 175{ 176 ASN1_SEQUENCE_ANY *seq; 177 const ASN1_TYPE *file, *hash; 178 char *fn = NULL; 179 const unsigned char *d = os->data; 180 size_t dsz = os->length; 181 int rc = 0; 182 struct mftfile *fent; 183 184 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 185 cryptowarnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 186 "failed ASN.1 sequence parse", p->fn); 187 goto out; 188 } else if (sk_ASN1_TYPE_num(seq) != 2) { 189 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 190 "want 2 elements, have %d", p->fn, 191 sk_ASN1_TYPE_num(seq)); 192 goto out; 193 } 194 195 /* First is the filename itself. */ 196 197 file = sk_ASN1_TYPE_value(seq, 0); 198 if (file->type != V_ASN1_IA5STRING) { 199 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 200 "want ASN.1 IA5 string, have %s (NID %d)", 201 p->fn, ASN1_tag2str(file->type), file->type); 202 goto out; 203 } 204 if (!valid_filename(file->value.ia5string->data, 205 file->value.ia5string->length)) { 206 warnx("%s: RFC 6486 section 4.2.2: bad filename", p->fn); 207 goto out; 208 } 209 fn = strndup((const char *)file->value.ia5string->data, 210 file->value.ia5string->length); 211 if (fn == NULL) 212 err(1, NULL); 213 214 /* Now hash value. */ 215 216 hash = sk_ASN1_TYPE_value(seq, 1); 217 if (hash->type != V_ASN1_BIT_STRING) { 218 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 219 "want ASN.1 bit string, have %s (NID %d)", 220 p->fn, ASN1_tag2str(hash->type), hash->type); 221 goto out; 222 } 223 224 if (hash->value.bit_string->length != SHA256_DIGEST_LENGTH) { 225 warnx("%s: RFC 6486 section 4.2.1: hash: " 226 "invalid SHA256 length, have %d", 227 p->fn, hash->value.bit_string->length); 228 goto out; 229 } 230 231 /* Insert the filename and hash value. */ 232 fent = &p->res->files[p->res->filesz++]; 233 234 fent->type = rtype_from_mftfile(fn); 235 fent->file = fn; 236 fn = NULL; 237 memcpy(fent->hash, hash->value.bit_string->data, SHA256_DIGEST_LENGTH); 238 239 rc = 1; 240out: 241 free(fn); 242 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 243 return rc; 244} 245 246/* 247 * Parse the "FileAndHash" sequence, RFC 6486, sec. 4.2. 248 * Return zero on failure, non-zero on success. 249 */ 250static int 251mft_parse_flist(struct parse *p, const ASN1_OCTET_STRING *os) 252{ 253 ASN1_SEQUENCE_ANY *seq; 254 const ASN1_TYPE *t; 255 const unsigned char *d = os->data; 256 size_t dsz = os->length; 257 int i, rc = 0; 258 259 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 260 cryptowarnx("%s: RFC 6486 section 4.2: fileList: " 261 "failed ASN.1 sequence parse", p->fn); 262 goto out; 263 } 264 265 if (sk_ASN1_TYPE_num(seq) > MAX_MANIFEST_ENTRIES) { 266 warnx("%s: %d exceeds manifest entry limit (%d)", p->fn, 267 sk_ASN1_TYPE_num(seq), MAX_MANIFEST_ENTRIES); 268 goto out; 269 } 270 271 p->res->files = calloc(sk_ASN1_TYPE_num(seq), sizeof(struct mftfile)); 272 if (p->res->files == NULL) 273 err(1, NULL); 274 275 for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { 276 t = sk_ASN1_TYPE_value(seq, i); 277 if (t->type != V_ASN1_SEQUENCE) { 278 warnx("%s: RFC 6486 section 4.2: fileList: " 279 "want ASN.1 sequence, have %s (NID %d)", 280 p->fn, ASN1_tag2str(t->type), t->type); 281 goto out; 282 } else if (!mft_parse_filehash(p, t->value.octet_string)) 283 goto out; 284 } 285 286 rc = 1; 287 out: 288 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 289 return rc; 290} 291 292/* 293 * Handle the eContent of the manifest object, RFC 6486 sec. 4.2. 294 * Returns 0 on failure and 1 on success. 295 */ 296static int 297mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p) 298{ 299 ASN1_SEQUENCE_ANY *seq; 300 const ASN1_TYPE *t; 301 const ASN1_GENERALIZEDTIME *from, *until; 302 long mft_version; 303 int i = 0, rc = 0; 304 305 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 306 cryptowarnx("%s: RFC 6486 section 4.2: Manifest: " 307 "failed ASN.1 sequence parse", p->fn); 308 goto out; 309 } 310 311 /* Test if the optional profile version field is present. */ 312 if (sk_ASN1_TYPE_num(seq) != 5 && 313 sk_ASN1_TYPE_num(seq) != 6) { 314 warnx("%s: RFC 6486 section 4.2: Manifest: " 315 "want 5 or 6 elements, have %d", p->fn, 316 sk_ASN1_TYPE_num(seq)); 317 goto out; 318 } 319 320 /* Parse the optional version field */ 321 if (sk_ASN1_TYPE_num(seq) == 6) { 322 t = sk_ASN1_TYPE_value(seq, i++); 323 d = t->value.asn1_string->data; 324 dsz = t->value.asn1_string->length; 325 326 if (cms_econtent_version(p->fn, &d, dsz, &mft_version) == -1) 327 goto out; 328 329 switch (mft_version) { 330 case 0: 331 warnx("%s: incorrect encoding for version 0", p->fn); 332 goto out; 333 default: 334 warnx("%s: version %ld not supported (yet)", p->fn, 335 mft_version); 336 goto out; 337 } 338 } 339 340 /* Now the manifest sequence number. */ 341 342 t = sk_ASN1_TYPE_value(seq, i++); 343 if (t->type != V_ASN1_INTEGER) { 344 warnx("%s: RFC 6486 section 4.2.1: manifestNumber: " 345 "want ASN.1 integer, have %s (NID %d)", 346 p->fn, ASN1_tag2str(t->type), t->type); 347 goto out; 348 } 349 350 p->res->seqnum = x509_convert_seqnum(p->fn, t->value.integer); 351 if (p->res->seqnum == NULL) 352 goto out; 353 354 /* 355 * Timestamps: this and next update time. 356 * Validate that the current date falls into this interval. 357 * This is required by section 4.4, (3). 358 * If we're after the given date, then the MFT is stale. 359 * This is made super complicated because it uses OpenSSL's 360 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could 361 * compare against the current time trivially. 362 */ 363 364 t = sk_ASN1_TYPE_value(seq, i++); 365 if (t->type != V_ASN1_GENERALIZEDTIME) { 366 warnx("%s: RFC 6486 section 4.2.1: thisUpdate: " 367 "want ASN.1 generalised time, have %s (NID %d)", 368 p->fn, ASN1_tag2str(t->type), t->type); 369 goto out; 370 } 371 from = t->value.generalizedtime; 372 373 t = sk_ASN1_TYPE_value(seq, i++); 374 if (t->type != V_ASN1_GENERALIZEDTIME) { 375 warnx("%s: RFC 6486 section 4.2.1: nextUpdate: " 376 "want ASN.1 generalised time, have %s (NID %d)", 377 p->fn, ASN1_tag2str(t->type), t->type); 378 goto out; 379 } 380 until = t->value.generalizedtime; 381 382 if (!mft_parse_time(from, until, p)) 383 goto out; 384 385 /* File list algorithm. */ 386 387 t = sk_ASN1_TYPE_value(seq, i++); 388 if (t->type != V_ASN1_OBJECT) { 389 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 390 "want ASN.1 object, have %s (NID %d)", 391 p->fn, ASN1_tag2str(t->type), t->type); 392 goto out; 393 } 394 if (OBJ_obj2nid(t->value.object) != NID_sha256) { 395 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 396 "want SHA256 object, have %s (NID %d)", p->fn, 397 ASN1_tag2str(OBJ_obj2nid(t->value.object)), 398 OBJ_obj2nid(t->value.object)); 399 goto out; 400 } 401 402 /* Now the sequence. */ 403 404 t = sk_ASN1_TYPE_value(seq, i++); 405 if (t->type != V_ASN1_SEQUENCE) { 406 warnx("%s: RFC 6486 section 4.2.1: fileList: " 407 "want ASN.1 sequence, have %s (NID %d)", 408 p->fn, ASN1_tag2str(t->type), t->type); 409 goto out; 410 } 411 412 if (!mft_parse_flist(p, t->value.octet_string)) 413 goto out; 414 415 rc = 1; 416out: 417 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 418 return rc; 419} 420 421/* 422 * Parse the objects that have been published in the manifest. 423 * This conforms to RFC 6486. 424 * Note that if the MFT is stale, all referenced objects are stripped 425 * from the parsed content. 426 * The MFT content is otherwise returned. 427 */ 428struct mft * 429mft_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len) 430{ 431 struct parse p; 432 int rc = 0; 433 size_t cmsz; 434 unsigned char *cms; 435 436 memset(&p, 0, sizeof(struct parse)); 437 p.fn = fn; 438 439 cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz); 440 if (cms == NULL) 441 return NULL; 442 assert(*x509 != NULL); 443 444 if ((p.res = calloc(1, sizeof(struct mft))) == NULL) 445 err(1, NULL); 446 447 if (!x509_get_aia(*x509, fn, &p.res->aia)) 448 goto out; 449 if (!x509_get_aki(*x509, fn, &p.res->aki)) 450 goto out; 451 if (!x509_get_ski(*x509, fn, &p.res->ski)) 452 goto out; 453 if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) { 454 warnx("%s: RFC 6487 section 4.8: " 455 "missing AIA, AKI or SKI X509 extension", fn); 456 goto out; 457 } 458 459 if (mft_parse_econtent(cms, cmsz, &p) == 0) 460 goto out; 461 462 rc = 1; 463out: 464 if (rc == 0) { 465 mft_free(p.res); 466 p.res = NULL; 467 X509_free(*x509); 468 *x509 = NULL; 469 } 470 free(cms); 471 return p.res; 472} 473 474/* 475 * Free an MFT pointer. 476 * Safe to call with NULL. 477 */ 478void 479mft_free(struct mft *p) 480{ 481 size_t i; 482 483 if (p == NULL) 484 return; 485 486 if (p->files != NULL) 487 for (i = 0; i < p->filesz; i++) 488 free(p->files[i].file); 489 490 free(p->aia); 491 free(p->aki); 492 free(p->ski); 493 free(p->path); 494 free(p->files); 495 free(p->seqnum); 496 free(p); 497} 498 499/* 500 * Serialise MFT parsed content into the given buffer. 501 * See mft_read() for the other side of the pipe. 502 */ 503void 504mft_buffer(struct ibuf *b, const struct mft *p) 505{ 506 size_t i; 507 508 io_simple_buffer(b, &p->stale, sizeof(p->stale)); 509 io_simple_buffer(b, &p->repoid, sizeof(p->repoid)); 510 io_str_buffer(b, p->path); 511 512 io_str_buffer(b, p->aia); 513 io_str_buffer(b, p->aki); 514 io_str_buffer(b, p->ski); 515 516 io_simple_buffer(b, &p->filesz, sizeof(size_t)); 517 for (i = 0; i < p->filesz; i++) { 518 io_str_buffer(b, p->files[i].file); 519 io_simple_buffer(b, &p->files[i].type, 520 sizeof(p->files[i].type)); 521 io_simple_buffer(b, &p->files[i].location, 522 sizeof(p->files[i].location)); 523 io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 524 } 525} 526 527/* 528 * Read an MFT structure from the file descriptor. 529 * Result must be passed to mft_free(). 530 */ 531struct mft * 532mft_read(struct ibuf *b) 533{ 534 struct mft *p = NULL; 535 size_t i; 536 537 if ((p = calloc(1, sizeof(struct mft))) == NULL) 538 err(1, NULL); 539 540 io_read_buf(b, &p->stale, sizeof(p->stale)); 541 io_read_buf(b, &p->repoid, sizeof(p->repoid)); 542 io_read_str(b, &p->path); 543 544 io_read_str(b, &p->aia); 545 io_read_str(b, &p->aki); 546 io_read_str(b, &p->ski); 547 assert(p->aia && p->aki && p->ski); 548 549 io_read_buf(b, &p->filesz, sizeof(size_t)); 550 if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL) 551 err(1, NULL); 552 553 for (i = 0; i < p->filesz; i++) { 554 io_read_str(b, &p->files[i].file); 555 io_read_buf(b, &p->files[i].type, sizeof(p->files[i].type)); 556 io_read_buf(b, &p->files[i].location, 557 sizeof(p->files[i].location)); 558 io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 559 } 560 561 return p; 562} 563 564/* 565 * Compare two MFT files, returns 1 if first MFT is preferred and 0 if second 566 * MFT should be used. 567 */ 568int 569mft_compare(const struct mft *a, const struct mft *b) 570{ 571 int r; 572 573 if (b == NULL) 574 return 1; 575 if (a == NULL) 576 return 0; 577 578 r = strlen(a->seqnum) - strlen(b->seqnum); 579 if (r > 0) /* seqnum in a is longer -> higher */ 580 return 1; 581 if (r < 0) /* seqnum in a is shorter -> smaller */ 582 return 0; 583 584 r = strcmp(a->seqnum, b->seqnum); 585 if (r >= 0) /* a is greater or equal, prefer a */ 586 return 1; 587 return 0; 588} 589