mft.c revision 1.48
1/*	$OpenBSD: mft.c,v 1.48 2022/01/18 16:24:55 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 <stdlib.h>
24#include <string.h>
25#include <unistd.h>
26
27#include <openssl/bn.h>
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
42extern ASN1_OBJECT    *mft_oid;
43
44static const char *
45gentime2str(const ASN1_GENERALIZEDTIME *time)
46{
47	static char	buf[64];
48	BIO		*mem;
49
50	if ((mem = BIO_new(BIO_s_mem())) == NULL)
51		cryptoerrx("BIO_new");
52	if (!ASN1_GENERALIZEDTIME_print(mem, time))
53		cryptoerrx("ASN1_GENERALIZEDTIME_print");
54	if (BIO_gets(mem, buf, sizeof(buf)) < 0)
55		cryptoerrx("BIO_gets");
56
57	BIO_free(mem);
58	return buf;
59}
60
61/*
62 * Convert an ASN1_GENERALIZEDTIME to a struct tm.
63 * Returns 1 on success, 0 on failure.
64 */
65static int
66generalizedtime_to_tm(const ASN1_GENERALIZEDTIME *gtime, struct tm *tm)
67{
68	const char *data;
69	size_t len;
70
71	data = ASN1_STRING_get0_data(gtime);
72	len = ASN1_STRING_length(gtime);
73
74	memset(tm, 0, sizeof(*tm));
75	return ASN1_time_parse(data, len, tm, V_ASN1_GENERALIZEDTIME) ==
76	    V_ASN1_GENERALIZEDTIME;
77}
78
79/*
80 * Validate and verify the time validity of the mft.
81 * Returns 1 if all is good, 0 if mft is stale, any other case -1.
82 */
83static int
84check_validity(const ASN1_GENERALIZEDTIME *from,
85    const ASN1_GENERALIZEDTIME *until, const char *fn)
86{
87	time_t now = time(NULL);
88	struct tm tm_from, tm_until, tm_now;
89
90	if (gmtime_r(&now, &tm_now) == NULL) {
91		warnx("%s: could not get current time", fn);
92		return -1;
93	}
94
95	if (!generalizedtime_to_tm(from, &tm_from)) {
96		warnx("%s: embedded from time format invalid", fn);
97		return -1;
98	}
99	if (!generalizedtime_to_tm(until, &tm_until)) {
100		warnx("%s: embedded until time format invalid", fn);
101		return -1;
102	}
103
104	/* check that until is not before from */
105	if (ASN1_time_tm_cmp(&tm_until, &tm_from) < 0) {
106		warnx("%s: bad update interval", fn);
107		return -1;
108	}
109	/* check that now is not before from */
110	if (ASN1_time_tm_cmp(&tm_from, &tm_now) > 0) {
111		warnx("%s: mft not yet valid %s", fn, gentime2str(from));
112		return -1;
113	}
114	/* check that now is not after until */
115	if (ASN1_time_tm_cmp(&tm_until, &tm_now) < 0) {
116		warnx("%s: mft expired on %s", fn, gentime2str(until));
117		return 0;
118	}
119
120	return 1;
121}
122
123/*
124 * Parse an individual "FileAndHash", RFC 6486, sec. 4.2.
125 * Return zero on failure, non-zero on success.
126 */
127static int
128mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os)
129{
130	ASN1_SEQUENCE_ANY	*seq;
131	const ASN1_TYPE		*file, *hash;
132	char			*fn = NULL;
133	const unsigned char	*d = os->data;
134	size_t			 dsz = os->length;
135	int			 rc = 0;
136	struct mftfile		*fent;
137
138	if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
139		cryptowarnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
140		    "failed ASN.1 sequence parse", p->fn);
141		goto out;
142	} else if (sk_ASN1_TYPE_num(seq) != 2) {
143		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
144		    "want 2 elements, have %d", p->fn,
145		    sk_ASN1_TYPE_num(seq));
146		goto out;
147	}
148
149	/* First is the filename itself. */
150
151	file = sk_ASN1_TYPE_value(seq, 0);
152	if (file->type != V_ASN1_IA5STRING) {
153		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
154		    "want ASN.1 IA5 string, have %s (NID %d)",
155		    p->fn, ASN1_tag2str(file->type), file->type);
156		goto out;
157	}
158	fn = strndup((const char *)file->value.ia5string->data,
159	    file->value.ia5string->length);
160	if (fn == NULL)
161		err(1, NULL);
162
163	if (!valid_filename(fn)) {
164		warnx("%s: invalid filename: %s", p->fn, fn);
165		goto out;
166	}
167
168	/* Now hash value. */
169
170	hash = sk_ASN1_TYPE_value(seq, 1);
171	if (hash->type != V_ASN1_BIT_STRING) {
172		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
173		    "want ASN.1 bit string, have %s (NID %d)",
174		    p->fn, ASN1_tag2str(hash->type), hash->type);
175		goto out;
176	}
177
178	if (hash->value.bit_string->length != SHA256_DIGEST_LENGTH) {
179		warnx("%s: RFC 6486 section 4.2.1: hash: "
180		    "invalid SHA256 length, have %d",
181		    p->fn, hash->value.bit_string->length);
182		goto out;
183	}
184
185	/* Insert the filename and hash value. */
186	fent = &p->res->files[p->res->filesz++];
187
188	fent->file = fn;
189	fn = NULL;
190	memcpy(fent->hash, hash->value.bit_string->data, SHA256_DIGEST_LENGTH);
191
192	rc = 1;
193out:
194	free(fn);
195	sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
196	return rc;
197}
198
199/*
200 * Parse the "FileAndHash" sequence, RFC 6486, sec. 4.2.
201 * Return zero on failure, non-zero on success.
202 */
203static int
204mft_parse_flist(struct parse *p, const ASN1_OCTET_STRING *os)
205{
206	ASN1_SEQUENCE_ANY	*seq;
207	const ASN1_TYPE		*t;
208	const unsigned char	*d = os->data;
209	size_t			 dsz = os->length;
210	int			 i, rc = 0;
211
212	if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
213		cryptowarnx("%s: RFC 6486 section 4.2: fileList: "
214		    "failed ASN.1 sequence parse", p->fn);
215		goto out;
216	}
217
218	if (sk_ASN1_TYPE_num(seq) > MAX_MANIFEST_ENTRIES) {
219		warnx("%s: %d exceeds manifest entry limit (%d)", p->fn,
220		    sk_ASN1_TYPE_num(seq), MAX_MANIFEST_ENTRIES);
221		goto out;
222	}
223
224	p->res->files = calloc(sk_ASN1_TYPE_num(seq), sizeof(struct mftfile));
225	if (p->res->files == NULL)
226		err(1, NULL);
227
228	for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) {
229		t = sk_ASN1_TYPE_value(seq, i);
230		if (t->type != V_ASN1_SEQUENCE) {
231			warnx("%s: RFC 6486 section 4.2: fileList: "
232			    "want ASN.1 sequence, have %s (NID %d)",
233			    p->fn, ASN1_tag2str(t->type), t->type);
234			goto out;
235		} else if (!mft_parse_filehash(p, t->value.octet_string))
236			goto out;
237	}
238
239	rc = 1;
240 out:
241	sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
242	return rc;
243}
244
245/*
246 * Handle the eContent of the manifest object, RFC 6486 sec. 4.2.
247 * Returns 0 on failure and 1 on success.
248 */
249static int
250mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p)
251{
252	ASN1_SEQUENCE_ANY	*seq;
253	const ASN1_TYPE		*t;
254	const ASN1_GENERALIZEDTIME *from, *until;
255	long			 mft_version;
256	BIGNUM			*mft_seqnum = NULL;
257	int			 i = 0, rc = 0;
258
259	if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
260		cryptowarnx("%s: RFC 6486 section 4.2: Manifest: "
261		    "failed ASN.1 sequence parse", p->fn);
262		goto out;
263	}
264
265	/* Test if the optional profile version field is present. */
266	if (sk_ASN1_TYPE_num(seq) != 5 &&
267	    sk_ASN1_TYPE_num(seq) != 6) {
268		warnx("%s: RFC 6486 section 4.2: Manifest: "
269		    "want 5 or 6 elements, have %d", p->fn,
270		    sk_ASN1_TYPE_num(seq));
271		goto out;
272	}
273
274	/* Parse the optional version field */
275	if (sk_ASN1_TYPE_num(seq) == 6) {
276		t = sk_ASN1_TYPE_value(seq, i++);
277		d = t->value.asn1_string->data;
278		dsz = t->value.asn1_string->length;
279
280		if (cms_econtent_version(p->fn, &d, dsz, &mft_version) == -1)
281			goto out;
282
283		switch (mft_version) {
284		case 0:
285			warnx("%s: incorrect encoding for version 0", p->fn);
286			goto out;
287		default:
288			warnx("%s: version %ld not supported (yet)", p->fn,
289			    mft_version);
290			goto out;
291		}
292	}
293
294	/* Now the manifest sequence number. */
295
296	t = sk_ASN1_TYPE_value(seq, i++);
297	if (t->type != V_ASN1_INTEGER) {
298		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
299		    "want ASN.1 integer, have %s (NID %d)",
300		    p->fn, ASN1_tag2str(t->type), t->type);
301		goto out;
302	}
303
304	mft_seqnum = ASN1_INTEGER_to_BN(t->value.integer, NULL);
305	if (mft_seqnum == NULL) {
306		warnx("%s: ASN1_INTEGER_to_BN error", p->fn);
307		goto out;
308	}
309
310	if (BN_is_negative(mft_seqnum)) {
311		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
312		    "want positive integer, have negative.", p->fn);
313		goto out;
314	}
315
316	if (BN_num_bytes(mft_seqnum) > 20) {
317		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
318		    "want 20 or less than octets, have more.", p->fn);
319		goto out;
320	}
321
322	p->res->seqnum = BN_bn2hex(mft_seqnum);
323	if (p->res->seqnum == NULL) {
324		warnx("%s: BN_bn2hex error", p->fn);
325		goto out;
326	}
327
328	/*
329	 * Timestamps: this and next update time.
330	 * Validate that the current date falls into this interval.
331	 * This is required by section 4.4, (3).
332	 * If we're after the given date, then the MFT is stale.
333	 * This is made super complicated because it uses OpenSSL's
334	 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could
335	 * compare against the current time trivially.
336	 */
337
338	t = sk_ASN1_TYPE_value(seq, i++);
339	if (t->type != V_ASN1_GENERALIZEDTIME) {
340		warnx("%s: RFC 6486 section 4.2.1: thisUpdate: "
341		    "want ASN.1 generalised time, have %s (NID %d)",
342		    p->fn, ASN1_tag2str(t->type), t->type);
343		goto out;
344	}
345	from = t->value.generalizedtime;
346
347	t = sk_ASN1_TYPE_value(seq, i++);
348	if (t->type != V_ASN1_GENERALIZEDTIME) {
349		warnx("%s: RFC 6486 section 4.2.1: nextUpdate: "
350		    "want ASN.1 generalised time, have %s (NID %d)",
351		    p->fn, ASN1_tag2str(t->type), t->type);
352		goto out;
353	}
354	until = t->value.generalizedtime;
355
356	switch (check_validity(from, until, p->fn)) {
357	case 0:
358		p->res->stale = 1;
359		/* FALLTHROUGH */
360	case 1:
361		break;
362	case -1:
363		goto out;
364	}
365
366	/* File list algorithm. */
367
368	t = sk_ASN1_TYPE_value(seq, i++);
369	if (t->type != V_ASN1_OBJECT) {
370		warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: "
371		    "want ASN.1 object, have %s (NID %d)",
372		    p->fn, ASN1_tag2str(t->type), t->type);
373		goto out;
374	}
375	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	}
392
393	if (!mft_parse_flist(p, t->value.octet_string))
394		goto out;
395
396	rc = 1;
397out:
398	sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
399	BN_free(mft_seqnum);
400	return rc;
401}
402
403/*
404 * Parse the objects that have been published in the manifest.
405 * This conforms to RFC 6486.
406 * Note that if the MFT is stale, all referenced objects are stripped
407 * from the parsed content.
408 * The MFT content is otherwise returned.
409 */
410struct mft *
411mft_parse(X509 **x509, const char *fn, const unsigned char *der, size_t len)
412{
413	struct parse	 p;
414	int		 rc = 0;
415	size_t		 cmsz;
416	unsigned char	*cms;
417
418	memset(&p, 0, sizeof(struct parse));
419	p.fn = fn;
420
421	cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz);
422	if (cms == NULL)
423		return NULL;
424	assert(*x509 != NULL);
425
426	if ((p.res = calloc(1, sizeof(struct mft))) == NULL)
427		err(1, NULL);
428
429	p.res->aia = x509_get_aia(*x509, fn);
430	p.res->aki = x509_get_aki(*x509, 0, fn);
431	p.res->ski = x509_get_ski(*x509, fn);
432	if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) {
433		warnx("%s: RFC 6487 section 4.8: "
434		    "missing AIA, AKI or SKI X509 extension", fn);
435		goto out;
436	}
437
438	if (mft_parse_econtent(cms, cmsz, &p) == 0)
439		goto out;
440
441	rc = 1;
442out:
443	if (rc == 0) {
444		mft_free(p.res);
445		p.res = NULL;
446		X509_free(*x509);
447		*x509 = NULL;
448	}
449	free(cms);
450	return p.res;
451}
452
453/*
454 * Free an MFT pointer.
455 * Safe to call with NULL.
456 */
457void
458mft_free(struct mft *p)
459{
460	size_t	 i;
461
462	if (p == NULL)
463		return;
464
465	if (p->files != NULL)
466		for (i = 0; i < p->filesz; i++)
467			free(p->files[i].file);
468
469	free(p->aia);
470	free(p->aki);
471	free(p->ski);
472	free(p->path);
473	free(p->files);
474	free(p->seqnum);
475	free(p);
476}
477
478/*
479 * Serialise MFT parsed content into the given buffer.
480 * See mft_read() for the other side of the pipe.
481 */
482void
483mft_buffer(struct ibuf *b, const struct mft *p)
484{
485	size_t		 i;
486
487	io_simple_buffer(b, &p->stale, sizeof(p->stale));
488	io_simple_buffer(b, &p->repoid, sizeof(p->repoid));
489	io_str_buffer(b, p->path);
490
491	io_str_buffer(b, p->aia);
492	io_str_buffer(b, p->aki);
493	io_str_buffer(b, p->ski);
494
495	io_simple_buffer(b, &p->filesz, sizeof(size_t));
496	for (i = 0; i < p->filesz; i++) {
497		io_str_buffer(b, p->files[i].file);
498		io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
499	}
500}
501
502/*
503 * Read an MFT structure from the file descriptor.
504 * Result must be passed to mft_free().
505 */
506struct mft *
507mft_read(struct ibuf *b)
508{
509	struct mft	*p = NULL;
510	size_t		 i;
511
512	if ((p = calloc(1, sizeof(struct mft))) == NULL)
513		err(1, NULL);
514
515	io_read_buf(b, &p->stale, sizeof(p->stale));
516	io_read_buf(b, &p->repoid, sizeof(p->repoid));
517	io_read_str(b, &p->path);
518
519	io_read_str(b, &p->aia);
520	io_read_str(b, &p->aki);
521	io_read_str(b, &p->ski);
522	assert(p->aia && p->aki && p->ski);
523
524	io_read_buf(b, &p->filesz, sizeof(size_t));
525	if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL)
526		err(1, NULL);
527
528	for (i = 0; i < p->filesz; i++) {
529		io_read_str(b, &p->files[i].file);
530		io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
531	}
532
533	return p;
534}
535