Skip to main content

native_ossl/
x509.rs

1//! X.509 certificate — reading, inspecting, and building.
2//!
3//! # Types
4//!
5//! | Type              | Owned / Borrowed | Description                          |
6//! |-------------------|-----------------|--------------------------------------|
7//! | [`X509`]          | Owned (Arc-like) | Certificate, Clone via `up_ref`      |
8//! | [`X509Name`]      | Borrowed `'cert` | Subject or issuer distinguished name |
9//! | [`X509NameEntry`] | Borrowed `'name` | One RDN entry (e.g. CN, O, C)       |
10//! | [`X509Extension`] | Borrowed `'cert` | One certificate extension            |
11//! | [`X509NameOwned`] | Owned            | Mutable name for certificate builder |
12//! | [`X509Builder`]   | Owned builder    | Constructs a new X.509 certificate   |
13
14use crate::bio::{MemBio, MemBioBuf};
15use crate::error::ErrorStack;
16use native_ossl_sys as sys;
17use std::ffi::CStr;
18use std::marker::PhantomData;
19use std::sync::Arc;
20
21// ── X509 — owned certificate ──────────────────────────────────────────────────
22
23/// An X.509 certificate (`X509*`).
24///
25/// Cloneable via `EVP_X509_up_ref`; wrapping in `Arc<X509>` is safe.
26pub struct X509 {
27    ptr: *mut sys::X509,
28}
29
30// SAFETY: `X509*` is reference-counted.
31unsafe impl Send for X509 {}
32unsafe impl Sync for X509 {}
33
34impl Clone for X509 {
35    fn clone(&self) -> Self {
36        unsafe { sys::X509_up_ref(self.ptr) };
37        X509 { ptr: self.ptr }
38    }
39}
40
41impl Drop for X509 {
42    fn drop(&mut self) {
43        unsafe { sys::X509_free(self.ptr) };
44    }
45}
46
47impl X509 {
48    /// Construct from a raw, owned `X509*`.
49    ///
50    /// # Safety
51    ///
52    /// `ptr` must be a valid, non-null `X509*` whose ownership is being transferred.
53    pub(crate) unsafe fn from_ptr(ptr: *mut sys::X509) -> Self {
54        X509 { ptr }
55    }
56
57    /// Load a certificate from PEM bytes.
58    ///
59    /// # Errors
60    pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
61        let bio = MemBioBuf::new(pem)?;
62        let ptr = unsafe {
63            sys::PEM_read_bio_X509(
64                bio.as_ptr(),
65                std::ptr::null_mut(),
66                None,
67                std::ptr::null_mut(),
68            )
69        };
70        if ptr.is_null() {
71            return Err(ErrorStack::drain());
72        }
73        Ok(unsafe { Self::from_ptr(ptr) })
74    }
75
76    /// Load a certificate from PEM bytes, accepting a library context for API
77    /// symmetry with other `from_pem_in` methods.
78    ///
79    /// OpenSSL 3.5 does not expose a libctx-aware `PEM_read_bio_X509_ex`
80    /// variant, so this calls the standard `PEM_read_bio_X509`.  The `ctx`
81    /// parameter is accepted but unused.  Certificate parsing itself does not
82    /// require provider dispatch; provider-bound operations use the context
83    /// stored in the key extracted from the certificate.
84    ///
85    /// # Errors
86    pub fn from_pem_in(_ctx: &Arc<crate::lib_ctx::LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack> {
87        Self::from_pem(pem)
88    }
89
90    /// Load a certificate from DER bytes.
91    ///
92    /// Zero-copy: parses from the caller's slice without an intermediate buffer.
93    ///
94    /// # Errors
95    ///
96    /// Returns `Err` if the DER is malformed, or if `der.len()` exceeds `i64::MAX`.
97    pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
98        let mut der_ptr = der.as_ptr();
99        let len = i64::try_from(der.len()).map_err(|_| ErrorStack::drain())?;
100        let ptr =
101            unsafe { sys::d2i_X509(std::ptr::null_mut(), std::ptr::addr_of_mut!(der_ptr), len) };
102        if ptr.is_null() {
103            return Err(ErrorStack::drain());
104        }
105        Ok(unsafe { Self::from_ptr(ptr) })
106    }
107
108    /// Serialise to PEM.
109    ///
110    /// # Errors
111    pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack> {
112        let mut bio = MemBio::new()?;
113        crate::ossl_call!(sys::PEM_write_bio_X509(bio.as_ptr(), self.ptr))?;
114        Ok(bio.into_vec())
115    }
116
117    /// Serialise to DER.
118    ///
119    /// Zero-copy: writes into a freshly allocated `Vec<u8>` without going
120    /// through an OpenSSL-owned buffer.
121    ///
122    /// # Errors
123    pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
124        let len = unsafe { sys::i2d_X509(self.ptr, std::ptr::null_mut()) };
125        if len < 0 {
126            return Err(ErrorStack::drain());
127        }
128        let mut buf = vec![0u8; usize::try_from(len).unwrap_or(0)];
129        let mut out_ptr = buf.as_mut_ptr();
130        let written = unsafe { sys::i2d_X509(self.ptr, std::ptr::addr_of_mut!(out_ptr)) };
131        if written < 0 {
132            return Err(ErrorStack::drain());
133        }
134        buf.truncate(usize::try_from(written).unwrap_or(0));
135        Ok(buf)
136    }
137
138    /// Subject distinguished name (borrowed).
139    #[must_use]
140    pub fn subject_name(&self) -> X509Name<'_> {
141        // get0: does not increment ref count; valid while self is alive.
142        // OpenSSL 4.x made this return `*const`; cast is safe — we never
143        // mutate through the borrowed pointer.
144        let ptr = unsafe { sys::X509_get_subject_name(self.ptr) }.cast();
145        X509Name {
146            ptr,
147            _owner: PhantomData,
148        }
149    }
150
151    /// Issuer distinguished name (borrowed).
152    #[must_use]
153    pub fn issuer_name(&self) -> X509Name<'_> {
154        let ptr = unsafe { sys::X509_get_issuer_name(self.ptr) }.cast();
155        X509Name {
156            ptr,
157            _owner: PhantomData,
158        }
159    }
160
161    /// Serial number as a signed 64-bit integer.
162    ///
163    /// Returns `None` if the serial number is too large to fit in `i64`.
164    #[must_use]
165    pub fn serial_number(&self) -> Option<i64> {
166        let ai = unsafe { sys::X509_get0_serialNumber(self.ptr) };
167        if ai.is_null() {
168            return None;
169        }
170        let mut n: i64 = 0;
171        let rc = unsafe { sys::ASN1_INTEGER_get_int64(std::ptr::addr_of_mut!(n), ai) };
172        if rc == 1 {
173            Some(n)
174        } else {
175            None
176        }
177    }
178
179    /// Validity `notBefore` as a human-readable UTC string.
180    ///
181    /// The format is `"Mmm DD HH:MM:SS YYYY GMT"` (OpenSSL default).
182    /// Returns `None` if the field is absent or cannot be printed.
183    #[must_use]
184    pub fn not_before_str(&self) -> Option<String> {
185        let t = unsafe { sys::X509_get0_notBefore(self.ptr) };
186        asn1_time_to_str(t)
187    }
188
189    /// Validity `notAfter` as a human-readable UTC string.
190    ///
191    /// Returns `None` if the field is absent or cannot be printed.
192    #[must_use]
193    pub fn not_after_str(&self) -> Option<String> {
194        let t = unsafe { sys::X509_get0_notAfter(self.ptr) };
195        asn1_time_to_str(t)
196    }
197
198    /// Returns `true` if the current time is within `[notBefore, notAfter]`.
199    #[must_use]
200    pub fn is_valid_now(&self) -> bool {
201        let nb = unsafe { sys::X509_get0_notBefore(self.ptr) };
202        let na = unsafe { sys::X509_get0_notAfter(self.ptr) };
203        // X509_cmp_time(t, NULL) < 0 → t < now; > 0 → t > now.
204        unsafe {
205            sys::X509_cmp_time(nb, std::ptr::null_mut()) <= 0
206                && sys::X509_cmp_time(na, std::ptr::null_mut()) >= 0
207        }
208    }
209
210    /// Extract the public key (owned `Pkey<Public>`).
211    ///
212    /// Calls `X509_get_pubkey` — the returned key is independently reference-counted.
213    ///
214    /// # Errors
215    pub fn public_key(&self) -> Result<crate::pkey::Pkey<crate::pkey::Public>, ErrorStack> {
216        let ptr = unsafe { sys::X509_get_pubkey(self.ptr) };
217        if ptr.is_null() {
218            return Err(ErrorStack::drain());
219        }
220        Ok(unsafe { crate::pkey::Pkey::from_ptr(ptr) })
221    }
222
223    /// Verify this certificate was signed by `key`.
224    ///
225    /// Returns `Ok(true)` if the signature is valid, `Ok(false)` if not, or
226    /// `Err` on a fatal error.
227    ///
228    /// # Errors
229    pub fn verify(&self, key: &crate::pkey::Pkey<crate::pkey::Public>) -> Result<bool, ErrorStack> {
230        match unsafe { sys::X509_verify(self.ptr, key.as_ptr()) } {
231            1 => Ok(true),
232            0 => Ok(false),
233            _ => Err(ErrorStack::drain()),
234        }
235    }
236
237    /// Returns `true` if the certificate is self-signed.
238    #[must_use]
239    pub fn is_self_signed(&self) -> bool {
240        // verify_signature=0 → only check name match, not signature itself.
241        unsafe { sys::X509_self_signed(self.ptr, 0) == 1 }
242    }
243
244    /// Number of extensions in this certificate.
245    #[must_use]
246    pub fn extension_count(&self) -> usize {
247        let n = unsafe { sys::X509_get_ext_count(self.ptr) };
248        usize::try_from(n).unwrap_or(0)
249    }
250
251    /// Access extension by index (0-based).
252    ///
253    /// Returns `None` if `idx` is out of range.
254    #[must_use]
255    pub fn extension(&self, idx: usize) -> Option<X509Extension<'_>> {
256        let idx_i32 = i32::try_from(idx).ok()?;
257        // OpenSSL 4.x returns *const; cast is safe — borrowed, never mutated.
258        let ptr = unsafe { sys::X509_get_ext(self.ptr, idx_i32) }.cast::<sys::X509_EXTENSION>();
259        if ptr.is_null() {
260            None
261        } else {
262            Some(X509Extension {
263                ptr,
264                _owner: PhantomData,
265            })
266        }
267    }
268
269    /// Find the first extension with the given NID.
270    ///
271    /// Returns `None` if no such extension exists.
272    #[must_use]
273    pub fn extension_by_nid(&self, nid: i32) -> Option<X509Extension<'_>> {
274        let idx = unsafe { sys::X509_get_ext_by_NID(self.ptr, nid, -1) };
275        if idx < 0 {
276            return None;
277        }
278        let ptr = unsafe { sys::X509_get_ext(self.ptr, idx) }.cast::<sys::X509_EXTENSION>();
279        if ptr.is_null() {
280            None
281        } else {
282            Some(X509Extension {
283                ptr,
284                _owner: PhantomData,
285            })
286        }
287    }
288
289    /// Raw `X509*` pointer valid for the lifetime of `self`.
290    #[must_use]
291    #[allow(dead_code)] // used by ssl module added in the next phase
292    pub(crate) fn as_ptr(&self) -> *mut sys::X509 {
293        self.ptr
294    }
295}
296
297// ── X509Name — borrowed distinguished name ────────────────────────────────────
298
299/// A borrowed distinguished name (`X509_NAME*`) tied to its owning `X509`.
300pub struct X509Name<'cert> {
301    ptr: *mut sys::X509_NAME,
302    _owner: PhantomData<&'cert X509>,
303}
304
305impl X509Name<'_> {
306    /// Number of entries (RDN components) in the name.
307    #[must_use]
308    pub fn entry_count(&self) -> usize {
309        usize::try_from(unsafe { sys::X509_NAME_entry_count(self.ptr) }).unwrap_or(0)
310    }
311
312    /// Access an entry by index (0-based).
313    #[must_use]
314    pub fn entry(&self, idx: usize) -> Option<X509NameEntry<'_>> {
315        let idx_i32 = i32::try_from(idx).ok()?;
316        // OpenSSL 4.x returns *const; cast is safe — borrowed, never mutated.
317        let ptr =
318            unsafe { sys::X509_NAME_get_entry(self.ptr, idx_i32) }.cast::<sys::X509_NAME_ENTRY>();
319        if ptr.is_null() {
320            None
321        } else {
322            Some(X509NameEntry {
323                ptr,
324                _owner: PhantomData,
325            })
326        }
327    }
328
329    /// Format the entire name as a single-line string.
330    ///
331    /// Uses `X509_NAME_print_ex` with `XN_FLAG_COMPAT` (traditional
332    /// `/CN=.../O=.../` format).  Returns `None` on error.
333    #[must_use]
334    pub fn to_string(&self) -> Option<String> {
335        let mut bio = MemBio::new().ok()?;
336        // flags = 0 → XN_FLAG_COMPAT (old /CN=…/O=… format).
337        let n = unsafe { sys::X509_NAME_print_ex(bio.as_ptr(), self.ptr, 0, 0) };
338        if n < 0 {
339            return None;
340        }
341        String::from_utf8(bio.into_vec()).ok()
342    }
343}
344
345// ── X509NameEntry — one RDN component ────────────────────────────────────────
346
347/// A borrowed entry within an [`X509Name`].
348pub struct X509NameEntry<'name> {
349    ptr: *mut sys::X509_NAME_ENTRY,
350    _owner: PhantomData<&'name ()>,
351}
352
353impl X509NameEntry<'_> {
354    /// NID of this field (e.g. `NID_commonName = 13`).
355    #[must_use]
356    pub fn nid(&self) -> i32 {
357        let obj = unsafe { sys::X509_NAME_ENTRY_get_object(self.ptr) };
358        unsafe { sys::OBJ_obj2nid(obj) }
359    }
360
361    /// Raw DER-encoded value bytes of this entry.
362    ///
363    /// The slice is valid as long as the owning certificate is alive.
364    #[must_use]
365    pub fn data(&self) -> &[u8] {
366        let asn1 = unsafe { sys::X509_NAME_ENTRY_get_data(self.ptr) };
367        if asn1.is_null() {
368            return &[];
369        }
370        // SAFETY: asn1 is valid for 'name (guaranteed by self._owner PhantomData).
371        unsafe { asn1_string_data(asn1) }
372    }
373}
374
375// ── X509Extension — borrowed extension ───────────────────────────────────────
376
377/// A borrowed extension within an [`X509`] certificate.
378pub struct X509Extension<'cert> {
379    ptr: *mut sys::X509_EXTENSION,
380    _owner: PhantomData<&'cert X509>,
381}
382
383impl X509Extension<'_> {
384    /// NID of this extension (e.g. `NID_subject_key_identifier`).
385    #[must_use]
386    pub fn nid(&self) -> i32 {
387        let obj = unsafe { sys::X509_EXTENSION_get_object(self.ptr) };
388        unsafe { sys::OBJ_obj2nid(obj) }
389    }
390
391    /// Returns `true` if this extension is marked critical.
392    #[must_use]
393    pub fn is_critical(&self) -> bool {
394        unsafe { sys::X509_EXTENSION_get_critical(self.ptr) == 1 }
395    }
396
397    /// Raw DER-encoded value bytes.
398    ///
399    /// The slice is valid as long as the owning certificate is alive.
400    #[must_use]
401    pub fn data(&self) -> &[u8] {
402        let asn1 = unsafe { sys::X509_EXTENSION_get_data(self.ptr) };
403        if asn1.is_null() {
404            return &[];
405        }
406        // SAFETY: asn1 is valid for 'cert (guaranteed by self._owner PhantomData).
407        // ASN1_OCTET_STRING is typedef'd to ASN1_STRING — cast is safe.
408        unsafe { asn1_string_data(asn1.cast()) }
409    }
410}
411
412// ── X509NameOwned — mutable name for the builder ─────────────────────────────
413
414/// An owned, mutable distinguished name (`X509_NAME*`).
415///
416/// Pass to [`X509Builder::set_subject_name`] / [`X509Builder::set_issuer_name`].
417pub struct X509NameOwned {
418    ptr: *mut sys::X509_NAME,
419}
420
421impl X509NameOwned {
422    /// Create an empty distinguished name.
423    ///
424    /// # Errors
425    pub fn new() -> Result<Self, ErrorStack> {
426        let ptr = unsafe { sys::X509_NAME_new() };
427        if ptr.is_null() {
428            return Err(ErrorStack::drain());
429        }
430        Ok(X509NameOwned { ptr })
431    }
432
433    /// Append a field entry by short name (e.g. `c"CN"`, `c"O"`, `c"C"`).
434    ///
435    /// `value` is the UTF-8 field value.
436    ///
437    /// # Panics
438    ///
439    /// # Errors
440    ///
441    /// Returns `Err` if the field cannot be added, or if `value.len()` exceeds `i32::MAX`.
442    pub fn add_entry_by_txt(&mut self, field: &CStr, value: &[u8]) -> Result<(), ErrorStack> {
443        let len = i32::try_from(value.len()).map_err(|_| ErrorStack::drain())?;
444        // MBSTRING_UTF8 = 0x1000 → encode value as UTF-8.
445        let rc = unsafe {
446            sys::X509_NAME_add_entry_by_txt(
447                self.ptr,
448                field.as_ptr(),
449                0x1000, // MBSTRING_UTF8
450                value.as_ptr(),
451                len,
452                -1, // append
453                0,
454            )
455        };
456        if rc != 1 {
457            return Err(ErrorStack::drain());
458        }
459        Ok(())
460    }
461}
462
463impl Drop for X509NameOwned {
464    fn drop(&mut self) {
465        unsafe { sys::X509_NAME_free(self.ptr) };
466    }
467}
468
469// ── X509Builder — certificate builder ────────────────────────────────────────
470
471/// Builder for a new X.509 certificate.
472///
473/// ```ignore
474/// let mut name = X509NameOwned::new()?;
475/// name.add_entry_by_txt(c"CN", b"example.com")?;
476///
477/// let cert = X509Builder::new()?
478///     .set_version(2)?                    // X.509v3
479///     .set_serial_number(1)?
480///     .set_not_before_offset(0)?          // valid from now
481///     .set_not_after_offset(365 * 86400)? // valid for 1 year
482///     .set_subject_name(&name)?
483///     .set_issuer_name(&name)?            // self-signed
484///     .set_public_key(&pub_key)?
485///     .sign(&priv_key, None)?             // None → no digest (Ed25519)
486///     .build();
487/// ```
488pub struct X509Builder {
489    ptr: *mut sys::X509,
490}
491
492impl X509Builder {
493    /// Allocate a new, empty `X509` structure.
494    ///
495    /// # Errors
496    pub fn new() -> Result<Self, ErrorStack> {
497        let ptr = unsafe { sys::X509_new() };
498        if ptr.is_null() {
499            return Err(ErrorStack::drain());
500        }
501        Ok(X509Builder { ptr })
502    }
503
504    /// Set the X.509 version (0 = v1, 1 = v2, 2 = v3).
505    ///
506    /// # Errors
507    pub fn set_version(self, version: i64) -> Result<Self, ErrorStack> {
508        crate::ossl_call!(sys::X509_set_version(self.ptr, version))?;
509        Ok(self)
510    }
511
512    /// Set the serial number.
513    ///
514    /// # Errors
515    pub fn set_serial_number(self, n: i64) -> Result<Self, ErrorStack> {
516        let ai = unsafe { sys::ASN1_INTEGER_new() };
517        if ai.is_null() {
518            return Err(ErrorStack::drain());
519        }
520        crate::ossl_call!(sys::ASN1_INTEGER_set_int64(ai, n)).map_err(|e| {
521            unsafe { sys::ASN1_INTEGER_free(ai) };
522            e
523        })?;
524        let rc = unsafe { sys::X509_set_serialNumber(self.ptr, ai) };
525        unsafe { sys::ASN1_INTEGER_free(ai) };
526        if rc != 1 {
527            return Err(ErrorStack::drain());
528        }
529        Ok(self)
530    }
531
532    /// Set `notBefore` to `now + offset_secs`.
533    ///
534    /// # Errors
535    pub fn set_not_before_offset(self, offset_secs: i64) -> Result<Self, ErrorStack> {
536        let t = unsafe { sys::X509_getm_notBefore(self.ptr) };
537        if unsafe { sys::X509_gmtime_adj(t, offset_secs) }.is_null() {
538            return Err(ErrorStack::drain());
539        }
540        Ok(self)
541    }
542
543    /// Set `notAfter` to `now + offset_secs`.
544    ///
545    /// # Errors
546    pub fn set_not_after_offset(self, offset_secs: i64) -> Result<Self, ErrorStack> {
547        let t = unsafe { sys::X509_getm_notAfter(self.ptr) };
548        if unsafe { sys::X509_gmtime_adj(t, offset_secs) }.is_null() {
549            return Err(ErrorStack::drain());
550        }
551        Ok(self)
552    }
553
554    /// Set the subject distinguished name.
555    ///
556    /// # Errors
557    pub fn set_subject_name(self, name: &X509NameOwned) -> Result<Self, ErrorStack> {
558        crate::ossl_call!(sys::X509_set_subject_name(self.ptr, name.ptr))?;
559        Ok(self)
560    }
561
562    /// Set the issuer distinguished name.
563    ///
564    /// # Errors
565    pub fn set_issuer_name(self, name: &X509NameOwned) -> Result<Self, ErrorStack> {
566        crate::ossl_call!(sys::X509_set_issuer_name(self.ptr, name.ptr))?;
567        Ok(self)
568    }
569
570    /// Set the public key.
571    ///
572    /// # Errors
573    pub fn set_public_key<T: crate::pkey::HasPublic>(
574        self,
575        key: &crate::pkey::Pkey<T>,
576    ) -> Result<Self, ErrorStack> {
577        crate::ossl_call!(sys::X509_set_pubkey(self.ptr, key.as_ptr()))?;
578        Ok(self)
579    }
580
581    /// Sign the certificate.
582    ///
583    /// Pass `digest = None` for one-shot algorithms such as Ed25519.
584    /// For ECDSA or RSA, pass the appropriate digest (e.g. SHA-256).
585    ///
586    /// # Errors
587    pub fn sign(
588        self,
589        key: &crate::pkey::Pkey<crate::pkey::Private>,
590        digest: Option<&crate::digest::DigestAlg>,
591    ) -> Result<Self, ErrorStack> {
592        let md_ptr = digest.map_or(std::ptr::null(), crate::digest::DigestAlg::as_ptr);
593        // X509_sign returns the signature length (> 0) on success.
594        let rc = unsafe { sys::X509_sign(self.ptr, key.as_ptr(), md_ptr) };
595        if rc <= 0 {
596            return Err(ErrorStack::drain());
597        }
598        Ok(self)
599    }
600
601    /// Finalise and return the certificate.
602    #[must_use]
603    pub fn build(self) -> X509 {
604        let ptr = self.ptr;
605        std::mem::forget(self);
606        X509 { ptr }
607    }
608}
609
610impl Drop for X509Builder {
611    fn drop(&mut self) {
612        unsafe { sys::X509_free(self.ptr) };
613    }
614}
615
616// ── X509Store — trust store ────────────────────────────────────────────────────
617
618/// An OpenSSL certificate trust store (`X509_STORE*`).
619///
620/// Cloneable via `X509_STORE_up_ref`; wrapping in `Arc<X509Store>` is safe.
621pub struct X509Store {
622    ptr: *mut sys::X509_STORE,
623}
624
625unsafe impl Send for X509Store {}
626unsafe impl Sync for X509Store {}
627
628impl Clone for X509Store {
629    fn clone(&self) -> Self {
630        unsafe { sys::X509_STORE_up_ref(self.ptr) };
631        X509Store { ptr: self.ptr }
632    }
633}
634
635impl Drop for X509Store {
636    fn drop(&mut self) {
637        unsafe { sys::X509_STORE_free(self.ptr) };
638    }
639}
640
641impl X509Store {
642    /// Create an empty trust store.
643    ///
644    /// # Errors
645    pub fn new() -> Result<Self, ErrorStack> {
646        let ptr = unsafe { sys::X509_STORE_new() };
647        if ptr.is_null() {
648            return Err(ErrorStack::drain());
649        }
650        Ok(X509Store { ptr })
651    }
652
653    /// Add a trusted certificate to the store.
654    ///
655    /// The certificate's reference count is incremented internally.
656    ///
657    /// # Errors
658    pub fn add_cert(&mut self, cert: &X509) -> Result<(), ErrorStack> {
659        let rc = unsafe { sys::X509_STORE_add_cert(self.ptr, cert.ptr) };
660        if rc != 1 {
661            return Err(ErrorStack::drain());
662        }
663        Ok(())
664    }
665
666    /// Add a CRL to the store.
667    ///
668    /// # Errors
669    pub fn add_crl(&mut self, crl: &X509Crl) -> Result<(), ErrorStack> {
670        let rc = unsafe { sys::X509_STORE_add_crl(self.ptr, crl.ptr) };
671        if rc != 1 {
672            return Err(ErrorStack::drain());
673        }
674        Ok(())
675    }
676
677    /// Set verification flags (e.g. `X509_V_FLAG_CRL_CHECK`).
678    ///
679    /// # Errors
680    pub fn set_flags(&mut self, flags: u64) -> Result<(), ErrorStack> {
681        let rc = unsafe { sys::X509_STORE_set_flags(self.ptr, flags) };
682        if rc != 1 {
683            return Err(ErrorStack::drain());
684        }
685        Ok(())
686    }
687
688    /// Return the raw `X509_STORE*` pointer.
689    #[must_use]
690    pub(crate) fn as_ptr(&self) -> *mut sys::X509_STORE {
691        self.ptr
692    }
693}
694
695// ── X509StoreCtx — verification context ──────────────────────────────────────
696
697/// A chain-verification context (`X509_STORE_CTX*`).
698///
699/// Create with [`X509StoreCtx::new`], initialise with [`X509StoreCtx::init`],
700/// then call [`X509StoreCtx::verify`].
701pub struct X509StoreCtx {
702    ptr: *mut sys::X509_STORE_CTX,
703}
704
705impl Drop for X509StoreCtx {
706    fn drop(&mut self) {
707        unsafe { sys::X509_STORE_CTX_free(self.ptr) };
708    }
709}
710
711unsafe impl Send for X509StoreCtx {}
712
713impl X509StoreCtx {
714    /// Allocate a new, uninitialised verification context.
715    ///
716    /// # Errors
717    pub fn new() -> Result<Self, ErrorStack> {
718        let ptr = unsafe { sys::X509_STORE_CTX_new() };
719        if ptr.is_null() {
720            return Err(ErrorStack::drain());
721        }
722        Ok(X509StoreCtx { ptr })
723    }
724
725    /// Initialise the context for verifying `cert` against `store`.
726    ///
727    /// Call this before [`Self::verify`].
728    ///
729    /// # Errors
730    pub fn init(&mut self, store: &X509Store, cert: &X509) -> Result<(), ErrorStack> {
731        let rc = unsafe {
732            sys::X509_STORE_CTX_init(self.ptr, store.ptr, cert.ptr, std::ptr::null_mut())
733        };
734        if rc != 1 {
735            return Err(ErrorStack::drain());
736        }
737        Ok(())
738    }
739
740    /// Verify the certificate chain.
741    ///
742    /// Returns `Ok(true)` if the chain is valid, `Ok(false)` if not (call
743    /// [`Self::error`] to retrieve the error code), or `Err` on a fatal error.
744    ///
745    /// # Errors
746    pub fn verify(&mut self) -> Result<bool, ErrorStack> {
747        match unsafe { sys::X509_verify_cert(self.ptr) } {
748            1 => Ok(true),
749            0 => Ok(false),
750            _ => Err(ErrorStack::drain()),
751        }
752    }
753
754    /// OpenSSL verification error code after a failed [`Self::verify`].
755    ///
756    /// Returns 0 (`X509_V_OK`) if no error occurred.  See `<openssl/x509_vfy.h>`
757    /// for the full list of `X509_V_ERR_*` constants.
758    #[must_use]
759    pub fn error(&self) -> i32 {
760        unsafe { sys::X509_STORE_CTX_get_error(self.ptr) }
761    }
762
763    /// Collect the verified chain into a `Vec<X509>`.
764    ///
765    /// Only meaningful after a successful [`Self::verify`].  Returns an empty
766    /// `Vec` if the chain is not available.
767    #[must_use]
768    // i < n where n came from OPENSSL_sk_num (i32), so the i as i32 cast is safe.
769    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
770    pub fn chain(&self) -> Vec<X509> {
771        let stack = unsafe { sys::X509_STORE_CTX_get0_chain(self.ptr) };
772        if stack.is_null() {
773            return Vec::new();
774        }
775        let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
776        let n = usize::try_from(n).unwrap_or(0);
777        let mut out = Vec::with_capacity(n);
778        for i in 0..n {
779            let raw =
780                unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i as i32) };
781            if raw.is_null() {
782                continue;
783            }
784            // up_ref so each X509 in the Vec has its own reference.
785            let cert_ptr = raw.cast::<sys::X509>();
786            unsafe { sys::X509_up_ref(cert_ptr) };
787            out.push(X509 { ptr: cert_ptr });
788        }
789        out
790    }
791}
792
793// ── X509Crl — certificate revocation list ─────────────────────────────────────
794
795/// An X.509 certificate revocation list (`X509_CRL*`).
796///
797/// Cloneable via `X509_CRL_up_ref`.
798pub struct X509Crl {
799    ptr: *mut sys::X509_CRL,
800}
801
802unsafe impl Send for X509Crl {}
803unsafe impl Sync for X509Crl {}
804
805impl Clone for X509Crl {
806    fn clone(&self) -> Self {
807        unsafe { sys::X509_CRL_up_ref(self.ptr) };
808        X509Crl { ptr: self.ptr }
809    }
810}
811
812impl Drop for X509Crl {
813    fn drop(&mut self) {
814        unsafe { sys::X509_CRL_free(self.ptr) };
815    }
816}
817
818impl X509Crl {
819    /// Construct from a raw, owned `X509_CRL*`.
820    ///
821    /// # Safety
822    ///
823    /// `ptr` must be a valid, non-null `X509_CRL*` whose ownership is transferred.
824    pub(crate) unsafe fn from_ptr(ptr: *mut sys::X509_CRL) -> Self {
825        X509Crl { ptr }
826    }
827
828    /// Load a CRL from PEM bytes.
829    ///
830    /// # Errors
831    pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
832        let bio = MemBioBuf::new(pem)?;
833        let ptr = unsafe {
834            sys::PEM_read_bio_X509_CRL(
835                bio.as_ptr(),
836                std::ptr::null_mut(),
837                None,
838                std::ptr::null_mut(),
839            )
840        };
841        if ptr.is_null() {
842            return Err(ErrorStack::drain());
843        }
844        Ok(unsafe { Self::from_ptr(ptr) })
845    }
846
847    /// Load a CRL from DER bytes.
848    ///
849    /// # Errors
850    pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
851        let bio = MemBioBuf::new(der)?;
852        let ptr = unsafe { sys::d2i_X509_CRL_bio(bio.as_ptr(), std::ptr::null_mut()) };
853        if ptr.is_null() {
854            return Err(ErrorStack::drain());
855        }
856        Ok(unsafe { Self::from_ptr(ptr) })
857    }
858
859    /// Serialise the CRL to PEM.
860    ///
861    /// # Errors
862    pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack> {
863        let mut bio = MemBio::new()?;
864        let rc = unsafe { sys::PEM_write_bio_X509_CRL(bio.as_ptr(), self.ptr) };
865        if rc != 1 {
866            return Err(ErrorStack::drain());
867        }
868        Ok(bio.into_vec())
869    }
870
871    /// Serialise the CRL to DER.
872    ///
873    /// # Errors
874    pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
875        let mut bio = MemBio::new()?;
876        let rc = unsafe { sys::i2d_X509_CRL_bio(bio.as_ptr(), self.ptr) };
877        if rc != 1 {
878            return Err(ErrorStack::drain());
879        }
880        Ok(bio.into_vec())
881    }
882
883    /// Issuer distinguished name (borrowed).
884    #[must_use]
885    pub fn issuer_name(&self) -> X509Name<'_> {
886        let ptr = unsafe { sys::X509_CRL_get_issuer(self.ptr) };
887        X509Name {
888            ptr: ptr.cast(),
889            _owner: PhantomData,
890        }
891    }
892
893    /// `thisUpdate` field as a human-readable string.
894    #[must_use]
895    pub fn last_update_str(&self) -> Option<String> {
896        let t = unsafe { sys::X509_CRL_get0_lastUpdate(self.ptr) };
897        asn1_time_to_str(t)
898    }
899
900    /// `nextUpdate` field as a human-readable string.
901    #[must_use]
902    pub fn next_update_str(&self) -> Option<String> {
903        let t = unsafe { sys::X509_CRL_get0_nextUpdate(self.ptr) };
904        asn1_time_to_str(t)
905    }
906
907    /// Verify this CRL was signed by `key`.
908    ///
909    /// Returns `Ok(true)` if valid, `Ok(false)` if not.
910    ///
911    /// # Errors
912    pub fn verify(&self, key: &crate::pkey::Pkey<crate::pkey::Public>) -> Result<bool, ErrorStack> {
913        match unsafe { sys::X509_CRL_verify(self.ptr, key.as_ptr()) } {
914            1 => Ok(true),
915            0 => Ok(false),
916            _ => Err(ErrorStack::drain()),
917        }
918    }
919
920    /// Raw `X509_CRL*` pointer for use by internal APIs.
921    #[must_use]
922    #[allow(dead_code)]
923    pub(crate) fn as_ptr(&self) -> *mut sys::X509_CRL {
924        self.ptr
925    }
926}
927
928// ── Private helpers ───────────────────────────────────────────────────────────
929
930/// Convert an `ASN1_TIME*` to a human-readable string via `ASN1_TIME_print`.
931fn asn1_time_to_str(t: *const sys::ASN1_TIME) -> Option<String> {
932    if t.is_null() {
933        return None;
934    }
935    let mut bio = MemBio::new().ok()?;
936    let rc = unsafe { sys::ASN1_TIME_print(bio.as_ptr(), t) };
937    if rc != 1 {
938        return None;
939    }
940    String::from_utf8(bio.into_vec()).ok()
941}
942
943/// Extract the raw data bytes from an `ASN1_STRING*`.
944///
945/// # Safety
946///
947/// `asn1` must be a valid, non-null pointer for at least the duration of
948/// lifetime `'a`.  The caller is responsible for ensuring the returned slice
949/// does not outlive the owning ASN1 object.  Call sites bind the true lifetime
950/// through their own `&self` borrow and `PhantomData` fields.
951unsafe fn asn1_string_data<'a>(asn1: *const sys::ASN1_STRING) -> &'a [u8] {
952    let len = usize::try_from(sys::ASN1_STRING_length(asn1)).unwrap_or(0);
953    let ptr = sys::ASN1_STRING_get0_data(asn1);
954    if ptr.is_null() || len == 0 {
955        return &[];
956    }
957    // SAFETY: ptr is valid for `len` bytes; lifetime 'a is upheld by the caller.
958    std::slice::from_raw_parts(ptr, len)
959}
960
961// ── Tests ─────────────────────────────────────────────────────────────────────
962
963#[cfg(test)]
964mod tests {
965    use super::*;
966    use crate::pkey::{KeygenCtx, Pkey, Private, Public};
967
968    /// Build a self-signed Ed25519 certificate and run all read operations.
969    fn make_self_signed() -> (X509, Pkey<Private>, Pkey<Public>) {
970        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
971        let priv_key = kgen.generate().unwrap();
972        let pub_key = Pkey::<Public>::from(priv_key.clone());
973
974        let mut name = X509NameOwned::new().unwrap();
975        name.add_entry_by_txt(c"CN", b"Test Cert").unwrap();
976        name.add_entry_by_txt(c"O", b"Example Org").unwrap();
977
978        let cert = X509Builder::new()
979            .unwrap()
980            .set_version(2)
981            .unwrap()
982            .set_serial_number(1)
983            .unwrap()
984            .set_not_before_offset(0)
985            .unwrap()
986            .set_not_after_offset(365 * 86400)
987            .unwrap()
988            .set_subject_name(&name)
989            .unwrap()
990            .set_issuer_name(&name)
991            .unwrap()
992            .set_public_key(&pub_key)
993            .unwrap()
994            .sign(&priv_key, None)
995            .unwrap()
996            .build();
997
998        (cert, priv_key, pub_key)
999    }
1000
1001    #[test]
1002    fn build_and_verify_self_signed() {
1003        let (cert, _, pub_key) = make_self_signed();
1004        assert!(cert.verify(&pub_key).unwrap());
1005        assert!(cert.is_self_signed());
1006    }
1007
1008    #[test]
1009    fn pem_round_trip() {
1010        let (cert, _, _) = make_self_signed();
1011        let pem = cert.to_pem().unwrap();
1012        assert!(pem.starts_with(b"-----BEGIN CERTIFICATE-----"));
1013
1014        let cert2 = X509::from_pem(&pem).unwrap();
1015        // Both should verify with the same key.
1016        assert_eq!(cert.to_der().unwrap(), cert2.to_der().unwrap());
1017    }
1018
1019    #[test]
1020    fn der_round_trip() {
1021        let (cert, _, _) = make_self_signed();
1022        let der = cert.to_der().unwrap();
1023        assert!(!der.is_empty());
1024
1025        let cert2 = X509::from_der(&der).unwrap();
1026        assert_eq!(cert2.to_der().unwrap(), der);
1027    }
1028
1029    #[test]
1030    fn subject_name_entries() {
1031        let (cert, _, _) = make_self_signed();
1032        let name = cert.subject_name();
1033
1034        assert_eq!(name.entry_count(), 2);
1035
1036        // First entry: CN (NID 13)
1037        let e0 = name.entry(0).unwrap();
1038        assert_eq!(e0.nid(), 13); // NID_commonName
1039        assert!(!e0.data().is_empty());
1040
1041        // to_string should include both components.
1042        let s = name.to_string().unwrap();
1043        assert!(s.contains("Test Cert") || s.contains("CN=Test Cert"));
1044    }
1045
1046    #[test]
1047    fn serial_number() {
1048        let (cert, _, _) = make_self_signed();
1049        assert_eq!(cert.serial_number(), Some(1));
1050    }
1051
1052    #[test]
1053    fn validity_strings_present() {
1054        let (cert, _, _) = make_self_signed();
1055        let nb = cert.not_before_str().unwrap();
1056        let na = cert.not_after_str().unwrap();
1057        // Both should contain "GMT" as OpenSSL uses UTC.
1058        assert!(nb.contains("GMT"), "not_before_str = {nb:?}");
1059        assert!(na.contains("GMT"), "not_after_str  = {na:?}");
1060    }
1061
1062    #[test]
1063    fn is_valid_now() {
1064        let (cert, _, _) = make_self_signed();
1065        assert!(cert.is_valid_now());
1066    }
1067
1068    #[test]
1069    fn public_key_extraction() {
1070        let (cert, _, pub_key) = make_self_signed();
1071        let extracted = cert.public_key().unwrap();
1072        // Both keys should verify the same signature.
1073        assert!(extracted.is_a(c"ED25519"));
1074        assert_eq!(pub_key.bits(), extracted.bits());
1075    }
1076
1077    #[test]
1078    fn clone_cert() {
1079        let (cert, _, pub_key) = make_self_signed();
1080        let cert2 = cert.clone();
1081        // Both references should share the same content.
1082        assert_eq!(cert.to_der().unwrap(), cert2.to_der().unwrap());
1083        assert!(cert2.verify(&pub_key).unwrap());
1084    }
1085
1086    #[test]
1087    fn verify_fails_with_wrong_key() {
1088        let (cert, _, _) = make_self_signed();
1089        // Generate a fresh, unrelated key pair.
1090        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
1091        let other_priv = kgen.generate().unwrap();
1092        let other_pub = Pkey::<Public>::from(other_priv);
1093
1094        // cert was not signed by other_pub → should return Ok(false).
1095        assert!(!cert.verify(&other_pub).unwrap());
1096    }
1097
1098    // ── X509Store / X509StoreCtx tests ──────────────────────────────────────
1099
1100    #[test]
1101    fn x509_store_add_cert_and_verify() {
1102        let (cert, _, _) = make_self_signed();
1103
1104        let mut store = X509Store::new().unwrap();
1105        store.add_cert(&cert).unwrap();
1106
1107        let mut ctx = X509StoreCtx::new().unwrap();
1108        ctx.init(&store, &cert).unwrap();
1109        // Self-signed cert trusted by its own store → should verify.
1110        assert!(ctx.verify().unwrap());
1111    }
1112
1113    #[test]
1114    fn x509_store_verify_untrusted_fails() {
1115        let (cert, _, _) = make_self_signed();
1116        // Empty store (nothing trusted).
1117        let store = X509Store::new().unwrap();
1118
1119        let mut ctx = X509StoreCtx::new().unwrap();
1120        ctx.init(&store, &cert).unwrap();
1121        assert!(!ctx.verify().unwrap());
1122        // Error code must be non-zero.
1123        assert_ne!(ctx.error(), 0);
1124    }
1125
1126    #[test]
1127    fn x509_store_ctx_chain_populated_after_verify() {
1128        let (cert, _, _) = make_self_signed();
1129        let mut store = X509Store::new().unwrap();
1130        store.add_cert(&cert).unwrap();
1131
1132        let mut ctx = X509StoreCtx::new().unwrap();
1133        ctx.init(&store, &cert).unwrap();
1134        assert!(ctx.verify().unwrap());
1135
1136        let chain = ctx.chain();
1137        assert!(
1138            !chain.is_empty(),
1139            "verified chain should contain at least the leaf"
1140        );
1141    }
1142
1143    // ── X509Crl tests ────────────────────────────────────────────────────────
1144
1145    // A minimal CRL signed by an RSA/SHA-256 CA (generated via the openssl CLI).
1146    const TEST_CRL_PEM: &[u8] = b"\
1147-----BEGIN X509 CRL-----\n\
1148MIIBVjBAMA0GCSqGSIb3DQEBCwUAMBExDzANBgNVBAMMBlJTQSBDQRcNMjYwNDE1\n\
1149MTUwNDEzWhcNMjYwNTE1MTUwNDEzWjANBgkqhkiG9w0BAQsFAAOCAQEAi209u0hh\n\
1150Vz42YaqLplQwBoYCjtjETenl4xXRNcFOYU6Y+FmR66XNGkl9HbPClrz3hRMnbBYr\n\
1151OQJfWQOKS9lS0zpEI4qtlH/H1JBNGwiY32HMqf5HULn0w0ARvmoXR4NzsCecK22G\n\
1152gN61k5FCCpPY8HztsuoHMHAQ65W1WfBiTWu8ZH0nCCU0CA4MSaPZUiNt8/mJZzTG\n\
1153UwTGcZ/hcHQMpocBX40nE7ta5opcIpjG+q2uiCWhXwoqmYsLvdJ+Obw20bLirMHt\n\
1154UsmESTw5G+vcRCudoiSw89Z/jzsYq8yuFhRzF9kA/RtqCoQ+ylQSSH5hxzW2+bPd\n\
1155QPHivSGDiUhH6Q==\n\
1156-----END X509 CRL-----\n";
1157
1158    #[test]
1159    fn crl_pem_round_trip() {
1160        let crl = X509Crl::from_pem(TEST_CRL_PEM).unwrap();
1161        // issuer_name should be non-empty (RSA CA)
1162        let issuer = crl.issuer_name();
1163        assert!(issuer.entry_count() > 0);
1164        // last_update and next_update are present.
1165        assert!(crl.last_update_str().is_some());
1166        assert!(crl.next_update_str().is_some());
1167        // to_pem produces a valid CRL PEM.
1168        let pem = crl.to_pem().unwrap();
1169        assert!(pem.starts_with(b"-----BEGIN X509 CRL-----"));
1170    }
1171
1172    #[test]
1173    fn crl_der_round_trip() {
1174        let crl = X509Crl::from_pem(TEST_CRL_PEM).unwrap();
1175        let der = crl.to_der().unwrap();
1176        assert!(!der.is_empty());
1177        let crl2 = X509Crl::from_der(&der).unwrap();
1178        assert_eq!(crl2.to_der().unwrap(), der);
1179    }
1180
1181    #[test]
1182    fn crl_clone() {
1183        let crl = X509Crl::from_pem(TEST_CRL_PEM).unwrap();
1184        let crl2 = crl.clone();
1185        assert_eq!(crl.to_der().unwrap(), crl2.to_der().unwrap());
1186    }
1187}