1/*	$OpenBSD: crl.c,v 1.42 2024/06/17 18:52:50 tb Exp $ */
2/*
3 * Copyright (c) 2024 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 <err.h>
20#include <stdlib.h>
21#include <string.h>
22#include <unistd.h>
23
24#include <openssl/x509.h>
25
26#include "extern.h"
27
28/*
29 * Check that the CRL number extension is present and that it is non-critical.
30 * Otherwise ignore it per draft-spaghetti-sidrops-rpki-crl-numbers.
31 */
32static int
33crl_has_crl_number(const char *fn, const X509_CRL *x509_crl)
34{
35	const X509_EXTENSION	*ext;
36	int			 idx;
37
38	if ((idx = X509_CRL_get_ext_by_NID(x509_crl, NID_crl_number, -1)) < 0) {
39		warnx("%s: RFC 6487, section 5: missing CRL number", fn);
40		return 0;
41	}
42	if ((ext = X509_CRL_get_ext(x509_crl, idx)) == NULL) {
43		warnx("%s: RFC 6487, section 5: failed to get CRL number", fn);
44		return 0;
45	}
46	if (X509_EXTENSION_get_critical(ext) != 0) {
47		warnx("%s: RFC 6487, section 5: CRL number not non-critical",
48		    fn);
49		return 0;
50	}
51
52	return 1;
53}
54
55/*
56 * Parse X509v3 authority key identifier (AKI) from the CRL.
57 * Returns the AKI or NULL if it could not be parsed.
58 * The AKI is formatted as a hex string.
59 */
60static char *
61crl_get_aki(const char *fn, X509_CRL *x509_crl)
62{
63	AUTHORITY_KEYID		*akid = NULL;
64	ASN1_OCTET_STRING	*os;
65	const unsigned char	*d;
66	int			 dsz, crit;
67	char			*res = NULL;
68
69	if ((akid = X509_CRL_get_ext_d2i(x509_crl, NID_authority_key_identifier,
70	    &crit, NULL)) == NULL) {
71		if (crit != -1)
72			warnx("%s: RFC 6487 section 4.8.3: AKI: "
73			    "failed to parse CRL extension", fn);
74		else
75			warnx("%s: RFC 6487 section 4.8.3: AKI: "
76			    "CRL extension missing", fn);
77		goto out;
78	}
79	if (crit != 0) {
80		warnx("%s: RFC 6487 section 4.8.3: "
81		    "AKI: extension not non-critical", fn);
82		goto out;
83	}
84	if (akid->issuer != NULL || akid->serial != NULL) {
85		warnx("%s: RFC 6487 section 4.8.3: AKI: "
86		    "authorityCertIssuer or authorityCertSerialNumber present",
87		    fn);
88		goto out;
89	}
90
91	os = akid->keyid;
92	if (os == NULL) {
93		warnx("%s: RFC 6487 section 4.8.3: AKI: "
94		    "Key Identifier missing", fn);
95		goto out;
96	}
97
98	d = os->data;
99	dsz = os->length;
100
101	if (dsz != SHA_DIGEST_LENGTH) {
102		warnx("%s: RFC 6487 section 4.8.3: AKI: "
103		    "want %d bytes SHA1 hash, have %d bytes",
104		    fn, SHA_DIGEST_LENGTH, dsz);
105		goto out;
106	}
107
108	res = hex_encode(d, dsz);
109 out:
110	AUTHORITY_KEYID_free(akid);
111	return res;
112}
113
114/*
115 * Check that the list of revoked certificates contains only the specified
116 * two fields, Serial Number and Revocation Date, and that no extensions are
117 * present.
118 */
119static int
120crl_check_revoked(const char *fn, X509_CRL *x509_crl)
121{
122	STACK_OF(X509_REVOKED)	*list;
123	X509_REVOKED		*revoked;
124	int			 count, i;
125
126	/* If there are no revoked certificates, there's nothing to check. */
127	if ((list = X509_CRL_get_REVOKED(x509_crl)) == NULL)
128		return 1;
129
130	if ((count = sk_X509_REVOKED_num(list)) <= 0) {
131		/*
132		 * XXX - as of May 2024, ~15% of RPKI CRLs fail this check due
133		 * to a bug in rpki-rs/Krill. So silently accept this for now.
134		 * https://github.com/NLnetLabs/krill/issues/1197
135		 * https://github.com/NLnetLabs/rpki-rs/pull/295
136		 */
137		if (verbose > 1)
138			warnx("%s: RFC 5280, section 5.1.2.6: revoked "
139			    "certificate list without entries disallowed", fn);
140		return 1;
141	}
142
143	for (i = 0; i < count; i++) {
144		revoked = sk_X509_REVOKED_value(list, i);
145
146		/*
147		 * serialNumber and revocationDate are mandatory in the ASN.1
148		 * template, so no need to check their presence.
149		 *
150		 * XXX - due to an old bug in Krill, we can't enforce that
151		 * revocationDate is in the past until at least mid-2025:
152		 * https://github.com/NLnetLabs/krill/issues/788.
153		 */
154
155		if (X509_REVOKED_get0_extensions(revoked) != NULL) {
156			warnx("%s: RFC 6487, section 5: CRL entry extensions "
157			    "disallowed", fn);
158			return 0;
159		}
160	}
161
162	return 1;
163}
164
165struct crl *
166crl_parse(const char *fn, const unsigned char *der, size_t len)
167{
168	const unsigned char	*oder;
169	struct crl		*crl;
170	const X509_NAME		*name;
171	const ASN1_TIME		*at;
172	int			 count, nid, rc = 0;
173
174	/* just fail for empty buffers, the warning was printed elsewhere */
175	if (der == NULL)
176		return NULL;
177
178	if ((crl = calloc(1, sizeof(*crl))) == NULL)
179		err(1, NULL);
180
181	oder = der;
182	if ((crl->x509_crl = d2i_X509_CRL(NULL, &der, len)) == NULL) {
183		warnx("%s: d2i_X509_CRL", fn);
184		goto out;
185	}
186	if (der != oder + len) {
187		warnx("%s: %td bytes trailing garbage", fn, oder + len - der);
188		goto out;
189	}
190
191	if (X509_CRL_get_version(crl->x509_crl) != 1) {
192		warnx("%s: RFC 6487 section 5: version 2 expected", fn);
193		goto out;
194	}
195
196	if ((name = X509_CRL_get_issuer(crl->x509_crl)) == NULL) {
197		warnx("%s: X509_CRL_get_issuer", fn);
198		goto out;
199	}
200	if (!x509_valid_name(fn, "issuer", name))
201		goto out;
202
203	if ((nid = X509_CRL_get_signature_nid(crl->x509_crl)) == NID_undef) {
204		warnx("%s: unknown signature type", fn);
205		goto out;
206	}
207	if (experimental && nid == NID_ecdsa_with_SHA256) {
208		if (verbose)
209			warnx("%s: P-256 support is experimental", fn);
210	} else if (nid != NID_sha256WithRSAEncryption) {
211		warnx("%s: RFC 7935: wrong signature algorithm %s, want %s",
212		    fn, nid2str(nid), LN_sha256WithRSAEncryption);
213		goto out;
214	}
215
216	/*
217	 * RFC 6487, section 5: AKI and crlNumber MUST be present, no other
218	 * CRL extensions are allowed.
219	 */
220	if ((count = X509_CRL_get_ext_count(crl->x509_crl)) != 2) {
221		warnx("%s: RFC 6487 section 5: unexpected number of extensions "
222		    "%d != 2", fn, count);
223		goto out;
224	}
225	if (!crl_has_crl_number(fn, crl->x509_crl))
226		goto out;
227	if ((crl->aki = crl_get_aki(fn, crl->x509_crl)) == NULL)
228		goto out;
229
230	at = X509_CRL_get0_lastUpdate(crl->x509_crl);
231	if (at == NULL) {
232		warnx("%s: X509_CRL_get0_lastUpdate failed", fn);
233		goto out;
234	}
235	if (!x509_get_time(at, &crl->thisupdate)) {
236		warnx("%s: ASN1_TIME_to_tm failed", fn);
237		goto out;
238	}
239
240	at = X509_CRL_get0_nextUpdate(crl->x509_crl);
241	if (at == NULL) {
242		warnx("%s: X509_CRL_get0_nextUpdate failed", fn);
243		goto out;
244	}
245	if (!x509_get_time(at, &crl->nextupdate)) {
246		warnx("%s: ASN1_TIME_to_tm failed", fn);
247		goto out;
248	}
249
250	if (!crl_check_revoked(fn, crl->x509_crl))
251		goto out;
252
253	rc = 1;
254 out:
255	if (rc == 0) {
256		crl_free(crl);
257		crl = NULL;
258	}
259	return crl;
260}
261
262static inline int
263crlcmp(struct crl *a, struct crl *b)
264{
265	int	 cmp;
266
267	cmp = strcmp(a->aki, b->aki);
268	if (cmp > 0)
269		return 1;
270	if (cmp < 0)
271		return -1;
272
273	/*
274	 * In filemode the mftpath cannot be determined easily,
275	 * but it is always set in normal top-down validation.
276	 */
277	if (a->mftpath == NULL || b->mftpath == NULL)
278		return 0;
279
280	cmp = strcmp(a->mftpath, b->mftpath);
281	if (cmp > 0)
282		return 1;
283	if (cmp < 0)
284		return -1;
285
286	return 0;
287}
288
289RB_GENERATE_STATIC(crl_tree, crl, entry, crlcmp);
290
291/*
292 * Find a CRL based on the auth SKI value.
293 */
294struct crl *
295crl_get(struct crl_tree *crlt, const struct auth *a)
296{
297	struct crl	find;
298
299	/* XXX - this should be removed, but filemode relies on it. */
300	if (a == NULL)
301		return NULL;
302
303	find.aki = a->cert->ski;
304	find.mftpath = a->cert->mft;
305
306	return RB_FIND(crl_tree, crlt, &find);
307}
308
309int
310crl_insert(struct crl_tree *crlt, struct crl *crl)
311{
312	return RB_INSERT(crl_tree, crlt, crl) == NULL;
313}
314
315void
316crl_free(struct crl *crl)
317{
318	if (crl == NULL)
319		return;
320	free(crl->aki);
321	free(crl->mftpath);
322	X509_CRL_free(crl->x509_crl);
323	free(crl);
324}
325
326void
327crl_tree_free(struct crl_tree *crlt)
328{
329	struct crl	*crl, *tcrl;
330
331	RB_FOREACH_SAFE(crl, crl_tree, crlt, tcrl) {
332		RB_REMOVE(crl_tree, crlt, crl);
333		crl_free(crl);
334	}
335}
336