xref: /openbsd-src/usr.sbin/rpki-client/mft.c (revision f1dd7b858388b4a23f4f67a4957ec5ff656ebbe8)
1 /*	$OpenBSD: mft.c,v 1.34 2021/05/11 11:32:51 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 <fcntl.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  */
38 struct	parse {
39 	const char	*fn; /* manifest file name */
40 	struct mft	*res; /* result object */
41 };
42 
43 static const char *
44 gentime2str(const ASN1_GENERALIZEDTIME *time)
45 {
46 	static char	buf[64];
47 	BIO		*mem;
48 
49 	if ((mem = BIO_new(BIO_s_mem())) == NULL)
50 		cryptoerrx("BIO_new");
51 	if (!ASN1_GENERALIZEDTIME_print(mem, time))
52 		cryptoerrx("ASN1_GENERALIZEDTIME_print");
53 	if (BIO_gets(mem, buf, sizeof(buf)) < 0)
54 		cryptoerrx("BIO_gets");
55 
56 	BIO_free(mem);
57 	return buf;
58 }
59 
60 /*
61  * Convert an ASN1_GENERALIZEDTIME to a struct tm.
62  * Returns 1 on success, 0 on failure.
63  */
64 static int
65 generalizedtime_to_tm(const ASN1_GENERALIZEDTIME *gtime, struct tm *tm)
66 {
67 	const char *data;
68 	size_t len;
69 
70 	data = ASN1_STRING_get0_data(gtime);
71 	len = ASN1_STRING_length(gtime);
72 
73 	memset(tm, 0, sizeof(*tm));
74 	return ASN1_time_parse(data, len, tm, V_ASN1_GENERALIZEDTIME) ==
75 	    V_ASN1_GENERALIZEDTIME;
76 }
77 
78 /*
79  * Validate and verify the time validity of the mft.
80  * Returns 1 if all is good, 0 if mft is stale, any other case -1.
81  */
82 static int
83 check_validity(const ASN1_GENERALIZEDTIME *from,
84     const ASN1_GENERALIZEDTIME *until, const char *fn)
85 {
86 	time_t now = time(NULL);
87 	struct tm tm_from, tm_until, tm_now;
88 
89 	if (gmtime_r(&now, &tm_now) == NULL) {
90 		warnx("%s: could not get current time", fn);
91 		return -1;
92 	}
93 
94 	if (!generalizedtime_to_tm(from, &tm_from)) {
95 		warnx("%s: embedded from time format invalid", fn);
96 		return -1;
97 	}
98 	if (!generalizedtime_to_tm(until, &tm_until)) {
99 		warnx("%s: embedded until time format invalid", fn);
100 		return -1;
101 	}
102 
103 	/* check that until is not before from */
104 	if (ASN1_time_tm_cmp(&tm_until, &tm_from) < 0) {
105 		warnx("%s: bad update interval", fn);
106 		return -1;
107 	}
108 	/* check that now is not before from */
109 	if (ASN1_time_tm_cmp(&tm_from, &tm_now) > 0) {
110 		warnx("%s: mft not yet valid %s", fn, gentime2str(from));
111 		return -1;
112 	}
113 	/* check that now is not after until */
114 	if (ASN1_time_tm_cmp(&tm_until, &tm_now) < 0) {
115 		warnx("%s: mft expired on %s", fn, gentime2str(until));
116 		return 0;
117 	}
118 
119 	return 1;
120 }
121 
122 /*
123  * Parse an individual "FileAndHash", RFC 6486, sec. 4.2.
124  * Return zero on failure, non-zero on success.
125  */
126 static int
127 mft_parse_filehash(struct parse *p, const ASN1_OCTET_STRING *os)
128 {
129 	ASN1_SEQUENCE_ANY	*seq;
130 	const ASN1_TYPE		*file, *hash;
131 	char			*fn = NULL;
132 	const unsigned char	*d = os->data;
133 	size_t			 dsz = os->length;
134 	int			 rc = 0;
135 	struct mftfile		*fent;
136 
137 	if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
138 		cryptowarnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
139 		    "failed ASN.1 sequence parse", p->fn);
140 		goto out;
141 	} else if (sk_ASN1_TYPE_num(seq) != 2) {
142 		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
143 		    "want 2 elements, have %d", p->fn,
144 		    sk_ASN1_TYPE_num(seq));
145 		goto out;
146 	}
147 
148 	/* First is the filename itself. */
149 
150 	file = sk_ASN1_TYPE_value(seq, 0);
151 	if (file->type != V_ASN1_IA5STRING) {
152 		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
153 		    "want ASN.1 IA5 string, have %s (NID %d)",
154 		    p->fn, ASN1_tag2str(file->type), file->type);
155 		goto out;
156 	}
157 	fn = strndup((const char *)file->value.ia5string->data,
158 	    file->value.ia5string->length);
159 	if (fn == NULL)
160 		err(1, NULL);
161 
162 	/*
163 	 * Make sure we're just a pathname and either an ROA or CER.
164 	 * I don't think that the RFC specifically mentions this, but
165 	 * it's in practical use and would really screw things up
166 	 * (arbitrary filenames) otherwise.
167 	 */
168 
169 	if (strchr(fn, '/') != NULL) {
170 		warnx("%s: path components disallowed in filename: %s",
171 		    p->fn, fn);
172 		goto out;
173 	} else if (strlen(fn) <= 4) {
174 		warnx("%s: filename must be large enough for suffix part: %s",
175 		    p->fn, fn);
176 		goto out;
177 	}
178 
179 	/* Now hash value. */
180 
181 	hash = sk_ASN1_TYPE_value(seq, 1);
182 	if (hash->type != V_ASN1_BIT_STRING) {
183 		warnx("%s: RFC 6486 section 4.2.1: FileAndHash: "
184 		    "want ASN.1 bit string, have %s (NID %d)",
185 		    p->fn, ASN1_tag2str(hash->type), hash->type);
186 		goto out;
187 	}
188 
189 	if (hash->value.bit_string->length != SHA256_DIGEST_LENGTH) {
190 		warnx("%s: RFC 6486 section 4.2.1: hash: "
191 		    "invalid SHA256 length, have %d",
192 		    p->fn, hash->value.bit_string->length);
193 		goto out;
194 	}
195 
196 	/* Insert the filename and hash value. */
197 
198 	p->res->files = recallocarray(p->res->files, p->res->filesz,
199 	    p->res->filesz + 1, sizeof(struct mftfile));
200 	if (p->res->files == NULL)
201 		err(1, NULL);
202 
203 	fent = &p->res->files[p->res->filesz++];
204 
205 	fent->file = fn;
206 	fn = NULL;
207 	memcpy(fent->hash, hash->value.bit_string->data, SHA256_DIGEST_LENGTH);
208 
209 	rc = 1;
210 out:
211 	free(fn);
212 	sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
213 	return rc;
214 }
215 
216 /*
217  * Parse the "FileAndHash" sequence, RFC 6486, sec. 4.2.
218  * Return zero on failure, non-zero on success.
219  */
220 static int
221 mft_parse_flist(struct parse *p, const ASN1_OCTET_STRING *os)
222 {
223 	ASN1_SEQUENCE_ANY	*seq;
224 	const ASN1_TYPE		*t;
225 	const unsigned char	*d = os->data;
226 	size_t			 dsz = os->length;
227 	int			 i, rc = 0;
228 
229 	if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
230 		cryptowarnx("%s: RFC 6486 section 4.2: fileList: "
231 		    "failed ASN.1 sequence parse", p->fn);
232 		goto out;
233 	}
234 
235 	for (i = 0; i < sk_ASN1_TYPE_num(seq); i++) {
236 		t = sk_ASN1_TYPE_value(seq, i);
237 		if (t->type != V_ASN1_SEQUENCE) {
238 			warnx("%s: RFC 6486 section 4.2: fileList: "
239 			    "want ASN.1 sequence, have %s (NID %d)",
240 			    p->fn, ASN1_tag2str(t->type), t->type);
241 			goto out;
242 		} else if (!mft_parse_filehash(p, t->value.octet_string))
243 			goto out;
244 	}
245 
246 	rc = 1;
247 out:
248 	sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
249 	return rc;
250 }
251 
252 /*
253  * Handle the eContent of the manifest object, RFC 6486 sec. 4.2.
254  * Returns <0 on failure, 0 on stale, >0 on success.
255  */
256 static int
257 mft_parse_econtent(const unsigned char *d, size_t dsz, struct parse *p)
258 {
259 	ASN1_SEQUENCE_ANY	*seq;
260 	const ASN1_TYPE		*t;
261 	const ASN1_GENERALIZEDTIME *from, *until;
262 	BIGNUM			*mft_seqnum = NULL;
263 	long			 mft_version;
264 	int			 i, rc = -1;
265 
266 	if ((seq = d2i_ASN1_SEQUENCE_ANY(NULL, &d, dsz)) == NULL) {
267 		cryptowarnx("%s: RFC 6486 section 4.2: Manifest: "
268 		    "failed ASN.1 sequence parse", p->fn);
269 		goto out;
270 	}
271 
272 	/* The profile version is optional. */
273 
274 	if (sk_ASN1_TYPE_num(seq) != 5 &&
275 	    sk_ASN1_TYPE_num(seq) != 6) {
276 		warnx("%s: RFC 6486 section 4.2: Manifest: "
277 		    "want 5 or 6 elements, have %d", p->fn,
278 		    sk_ASN1_TYPE_num(seq));
279 		goto out;
280 	}
281 
282 	/* Start with optional profile version. */
283 
284 	i = 0;
285 	if (sk_ASN1_TYPE_num(seq) == 6) {
286 		t = sk_ASN1_TYPE_value(seq, i++);
287 		if (t->type != V_ASN1_INTEGER) {
288 			warnx("%s: RFC 6486 section 4.2.1: version: "
289 			    "want ASN.1 integer, have %s (NID %d)",
290 			    p->fn, ASN1_tag2str(t->type), t->type);
291 			goto out;
292 		}
293 
294 		if (t->value.integer == NULL)
295 			goto out;
296 
297 		mft_version = ASN1_INTEGER_get(t->value.integer);
298 		if (mft_version != 0) {
299 			warnx("%s: RFC 6486 section 4.2.1: version: "
300 			    "want 0, have %ld", p->fn, mft_version);
301 			goto out;
302 		}
303 	}
304 
305 	/* Now the manifest sequence number. */
306 
307 	t = sk_ASN1_TYPE_value(seq, i++);
308 	if (t->type != V_ASN1_INTEGER) {
309 		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
310 		    "want ASN.1 integer, have %s (NID %d)",
311 		    p->fn, ASN1_tag2str(t->type), t->type);
312 		goto out;
313 	}
314 
315 	mft_seqnum = ASN1_INTEGER_to_BN(t->value.integer, NULL);
316 	if (mft_seqnum == NULL) {
317 		warnx("%s: ASN1_INTEGER_to_BN error", p->fn);
318 		goto out;
319 	}
320 
321 	if (BN_is_negative(mft_seqnum)) {
322 		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
323 		    "want positive integer, have negative.", p->fn);
324 		goto out;
325 	}
326 
327 	if (BN_num_bytes(mft_seqnum) > 20) {
328 		warnx("%s: RFC 6486 section 4.2.1: manifestNumber: "
329 		    "want 20 or less than octets, have more.", p->fn);
330 		goto out;
331 	}
332 
333 	p->res->seqnum = BN_bn2hex(mft_seqnum);
334 	if (p->res->seqnum == NULL) {
335 		warnx("%s: BN_bn2hex error", p->fn);
336 		goto out;
337 	}
338 
339 	/*
340 	 * Timestamps: this and next update time.
341 	 * Validate that the current date falls into this interval.
342 	 * This is required by section 4.4, (3).
343 	 * If we're after the given date, then the MFT is stale.
344 	 * This is made super complicated because it uses OpenSSL's
345 	 * ASN1_GENERALIZEDTIME instead of ASN1_TIME, which we could
346 	 * compare against the current time trivially.
347 	 */
348 
349 	t = sk_ASN1_TYPE_value(seq, i++);
350 	if (t->type != V_ASN1_GENERALIZEDTIME) {
351 		warnx("%s: RFC 6486 section 4.2.1: thisUpdate: "
352 		    "want ASN.1 generalised time, have %s (NID %d)",
353 		    p->fn, ASN1_tag2str(t->type), t->type);
354 		goto out;
355 	}
356 	from = t->value.generalizedtime;
357 
358 	t = sk_ASN1_TYPE_value(seq, i++);
359 	if (t->type != V_ASN1_GENERALIZEDTIME) {
360 		warnx("%s: RFC 6486 section 4.2.1: nextUpdate: "
361 		    "want ASN.1 generalised time, have %s (NID %d)",
362 		    p->fn, ASN1_tag2str(t->type), t->type);
363 		goto out;
364 	}
365 	until = t->value.generalizedtime;
366 
367 	rc = check_validity(from, until, p->fn);
368 	if (rc != 1)
369 		goto out;
370 
371 	/* The mft is valid. Reset rc so later 'goto out' return failure. */
372 	rc = -1;
373 
374 	/* File list algorithm. */
375 
376 	t = sk_ASN1_TYPE_value(seq, i++);
377 	if (t->type != V_ASN1_OBJECT) {
378 		warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: "
379 		    "want ASN.1 object time, have %s (NID %d)",
380 		    p->fn, ASN1_tag2str(t->type), t->type);
381 		goto out;
382 	} else if (OBJ_obj2nid(t->value.object) != NID_sha256) {
383 		warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: "
384 		    "want SHA256 object, have %s (NID %d)", p->fn,
385 		    ASN1_tag2str(OBJ_obj2nid(t->value.object)),
386 		    OBJ_obj2nid(t->value.object));
387 		goto out;
388 	}
389 
390 	/* Now the sequence. */
391 
392 	t = sk_ASN1_TYPE_value(seq, i++);
393 	if (t->type != V_ASN1_SEQUENCE) {
394 		warnx("%s: RFC 6486 section 4.2.1: fileList: "
395 		    "want ASN.1 sequence, have %s (NID %d)",
396 		    p->fn, ASN1_tag2str(t->type), t->type);
397 		goto out;
398 	} else if (!mft_parse_flist(p, t->value.octet_string))
399 		goto out;
400 
401 	rc = 1;
402 out:
403 	sk_ASN1_TYPE_pop_free(seq, ASN1_TYPE_free);
404 	BN_free(mft_seqnum);
405 	return rc;
406 }
407 
408 /*
409  * Parse the objects that have been published in the manifest.
410  * This conforms to RFC 6486.
411  * Note that if the MFT is stale, all referenced objects are stripped
412  * from the parsed content.
413  * The MFT content is otherwise returned.
414  */
415 struct mft *
416 mft_parse(X509 **x509, const char *fn)
417 {
418 	struct parse	 p;
419 	int		 c, rc = 0;
420 	size_t		 i, cmsz;
421 	unsigned char	*cms;
422 
423 	memset(&p, 0, sizeof(struct parse));
424 	p.fn = fn;
425 
426 	cms = cms_parse_validate(x509, fn, "1.2.840.113549.1.9.16.1.26",
427 	    &cmsz);
428 	if (cms == NULL)
429 		return NULL;
430 	assert(*x509 != NULL);
431 
432 	if ((p.res = calloc(1, sizeof(struct mft))) == NULL)
433 		err(1, NULL);
434 	if ((p.res->file = strdup(fn)) == NULL)
435 		err(1, NULL);
436 
437 	p.res->aia = x509_get_aia(*x509, fn);
438 	p.res->aki = x509_get_aki(*x509, 0, fn);
439 	p.res->ski = x509_get_ski(*x509, fn);
440 	if (p.res->aia == NULL || p.res->aki == NULL || p.res->ski == NULL) {
441 		warnx("%s: RFC 6487 section 4.8: "
442 		    "missing AIA, AKI or SKI X509 extension", fn);
443 		goto out;
444 	}
445 
446 	/*
447 	 * If we're stale, then remove all of the files that the MFT
448 	 * references as well as marking it as stale.
449 	 */
450 
451 	if ((c = mft_parse_econtent(cms, cmsz, &p)) == 0) {
452 		/*
453 		 * FIXME: it should suffice to just mark this as stale
454 		 * and have the logic around mft_read() simply ignore
455 		 * the contents of stale entries, just like it does for
456 		 * invalid ROAs or certificates.
457 		 */
458 
459 		p.res->stale = 1;
460 		if (p.res->files != NULL)
461 			for (i = 0; i < p.res->filesz; i++)
462 				free(p.res->files[i].file);
463 		free(p.res->files);
464 		p.res->filesz = 0;
465 		p.res->files = NULL;
466 	} else if (c == -1)
467 		goto out;
468 
469 	rc = 1;
470 out:
471 	if (rc == 0) {
472 		mft_free(p.res);
473 		p.res = NULL;
474 		X509_free(*x509);
475 		*x509 = NULL;
476 	}
477 	free(cms);
478 	return p.res;
479 }
480 
481 /*
482  * Check all files and their hashes in a MFT structure.
483  * Return zero on failure, non-zero on success.
484  */
485 int
486 mft_check(const char *fn, struct mft *p)
487 {
488 	size_t	i;
489 	int	rc = 1;
490 	char	*cp, *path = NULL;
491 
492 	/* Check hash of file now, but first build path for it */
493 	cp = strrchr(fn, '/');
494 	assert(cp != NULL);
495 	assert(cp - fn < INT_MAX);
496 
497 	for (i = 0; i < p->filesz; i++) {
498 		const struct mftfile *m = &p->files[i];
499 		if (asprintf(&path, "%.*s/%s", (int)(cp - fn), fn,
500 		    m->file) == -1)
501 			err(1, NULL);
502 		if (!valid_filehash(path, m->hash, sizeof(m->hash))) {
503 			warnx("%s: bad message digest for %s", fn, m->file);
504 			rc = 0;
505 		}
506 		free(path);
507 	}
508 
509 	return rc;
510 }
511 
512 /*
513  * Free an MFT pointer.
514  * Safe to call with NULL.
515  */
516 void
517 mft_free(struct mft *p)
518 {
519 	size_t	 i;
520 
521 	if (p == NULL)
522 		return;
523 
524 	if (p->files != NULL)
525 		for (i = 0; i < p->filesz; i++)
526 			free(p->files[i].file);
527 
528 	free(p->aia);
529 	free(p->aki);
530 	free(p->ski);
531 	free(p->file);
532 	free(p->files);
533 	free(p->seqnum);
534 	free(p);
535 }
536 
537 /*
538  * Serialise MFT parsed content into the given buffer.
539  * See mft_read() for the other side of the pipe.
540  */
541 void
542 mft_buffer(struct ibuf *b, const struct mft *p)
543 {
544 	size_t		 i;
545 
546 	io_simple_buffer(b, &p->stale, sizeof(int));
547 	io_str_buffer(b, p->file);
548 	io_simple_buffer(b, &p->filesz, sizeof(size_t));
549 
550 	for (i = 0; i < p->filesz; i++) {
551 		io_str_buffer(b, p->files[i].file);
552 		io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
553 	}
554 
555 	io_str_buffer(b, p->aia);
556 	io_str_buffer(b, p->aki);
557 	io_str_buffer(b, p->ski);
558 }
559 
560 /*
561  * Read an MFT structure from the file descriptor.
562  * Result must be passed to mft_free().
563  */
564 struct mft *
565 mft_read(int fd)
566 {
567 	struct mft	*p = NULL;
568 	size_t		 i;
569 
570 	if ((p = calloc(1, sizeof(struct mft))) == NULL)
571 		err(1, NULL);
572 
573 	io_simple_read(fd, &p->stale, sizeof(int));
574 	io_str_read(fd, &p->file);
575 	assert(p->file);
576 	io_simple_read(fd, &p->filesz, sizeof(size_t));
577 
578 	if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL)
579 		err(1, NULL);
580 
581 	for (i = 0; i < p->filesz; i++) {
582 		io_str_read(fd, &p->files[i].file);
583 		io_simple_read(fd, p->files[i].hash, SHA256_DIGEST_LENGTH);
584 	}
585 
586 	io_str_read(fd, &p->aia);
587 	io_str_read(fd, &p->aki);
588 	io_str_read(fd, &p->ski);
589 	assert(p->aia && p->aki && p->ski);
590 
591 	return p;
592 }
593