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