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 #[cfg(not(awslc_fips))]
41 const NO_CA_SIGN = ffi::OCSP_NOCASIGN as c_ulong;
42 #[cfg(not(awslc_fips))]
43 const NO_DELEGATED = ffi::OCSP_NODELEGATED as c_ulong;
44 #[cfg(not(awslc_fips))]
45 const NO_CHECKS = ffi::OCSP_NOCHECKS as c_ulong;
46 const TRUST_OTHER = ffi::OCSP_TRUSTOTHER as c_ulong;
47 const RESPID_KEY = ffi::OCSP_RESPID_KEY as c_ulong;
48 const NO_TIME = ffi::OCSP_NOTIME as c_ulong;
49 }
50}
51
52#[derive(Copy, Clone, Debug, PartialEq, Eq)]
53pub struct OcspResponseStatus(c_int);
54
55impl OcspResponseStatus {
56 pub const SUCCESSFUL: OcspResponseStatus =
57 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_SUCCESSFUL);
58 pub const MALFORMED_REQUEST: OcspResponseStatus =
59 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_MALFORMEDREQUEST);
60 pub const INTERNAL_ERROR: OcspResponseStatus =
61 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_INTERNALERROR);
62 pub const TRY_LATER: OcspResponseStatus =
63 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_TRYLATER);
64 pub const SIG_REQUIRED: OcspResponseStatus =
65 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_SIGREQUIRED);
66 pub const UNAUTHORIZED: OcspResponseStatus =
67 OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_UNAUTHORIZED);
68
69 pub fn from_raw(raw: c_int) -> OcspResponseStatus {
70 OcspResponseStatus(raw)
71 }
72
73 #[allow(clippy::trivially_copy_pass_by_ref)]
74 pub fn as_raw(&self) -> c_int {
75 self.0
76 }
77}
78
79#[derive(Copy, Clone, Debug, PartialEq, Eq)]
80pub struct OcspCertStatus(c_int);
81
82impl OcspCertStatus {
83 pub const GOOD: OcspCertStatus = OcspCertStatus(ffi::V_OCSP_CERTSTATUS_GOOD);
84 pub const REVOKED: OcspCertStatus = OcspCertStatus(ffi::V_OCSP_CERTSTATUS_REVOKED);
85 pub const UNKNOWN: OcspCertStatus = OcspCertStatus(ffi::V_OCSP_CERTSTATUS_UNKNOWN);
86
87 pub fn from_raw(raw: c_int) -> OcspCertStatus {
88 OcspCertStatus(raw)
89 }
90
91 #[allow(clippy::trivially_copy_pass_by_ref)]
92 pub fn as_raw(&self) -> c_int {
93 self.0
94 }
95}
96
97#[derive(Copy, Clone, Debug, PartialEq, Eq)]
98pub struct OcspRevokedStatus(c_int);
99
100impl OcspRevokedStatus {
101 #[cfg(not(awslc_fips))]
102 pub const NO_STATUS: OcspRevokedStatus = OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_NOSTATUS);
103 #[cfg(awslc_fips)]
104 pub const NO_STATUS: OcspRevokedStatus = OcspRevokedStatus(-1);
105 pub const UNSPECIFIED: OcspRevokedStatus =
106 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_UNSPECIFIED);
107 pub const KEY_COMPROMISE: OcspRevokedStatus =
108 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_KEYCOMPROMISE);
109 pub const CA_COMPROMISE: OcspRevokedStatus =
110 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_CACOMPROMISE);
111 pub const AFFILIATION_CHANGED: OcspRevokedStatus =
112 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_AFFILIATIONCHANGED);
113 pub const STATUS_SUPERSEDED: OcspRevokedStatus =
114 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_SUPERSEDED);
115 pub const STATUS_CESSATION_OF_OPERATION: OcspRevokedStatus =
116 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_CESSATIONOFOPERATION);
117 pub const STATUS_CERTIFICATE_HOLD: OcspRevokedStatus =
118 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_CERTIFICATEHOLD);
119 pub const REMOVE_FROM_CRL: OcspRevokedStatus =
120 OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_REMOVEFROMCRL);
121
122 pub fn from_raw(raw: c_int) -> OcspRevokedStatus {
123 OcspRevokedStatus(raw)
124 }
125
126 #[allow(clippy::trivially_copy_pass_by_ref)]
127 pub fn as_raw(&self) -> c_int {
128 self.0
129 }
130}
131
132pub struct OcspStatus<'a> {
133 pub status: OcspCertStatus,
135 pub reason: OcspRevokedStatus,
137 pub revocation_time: Option<&'a Asn1GeneralizedTimeRef>,
139 pub this_update: &'a Asn1GeneralizedTimeRef,
141 #[deprecated(since = "0.10.75", note = "Use the next_update() method instead")]
148 pub next_update: &'a Asn1GeneralizedTimeRef,
149 next_update_opt: Option<&'a Asn1GeneralizedTimeRef>,
151}
152
153impl OcspStatus<'_> {
154 pub fn next_update(&self) -> Option<&Asn1GeneralizedTimeRef> {
159 self.next_update_opt
160 }
161
162 #[corresponds(OCSP_check_validity)]
170 pub fn check_validity(&self, nsec: u32, maxsec: Option<u32>) -> Result<(), ErrorStack> {
171 let next_update_ptr = self
172 .next_update_opt
173 .map(|t| t.as_ptr())
174 .unwrap_or(ptr::null_mut());
175 unsafe {
176 cvt(ffi::OCSP_check_validity(
177 self.this_update.as_ptr(),
178 next_update_ptr,
179 nsec as c_long,
180 maxsec.map(|n| n as c_long).unwrap_or(-1),
181 ))
182 .map(|_| ())
183 }
184 }
185}
186
187foreign_type_and_impl_send_sync! {
188 type CType = ffi::OCSP_BASICRESP;
189 fn drop = ffi::OCSP_BASICRESP_free;
190
191 pub struct OcspBasicResponse;
192 pub struct OcspBasicResponseRef;
193}
194
195impl OcspBasicResponseRef {
196 #[corresponds(OCSP_basic_verify)]
201 pub fn verify(
202 &self,
203 certs: &StackRef<X509>,
204 store: &X509StoreRef,
205 flags: OcspFlag,
206 ) -> Result<(), ErrorStack> {
207 unsafe {
208 cvt(ffi::OCSP_basic_verify(
209 self.as_ptr(),
210 certs.as_ptr(),
211 store.as_ptr(),
212 flags.bits(),
213 ))
214 .map(|_| ())
215 }
216 }
217
218 #[corresponds(OCSP_resp_find_status)]
220 pub fn find_status<'a>(&'a self, id: &OcspCertIdRef) -> Option<OcspStatus<'a>> {
221 unsafe {
222 let mut status = ffi::V_OCSP_CERTSTATUS_UNKNOWN;
223 let mut reason = ffi::OCSP_REVOKED_STATUS_NOSTATUS;
224 let mut revocation_time = ptr::null_mut();
225 let mut this_update = ptr::null_mut();
226 let mut next_update = ptr::null_mut();
227
228 let r = ffi::OCSP_resp_find_status(
229 self.as_ptr(),
230 id.as_ptr(),
231 &mut status,
232 &mut reason,
233 &mut revocation_time,
234 &mut this_update,
235 &mut next_update,
236 );
237 if r == 1 {
238 let revocation_time = Asn1GeneralizedTimeRef::from_const_ptr_opt(revocation_time);
239 let next_update_opt = Asn1GeneralizedTimeRef::from_const_ptr_opt(next_update);
240 let next_update_compat = next_update_opt.unwrap_or_else(|| get_sentinel_max_time());
242
243 #[allow(deprecated)]
244 Some(OcspStatus {
245 status: OcspCertStatus(status),
246 reason: OcspRevokedStatus(reason),
247 revocation_time,
248 this_update: Asn1GeneralizedTimeRef::from_ptr(this_update),
249 next_update: next_update_compat,
250 next_update_opt,
251 })
252 } else {
253 None
254 }
255 }
256 }
257}
258
259foreign_type_and_impl_send_sync! {
260 type CType = ffi::OCSP_CERTID;
261 fn drop = ffi::OCSP_CERTID_free;
262
263 pub struct OcspCertId;
264 pub struct OcspCertIdRef;
265}
266
267impl OcspCertId {
268 #[corresponds(OCSP_cert_to_id)]
270 pub fn from_cert(
271 digest: MessageDigest,
272 subject: &X509Ref,
273 issuer: &X509Ref,
274 ) -> Result<OcspCertId, ErrorStack> {
275 unsafe {
276 cvt_p(ffi::OCSP_cert_to_id(
277 digest.as_ptr(),
278 subject.as_ptr(),
279 issuer.as_ptr(),
280 ))
281 .map(OcspCertId)
282 }
283 }
284}
285
286foreign_type_and_impl_send_sync! {
287 type CType = ffi::OCSP_RESPONSE;
288 fn drop = ffi::OCSP_RESPONSE_free;
289
290 pub struct OcspResponse;
291 pub struct OcspResponseRef;
292}
293
294impl OcspResponse {
295 #[corresponds(OCSP_response_create)]
299 pub fn create(
300 status: OcspResponseStatus,
301 body: Option<&OcspBasicResponseRef>,
302 ) -> Result<OcspResponse, ErrorStack> {
303 unsafe {
304 ffi::init();
305
306 cvt_p(ffi::OCSP_response_create(
307 status.as_raw(),
308 body.map(|r| r.as_ptr()).unwrap_or(ptr::null_mut()),
309 ))
310 .map(OcspResponse)
311 }
312 }
313
314 from_der! {
315 #[corresponds(d2i_OCSP_RESPONSE)]
317 from_der,
318 OcspResponse,
319 ffi::d2i_OCSP_RESPONSE
320 }
321}
322
323impl OcspResponseRef {
324 to_der! {
325 #[corresponds(i2d_OCSP_RESPONSE)]
327 to_der,
328 ffi::i2d_OCSP_RESPONSE
329 }
330
331 #[corresponds(OCSP_response_status)]
333 pub fn status(&self) -> OcspResponseStatus {
334 unsafe { OcspResponseStatus(ffi::OCSP_response_status(self.as_ptr())) }
335 }
336
337 #[corresponds(OCSP_response_get1_basic)]
341 pub fn basic(&self) -> Result<OcspBasicResponse, ErrorStack> {
342 unsafe { cvt_p(ffi::OCSP_response_get1_basic(self.as_ptr())).map(OcspBasicResponse) }
343 }
344}
345
346foreign_type_and_impl_send_sync! {
347 type CType = ffi::OCSP_REQUEST;
348 fn drop = ffi::OCSP_REQUEST_free;
349
350 pub struct OcspRequest;
351 pub struct OcspRequestRef;
352}
353
354impl OcspRequest {
355 #[corresponds(OCSP_REQUEST_new)]
356 pub fn new() -> Result<OcspRequest, ErrorStack> {
357 unsafe {
358 ffi::init();
359
360 cvt_p(ffi::OCSP_REQUEST_new()).map(OcspRequest)
361 }
362 }
363
364 from_der! {
365 #[corresponds(d2i_OCSP_REQUEST)]
367 from_der,
368 OcspRequest,
369 ffi::d2i_OCSP_REQUEST
370 }
371}
372
373impl OcspRequestRef {
374 to_der! {
375 #[corresponds(i2d_OCSP_REQUEST)]
377 to_der,
378 ffi::i2d_OCSP_REQUEST
379 }
380
381 #[corresponds(OCSP_request_add0_id)]
382 pub fn add_id(&mut self, id: OcspCertId) -> Result<&mut OcspOneReqRef, ErrorStack> {
383 unsafe {
384 let ptr = cvt_p(ffi::OCSP_request_add0_id(self.as_ptr(), id.as_ptr()))?;
385 mem::forget(id);
386 Ok(OcspOneReqRef::from_ptr_mut(ptr))
387 }
388 }
389}
390
391foreign_type_and_impl_send_sync! {
392 type CType = ffi::OCSP_ONEREQ;
393 fn drop = ffi::OCSP_ONEREQ_free;
394
395 pub struct OcspOneReq;
396 pub struct OcspOneReqRef;
397}
398
399#[cfg(test)]
400mod tests {
401 use super::{
402 get_sentinel_max_time, OcspCertId, OcspCertStatus, OcspResponse, OcspResponseStatus,
403 OcspRevokedStatus,
404 };
405 use crate::hash::MessageDigest;
406 use crate::x509::X509;
407
408 const OCSP_RESPONSE_NO_NEXTUPDATE: &[u8] =
410 include_bytes!("../test/ocsp_resp_no_nextupdate.der");
411 const OCSP_CA_CERT: &[u8] = include_bytes!("../test/ocsp_ca_cert.der");
412 const OCSP_SUBJECT_CERT: &[u8] = include_bytes!("../test/ocsp_subject_cert.der");
413 const OCSP_RESPONSE_REVOKED: &[u8] = include_bytes!("../test/ocsp_resp_revoked.der");
414
415 #[test]
416 fn test_ocsp_no_next_update() {
417 let response = OcspResponse::from_der(OCSP_RESPONSE_NO_NEXTUPDATE).unwrap();
419 assert_eq!(response.status(), OcspResponseStatus::SUCCESSFUL);
420
421 let ca_cert = X509::from_der(OCSP_CA_CERT).unwrap();
422 let subject_cert = X509::from_der(OCSP_SUBJECT_CERT).unwrap();
423 let basic = response.basic().unwrap();
424
425 let cert_id =
426 OcspCertId::from_cert(MessageDigest::sha256(), &subject_cert, &ca_cert).unwrap();
427
428 let status = basic
429 .find_status(&cert_id)
430 .expect("find_status should find the status");
431
432 assert!(status.next_update().is_none());
433
434 #[allow(deprecated)]
435 let deprecated_next = status.next_update;
436 let sentinel = get_sentinel_max_time();
437 assert_eq!(format!("{}", deprecated_next), format!("{}", sentinel));
438
439 assert_eq!(status.status, OcspCertStatus::GOOD);
440 }
441
442 #[test]
443 fn test_ocsp_revoked() {
444 let response = OcspResponse::from_der(OCSP_RESPONSE_REVOKED).unwrap();
445 assert_eq!(response.status(), OcspResponseStatus::SUCCESSFUL);
446
447 let ca_cert = X509::from_der(OCSP_CA_CERT).unwrap();
448 let subject_cert = X509::from_der(OCSP_SUBJECT_CERT).unwrap();
449 let basic = response.basic().unwrap();
450
451 let cert_id =
452 OcspCertId::from_cert(MessageDigest::sha256(), &subject_cert, &ca_cert).unwrap();
453
454 let status = basic
455 .find_status(&cert_id)
456 .expect("find_status should find the status");
457
458 assert_eq!(status.status, OcspCertStatus::REVOKED);
459 assert_eq!(status.reason, OcspRevokedStatus::STATUS_SUPERSEDED);
460 }
461}