mft.c revision 1.12
1/* $OpenBSD: mft.c,v 1.12 2020/03/30 12:12:51 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 <stdarg.h> 21#include <stdint.h> 22#include <stdlib.h> 23#include <string.h> 24#include <unistd.h> 25#include <sha2.h> 26 27#include <openssl/ssl.h> 28 29#include "extern.h" 30 31/* 32 * Parse results and data of the manifest file. 33 */ 34struct parse { 35 const char *fn; /* manifest file name */ 36 struct mft *res; /* result object */ 37}; 38 39static const char * 40gentime2str(const ASN1_GENERALIZEDTIME *time) 41{ 42 static char buf[64]; 43 BIO *mem; 44 45 if ((mem = BIO_new(BIO_s_mem())) == NULL) 46 cryptoerrx("BIO_new"); 47 if (!ASN1_GENERALIZEDTIME_print(mem, time)) 48 cryptoerrx("ASN1_GENERALIZEDTIME_print"); 49 if (BIO_gets(mem, buf, sizeof(buf)) < 0) 50 cryptoerrx("BIO_gets"); 51 52 BIO_free(mem); 53 return buf; 54} 55 56/* 57 * Validate and verify the time validity of the mft. 58 * Returns 1 if all is good, 0 if mft is stale, any other case -1. 59 * XXX should use ASN1_time_tm_cmp() once libressl is used. 60 */ 61static time_t 62check_validity(const ASN1_GENERALIZEDTIME *from, 63 const ASN1_GENERALIZEDTIME *until, const char *fn, int force) 64{ 65 time_t now = time(NULL); 66 67 if (!ASN1_GENERALIZEDTIME_check(from) || 68 !ASN1_GENERALIZEDTIME_check(until)) { 69 warnx("%s: embedded time format invalid", fn); 70 return -1; 71 } 72 /* check that until is not before from */ 73 if (ASN1_STRING_cmp(until, from) < 0) { 74 warnx("%s: bad update interval", fn); 75 return -1; 76 } 77 /* check that now is not before from */ 78 if (X509_cmp_time(from, &now) > 0) { 79 warnx("%s: mft not yet valid %s", fn, gentime2str(from)); 80 return -1; 81 } 82 /* check that now is not after until */ 83 if (X509_cmp_time(until, &now) < 0) { 84 warnx("%s: mft expired on %s%s", fn, gentime2str(until), 85 force ? " (ignoring)" : ""); 86 if (!force) 87 return 0; 88 } 89 90 return 1; 91} 92 93static int 94hex_pton(u_char const *src, size_t srclen, char *target, size_t targlen) 95{ 96 static const char hex[] = "0123456789abcdef"; 97 size_t i, t = 0; 98 99 for (i = 0; i < srclen; i++) { 100 if (t + 1 >= targlen) 101 return -1; 102 target[t++] = hex[src[i] >> 4]; 103 target[t++] = hex[src[i] & 0x0f]; 104 } 105 if (t >= targlen) 106 return -1; 107 target[t] = '\0'; 108 109 return t; 110} 111 112 113/* 114 * Parse an individual "FileAndHash", RFC 6486, sec. 4.2. 115 * Return zero on failure, non-zero on success. 116 */ 117static int 118mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os, int just_check) 119{ 120 char file_hash[SHA256_DIGEST_STRING_LENGTH]; 121 char cert_hash[SHA256_DIGEST_STRING_LENGTH]; 122 ASN1_SEQUENCE_ANY *seq; 123 const ASN1_TYPE *file, *hash; 124 char *fn = NULL; 125 const unsigned char *d = os->data; 126 size_t dsz = os->length, sz; 127 struct mftfile *fent; 128 char *cp, *path = NULL; 129 130 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 131 cryptowarnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 132 "failed ASN.1 sequence parse", p->fn); 133 goto fail; 134 } else if (sk_ASN1_TYPE_num(seq) != 2) { 135 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 136 "want 2 elements, have %d", p->fn, 137 sk_ASN1_TYPE_num(seq)); 138 goto fail; 139 } 140 141 /* First is the filename itself. */ 142 143 file = sk_ASN1_TYPE_value(seq, 0); 144 if (file->type != V_ASN1_IA5STRING) { 145 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 146 "want ASN.1 IA5 string, have %s (NID %d)", 147 p->fn, ASN1_tag2str(file->type), file->type); 148 goto fail; 149 } 150 fn = strndup((const char *)file->value.ia5string->data, 151 file->value.ia5string->length); 152 if (fn == NULL) 153 err(1, NULL); 154 155 /* 156 * Make sure we're just a pathname and either an ROA or CER. 157 * I don't think that the RFC specifically mentions this, but 158 * it's in practical use and would really screw things up 159 * (arbitrary filenames) otherwise. 160 */ 161 162 if (strchr(fn, '/') != NULL) { 163 warnx("%s: path components disallowed in filename: %s", 164 p->fn, fn); 165 goto fail; 166 } else if ((sz = strlen(fn)) <= 4) { 167 warnx("%s: filename must be large enough for suffix part: %s", 168 p->fn, fn); 169 goto fail; 170 } 171 172 if (strcasecmp(fn + sz - 4, ".roa") && 173 strcasecmp(fn + sz - 4, ".crl") && 174 strcasecmp(fn + sz - 4, ".cer")) { 175 /* ignore unknown files */ 176 free(fn); 177 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 178 return 1; 179 } 180 181 /* Now hash value. */ 182 hash = sk_ASN1_TYPE_value(seq, 1); 183 if (hash->type != V_ASN1_BIT_STRING) { 184 warnx("%s: RFC 6486 section 4.2.1: FileAndHash: " 185 "want ASN.1 bit string, have %s (NID %d)", 186 p->fn, ASN1_tag2str(hash->type), hash->type); 187 goto fail; 188 } 189 190 if (hash->value.bit_string->length != SHA256_DIGEST_LENGTH) { 191 warnx("%s: RFC 6486 section 4.2.1: hash: " 192 "invalid SHA256 length, have %d", 193 p->fn, hash->value.bit_string->length); 194 goto fail; 195 } 196 197 if (hex_pton(hash->value.bit_string->data, SHA256_DIGEST_LENGTH, 198 cert_hash, sizeof(cert_hash)) == -1) 199 errx(1, "hexnum conversion failed"); 200 201 /* Check hash of file now, but first build path for it */ 202 cp = strrchr(p->fn, '/'); 203 assert(cp != NULL); 204 if (asprintf(&path, "%.*s/%s", (int)(cp - p->fn), p->fn, fn) == -1) 205 err(1, "asprintf"); 206 207 if (SHA256File(path, file_hash) == NULL) { 208 warn("%s: referenced file %s", p->fn, fn); 209 goto fail; 210 } 211 free(path); 212 path = NULL; 213 214 if (strcmp(file_hash, cert_hash) != 0) { 215 warnx("%s: bad message digest for %s", p->fn, fn); 216 goto fail; 217 } 218 219 if (just_check) 220 goto fail; 221 222 /* Insert the filename and hash value. */ 223 224 p->res->files = reallocarray(p->res->files, p->res->filesz + 1, 225 sizeof(struct mftfile)); 226 if (p->res->files == NULL) 227 err(1, NULL); 228 229 fent = &p->res->files[p->res->filesz++]; 230 memset(fent, 0, sizeof(struct mftfile)); 231 232 fent->file = fn; 233 memcpy(fent->hash, hash->value.bit_string->data, SHA256_DIGEST_LENGTH); 234 235 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 236 return 1; 237 238fail: 239 free(path); 240 free(fn); 241 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 242 return 0; 243} 244 245/* 246 * Parse the "FileAndHash" sequence, RFC 6486, sec. 4.2. 247 * Return zero on failure, non-zero on success. 248 */ 249static int 250mft_parse_flist(struct parse *p, const ASN1_OCTET_STRING *os) 251{ 252 ASN1_SEQUENCE_ANY *seq; 253 const ASN1_TYPE *t; 254 const unsigned char *d = os->data; 255 size_t dsz = os->length; 256 int i, rc = 0, failed = 0; 257 258 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 259 cryptowarnx("%s: RFC 6486 section 4.2: fileList: " 260 "failed ASN.1 sequence parse", p->fn); 261 goto out; 262 } 263 264 for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) { 265 t = sk_ASN1_TYPE_value(seq, i); 266 if (t->type != V_ASN1_SEQUENCE) { 267 warnx("%s: RFC 6486 section 4.2: fileList: " 268 "want ASN.1 sequence, have %s (NID %d)", 269 p->fn, ASN1_tag2str(t->type), t->type); 270 goto out; 271 } else if (!mft_parse_filehash(p, t->value.octet_string, 272 failed)) 273 failed = 1; 274 } 275 if (failed) 276 goto out; 277 278 rc = 1; 279out: 280 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 281 return rc; 282} 283 284/* 285 * Handle the eContent of the manifest object, RFC 6486 sec. 4.2. 286 * Returns <0 on failure, 0 on stale, >0 on success. 287 */ 288static int 289mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p, int force) 290{ 291 ASN1_SEQUENCE_ANY *seq; 292 const ASN1_TYPE *t; 293 const ASN1_GENERALIZEDTIME *from, *until; 294 int i, rc = -1; 295 296 if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) { 297 cryptowarnx("%s: RFC 6486 section 4.2: Manifest: " 298 "failed ASN.1 sequence parse", p->fn); 299 goto out; 300 } 301 302 /* The version is optional. */ 303 304 if (sk_ASN1_TYPE_num(seq) != 5 && 305 sk_ASN1_TYPE_num(seq) != 6) { 306 warnx("%s: RFC 6486 section 4.2: Manifest: " 307 "want 5 or 6 elements, have %d", p->fn, 308 sk_ASN1_TYPE_num(seq)); 309 goto out; 310 } 311 312 /* Start with optional version. */ 313 314 i = 0; 315 if (sk_ASN1_TYPE_num(seq) == 6) { 316 t = sk_ASN1_TYPE_value(seq, i++); 317 if (t->type != V_ASN1_INTEGER) { 318 warnx("%s: RFC 6486 section 4.2.1: version: " 319 "want ASN.1 integer, have %s (NID %d)", 320 p->fn, ASN1_tag2str(t->type), t->type); 321 goto out; 322 } 323 } 324 325 /* Now the manifest sequence number. */ 326 327 t = sk_ASN1_TYPE_value(seq, i++); 328 if (t->type != V_ASN1_INTEGER) { 329 warnx("%s: RFC 6486 section 4.2.1: manifestNumber: " 330 "want ASN.1 integer, have %s (NID %d)", 331 p->fn, ASN1_tag2str(t->type), t->type); 332 goto out; 333 } 334 335 /* 336 * Timestamps: this and next update time. 337 * Validate that the current date falls into this interval. 338 * This is required by section 4.4, (3). 339 * If we're after the given date, then the MFT is stale. 340 * This is made super complicated because it usees OpenSSL's 341 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could 342 * compare against the current time trivially. 343 */ 344 345 t = sk_ASN1_TYPE_value(seq, i++); 346 if (t->type != V_ASN1_GENERALIZEDTIME) { 347 warnx("%s: RFC 6486 section 4.2.1: thisUpdate: " 348 "want ASN.1 generalised time, have %s (NID %d)", 349 p->fn, ASN1_tag2str(t->type), t->type); 350 goto out; 351 } 352 from = t->value.generalizedtime; 353 354 t = sk_ASN1_TYPE_value(seq, i++); 355 if (t->type != V_ASN1_GENERALIZEDTIME) { 356 warnx("%s: RFC 6486 section 4.2.1: nextUpdate: " 357 "want ASN.1 generalised time, have %s (NID %d)", 358 p->fn, ASN1_tag2str(t->type), t->type); 359 goto out; 360 } 361 until = t->value.generalizedtime; 362 363 rc = check_validity(from, until, p->fn, force); 364 if (rc != 1) 365 goto out; 366 367 /* File list algorithm. */ 368 369 t = sk_ASN1_TYPE_value(seq, i++); 370 if (t->type != V_ASN1_OBJECT) { 371 warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: " 372 "want ASN.1 object time, have %s (NID %d)", 373 p->fn, ASN1_tag2str(t->type), t->type); 374 goto out; 375 } else 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 } else if (!mft_parse_flist(p, t->value.octet_string)) 392 goto out; 393 394 rc = 1; 395out: 396 sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free); 397 return rc; 398} 399 400/* 401 * Parse the objects that have been published in the manifest. 402 * This conforms to RFC 6486. 403 * Note that if the MFT is stale, all referenced objects are stripped 404 * from the parsed content. 405 * The MFT content is otherwise returned. 406 */ 407struct mft * 408mft_parse(X509 **x509, const char *fn, int force) 409{ 410 struct parse p; 411 int c, rc = 0; 412 size_t i, cmsz; 413 unsigned char *cms; 414 415 memset(&p, 0, sizeof(struct parse)); 416 p.fn = fn; 417 418 cms = cms_parse_validate(x509, fn, "1.2.840.113549.1.9.16.1.26", 419 NULL, &cmsz); 420 if (cms == NULL) 421 return NULL; 422 assert(*x509 != NULL); 423 424 if ((p.res = calloc(1, sizeof(struct mft))) == NULL) 425 err(1, NULL); 426 if ((p.res->file = strdup(fn)) == NULL) 427 err(1, NULL); 428 if (!x509_get_ski_aki(*x509, fn, &p.res->ski, &p.res->aki)) 429 goto out; 430 431 /* 432 * If we're stale, then remove all of the files that the MFT 433 * references as well as marking it as stale. 434 */ 435 436 if ((c = mft_parse_econtent(cms, cmsz, &p, force)) == 0) { 437 /* 438 * FIXME: it should suffice to just mark this as stale 439 * and have the logic around mft_read() simply ignore 440 * the contents of stale entries, just like it does for 441 * invalid ROAs or certificates. 442 */ 443 444 p.res->stale = 1; 445 if (p.res->files != NULL) 446 for (i = 0; i < p.res->filesz; i++) 447 free(p.res->files[i].file); 448 free(p.res->files); 449 p.res->filesz = 0; 450 p.res->files = NULL; 451 } else if (c == -1) 452 goto out; 453 454 rc = 1; 455out: 456 if (rc == 0) { 457 mft_free(p.res); 458 p.res = NULL; 459 X509_free(*x509); 460 *x509 = NULL; 461 } 462 free(cms); 463 return p.res; 464} 465 466/* 467 * Free an MFT pointer. 468 * Safe to call with NULL. 469 */ 470void 471mft_free(struct mft *p) 472{ 473 size_t i; 474 475 if (p == NULL) 476 return; 477 478 if (p->files != NULL) 479 for (i = 0; i < p->filesz; i++) 480 free(p->files[i].file); 481 482 free(p->aki); 483 free(p->ski); 484 free(p->file); 485 free(p->files); 486 free(p); 487} 488 489/* 490 * Serialise MFT parsed content into the given buffer. 491 * See mft_read() for the other side of the pipe. 492 */ 493void 494mft_buffer(char **b, size_t *bsz, size_t *bmax, const struct mft *p) 495{ 496 size_t i; 497 498 io_simple_buffer(b, bsz, bmax, &p->stale, sizeof(int)); 499 io_str_buffer(b, bsz, bmax, p->file); 500 io_simple_buffer(b, bsz, bmax, &p->filesz, sizeof(size_t)); 501 502 for (i = 0; i < p->filesz; i++) { 503 io_str_buffer(b, bsz, bmax, p->files[i].file); 504 io_simple_buffer(b, bsz, bmax, 505 p->files[i].hash, SHA256_DIGEST_LENGTH); 506 } 507 508 io_str_buffer(b, bsz, bmax, p->aki); 509 io_str_buffer(b, bsz, bmax, p->ski); 510} 511 512/* 513 * Read an MFT structure from the file descriptor. 514 * Result must be passed to mft_free(). 515 */ 516struct mft * 517mft_read(int fd) 518{ 519 struct mft *p = NULL; 520 size_t i; 521 522 if ((p = calloc(1, sizeof(struct mft))) == NULL) 523 err(1, NULL); 524 525 io_simple_read(fd, &p->stale, sizeof(int)); 526 io_str_read(fd, &p->file); 527 io_simple_read(fd, &p->filesz, sizeof(size_t)); 528 529 if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL) 530 err(1, NULL); 531 532 for (i = 0; i < p->filesz; i++) { 533 io_str_read(fd, &p->files[i].file); 534 io_simple_read(fd, p->files[i].hash, SHA256_DIGEST_LENGTH); 535 } 536 537 io_str_read(fd, &p->aki); 538 io_str_read(fd, &p->ski); 539 return p; 540} 541