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