1use bitflags::bitflags;
2use foreign_types::{ForeignType, ForeignTypeRef};
3use libc::{c_int, c_long, c_ulong};
4use std::mem;
5use std::ptr;
6use std::sync::OnceLock;
7
8use crate::asn1::{Asn1GeneralizedTime, Asn1GeneralizedTimeRef};
9use crate::error::ErrorStack;
10use crate::hash::MessageDigest;
11use crate::stack::StackRef;
12use crate::util::ForeignTypeRefExt;
13use crate::x509::store::X509StoreRef;
14use crate::x509::{X509Ref, X509};
15use crate::{cvt, cvt_p};
16use openssl_macros::corresponds;
17
18static SENTINEL_MAX_TIME: OnceLock<Asn1GeneralizedTime> = OnceLock::new();
21
22fn get_sentinel_max_time() -> &'static Asn1GeneralizedTimeRef {
23 SENTINEL_MAX_TIME
24 .get_or_init(|| {
25 Asn1GeneralizedTime::from_str("99991231235959Z")
26 .expect("Failed to create sentinel time")
27 })
28 .as_ref()
29}
30
31bitflags! {
32 #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
33 #[repr(transparent)]
34 pub struct OcspFlag: c_ulong {
35 const NO_CERTS = ffi::OCSP_NOCERTS as c_ulong;
36 const NO_INTERN = ffi::OCSP_NOINTERN as c_ulong;
37 const NO_CHAIN = ffi::OCSP_NOCHAIN as c_ulong;
38 const NO_VERIFY = ffi::OCSP_NOVERIFY as c_ulong;
39 const NO_EXPLICIT = ffi::OCSP_NOEXPLICIT as c_ulong;
40 const NO_CA_SIGN = ffi::OCSP_NOCASIGN as c_ulong;
41 const NO_DELEGATED = ffi::OCSP_NODELEGATED as c_ulong;
42 const NO_CHECKS = ffi::OCSP_NOCHECKS as c_ulong;
43 const TRUST_OTHER = ffi::OCSP_TRUSTOTHER as c_ulong;
44 const RESPID_KEY = ffi::OCSP_RESPID_KEY as c_ulong;
45 const NO_TIME = ffi::OCSP_NOTIME as c_ulong;
46 }
47}
48
49#[derive(Copy, Clone, Debug, PartialEq, Eq)]
50pub struct OcspResponseStatus(c_int);
51
52impl OcspResponseStatus {
53 pub const SUCCESSFUL: OcspResponseStatus =
54 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_SUCCESSFUL);
55 pub const MALFORMED_REQUEST: OcspResponseStatus =
56 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_MALFORMEDREQUEST);
57 pub const INTERNAL_ERROR: OcspResponseStatus =
58 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_INTERNALERROR);
59 pub const TRY_LATER: OcspResponseStatus =
60 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_TRYLATER);
61 pub const SIG_REQUIRED: OcspResponseStatus =
62 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_SIGREQUIRED);
63 pub const UNAUTHORIZED: OcspResponseStatus =
64 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_UNAUTHORIZED);
65
66 pub fn from_raw(raw: c_int) -> OcspResponseStatus {
67 OcspResponseStatus(raw)
68 }
69
70 #[allow(clippy::trivially_copy_pass_by_ref)]
71 pub fn as_raw(&self) -> c_int {
72 self.0
73 }
74}
75
76#[derive(Copy, Clone, Debug, PartialEq, Eq)]
77pub struct OcspCertStatus(c_int);
78
79impl OcspCertStatus {
80 pub const GOOD: OcspCertStatus = OcspCertStatus(ffi::V_OCSP_CERTSTATUS_GOOD);
81 pub const REVOKED: OcspCertStatus = OcspCertStatus(ffi::V_OCSP_CERTSTATUS_REVOKED);
82 pub const UNKNOWN: OcspCertStatus = OcspCertStatus(ffi::V_OCSP_CERTSTATUS_UNKNOWN);
83
84 pub fn from_raw(raw: c_int) -> OcspCertStatus {
85 OcspCertStatus(raw)
86 }
87
88 #[allow(clippy::trivially_copy_pass_by_ref)]
89 pub fn as_raw(&self) -> c_int {
90 self.0
91 }
92}
93
94#[derive(Copy, Clone, Debug, PartialEq, Eq)]
95pub struct OcspRevokedStatus(c_int);
96
97impl OcspRevokedStatus {
98 pub const NO_STATUS: OcspRevokedStatus = OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_NOSTATUS);
99 pub const UNSPECIFIED: OcspRevokedStatus =
100 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_UNSPECIFIED);
101 pub const KEY_COMPROMISE: OcspRevokedStatus =
102 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_KEYCOMPROMISE);
103 pub const CA_COMPROMISE: OcspRevokedStatus =
104 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_CACOMPROMISE);
105 pub const AFFILIATION_CHANGED: OcspRevokedStatus =
106 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_AFFILIATIONCHANGED);
107 pub const STATUS_SUPERSEDED: OcspRevokedStatus =
108 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_SUPERSEDED);
109 pub const STATUS_CESSATION_OF_OPERATION: OcspRevokedStatus =
110 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_CESSATIONOFOPERATION);
111 pub const STATUS_CERTIFICATE_HOLD: OcspRevokedStatus =
112 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_CERTIFICATEHOLD);
113 pub const REMOVE_FROM_CRL: OcspRevokedStatus =
114 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_REMOVEFROMCRL);
115
116 pub fn from_raw(raw: c_int) -> OcspRevokedStatus {
117 OcspRevokedStatus(raw)
118 }
119
120 #[allow(clippy::trivially_copy_pass_by_ref)]
121 pub fn as_raw(&self) -> c_int {
122 self.0
123 }
124}
125
126pub struct OcspStatus<'a> {
127 pub status: OcspCertStatus,
129 pub reason: OcspRevokedStatus,
131 pub revocation_time: Option<&'a Asn1GeneralizedTimeRef>,
133 pub this_update: &'a Asn1GeneralizedTimeRef,
135 #[deprecated(since = "0.10.75", note = "Use the next_update() method instead")]
142 pub next_update: &'a Asn1GeneralizedTimeRef,
143 next_update_opt: Option<&'a Asn1GeneralizedTimeRef>,
145}
146
147impl OcspStatus<'_> {
148 pub fn next_update(&self) -> Option<&Asn1GeneralizedTimeRef> {
153 self.next_update_opt
154 }
155
156 #[corresponds(OCSP_check_validity)]
164 pub fn check_validity(&self, nsec: u32, maxsec: Option<u32>) -> Result<(), ErrorStack> {
165 let next_update_ptr = self
166 .next_update_opt
167 .map(|t| t.as_ptr())
168 .unwrap_or(ptr::null_mut());
169 unsafe {
170 cvt(ffi::OCSP_check_validity(
171 self.this_update.as_ptr(),
172 next_update_ptr,
173 nsec as c_long,
174 maxsec.map(|n| n as c_long).unwrap_or(-1),
175 ))
176 .map(|_| ())
177 }
178 }
179}
180
181foreign_type_and_impl_send_sync! {
182 type CType = ffi::OCSP_BASICRESP;
183 fn drop = ffi::OCSP_BASICRESP_free;
184
185 pub struct OcspBasicResponse;
186 pub struct OcspBasicResponseRef;
187}
188
189impl OcspBasicResponseRef {
190 #[corresponds(OCSP_basic_verify)]
195 pub fn verify(
196 &self,
197 certs: &StackRef<X509>,
198 store: &X509StoreRef,
199 flags: OcspFlag,
200 ) -> Result<(), ErrorStack> {
201 unsafe {
202 cvt(ffi::OCSP_basic_verify(
203 self.as_ptr(),
204 certs.as_ptr(),
205 store.as_ptr(),
206 flags.bits(),
207 ))
208 .map(|_| ())
209 }
210 }
211
212 #[corresponds(OCSP_resp_find_status)]
214 pub fn find_status<'a>(&'a self, id: &OcspCertIdRef) -> Option<OcspStatus<'a>> {
215 unsafe {
216 let mut status = ffi::V_OCSP_CERTSTATUS_UNKNOWN;
217 let mut reason = ffi::OCSP_REVOKED_STATUS_NOSTATUS;
218 let mut revocation_time = ptr::null_mut();
219 let mut this_update = ptr::null_mut();
220 let mut next_update = ptr::null_mut();
221
222 let r = ffi::OCSP_resp_find_status(
223 self.as_ptr(),
224 id.as_ptr(),
225 &mut status,
226 &mut reason,
227 &mut revocation_time,
228 &mut this_update,
229 &mut next_update,
230 );
231 if r == 1 {
232 let revocation_time = Asn1GeneralizedTimeRef::from_const_ptr_opt(revocation_time);
233 let next_update_opt = Asn1GeneralizedTimeRef::from_const_ptr_opt(next_update);
234 let next_update_compat = next_update_opt.unwrap_or_else(|| get_sentinel_max_time());
236
237 #[allow(deprecated)]
238 Some(OcspStatus {
239 status: OcspCertStatus(status),
240 reason: OcspRevokedStatus(status),
241 revocation_time,
242 this_update: Asn1GeneralizedTimeRef::from_ptr(this_update),
243 next_update: next_update_compat,
244 next_update_opt,
245 })
246 } else {
247 None
248 }
249 }
250 }
251}
252
253foreign_type_and_impl_send_sync! {
254 type CType = ffi::OCSP_CERTID;
255 fn drop = ffi::OCSP_CERTID_free;
256
257 pub struct OcspCertId;
258 pub struct OcspCertIdRef;
259}
260
261impl OcspCertId {
262 #[corresponds(OCSP_cert_to_id)]
264 pub fn from_cert(
265 digest: MessageDigest,
266 subject: &X509Ref,
267 issuer: &X509Ref,
268 ) -> Result<OcspCertId, ErrorStack> {
269 unsafe {
270 cvt_p(ffi::OCSP_cert_to_id(
271 digest.as_ptr(),
272 subject.as_ptr(),
273 issuer.as_ptr(),
274 ))
275 .map(OcspCertId)
276 }
277 }
278}
279
280foreign_type_and_impl_send_sync! {
281 type CType = ffi::OCSP_RESPONSE;
282 fn drop = ffi::OCSP_RESPONSE_free;
283
284 pub struct OcspResponse;
285 pub struct OcspResponseRef;
286}
287
288impl OcspResponse {
289 #[corresponds(OCSP_response_create)]
293 pub fn create(
294 status: OcspResponseStatus,
295 body: Option<&OcspBasicResponseRef>,
296 ) -> Result<OcspResponse, ErrorStack> {
297 unsafe {
298 ffi::init();
299
300 cvt_p(ffi::OCSP_response_create(
301 status.as_raw(),
302 body.map(|r| r.as_ptr()).unwrap_or(ptr::null_mut()),
303 ))
304 .map(OcspResponse)
305 }
306 }
307
308 from_der! {
309 #[corresponds(d2i_OCSP_RESPONSE)]
311 from_der,
312 OcspResponse,
313 ffi::d2i_OCSP_RESPONSE
314 }
315}
316
317impl OcspResponseRef {
318 to_der! {
319 #[corresponds(i2d_OCSP_RESPONSE)]
321 to_der,
322 ffi::i2d_OCSP_RESPONSE
323 }
324
325 #[corresponds(OCSP_response_status)]
327 pub fn status(&self) -> OcspResponseStatus {
328 unsafe { OcspResponseStatus(ffi::OCSP_response_status(self.as_ptr())) }
329 }
330
331 #[corresponds(OCSP_response_get1_basic)]
335 pub fn basic(&self) -> Result<OcspBasicResponse, ErrorStack> {
336 unsafe { cvt_p(ffi::OCSP_response_get1_basic(self.as_ptr())).map(OcspBasicResponse) }
337 }
338}
339
340foreign_type_and_impl_send_sync! {
341 type CType = ffi::OCSP_REQUEST;
342 fn drop = ffi::OCSP_REQUEST_free;
343
344 pub struct OcspRequest;
345 pub struct OcspRequestRef;
346}
347
348impl OcspRequest {
349 #[corresponds(OCSP_REQUEST_new)]
350 pub fn new() -> Result<OcspRequest, ErrorStack> {
351 unsafe {
352 ffi::init();
353
354 cvt_p(ffi::OCSP_REQUEST_new()).map(OcspRequest)
355 }
356 }
357
358 from_der! {
359 #[corresponds(d2i_OCSP_REQUEST)]
361 from_der,
362 OcspRequest,
363 ffi::d2i_OCSP_REQUEST
364 }
365}
366
367impl OcspRequestRef {
368 to_der! {
369 #[corresponds(i2d_OCSP_REQUEST)]
371 to_der,
372 ffi::i2d_OCSP_REQUEST
373 }
374
375 #[corresponds(OCSP_request_add0_id)]
376 pub fn add_id(&mut self, id: OcspCertId) -> Result<&mut OcspOneReqRef, ErrorStack> {
377 unsafe {
378 let ptr = cvt_p(ffi::OCSP_request_add0_id(self.as_ptr(), id.as_ptr()))?;
379 mem::forget(id);
380 Ok(OcspOneReqRef::from_ptr_mut(ptr))
381 }
382 }
383}
384
385foreign_type_and_impl_send_sync! {
386 type CType = ffi::OCSP_ONEREQ;
387 fn drop = ffi::OCSP_ONEREQ_free;
388
389 pub struct OcspOneReq;
390 pub struct OcspOneReqRef;
391}
392
393#[cfg(test)]
394mod tests {
395 use super::{
396 get_sentinel_max_time, OcspCertId, OcspCertStatus, OcspResponse, OcspResponseStatus,
397 };
398 use crate::hash::MessageDigest;
399 use crate::x509::X509;
400
401 const OCSP_RESPONSE_NO_NEXTUPDATE: &[u8] =
403 include_bytes!("../test/ocsp_resp_no_nextupdate.der");
404 const OCSP_CA_CERT: &[u8] = include_bytes!("../test/ocsp_ca_cert.der");
405 const OCSP_SUBJECT_CERT: &[u8] = include_bytes!("../test/ocsp_subject_cert.der");
406
407 #[test]
408 fn test_ocsp_no_next_update() {
409 let response = OcspResponse::from_der(OCSP_RESPONSE_NO_NEXTUPDATE).unwrap();
411 assert_eq!(response.status(), OcspResponseStatus::SUCCESSFUL);
412
413 let ca_cert = X509::from_der(OCSP_CA_CERT).unwrap();
414 let subject_cert = X509::from_der(OCSP_SUBJECT_CERT).unwrap();
415 let basic = response.basic().unwrap();
416
417 let cert_id =
418 OcspCertId::from_cert(MessageDigest::sha256(), &subject_cert, &ca_cert).unwrap();
419
420 let status = basic
421 .find_status(&cert_id)
422 .expect("find_status should find the status");
423
424 assert!(status.next_update().is_none());
425
426 #[allow(deprecated)]
427 let deprecated_next = status.next_update;
428 let sentinel = get_sentinel_max_time();
429 assert_eq!(format!("{}", deprecated_next), format!("{}", sentinel));
430
431 assert_eq!(status.status, OcspCertStatus::GOOD);
432 }
433}