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