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 #[cfg(not(awslc_fips))]
224 let mut reason = ffi::OCSP_REVOKED_STATUS_NOSTATUS;
225 #[cfg(awslc_fips)]
226 let mut reason = -1;
227 let mut revocation_time = ptr::null_mut();
228 let mut this_update = ptr::null_mut();
229 let mut next_update = ptr::null_mut();
230
231 let r = ffi::OCSP_resp_find_status(
232 self.as_ptr(),
233 id.as_ptr(),
234 &mut status,
235 &mut reason,
236 &mut revocation_time,
237 &mut this_update,
238 &mut next_update,
239 );
240 if r == 1 {
241 let revocation_time = Asn1GeneralizedTimeRef::from_const_ptr_opt(revocation_time);
242 let next_update_opt = Asn1GeneralizedTimeRef::from_const_ptr_opt(next_update);
243 let next_update_compat = next_update_opt.unwrap_or_else(|| get_sentinel_max_time());
245
246 #[allow(deprecated)]
247 Some(OcspStatus {
248 status: OcspCertStatus(status),
249 reason: OcspRevokedStatus(reason),
250 revocation_time,
251 this_update: Asn1GeneralizedTimeRef::from_ptr(this_update),
252 next_update: next_update_compat,
253 next_update_opt,
254 })
255 } else {
256 None
257 }
258 }
259 }
260}
261
262foreign_type_and_impl_send_sync! {
263 type CType = ffi::OCSP_CERTID;
264 fn drop = ffi::OCSP_CERTID_free;
265
266 pub struct OcspCertId;
267 pub struct OcspCertIdRef;
268}
269
270impl OcspCertId {
271 #[corresponds(OCSP_cert_to_id)]
273 pub fn from_cert(
274 digest: MessageDigest,
275 subject: &X509Ref,
276 issuer: &X509Ref,
277 ) -> Result<OcspCertId, ErrorStack> {
278 unsafe {
279 cvt_p(ffi::OCSP_cert_to_id(
280 digest.as_ptr(),
281 subject.as_ptr(),
282 issuer.as_ptr(),
283 ))
284 .map(OcspCertId)
285 }
286 }
287}
288
289foreign_type_and_impl_send_sync! {
290 type CType = ffi::OCSP_RESPONSE;
291 fn drop = ffi::OCSP_RESPONSE_free;
292
293 pub struct OcspResponse;
294 pub struct OcspResponseRef;
295}
296
297impl OcspResponse {
298 #[corresponds(OCSP_response_create)]
302 pub fn create(
303 status: OcspResponseStatus,
304 body: Option<&OcspBasicResponseRef>,
305 ) -> Result<OcspResponse, ErrorStack> {
306 unsafe {
307 ffi::init();
308
309 cvt_p(ffi::OCSP_response_create(
310 status.as_raw(),
311 body.map(|r| r.as_ptr()).unwrap_or(ptr::null_mut()),
312 ))
313 .map(OcspResponse)
314 }
315 }
316
317 from_der! {
318 #[corresponds(d2i_OCSP_RESPONSE)]
320 from_der,
321 OcspResponse,
322 ffi::d2i_OCSP_RESPONSE
323 }
324}
325
326impl OcspResponseRef {
327 to_der! {
328 #[corresponds(i2d_OCSP_RESPONSE)]
330 to_der,
331 ffi::i2d_OCSP_RESPONSE
332 }
333
334 #[corresponds(OCSP_response_status)]
336 pub fn status(&self) -> OcspResponseStatus {
337 unsafe { OcspResponseStatus(ffi::OCSP_response_status(self.as_ptr())) }
338 }
339
340 #[corresponds(OCSP_response_get1_basic)]
344 pub fn basic(&self) -> Result<OcspBasicResponse, ErrorStack> {
345 unsafe { cvt_p(ffi::OCSP_response_get1_basic(self.as_ptr())).map(OcspBasicResponse) }
346 }
347}
348
349foreign_type_and_impl_send_sync! {
350 type CType = ffi::OCSP_REQUEST;
351 fn drop = ffi::OCSP_REQUEST_free;
352
353 pub struct OcspRequest;
354 pub struct OcspRequestRef;
355}
356
357impl OcspRequest {
358 #[corresponds(OCSP_REQUEST_new)]
359 pub fn new() -> Result<OcspRequest, ErrorStack> {
360 unsafe {
361 ffi::init();
362
363 cvt_p(ffi::OCSP_REQUEST_new()).map(OcspRequest)
364 }
365 }
366
367 from_der! {
368 #[corresponds(d2i_OCSP_REQUEST)]
370 from_der,
371 OcspRequest,
372 ffi::d2i_OCSP_REQUEST
373 }
374}
375
376impl OcspRequestRef {
377 to_der! {
378 #[corresponds(i2d_OCSP_REQUEST)]
380 to_der,
381 ffi::i2d_OCSP_REQUEST
382 }
383
384 #[corresponds(OCSP_request_add0_id)]
385 pub fn add_id(&mut self, id: OcspCertId) -> Result<&mut OcspOneReqRef, ErrorStack> {
386 unsafe {
387 let ptr = cvt_p(ffi::OCSP_request_add0_id(self.as_ptr(), id.as_ptr()))?;
388 mem::forget(id);
389 Ok(OcspOneReqRef::from_ptr_mut(ptr))
390 }
391 }
392}
393
394foreign_type_and_impl_send_sync! {
395 type CType = ffi::OCSP_ONEREQ;
396 fn drop = ffi::OCSP_ONEREQ_free;
397
398 pub struct OcspOneReq;
399 pub struct OcspOneReqRef;
400}
401
402#[cfg(test)]
403mod tests {
404 use super::{
405 get_sentinel_max_time, OcspCertId, OcspCertStatus, OcspResponse, OcspResponseStatus,
406 OcspRevokedStatus,
407 };
408 use crate::hash::MessageDigest;
409 use crate::x509::X509;
410
411 const OCSP_RESPONSE_NO_NEXTUPDATE: &[u8] =
413 include_bytes!("../test/ocsp_resp_no_nextupdate.der");
414 const OCSP_CA_CERT: &[u8] = include_bytes!("../test/ocsp_ca_cert.der");
415 const OCSP_SUBJECT_CERT: &[u8] = include_bytes!("../test/ocsp_subject_cert.der");
416 const OCSP_RESPONSE_REVOKED: &[u8] = include_bytes!("../test/ocsp_resp_revoked.der");
417
418 #[test]
419 fn test_ocsp_no_next_update() {
420 let response = OcspResponse::from_der(OCSP_RESPONSE_NO_NEXTUPDATE).unwrap();
422 assert_eq!(response.status(), OcspResponseStatus::SUCCESSFUL);
423
424 let ca_cert = X509::from_der(OCSP_CA_CERT).unwrap();
425 let subject_cert = X509::from_der(OCSP_SUBJECT_CERT).unwrap();
426 let basic = response.basic().unwrap();
427
428 let cert_id =
429 OcspCertId::from_cert(MessageDigest::sha256(), &subject_cert, &ca_cert).unwrap();
430
431 let status = basic
432 .find_status(&cert_id)
433 .expect("find_status should find the status");
434
435 assert!(status.next_update().is_none());
436
437 #[allow(deprecated)]
438 let deprecated_next = status.next_update;
439 let sentinel = get_sentinel_max_time();
440 assert_eq!(format!("{}", deprecated_next), format!("{}", sentinel));
441
442 assert_eq!(status.status, OcspCertStatus::GOOD);
443 }
444
445 #[test]
446 fn test_ocsp_revoked() {
447 let response = OcspResponse::from_der(OCSP_RESPONSE_REVOKED).unwrap();
448 assert_eq!(response.status(), OcspResponseStatus::SUCCESSFUL);
449
450 let ca_cert = X509::from_der(OCSP_CA_CERT).unwrap();
451 let subject_cert = X509::from_der(OCSP_SUBJECT_CERT).unwrap();
452 let basic = response.basic().unwrap();
453
454 let cert_id =
455 OcspCertId::from_cert(MessageDigest::sha256(), &subject_cert, &ca_cert).unwrap();
456
457 let status = basic
458 .find_status(&cert_id)
459 .expect("find_status should find the status");
460
461 assert_eq!(status.status, OcspCertStatus::REVOKED);
462 assert_eq!(status.reason, OcspRevokedStatus::STATUS_SUPERSEDED);
463 }
464}