native-ossl 0.1.1

Native Rust idiomatic bindings to OpenSSL
Documentation
//! PKCS#12 / PFX bundle — parsing and creation.
//!
//! A PKCS#12 file bundles a private key, its end-entity certificate, and an
//! optional chain of CA certificates into a single password-protected blob.
//!
//! # Example
//!
//! ```ignore
//! let der = std::fs::read("bundle.p12").unwrap();
//! let p12 = Pkcs12::from_der(&der).unwrap();
//! let (key, cert, chain) = p12.parse("secret").unwrap();
//! ```

use crate::bio::{MemBio, MemBioBuf};
use crate::error::ErrorStack;
use native_ossl_sys as sys;
use std::ffi::CString;

// ── Pkcs12 ────────────────────────────────────────────────────────────────────

/// A PKCS#12 / PFX bundle (`PKCS12*`).
///
/// Load from DER with [`Pkcs12::from_der`] or create with [`Pkcs12::create`].
pub struct Pkcs12 {
    ptr: *mut sys::PKCS12,
}

unsafe impl Send for Pkcs12 {}
unsafe impl Sync for Pkcs12 {}

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

impl Pkcs12 {
    /// Load a PKCS#12 bundle from DER-encoded bytes.
    ///
    /// # Errors
    pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
        let bio = MemBioBuf::new(der)?;
        let ptr = unsafe { sys::d2i_PKCS12_bio(bio.as_ptr(), std::ptr::null_mut()) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(Pkcs12 { ptr })
    }

    /// Serialise the bundle to DER.
    ///
    /// # Errors
    pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
        let mut bio = MemBio::new()?;
        let rc = unsafe { sys::i2d_PKCS12_bio(bio.as_ptr(), self.ptr) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(bio.into_vec())
    }

    /// Parse the bundle, returning the private key, end-entity certificate,
    /// and any additional CA certificates.
    ///
    /// `password` is the MAC / encryption password.  Pass `""` for an
    /// unencrypted bundle.
    ///
    /// # Errors
    pub fn parse(
        &self,
        password: &str,
    ) -> Result<
        (
            crate::pkey::Pkey<crate::pkey::Private>,
            crate::x509::X509,
            Vec<crate::x509::X509>,
        ),
        ErrorStack,
    > {
        let pass = CString::new(password).map_err(|_| ErrorStack::drain())?;

        let mut pkey_ptr: *mut sys::EVP_PKEY = std::ptr::null_mut();
        let mut cert_ptr: *mut sys::X509 = std::ptr::null_mut();
        let mut ca_ptr: *mut sys::stack_st_X509 = std::ptr::null_mut();

        let rc = unsafe {
            sys::PKCS12_parse(
                self.ptr,
                pass.as_ptr(),
                std::ptr::addr_of_mut!(pkey_ptr),
                std::ptr::addr_of_mut!(cert_ptr),
                std::ptr::addr_of_mut!(ca_ptr),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }

        if pkey_ptr.is_null() || cert_ptr.is_null() {
            // Free anything that was allocated before returning the error.
            if !pkey_ptr.is_null() {
                unsafe { sys::EVP_PKEY_free(pkey_ptr) };
            }
            if !cert_ptr.is_null() {
                unsafe { sys::X509_free(cert_ptr) };
            }
            free_x509_stack(ca_ptr);
            return Err(ErrorStack::drain());
        }

        let key = unsafe { crate::pkey::Pkey::from_ptr(pkey_ptr) };
        let cert = unsafe { crate::x509::X509::from_ptr(cert_ptr) };
        let ca = drain_x509_stack(ca_ptr);
        Ok((key, cert, ca))
    }

    /// Create a PKCS#12 bundle from a private key and certificate.
    ///
    /// - `password`: MAC / encryption passphrase.
    /// - `name`:     Friendly name stored in the bundle (e.g. the subject CN).
    /// - `ca`:       Optional slice of additional CA certificates.
    ///
    /// Uses AES-256-CBC for key encryption and SHA-256 for the MAC
    /// (`nid_key = 0`, `nid_cert = 0` → OpenSSL defaults).
    ///
    /// # Errors
    pub fn create(
        password: &str,
        name: &str,
        key: &crate::pkey::Pkey<crate::pkey::Private>,
        cert: &crate::x509::X509,
        ca: &[crate::x509::X509],
    ) -> Result<Self, ErrorStack> {
        // Build the CA stack if needed.
        let ca_stack = if ca.is_empty() {
            std::ptr::null_mut()
        } else {
            build_x509_stack(ca)?
        };

        let pass = CString::new(password).map_err(|_| ErrorStack::drain())?;
        let name = CString::new(name).map_err(|_| ErrorStack::drain())?;

        let ptr = unsafe {
            sys::PKCS12_create_ex(
                pass.as_ptr(),
                name.as_ptr(),
                key.as_ptr(),
                cert.as_ptr(),
                ca_stack,
                0,                    // nid_key  → default
                0,                    // nid_cert → default
                0,                    // iter     → default (2048)
                0,                    // mac_iter → default (1)
                0,                    // keytype  → default
                std::ptr::null_mut(), // libctx  → global
                std::ptr::null(),     // propq   → none
            )
        };

        // Free the helper stack regardless of outcome.
        if !ca_stack.is_null() {
            unsafe {
                sys::OPENSSL_sk_free(ca_stack.cast::<sys::OPENSSL_STACK>());
            }
        }

        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(Pkcs12 { ptr })
    }
}

// ── Stack helpers (private) ───────────────────────────────────────────────────

/// Drain a `STACK_OF(X509)*` into a `Vec<X509>`, freeing the stack wrapper.
///
/// Takes ownership of the stack pointer (calls `OPENSSL_sk_free` after
/// draining).  Each `X509*` in the stack is transferred into an `X509` value
/// (no extra `up_ref`).
fn drain_x509_stack(stack: *mut sys::stack_st_X509) -> Vec<crate::x509::X509> {
    if stack.is_null() {
        return Vec::new();
    }
    let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
    let n = usize::try_from(n).unwrap_or(0);
    let mut out = Vec::with_capacity(n);
    for i in 0..n {
        // i < n, and n came from sk_num() which returns i32, so i fits.
        #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
        let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i as i32) };
        if !raw.is_null() {
            // Transfer ownership — no up_ref; each element is owned by the stack.
            out.push(unsafe { crate::x509::X509::from_ptr(raw.cast::<sys::X509>()) });
        }
    }
    // Free the stack wrapper only (elements were moved out above).
    unsafe { sys::OPENSSL_sk_free(stack.cast::<sys::OPENSSL_STACK>()) };
    out
}

