mft.c revision 1.37
1/*	$OpenBSD: mft.c,v 1.37 2021/09/08 16:37:20 claudio Exp $ */
2/*
3 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <assert.h>
19#include <err.h>
20#include <limits.h>
21#include <stdarg.h>
22#include <stdint.h>
23#include <fcntl.h>
24#include <stdlib.h>
25#include <string.h>
26#include <unistd.h>
27
28#include <openssl/bn.h>
29#include <openssl/asn1.h>
30#include <openssl/sha.h>
31#include <openssl/x509.h>
32
33#include "extern.h"
34
35/*
36 * Parse results and data of the manifest file.
37 */
38struct	parse {
39	const char	*fn; /* manifest file name */
40	struct mft	*res; /* result object */
41};
42
43static const char *
44gentime2str(const ASN1_GENERALIZEDTIME *time)
45{
46	static char	buf[64];
47	BIO		*mem;
48
49	if ((mem = BIO_new(BIO_s_mem())) == NULL)
50		cryptoerrx("BIO_new");
51	if (!ASN1_GENERALIZEDTIME_print(mem, time))
52		cryptoerrx("ASN1_GENERALIZEDTIME_print");
53	if (BIO_gets(mem, buf, sizeof(buf)) < 0)
54		cryptoerrx("BIO_gets");
55
56	BIO_free(mem);
57	return buf;
58}
59
60/*
61 * Convert an ASN1_GENERALIZEDTIME to a struct tm.
62 * Returns 1 on success, 0 on failure.
63 */
64static int
65generalizedtime_to_tm(const ASN1_GENERALIZEDTIME *gtime, struct tm *tm)
66{
67	const char *data;
68	size_t len;
69
70	data = ASN1_STRING_get0_data(gtime);
71	len = ASN1_STRING_length(gtime);
72
73	memset(tm, 0, sizeof(*tm));
74	return ASN1_time_parse(data, len, tm, V_ASN1_GENERALIZEDTIME) ==
75	    V_ASN1_GENERALIZEDTIME;
76}
77
78/*
79 * Validate and verify the time validity of the mft.
80 * Returns 1 if all is good, 0 if mft is stale, any other case -1.
81 */
82static int
83check_validity(const ASN1_GENERALIZEDTIME *from,
84    const ASN1_GENERALIZEDTIME *until, const char *fn)
85{
86	time_t now = time(NULL);
87	struct tm tm_from, tm_until, tm_now;
88
89	if (gmtime_r(&now, &tm_now) == NULL) {
90		warnx("%s: could not get current time", fn);
91		return -1;
92	}
93
94	if (!generalizedtime_to_tm(from, &tm_from)) {
95		warnx("%s: embedded from time format invalid", fn);
96		return -1;
97	}
98	if (!generalizedtime_to_tm(until, &tm_until)) {
99		warnx("%s: embedded until time format invalid", fn);
100		return -1;
101	}
102
103	/* check that until is not before from */
104	if (ASN1_time_tm_cmp(&tm_until, &tm_from) < 0) {
105		warnx("%s: bad update interval", fn);
106		return -1;
107	}
108	/* check that now is not before from */
109	if (ASN1_time_tm_cmp(&tm_from, &tm_now) > 0) {
110		warnx("%s: mft not yet valid %s", fn, gentime2str(from));
111		return -1;
112	}
113	/* check that now is not after until */
114	if (ASN1_time_tm_cmp(&tm_until, &tm_now) < 0) {
115		warnx("%s: mft expired on %s", fn, gentime2str(until));
116		return 0;
117	}
118
119	return 1;
120}
121
122/*
123 * Parse an individual "FileAndHash", RFC 6486, sec. 4.2.
124 * Return zero on failure, non-zero on success.
125 */
126static int
127mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os)
128{
129	ASN1_SEQUENCE_ANY	*seq;
130	const ASN1_TYPE		*file, *hash;
131	char			*fn = NULL;
132	const unsigned char	*d = os->data;
133	size_t			 dsz = os->length;
134	int			 rc = 0;
135	struct mftfile		*fent;
136
137	if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
138		cryptowarnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
139		    "failed ASN.1 sequence parse", p->fn);
140		goto out;
141	} else if (sk_ASN1_TYPE_num(seq) != 2) {
142		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
143		    "want 2 elements, have %d", p->fn,
144		    sk_ASN1_TYPE_num(seq));
145		goto out;
146	}
147
148	/* First is the filename itself. */
149
150	file = sk_ASN1_TYPE_value(seq, 0);
151	if (file->type != V_ASN1_IA5STRING) {
152		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
153		    "want ASN.1 IA5 string, have %s (NID %d)",
154		    p->fn, ASN1_tag2str(file->type), file->type);
155		goto out;
156	}
157	fn = strndup((const char *)file->value.ia5string->data,
158	    file->value.ia5string->length);
159	if (fn == NULL)
160		err(1, NULL);
161
162	/*
163	 * Make sure we're just a pathname and either an ROA or CER.
164	 * I don't think that the RFC specifically mentions this, but
165	 * it's in practical use and would really screw things up
166	 * (arbitrary filenames) otherwise.
167	 */
168
169	if (strchr(fn, '/') != NULL) {
170		warnx("%s: path components disallowed in filename: %s",
171		    p->fn, fn);
172		goto out;
173	} else if (strlen(fn) <= 4) {
174		warnx("%s: filename must be large enough for suffix part: %s",
175		    p->fn, fn);
176		goto out;
177	}
178
179	/* Now hash value. */
180
181	hash = sk_ASN1_TYPE_value(seq, 1);
182	if (hash->type != V_ASN1_BIT_STRING) {
183		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
184		    "want ASN.1 bit string, have %s (NID %d)",
185		    p->fn, ASN1_tag2str(hash->type), hash->type);
186		goto out;
187	}
188
189	if (hash->value.bit_string->length != SHA256_DIGEST_LENGTH) {
190		warnx("%s: RFC 6486 section 4.2.1: hash: "
191		    "invalid SHA256 length, have %d",
192		    p->fn, hash->value.bit_string->length);
193		goto out;
194	}
195
196	/* Insert the filename and hash value. */
197	fent = &p->res->files[p->res->filesz++];
198
199	fent->file = fn;
200	fn = NULL;
201	memcpy(fent->hash, hash->value.bit_string->data, SHA256_DIGEST_LENGTH);
202
203	rc = 1;
204out:
205	free(fn);
206	sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
207	return rc;
208}
209
210/*
211 * Parse the "FileAndHash" sequence, RFC 6486, sec. 4.2.
212 * Return zero on failure, non-zero on success.
213 */
214static int
215mft_parse_flist(struct parse *p, const ASN1_OCTET_STRING *os)
216{
217	ASN1_SEQUENCE_ANY	*seq;
218	const ASN1_TYPE		*t;
219	const unsigned char	*d = os->data;
220	size_t			 dsz = os->length;
221	int			 i, rc = 0;
222
223	if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
224		cryptowarnx("%s: RFC 6486 section 4.2: fileList: "
225		    "failed ASN.1 sequence parse", p->fn);
226		goto out;
227	}
228
229	p->res->files = calloc(sk_ASN1_TYPE_num(seq), sizeof(struct mftfile));
230	if (p->res->files == NULL)
231		err(1, NULL);
232
233	for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) {
234		t = sk_ASN1_TYPE_value(seq, i);
235		if (t->type != V_ASN1_SEQUENCE) {
236			warnx("%s: RFC 6486 section 4.2: fileList: "
237			    "want ASN.1 sequence, have %s (NID %d)",
238			    p->fn, ASN1_tag2str(t->type), t->type);
239			goto out;
240		} else if (!mft_parse_filehash(p, t->value.octet_string))
241			goto out;
242	}
243
244	rc = 1;
245out:
246	sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
247	return rc;
248}
249
250/*
251 * Handle the eContent of the manifest object, RFC 6486 sec. 4.2.
252 * Returns <0 on failure, 0 on stale, >0 on success.
253 */
254static int
255mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p)
256{
257	ASN1_SEQUENCE_ANY	*seq;
258	const ASN1_TYPE		*t;
259	const ASN1_GENERALIZEDTIME *from, *until;
260	long			 mft_version;
261	BIGNUM			*mft_seqnum = NULL;
262	int			 i = 0, rc = -1;
263
264	if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
265		cryptowarnx("%s: RFC 6486 section 4.2: Manifest: "
266		    "failed ASN.1 sequence parse", p->fn);
267		goto out;
268	}
269
270	/* Test if the optional profile version field is present. */
271	if (sk_ASN1_TYPE_num(seq) != 5 &&
272	    sk_ASN1_TYPE_num(seq) != 6) {
273		warnx("%s: RFC 6486 section 4.2: Manifest: "
274		    "want 5 or 6 elements, have %d", p->fn,
275		    sk_ASN1_TYPE_num(seq));
276		goto out;
277	}
278
279	/* Parse the optional version field */
280	if (sk_ASN1_TYPE_num(seq) == 6) {
281		t = sk_ASN1_TYPE_value(seq, i++);
282		d = t->value.asn1_string->data;
283		dsz = t->value.asn1_string->length;
284
285		if (cms_econtent_version(p->fn, &d, dsz, &mft_version) == -1)
286			goto out;
287
288		switch (mft_version) {
289		case 0:
290			warnx("%s: incorrect encoding for version 0", p->fn);
291			goto out;
292		default:
293			warnx("%s: version %ld not supported (yet)", p->fn,
294			    mft_version);
295			goto out;
296		}
297	}
298
299	/* Now the manifest sequence number. */
300
301	t = sk_ASN1_TYPE_value(seq, i++);
302	if (t->type != V_ASN1_INTEGER) {
303		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
304		    "want ASN.1 integer, have %s (NID %d)",
305		    p->fn, ASN1_tag2str(t->type), t->type);
306		goto out;
307	}
308
309	mft_seqnum = ASN1_INTEGER_to_BN(t->value.integer, NULL);
310	if (mft_seqnum == NULL) {
311		warnx("%s: ASN1_INTEGER_to_BN error", p->fn);
312		goto out;
313	}
314
315	if (BN_is_negative(mft_seqnum)) {
316		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
317		    "want positive integer, have negative.", p->fn);
318		goto out;
319	}
320
321	if (BN_num_bytes(mft_seqnum) > 20) {
322		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
323		    "want 20 or less than octets, have more.", p->fn);
324		goto out;
325	}
326
327	p->res->seqnum = BN_bn2hex(mft_seqnum);
328	if (p->res->seqnum == NULL) {
329		warnx("%s: BN_bn2hex error", p->fn);
330		goto out;
331	}
332
333	/*
334	 * Timestamps: this and next update time.
335	 * Validate that the current date falls into this interval.
336	 * This is required by section 4.4, (3).
337	 * If we're after the given date, then the MFT is stale.
338	 * This is made super complicated because it uses OpenSSL's
339	 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could
340	 * compare against the current time trivially.
341	 */
342
343	t = sk_ASN1_TYPE_value(seq, i++);
344	if (t->type != V_ASN1_GENERALIZEDTIME) {
345		warnx("%s: RFC 6486 section 4.2.1: thisUpdate: "
346		    "want ASN.1 generalised time, have %s (NID %d)",
347		    p->fn, ASN1_tag2str(t->type), t->type);
348		goto out;
349	}
350	from = t->value.generalizedtime;
351
352	t = sk_ASN1_TYPE_value(seq, i++);
353	if (t->type != V_ASN1_GENERALIZEDTIME) {
354		warnx("%s: RFC 6486 section 4.2.1: nextUpdate: "
355		    "want ASN.1 generalised time, have %s (NID %d)",
356		    p->fn, ASN1_tag2str(t->type), t->type);
357		goto out;
358	}
359	until = t->value.generalizedtime;
360
361	rc = check_validity(from, until, p->fn);
362	if (rc != 1)
363		goto out;
364
365	/* The mft is valid. Reset rc so later 'goto out' return failure. */
366	rc = -1;
367
368	/* File list algorithm. */
369
370	t = sk_ASN1_TYPE_value(seq, i++);
371	if (t->type != V_ASN1_OBJECT) {
372		warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: "
373		    "want ASN.1 object time, have %s (NID %d)",
374		    p->fn, ASN1_tag2str(t->type), t->type);
375		goto out;
376	} else if (OBJ_obj2nid(t->value.object) != NID_sha256) {
377		warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: "
378		    "want SHA256 object, have %s (NID %d)", p->fn,
379		    ASN1_tag2str(OBJ_obj2nid(t->value.object)),
380		    OBJ_obj2nid(t->value.object));
381		goto out;
382	}
383
384	/* Now the sequence. */
385
386	t = sk_ASN1_TYPE_value(seq, i++);
387	if (t->type != V_ASN1_SEQUENCE) {
388		warnx("%s: RFC 6486 section 4.2.1: fileList: "
389		    "want ASN.1 sequence, have %s (NID %d)",
390		    p->fn, ASN1_tag2str(t->type), t->type);
391		goto out;
392	} else if (!mft_parse_flist(p, t->value.octet_string))
393		goto out;
394
395	rc = 1;
396out:
397	sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
398	BN_free(mft_seqnum);
399	return rc;
400}
401
402/*
403 * Parse the objects that have been published in the manifest.
404 * This conforms to RFC 6486.
405 * Note that if the MFT is stale, all referenced objects are stripped
406 * from the parsed content.
407 * The MFT content is otherwise returned.
408 */
409struct mft *
410mft_parse(X509 **x509, const char *fn)
411{
412	struct parse	 p;
413	int		 c, rc = 0;
414	size_t		 i, cmsz;
415	unsigned char	*cms;
416
417	memset(&p, 0, sizeof(struct parse));
418	p.fn = fn;
419
420	cms = cms_parse_validate(x509, fn, "1.2.840.113549.1.9.16.1.26",
421	    &cmsz);
422	if (cms == NULL)
423		return NULL;
424	assert(*x509 != NULL);
425
426	if ((p.res = calloc(1, sizeof(struct mft))) == NULL)
427		err(1, NULL);
428	if ((p.res->file = strdup(fn)) == NULL)
429		err(1, NULL);
430
431	p.res->aia = x509_get_aia(*x509, fn);
432	p.res->aki = x509_get_aki(*x509, 0, fn);
433	p.res->ski = x509_get_ski(*x509, fn);
434	if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) {
435		warnx("%s: RFC 6487 section 4.8: "
436		    "missing AIA, AKI or SKI X509 extension", fn);
437		goto out;
438	}
439
440	/*
441	 * If we're stale, then remove all of the files that the MFT
442	 * references as well as marking it as stale.
443	 */
444
445	if ((c = mft_parse_econtent(cms, cmsz, &p)) == 0) {
446		/*
447		 * FIXME: it should suffice to just mark this as stale
448		 * and have the logic around mft_read() simply ignore
449		 * the contents of stale entries, just like it does for
450		 * invalid ROAs or certificates.
451		 */
452
453		p.res->stale = 1;
454		if (p.res->files != NULL)
455			for (i = 0; i < p.res->filesz; i++)
456				free(p.res->files[i].file);
457		free(p.res->files);
458		p.res->filesz = 0;
459		p.res->files = NULL;
460	} else if (c == -1)
461		goto out;
462
463	rc = 1;
464out:
465	if (rc == 0) {
466		mft_free(p.res);
467		p.res = NULL;
468		X509_free(*x509);
469		*x509 = NULL;
470	}
471	free(cms);
472	return p.res;
473}
474
475/*
476 * Check all files and their hashes in a MFT structure.
477 * Return zero on failure, non-zero on success.
478 */
479int
480mft_check(const char *fn, struct mft *p)
481{
482	size_t	i;
483	int	rc = 1;
484	char	*cp, *path = NULL;
485
486	/* Check hash of file now, but first build path for it */
487	cp = strrchr(fn, '/');
488	assert(cp != NULL);
489	assert(cp - fn < INT_MAX);
490
491	for (i = 0; i < p->filesz; i++) {
492		const struct mftfile *m = &p->files[i];
493		if (asprintf(&path, "%.*s/%s", (int)(cp - fn), fn,
494		    m->file) == -1)
495			err(1, NULL);
496		if (!valid_filehash(path, m->hash, sizeof(m->hash))) {
497			warnx("%s: bad message digest for %s", fn, m->file);
498			rc = 0;
499		}
500		free(path);
501	}
502
503	return rc;
504}
505
506/*
507 * Free an MFT pointer.
508 * Safe to call with NULL.
509 */
510void
511mft_free(struct mft *p)
512{
513	size_t	 i;
514
515	if (p == NULL)
516		return;
517
518	if (p->files != NULL)
519		for (i = 0; i < p->filesz; i++)
520			free(p->files[i].file);
521
522	free(p->aia);
523	free(p->aki);
524	free(p->ski);
525	free(p->file);
526	free(p->files);
527	free(p->seqnum);
528	free(p);
529}
530
531/*
532 * Serialise MFT parsed content into the given buffer.
533 * See mft_read() for the other side of the pipe.
534 */
535void
536mft_buffer(struct ibuf *b, const struct mft *p)
537{
538	size_t		 i;
539
540	io_simple_buffer(b, &p->stale, sizeof(int));
541	io_str_buffer(b, p->file);
542	io_simple_buffer(b, &p->filesz, sizeof(size_t));
543
544	for (i = 0; i < p->filesz; i++) {
545		io_str_buffer(b, p->files[i].file);
546		io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
547	}
548
549	io_str_buffer(b, p->aia);
550	io_str_buffer(b, p->aki);
551	io_str_buffer(b, p->ski);
552}
553
554/*
555 * Read an MFT structure from the file descriptor.
556 * Result must be passed to mft_free().
557 */
558struct mft *
559mft_read(int fd)
560{
561	struct mft	*p = NULL;
562	size_t		 i;
563
564	if ((p = calloc(1, sizeof(struct mft))) == NULL)
565		err(1, NULL);
566
567	io_simple_read(fd, &p->stale, sizeof(int));
568	io_str_read(fd, &p->file);
569	assert(p->file);
570	io_simple_read(fd, &p->filesz, sizeof(size_t));
571
572	if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL)
573		err(1, NULL);
574
575	for (i = 0; i < p->filesz; i++) {
576		io_str_read(fd, &p->files[i].file);
577		io_simple_read(fd, p->files[i].hash, SHA256_DIGEST_LENGTH);
578	}
579
580	io_str_read(fd, &p->aia);
581	io_str_read(fd, &p->aki);
582	io_str_read(fd, &p->ski);
583	assert(p->aia && p->aki && p->ski);
584
585	return p;
586}
587