/src/botan/src/lib/x509/x509_crl.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * X.509 CRL |
3 | | * (C) 1999-2007 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/ber_dec.h> |
11 | | #include <botan/x509_ext.h> |
12 | | #include <botan/x509cert.h> |
13 | | |
14 | | #include <sstream> |
15 | | |
16 | | namespace Botan { |
17 | | |
18 | | struct CRL_Data { |
19 | | X509_DN m_issuer; |
20 | | size_t m_version; |
21 | | X509_Time m_this_update; |
22 | | X509_Time m_next_update; |
23 | | std::vector<CRL_Entry> m_entries; |
24 | | Extensions m_extensions; |
25 | | |
26 | | // cached values from extensions |
27 | | size_t m_crl_number = 0; |
28 | | std::vector<uint8_t> m_auth_key_id; |
29 | | std::vector<std::string> m_idp_urls; |
30 | | }; |
31 | | |
32 | 4.67k | std::string X509_CRL::PEM_label() const { |
33 | 4.67k | return "X509 CRL"; |
34 | 4.67k | } |
35 | | |
36 | 146 | std::vector<std::string> X509_CRL::alternate_PEM_labels() const { |
37 | 146 | return {"CRL"}; |
38 | 146 | } |
39 | | |
40 | 5.12k | X509_CRL::X509_CRL(DataSource& src) { |
41 | 5.12k | load_data(src); |
42 | 5.12k | } |
43 | | |
44 | 0 | X509_CRL::X509_CRL(const std::vector<uint8_t>& vec) { |
45 | 0 | DataSource_Memory src(vec.data(), vec.size()); |
46 | 0 | load_data(src); |
47 | 0 | } |
48 | | |
49 | | #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) |
50 | 0 | X509_CRL::X509_CRL(std::string_view fsname) { |
51 | 0 | DataSource_Stream src(fsname, true); |
52 | 0 | load_data(src); |
53 | 0 | } |
54 | | #endif |
55 | | |
56 | | X509_CRL::X509_CRL(const X509_DN& issuer, |
57 | | const X509_Time& this_update, |
58 | | const X509_Time& next_update, |
59 | | const std::vector<CRL_Entry>& revoked) : |
60 | 0 | X509_Object() { |
61 | 0 | m_data = std::make_shared<CRL_Data>(); |
62 | 0 | m_data->m_issuer = issuer; |
63 | 0 | m_data->m_this_update = this_update; |
64 | 0 | m_data->m_next_update = next_update; |
65 | 0 | m_data->m_entries = revoked; |
66 | 0 | } |
67 | | |
68 | | /** |
69 | | * Check if this particular certificate is listed in the CRL |
70 | | */ |
71 | 0 | bool X509_CRL::is_revoked(const X509_Certificate& cert) const { |
72 | | /* |
73 | | If the cert wasn't issued by the CRL issuer, it's possible the cert |
74 | | is revoked, but not by this CRL. Maybe throw an exception instead? |
75 | | */ |
76 | 0 | if(cert.issuer_dn() != issuer_dn()) { |
77 | 0 | return false; |
78 | 0 | } |
79 | | |
80 | 0 | std::vector<uint8_t> crl_akid = authority_key_id(); |
81 | 0 | const std::vector<uint8_t>& cert_akid = cert.authority_key_id(); |
82 | |
|
83 | 0 | if(!crl_akid.empty() && !cert_akid.empty()) { |
84 | 0 | if(crl_akid != cert_akid) { |
85 | 0 | return false; |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | 0 | const std::vector<uint8_t>& cert_serial = cert.serial_number(); |
90 | |
|
91 | 0 | bool is_revoked = false; |
92 | | |
93 | | // FIXME would be nice to avoid a linear scan here - maybe sort the entries? |
94 | 0 | for(const CRL_Entry& entry : get_revoked()) { |
95 | 0 | if(cert_serial == entry.serial_number()) { |
96 | 0 | if(entry.reason_code() == CRL_Code::RemoveFromCrl) { |
97 | 0 | is_revoked = false; |
98 | 0 | } else { |
99 | 0 | is_revoked = true; |
100 | 0 | } |
101 | 0 | } |
102 | 0 | } |
103 | |
|
104 | 0 | return is_revoked; |
105 | 0 | } |
106 | | |
107 | | /* |
108 | | * Decode the TBSCertList data |
109 | | */ |
110 | | namespace { |
111 | | |
112 | 3.75k | std::unique_ptr<CRL_Data> decode_crl_body(const std::vector<uint8_t>& body, const AlgorithmIdentifier& sig_algo) { |
113 | 3.75k | auto data = std::make_unique<CRL_Data>(); |
114 | | |
115 | 3.75k | BER_Decoder tbs_crl(body); |
116 | | |
117 | 3.75k | tbs_crl.decode_optional(data->m_version, ASN1_Type::Integer, ASN1_Class::Universal); |
118 | 3.75k | data->m_version += 1; // wire-format is 0-based |
119 | | |
120 | 3.75k | if(data->m_version != 1 && data->m_version != 2) { |
121 | 50 | throw Decoding_Error("Unknown X.509 CRL version " + std::to_string(data->m_version)); |
122 | 50 | } |
123 | | |
124 | 3.70k | AlgorithmIdentifier sig_algo_inner; |
125 | 3.70k | tbs_crl.decode(sig_algo_inner); |
126 | | |
127 | 3.70k | if(sig_algo != sig_algo_inner) { |
128 | 245 | throw Decoding_Error("Algorithm identifier mismatch in CRL"); |
129 | 245 | } |
130 | | |
131 | 3.46k | tbs_crl.decode(data->m_issuer).decode(data->m_this_update); |
132 | | |
133 | | // According to RFC 5280 Section 5.1, nextUpdate is OPTIONAL and may be |
134 | | // encoded as either a UTCTime or a GeneralizedTime. Section 5.1.2.5 |
135 | | // further states that "[c]onforming CRL issuers MUST include the nextUpdate |
136 | | // field in all CRLs". Obviously, not everyone complies... |
137 | | // |
138 | | // See https://github.com/randombit/botan/issues/4722 for more details. |
139 | 3.46k | tbs_crl.decode_optional(data->m_next_update, ASN1_Type::UtcTime, ASN1_Class::Universal); |
140 | 3.46k | if(!data->m_next_update.time_is_set()) { |
141 | 1.14k | tbs_crl.decode_optional(data->m_next_update, ASN1_Type::GeneralizedTime, ASN1_Class::Universal); |
142 | 1.14k | } |
143 | | |
144 | 3.46k | BER_Object next = tbs_crl.get_next_object(); |
145 | | |
146 | 3.46k | if(next.is_a(ASN1_Type::Sequence, ASN1_Class::Constructed)) { |
147 | 329 | BER_Decoder cert_list(std::move(next)); |
148 | | |
149 | 5.84k | while(cert_list.more_items()) { |
150 | 5.51k | CRL_Entry entry; |
151 | 5.51k | cert_list.decode(entry); |
152 | 5.51k | data->m_entries.push_back(entry); |
153 | 5.51k | } |
154 | 329 | next = tbs_crl.get_next_object(); |
155 | 329 | } |
156 | | |
157 | 3.46k | if(next.is_a(0, ASN1_Class::Constructed | ASN1_Class::ContextSpecific)) { |
158 | 2.15k | BER_Decoder crl_options(std::move(next)); |
159 | 2.15k | crl_options.decode(data->m_extensions).verify_end(); |
160 | 2.15k | next = tbs_crl.get_next_object(); |
161 | 2.15k | } |
162 | | |
163 | 3.46k | if(next.is_set()) { |
164 | 446 | throw Decoding_Error("Unknown tag following extensions in CRL"); |
165 | 446 | } |
166 | | |
167 | 3.01k | tbs_crl.verify_end(); |
168 | | |
169 | | // Now cache some fields from the extensions |
170 | 3.01k | if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::CRL_Number>()) { |
171 | 6 | data->m_crl_number = ext->get_crl_number(); |
172 | 6 | } |
173 | 3.01k | if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::Authority_Key_ID>()) { |
174 | 2 | data->m_auth_key_id = ext->get_key_id(); |
175 | 2 | } |
176 | 3.01k | if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::CRL_Issuing_Distribution_Point>()) { |
177 | 3 | data->m_idp_urls = ext->get_point().get_attribute("URL"); |
178 | 3 | } |
179 | | |
180 | 3.01k | return data; |
181 | 3.46k | } |
182 | | |
183 | | } // namespace |
184 | | |
185 | 3.75k | void X509_CRL::force_decode() { |
186 | 3.75k | m_data.reset(decode_crl_body(signed_body(), signature_algorithm()).release()); |
187 | 3.75k | } |
188 | | |
189 | 0 | const CRL_Data& X509_CRL::data() const { |
190 | 0 | if(!m_data) { |
191 | 0 | throw Invalid_State("X509_CRL uninitialized"); |
192 | 0 | } |
193 | 0 | return *m_data; |
194 | 0 | } |
195 | | |
196 | 0 | const Extensions& X509_CRL::extensions() const { |
197 | 0 | return data().m_extensions; |
198 | 0 | } |
199 | | |
200 | | /* |
201 | | * Return the list of revoked certificates |
202 | | */ |
203 | 0 | const std::vector<CRL_Entry>& X509_CRL::get_revoked() const { |
204 | 0 | return data().m_entries; |
205 | 0 | } |
206 | | |
207 | 0 | uint32_t X509_CRL::x509_version() const { |
208 | 0 | return static_cast<uint32_t>(data().m_version); |
209 | 0 | } |
210 | | |
211 | | /* |
212 | | * Return the distinguished name of the issuer |
213 | | */ |
214 | 0 | const X509_DN& X509_CRL::issuer_dn() const { |
215 | 0 | return data().m_issuer; |
216 | 0 | } |
217 | | |
218 | | /* |
219 | | * Return the key identifier of the issuer |
220 | | */ |
221 | 0 | const std::vector<uint8_t>& X509_CRL::authority_key_id() const { |
222 | 0 | return data().m_auth_key_id; |
223 | 0 | } |
224 | | |
225 | | /* |
226 | | * Return the CRL number of this CRL |
227 | | */ |
228 | 0 | uint32_t X509_CRL::crl_number() const { |
229 | 0 | return static_cast<uint32_t>(data().m_crl_number); |
230 | 0 | } |
231 | | |
232 | | /* |
233 | | * Return the issue data of the CRL |
234 | | */ |
235 | 0 | const X509_Time& X509_CRL::this_update() const { |
236 | 0 | return data().m_this_update; |
237 | 0 | } |
238 | | |
239 | | /* |
240 | | * Return the date when a new CRL will be issued |
241 | | */ |
242 | 0 | const X509_Time& X509_CRL::next_update() const { |
243 | 0 | return data().m_next_update; |
244 | 0 | } |
245 | | |
246 | | /* |
247 | | * Return the CRL's distribution point |
248 | | */ |
249 | 0 | std::string X509_CRL::crl_issuing_distribution_point() const { |
250 | 0 | if(!data().m_idp_urls.empty()) { |
251 | 0 | return data().m_idp_urls[0]; |
252 | 0 | } |
253 | 0 | return ""; |
254 | 0 | } |
255 | | |
256 | | /* |
257 | | * Return the CRL's issuing distribution point |
258 | | */ |
259 | 0 | std::vector<std::string> X509_CRL::issuing_distribution_points() const { |
260 | 0 | return data().m_idp_urls; |
261 | 0 | } |
262 | | |
263 | | } // namespace Botan |