Botan 3.13.0
Crypto and TLS for C&
x509_crl.cpp
Go to the documentation of this file.
1/*
2* X.509 CRL
3* (C) 1999-2007,2026 Jack Lloyd
4*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/x509_crl.h>
9
10#include <botan/asn1_obj.h>
11#include <botan/asn1_time.h>
12#include <botan/assert.h>
13#include <botan/ber_dec.h>
14#include <botan/bigint.h>
15#include <botan/data_src.h>
16#include <botan/x509_ext.h>
17#include <botan/x509cert.h>
18#include <botan/internal/x509_utils.h>
19#include <algorithm>
20#include <set>
21
22namespace Botan {
23
24class CRL_Data final {
25 public:
26 CRL_Data(const X509_DN& issuer,
27 const X509_Time& this_update,
28 const X509_Time& next_update,
29 const std::vector<CRL_Entry>& revoked) :
30 m_issuer(issuer), m_this_update(this_update), m_next_update(next_update), m_entries(revoked) {
31 this->update_index();
32 }
33
34 CRL_Data() = default;
35
36 void update_index() {
37 m_revoked_serials.clear();
38 for(const auto& entry : m_entries) {
39 m_revoked_serials.insert(entry.serial());
40 }
41 }
42
43 // NOLINTBEGIN(*non-private-member-variables-in-classes)
44 X509_DN m_issuer;
45 size_t m_version{};
46 X509_Time m_this_update;
47 X509_Time m_next_update;
48 std::vector<CRL_Entry> m_entries;
49 Extensions m_extensions;
50
51 // cached values from entries
52 std::set<X509_Serial_Number> m_revoked_serials;
53
54 // cached values from extensions
55 std::optional<BigInt> m_crl_number;
56 std::vector<uint8_t> m_auth_key_id;
57 std::vector<URI> m_idp_urls;
58 bool m_has_unknown_critical_extension = false;
59 // NOLINTEND(*non-private-member-variables-in-classes)
60};
61
62std::string X509_CRL::PEM_label() const {
63 return "X509 CRL";
64}
65
66std::vector<std::string> X509_CRL::alternate_PEM_labels() const {
67 return {"CRL"};
68}
69
73
74X509_CRL::X509_CRL(const std::vector<uint8_t>& vec) {
75 DataSource_Memory src(vec.data(), vec.size());
76 load_data(src);
77}
78
79#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
80X509_CRL::X509_CRL(std::string_view fsname) {
81 DataSource_Stream src(fsname, true);
82 load_data(src);
83}
84#endif
85
89 const std::vector<CRL_Entry>& revoked) {
90 m_data = std::make_shared<CRL_Data>(issuer, this_update, next_update, revoked);
91}
92
93/**
94* Check if this particular certificate is listed in the CRL
95*/
96bool X509_CRL::is_revoked(const X509_Certificate& cert) const {
97 const bool serial_appears = data().m_revoked_serials.contains(cert.serial());
98
99 // If the serial number does not appear in the revocation list then
100 // the later checks are not necessary anyway
101 if(!serial_appears) {
102 return false;
103 }
104
105 /*
106 If the cert wasn't issued by the CRL issuer, it's possible the cert
107 is revoked, but not by this CRL. Maybe throw an exception instead?
108 */
109 if(cert.issuer_dn() != issuer_dn()) {
110 return false;
111 }
112
113 const std::vector<uint8_t> crl_akid = authority_key_id();
114 const std::vector<uint8_t>& cert_akid = cert.authority_key_id();
115
116 if(!crl_akid.empty() && !cert_akid.empty()) {
117 if(crl_akid != cert_akid) {
118 return false;
119 }
120 }
121
122 return serial_appears;
123}
124
125namespace {
126
127/*
128* Decode the TBSCertList data
129*/
130std::unique_ptr<CRL_Data> decode_crl_body(const std::vector<uint8_t>& body, const AlgorithmIdentifier& sig_algo) {
131 auto data = std::make_unique<CRL_Data>();
132
133 BER_Decoder tbs_crl(body, BER_Decoder::Limits::DER());
134
135 tbs_crl.decode_optional(data->m_version, ASN1_Type::Integer, ASN1_Class::Universal);
136 data->m_version += 1; // wire-format is 0-based
137
138 if(data->m_version != 1 && data->m_version != 2) {
139 throw Decoding_Error("Unknown X.509 CRL version " + std::to_string(data->m_version));
140 }
141
142 // Extensions are only defined for v2 CRLs
143 const bool supports_extensions = data->m_version > 1;
144
145 AlgorithmIdentifier sig_algo_inner;
146 tbs_crl.decode(sig_algo_inner);
147
148 if(sig_algo != sig_algo_inner) {
149 throw Decoding_Error("Algorithm identifier mismatch in CRL");
150 }
151
152 tbs_crl.decode(data->m_issuer).decode(data->m_this_update);
153
154 // According to RFC 5280 Section 5.1, nextUpdate is OPTIONAL and may be
155 // encoded as either a UTCTime or a GeneralizedTime. Section 5.1.2.5
156 // further states that "[c]onforming CRL issuers MUST include the nextUpdate
157 // field in all CRLs". Obviously, not everyone complies...
158 //
159 // See https://github.com/randombit/botan/issues/4722 for more details.
160 {
161 const auto& next_update = tbs_crl.peek_next_object();
162 if(next_update.is_a(ASN1_Type::UtcTime, ASN1_Class::Universal) ||
164 tbs_crl.decode(data->m_next_update);
165 }
166 }
167
168 BER_Object next = tbs_crl.get_next_object();
169
171 BER_Decoder cert_list(next, tbs_crl.limits());
172
173 while(cert_list.more_items()) {
174 CRL_Entry entry;
175 cert_list.decode(entry);
176
177 if(entry.extensions().has_unknown_critical_extension()) {
178 data->m_has_unknown_critical_extension = true;
179 }
180
181 if(!supports_extensions && entry.extensions().count() > 0) {
182 throw Decoding_Error("X509 CRL included extensions in a version that doesn't support them");
183 }
184
185 data->m_entries.push_back(std::move(entry));
186 }
187
188 /*
189 RFC 5280 Section 5.1.2.6
190 When there are no revoked certificates, the revoked certificates list MUST be absent.
191
192 So strictly speaking we should be checking that m_entries is not empty. But practically,
193 it seems nearly all implementations accept a present-but-empty SEQUENCE as equivalent
194 to an absent one, and several major ones (including GnuTLS) will emit it. Considering
195 this situation, and the benign nature of the deviation, accept the non-conforming encoding.
196 */
197
198 next = tbs_crl.get_next_object();
199 }
200
202 if(!supports_extensions) {
203 throw Decoding_Error("X509 CRL included extensions in a version that doesn't support them");
204 }
205 BER_Decoder crl_options(next, tbs_crl.limits());
206 data->m_extensions.decode_from(crl_options, Extension_Context::CRL);
207 crl_options.verify_end();
208 if(data->m_extensions.has_unknown_critical_extension()) {
209 data->m_has_unknown_critical_extension = true;
210 }
211
212 if(tbs_crl.get_next_object().is_set()) {
213 throw Decoding_Error("Unknown tag following extensions in CRL");
214 }
215 }
216
217 tbs_crl.verify_end("Unexpected trailing data after CRL");
218
219 // Now cache some fields from the extensions
220 if(const auto* ext = data->m_extensions.get_extension_object_as<Cert_Extension::CRL_Number>()) {
221 data->m_crl_number = ext->crl_number();
222 }
223 if(const auto* ext = data->m_extensions.get_extension_object_as<Cert_Extension::Authority_Key_ID>()) {
224 data->m_auth_key_id = ext->get_key_id();
225 }
226 if(const auto* ext = data->m_extensions.get_extension_object_as<Cert_Extension::CRL_Issuing_Distribution_Point>()) {
227 const auto& dpn = ext->distribution_point_name();
228 if(dpn.has_value() && dpn->full_name().has_value()) {
229 for(const auto& uri : dpn->full_name()->uri_names()) {
230 data->m_idp_urls.push_back(uri);
231 }
232 }
233 }
234
235 data->update_index();
236
237 return data;
238}
239
240} // namespace
241
242void X509_CRL::force_decode() {
243 m_data.reset(decode_crl_body(signed_body(), signature_algorithm()).release());
244}
245
246const CRL_Data& X509_CRL::data() const {
247 if(!m_data) {
248 throw Invalid_State("X509_CRL uninitialized");
249 }
250 return *m_data;
251}
252
254 return data().m_extensions;
255}
256
257/*
258* Return the list of revoked certificates
259*/
260const std::vector<CRL_Entry>& X509_CRL::get_revoked() const {
261 return data().m_entries;
262}
263
264uint32_t X509_CRL::x509_version() const {
265 return static_cast<uint32_t>(data().m_version);
266}
267
269 return data().m_has_unknown_critical_extension;
270}
271
272/*
273* Return the distinguished name of the issuer
274*/
276 return data().m_issuer;
277}
278
279/*
280* Return the key identifier of the issuer
281*/
282const std::vector<uint8_t>& X509_CRL::authority_key_id() const {
283 return data().m_auth_key_id;
284}
285
286/*
287* Return the CRL number of this CRL
288*/
289const std::optional<BigInt>& X509_CRL::crl_number_bigint() const {
290 return data().m_crl_number;
291}
292
293uint32_t X509_CRL::crl_number() const {
294 if(const auto num = this->crl_number_bigint()) {
295 // This should already be caught at decode time
296 BOTAN_ASSERT_NOMSG(num->signum() >= 0);
297
298 if(num->bits() > 32) {
299 throw Encoding_Error("CRL number is too large to fit in uint32_t");
300 }
301
302 return num->to_u32bit();
303 } else {
304 return 0;
305 }
306}
307
308/*
309* Return the issue data of the CRL
310*/
312 return data().m_this_update;
313}
314
315/*
316* Return the date when a new CRL will be issued
317*/
319 return data().m_next_update;
320}
321
322/*
323* Return the CRL's distribution point
324*/
326 if(!data().m_idp_urls.empty()) {
327 return data().m_idp_urls[0].original_input();
328 }
329 return "";
330}
331
332/*
333* Return the CRL's issuing distribution point
334*/
335std::vector<std::string> X509_CRL::issuing_distribution_points() const {
336 std::vector<std::string> out;
337 out.reserve(data().m_idp_urls.size());
338 for(const auto& uri : data().m_idp_urls) {
339 out.push_back(uri.original_input());
340 }
341 return out;
342}
343
344const std::vector<URI>& X509_CRL::issuing_distribution_point_uris() const {
345 return data().m_idp_urls;
346}
347
348namespace {
349
350/*
351* Compare two distribution point names for overlap, per RFC 5280 section 6.3.3
352* step (b)(2). In practice CRLDP/IDP general names are either uniformResourceIdentifier
353* or directoryName; the other GeneralName variants have no defined semantics for a
354* distribution point (RFC 5280 4.2.1.13 and 5.2.5) so they are ignored here.
355*/
356bool dp_names_overlap(const AlternativeName& a, const AlternativeName& b) {
357 auto has_common = [](const auto& s1, const auto& s2) {
358 return std::ranges::any_of(s1, [&](const auto& e) { return s2.contains(e); });
359 };
360
361 return has_common(a.uri_names(), b.uri_names()) || has_common(a.directory_names(), b.directory_names());
362}
363
364bool dp_issuer_and_scope_ok(const Cert_Extension::CRL_Distribution_Points::Distribution_Point& dp,
365 const X509_DN& crl_issuer_dn,
367 const X509_Certificate& cert) {
368 /*
369 * RFC 5280 6.3.3 step (b)(1):
370 * If the DP includes cRLIssuer, then verify that the issuer field in
371 * the complete CRL matches cRLIssuer in the DP and that the complete
372 * CRL contains an issuing distribution point extension with the
373 * indirectCRL boolean asserted. Otherwise, verify that the CRL
374 * issuer matches the certificate issuer.
375 */
376
377 if(dp.crl_issuer().has_value()) {
378 // Verify that the DP cRLIssuer field matches the CRL issuer
379 if(!dp.crl_issuer()->directory_names().contains(crl_issuer_dn)) {
380 return false;
381 }
382 // Verify that the IDP with the indirectCRL boolean asserted
383 if(idp_ext == nullptr || !idp_ext->indirect_crl()) {
384 return false;
385 }
386 return true;
387 } else {
388 // Verify that the CRL issuer matches the certificate issuer
389 return crl_issuer_dn == cert.issuer_dn();
390 }
391}
392
393bool dp_idp_name_matches(const Cert_Extension::CRL_Distribution_Points::Distribution_Point& dp,
395 /*
396 * RFC 5280 6.3.3 step (b)(2)(i):
397 * If the distribution point name is present in the IDP CRL extension
398 * and the distribution field is present in the DP, then verify that
399 * one of the names in the IDP matches one of the names in the DP.
400 * If the distribution point name is present in the IDP CRL extension
401 * and the distribution field is omitted from the DP, then verify
402 * that one of the names in the IDP matches one of the names in the
403 * cRLIssuer field of the DP.
404 */
405 if(idp_ext != nullptr) {
406 const auto& idp_dpn = idp_ext->distribution_point_name();
407 if(!idp_dpn.has_value()) {
408 return true;
409 }
410 const auto& cert_dpn = dp.distribution_point_name();
411 if(cert_dpn.has_value()) {
412 // Match the cert's DistributionPoint name against the CRL's IDP DistributionPoint name.
413 if(cert_dpn->full_name().has_value() && idp_dpn->full_name().has_value()) {
414 return dp_names_overlap(*cert_dpn->full_name(), *idp_dpn->full_name());
415 } else {
416 return false;
417 }
418 }
419 // DP omits distributionPoint: match IDP name against names in dp.cRLIssuer.
420 if(dp.crl_issuer().has_value() && idp_dpn->full_name().has_value()) {
421 return dp_names_overlap(*idp_dpn->full_name(), *dp.crl_issuer());
422 }
423 return false;
424 } else {
425 return true;
426 }
427}
428
429/*
430* True if the cert has no CDP, in which case RFC 5280 6.3.3 trailing
431* paragraph applies: assume an implicit DP whose name is the certificate
432* issuer field plus the certificate issuerAltName entries, and whose
433* cRLIssuer and reasons are omitted.
434*/
435bool implicit_dp_matches(const X509_CRL& crl,
436 const X509_Certificate& cert,
438 if(crl.issuer_dn() != cert.issuer_dn()) {
439 return false;
440 }
441 if(idp_ext == nullptr) {
442 return true;
443 }
444 const auto& idp_dpn = idp_ext->distribution_point_name();
445 if(!idp_dpn.has_value()) {
446 return true;
447 }
448 if(!idp_dpn->full_name().has_value()) {
449 return false;
450 }
451 AlternativeName implicit_full_name = cert.issuer_alt_name();
452 implicit_full_name.add_dn(cert.issuer_dn());
453 return dp_names_overlap(*idp_dpn->full_name(), implicit_full_name);
454}
455
456} // namespace
457
461
462 /*
463 * RFC 5280 6.3.3 trailing paragraph: "If the revocation status has not
464 * been determined, repeat the process above with any available CRLs not
465 * specified in a distribution point but issued by the certificate issuer.
466 * For the processing of such a CRL, assume a DP with both the reasons and
467 * the cRLIssuer fields omitted and a distribution point name of the
468 * certificate issuer."
469 *
470 * When the cert has no CDP this implicit DP is the only DP; with no reasons
471 * field it covers all reasons by construction.
472 */
473 if(cdp_ext == nullptr || cdp_ext->distribution_points().empty()) {
474 const bool match = implicit_dp_matches(crl, cert, idp_ext);
475 return {match, match};
476 }
477
478 /*
479 * Walk the cert's CDP once, recording both the bare name-match and whether
480 * any matching DP omits the reasons field. (b)(1) cRLIssuer + indirectCRL
481 * and (b)(2)(i) IDP-vs-DP name overlap live in the helpers; reason coverage
482 * is decided per (d)(3): a matching DP whose reasons field is set narrows
483 * the CRL's coverage to that subset, so full coverage requires a
484 * matching DP with no reasons field.
485 */
486 const auto name_matches = [&](const auto& dp) {
487 return dp_issuer_and_scope_ok(dp, crl.issuer_dn(), idp_ext, cert) && dp_idp_name_matches(dp, idp_ext);
488 };
489
490 bool any = false;
491 bool any_with_absent_reasons = false;
492 for(const auto& dp : cdp_ext->distribution_points()) {
493 if(name_matches(dp)) {
494 any = true;
495 if(!dp.reasons().has_value()) {
496 any_with_absent_reasons = true;
497 }
498 }
499 }
500 if(any) {
501 return {true, any_with_absent_reasons};
502 }
503
504 /*
505 * Implicit-DP fallback: a same-issuer complete CRL that matches no explicit
506 * DP is still usable, unless its own IDP scopes it to a distribution point
507 * (see crl_eligible_for_implicit_dp_fallback). The implicit DP omits
508 * reasons, so a name match here also gives full reason coverage.
509 */
510 const bool implicit_dp_fallback = (idp_ext == nullptr || !idp_ext->distribution_point_name().has_value());
511 const bool implicit = implicit_dp_fallback && implicit_dp_matches(crl, cert, idp_ext);
512 return {implicit, implicit};
513}
514
516 return distribution_point_match(*this, cert).any;
517}
518
519} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
const std::set< X509_DN > & directory_names() const
Return the set of directory names included in this alternative name.
Definition pkix_types.h:443
void add_dn(const X509_DN &dn)
Add a directory name to this AlternativeName.
Definition alt_name.cpp:101
const std::set< URI > & uri_names() const
Return the set of URIs included in this alternative name.
Definition pkix_types.h:391
static Limits DER()
Definition ber_dec.h:42
Definition x509_crl.h:32
const T * get_extension_object_as(const OID &oid=T::static_oid()) const
Definition pkix_types.h:884
const std::vector< CRL_Entry > & get_revoked() const
Definition x509_crl.cpp:260
const std::vector< uint8_t > & authority_key_id() const
Definition x509_crl.cpp:282
const X509_Time & this_update() const
Definition x509_crl.cpp:311
std::vector< std::string > issuing_distribution_points() const
Definition x509_crl.cpp:335
X509_CRL()=default
const Extensions & extensions() const
Definition x509_crl.cpp:253
uint32_t crl_number() const
Definition x509_crl.cpp:293
const std::vector< URI > & issuing_distribution_point_uris() const
Definition x509_crl.cpp:344
const X509_Time & next_update() const
Definition x509_crl.cpp:318
const X509_DN & issuer_dn() const
Definition x509_crl.cpp:275
bool has_unknown_critical_extension() const
Definition x509_crl.cpp:268
bool has_matching_distribution_point(const X509_Certificate &cert) const
Definition x509_crl.cpp:515
bool is_revoked(const X509_Certificate &cert) const
Definition x509_crl.cpp:96
std::string crl_issuing_distribution_point() const
Definition x509_crl.cpp:325
uint32_t x509_version() const
Definition x509_crl.cpp:264
const std::optional< BigInt > & crl_number_bigint() const
Definition x509_crl.cpp:289
const X509_Serial_Number & serial() const
Definition x509cert.cpp:444
const std::vector< uint8_t > & authority_key_id() const
Definition x509cert.cpp:432
const Extensions & v3_extensions() const
Definition x509cert.cpp:519
const X509_DN & issuer_dn() const
Definition x509cert.cpp:456
const std::vector< uint8_t > & signed_body() const
Definition x509_obj.cpp:66
const AlgorithmIdentifier & signature_algorithm() const
Definition x509_obj.cpp:73
virtual std::vector< std::string > alternate_PEM_labels() const
Definition x509_obj.h:102
void load_data(DataSource &src)
Definition x509_obj.cpp:24
virtual std::string PEM_label() const =0
ASN1_Time X509_Time
Definition asn1_obj.h:27
DistributionPointMatch distribution_point_match(const X509_CRL &crl, const X509_Certificate &cert)
Definition x509_crl.cpp:458