mft.c revision 1.33
1/*	$OpenBSD: mft.c,v 1.33 2021/05/09 11:25:32 tb 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	return ASN1_time_parse(data, len, tm, V_ASN1_GENERALIZEDTIME) ==
74	    V_ASN1_GENERALIZEDTIME;
75}
76
77/*
78 * Validate and verify the time validity of the mft.
79 * Returns 1 if all is good, 0 if mft is stale, any other case -1.
80 */
81static int
82check_validity(const ASN1_GENERALIZEDTIME *from,
83    const ASN1_GENERALIZEDTIME *until, const char *fn)
84{
85	time_t now = time(NULL);
86	struct tm tm_from, tm_until, tm_now;
87
88	if (gmtime_r(&now, &tm_now) == NULL) {
89		warnx("%s: could not get current time", fn);
90		return -1;
91	}
92
93	if (!generalizedtime_to_tm(from, &tm_from)) {
94		warnx("%s: embedded from time format invalid", fn);
95		return -1;
96	}
97	if (!generalizedtime_to_tm(until, &tm_until)) {
98		warnx("%s: embedded until time format invalid", fn);
99		return -1;
100	}
101
102	/* check that until is not before from */
103	if (ASN1_time_tm_cmp(&tm_until, &tm_from) < 0) {
104		warnx("%s: bad update interval", fn);
105		return -1;
106	}
107	/* check that now is not before from */
108	if (ASN1_time_tm_cmp(&tm_from, &tm_now) > 0) {
109		warnx("%s: mft not yet valid %s", fn, gentime2str(from));
110		return -1;
111	}
112	/* check that now is not after until */
113	if (ASN1_time_tm_cmp(&tm_until, &tm_now) < 0) {
114		warnx("%s: mft expired on %s", fn, gentime2str(until));
115		return 0;
116	}
117
118	return 1;
119}
120
121/*
122 * Parse an individual "FileAndHash", RFC 6486, sec. 4.2.
123 * Return zero on failure, non-zero on success.
124 */
125static int
126mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os)
127{
128	ASN1_SEQUENCE_ANY	*seq;
129	const ASN1_TYPE		*file, *hash;
130	char			*fn = NULL;
131	const unsigned char	*d = os->data;
132	size_t			 dsz = os->length;
133	int			 rc = 0;
134	struct mftfile		*fent;
135
136	if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
137		cryptowarnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
138		    "failed ASN.1 sequence parse", p->fn);
139		goto out;
140	} else if (sk_ASN1_TYPE_num(seq) != 2) {
141		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
142		    "want 2 elements, have %d", p->fn,
143		    sk_ASN1_TYPE_num(seq));
144		goto out;
145	}
146
147	/* First is the filename itself. */
148
149	file = sk_ASN1_TYPE_value(seq, 0);
150	if (file->type != V_ASN1_IA5STRING) {
151		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
152		    "want ASN.1 IA5 string, have %s (NID %d)",
153		    p->fn, ASN1_tag2str(file->type), file->type);
154		goto out;
155	}
156	fn = strndup((const char *)file->value.ia5string->data,
157	    file->value.ia5string->length);
158	if (fn == NULL)
159		err(1, NULL);
160
161	/*
162	 * Make sure we're just a pathname and either an ROA or CER.
163	 * I don't think that the RFC specifically mentions this, but
164	 * it's in practical use and would really screw things up
165	 * (arbitrary filenames) otherwise.
166	 */
167
168	if (strchr(fn, '/') != NULL) {
169		warnx("%s: path components disallowed in filename: %s",
170		    p->fn, fn);
171		goto out;
172	} else if (strlen(fn) <= 4) {
173		warnx("%s: filename must be large enough for suffix part: %s",
174		    p->fn, fn);
175		goto out;
176	}
177
178	/* Now hash value. */
179
180	hash = sk_ASN1_TYPE_value(seq, 1);
181	if (hash->type != V_ASN1_BIT_STRING) {
182		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
183		    "want ASN.1 bit string, have %s (NID %d)",
184		    p->fn, ASN1_tag2str(hash->type), hash->type);
185		goto out;
186	}
187
188	if (hash->value.bit_string->length != SHA256_DIGEST_LENGTH) {
189		warnx("%s: RFC 6486 section 4.2.1: hash: "
190		    "invalid SHA256 length, have %d",
191		    p->fn, hash->value.bit_string->length);
192		goto out;
193	}
194
195	/* Insert the filename and hash value. */
196
197	p->res->files = recallocarray(p->res->files, p->res->filesz,
198	    p->res->filesz + 1, sizeof(struct mftfile));
199	if (p->res->files == NULL)
200		err(1, NULL);
201
202	fent = &p->res->files[p->res->filesz++];
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	BIGNUM			*mft_seqnum = NULL;
262	long			 mft_version;
263	int			 i, rc = -1;
264
265	if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
266		cryptowarnx("%s: RFC 6486 section 4.2: Manifest: "
267		    "failed ASN.1 sequence parse", p->fn);
268		goto out;
269	}
270
271	/* The profile version is optional. */
272
273	if (sk_ASN1_TYPE_num(seq) != 5 &&
274	    sk_ASN1_TYPE_num(seq) != 6) {
275		warnx("%s: RFC 6486 section 4.2: Manifest: "
276		    "want 5 or 6 elements, have %d", p->fn,
277		    sk_ASN1_TYPE_num(seq));
278		goto out;
279	}
280
281	/* Start with optional profile version. */
282
283	i = 0;
284	if (sk_ASN1_TYPE_num(seq) == 6) {
285		t = sk_ASN1_TYPE_value(seq, i++);
286		if (t->type != V_ASN1_INTEGER) {
287			warnx("%s: RFC 6486 section 4.2.1: version: "
288			    "want ASN.1 integer, have %s (NID %d)",
289			    p->fn, ASN1_tag2str(t->type), t->type);
290			goto out;
291		}
292
293		if (t->value.integer == NULL)
294			goto out;
295
296		mft_version = ASN1_INTEGER_get(t->value.integer);
297		if (mft_version != 0) {
298			warnx("%s: RFC 6486 section 4.2.1: version: "
299			    "want 0, have %ld", p->fn, mft_version);
300			goto out;
301		}
302	}
303
304	/* Now the manifest sequence number. */
305
306	t = sk_ASN1_TYPE_value(seq, i++);
307	if (t->type != V_ASN1_INTEGER) {
308		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
309		    "want ASN.1 integer, have %s (NID %d)",
310		    p->fn, ASN1_tag2str(t->type), t->type);
311		goto out;
312	}
313
314	mft_seqnum = ASN1_INTEGER_to_BN(t->value.integer, NULL);
315	if (mft_seqnum == NULL) {
316		warnx("%s: ASN1_INTEGER_to_BN error", p->fn);
317		goto out;
318	}
319
320	if (BN_is_negative(mft_seqnum)) {
321		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
322		    "want positive integer, have negative.", p->fn);
323		goto out;
324	}
325
326	if (BN_num_bytes(mft_seqnum) > 20) {
327		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
328		    "want 20 or less than octets, have more.", p->fn);
329		goto out;
330	}
331
332	p->res->seqnum = BN_bn2hex(mft_seqnum);
333	if (p->res->seqnum == NULL) {
334		warnx("%s: BN_bn2hex error", p->fn);
335		goto out;
336	}
337
338	/*
339	 * Timestamps: this and next update time.
340	 * Validate that the current date falls into this interval.
341	 * This is required by section 4.4, (3).
342	 * If we're after the given date, then the MFT is stale.
343	 * This is made super complicated because it uses OpenSSL's
344	 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could
345	 * compare against the current time trivially.
346	 */
347
348	t = sk_ASN1_TYPE_value(seq, i++);
349	if (t->type != V_ASN1_GENERALIZEDTIME) {
350		warnx("%s: RFC 6486 section 4.2.1: thisUpdate: "
351		    "want ASN.1 generalised time, have %s (NID %d)",
352		    p->fn, ASN1_tag2str(t->type), t->type);
353		goto out;
354	}
355	from = t->value.generalizedtime;
356
357	t = sk_ASN1_TYPE_value(seq, i++);
358	if (t->type != V_ASN1_GENERALIZEDTIME) {
359		warnx("%s: RFC 6486 section 4.2.1: nextUpdate: "
360		    "want ASN.1 generalised time, have %s (NID %d)",
361		    p->fn, ASN1_tag2str(t->type), t->type);
362		goto out;
363	}
364	until = t->value.generalizedtime;
365
366	rc = check_validity(from, until, p->fn);
367	if (rc != 1)
368		goto out;
369
370	/* The mft is valid. Reset rc so later 'goto out' return failure. */
371	rc = -1;
372
373	/* File list algorithm. */
374
375	t = sk_ASN1_TYPE_value(seq, i++);
376	if (t->type != V_ASN1_OBJECT) {
377		warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: "
378		    "want ASN.1 object time, have %s (NID %d)",
379		    p->fn, ASN1_tag2str(t->type), t->type);
380		goto out;
381	} else if (OBJ_obj2nid(t->value.object) != NID_sha256) {
382		warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: "
383		    "want SHA256 object, have %s (NID %d)", p->fn,
384		    ASN1_tag2str(OBJ_obj2nid(t->value.object)),
385		    OBJ_obj2nid(t->value.object));
386		goto out;
387	}
388
389	/* Now the sequence. */
390
391	t = sk_ASN1_TYPE_value(seq, i++);
392	if (t->type != V_ASN1_SEQUENCE) {
393		warnx("%s: RFC 6486 section 4.2.1: fileList: "
394		    "want ASN.1 sequence, have %s (NID %d)",
395		    p->fn, ASN1_tag2str(t->type), t->type);
396		goto out;
397	} else if (!mft_parse_flist(p, t->value.octet_string))
398		goto out;
399
400	rc = 1;
401out:
402	sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
403	BN_free(mft_seqnum);
404	return rc;
405}
406
407/*
408 * Parse the objects that have been published in the manifest.
409 * This conforms to RFC 6486.
410 * Note that if the MFT is stale, all referenced objects are stripped
411 * from the parsed content.
412 * The MFT content is otherwise returned.
413 */
414struct mft *
415mft_parse(X509 **x509, const char *fn)
416{
417	struct parse	 p;
418	int		 c, rc = 0;
419	size_t		 i, cmsz;
420	unsigned char	*cms;
421
422	memset(&p, 0, sizeof(struct parse));
423	p.fn = fn;
424
425	cms = cms_parse_validate(x509, fn, "1.2.840.113549.1.9.16.1.26",
426	    &cmsz);
427	if (cms == NULL)
428		return NULL;
429	assert(*x509 != NULL);
430
431	if ((p.res = calloc(1, sizeof(struct mft))) == NULL)
432		err(1, NULL);
433	if ((p.res->file = strdup(fn)) == NULL)
434		err(1, NULL);
435
436	p.res->aia = x509_get_aia(*x509, fn);
437	p.res->aki = x509_get_aki(*x509, 0, fn);
438	p.res->ski = x509_get_ski(*x509, fn);
439	if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) {
440		warnx("%s: RFC 6487 section 4.8: "
441		    "missing AIA, AKI or SKI X509 extension", fn);
442		goto out;
443	}
444
445	/*
446	 * If we're stale, then remove all of the files that the MFT
447	 * references as well as marking it as stale.
448	 */
449
450	if ((c = mft_parse_econtent(cms, cmsz, &p)) == 0) {
451		/*
452		 * FIXME: it should suffice to just mark this as stale
453		 * and have the logic around mft_read() simply ignore
454		 * the contents of stale entries, just like it does for
455		 * invalid ROAs or certificates.
456		 */
457
458		p.res->stale = 1;
459		if (p.res->files != NULL)
460			for (i = 0; i < p.res->filesz; i++)
461				free(p.res->files[i].file);
462		free(p.res->files);
463		p.res->filesz = 0;
464		p.res->files = NULL;
465	} else if (c == -1)
466		goto out;
467
468	rc = 1;
469out:
470	if (rc == 0) {
471		mft_free(p.res);
472		p.res = NULL;
473		X509_free(*x509);
474		*x509 = NULL;
475	}
476	free(cms);
477	return p.res;
478}
479
480/*
481 * Check all files and their hashes in a MFT structure.
482 * Return zero on failure, non-zero on success.
483 */
484int
485mft_check(const char *fn, struct mft *p)
486{
487	size_t	i;
488	int	rc = 1;
489	char	*cp, *path = NULL;
490
491	/* Check hash of file now, but first build path for it */
492	cp = strrchr(fn, '/');
493	assert(cp != NULL);
494	assert(cp - fn < INT_MAX);
495
496	for (i = 0; i < p->filesz; i++) {
497		const struct mftfile *m = &p->files[i];
498		if (asprintf(&path, "%.*s/%s", (int)(cp - fn), fn,
499		    m->file) == -1)
500			err(1, NULL);
501		if (!valid_filehash(path, m->hash, sizeof(m->hash))) {
502			warnx("%s: bad message digest for %s", fn, m->file);
503			rc = 0;
504		}
505		free(path);
506	}
507
508	return rc;
509}
510
511/*
512 * Free an MFT pointer.
513 * Safe to call with NULL.
514 */
515void
516mft_free(struct mft *p)
517{
518	size_t	 i;
519
520	if (p == NULL)
521		return;
522
523	if (p->files != NULL)
524		for (i = 0; i < p->filesz; i++)
525			free(p->files[i].file);
526
527	free(p->aia);
528	free(p->aki);
529	free(p->ski);
530	free(p->file);
531	free(p->files);
532	free(p->seqnum);
533	free(p);
534}
535
536/*
537 * Serialise MFT parsed content into the given buffer.
538 * See mft_read() for the other side of the pipe.
539 */
540void
541mft_buffer(struct ibuf *b, const struct mft *p)
542{
543	size_t		 i;
544
545	io_simple_buffer(b, &p->stale, sizeof(int));
546	io_str_buffer(b, p->file);
547	io_simple_buffer(b, &p->filesz, sizeof(size_t));
548
549	for (i = 0; i < p->filesz; i++) {
550		io_str_buffer(b, p->files[i].file);
551		io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
552	}
553
554	io_str_buffer(b, p->aia);
555	io_str_buffer(b, p->aki);
556	io_str_buffer(b, p->ski);
557}
558
559/*
560 * Read an MFT structure from the file descriptor.
561 * Result must be passed to mft_free().
562 */
563struct mft *
564mft_read(int fd)
565{
566	struct mft	*p = NULL;
567	size_t		 i;
568
569	if ((p = calloc(1, sizeof(struct mft))) == NULL)
570		err(1, NULL);
571
572	io_simple_read(fd, &p->stale, sizeof(int));
573	io_str_read(fd, &p->file);
574	assert(p->file);
575	io_simple_read(fd, &p->filesz, sizeof(size_t));
576
577	if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL)
578		err(1, NULL);
579
580	for (i = 0; i < p->filesz; i++) {
581		io_str_read(fd, &p->files[i].file);
582		io_simple_read(fd, p->files[i].hash, SHA256_DIGEST_LENGTH);
583	}
584
585	io_str_read(fd, &p->aia);
586	io_str_read(fd, &p->aki);
587	io_str_read(fd, &p->ski);
588	assert(p->aia && p->aki && p->ski);
589
590	return p;
591}
592