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