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