native-ossl 0.1.1

Native Rust idiomatic bindings to OpenSSL
Documentation
//! `LibCtx` — `OSSL_LIB_CTX` wrapper and `Provider` — `OSSL_PROVIDER` wrapper.
//!
//! Most callers use the global default library context and never need this module.
//! Create an explicit context when:
//! - FIPS mode is required (load `"fips"` + `"base"` providers).
//! - Per-process isolation of algorithm state is needed.
//!
//! ## `Arc<LibCtx>` ownership
//!
//! OpenSSL does not expose `OSSL_LIB_CTX_up_ref`.  Rust manages the lifetime
//! via `Arc<LibCtx>`.  Algorithm descriptors that use an explicit context store
//! `Option<Arc<LibCtx>>` to extend its lifetime.

use crate::error::ErrorStack;
use native_ossl_sys as sys;

// ── LibCtx ────────────────────────────────────────────────────────────────────

/// An OpenSSL library context (`OSSL_LIB_CTX*`).
///
/// Wrap in `Arc<LibCtx>` before passing to algorithm descriptors; they will
/// clone the `Arc` to keep the context alive.
#[derive(Debug)]
pub struct LibCtx {
    ptr: *mut sys::OSSL_LIB_CTX,
    /// When `false` this `LibCtx` was created from an externally-owned pointer
    /// and must **not** call `OSSL_LIB_CTX_free` on drop.
    owned: bool,
}

impl LibCtx {
    /// Create a new, empty library context.
    ///
    /// No providers are loaded by default.  Call `load_provider` at least once
    /// before using algorithms.
    ///
    /// # Errors
    ///
    /// Returns `Err` if OpenSSL cannot allocate the context.
    pub fn new() -> Result<Self, ErrorStack> {
        let ptr = unsafe { sys::OSSL_LIB_CTX_new() };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(LibCtx { ptr, owned: true })
    }

    /// Load a provider into this library context.
    ///
    /// Common provider names:
    /// - `c"default"` — standard algorithms.
    /// - `c"fips"` — FIPS 140-3 validated algorithms (requires OpenSSL FIPS module).
    /// - `c"base"` — must be loaded alongside `"fips"` for PEM/DER encoders.
    ///
    /// The returned `Provider` keeps the provider loaded until dropped.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the provider cannot be loaded.
    pub fn load_provider(&self, name: &std::ffi::CStr) -> Result<Provider, ErrorStack> {
        let ptr = unsafe { sys::OSSL_PROVIDER_load(self.ptr, name.as_ptr()) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(Provider { ptr })
    }

    /// Return the raw `OSSL_LIB_CTX*` pointer.  Valid while `self` is alive.
    #[must_use]
    pub fn as_ptr(&self) -> *mut sys::OSSL_LIB_CTX {
        self.ptr
    }

    /// Wrap a raw `OSSL_LIB_CTX*` that is owned and managed externally.
    ///
    /// The resulting `LibCtx` will NOT call `OSSL_LIB_CTX_free` when dropped.
    /// Use this only when the raw pointer's lifetime is guaranteed to exceed the
    /// `LibCtx` (e.g. a context received from a FIPS provider callback).
    ///
    /// # Safety
    ///
    /// The caller must ensure that `ptr` is a valid, non-null `OSSL_LIB_CTX*`
    /// that remains valid for as long as the returned `LibCtx` is alive.
    pub unsafe fn from_raw_unowned(ptr: *mut sys::OSSL_LIB_CTX) -> Self {
        LibCtx { ptr, owned: false }
    }
}

impl Drop for LibCtx {
    fn drop(&mut self) {
        if self.owned {
            unsafe { sys::OSSL_LIB_CTX_free(self.ptr) };
        }
    }
}

// SAFETY: `OSSL_LIB_CTX` is designed to be shared across threads after setup.
unsafe impl Send for LibCtx {}
unsafe impl Sync for LibCtx {}

// ── Provider ──────────────────────────────────────────────────────────────────

/// A loaded OpenSSL provider (`OSSL_PROVIDER*`).
///
/// The provider remains loaded until this value is dropped.  Keep it alive for
/// the lifetime of any algorithm descriptors that use the associated `LibCtx`.
pub struct Provider {
    ptr: *mut sys::OSSL_PROVIDER,
}

impl Drop for Provider {
    fn drop(&mut self) {
        unsafe { sys::OSSL_PROVIDER_unload(self.ptr) };
    }
}

// SAFETY: `OSSL_PROVIDER*` is managed by the library context thread-safely.
unsafe impl Send for Provider {}
unsafe impl Sync for Provider {}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::Arc;

    #[test]
    fn create_and_drop() {
        let ctx = LibCtx::new().unwrap();
        drop(ctx);
    }

    #[test]
    fn load_default_provider() {
        let ctx = LibCtx::new().unwrap();
        let _prov = ctx.load_provider(c"default").unwrap();
        // Provider unloaded when `_prov` drops.
    }

    #[test]
    fn arc_shared_context() {
        let ctx = Arc::new(LibCtx::new().unwrap());
        let _prov = ctx.load_provider(c"default").unwrap();
        let ctx2 = Arc::clone(&ctx);
        drop(ctx);
        // ctx2 keeps the context alive; prov is still valid.
        drop(ctx2);
    }
}