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