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