mft.c revision 1.72
1/* $OpenBSD: mft.c,v 1.72 2022/06/10 10:41:09 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 int i, rc = 0; 274 275 if ((mft = d2i_Manifest(NULL, &d, dsz)) == NULL) { 276 cryptowarnx("%s: RFC 6486 section 4: failed to parse Manifest", 277 p->fn); 278 goto out; 279 } 280 281 if (!valid_econtent_version(p->fn, mft->version)) 282 goto out; 283 284 p->res->seqnum = x509_convert_seqnum(p->fn, mft->manifestNumber); 285 if (p->res->seqnum == NULL) 286 goto out; 287 288 /* 289 * Timestamps: this and next update time. 290 * Validate that the current date falls into this interval. 291 * This is required by section 4.4, (3). 292 * If we're after the given date, then the MFT is stale. 293 * This is made super complicated because it uses OpenSSL's 294 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could 295 * compare against the current time trivially. 296 */ 297 298 if (!mft_parse_time(mft->thisUpdate, mft->nextUpdate, p)) 299 goto out; 300 301 if (OBJ_obj2nid(mft->fileHashAlg) != NID_sha256) { 302 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 303 "want SHA256 object, have %s (NID %d)", p->fn, 304 ASN1_tag2str(OBJ_obj2nid(mft->fileHashAlg)), 305 OBJ_obj2nid(mft->fileHashAlg)); 306 goto out; 307 } 308 309 if (sk_FileAndHash_num(mft->fileList) >= MAX_MANIFEST_ENTRIES) { 310 warnx("%s: %d exceeds manifest entry limit (%d)", p->fn, 311 sk_FileAndHash_num(mft->fileList), MAX_MANIFEST_ENTRIES); 312 goto out; 313 } 314 315 p->res->files = calloc(sk_FileAndHash_num(mft->fileList), 316 sizeof(struct mftfile)); 317 if (p->res->files == NULL) 318 err(1, NULL); 319 320 for (i = 0; i < sk_FileAndHash_num(mft->fileList); i++) { 321 fh = sk_FileAndHash_value(mft->fileList, i); 322 if (!mft_parse_filehash(p, fh)) 323 goto out; 324 } 325 326 if (!p->found_crl) { 327 warnx("%s: CRL not part of MFT fileList", p->fn); 328 goto out; 329 } 330 331 rc = 1; 332 out: 333 Manifest_free(mft); 334 return rc; 335} 336 337/* 338 * Parse the objects that have been published in the manifest. 339 * This conforms to RFC 6486. 340 * Note that if the MFT is stale, all referenced objects are stripped 341 * from the parsed content. 342 * The MFT content is otherwise returned. 343 */ 344struct mft * 345mft_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len) 346{ 347 struct parse p; 348 int rc = 0; 349 size_t cmsz; 350 unsigned char *cms; 351 char *crldp = NULL, *crlfile; 352 353 memset(&p, 0, sizeof(struct parse)); 354 p.fn = fn; 355 356 cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz); 357 if (cms == NULL) 358 return NULL; 359 assert(*x509 != NULL); 360 361 if ((p.res = calloc(1, sizeof(struct mft))) == NULL) 362 err(1, NULL); 363 364 if (!x509_get_aia(*x509, fn, &p.res->aia)) 365 goto out; 366 if (!x509_get_aki(*x509, fn, &p.res->aki)) 367 goto out; 368 if (!x509_get_ski(*x509, fn, &p.res->ski)) 369 goto out; 370 if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) { 371 warnx("%s: RFC 6487 section 4.8: " 372 "missing AIA, AKI or SKI X509 extension", fn); 373 goto out; 374 } 375 376 if (!x509_inherits(*x509)) { 377 warnx("%s: RFC 3779 extension not set to inherit", fn); 378 goto out; 379 } 380 381 /* get CRL info for later */ 382 if (!x509_get_crl(*x509, fn, &crldp)) 383 goto out; 384 if (crldp == NULL) { 385 warnx("%s: RFC 6487 section 4.8.6: CRL: " 386 "missing CRL distribution point extension", fn); 387 goto out; 388 } 389 if ((crlfile = strrchr(crldp, '/')) == NULL || 390 !valid_mft_filename(crlfile + 1, strlen(crlfile + 1)) || 391 rtype_from_file_extension(crlfile + 1) != RTYPE_CRL) { 392 warnx("%s: RFC 6487 section 4.8.6: CRL: " 393 "bad CRL distribution point extension", fn); 394 goto out; 395 } 396 if ((p.res->crl = strdup(crlfile + 1)) == NULL) 397 err(1, NULL); 398 399 if (mft_parse_econtent(cms, cmsz, &p) == 0) 400 goto out; 401 402 rc = 1; 403out: 404 if (rc == 0) { 405 mft_free(p.res); 406 p.res = NULL; 407 X509_free(*x509); 408 *x509 = NULL; 409 } 410 free(crldp); 411 free(cms); 412 return p.res; 413} 414 415/* 416 * Free an MFT pointer. 417 * Safe to call with NULL. 418 */ 419void 420mft_free(struct mft *p) 421{ 422 size_t i; 423 424 if (p == NULL) 425 return; 426 427 if (p->files != NULL) 428 for (i = 0; i < p->filesz; i++) 429 free(p->files[i].file); 430 431 free(p->aia); 432 free(p->aki); 433 free(p->ski); 434 free(p->path); 435 free(p->files); 436 free(p->seqnum); 437 free(p); 438} 439 440/* 441 * Serialise MFT parsed content into the given buffer. 442 * See mft_read() for the other side of the pipe. 443 */ 444void 445mft_buffer(struct ibuf *b, const struct mft *p) 446{ 447 size_t i; 448 449 io_simple_buffer(b, &p->stale, sizeof(p->stale)); 450 io_simple_buffer(b, &p->repoid, sizeof(p->repoid)); 451 io_str_buffer(b, p->path); 452 453 io_str_buffer(b, p->aia); 454 io_str_buffer(b, p->aki); 455 io_str_buffer(b, p->ski); 456 457 io_simple_buffer(b, &p->filesz, sizeof(size_t)); 458 for (i = 0; i < p->filesz; i++) { 459 io_str_buffer(b, p->files[i].file); 460 io_simple_buffer(b, &p->files[i].type, 461 sizeof(p->files[i].type)); 462 io_simple_buffer(b, &p->files[i].location, 463 sizeof(p->files[i].location)); 464 io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 465 } 466} 467 468/* 469 * Read an MFT structure from the file descriptor. 470 * Result must be passed to mft_free(). 471 */ 472struct mft * 473mft_read(struct ibuf *b) 474{ 475 struct mft *p = NULL; 476 size_t i; 477 478 if ((p = calloc(1, sizeof(struct mft))) == NULL) 479 err(1, NULL); 480 481 io_read_buf(b, &p->stale, sizeof(p->stale)); 482 io_read_buf(b, &p->repoid, sizeof(p->repoid)); 483 io_read_str(b, &p->path); 484 485 io_read_str(b, &p->aia); 486 io_read_str(b, &p->aki); 487 io_read_str(b, &p->ski); 488 assert(p->aia && p->aki && p->ski); 489 490 io_read_buf(b, &p->filesz, sizeof(size_t)); 491 if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL) 492 err(1, NULL); 493 494 for (i = 0; i < p->filesz; i++) { 495 io_read_str(b, &p->files[i].file); 496 io_read_buf(b, &p->files[i].type, sizeof(p->files[i].type)); 497 io_read_buf(b, &p->files[i].location, 498 sizeof(p->files[i].location)); 499 io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 500 } 501 502 return p; 503} 504 505/* 506 * Compare two MFT files, returns 1 if first MFT is preferred and 0 if second 507 * MFT should be used. 508 */ 509int 510mft_compare(const struct mft *a, const struct mft *b) 511{ 512 int r; 513 514 if (b == NULL) 515 return 1; 516 if (a == NULL) 517 return 0; 518 519 r = strlen(a->seqnum) - strlen(b->seqnum); 520 if (r > 0) /* seqnum in a is longer -> higher */ 521 return 1; 522 if (r < 0) /* seqnum in a is shorter -> smaller */ 523 return 0; 524 525 r = strcmp(a->seqnum, b->seqnum); 526 if (r >= 0) /* a is greater or equal, prefer a */ 527 return 1; 528 return 0; 529} 530