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