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