1use std::time::Duration;
2
3use anyhow::{anyhow, Context, Error};
4use base64::prelude::*;
5use http::{header::CONTENT_TYPE, StatusCode};
6use num_bigint::BigInt;
7use rasn::types::{OctetString, Oid};
8use rasn_ocsp::{
9 BasicOcspResponse, CertId, CertStatus, OcspRequest, OcspResponse, OcspResponseStatus, Request,
10 TbsRequest, Version,
11};
12use rasn_pkix::AlgorithmIdentifier;
13use sha1::{Digest, Sha1};
14use url::Url;
15use x509_parser::{oid_registry::OID_PKIX_ACCESS_DESCRIPTOR_OCSP, prelude::*};
16
17use super::Validity;
18
19pub struct Response {
21 pub raw: Vec<u8>,
24 pub ocsp_validity: Validity,
26 pub cert_status: CertStatus,
28}
29
30fn extract_ocsp_url(cert: &X509Certificate) -> Option<String> {
32 cert.extensions()
33 .iter()
34 .find_map(|x| {
35 if let ParsedExtension::AuthorityInfoAccess(v) = x.parsed_extension() {
36 Some(v)
37 } else {
38 None
39 }
40 })?
41 .accessdescs
42 .iter()
43 .filter(|x| x.access_method == OID_PKIX_ACCESS_DESCRIPTOR_OCSP)
44 .find_map(|x| {
45 if let GeneralName::URI(v) = x.access_location {
46 Some(v.to_string())
47 } else {
48 None
49 }
50 })
51}
52
53fn prepare_ocsp_request(cert: &[u8], issuer: &[u8]) -> Result<(OcspRequest, Url), Error> {
55 let cert = X509Certificate::from_der(cert)
57 .context("unable to parse cert")?
58 .1;
59 let issuer = X509Certificate::from_der(issuer)
60 .context("unable to parse issuer")?
61 .1;
62
63 let url =
65 Url::parse(&extract_ocsp_url(&cert).ok_or_else(|| anyhow!("unable to extract OCSP URL"))?)
66 .context("unable to parse OCSP URL")?;
67
68 let hash_algorithm = AlgorithmIdentifier {
75 algorithm: Oid::ISO_IDENTIFIED_ORGANISATION_OIW_SECSIG_ALGORITHM_SHA1.to_owned(),
76 parameters: None,
77 };
78
79 let issuer_name_hash = OctetString::from_slice(Sha1::digest(cert.issuer.as_raw()).as_slice());
81 let issuer_key_hash =
82 OctetString::from_slice(Sha1::digest(&issuer.public_key().subject_public_key).as_slice());
83
84 let serial_number: BigInt = cert.serial.clone().into();
86 let req_cert = CertId {
87 hash_algorithm,
88 serial_number: serial_number.into(),
89 issuer_name_hash,
90 issuer_key_hash,
91 };
92
93 let request = Request {
94 req_cert,
95 single_request_extensions: None,
96 };
97
98 let tbs_request = TbsRequest {
99 version: Version::ZERO,
100 requestor_name: None,
101 request_list: vec![request],
102 request_extensions: None,
103 };
104
105 Ok((
106 OcspRequest {
107 tbs_request,
108 optional_signature: None,
109 },
110 url,
111 ))
112}
113
114pub struct Client {
116 http_client: reqwest::Client,
117}
118
119impl Default for Client {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125impl Client {
126 pub fn new() -> Self {
128 Self {
129 http_client: reqwest::Client::builder()
130 .connect_timeout(Duration::from_millis(3000))
131 .timeout(Duration::from_millis(6000))
132 .build()
133 .unwrap(),
134 }
135 }
136
137 pub const fn new_with_client(http_client: reqwest::Client) -> Self {
139 Self { http_client }
140 }
141
142 async fn execute(&self, url: Url, ocsp_request: OcspRequest) -> Result<OcspResponse, Error> {
143 let ocsp_request = rasn::der::encode(&ocsp_request)
145 .map_err(|e| anyhow!("unable to serialize OCSP request: {e}"))?;
146
147 let request = if ocsp_request.len() <= 255 {
151 let ocsp_request = BASE64_STANDARD.encode(ocsp_request);
153 let url = url
154 .join(&ocsp_request)
155 .context("unable to append base64 request")?;
156
157 self.http_client.get(url)
158 } else {
159 self.http_client.post(url).body(ocsp_request)
160 };
161
162 let response = request
163 .header(CONTENT_TYPE, "application/ocsp-request")
164 .send()
165 .await
166 .context("HTTP request failed")?;
167
168 if response.status() != StatusCode::OK {
169 return Err(anyhow!("Incorrect HTTP code: {}", response.status()));
170 }
171
172 let body = response
173 .bytes()
174 .await
175 .context("unable to read OCSP response body")?;
176
177 let ocsp_response: OcspResponse = rasn::der::decode(&body)
179 .map_err(|e| anyhow!("unable to decode OcspResponse: {e:#}"))?;
180
181 Ok(ocsp_response)
182 }
183
184 pub async fn query_raw(&self, cert: &[u8], issuer: &[u8]) -> Result<OcspResponse, Error> {
187 let (ocsp_request, url) =
189 prepare_ocsp_request(cert, issuer).context("unable to prepare OCSP request")?;
190
191 self.execute(url, ocsp_request).await
192 }
193
194 pub async fn query(&self, cert: &[u8], issuer: &[u8]) -> Result<Response, Error> {
197 let ocsp_response = self
198 .query_raw(cert, issuer)
199 .await
200 .context("Unable to perform OCSP query")?;
201
202 if ocsp_response.status != OcspResponseStatus::Successful {
203 return Err(anyhow!(
204 "Incorrect OCSP response status: {:?}",
205 ocsp_response.status
206 ));
207 }
208
209 let raw = rasn::der::encode(&ocsp_response)
211 .map_err(|e| anyhow!("unable to serialize OCSP response: {e}"))?;
212
213 let ocsp_basic: BasicOcspResponse = rasn::der::decode(
214 &ocsp_response
215 .bytes
216 .ok_or_else(|| anyhow!("empty OCSP response"))?
217 .response,
218 )
219 .map_err(|e| anyhow!("unable to decode BasicOcspResponse: {e}"))?;
220
221 if ocsp_basic.tbs_response_data.responses.len() != 1 {
222 return Err(anyhow!(
223 "OCSP response should contain exactly one certificate"
224 ));
225 }
226
227 let resp = ocsp_basic.tbs_response_data.responses[0].clone();
228
229 Ok(Response {
230 raw,
231 cert_status: resp.cert_status,
232 ocsp_validity: Validity {
233 not_before: resp.this_update,
234 not_after: resp
235 .next_update
236 .ok_or_else(|| anyhow!("No next-update field in the response"))?,
237 },
238 })
239 }
240}
241
242#[cfg(test)]
243pub(crate) mod test {
244 use std::str::FromStr;
245
246 use super::*;
247
248 use hex_literal::hex;
249 use httptest::{matchers::*, responders::*, Expectation, Server};
250 use rasn::types::{Integer, OctetString};
251 use rustls::{crypto::ring, sign::CertifiedKey};
252
253 const OCSP_REQUEST: &[u8] = include_bytes!("../test/ocsp_request.bin");
254 const OCSP_RESPONSE: &[u8] = include_bytes!("../test/ocsp_response.bin");
255 const CHAIN: &[u8] = include_bytes!("../test/chain.pem");
256 const KEY: &[u8] = include_bytes!("../test/key.pem");
257
258 fn test_request() -> OcspRequest {
259 OcspRequest {
260 tbs_request: TbsRequest {
261 version: Version::ZERO,
262 requestor_name: None,
263 request_list: vec![Request {
264 req_cert: CertId {
265 hash_algorithm: AlgorithmIdentifier {
266 algorithm: Oid::ISO_IDENTIFIED_ORGANISATION_OIW_SECSIG_ALGORITHM_SHA1
267 .to_owned(),
268 parameters: None,
269 },
270 issuer_name_hash: OctetString::from(
271 &hex!("36175FAA02C887BDD95CA13549512D1E97FADFA9")[..],
272 ),
273 issuer_key_hash: OctetString::from(
274 &hex!("6691287B8D8654BAF6203197AEC491E9AFB70BCB")[..],
275 ),
276 serial_number: Integer::from(
277 num_bigint::BigInt::from_str(
278 "3819096869935823013274658159093914787918510",
279 )
280 .unwrap(),
281 ),
282 },
283 single_request_extensions: None,
284 }],
285 request_extensions: None,
286 },
287 optional_signature: None,
288 }
289 }
290
291 pub(crate) fn test_ckey() -> CertifiedKey {
292 let certs = CHAIN.to_vec();
293 let certs = rustls_pemfile::certs(&mut certs.as_ref())
294 .collect::<Result<Vec<_>, _>>()
295 .unwrap();
296
297 let key = KEY.to_vec();
298 let key = rustls_pemfile::private_key(&mut key.as_ref())
299 .unwrap()
300 .unwrap();
301
302 let key = ring::sign::any_supported_type(&key).unwrap();
303 CertifiedKey::new(certs, key)
304 }
305
306 #[test]
307 fn test_extract_url() {
308 let certs = CHAIN.to_vec();
309 let certs = rustls_pemfile::certs(&mut certs.as_ref())
310 .collect::<Result<Vec<_>, _>>()
311 .unwrap();
312
313 let cert = X509Certificate::from_der(&certs[0]).unwrap().1;
314
315 assert_eq!(
316 extract_ocsp_url(&cert),
317 Some("http://stg-e5.o.lencr.org".to_string())
318 )
319 }
320
321 #[test]
322 fn test_prepare_request() {
323 let certs = CHAIN.to_vec();
324 let certs = rustls_pemfile::certs(&mut certs.as_ref())
325 .collect::<Result<Vec<_>, _>>()
326 .unwrap();
327
328 let (ocsp_request, url) = prepare_ocsp_request(&certs[0], &certs[1]).unwrap();
329
330 assert_eq!(url.to_string(), "http://stg-e5.o.lencr.org/");
331 assert_eq!(ocsp_request, test_request());
332 assert_eq!(rasn::der::encode(&ocsp_request).unwrap(), OCSP_REQUEST);
333 }
334
335 #[tokio::test]
336 async fn test_execute() {
337 ring::default_provider()
339 .install_default()
340 .unwrap_or_default();
341
342 let server = Server::run();
343
344 server.expect(
345 Expectation::matching(request::method("GET"))
346 .respond_with(status_code(200).body(OCSP_RESPONSE)),
347 );
348
349 let client = Client::new();
350 let ocsp_response = client
351 .execute(Url::parse(&server.url_str("/")).unwrap(), test_request())
352 .await
353 .unwrap();
354
355 assert_eq!(OCSP_RESPONSE, rasn::der::encode(&ocsp_response).unwrap());
356 }
357}