native_ossl/lib_ctx.rs
1//! `LibCtx` — `OSSL_LIB_CTX` wrapper and `Provider` — `OSSL_PROVIDER` wrapper.
2//!
3//! Most callers use the global default library context and never need this module.
4//! Create an explicit context when:
5//! - FIPS mode is required (load `"fips"` + `"base"` providers).
6//! - Per-process isolation of algorithm state is needed.
7//!
8//! ## `Arc<LibCtx>` ownership
9//!
10//! OpenSSL does not expose `OSSL_LIB_CTX_up_ref`. Rust manages the lifetime
11//! via `Arc<LibCtx>`. Algorithm descriptors that use an explicit context store
12//! `Option<Arc<LibCtx>>` to extend its lifetime.
13
14use crate::error::ErrorStack;
15use native_ossl_sys as sys;
16use std::ffi::CString;
17use std::sync::atomic::{AtomicU64, Ordering};
18
19static PKCS11_CFG_COUNTER: AtomicU64 = AtomicU64::new(0);
20
21// ── LibCtx ────────────────────────────────────────────────────────────────────
22
23/// An OpenSSL library context (`OSSL_LIB_CTX*`).
24///
25/// Wrap in `Arc<LibCtx>` before passing to algorithm descriptors; they will
26/// clone the `Arc` to keep the context alive.
27#[derive(Debug)]
28pub struct LibCtx {
29 ptr: *mut sys::OSSL_LIB_CTX,
30 /// When `false` this `LibCtx` was created from an externally-owned pointer
31 /// and must **not** call `OSSL_LIB_CTX_free` on drop.
32 owned: bool,
33}
34
35impl LibCtx {
36 /// Create a new, empty library context.
37 ///
38 /// No providers are loaded by default. Call `load_provider` at least once
39 /// before using algorithms.
40 ///
41 /// # Errors
42 ///
43 /// Returns `Err` if OpenSSL cannot allocate the context.
44 pub fn new() -> Result<Self, ErrorStack> {
45 let ptr = unsafe { sys::OSSL_LIB_CTX_new() };
46 if ptr.is_null() {
47 return Err(ErrorStack::drain());
48 }
49 Ok(LibCtx { ptr, owned: true })
50 }
51
52 /// Load a provider into this library context.
53 ///
54 /// Common provider names:
55 /// - `c"default"` — standard algorithms.
56 /// - `c"fips"` — FIPS 140-3 validated algorithms (requires OpenSSL FIPS module).
57 /// - `c"base"` — must be loaded alongside `"fips"` for PEM/DER encoders.
58 ///
59 /// The returned `Provider` keeps the provider loaded until dropped.
60 ///
61 /// # Errors
62 ///
63 /// Returns `Err` if the provider cannot be loaded.
64 pub fn load_provider(&self, name: &std::ffi::CStr) -> Result<Provider, ErrorStack> {
65 let ptr = unsafe { sys::OSSL_PROVIDER_load(self.ptr, name.as_ptr()) };
66 if ptr.is_null() {
67 return Err(ErrorStack::drain());
68 }
69 Ok(Provider { ptr })
70 }
71
72 /// Return the raw `OSSL_LIB_CTX*` pointer. Valid while `self` is alive.
73 #[must_use]
74 pub fn as_ptr(&self) -> *mut sys::OSSL_LIB_CTX {
75 self.ptr
76 }
77
78 /// Load an `openssl.cnf`-format configuration file into this context.
79 ///
80 /// Providers and algorithm configuration declared in the file are activated
81 /// within this context only — the global default context is not affected.
82 ///
83 /// # Errors
84 ///
85 /// Returns `Err` if `path` contains a null byte, the file cannot be read,
86 /// or the configuration is syntactically invalid.
87 pub fn load_config(&self, path: &std::path::Path) -> Result<(), ErrorStack> {
88 let path_str = path.to_str().ok_or_else(ErrorStack::drain)?;
89 let path_cs = CString::new(path_str).map_err(|_| ErrorStack::drain())?;
90 // SAFETY: self.ptr is valid for the duration of this call;
91 // path_cs is NUL-terminated and lives through the FFI boundary.
92 let rc = unsafe { sys::OSSL_LIB_CTX_load_config(self.ptr, path_cs.as_ptr()) };
93 if rc == 1 {
94 Ok(())
95 } else {
96 Err(ErrorStack::drain())
97 }
98 }
99
100 /// Load and activate the `pkcs11` provider, pointing it at `module`.
101 ///
102 /// Writes a minimal `openssl.cnf` snippet to a temporary file, calls
103 /// [`load_config`](Self::load_config) on it, then loads the `pkcs11`
104 /// provider into this context. The temporary file is deleted before this
105 /// function returns.
106 ///
107 /// The returned [`Provider`] keeps the provider active until dropped.
108 ///
109 /// # Errors
110 ///
111 /// Returns `Err` if `module` cannot be represented as a valid path string,
112 /// if the temporary file cannot be written, or if the provider fails to load.
113 pub fn load_pkcs11_provider(&self, module: &std::path::Path) -> Result<Provider, ErrorStack> {
114 let module_str = module.to_str().ok_or_else(ErrorStack::drain)?;
115 let config = format!(
116 "openssl_conf = openssl_init\n\
117 [openssl_init]\n\
118 providers = provider_sect\n\
119 [provider_sect]\n\
120 pkcs11 = pkcs11_sect\n\
121 default = default_sect\n\
122 [pkcs11_sect]\n\
123 module = pkcs11-provider.so\n\
124 pkcs11-module-path = {module_str}\n\
125 activate = 1\n\
126 [default_sect]\n\
127 activate = 1\n"
128 );
129 // Write config to a uniquely-named temp file, load it, then remove it.
130 // Using process ID + a counter ensures uniqueness in concurrent tests.
131 let seq = PKCS11_CFG_COUNTER.fetch_add(1, Ordering::Relaxed);
132 let tmp_path = std::env::temp_dir().join(format!(
133 "native-ossl-pkcs11-{}-{seq}.cnf",
134 std::process::id()
135 ));
136 std::fs::write(&tmp_path, config.as_bytes()).map_err(|_| ErrorStack::drain())?;
137 let result = self.load_config(&tmp_path);
138 let _ = std::fs::remove_file(&tmp_path);
139 result?;
140 self.load_provider(c"pkcs11")
141 }
142
143 /// Wrap a raw `OSSL_LIB_CTX*` that is owned and managed externally.
144 ///
145 /// The resulting `LibCtx` will NOT call `OSSL_LIB_CTX_free` when dropped.
146 /// Use this only when the raw pointer's lifetime is guaranteed to exceed the
147 /// `LibCtx` (e.g. a context received from a FIPS provider callback).
148 ///
149 /// # Safety
150 ///
151 /// The caller must ensure that `ptr` is a valid, non-null `OSSL_LIB_CTX*`
152 /// that remains valid for as long as the returned `LibCtx` is alive.
153 pub unsafe fn from_raw_unowned(ptr: *mut sys::OSSL_LIB_CTX) -> Self {
154 LibCtx { ptr, owned: false }
155 }
156}
157
158impl Drop for LibCtx {
159 fn drop(&mut self) {
160 if self.owned {
161 unsafe { sys::OSSL_LIB_CTX_free(self.ptr) };
162 }
163 }
164}
165
166// SAFETY: `OSSL_LIB_CTX` is designed to be shared across threads after setup.
167unsafe impl Send for LibCtx {}
168unsafe impl Sync for LibCtx {}
169
170// ── Provider ──────────────────────────────────────────────────────────────────
171
172/// A loaded OpenSSL provider (`OSSL_PROVIDER*`).
173///
174/// The provider remains loaded until this value is dropped. Keep it alive for
175/// the lifetime of any algorithm descriptors that use the associated `LibCtx`.
176pub struct Provider {
177 ptr: *mut sys::OSSL_PROVIDER,
178}
179
180impl Drop for Provider {
181 fn drop(&mut self) {
182 unsafe { sys::OSSL_PROVIDER_unload(self.ptr) };
183 }
184}
185
186// SAFETY: `OSSL_PROVIDER*` is managed by the library context thread-safely.
187unsafe impl Send for Provider {}
188unsafe impl Sync for Provider {}
189
190// ── Tests ─────────────────────────────────────────────────────────────────────
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use std::sync::Arc;
196
197 #[test]
198 fn create_and_drop() {
199 let ctx = LibCtx::new().unwrap();
200 drop(ctx);
201 }
202
203 #[test]
204 fn load_default_provider() {
205 let ctx = LibCtx::new().unwrap();
206 let _prov = ctx.load_provider(c"default").unwrap();
207 // Provider unloaded when `_prov` drops.
208 }
209
210 #[test]
211 fn arc_shared_context() {
212 let ctx = Arc::new(LibCtx::new().unwrap());
213 let _prov = ctx.load_provider(c"default").unwrap();
214 let ctx2 = Arc::clone(&ctx);
215 drop(ctx);
216 // ctx2 keeps the context alive; prov is still valid.
217 drop(ctx2);
218 }
219}