mft.c revision 1.83
1/*	$OpenBSD: mft.c,v 1.83 2023/03/09 09:46:21 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/*
91 * Convert an ASN1_GENERALIZEDTIME to a struct tm.
92 * Returns 1 on success, 0 on failure.
93 */
94static int
95generalizedtime_to_tm(const ASN1_GENERALIZEDTIME *gtime, struct tm *tm)
96{
97	const char *data;
98	size_t len;
99
100	data = ASN1_STRING_get0_data(gtime);
101	len = ASN1_STRING_length(gtime);
102
103	memset(tm, 0, sizeof(*tm));
104	return ASN1_time_parse(data, len, tm, V_ASN1_GENERALIZEDTIME) ==
105	    V_ASN1_GENERALIZEDTIME;
106}
107
108/*
109 * Validate and verify the time validity of the mft.
110 * Returns 1 if all is good and for any other case 0.
111 */
112static int
113mft_parse_time(const ASN1_GENERALIZEDTIME *from,
114    const ASN1_GENERALIZEDTIME *until, struct parse *p)
115{
116	struct tm tm_from, tm_until;
117
118	if (!generalizedtime_to_tm(from, &tm_from)) {
119		warnx("%s: embedded from time format invalid", p->fn);
120		return 0;
121	}
122	if (!generalizedtime_to_tm(until, &tm_until)) {
123		warnx("%s: embedded until time format invalid", p->fn);
124		return 0;
125	}
126
127	/* check that until is not before from */
128	if (ASN1_time_tm_cmp(&tm_until, &tm_from) < 0) {
129		warnx("%s: bad update interval", p->fn);
130		return 0;
131	}
132
133	if ((p->res->valid_since = timegm(&tm_from)) == -1 ||
134	    (p->res->valid_until = timegm(&tm_until)) == -1)
135		errx(1, "%s: timegm failed", p->fn);
136
137	return 1;
138}
139
140/*
141 * Determine rtype corresponding to file extension. Returns RTYPE_INVALID
142 * on error or unkown extension.
143 */
144enum rtype
145rtype_from_file_extension(const char *fn)
146{
147	size_t	 sz;
148
149	sz = strlen(fn);
150	if (sz < 5)
151		return RTYPE_INVALID;
152
153	if (strcasecmp(fn + sz - 4, ".tal") == 0)
154		return RTYPE_TAL;
155	if (strcasecmp(fn + sz - 4, ".cer") == 0)
156		return RTYPE_CER;
157	if (strcasecmp(fn + sz - 4, ".crl") == 0)
158		return RTYPE_CRL;
159	if (strcasecmp(fn + sz - 4, ".mft") == 0)
160		return RTYPE_MFT;
161	if (strcasecmp(fn + sz - 4, ".roa") == 0)
162		return RTYPE_ROA;
163	if (strcasecmp(fn + sz - 4, ".gbr") == 0)
164		return RTYPE_GBR;
165	if (strcasecmp(fn + sz - 4, ".sig") == 0)
166		return RTYPE_RSC;
167	if (strcasecmp(fn + sz - 4, ".asa") == 0)
168		return RTYPE_ASPA;
169	if (strcasecmp(fn + sz - 4, ".tak") == 0)
170		return RTYPE_TAK;
171	if (strcasecmp(fn + sz - 4, ".csv") == 0)
172		return RTYPE_GEOFEED;
173
174	return RTYPE_INVALID;
175}
176
177/*
178 * Validate that a filename listed on a Manifest only contains characters
179 * permitted in draft-ietf-sidrops-6486bis section 4.2.2
180 * Also ensure that there is exactly one '.'.
181 */
182static int
183valid_mft_filename(const char *fn, size_t len)
184{
185	const unsigned char *c;
186
187	if (!valid_filename(fn, len))
188		return 0;
189
190	c = memchr(fn, '.', len);
191	if (c == NULL || c != memrchr(fn, '.', len))
192		return 0;
193
194	return 1;
195}
196
197/*
198 * Check that the file is allowed to be part of a manifest and the parser
199 * for this type is implemented in rpki-client.
200 * Returns corresponding rtype or RTYPE_INVALID to mark the file as unknown.
201 */
202static enum rtype
203rtype_from_mftfile(const char *fn)
204{
205	enum rtype		 type;
206
207	type = rtype_from_file_extension(fn);
208	switch (type) {
209	case RTYPE_CER:
210	case RTYPE_CRL:
211	case RTYPE_GBR:
212	case RTYPE_ROA:
213	case RTYPE_ASPA:
214	case RTYPE_TAK:
215		return type;
216	default:
217		return RTYPE_INVALID;
218	}
219}
220
221/*
222 * Parse an individual "FileAndHash", RFC 6486, sec. 4.2.
223 * Return zero on failure, non-zero on success.
224 */
225static int
226mft_parse_filehash(struct parse *p, const FileAndHash *fh)
227{
228	char			*fn = NULL;
229	int			 rc = 0;
230	struct mftfile		*fent;
231	enum rtype		 type;
232
233	if (!valid_mft_filename(fh->file->data, fh->file->length)) {
234		warnx("%s: RFC 6486 section 4.2.2: bad filename", p->fn);
235		goto out;
236	}
237	fn = strndup(fh->file->data, fh->file->length);
238	if (fn == NULL)
239		err(1, NULL);
240
241	if (fh->hash->length != SHA256_DIGEST_LENGTH) {
242		warnx("%s: RFC 6486 section 4.2.1: hash: "
243		    "invalid SHA256 length, have %d",
244		    p->fn, fh->hash->length);
245		goto out;
246	}
247
248	type = rtype_from_mftfile(fn);
249	/* remember the filehash for the CRL in struct mft */
250	if (type == RTYPE_CRL && strcmp(fn, p->res->crl) == 0) {
251		memcpy(p->res->crlhash, fh->hash->data, SHA256_DIGEST_LENGTH);
252		p->found_crl = 1;
253	}
254
255	/* Insert the filename and hash value. */
256	fent = &p->res->files[p->res->filesz++];
257	fent->type = type;
258	fent->file = fn;
259	fn = NULL;
260	memcpy(fent->hash, fh->hash->data, SHA256_DIGEST_LENGTH);
261
262	rc = 1;
263 out:
264	free(fn);
265	return rc;
266}
267
268/*
269 * Handle the eContent of the manifest object, RFC 6486 sec. 4.2.
270 * Returns 0 on failure and 1 on success.
271 */
272static int
273mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p)
274{
275	Manifest		*mft;
276	FileAndHash		*fh;
277	int			 i, rc = 0;
278
279	if ((mft = d2i_Manifest(NULL, &d, dsz)) == NULL) {
280		cryptowarnx("%s: RFC 6486 section 4: failed to parse Manifest",
281		    p->fn);
282		goto out;
283	}
284
285	if (!valid_econtent_version(p->fn, mft->version))
286		goto out;
287
288	p->res->seqnum = x509_convert_seqnum(p->fn, mft->manifestNumber);
289	if (p->res->seqnum == NULL)
290		goto out;
291
292	/*
293	 * Timestamps: this and next update time.
294	 * Validate that the current date falls into this interval.
295	 * This is required by section 4.4, (3).
296	 * If we're after the given date, then the MFT is stale.
297	 * This is made super complicated because it uses OpenSSL's
298	 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could
299	 * compare against the current time trivially.
300	 */
301
302	if (!mft_parse_time(mft->thisUpdate, mft->nextUpdate, p))
303		goto out;
304
305	if (OBJ_obj2nid(mft->fileHashAlg) != NID_sha256) {
306		warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: "
307		    "want SHA256 object, have %s (NID %d)", p->fn,
308		    ASN1_tag2str(OBJ_obj2nid(mft->fileHashAlg)),
309		    OBJ_obj2nid(mft->fileHashAlg));
310		goto out;
311	}
312
313	if (sk_FileAndHash_num(mft->fileList) >= MAX_MANIFEST_ENTRIES) {
314		warnx("%s: %d exceeds manifest entry limit (%d)", p->fn,
315		    sk_FileAndHash_num(mft->fileList), MAX_MANIFEST_ENTRIES);
316		goto out;
317	}
318
319	p->res->files = calloc(sk_FileAndHash_num(mft->fileList),
320	    sizeof(struct mftfile));
321	if (p->res->files == NULL)
322		err(1, NULL);
323
324	for (i = 0; i < sk_FileAndHash_num(mft->fileList); i++) {
325		fh = sk_FileAndHash_value(mft->fileList, i);
326		if (!mft_parse_filehash(p, fh))
327			goto out;
328	}
329
330	if (!p->found_crl) {
331		warnx("%s: CRL not part of MFT fileList", p->fn);
332		goto out;
333	}
334
335	rc = 1;
336 out:
337	Manifest_free(mft);
338	return rc;
339}
340
341/*
342 * Parse the objects that have been published in the manifest.
343 * This conforms to RFC 6486.
344 * Note that if the MFT is stale, all referenced objects are stripped
345 * from the parsed content.
346 * The MFT content is otherwise returned.
347 */
348struct mft *
349mft_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len)
350{
351	struct parse	 p;
352	int		 rc = 0;
353	size_t		 cmsz;
354	unsigned char	*cms;
355	char		*crldp = NULL, *crlfile;
356	time_t		 signtime;
357
358	memset(&p, 0, sizeof(struct parse));
359	p.fn = fn;
360
361	cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz,
362	    &signtime);
363	if (cms == NULL)
364		return NULL;
365	assert(*x509 != NULL);
366
367	if ((p.res = calloc(1, sizeof(struct mft))) == NULL)
368		err(1, NULL);
369	p.res->signtime = signtime;
370
371	if (!x509_get_aia(*x509, fn, &p.res->aia))
372		goto out;
373	if (!x509_get_aki(*x509, fn, &p.res->aki))
374		goto out;
375	if (!x509_get_sia(*x509, fn, &p.res->sia))
376		goto out;
377	if (!x509_get_ski(*x509, fn, &p.res->ski))
378		goto out;
379	if (p.res->aia == NULL || p.res->aki == NULL || p.res->sia == NULL ||
380	    p.res->ski == NULL) {
381		warnx("%s: RFC 6487 section 4.8: "
382		    "missing AIA, AKI, SIA, or SKI X509 extension", fn);
383		goto out;
384	}
385
386	if (!x509_inherits(*x509)) {
387		warnx("%s: RFC 3779 extension not set to inherit", fn);
388		goto out;
389	}
390
391	/* get CRL info for later */
392	if (!x509_get_crl(*x509, fn, &crldp))
393		goto out;
394	if (crldp == NULL) {
395		warnx("%s: RFC 6487 section 4.8.6: CRL: "
396		    "missing CRL distribution point extension", fn);
397		goto out;
398	}
399	crlfile = strrchr(crldp, '/');
400	if (crlfile == NULL) {
401		warnx("%s: RFC 6487 section 4.8.6: "
402		    "invalid CRL distribution point", fn);
403		goto out;
404	}
405	crlfile++;
406	if (!valid_mft_filename(crlfile, strlen(crlfile)) ||
407	    rtype_from_file_extension(crlfile) != RTYPE_CRL) {
408		warnx("%s: RFC 6487 section 4.8.6: CRL: "
409		    "bad CRL distribution point extension", fn);
410		goto out;
411	}
412	if ((p.res->crl = strdup(crlfile)) == NULL)
413		err(1, NULL);
414
415	if (mft_parse_econtent(cms, cmsz, &p) == 0)
416		goto out;
417
418	rc = 1;
419out:
420	if (rc == 0) {
421		mft_free(p.res);
422		p.res = NULL;
423		X509_free(*x509);
424		*x509 = NULL;
425	}
426	free(crldp);
427	free(cms);
428	return p.res;
429}
430
431/*
432 * Free an MFT pointer.
433 * Safe to call with NULL.
434 */
435void
436mft_free(struct mft *p)
437{
438	size_t	 i;
439
440	if (p == NULL)
441		return;
442
443	if (p->files != NULL)
444		for (i = 0; i < p->filesz; i++)
445			free(p->files[i].file);
446
447	free(p->aia);
448	free(p->aki);
449	free(p->sia);
450	free(p->ski);
451	free(p->path);
452	free(p->files);
453	free(p->seqnum);
454	free(p);
455}
456
457/*
458 * Serialise MFT parsed content into the given buffer.
459 * See mft_read() for the other side of the pipe.
460 */
461void
462mft_buffer(struct ibuf *b, const struct mft *p)
463{
464	size_t		 i;
465
466	io_simple_buffer(b, &p->stale, sizeof(p->stale));
467	io_simple_buffer(b, &p->repoid, sizeof(p->repoid));
468	io_str_buffer(b, p->path);
469
470	io_str_buffer(b, p->aia);
471	io_str_buffer(b, p->aki);
472	io_str_buffer(b, p->ski);
473
474	io_simple_buffer(b, &p->filesz, sizeof(size_t));
475	for (i = 0; i < p->filesz; i++) {
476		io_str_buffer(b, p->files[i].file);
477		io_simple_buffer(b, &p->files[i].type,
478		    sizeof(p->files[i].type));
479		io_simple_buffer(b, &p->files[i].location,
480		    sizeof(p->files[i].location));
481		io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
482	}
483}
484
485/*
486 * Read an MFT structure from the file descriptor.
487 * Result must be passed to mft_free().
488 */
489struct mft *
490mft_read(struct ibuf *b)
491{
492	struct mft	*p = NULL;
493	size_t		 i;
494
495	if ((p = calloc(1, sizeof(struct mft))) == NULL)
496		err(1, NULL);
497
498	io_read_buf(b, &p->stale, sizeof(p->stale));
499	io_read_buf(b, &p->repoid, sizeof(p->repoid));
500	io_read_str(b, &p->path);
501
502	io_read_str(b, &p->aia);
503	io_read_str(b, &p->aki);
504	io_read_str(b, &p->ski);
505	assert(p->aia && p->aki && p->ski);
506
507	io_read_buf(b, &p->filesz, sizeof(size_t));
508	if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL)
509		err(1, NULL);
510
511	for (i = 0; i < p->filesz; i++) {
512		io_read_str(b, &p->files[i].file);
513		io_read_buf(b, &p->files[i].type, sizeof(p->files[i].type));
514		io_read_buf(b, &p->files[i].location,
515		    sizeof(p->files[i].location));
516		io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
517	}
518
519	return p;
520}
521
522/*
523 * Compare two MFT files, returns 1 if first MFT is preferred and 0 if second
524 * MFT should be used.
525 */
526int
527mft_compare(const struct mft *a, const struct mft *b)
528{
529	int r;
530
531	if (b == NULL)
532		return 1;
533	if (a == NULL)
534		return 0;
535
536	r = strlen(a->seqnum) - strlen(b->seqnum);
537	if (r > 0)	/* seqnum in a is longer -> higher */
538		return 1;
539	if (r < 0)	/* seqnum in a is shorter -> smaller */
540		return 0;
541
542	r = strcmp(a->seqnum, b->seqnum);
543	if (r >= 0)	/* a is greater or equal, prefer a */
544		return 1;
545	return 0;
546}
547