/// Free a `STACK_OF(X509)*` and all elements via `X509_free`.
fn free_x509_stack(stack: *mut sys::stack_st_X509) {
    if stack.is_null() {
        return;
    }
    let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
    for i in 0..n {
        let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i) };
        if !raw.is_null() {
            unsafe { sys::X509_free(raw.cast::<sys::X509>()) };
        }
    }
    unsafe { sys::OPENSSL_sk_free(stack.cast::<sys::OPENSSL_STACK>()) };
}

/// Build a new `STACK_OF(X509)*` from a slice, up-ref'ing each cert.
///
/// The caller is responsible for freeing the returned stack with
/// `OPENSSL_sk_free` (not `pop_free`); `PKCS12_create_ex` copies the certs
/// internally and does not consume the stack.
fn build_x509_stack(certs: &[crate::x509::X509]) -> Result<*mut sys::stack_st_X509, ErrorStack> {
    // OPENSSL_STACK and stack_st_X509 are layout-compatible — the typed stacks
    // are just OPENSSL_STACK wrapped in a newtype macro.
    let raw = unsafe { sys::OPENSSL_sk_new_null() };
    if raw.is_null() {
        return Err(ErrorStack::drain());
    }
    for cert in certs {
        // up_ref so the stack holds an independent reference.
        unsafe { sys::X509_up_ref(cert.as_ptr()) };
        let rc = unsafe { sys::OPENSSL_sk_push(raw, cert.as_ptr().cast::<std::ffi::c_void>()) };
        if rc == 0 {
            // Push failed: free the up-ref we just took and the stack.
            unsafe { sys::X509_free(cert.as_ptr()) };
            // Free each already-pushed cert.
            let n = unsafe { sys::OPENSSL_sk_num(raw) };
            for i in 0..n {
                let p = unsafe { sys::OPENSSL_sk_value(raw, i) };
                if !p.is_null() {
                    unsafe { sys::X509_free(p.cast::<sys::X509>()) };
                }
            }
            unsafe { sys::OPENSSL_sk_free(raw) };
            return Err(ErrorStack::drain());
        }
    }
    Ok(raw.cast::<sys::stack_st_X509>())
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::pkey::{KeygenCtx, Pkey, Private, Public};
    use crate::x509::{X509Builder, X509NameOwned};

    fn make_self_signed() -> (crate::x509::X509, Pkey<Private>) {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let priv_key = kgen.generate().unwrap();
        let pub_key = Pkey::<Public>::from(priv_key.clone());

        let mut name = X509NameOwned::new().unwrap();
        name.add_entry_by_txt(c"CN", b"PKCS12 Test").unwrap();

        let cert = X509Builder::new()
            .unwrap()
            .set_version(2)
            .unwrap()
            .set_serial_number(1)
            .unwrap()
            .set_not_before_offset(0)
            .unwrap()
            .set_not_after_offset(365 * 86400)
            .unwrap()
            .set_subject_name(&name)
            .unwrap()
            .set_issuer_name(&name)
            .unwrap()
            .set_public_key(&pub_key)
            .unwrap()
            .sign(&priv_key, None)
            .unwrap()
            .build();

        (cert, priv_key)
    }

    #[test]
    fn pkcs12_create_and_parse_roundtrip() {
        let (cert, priv_key) = make_self_signed();

        let p12 = Pkcs12::create("testpass", "test", &priv_key, &cert, &[]).unwrap();
        let der = p12.to_der().unwrap();
        assert!(!der.is_empty());

        // Parse back.
        let p12b = Pkcs12::from_der(&der).unwrap();
        let (key2, cert2, ca2) = p12b.parse("testpass").unwrap();

        // Key type should match.
        assert!(key2.is_a(c"ED25519"));
        // Certificate subject should be intact.
        let subj = cert2.subject_name().to_string().unwrap();
        assert!(subj.contains("PKCS12 Test"));
        // No extra CA certs.
        assert!(ca2.is_empty());
    }

    #[test]
    fn pkcs12_der_roundtrip() {
        let (cert, priv_key) = make_self_signed();
        let p12 = Pkcs12::create("pass", "n", &priv_key, &cert, &[]).unwrap();
        let der1 = p12.to_der().unwrap();
        let p12b = Pkcs12::from_der(&der1).unwrap();
        let der2 = p12b.to_der().unwrap();
        assert_eq!(der1, der2);
    }

    #[test]
    fn pkcs12_wrong_password_fails() {
        let (cert, priv_key) = make_self_signed();
        let p12 = Pkcs12::create("rightpass", "n", &priv_key, &cert, &[]).unwrap();
        let der = p12.to_der().unwrap();
        let p12b = Pkcs12::from_der(&der).unwrap();
        assert!(p12b.parse("wrongpass").is_err());
    }
}