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