1/*	$OpenBSD: mft.c,v 1.117 2024/06/11 10:38:40 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
37extern ASN1_OBJECT	*mft_oid;
38
39/*
40 * Types and templates for the Manifest eContent, RFC 6486, section 4.2.
41 */
42
43ASN1_ITEM_EXP FileAndHash_it;
44ASN1_ITEM_EXP Manifest_it;
45
46typedef struct {
47	ASN1_IA5STRING	*file;
48	ASN1_BIT_STRING	*hash;
49} FileAndHash;
50
51DECLARE_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
63typedef 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
72ASN1_SEQUENCE(FileAndHash) = {
73	ASN1_SIMPLE(FileAndHash, file, ASN1_IA5STRING),
74	ASN1_SIMPLE(FileAndHash, hash, ASN1_BIT_STRING),
75} ASN1_SEQUENCE_END(FileAndHash);
76
77ASN1_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
86DECLARE_ASN1_FUNCTIONS(Manifest);
87IMPLEMENT_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 */
95enum rtype
96rtype_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 */
135static int
136valid_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 */
155static enum rtype
156rtype_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 */
179static int
180mft_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
239static int
240mft_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
250static int
251mft_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 */
263static int
264mft_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 */
313static int
314mft_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", fn,
370		    nid2str(OBJ_obj2nid(mft_asn1->fileHashAlg)));
371		goto out;
372	}
373
374	if (sk_FileAndHash_num(mft_asn1->fileList) >= MAX_MANIFEST_ENTRIES) {
375		warnx("%s: %d exceeds manifest entry limit (%d)", fn,
376		    sk_FileAndHash_num(mft_asn1->fileList),
377		    MAX_MANIFEST_ENTRIES);
378		goto out;
379	}
380
381	mft->files = calloc(sk_FileAndHash_num(mft_asn1->fileList),
382	    sizeof(struct mftfile));
383	if (mft->files == NULL)
384		err(1, NULL);
385
386	found_crl = 0;
387	for (i = 0; i < sk_FileAndHash_num(mft_asn1->fileList); i++) {
388		fh = sk_FileAndHash_value(mft_asn1->fileList, i);
389		if (!mft_parse_filehash(fn, mft, fh, &found_crl))
390			goto out;
391	}
392
393	if (!found_crl) {
394		warnx("%s: CRL not part of MFT fileList", fn);
395		goto out;
396	}
397
398	if (!mft_has_unique_names_and_hashes(fn, mft_asn1))
399		goto out;
400
401	rc = 1;
402 out:
403	Manifest_free(mft_asn1);
404	return rc;
405}
406
407/*
408 * Parse the objects that have been published in the manifest.
409 * Return mft if it conforms to RFC 6486, otherwise NULL.
410 */
411struct mft *
412mft_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
413    size_t len)
414{
415	struct mft	*mft;
416	struct cert	*cert = NULL;
417	int		 rc = 0;
418	size_t		 cmsz;
419	unsigned char	*cms;
420	char		*crldp = NULL, *crlfile;
421	time_t		 signtime = 0;
422
423	cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz, &signtime);
424	if (cms == NULL)
425		return NULL;
426	assert(*x509 != NULL);
427
428	if ((mft = calloc(1, sizeof(*mft))) == NULL)
429		err(1, NULL);
430	mft->signtime = signtime;
431
432	if (!x509_get_aia(*x509, fn, &mft->aia))
433		goto out;
434	if (!x509_get_aki(*x509, fn, &mft->aki))
435		goto out;
436	if (!x509_get_sia(*x509, fn, &mft->sia))
437		goto out;
438	if (!x509_get_ski(*x509, fn, &mft->ski))
439		goto out;
440	if (mft->aia == NULL || mft->aki == NULL || mft->sia == NULL ||
441	    mft->ski == NULL) {
442		warnx("%s: RFC 6487 section 4.8: "
443		    "missing AIA, AKI, SIA, or SKI X509 extension", fn);
444		goto out;
445	}
446
447	if (!x509_inherits(*x509)) {
448		warnx("%s: RFC 3779 extension not set to inherit", fn);
449		goto out;
450	}
451
452	/* get CRL info for later */
453	if (!x509_get_crl(*x509, fn, &crldp))
454		goto out;
455	if (crldp == NULL) {
456		warnx("%s: RFC 6487 section 4.8.6: CRL: "
457		    "missing CRL distribution point extension", fn);
458		goto out;
459	}
460	crlfile = strrchr(crldp, '/');
461	if (crlfile == NULL) {
462		warnx("%s: RFC 6487 section 4.8.6: "
463		    "invalid CRL distribution point", fn);
464		goto out;
465	}
466	crlfile++;
467	if (!valid_mft_filename(crlfile, strlen(crlfile)) ||
468	    rtype_from_file_extension(crlfile) != RTYPE_CRL) {
469		warnx("%s: RFC 6487 section 4.8.6: CRL: "
470		    "bad CRL distribution point extension", fn);
471		goto out;
472	}
473	if ((mft->crl = strdup(crlfile)) == NULL)
474		err(1, NULL);
475
476	if (mft_parse_econtent(fn, mft, cms, cmsz) == 0)
477		goto out;
478
479	if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
480		goto out;
481
482	if (mft->signtime > mft->nextupdate) {
483		warnx("%s: dating issue: CMS signing-time after MFT nextUpdate",
484		    fn);
485		goto out;
486	}
487
488	rc = 1;
489out:
490	if (rc == 0) {
491		mft_free(mft);
492		mft = NULL;
493		X509_free(*x509);
494		*x509 = NULL;
495	}
496	free(crldp);
497	cert_free(cert);
498	free(cms);
499	return mft;
500}
501
502/*
503 * Free an MFT pointer.
504 * Safe to call with NULL.
505 */
506void
507mft_free(struct mft *p)
508{
509	size_t	 i;
510
511	if (p == NULL)
512		return;
513
514	for (i = 0; i < p->filesz; i++)
515		free(p->files[i].file);
516
517	free(p->path);
518	free(p->files);
519	free(p->seqnum);
520	free(p->aia);
521	free(p->aki);
522	free(p->sia);
523	free(p->ski);
524	free(p->crl);
525	free(p);
526}
527
528/*
529 * Serialise MFT parsed content into the given buffer.
530 * See mft_read() for the other side of the pipe.
531 */
532void
533mft_buffer(struct ibuf *b, const struct mft *p)
534{
535	size_t		 i;
536
537	io_simple_buffer(b, &p->repoid, sizeof(p->repoid));
538	io_simple_buffer(b, &p->talid, sizeof(p->talid));
539	io_simple_buffer(b, &p->certid, sizeof(p->certid));
540	io_str_buffer(b, p->path);
541
542	io_str_buffer(b, p->aia);
543	io_str_buffer(b, p->aki);
544	io_str_buffer(b, p->ski);
545
546	io_simple_buffer(b, &p->filesz, sizeof(size_t));
547	for (i = 0; i < p->filesz; i++) {
548		io_str_buffer(b, p->files[i].file);
549		io_simple_buffer(b, &p->files[i].type,
550		    sizeof(p->files[i].type));
551		io_simple_buffer(b, &p->files[i].location,
552		    sizeof(p->files[i].location));
553		io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
554	}
555}
556
557/*
558 * Read an MFT structure from the file descriptor.
559 * Result must be passed to mft_free().
560 */
561struct mft *
562mft_read(struct ibuf *b)
563{
564	struct mft	*p = NULL;
565	size_t		 i;
566
567	if ((p = calloc(1, sizeof(struct mft))) == NULL)
568		err(1, NULL);
569
570	io_read_buf(b, &p->repoid, sizeof(p->repoid));
571	io_read_buf(b, &p->talid, sizeof(p->talid));
572	io_read_buf(b, &p->certid, sizeof(p->certid));
573	io_read_str(b, &p->path);
574
575	io_read_str(b, &p->aia);
576	io_read_str(b, &p->aki);
577	io_read_str(b, &p->ski);
578	assert(p->aia && p->aki && p->ski);
579
580	io_read_buf(b, &p->filesz, sizeof(size_t));
581	if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL)
582		err(1, NULL);
583
584	for (i = 0; i < p->filesz; i++) {
585		io_read_str(b, &p->files[i].file);
586		io_read_buf(b, &p->files[i].type, sizeof(p->files[i].type));
587		io_read_buf(b, &p->files[i].location,
588		    sizeof(p->files[i].location));
589		io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
590	}
591
592	return p;
593}
594
595/*
596 * Compare the thisupdate time of two mft files.
597 */
598int
599mft_compare_issued(const struct mft *a, const struct mft *b)
600{
601	if (a->thisupdate > b->thisupdate)
602		return 1;
603	if (a->thisupdate < b->thisupdate)
604		return -1;
605	return 0;
606}
607
608/*
609 * Compare the manifestNumber of two mft files.
610 */
611int
612mft_compare_seqnum(const struct mft *a, const struct mft *b)
613{
614	int r;
615
616	r = strlen(a->seqnum) - strlen(b->seqnum);
617	if (r > 0)	/* seqnum in a is longer -> higher */
618		return 1;
619	if (r < 0)	/* seqnum in a is shorter -> smaller */
620		return -1;
621
622	r = strcmp(a->seqnum, b->seqnum);
623	if (r > 0)	/* a is greater, prefer a */
624		return 1;
625	if (r < 0)	/* b is greater, prefer b */
626		return -1;
627
628	return 0;
629}
630