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