xref: /openbsd-src/usr.sbin/rpki-client/mft.c (revision bb0e26feb6a1469f5f4962d82baf3f0a93cfe014)
1*bb0e26feStb /*	$OpenBSD: mft.c,v 1.121 2024/12/24 10:03:59 tb Exp $ */
29a7e9e7fSjob /*
3740e9a54Stb  * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
49a7e9e7fSjob  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
59a7e9e7fSjob  *
69a7e9e7fSjob  * Permission to use, copy, modify, and distribute this software for any
79a7e9e7fSjob  * purpose with or without fee is hereby granted, provided that the above
89a7e9e7fSjob  * copyright notice and this permission notice appear in all copies.
99a7e9e7fSjob  *
109a7e9e7fSjob  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
119a7e9e7fSjob  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
129a7e9e7fSjob  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
139a7e9e7fSjob  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
149a7e9e7fSjob  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
159a7e9e7fSjob  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
169a7e9e7fSjob  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
179a7e9e7fSjob  */
189a7e9e7fSjob 
199a7e9e7fSjob #include <assert.h>
209a7e9e7fSjob #include <err.h>
21fa2ce094Sclaudio #include <limits.h>
229a7e9e7fSjob #include <stdint.h>
239a7e9e7fSjob #include <stdlib.h>
249a7e9e7fSjob #include <string.h>
259a7e9e7fSjob #include <unistd.h>
269a7e9e7fSjob 
27ab5c69fdSjob #include <openssl/bn.h>
28a1753de6Sclaudio #include <openssl/asn1.h>
2923e50b68Stb #include <openssl/asn1t.h>
3023e50b68Stb #include <openssl/safestack.h>
3196932cf3Sderaadt #include <openssl/sha.h>
3223e50b68Stb #include <openssl/stack.h>
33a1753de6Sclaudio #include <openssl/x509.h>
349a7e9e7fSjob 
359a7e9e7fSjob #include "extern.h"
369a7e9e7fSjob 
37de9b6f5dSclaudio extern ASN1_OBJECT	*mft_oid;
38318f0572Sjob BN_CTX			*bn_ctx;
39d2e465bbSclaudio 
409a7e9e7fSjob /*
4123e50b68Stb  * Types and templates for the Manifest eContent, RFC 6486, section 4.2.
4223e50b68Stb  */
4323e50b68Stb 
44d3d26873Sjob ASN1_ITEM_EXP FileAndHash_it;
45d3d26873Sjob ASN1_ITEM_EXP Manifest_it;
46d3d26873Sjob 
4723e50b68Stb typedef struct {
4823e50b68Stb 	ASN1_IA5STRING	*file;
4923e50b68Stb 	ASN1_BIT_STRING	*hash;
5023e50b68Stb } FileAndHash;
5123e50b68Stb 
5223e50b68Stb DECLARE_STACK_OF(FileAndHash);
5323e50b68Stb 
549395df6fStb #ifndef DEFINE_STACK_OF
5558c75743Stb #define sk_FileAndHash_dup(sk)		SKM_sk_dup(FileAndHash, (sk))
5658c75743Stb #define sk_FileAndHash_free(sk)		SKM_sk_free(FileAndHash, (sk))
5723e50b68Stb #define sk_FileAndHash_num(sk)		SKM_sk_num(FileAndHash, (sk))
5823e50b68Stb #define sk_FileAndHash_value(sk, i)	SKM_sk_value(FileAndHash, (sk), (i))
5958c75743Stb #define sk_FileAndHash_sort(sk)		SKM_sk_sort(FileAndHash, (sk))
6058c75743Stb #define sk_FileAndHash_set_cmp_func(sk, cmp) \
6158c75743Stb     SKM_sk_set_cmp_func(FileAndHash, (sk), (cmp))
6223e50b68Stb #endif
6323e50b68Stb 
6423e50b68Stb typedef struct {
6523e50b68Stb 	ASN1_INTEGER		*version;
6623e50b68Stb 	ASN1_INTEGER		*manifestNumber;
6723e50b68Stb 	ASN1_GENERALIZEDTIME	*thisUpdate;
6823e50b68Stb 	ASN1_GENERALIZEDTIME	*nextUpdate;
6923e50b68Stb 	ASN1_OBJECT		*fileHashAlg;
7023e50b68Stb 	STACK_OF(FileAndHash)	*fileList;
7123e50b68Stb } Manifest;
7223e50b68Stb 
7323e50b68Stb ASN1_SEQUENCE(FileAndHash) = {
7423e50b68Stb 	ASN1_SIMPLE(FileAndHash, file, ASN1_IA5STRING),
7523e50b68Stb 	ASN1_SIMPLE(FileAndHash, hash, ASN1_BIT_STRING),
7623e50b68Stb } ASN1_SEQUENCE_END(FileAndHash);
7723e50b68Stb 
7823e50b68Stb ASN1_SEQUENCE(Manifest) = {
79bb9f70d1Stb 	ASN1_EXP_OPT(Manifest, version, ASN1_INTEGER, 0),
8023e50b68Stb 	ASN1_SIMPLE(Manifest, manifestNumber, ASN1_INTEGER),
8123e50b68Stb 	ASN1_SIMPLE(Manifest, thisUpdate, ASN1_GENERALIZEDTIME),
8223e50b68Stb 	ASN1_SIMPLE(Manifest, nextUpdate, ASN1_GENERALIZEDTIME),
8323e50b68Stb 	ASN1_SIMPLE(Manifest, fileHashAlg, ASN1_OBJECT),
8423e50b68Stb 	ASN1_SEQUENCE_OF(Manifest, fileList, FileAndHash),
8523e50b68Stb } ASN1_SEQUENCE_END(Manifest);
8623e50b68Stb 
8723e50b68Stb DECLARE_ASN1_FUNCTIONS(Manifest);
8823e50b68Stb IMPLEMENT_ASN1_FUNCTIONS(Manifest);
8923e50b68Stb 
908a0e7acdStb #define GENTIME_LENGTH 15
918a0e7acdStb 
9223e50b68Stb /*
93043caafeStb  * Determine rtype corresponding to file extension. Returns RTYPE_INVALID
94f452fe1cStb  * on error or unknown extension.
95043caafeStb  */
96043caafeStb enum rtype
97043caafeStb rtype_from_file_extension(const char *fn)
98043caafeStb {
99043caafeStb 	size_t	 sz;
100043caafeStb 
101043caafeStb 	sz = strlen(fn);
102043caafeStb 	if (sz < 5)
103043caafeStb 		return RTYPE_INVALID;
104043caafeStb 
105043caafeStb 	if (strcasecmp(fn + sz - 4, ".tal") == 0)
106043caafeStb 		return RTYPE_TAL;
107043caafeStb 	if (strcasecmp(fn + sz - 4, ".cer") == 0)
108043caafeStb 		return RTYPE_CER;
109043caafeStb 	if (strcasecmp(fn + sz - 4, ".crl") == 0)
110043caafeStb 		return RTYPE_CRL;
111043caafeStb 	if (strcasecmp(fn + sz - 4, ".mft") == 0)
112043caafeStb 		return RTYPE_MFT;
113043caafeStb 	if (strcasecmp(fn + sz - 4, ".roa") == 0)
114043caafeStb 		return RTYPE_ROA;
115043caafeStb 	if (strcasecmp(fn + sz - 4, ".gbr") == 0)
116043caafeStb 		return RTYPE_GBR;
11704834fbdSjob 	if (strcasecmp(fn + sz - 4, ".sig") == 0)
11804834fbdSjob 		return RTYPE_RSC;
119a29ddfd5Sjob 	if (strcasecmp(fn + sz - 4, ".asa") == 0)
120a29ddfd5Sjob 		return RTYPE_ASPA;
121ee2a33daSjob 	if (strcasecmp(fn + sz - 4, ".tak") == 0)
122ee2a33daSjob 		return RTYPE_TAK;
123ef3f6f56Sjob 	if (strcasecmp(fn + sz - 4, ".csv") == 0)
124ef3f6f56Sjob 		return RTYPE_GEOFEED;
125d4be4cdeSjob 	if (strcasecmp(fn + sz - 4, ".spl") == 0)
126d4be4cdeSjob 		return RTYPE_SPL;
127043caafeStb 
128043caafeStb 	return RTYPE_INVALID;
129043caafeStb }
130043caafeStb 
131043caafeStb /*
132043caafeStb  * Validate that a filename listed on a Manifest only contains characters
133c7a965b3Stb  * permitted in RFC 9286 section 4.2.2.
134203dfefcStb  * Also ensure that there is exactly one '.'.
135043caafeStb  */
136685326f5Sclaudio static int
137203dfefcStb valid_mft_filename(const char *fn, size_t len)
138043caafeStb {
139043caafeStb 	const unsigned char *c;
140043caafeStb 
141203dfefcStb 	if (!valid_filename(fn, len))
142685326f5Sclaudio 		return 0;
143043caafeStb 
144685326f5Sclaudio 	c = memchr(fn, '.', len);
145685326f5Sclaudio 	if (c == NULL || c != memrchr(fn, '.', len))
146685326f5Sclaudio 		return 0;
147685326f5Sclaudio 
148685326f5Sclaudio 	return 1;
149685326f5Sclaudio }
150685326f5Sclaudio 
151685326f5Sclaudio /*
152a7b03bb4Sclaudio  * Check that the file is allowed to be part of a manifest and the parser
153a7b03bb4Sclaudio  * for this type is implemented in rpki-client.
154a7b03bb4Sclaudio  * Returns corresponding rtype or RTYPE_INVALID to mark the file as unknown.
155685326f5Sclaudio  */
156685326f5Sclaudio static enum rtype
157685326f5Sclaudio rtype_from_mftfile(const char *fn)
158685326f5Sclaudio {
159685326f5Sclaudio 	enum rtype		 type;
160043caafeStb 
161043caafeStb 	type = rtype_from_file_extension(fn);
162043caafeStb 	switch (type) {
163043caafeStb 	case RTYPE_CER:
164043caafeStb 	case RTYPE_CRL:
165043caafeStb 	case RTYPE_GBR:
166043caafeStb 	case RTYPE_ROA:
167a29ddfd5Sjob 	case RTYPE_ASPA:
168d4be4cdeSjob 	case RTYPE_SPL:
169ee2a33daSjob 	case RTYPE_TAK:
170043caafeStb 		return type;
171043caafeStb 	default:
172043caafeStb 		return RTYPE_INVALID;
173043caafeStb 	}
174043caafeStb }
175043caafeStb 
176043caafeStb /*
1779a7e9e7fSjob  * Parse an individual "FileAndHash", RFC 6486, sec. 4.2.
1789a7e9e7fSjob  * Return zero on failure, non-zero on success.
1799a7e9e7fSjob  */
1809a7e9e7fSjob static int
181be6e5ad5Stb mft_parse_filehash(const char *fn, struct mft *mft, const FileAndHash *fh,
182be6e5ad5Stb     int *found_crl)
1839a7e9e7fSjob {
184d7658e14Stb 	char			*file = NULL;
185630e12adSclaudio 	int			 rc = 0;
1869a7e9e7fSjob 	struct mftfile		*fent;
18723bc08f8Sclaudio 	enum rtype		 type;
18831ac5904Sjob 	size_t			 new_idx = 0;
1899a7e9e7fSjob 
19023e50b68Stb 	if (!valid_mft_filename(fh->file->data, fh->file->length)) {
191be6e5ad5Stb 		warnx("%s: RFC 6486 section 4.2.2: bad filename", fn);
192685326f5Sclaudio 		goto out;
193685326f5Sclaudio 	}
194d7658e14Stb 	file = strndup(fh->file->data, fh->file->length);
195d7658e14Stb 	if (file == NULL)
1967addb040Sbenno 		err(1, NULL);
1979a7e9e7fSjob 
19823e50b68Stb 	if (fh->hash->length != SHA256_DIGEST_LENGTH) {
1999a7e9e7fSjob 		warnx("%s: RFC 6486 section 4.2.1: hash: "
200be6e5ad5Stb 		    "invalid SHA256 length, have %d", fn, fh->hash->length);
201630e12adSclaudio 		goto out;
2029a7e9e7fSjob 	}
2039a7e9e7fSjob 
204d7658e14Stb 	type = rtype_from_mftfile(file);
2058a9424f8Sjob 	if (type == RTYPE_CRL) {
2068a9424f8Sjob 		if (*found_crl == 1) {
2078a9424f8Sjob 			warnx("%s: RFC 6487: too many CRLs listed on MFT", fn);
2088a9424f8Sjob 			goto out;
2098a9424f8Sjob 		}
2108a9424f8Sjob 		if (strcmp(file, mft->crl) != 0) {
2118a9424f8Sjob 			warnx("%s: RFC 6487: name (%s) doesn't match CRLDP "
2128a9424f8Sjob 			    "(%s)", fn, file, mft->crl);
2138a9424f8Sjob 			goto out;
2148a9424f8Sjob 		}
21523bc08f8Sclaudio 		/* remember the filehash for the CRL in struct mft */
216be6e5ad5Stb 		memcpy(mft->crlhash, fh->hash->data, SHA256_DIGEST_LENGTH);
217be6e5ad5Stb 		*found_crl = 1;
21823bc08f8Sclaudio 	}
21923bc08f8Sclaudio 
22031ac5904Sjob 	if (filemode)
221be6e5ad5Stb 		fent = &mft->files[mft->filesz++];
22231ac5904Sjob 	else {
22331ac5904Sjob 		/* Fisher-Yates shuffle */
224be6e5ad5Stb 		new_idx = arc4random_uniform(mft->filesz + 1);
225be6e5ad5Stb 		mft->files[mft->filesz++] = mft->files[new_idx];
226be6e5ad5Stb 		fent = &mft->files[new_idx];
22731ac5904Sjob 	}
22831ac5904Sjob 
22923bc08f8Sclaudio 	fent->type = type;
230d7658e14Stb 	fent->file = file;
231d7658e14Stb 	file = NULL;
23223e50b68Stb 	memcpy(fent->hash, fh->hash->data, SHA256_DIGEST_LENGTH);
2339a7e9e7fSjob 
234630e12adSclaudio 	rc = 1;
235630e12adSclaudio  out:
236d7658e14Stb 	free(file);
2379a7e9e7fSjob 	return rc;
2389a7e9e7fSjob }
2399a7e9e7fSjob 
24058c75743Stb static int
24158c75743Stb mft_fh_cmp_name(const FileAndHash *const *a, const FileAndHash *const *b)
24258c75743Stb {
24358c75743Stb 	if ((*a)->file->length < (*b)->file->length)
24458c75743Stb 		return -1;
24558c75743Stb 	if ((*a)->file->length > (*b)->file->length)
24658c75743Stb 		return 1;
24758c75743Stb 
24858c75743Stb 	return memcmp((*a)->file->data, (*b)->file->data, (*b)->file->length);
24958c75743Stb }
25058c75743Stb 
25158c75743Stb static int
25258c75743Stb mft_fh_cmp_hash(const FileAndHash *const *a, const FileAndHash *const *b)
25358c75743Stb {
25458c75743Stb 	assert((*a)->hash->length == SHA256_DIGEST_LENGTH);
25558c75743Stb 	assert((*b)->hash->length == SHA256_DIGEST_LENGTH);
25658c75743Stb 
25758c75743Stb 	return memcmp((*a)->hash->data, (*b)->hash->data, (*b)->hash->length);
25858c75743Stb }
25958c75743Stb 
26058c75743Stb /*
26158c75743Stb  * Assuming that the hash lengths are validated, this checks that all file names
26258c75743Stb  * and hashes in a manifest are unique. Returns 1 on success, 0 on failure.
26358c75743Stb  */
26458c75743Stb static int
26558c75743Stb mft_has_unique_names_and_hashes(const char *fn, const Manifest *mft)
26658c75743Stb {
26758c75743Stb 	STACK_OF(FileAndHash)	*fhs;
26858c75743Stb 	int			 i, ret = 0;
26958c75743Stb 
27058c75743Stb 	if ((fhs = sk_FileAndHash_dup(mft->fileList)) == NULL)
27158c75743Stb 		err(1, NULL);
27258c75743Stb 
27358c75743Stb 	(void)sk_FileAndHash_set_cmp_func(fhs, mft_fh_cmp_name);
27458c75743Stb 	sk_FileAndHash_sort(fhs);
27558c75743Stb 
27658c75743Stb 	for (i = 0; i < sk_FileAndHash_num(fhs) - 1; i++) {
27758c75743Stb 		const FileAndHash *curr = sk_FileAndHash_value(fhs, i);
27858c75743Stb 		const FileAndHash *next = sk_FileAndHash_value(fhs, i + 1);
27958c75743Stb 
28058c75743Stb 		if (mft_fh_cmp_name(&curr, &next) == 0) {
28158c75743Stb 			warnx("%s: duplicate name: %.*s", fn,
28258c75743Stb 			    curr->file->length, curr->file->data);
28358c75743Stb 			goto err;
28458c75743Stb 		}
28558c75743Stb 	}
28658c75743Stb 
28758c75743Stb 	(void)sk_FileAndHash_set_cmp_func(fhs, mft_fh_cmp_hash);
28858c75743Stb 	sk_FileAndHash_sort(fhs);
28958c75743Stb 
29058c75743Stb 	for (i = 0; i < sk_FileAndHash_num(fhs) - 1; i++) {
29158c75743Stb 		const FileAndHash *curr = sk_FileAndHash_value(fhs, i);
29258c75743Stb 		const FileAndHash *next = sk_FileAndHash_value(fhs, i + 1);
29358c75743Stb 
29458c75743Stb 		if (mft_fh_cmp_hash(&curr, &next) == 0) {
29558c75743Stb 			warnx("%s: duplicate hash for %.*s and %.*s", fn,
29658c75743Stb 			    curr->file->length, curr->file->data,
29758c75743Stb 			    next->file->length, next->file->data);
29858c75743Stb 			goto err;
29958c75743Stb 		}
30058c75743Stb 	}
30158c75743Stb 
30258c75743Stb 	ret = 1;
30358c75743Stb 
30458c75743Stb  err:
30558c75743Stb 	sk_FileAndHash_free(fhs);
30658c75743Stb 
30758c75743Stb 	return ret;
30858c75743Stb }
30958c75743Stb 
3109a7e9e7fSjob /*
3119a7e9e7fSjob  * Handle the eContent of the manifest object, RFC 6486 sec. 4.2.
312651b53faSclaudio  * Returns 0 on failure and 1 on success.
3139a7e9e7fSjob  */
3149a7e9e7fSjob static int
315be6e5ad5Stb mft_parse_econtent(const char *fn, struct mft *mft, const unsigned char *d,
316be6e5ad5Stb     size_t dsz)
3179a7e9e7fSjob {
318d115f50dSjob 	const unsigned char	*oder;
319cd9dc441Stb 	Manifest		*mft_asn1;
32023e50b68Stb 	FileAndHash		*fh;
321be6e5ad5Stb 	int			 found_crl, i, rc = 0;
3229a7e9e7fSjob 
323d115f50dSjob 	oder = d;
324cd9dc441Stb 	if ((mft_asn1 = d2i_Manifest(NULL, &d, dsz)) == NULL) {
325be6e5ad5Stb 		warnx("%s: RFC 6486 section 4: failed to parse Manifest", fn);
3269a7e9e7fSjob 		goto out;
3279a7e9e7fSjob 	}
328d115f50dSjob 	if (d != oder + dsz) {
329be6e5ad5Stb 		warnx("%s: %td bytes trailing garbage in eContent", fn,
330d115f50dSjob 		    oder + dsz - d);
331d115f50dSjob 		goto out;
332d115f50dSjob 	}
3339a7e9e7fSjob 
334be6e5ad5Stb 	if (!valid_econtent_version(fn, mft_asn1->version, 0))
3359a7e9e7fSjob 		goto out;
336ab5c69fdSjob 
337904d9c60Stb 	mft->seqnum = x509_convert_seqnum(fn, "manifest number",
338904d9c60Stb 	    mft_asn1->manifestNumber);
339be6e5ad5Stb 	if (mft->seqnum == NULL)
340ab5c69fdSjob 		goto out;
341ab5c69fdSjob 
342851cae3dSjob 	/*
343851cae3dSjob 	 * OpenSSL's DER decoder implementation will accept a GeneralizedTime
344851cae3dSjob 	 * which doesn't conform to RFC 5280. So, double check.
345851cae3dSjob 	 */
346cd9dc441Stb 	if (ASN1_STRING_length(mft_asn1->thisUpdate) != GENTIME_LENGTH) {
347be6e5ad5Stb 		warnx("%s: embedded from time format invalid", fn);
3489a7e9e7fSjob 		goto out;
349851cae3dSjob 	}
350cd9dc441Stb 	if (ASN1_STRING_length(mft_asn1->nextUpdate) != GENTIME_LENGTH) {
351be6e5ad5Stb 		warnx("%s: embedded until time format invalid", fn);
352851cae3dSjob 		goto out;
353851cae3dSjob 	}
354851cae3dSjob 
355be6e5ad5Stb 	if (!x509_get_time(mft_asn1->thisUpdate, &mft->thisupdate)) {
356be6e5ad5Stb 		warn("%s: parsing manifest thisUpdate failed", fn);
357851cae3dSjob 		goto out;
358851cae3dSjob 	}
359be6e5ad5Stb 	if (!x509_get_time(mft_asn1->nextUpdate, &mft->nextupdate)) {
360be6e5ad5Stb 		warn("%s: parsing manifest nextUpdate failed", fn);
361851cae3dSjob 		goto out;
362851cae3dSjob 	}
363851cae3dSjob 
364be6e5ad5Stb 	if (mft->thisupdate > mft->nextupdate) {
365be6e5ad5Stb 		warnx("%s: bad update interval", fn);
366851cae3dSjob 		goto out;
367851cae3dSjob 	}
368bf682897Stb 
369cd9dc441Stb 	if (OBJ_obj2nid(mft_asn1->fileHashAlg) != NID_sha256) {
3709a7e9e7fSjob 		warnx("%s: RFC 6486 section 4.2.1: fileHashAlg: "
3711731179dStb 		    "want SHA256 object, have %s", fn,
3721731179dStb 		    nid2str(OBJ_obj2nid(mft_asn1->fileHashAlg)));
3739a7e9e7fSjob 		goto out;
3749a7e9e7fSjob 	}
3759a7e9e7fSjob 
376*bb0e26feStb 	if (sk_FileAndHash_num(mft_asn1->fileList) <= 0) {
377*bb0e26feStb 		warnx("%s: no files in manifest fileList", fn);
378*bb0e26feStb 		goto out;
379*bb0e26feStb 	}
380cd9dc441Stb 	if (sk_FileAndHash_num(mft_asn1->fileList) >= MAX_MANIFEST_ENTRIES) {
381be6e5ad5Stb 		warnx("%s: %d exceeds manifest entry limit (%d)", fn,
382cd9dc441Stb 		    sk_FileAndHash_num(mft_asn1->fileList),
383cd9dc441Stb 		    MAX_MANIFEST_ENTRIES);
3849a7e9e7fSjob 		goto out;
385651b53faSclaudio 	}
386651b53faSclaudio 
387be6e5ad5Stb 	mft->files = calloc(sk_FileAndHash_num(mft_asn1->fileList),
38823e50b68Stb 	    sizeof(struct mftfile));
389be6e5ad5Stb 	if (mft->files == NULL)
39023e50b68Stb 		err(1, NULL);
39123e50b68Stb 
392be6e5ad5Stb 	found_crl = 0;
393cd9dc441Stb 	for (i = 0; i < sk_FileAndHash_num(mft_asn1->fileList); i++) {
394cd9dc441Stb 		fh = sk_FileAndHash_value(mft_asn1->fileList, i);
395be6e5ad5Stb 		if (!mft_parse_filehash(fn, mft, fh, &found_crl))
3969a7e9e7fSjob 			goto out;
39723e50b68Stb 	}
39823e50b68Stb 
399be6e5ad5Stb 	if (!found_crl) {
400be6e5ad5Stb 		warnx("%s: CRL not part of MFT fileList", fn);
40123e50b68Stb 		goto out;
40223e50b68Stb 	}
4039a7e9e7fSjob 
404be6e5ad5Stb 	if (!mft_has_unique_names_and_hashes(fn, mft_asn1))
40558c75743Stb 		goto out;
40658c75743Stb 
407bf682897Stb 	rc = 1;
4089a7e9e7fSjob  out:
409cd9dc441Stb 	Manifest_free(mft_asn1);
4109a7e9e7fSjob 	return rc;
4119a7e9e7fSjob }
4129a7e9e7fSjob 
4139a7e9e7fSjob /*
4149a7e9e7fSjob  * Parse the objects that have been published in the manifest.
415c6be5ad7Sjob  * Return mft if it conforms to RFC 6486, otherwise NULL.
4169a7e9e7fSjob  */
4179a7e9e7fSjob struct mft *
4180636c4d0Stb mft_parse(X509 **x509, const char *fn, int talid, const unsigned char *der,
4190636c4d0Stb     size_t len)
4209a7e9e7fSjob {
421be6e5ad5Stb 	struct mft	*mft;
422b454bae8Sjob 	struct cert	*cert = NULL;
423651b53faSclaudio 	int		 rc = 0;
424651b53faSclaudio 	size_t		 cmsz;
4259a7e9e7fSjob 	unsigned char	*cms;
42605ed64a0Stb 	char		*crldp = NULL, *crlfile;
427beccb378Stb 	time_t		 signtime = 0;
4289a7e9e7fSjob 
4291330c972Stb 	cms = cms_parse_validate(x509, fn, der, len, mft_oid, &cmsz, &signtime);
4309a7e9e7fSjob 	if (cms == NULL)
4319a7e9e7fSjob 		return NULL;
4329a7e9e7fSjob 	assert(*x509 != NULL);
4339a7e9e7fSjob 
434be6e5ad5Stb 	if ((mft = calloc(1, sizeof(*mft))) == NULL)
4357addb040Sbenno 		err(1, NULL);
436be6e5ad5Stb 	mft->signtime = signtime;
4371f25fa5dStb 
438be6e5ad5Stb 	if (!x509_get_aia(*x509, fn, &mft->aia))
439f999fe57Sclaudio 		goto out;
440be6e5ad5Stb 	if (!x509_get_aki(*x509, fn, &mft->aki))
441f999fe57Sclaudio 		goto out;
442be6e5ad5Stb 	if (!x509_get_sia(*x509, fn, &mft->sia))
4432cf0e122Sjob 		goto out;
444be6e5ad5Stb 	if (!x509_get_ski(*x509, fn, &mft->ski))
445f999fe57Sclaudio 		goto out;
446be6e5ad5Stb 	if (mft->aia == NULL || mft->aki == NULL || mft->sia == NULL ||
447be6e5ad5Stb 	    mft->ski == NULL) {
4481f25fa5dStb 		warnx("%s: RFC 6487 section 4.8: "
4492cf0e122Sjob 		    "missing AIA, AKI, SIA, or SKI X509 extension", fn);
4509a7e9e7fSjob 		goto out;
4511f25fa5dStb 	}
4529a7e9e7fSjob 
4533a363cbdSjob 	if (!x509_inherits(*x509)) {
4543a363cbdSjob 		warnx("%s: RFC 3779 extension not set to inherit", fn);
4553a363cbdSjob 		goto out;
4563a363cbdSjob 	}
4573a363cbdSjob 
45823bc08f8Sclaudio 	/* get CRL info for later */
45923bc08f8Sclaudio 	if (!x509_get_crl(*x509, fn, &crldp))
46023bc08f8Sclaudio 		goto out;
46123bc08f8Sclaudio 	if (crldp == NULL) {
46223bc08f8Sclaudio 		warnx("%s: RFC 6487 section 4.8.6: CRL: "
46323bc08f8Sclaudio 		    "missing CRL distribution point extension", fn);
46423bc08f8Sclaudio 		goto out;
46523bc08f8Sclaudio 	}
4666747684dSjob 	crlfile = strrchr(crldp, '/');
4676747684dSjob 	if (crlfile == NULL) {
4686747684dSjob 		warnx("%s: RFC 6487 section 4.8.6: "
4696747684dSjob 		    "invalid CRL distribution point", fn);
4706747684dSjob 		goto out;
4716747684dSjob 	}
4726747684dSjob 	crlfile++;
4736747684dSjob 	if (!valid_mft_filename(crlfile, strlen(crlfile)) ||
4746747684dSjob 	    rtype_from_file_extension(crlfile) != RTYPE_CRL) {
47523bc08f8Sclaudio 		warnx("%s: RFC 6487 section 4.8.6: CRL: "
47623bc08f8Sclaudio 		    "bad CRL distribution point extension", fn);
47723bc08f8Sclaudio 		goto out;
47823bc08f8Sclaudio 	}
479be6e5ad5Stb 	if ((mft->crl = strdup(crlfile)) == NULL)
48023bc08f8Sclaudio 		err(1, NULL);
48123bc08f8Sclaudio 
482be6e5ad5Stb 	if (mft_parse_econtent(fn, mft, cms, cmsz) == 0)
4839a7e9e7fSjob 		goto out;
4849a7e9e7fSjob 
485891d6bceSjob 	if ((cert = cert_parse_ee_cert(fn, talid, *x509)) == NULL)
486b454bae8Sjob 		goto out;
487b454bae8Sjob 
488be6e5ad5Stb 	if (mft->signtime > mft->nextupdate) {
48936b57f80Sjob 		warnx("%s: dating issue: CMS signing-time after MFT nextUpdate",
49036b57f80Sjob 		    fn);
49136b57f80Sjob 		goto out;
49236b57f80Sjob 	}
49336b57f80Sjob 
4949a7e9e7fSjob 	rc = 1;
4959a7e9e7fSjob out:
4969a7e9e7fSjob 	if (rc == 0) {
497be6e5ad5Stb 		mft_free(mft);
498be6e5ad5Stb 		mft = NULL;
4999a7e9e7fSjob 		X509_free(*x509);
5009a7e9e7fSjob 		*x509 = NULL;
5019a7e9e7fSjob 	}
50205ed64a0Stb 	free(crldp);
503b454bae8Sjob 	cert_free(cert);
5049a7e9e7fSjob 	free(cms);
505be6e5ad5Stb 	return mft;
5069a7e9e7fSjob }
5079a7e9e7fSjob 
5089a7e9e7fSjob /*
5099a7e9e7fSjob  * Free an MFT pointer.
5109a7e9e7fSjob  * Safe to call with NULL.
5119a7e9e7fSjob  */
5129a7e9e7fSjob void
5139a7e9e7fSjob mft_free(struct mft *p)
5149a7e9e7fSjob {
5159a7e9e7fSjob 	size_t	 i;
5169a7e9e7fSjob 
5179a7e9e7fSjob 	if (p == NULL)
5189a7e9e7fSjob 		return;
5199a7e9e7fSjob 
5209a7e9e7fSjob 	for (i = 0; i < p->filesz; i++)
5219a7e9e7fSjob 		free(p->files[i].file);
5229a7e9e7fSjob 
523a9a51c4fStb 	free(p->path);
524a9a51c4fStb 	free(p->files);
525a9a51c4fStb 	free(p->seqnum);
526ebd55816Sjob 	free(p->aia);
5279a7e9e7fSjob 	free(p->aki);
5282cf0e122Sjob 	free(p->sia);
5299a7e9e7fSjob 	free(p->ski);
530a9a51c4fStb 	free(p->crl);
5319a7e9e7fSjob 	free(p);
5329a7e9e7fSjob }
5339a7e9e7fSjob 
5349a7e9e7fSjob /*
5359a7e9e7fSjob  * Serialise MFT parsed content into the given buffer.
5369a7e9e7fSjob  * See mft_read() for the other side of the pipe.
5379a7e9e7fSjob  */
5389a7e9e7fSjob void
53908db1177Sclaudio mft_buffer(struct ibuf *b, const struct mft *p)
5409a7e9e7fSjob {
5419a7e9e7fSjob 	size_t		 i;
5429a7e9e7fSjob 
543100ded9eSclaudio 	io_simple_buffer(b, &p->repoid, sizeof(p->repoid));
5441fc2657fSclaudio 	io_simple_buffer(b, &p->talid, sizeof(p->talid));
5450bc420b9Sclaudio 	io_simple_buffer(b, &p->certid, sizeof(p->certid));
546318f0572Sjob 	io_simple_buffer(b, &p->seqnum_gap, sizeof(p->seqnum_gap));
547100ded9eSclaudio 	io_str_buffer(b, p->path);
5489a7e9e7fSjob 
549ebd55816Sjob 	io_str_buffer(b, p->aia);
55008db1177Sclaudio 	io_str_buffer(b, p->aki);
55108db1177Sclaudio 	io_str_buffer(b, p->ski);
552100ded9eSclaudio 
553100ded9eSclaudio 	io_simple_buffer(b, &p->filesz, sizeof(size_t));
554100ded9eSclaudio 	for (i = 0; i < p->filesz; i++) {
555100ded9eSclaudio 		io_str_buffer(b, p->files[i].file);
55622cec6c4Stb 		io_simple_buffer(b, &p->files[i].type,
55722cec6c4Stb 		    sizeof(p->files[i].type));
558df512fbcSclaudio 		io_simple_buffer(b, &p->files[i].location,
559df512fbcSclaudio 		    sizeof(p->files[i].location));
560100ded9eSclaudio 		io_simple_buffer(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
561100ded9eSclaudio 	}
5629a7e9e7fSjob }
5639a7e9e7fSjob 
5649a7e9e7fSjob /*
5659a7e9e7fSjob  * Read an MFT structure from the file descriptor.
5669a7e9e7fSjob  * Result must be passed to mft_free().
5679a7e9e7fSjob  */
5689a7e9e7fSjob struct mft *
5697eb79a4aSclaudio mft_read(struct ibuf *b)
5709a7e9e7fSjob {
5719a7e9e7fSjob 	struct mft	*p = NULL;
5729a7e9e7fSjob 	size_t		 i;
5739a7e9e7fSjob 
5749a7e9e7fSjob 	if ((p = calloc(1, sizeof(struct mft))) == NULL)
5757addb040Sbenno 		err(1, NULL);
5769a7e9e7fSjob 
577100ded9eSclaudio 	io_read_buf(b, &p->repoid, sizeof(p->repoid));
5781fc2657fSclaudio 	io_read_buf(b, &p->talid, sizeof(p->talid));
5790bc420b9Sclaudio 	io_read_buf(b, &p->certid, sizeof(p->certid));
580318f0572Sjob 	io_read_buf(b, &p->seqnum_gap, sizeof(p->seqnum_gap));
581100ded9eSclaudio 	io_read_str(b, &p->path);
5829a7e9e7fSjob 
583100ded9eSclaudio 	io_read_str(b, &p->aia);
584100ded9eSclaudio 	io_read_str(b, &p->aki);
585100ded9eSclaudio 	io_read_str(b, &p->ski);
586100ded9eSclaudio 	assert(p->aia && p->aki && p->ski);
587100ded9eSclaudio 
588100ded9eSclaudio 	io_read_buf(b, &p->filesz, sizeof(size_t));
5899a7e9e7fSjob 	if ((p->files = calloc(p->filesz, sizeof(struct mftfile))) == NULL)
5907addb040Sbenno 		err(1, NULL);
5919a7e9e7fSjob 
5929a7e9e7fSjob 	for (i = 0; i < p->filesz; i++) {
5937eb79a4aSclaudio 		io_read_str(b, &p->files[i].file);
59422cec6c4Stb 		io_read_buf(b, &p->files[i].type, sizeof(p->files[i].type));
595df512fbcSclaudio 		io_read_buf(b, &p->files[i].location,
596df512fbcSclaudio 		    sizeof(p->files[i].location));
5977eb79a4aSclaudio 		io_read_buf(b, p->files[i].hash, SHA256_DIGEST_LENGTH);
5989a7e9e7fSjob 	}
5999a7e9e7fSjob 
6009a7e9e7fSjob 	return p;
6019a7e9e7fSjob }
602df512fbcSclaudio 
603df512fbcSclaudio /*
6041039ba60Stb  * Compare the thisupdate time of two mft files.
6051039ba60Stb  */
6061039ba60Stb int
6071039ba60Stb mft_compare_issued(const struct mft *a, const struct mft *b)
6081039ba60Stb {
6091039ba60Stb 	if (a->thisupdate > b->thisupdate)
6101039ba60Stb 		return 1;
6111039ba60Stb 	if (a->thisupdate < b->thisupdate)
6121039ba60Stb 		return -1;
6131039ba60Stb 	return 0;
6141039ba60Stb }
6151039ba60Stb 
6161039ba60Stb /*
617e73085f8Stb  * Compare the manifestNumber of two mft files.
618df512fbcSclaudio  */
619df512fbcSclaudio int
620e73085f8Stb mft_compare_seqnum(const struct mft *a, const struct mft *b)
621df512fbcSclaudio {
622df512fbcSclaudio 	int r;
623df512fbcSclaudio 
624df512fbcSclaudio 	r = strlen(a->seqnum) - strlen(b->seqnum);
625df512fbcSclaudio 	if (r > 0)	/* seqnum in a is longer -> higher */
626df512fbcSclaudio 		return 1;
627df512fbcSclaudio 	if (r < 0)	/* seqnum in a is shorter -> smaller */
6280c60a445Sjob 		return -1;
629df512fbcSclaudio 
630df512fbcSclaudio 	r = strcmp(a->seqnum, b->seqnum);
63173a8ca21Sclaudio 	if (r > 0)	/* a is greater, prefer a */
632df512fbcSclaudio 		return 1;
6330c60a445Sjob 	if (r < 0)	/* b is greater, prefer b */
6340c60a445Sjob 		return -1;
6350c60a445Sjob 
636df512fbcSclaudio 	return 0;
637df512fbcSclaudio }
638318f0572Sjob 
639318f0572Sjob /*
640318f0572Sjob  * Test if there is a gap in the sequence numbers of two MFTs.
641318f0572Sjob  * Return 1 if a gap is detected.
642318f0572Sjob  */
643318f0572Sjob int
644318f0572Sjob mft_seqnum_gap_present(const struct mft *a, const struct mft *b)
645318f0572Sjob {
646318f0572Sjob 	BIGNUM *diff, *seqnum_a, *seqnum_b;
647318f0572Sjob 	int ret = 0;
648318f0572Sjob 
649318f0572Sjob 	BN_CTX_start(bn_ctx);
650318f0572Sjob 	if ((diff = BN_CTX_get(bn_ctx)) == NULL ||
651318f0572Sjob 	    (seqnum_a = BN_CTX_get(bn_ctx)) == NULL ||
652318f0572Sjob 	    (seqnum_b = BN_CTX_get(bn_ctx)) == NULL)
653318f0572Sjob 		errx(1, "BN_CTX_get");
654318f0572Sjob 
655318f0572Sjob 	if (!BN_hex2bn(&seqnum_a, a->seqnum))
656318f0572Sjob 		errx(1, "BN_hex2bn");
657318f0572Sjob 
658318f0572Sjob 	if (!BN_hex2bn(&seqnum_b, b->seqnum))
659318f0572Sjob 		errx(1, "BN_hex2bn");
660318f0572Sjob 
661318f0572Sjob 	if (!BN_sub(diff, seqnum_a, seqnum_b))
662318f0572Sjob 		errx(1, "BN_sub");
663318f0572Sjob 
664318f0572Sjob 	ret = !BN_is_one(diff);
665318f0572Sjob 
666318f0572Sjob 	BN_CTX_end(bn_ctx);
667318f0572Sjob 
668318f0572Sjob 	return ret;
669318f0572Sjob }
670