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