synta-certificate 0.2.6

X.509 certificate structures for synta ASN.1 library
Documentation
//! PKCS#11 URI key loading and signing via the OpenSSL `OSSL_STORE` API.
//!
//! `OSSL_STORE_open_ex()` accepts `pkcs11:` URIs when the `pkcs11-provider`
//! is loaded.  The provider must be configured externally (via `openssl.cnf`
//! or `OPENSSL_CONF`) before calling `priv_load_from_pkcs11_uri`.

use std::{ffi::CString, ptr};

// ── OSSL_STORE inline FFI ─────────────────────────────────────────────────────

/// Inline FFI for `OSSL_STORE_*` functions absent from the current
/// native-ossl bindings.  All declarations match the OpenSSL 3.x public API.
mod ossl_store_ffi {
    use std::os::raw::{c_char, c_int, c_void};

    /// Object type tag: the loaded object is a private key (`EVP_PKEY *`).
    pub const OSSL_STORE_INFO_PKEY: c_int = 4;

    // Opaque OSSL_STORE types — only ever used through raw pointers.
    pub enum OsslStoreCtx {}
    pub enum OsslStoreInfo {}

    extern "C" {
        /// Open a URI with the given `OSSL_LIB_CTX`.
        ///
        /// Pass `NULL` for `libctx` to use the process-default context,
        /// `NULL` for `propq`, and `NULL` for all callback / post-processing
        /// arguments (ui_method, ui_data, params, post_cb, post_cb_data).
        pub fn OSSL_STORE_open_ex(
            uri: *const c_char,
            libctx: *const c_void,
            propq: *const c_char,
            ui_method: *const c_void,
            ui_data: *const c_void,
            params: *const c_void,
            post_cb: *const c_void,
            post_cb_data: *const c_void,
        ) -> *mut OsslStoreCtx;

        /// Return 1 if the store has no more objects to load.
        pub fn OSSL_STORE_eof(ctx: *mut OsslStoreCtx) -> c_int;

        /// Return 1 if the last `OSSL_STORE_load` set an error.
        pub fn OSSL_STORE_error(ctx: *mut OsslStoreCtx) -> c_int;

        /// Load the next object from the store.
        ///
        /// Returns `NULL` on error or when `OSSL_STORE_eof` would be true.
        pub fn OSSL_STORE_load(ctx: *mut OsslStoreCtx) -> *mut OsslStoreInfo;

        /// Close the store and free all resources.
        pub fn OSSL_STORE_close(ctx: *mut OsslStoreCtx) -> c_int;

        /// Return the type tag of a loaded `OSSL_STORE_INFO`.
        pub fn OSSL_STORE_INFO_get_type(info: *const OsslStoreInfo) -> c_int;

        /// Extract the `EVP_PKEY *` from an `OSSL_STORE_INFO` of type
        /// `OSSL_STORE_INFO_PKEY`.  Increments the refcount; caller must free
        /// with `EVP_PKEY_free`.  Returned as `*mut c_void` to avoid a direct
        /// dep on `native_ossl_sys`; cast to `*mut EVP_PKEY` at the call site.
        pub fn OSSL_STORE_INFO_get1_PKEY(info: *const OsslStoreInfo) -> *mut std::ffi::c_void;

        /// Free an `OSSL_STORE_INFO` object returned by `OSSL_STORE_load`.
        pub fn OSSL_STORE_INFO_free(info: *mut OsslStoreInfo);
    }
}

// ── RAII store context guard ──────────────────────────────────────────────────

/// RAII guard that closes an `OSSL_STORE_CTX` on drop.
///
/// Constructed immediately after a successful `OSSL_STORE_open_ex` call.
/// When dropped it calls `OSSL_STORE_close`, which frees all store resources
/// regardless of how many objects have been loaded from the store.
struct StoreGuard(*mut ossl_store_ffi::OsslStoreCtx);

impl Drop for StoreGuard {
    fn drop(&mut self) {
        // SAFETY: pointer was returned by OSSL_STORE_open_ex and is non-null.
        unsafe { ossl_store_ffi::OSSL_STORE_close(self.0) };
    }
}

// ── Core PKCS#11 key loading ──────────────────────────────────────────────────

