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