mft.c revision 1.45
1/*	$OpenBSD: mft.c,v 1.45 2022/01/13 13:46: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 <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
42static 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	if (mft_oid == NULL) {
422		mft_oid = OBJ_txt2obj("1.2.840.113549.1.9.16.1.26", 1);
423		if (mft_oid == NULL)
424			errx(1, "OBJ_txt2obj for %s failed",
425			    "1.2.840.113549.1.9.16.1.26");
426	}
427
428	cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz);
429	if (cms == NULL)
430		return NULL;
431	assert(*x509 != NULL);
432
433	if ((p.res = calloc(1, sizeof(struct mft))) == 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	if (mft_parse_econtent(cms, cmsz, &p) == 0)
446		goto out;
447
448	rc = 1;
449out:
450	if (rc == 0) {
451		mft_free(p.res);
452		p.res = NULL;
453		X509_free(*x509);
454		*x509 = NULL;
455	}
456	free(cms);
457	return p.res;
458}
459
460/*
461 * Free an MFT pointer.
462 * Safe to call with NULL.
463 */
464void
465mft_free(struct mft *p)
466{
467	size_t	 i;
468
469	if (p == NULL)
470		return;
471
472	if (p->files != NULL)
473		for (i = 0; i < p->filesz; i++)
474			free(p->files[i].file);
475
476	free(p->aia);
477	free(p->aki);
478	free(p->ski);
479	free(p->path);
480	free(p->files);
481	free(p->seqnum);
482	free(p);
483}
484
485/*
486 * Serialise MFT parsed content into the given buffer.
487 * See mft_read() for the other side of the pipe.
488 */
489void
490mft_buffer(struct ibuf *b, const struct mft *p)
491{
492	size_t		 i;
493
494	io_simple_buffer(b, &p->stale, sizeof(p->stale));
495	io_simple_buffer(b, &p->repoid, sizeof(p->repoid));
496	io_str_buffer(b, p->path);
497
498	io_str_buffer(b, p->aia);
499	io_str_buffer(b, p->aki);
500	io_str_buffer(b, p->ski);
501
502	io_simple_buffer(b, &p->filesz, sizeof(size_t));
503	for (i = 0; i < p->filesz; i++) {
504		io_str_buffer(b, p->files[i].file);
505		io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
506	}
507}
508
509/*
510 * Read an MFT structure from the file descriptor.
511 * Result must be passed to mft_free().
512 */
513struct mft *
514mft_read(struct ibuf *b)
515{
516	struct mft	*p = NULL;
517	size_t		 i;
518
519	if ((p = calloc(1, sizeof(struct mft))) == NULL)
520		err(1, NULL);
521
522	io_read_buf(b, &p->stale, sizeof(p->stale));
523	io_read_buf(b, &p->repoid, sizeof(p->repoid));
524	io_read_str(b, &p->path);
525
526	io_read_str(b, &p->aia);
527	io_read_str(b, &p->aki);
528	io_read_str(b, &p->ski);
529	assert(p->aia && p->aki && p->ski);
530
531	io_read_buf(b, &p->filesz, sizeof(size_t));
532	if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL)
533		err(1, NULL);
534
535	for (i = 0; i < p->filesz; i++) {
536		io_read_str(b, &p->files[i].file);
537		io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
538	}
539
540	return p;
541}
542