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