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