mft.c revision 1.77
1/* $OpenBSD: mft.c,v 1.77 2022/11/04 09:43:13 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 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 an CER, CRL, GBR or a ROA. 199 * Returns corresponding rtype or RTYPE_INVALID on error. 200 */ 201static enum rtype 202rtype_from_mftfile(const char *fn) 203{ 204 enum rtype type; 205 206 type = rtype_from_file_extension(fn); 207 switch (type) { 208 case RTYPE_CER: 209 case RTYPE_CRL: 210 case RTYPE_GBR: 211 case RTYPE_ROA: 212 case RTYPE_ASPA: 213 case RTYPE_TAK: 214 return type; 215 default: 216 return RTYPE_INVALID; 217 } 218} 219 220/* 221 * Parse an individual "FileAndHash", RFC 6486, sec. 4.2. 222 * Return zero on failure, non-zero on success. 223 */ 224static int 225mft_parse_filehash(struct parse *p, const FileAndHash *fh) 226{ 227 char *fn = NULL; 228 int rc = 0; 229 struct mftfile *fent; 230 enum rtype type; 231 232 if (!valid_mft_filename(fh->file->data, fh->file->length)) { 233 warnx("%s: RFC 6486 section 4.2.2: bad filename", p->fn); 234 goto out; 235 } 236 fn = strndup(fh->file->data, fh->file->length); 237 if (fn == NULL) 238 err(1, NULL); 239 240 if (fh->hash->length != SHA256_DIGEST_LENGTH) { 241 warnx("%s: RFC 6486 section 4.2.1: hash: " 242 "invalid SHA256 length, have %d", 243 p->fn, fh->hash->length); 244 goto out; 245 } 246 247 type = rtype_from_mftfile(fn); 248 /* remember the filehash for the CRL in struct mft */ 249 if (type == RTYPE_CRL && strcmp(fn, p->res->crl) == 0) { 250 memcpy(p->res->crlhash, fh->hash->data, SHA256_DIGEST_LENGTH); 251 p->found_crl = 1; 252 } 253 254 /* Insert the filename and hash value. */ 255 fent = &p->res->files[p->res->filesz++]; 256 fent->type = type; 257 fent->file = fn; 258 fn = NULL; 259 memcpy(fent->hash, fh->hash->data, SHA256_DIGEST_LENGTH); 260 261 rc = 1; 262 out: 263 free(fn); 264 return rc; 265} 266 267/* 268 * Handle the eContent of the manifest object, RFC 6486 sec. 4.2. 269 * Returns 0 on failure and 1 on success. 270 */ 271static int 272mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p) 273{ 274 Manifest *mft; 275 FileAndHash *fh; 276 int i, rc = 0; 277 278 if ((mft = d2i_Manifest(NULL, &d, dsz)) == NULL) { 279 cryptowarnx("%s: RFC 6486 section 4: failed to parse Manifest", 280 p->fn); 281 goto out; 282 } 283 284 if (!valid_econtent_version(p->fn, mft->version)) 285 goto out; 286 287 p->res->seqnum = x509_convert_seqnum(p->fn, mft->manifestNumber); 288 if (p->res->seqnum == NULL) 289 goto out; 290 291 /* 292 * Timestamps: this and next update time. 293 * Validate that the current date falls into this interval. 294 * This is required by section 4.4, (3). 295 * If we're after the given date, then the MFT is stale. 296 * This is made super complicated because it uses OpenSSL's 297 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could 298 * compare against the current time trivially. 299 */ 300 301 if (!mft_parse_time(mft->thisUpdate, mft->nextUpdate, p)) 302 goto out; 303 304 if (OBJ_obj2nid(mft->fileHashAlg) != NID_sha256) { 305 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 306 "want SHA256 object, have %s (NID %d)", p->fn, 307 ASN1_tag2str(OBJ_obj2nid(mft->fileHashAlg)), 308 OBJ_obj2nid(mft->fileHashAlg)); 309 goto out; 310 } 311 312 if (sk_FileAndHash_num(mft->fileList) >= MAX_MANIFEST_ENTRIES) { 313 warnx("%s: %d exceeds manifest entry limit (%d)", p->fn, 314 sk_FileAndHash_num(mft->fileList), MAX_MANIFEST_ENTRIES); 315 goto out; 316 } 317 318 p->res->files = calloc(sk_FileAndHash_num(mft->fileList), 319 sizeof(struct mftfile)); 320 if (p->res->files == NULL) 321 err(1, NULL); 322 323 for (i = 0; i < sk_FileAndHash_num(mft->fileList); i++) { 324 fh = sk_FileAndHash_value(mft->fileList, i); 325 if (!mft_parse_filehash(p, fh)) 326 goto out; 327 } 328 329 if (!p->found_crl) { 330 warnx("%s: CRL not part of MFT fileList", p->fn); 331 goto out; 332 } 333 334 rc = 1; 335 out: 336 Manifest_free(mft); 337 return rc; 338} 339 340/* 341 * Parse the objects that have been published in the manifest. 342 * This conforms to RFC 6486. 343 * Note that if the MFT is stale, all referenced objects are stripped 344 * from the parsed content. 345 * The MFT content is otherwise returned. 346 */ 347struct mft * 348mft_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len) 349{ 350 struct parse p; 351 int rc = 0; 352 size_t cmsz; 353 unsigned char *cms; 354 char *crldp = NULL, *crlfile; 355 356 memset(&p, 0, sizeof(struct parse)); 357 p.fn = fn; 358 359 cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz); 360 if (cms == NULL) 361 return NULL; 362 assert(*x509 != NULL); 363 364 if ((p.res = calloc(1, sizeof(struct mft))) == NULL) 365 err(1, NULL); 366 367 if (!x509_get_aia(*x509, fn, &p.res->aia)) 368 goto out; 369 if (!x509_get_aki(*x509, fn, &p.res->aki)) 370 goto out; 371 if (!x509_get_sia(*x509, fn, &p.res->sia)) 372 goto out; 373 if (!x509_get_ski(*x509, fn, &p.res->ski)) 374 goto out; 375 if (p.res->aia == NULL || p.res->aki == NULL || p.res->sia == NULL || 376 p.res->ski == NULL) { 377 warnx("%s: RFC 6487 section 4.8: " 378 "missing AIA, AKI, SIA, or SKI X509 extension", fn); 379 goto out; 380 } 381 382 if (!x509_inherits(*x509)) { 383 warnx("%s: RFC 3779 extension not set to inherit", fn); 384 goto out; 385 } 386 387 /* get CRL info for later */ 388 if (!x509_get_crl(*x509, fn, &crldp)) 389 goto out; 390 if (crldp == NULL) { 391 warnx("%s: RFC 6487 section 4.8.6: CRL: " 392 "missing CRL distribution point extension", fn); 393 goto out; 394 } 395 if ((crlfile = strrchr(crldp, '/')) == NULL || 396 !valid_mft_filename(crlfile + 1, strlen(crlfile + 1)) || 397 rtype_from_file_extension(crlfile + 1) != RTYPE_CRL) { 398 warnx("%s: RFC 6487 section 4.8.6: CRL: " 399 "bad CRL distribution point extension", fn); 400 goto out; 401 } 402 if ((p.res->crl = strdup(crlfile + 1)) == NULL) 403 err(1, NULL); 404 405 if (mft_parse_econtent(cms, cmsz, &p) == 0) 406 goto out; 407 408 rc = 1; 409out: 410 if (rc == 0) { 411 mft_free(p.res); 412 p.res = NULL; 413 X509_free(*x509); 414 *x509 = NULL; 415 } 416 free(crldp); 417 free(cms); 418 return p.res; 419} 420 421/* 422 * Free an MFT pointer. 423 * Safe to call with NULL. 424 */ 425void 426mft_free(struct mft *p) 427{ 428 size_t i; 429 430 if (p == NULL) 431 return; 432 433 if (p->files != NULL) 434 for (i = 0; i < p->filesz; i++) 435 free(p->files[i].file); 436 437 free(p->aia); 438 free(p->aki); 439 free(p->sia); 440 free(p->ski); 441 free(p->path); 442 free(p->files); 443 free(p->seqnum); 444 free(p); 445} 446 447/* 448 * Serialise MFT parsed content into the given buffer. 449 * See mft_read() for the other side of the pipe. 450 */ 451void 452mft_buffer(struct ibuf *b, const struct mft *p) 453{ 454 size_t i; 455 456 io_simple_buffer(b, &p->stale, sizeof(p->stale)); 457 io_simple_buffer(b, &p->repoid, sizeof(p->repoid)); 458 io_str_buffer(b, p->path); 459 460 io_str_buffer(b, p->aia); 461 io_str_buffer(b, p->aki); 462 io_str_buffer(b, p->ski); 463 464 io_simple_buffer(b, &p->filesz, sizeof(size_t)); 465 for (i = 0; i < p->filesz; i++) { 466 io_str_buffer(b, p->files[i].file); 467 io_simple_buffer(b, &p->files[i].type, 468 sizeof(p->files[i].type)); 469 io_simple_buffer(b, &p->files[i].location, 470 sizeof(p->files[i].location)); 471 io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 472 } 473} 474 475/* 476 * Read an MFT structure from the file descriptor. 477 * Result must be passed to mft_free(). 478 */ 479struct mft * 480mft_read(struct ibuf *b) 481{ 482 struct mft *p = NULL; 483 size_t i; 484 485 if ((p = calloc(1, sizeof(struct mft))) == NULL) 486 err(1, NULL); 487 488 io_read_buf(b, &p->stale, sizeof(p->stale)); 489 io_read_buf(b, &p->repoid, sizeof(p->repoid)); 490 io_read_str(b, &p->path); 491 492 io_read_str(b, &p->aia); 493 io_read_str(b, &p->aki); 494 io_read_str(b, &p->ski); 495 assert(p->aia && p->aki && p->ski); 496 497 io_read_buf(b, &p->filesz, sizeof(size_t)); 498 if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL) 499 err(1, NULL); 500 501 for (i = 0; i < p->filesz; i++) { 502 io_read_str(b, &p->files[i].file); 503 io_read_buf(b, &p->files[i].type, sizeof(p->files[i].type)); 504 io_read_buf(b, &p->files[i].location, 505 sizeof(p->files[i].location)); 506 io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 507 } 508 509 return p; 510} 511 512/* 513 * Compare two MFT files, returns 1 if first MFT is preferred and 0 if second 514 * MFT should be used. 515 */ 516int 517mft_compare(const struct mft *a, const struct mft *b) 518{ 519 int r; 520 521 if (b == NULL) 522 return 1; 523 if (a == NULL) 524 return 0; 525 526 r = strlen(a->seqnum) - strlen(b->seqnum); 527 if (r > 0) /* seqnum in a is longer -> higher */ 528 return 1; 529 if (r < 0) /* seqnum in a is shorter -> smaller */ 530 return 0; 531 532 r = strcmp(a->seqnum, b->seqnum); 533 if (r >= 0) /* a is greater or equal, prefer a */ 534 return 1; 535 return 0; 536} 537