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