/// Open the PKCS#11 store for `uri`, find the first private key, and return
/// it as a `native_ossl::pkey::Pkey<Private>`.
///
/// Caller may clone the returned key cheaply (it is refcounted under the hood)
/// or use it directly.  The store is closed on return via the RAII guard.
fn load_pkcs11_pkey(
    uri: &str,
) -> Result<native_ossl::pkey::Pkey<native_ossl::pkey::Private>, super::OpensslKeyError> {
    use native_ossl::pkey::Pkey;
    use ossl_store_ffi::*;

    let uri_c = CString::new(uri).map_err(|e| super::OpensslKeyError(e.to_string()))?;

    // SAFETY: uri_c is a valid NUL-terminated C string; all NULL arguments
    // select defaults documented in the OSSL_STORE_open_ex(3) man page.
    let store_ctx = unsafe {
        OSSL_STORE_open_ex(
            uri_c.as_ptr(),
            ptr::null(), // libctx: NULL → process default
            ptr::null(), // propq: NULL → no property query
            ptr::null(), // ui_method
            ptr::null(), // ui_data
            ptr::null(), // params
            ptr::null(), // post_cb
            ptr::null(), // post_cb_data
        )
    };
    if store_ctx.is_null() {
        return Err(super::OpensslKeyError(format!(
            "OSSL_STORE_open_ex('{}') failed: {}",
            uri,
            native_ossl::error::ErrorStack::drain()
        )));
    }

    let _guard = StoreGuard(store_ctx);

    let mut found: Option<native_ossl::pkey::Pkey<native_ossl::pkey::Private>> = None;

    // Iterate store objects until we find a private key or reach EOF.
    while found.is_none() && unsafe { OSSL_STORE_eof(store_ctx) } == 0 {
        // SAFETY: store_ctx is non-null.
        let info = unsafe { OSSL_STORE_load(store_ctx) };
        if info.is_null() {
            if unsafe { OSSL_STORE_error(store_ctx) } != 0 {
                return Err(super::OpensslKeyError(format!(
                    "OSSL_STORE_load('{}') failed: {}",
                    uri,
                    native_ossl::error::ErrorStack::drain()
                )));
            }
            break;
        }

        // SAFETY: info is non-null.
        let ty = unsafe { OSSL_STORE_INFO_get_type(info) };
        if ty == OSSL_STORE_INFO_PKEY {
            // get1 increments the refcount; Pkey::from_ptr takes ownership.
            let raw = unsafe { OSSL_STORE_INFO_get1_PKEY(info) };
            // SAFETY: info is non-null and was returned by OSSL_STORE_load.
            unsafe { OSSL_STORE_INFO_free(info) };
            if !raw.is_null() {
                // SAFETY: raw is a valid, refcount-incremented EVP_PKEY pointer.
                // The c_void cast avoids a native_ossl_sys direct dependency.
                found = Some(unsafe { Pkey::from_ptr(raw as *mut _) });
            }
        } else {
            // SAFETY: info is non-null.
            unsafe { OSSL_STORE_INFO_free(info) };
        }
    }

    found.ok_or_else(|| {
        super::OpensslKeyError(format!("no private key found at PKCS#11 URI '{}'", uri))
    })
}

// ── Public API: key loading ───────────────────────────────────────────────────

/// Load a private key from the PKCS#11 token identified by `uri`.
///
/// Delegates to `OSSL_STORE_open_ex()` with the process-default
/// `OSSL_LIB_CTX` (NULL).  The `pkcs11-provider` must already be configured
/// (via `openssl.cnf` / `OPENSSL_CONF`) before calling this function.
///
/// Returns a [`crate::crypto::BackendPrivateKey`] whose `spki_cache` is
/// pre-populated from the public half and whose `pkey` holds the OpenSSL
/// handle to the token key.  `pkcs8_der` is empty — the key material never
/// leaves the token.
pub(crate) fn priv_load_from_pkcs11_uri(
    uri: &str,
) -> Result<crate::crypto::BackendPrivateKey, super::OpensslKeyError> {
    let pkey = load_pkcs11_pkey(uri)?;
    let spki_der = pkey
        .public_key_to_der()
        .map_err(|e| super::OpensslKeyError(e.to_string()))?;

    // Parse the URI once and cache the decoded attributes.
    // OSSL_STORE already validated the URI; unwrap is safe here.
    let pkcs11 = crate::pkcs11_uri::Pkcs11Uri::parse(uri)
        .expect("URI passed OSSL_STORE_open_ex but failed pkcs11_uri::parse");

    Ok(crate::crypto::BackendPrivateKey {
        pkcs8_der: std::sync::OnceLock::new(),
        spki_cache: Some(spki_der),
        pkey: Some(pkey),
        pkcs11: Some(pkcs11),
    })
}