native_ossl/
pkcs12.rs

1//! PKCS#12 / PFX bundle — parsing and creation.
2//!
3//! A PKCS#12 file bundles a private key, its end-entity certificate, and an
4//! optional chain of CA certificates into a single password-protected blob.
5//!
6//! # Example
7//!
8//! ```ignore
9//! let der = std::fs::read("bundle.p12").unwrap();
10//! let p12 = Pkcs12::from_der(&der).unwrap();
11//! let (key, cert, chain) = p12.parse("secret").unwrap();
12//! ```
13
14use crate::bio::{MemBio, MemBioBuf};
15use crate::error::ErrorStack;
16use native_ossl_sys as sys;
17use std::ffi::CString;
18
19// ── Pkcs12 ────────────────────────────────────────────────────────────────────
20
21/// A PKCS#12 / PFX bundle (`PKCS12*`).
22///
23/// Load from DER with [`Pkcs12::from_der`] or create with [`Pkcs12::create`].
24pub struct Pkcs12 {
25    ptr: *mut sys::PKCS12,
26}
27
28unsafe impl Send for Pkcs12 {}
29unsafe impl Sync for Pkcs12 {}
30
31impl Drop for Pkcs12 {
32    fn drop(&mut self) {
33        unsafe { sys::PKCS12_free(self.ptr) };
34    }
35}
36
37impl Pkcs12 {
38    /// Allocate a new, empty `PKCS12` structure.
39    ///
40    /// Returns an initialised-but-empty bundle. Use this when building a PKCS#12
41    /// structure field-by-field via the raw OpenSSL API. For the common case of
42    /// creating a complete bundle from a key and certificate, prefer [`Pkcs12::create`].
43    ///
44    /// # Errors
45    ///
46    /// Returns `Err` if OpenSSL cannot allocate the structure.
47    pub fn new() -> Result<Self, ErrorStack> {
48        // SAFETY:
49        // - PKCS12_new() takes no arguments and returns a new heap allocation
50        // - the returned pointer is non-null on success; null on allocation failure
51        // - ownership is fully transferred to this Pkcs12 value; Drop calls PKCS12_free
52        let ptr = unsafe { sys::PKCS12_new() };
53        if ptr.is_null() {
54            return Err(ErrorStack::drain());
55        }
56        Ok(Pkcs12 { ptr })
57    }
58
59    /// Load a PKCS#12 bundle from DER-encoded bytes.
60    ///
61    /// # Errors
62    pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
63        let bio = MemBioBuf::new(der)?;
64        let ptr = unsafe { sys::d2i_PKCS12_bio(bio.as_ptr(), std::ptr::null_mut()) };
65        if ptr.is_null() {
66            return Err(ErrorStack::drain());
67        }
68        Ok(Pkcs12 { ptr })
69    }
70
71    /// Serialise the bundle to DER.
72    ///
73    /// # Errors
74    pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
75        let mut bio = MemBio::new()?;
76        let rc = unsafe { sys::i2d_PKCS12_bio(bio.as_ptr(), self.ptr) };
77        if rc != 1 {
78            return Err(ErrorStack::drain());
79        }
80        Ok(bio.into_vec())
81    }
82
83    /// Parse the bundle, returning the private key, end-entity certificate,
84    /// and any additional CA certificates.
85    ///
86    /// `password` is the MAC / encryption password.  Pass `""` for an
87    /// unencrypted bundle.
88    ///
89    /// # Errors
90    pub fn parse(
91        &self,
92        password: &str,
93    ) -> Result<
94        (
95            crate::pkey::Pkey<crate::pkey::Private>,
96            crate::x509::X509,
97            Vec<crate::x509::X509>,
98        ),
99        ErrorStack,
100    > {
101        let pass = CString::new(password).map_err(|_| ErrorStack::drain())?;
102
103        let mut pkey_ptr: *mut sys::EVP_PKEY = std::ptr::null_mut();
104        let mut cert_ptr: *mut sys::X509 = std::ptr::null_mut();
105        let mut ca_ptr: *mut sys::stack_st_X509 = std::ptr::null_mut();
106
107        let rc = unsafe {
108            sys::PKCS12_parse(
109                self.ptr,
110                pass.as_ptr(),
111                std::ptr::addr_of_mut!(pkey_ptr),
112                std::ptr::addr_of_mut!(cert_ptr),
113                std::ptr::addr_of_mut!(ca_ptr),
114            )
115        };
116        if rc != 1 {
117            return Err(ErrorStack::drain());
118        }
119
120        if pkey_ptr.is_null() || cert_ptr.is_null() {
121            // Free anything that was allocated before returning the error.
122            if !pkey_ptr.is_null() {
123                unsafe { sys::EVP_PKEY_free(pkey_ptr) };
124            }
125            if !cert_ptr.is_null() {
126                unsafe { sys::X509_free(cert_ptr) };
127            }
128            free_x509_stack(ca_ptr);
129            return Err(ErrorStack::drain());
130        }
131
132        let key = unsafe { crate::pkey::Pkey::from_ptr(pkey_ptr) };
133        let cert = unsafe { crate::x509::X509::from_ptr(cert_ptr) };
134        let ca = drain_x509_stack(ca_ptr);
135        Ok((key, cert, ca))
136    }
137
138    /// Create a PKCS#12 bundle from a private key and certificate.
139    ///
140    /// - `password`: MAC / encryption passphrase.
141    /// - `name`:     Friendly name stored in the bundle (e.g. the subject CN).
142    /// - `ca`:       Optional slice of additional CA certificates.
143    ///
144    /// Uses AES-256-CBC for key encryption and SHA-256 for the MAC
145    /// (`nid_key = 0`, `nid_cert = 0` → OpenSSL defaults).
146    ///
147    /// # Errors
148    pub fn create(
149        password: &str,
150        name: &str,
151        key: &crate::pkey::Pkey<crate::pkey::Private>,
152        cert: &crate::x509::X509,
153        ca: &[crate::x509::X509],
154    ) -> Result<Self, ErrorStack> {
155        // Build the CA stack if needed.
156        let ca_stack = if ca.is_empty() {
157            std::ptr::null_mut()
158        } else {
159            build_x509_stack(ca)?
160        };
161
162        let pass = CString::new(password).map_err(|_| ErrorStack::drain())?;
163        let name = CString::new(name).map_err(|_| ErrorStack::drain())?;
164
165        let ptr = unsafe {
166            sys::PKCS12_create_ex(
167                pass.as_ptr(),
168                name.as_ptr(),
169                key.as_ptr(),
170                cert.as_ptr(),
171                ca_stack,
172                0,                    // nid_key  → default
173                0,                    // nid_cert → default
174                0,                    // iter     → default (2048)
175                0,                    // mac_iter → default (1)
176                0,                    // keytype  → default
177                std::ptr::null_mut(), // libctx  → global
178                std::ptr::null(),     // propq   → none
179            )
180        };
181
182        // Free the helper stack regardless of outcome.
183        if !ca_stack.is_null() {
184            unsafe {
185                sys::OPENSSL_sk_free(ca_stack.cast::<sys::OPENSSL_STACK>());
186            }
187        }
188
189        if ptr.is_null() {
190            return Err(ErrorStack::drain());
191        }
192        Ok(Pkcs12 { ptr })
193    }
194}
195
196// ── Stack helpers (private) ───────────────────────────────────────────────────
197
198/// Drain a `STACK_OF(X509)*` into a `Vec<X509>`, freeing the stack wrapper.
199///
200/// Takes ownership of the stack pointer (calls `OPENSSL_sk_free` after
201/// draining).  Each `X509*` in the stack is transferred into an `X509` value
202/// (no extra `up_ref`).
203fn drain_x509_stack(stack: *mut sys::stack_st_X509) -> Vec<crate::x509::X509> {
204    if stack.is_null() {
205        return Vec::new();
206    }
207    let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
208    let n = usize::try_from(n).unwrap_or(0);
209    let mut out = Vec::with_capacity(n);
210    for i in 0..n {
211        // i < n, and n came from sk_num() which returns i32, so i fits.
212        #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
213        let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i as i32) };
214        if !raw.is_null() {
215            // Transfer ownership — no up_ref; each element is owned by the stack.
216            out.push(unsafe { crate::x509::X509::from_ptr(raw.cast::<sys::X509>()) });
217        }
218    }
219    // Free the stack wrapper only (elements were moved out above).
220    unsafe { sys::OPENSSL_sk_free(stack.cast::<sys::OPENSSL_STACK>()) };
221    out
222}
223
224/// Free a `STACK_OF(X509)*` and all elements via `X509_free`.
225fn free_x509_stack(stack: *mut sys::stack_st_X509) {
226    if stack.is_null() {
227        return;
228    }
229    let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
230    for i in 0..n {
231        let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i) };
232        if !raw.is_null() {
233            unsafe { sys::X509_free(raw.cast::<sys::X509>()) };
234        }
235    }
236    unsafe { sys::OPENSSL_sk_free(stack.cast::<sys::OPENSSL_STACK>()) };
237}
238
239/// Build a new `STACK_OF(X509)*` from a slice, up-ref'ing each cert.
240///
241/// The caller is responsible for freeing the returned stack with
242/// `OPENSSL_sk_free` (not `pop_free`); `PKCS12_create_ex` copies the certs
243/// internally and does not consume the stack.
244fn build_x509_stack(certs: &[crate::x509::X509]) -> Result<*mut sys::stack_st_X509, ErrorStack> {
245    // OPENSSL_STACK and stack_st_X509 are layout-compatible — the typed stacks
246    // are just OPENSSL_STACK wrapped in a newtype macro.
247    let raw = unsafe { sys::OPENSSL_sk_new_null() };
248    if raw.is_null() {
249        return Err(ErrorStack::drain());
250    }
251    for cert in certs {
252        // up_ref so the stack holds an independent reference.
253        unsafe { sys::X509_up_ref(cert.as_ptr()) };
254        let rc = unsafe { sys::OPENSSL_sk_push(raw, cert.as_ptr().cast::<std::ffi::c_void>()) };
255        if rc == 0 {
256            // Push failed: free the up-ref we just took and the stack.
257            unsafe { sys::X509_free(cert.as_ptr()) };
258            // Free each already-pushed cert.
259            let n = unsafe { sys::OPENSSL_sk_num(raw) };
260            for i in 0..n {
261                let p = unsafe { sys::OPENSSL_sk_value(raw, i) };
262                if !p.is_null() {
263                    unsafe { sys::X509_free(p.cast::<sys::X509>()) };
264                }
265            }
266            unsafe { sys::OPENSSL_sk_free(raw) };
267            return Err(ErrorStack::drain());
268        }
269    }
270    Ok(raw.cast::<sys::stack_st_X509>())
271}
272
273// ── Tests ─────────────────────────────────────────────────────────────────────
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278    use crate::pkey::{KeygenCtx, Pkey, Private, Public};
279    use crate::x509::{X509Builder, X509NameOwned};
280
281    fn make_self_signed() -> (crate::x509::X509, Pkey<Private>) {
282        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
283        let priv_key = kgen.generate().unwrap();
284        let pub_key = Pkey::<Public>::from(priv_key.clone());
285
286        let mut name = X509NameOwned::new().unwrap();
287        name.add_entry_by_txt(c"CN", b"PKCS12 Test").unwrap();
288
289        let cert = X509Builder::new()
290            .unwrap()
291            .set_version(2)
292            .unwrap()
293            .set_serial_number(1)
294            .unwrap()
295            .set_not_before_offset(0)
296            .unwrap()
297            .set_not_after_offset(365 * 86400)
298            .unwrap()
299            .set_subject_name(&name)
300            .unwrap()
301            .set_issuer_name(&name)
302            .unwrap()
303            .set_public_key(&pub_key)
304            .unwrap()
305            .sign(&priv_key, None)
306            .unwrap()
307            .build();
308
309        (cert, priv_key)
310    }
311
312    #[test]
313    fn pkcs12_new_is_ok() {
314        // Verify that Pkcs12::new() returns Ok and the resulting object can be dropped
315        // without a crash (i.e., PKCS12_free is called on a valid allocation).
316        let p12 = Pkcs12::new().expect("PKCS12_new should succeed");
317        drop(p12);
318    }
319
320    #[test]
321    fn pkcs12_create_and_parse_roundtrip() {
322        let (cert, priv_key) = make_self_signed();
323
324        let p12 = Pkcs12::create("testpass", "test", &priv_key, &cert, &[]).unwrap();
325        let der = p12.to_der().unwrap();
326        assert!(!der.is_empty());
327
328        // Parse back.
329        let p12b = Pkcs12::from_der(&der).unwrap();
330        let (key2, cert2, ca2) = p12b.parse("testpass").unwrap();
331
332        // Key type should match.
333        assert!(key2.is_a(c"ED25519"));
334        // Certificate subject should be intact.
335        let subj = cert2.subject_name().to_string().unwrap();
336        assert!(subj.contains("PKCS12 Test"));
337        // No extra CA certs.
338        assert!(ca2.is_empty());
339    }
340
341    #[test]
342    fn pkcs12_der_roundtrip() {
343        let (cert, priv_key) = make_self_signed();
344        let p12 = Pkcs12::create("pass", "n", &priv_key, &cert, &[]).unwrap();
345        let der1 = p12.to_der().unwrap();
346        let p12b = Pkcs12::from_der(&der1).unwrap();
347        let der2 = p12b.to_der().unwrap();
348        assert_eq!(der1, der2);
349    }
350
351    #[test]
352    fn pkcs12_wrong_password_fails() {
353        let (cert, priv_key) = make_self_signed();
354        let p12 = Pkcs12::create("rightpass", "n", &priv_key, &cert, &[]).unwrap();
355        let der = p12.to_der().unwrap();
356        let p12b = Pkcs12::from_der(&der).unwrap();
357        assert!(p12b.parse("wrongpass").is_err());
358    }
359}