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