mft.c revision 1.45
1/* $OpenBSD: mft.c,v 1.45 2022/01/13 13:46:03 claudio 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 <err.h> 20#include <limits.h> 21#include <stdarg.h> 22#include <stdint.h> 23#include <stdlib.h> 24#include <string.h> 25#include <unistd.h> 26 27#include <openssl/bn.h> 28#include <openssl/asn1.h> 29#include <openssl/sha.h> 30#include <openssl/x509.h> 31 32#include "extern.h" 33 34/* 35 * Parse results and data of the manifest file. 36 */ 37struct parse { 38 const char *fn; /* manifest file name */ 39 struct mft *res; /* result object */ 40}; 41 42static ASN1_OBJECT *mft_oid; 43 44static const char * 45gentime2str(const ASN1_GENERALIZEDTIME *time) 46{ 47 static char buf[64]; 48 BIO *mem; 49 50 if ((mem = BIO_new(BIO_s_mem())) == NULL) 51 cryptoerrx("BIO_new"); 52 if (!ASN1_GENERALIZEDTIME_print(mem, time)) 53 cryptoerrx("ASN1_GENERALIZEDTIME_print"); 54 if (BIO_gets(mem, buf, sizeof(buf)) < 0) 55 cryptoerrx("BIO_gets"); 56 57 BIO_free(mem); 58 return buf; 59} 60 61/* 62 * Convert an ASN1_GENERALIZEDTIME to a struct tm. 63 * Returns 1 on success, 0 on failure. 64 */ 65static int 66generalizedtime_to_tm(const ASN1_GENERALIZEDTIME *gtime, struct tm *tm) 67{ 68 const char *data; 69 size_t len; 70 71 data = ASN1_STRING_get0_data(gtime); 72 len = ASN1_STRING_length(gtime); 73 74 memset(tm, 0, sizeof(*tm)); 75 return ASN1_time_parse(data, len, tm, V_ASN1_GENERALIZEDTIME) == 76 V_ASN1_GENERALIZEDTIME; 77} 78 79/* 80 * Validate and verify the time validity of the mft. 81 * Returns 1 if all is good, 0 if mft is stale, any other case -1. 82 */ 83static int 84check_validity(const ASN1_GENERALIZEDTIME *from, 85 const ASN1_GENERALIZEDTIME *until, const char *fn) 86{ 87 time_t now = time(NULL); 88 struct tm tm_from, tm_until, tm_now; 89 90 if (gmtime_r(&now, &tm_now) == NULL) { 91 warnx("%s: could not get current time", fn); 92 return -1; 93 } 94 95 if (!generalizedtime_to_tm(from, &tm_from)) { 96 warnx("%s: embedded from time format invalid", fn); 97 return -1; 98 } 99 if (!generalizedtime_to_tm(until, &tm_until)) { 100 warnx("%s: embedded until time format invalid", fn); 101 return -1; 102 } 103 104 /* check that until is not before from */ 105 if (ASN1_time_tm_cmp(&tm_until, &tm_from) < 0) { 106 warnx("%s: bad update interval", fn); 107 return -1; 108 } 109 /* check that now is not before from */ 110 if (ASN1_time_tm_cmp(&tm_from, &tm_now) > 0) { 111 warnx("%s: mft not yet valid %s", fn, gentime2str(from)); 112 return -1; 113 } 114 /* check that now is not after until */ 115 if (ASN1_time_tm_cmp(&tm_until, &tm_now) < 0) { 116 warnx("%s: mft expired on %s", fn, gentime2str(until)); 117 return 0; 118 } 119 120 return 1; 121} 122 123/* 124 * Parse an individual "FileAndHash", RFC 6486, sec. 4.2. 125 * Return zero on failure, non-zero on success. 126 */ 127static int 128mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os) 129{ 130 ASN1_SEQUENCE_ANY *seq; 131 const ASN1_TYPE *file, *hash; 132 char *fn = NULL; 133 const unsigned char *d = os->data; 134 size_t dsz = os->length; 135 int rc = 0; 136 struct mftfile *fent; 137 138 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 139 cryptowarnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 140 "failed ASN.1 sequence parse", p->fn); 141 goto out; 142 } else if (sk_ASN1_TYPE_num(seq) != 2) { 143 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 144 "want 2 elements, have %d", p->fn, 145 sk_ASN1_TYPE_num(seq)); 146 goto out; 147 } 148 149 /* First is the filename itself. */ 150 151 file = sk_ASN1_TYPE_value(seq, 0); 152 if (file->type != V_ASN1_IA5STRING) { 153 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 154 "want ASN.1 IA5 string, have %s (NID %d)", 155 p->fn, ASN1_tag2str(file->type), file->type); 156 goto out; 157 } 158 fn = strndup((const char *)file->value.ia5string->data, 159 file->value.ia5string->length); 160 if (fn == NULL) 161 err(1, NULL); 162 163 if (!valid_filename(fn)) { 164 warnx("%s: invalid filename: %s", p->fn, fn); 165 goto out; 166 } 167 168 /* Now hash value. */ 169 170 hash = sk_ASN1_TYPE_value(seq, 1); 171 if (hash->type != V_ASN1_BIT_STRING) { 172 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 173 "want ASN.1 bit string, have %s (NID %d)", 174 p->fn, ASN1_tag2str(hash->type), hash->type); 175 goto out; 176 } 177 178 if (hash->value.bit_string->length != SHA256_DIGEST_LENGTH) { 179 warnx("%s: RFC 6486 section 4.2.1: hash: " 180 "invalid SHA256 length, have %d", 181 p->fn, hash->value.bit_string->length); 182 goto out; 183 } 184 185 /* Insert the filename and hash value. */ 186 fent = &p->res->files[p->res->filesz++]; 187 188 fent->file = fn; 189 fn = NULL; 190 memcpy(fent->hash, hash->value.bit_string->data, SHA256_DIGEST_LENGTH); 191 192 rc = 1; 193out: 194 free(fn); 195 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 196 return rc; 197} 198 199/* 200 * Parse the "FileAndHash" sequence, RFC 6486, sec. 4.2. 201 * Return zero on failure, non-zero on success. 202 */ 203static int 204mft_parse_flist(struct parse *p, const ASN1_OCTET_STRING *os) 205{ 206 ASN1_SEQUENCE_ANY *seq; 207 const ASN1_TYPE *t; 208 const unsigned char *d = os->data; 209 size_t dsz = os->length; 210 int i, rc = 0; 211 212 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 213 cryptowarnx("%s: RFC 6486 section 4.2: fileList: " 214 "failed ASN.1 sequence parse", p->fn); 215 goto out; 216 } 217 218 if (sk_ASN1_TYPE_num(seq) > MAX_MANIFEST_ENTRIES) { 219 warnx("%s: %d exceeds manifest entry limit (%d)", p->fn, 220 sk_ASN1_TYPE_num(seq), MAX_MANIFEST_ENTRIES); 221 goto out; 222 } 223 224 p->res->files = calloc(sk_ASN1_TYPE_num(seq), sizeof(struct mftfile)); 225 if (p->res->files == NULL) 226 err(1, NULL); 227 228 for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { 229 t = sk_ASN1_TYPE_value(seq, i); 230 if (t->type != V_ASN1_SEQUENCE) { 231 warnx("%s: RFC 6486 section 4.2: fileList: " 232 "want ASN.1 sequence, have %s (NID %d)", 233 p->fn, ASN1_tag2str(t->type), t->type); 234 goto out; 235 } else if (!mft_parse_filehash(p, t->value.octet_string)) 236 goto out; 237 } 238 239 rc = 1; 240 out: 241 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 242 return rc; 243} 244 245/* 246 * Handle the eContent of the manifest object, RFC 6486 sec. 4.2. 247 * Returns 0 on failure and 1 on success. 248 */ 249static int 250mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p) 251{ 252 ASN1_SEQUENCE_ANY *seq; 253 const ASN1_TYPE *t; 254 const ASN1_GENERALIZEDTIME *from, *until; 255 long mft_version; 256 BIGNUM *mft_seqnum = NULL; 257 int i = 0, rc = 0; 258 259 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 260 cryptowarnx("%s: RFC 6486 section 4.2: Manifest: " 261 "failed ASN.1 sequence parse", p->fn); 262 goto out; 263 } 264 265 /* Test if the optional profile version field is present. */ 266 if (sk_ASN1_TYPE_num(seq) != 5 && 267 sk_ASN1_TYPE_num(seq) != 6) { 268 warnx("%s: RFC 6486 section 4.2: Manifest: " 269 "want 5 or 6 elements, have %d", p->fn, 270 sk_ASN1_TYPE_num(seq)); 271 goto out; 272 } 273 274 /* Parse the optional version field */ 275 if (sk_ASN1_TYPE_num(seq) == 6) { 276 t = sk_ASN1_TYPE_value(seq, i++); 277 d = t->value.asn1_string->data; 278 dsz = t->value.asn1_string->length; 279 280 if (cms_econtent_version(p->fn, &d, dsz, &mft_version) == -1) 281 goto out; 282 283 switch (mft_version) { 284 case 0: 285 warnx("%s: incorrect encoding for version 0", p->fn); 286 goto out; 287 default: 288 warnx("%s: version %ld not supported (yet)", p->fn, 289 mft_version); 290 goto out; 291 } 292 } 293 294 /* Now the manifest sequence number. */ 295 296 t = sk_ASN1_TYPE_value(seq, i++); 297 if (t->type != V_ASN1_INTEGER) { 298 warnx("%s: RFC 6486 section 4.2.1: manifestNumber: " 299 "want ASN.1 integer, have %s (NID %d)", 300 p->fn, ASN1_tag2str(t->type), t->type); 301 goto out; 302 } 303 304 mft_seqnum = ASN1_INTEGER_to_BN(t->value.integer, NULL); 305 if (mft_seqnum == NULL) { 306 warnx("%s: ASN1_INTEGER_to_BN error", p->fn); 307 goto out; 308 } 309 310 if (BN_is_negative(mft_seqnum)) { 311 warnx("%s: RFC 6486 section 4.2.1: manifestNumber: " 312 "want positive integer, have negative.", p->fn); 313 goto out; 314 } 315 316 if (BN_num_bytes(mft_seqnum) > 20) { 317 warnx("%s: RFC 6486 section 4.2.1: manifestNumber: " 318 "want 20 or less than octets, have more.", p->fn); 319 goto out; 320 } 321 322 p->res->seqnum = BN_bn2hex(mft_seqnum); 323 if (p->res->seqnum == NULL) { 324 warnx("%s: BN_bn2hex error", p->fn); 325 goto out; 326 } 327 328 /* 329 * Timestamps: this and next update time. 330 * Validate that the current date falls into this interval. 331 * This is required by section 4.4, (3). 332 * If we're after the given date, then the MFT is stale. 333 * This is made super complicated because it uses OpenSSL's 334 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could 335 * compare against the current time trivially. 336 */ 337 338 t = sk_ASN1_TYPE_value(seq, i++); 339 if (t->type != V_ASN1_GENERALIZEDTIME) { 340 warnx("%s: RFC 6486 section 4.2.1: thisUpdate: " 341 "want ASN.1 generalised time, have %s (NID %d)", 342 p->fn, ASN1_tag2str(t->type), t->type); 343 goto out; 344 } 345 from = t->value.generalizedtime; 346 347 t = sk_ASN1_TYPE_value(seq, i++); 348 if (t->type != V_ASN1_GENERALIZEDTIME) { 349 warnx("%s: RFC 6486 section 4.2.1: nextUpdate: " 350 "want ASN.1 generalised time, have %s (NID %d)", 351 p->fn, ASN1_tag2str(t->type), t->type); 352 goto out; 353 } 354 until = t->value.generalizedtime; 355 356 switch (check_validity(from, until, p->fn)) { 357 case 0: 358 p->res->stale = 1; 359 /* FALLTHROUGH */ 360 case 1: 361 break; 362 case -1: 363 goto out; 364 } 365 366 /* File list algorithm. */ 367 368 t = sk_ASN1_TYPE_value(seq, i++); 369 if (t->type != V_ASN1_OBJECT) { 370 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 371 "want ASN.1 object, have %s (NID %d)", 372 p->fn, ASN1_tag2str(t->type), t->type); 373 goto out; 374 } 375 if (OBJ_obj2nid(t->value.object) != NID_sha256) { 376 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 377 "want SHA256 object, have %s (NID %d)", p->fn, 378 ASN1_tag2str(OBJ_obj2nid(t->value.object)), 379 OBJ_obj2nid(t->value.object)); 380 goto out; 381 } 382 383 /* Now the sequence. */ 384 385 t = sk_ASN1_TYPE_value(seq, i++); 386 if (t->type != V_ASN1_SEQUENCE) { 387 warnx("%s: RFC 6486 section 4.2.1: fileList: " 388 "want ASN.1 sequence, have %s (NID %d)", 389 p->fn, ASN1_tag2str(t->type), t->type); 390 goto out; 391 } 392 393 if (!mft_parse_flist(p, t->value.octet_string)) 394 goto out; 395 396 rc = 1; 397out: 398 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 399 BN_free(mft_seqnum); 400 return rc; 401} 402 403/* 404 * Parse the objects that have been published in the manifest. 405 * This conforms to RFC 6486. 406 * Note that if the MFT is stale, all referenced objects are stripped 407 * from the parsed content. 408 * The MFT content is otherwise returned. 409 */ 410struct mft * 411mft_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len) 412{ 413 struct parse p; 414 int rc = 0; 415 size_t cmsz; 416 unsigned char *cms; 417 418 memset(&p, 0, sizeof(struct parse)); 419 p.fn = fn; 420 421 if (mft_oid == NULL) { 422 mft_oid = OBJ_txt2obj("1.2.840.113549.1.9.16.1.26", 1); 423 if (mft_oid == NULL) 424 errx(1, "OBJ_txt2obj for %s failed", 425 "1.2.840.113549.1.9.16.1.26"); 426 } 427 428 cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz); 429 if (cms == NULL) 430 return NULL; 431 assert(*x509 != NULL); 432 433 if ((p.res = calloc(1, sizeof(struct mft))) == NULL) 434 err(1, NULL); 435 436 p.res->aia = x509_get_aia(*x509, fn); 437 p.res->aki = x509_get_aki(*x509, 0, fn); 438 p.res->ski = x509_get_ski(*x509, fn); 439 if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) { 440 warnx("%s: RFC 6487 section 4.8: " 441 "missing AIA, AKI or SKI X509 extension", fn); 442 goto out; 443 } 444 445 if (mft_parse_econtent(cms, cmsz, &p) == 0) 446 goto out; 447 448 rc = 1; 449out: 450 if (rc == 0) { 451 mft_free(p.res); 452 p.res = NULL; 453 X509_free(*x509); 454 *x509 = NULL; 455 } 456 free(cms); 457 return p.res; 458} 459 460/* 461 * Free an MFT pointer. 462 * Safe to call with NULL. 463 */ 464void 465mft_free(struct mft *p) 466{ 467 size_t i; 468 469 if (p == NULL) 470 return; 471 472 if (p->files != NULL) 473 for (i = 0; i < p->filesz; i++) 474 free(p->files[i].file); 475 476 free(p->aia); 477 free(p->aki); 478 free(p->ski); 479 free(p->path); 480 free(p->files); 481 free(p->seqnum); 482 free(p); 483} 484 485/* 486 * Serialise MFT parsed content into the given buffer. 487 * See mft_read() for the other side of the pipe. 488 */ 489void 490mft_buffer(struct ibuf *b, const struct mft *p) 491{ 492 size_t i; 493 494 io_simple_buffer(b, &p->stale, sizeof(p->stale)); 495 io_simple_buffer(b, &p->repoid, sizeof(p->repoid)); 496 io_str_buffer(b, p->path); 497 498 io_str_buffer(b, p->aia); 499 io_str_buffer(b, p->aki); 500 io_str_buffer(b, p->ski); 501 502 io_simple_buffer(b, &p->filesz, sizeof(size_t)); 503 for (i = 0; i < p->filesz; i++) { 504 io_str_buffer(b, p->files[i].file); 505 io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 506 } 507} 508 509/* 510 * Read an MFT structure from the file descriptor. 511 * Result must be passed to mft_free(). 512 */ 513struct mft * 514mft_read(struct ibuf *b) 515{ 516 struct mft *p = NULL; 517 size_t i; 518 519 if ((p = calloc(1, sizeof(struct mft))) == NULL) 520 err(1, NULL); 521 522 io_read_buf(b, &p->stale, sizeof(p->stale)); 523 io_read_buf(b, &p->repoid, sizeof(p->repoid)); 524 io_read_str(b, &p->path); 525 526 io_read_str(b, &p->aia); 527 io_read_str(b, &p->aki); 528 io_read_str(b, &p->ski); 529 assert(p->aia && p->aki && p->ski); 530 531 io_read_buf(b, &p->filesz, sizeof(size_t)); 532 if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL) 533 err(1, NULL); 534 535 for (i = 0; i < p->filesz; i++) { 536 io_read_str(b, &p->files[i].file); 537 io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH); 538 } 539 540 return p; 541} 542