//! `Pkey<T>` — asymmetric key container and operations.
//!
//! Phase 5 delivers key loading/serialisation (5.1), keygen (5.2),
//! sign/verify (5.3), derive (5.4), asymmetric encrypt/decrypt (5.5),
//! and KEM encapsulate/decapsulate (5.6).
//!
//! # Type-state markers
//!
//! `Pkey<Private>`, `Pkey<Public>`, and `Pkey<Params>` statically prevent
//! misuse (e.g. signing with a public key).  `HasPrivate: HasPublic` means
//! every `Pkey<Private>` can also be used wherever `Pkey<Public>` is needed.

use crate::bio::{MemBio, MemBioBuf};
use crate::error::ErrorStack;
use native_ossl_sys as sys;
use std::marker::PhantomData;
use std::sync::Arc;

// ── Marker types and sealed trait hierarchy ───────────────────────────────────

/// Marker: key holds public key material only.
pub struct Public;
/// Marker: key holds public + private key material.
pub struct Private;
/// Marker: key holds PKEY parameters only (e.g. EC group with no key).
pub struct Params;

mod sealed {
    /// Sealed base: any key role.
    pub trait HasParams {}
    impl HasParams for super::Public {}
    impl HasParams for super::Private {}
    impl HasParams for super::Params {}

    /// Sealed: key has public material.
    pub trait HasPublic: HasParams {}
    impl HasPublic for super::Public {}
    impl HasPublic for super::Private {}

    /// Sealed: key has private material.
    pub trait HasPrivate: HasPublic {}
    impl HasPrivate for super::Private {}
}

/// All key markers satisfy this bound.
pub trait HasParams: sealed::HasParams {}
impl<T: sealed::HasParams> HasParams for T {}

/// Public key material is accessible (both `Public` and `Private` keys).
pub trait HasPublic: sealed::HasPublic {}
impl<T: sealed::HasPublic> HasPublic for T {}

/// Private key material is accessible.
pub trait HasPrivate: sealed::HasPrivate {}
impl<T: sealed::HasPrivate> HasPrivate for T {}

// ── Pkey<T> — key container ───────────────────────────────────────────────────

/// An asymmetric key (`EVP_PKEY*`) with a compile-time role marker.
///
/// Cloneable via `EVP_PKEY_up_ref`; wrapping in `Arc<Pkey<T>>` is safe.
pub struct Pkey<T> {
    ptr: *mut sys::EVP_PKEY,
    _role: PhantomData<T>,
}

// SAFETY: `EVP_PKEY` is reference-counted.
unsafe impl<T> Send for Pkey<T> {}
unsafe impl<T> Sync for Pkey<T> {}

impl<T> Clone for Pkey<T> {
    fn clone(&self) -> Self {
        unsafe { sys::EVP_PKEY_up_ref(self.ptr) };
        Pkey {
            ptr: self.ptr,
            _role: PhantomData,
        }
    }
}

impl<T> Drop for Pkey<T> {
    fn drop(&mut self) {
        unsafe { sys::EVP_PKEY_free(self.ptr) };
    }
}

impl<T: HasParams> Pkey<T> {
    /// Construct from a raw (owned) `EVP_PKEY*`.
    ///
    /// # Safety
    ///
    /// `ptr` must be a valid, non-null `EVP_PKEY*` that the caller is giving up ownership of.
    #[must_use]
    pub unsafe fn from_ptr(ptr: *mut sys::EVP_PKEY) -> Self {
        Pkey {
            ptr,
            _role: PhantomData,
        }
    }

    /// Raw `EVP_PKEY*` pointer valid for the lifetime of `self`.
    #[must_use]
    pub fn as_ptr(&self) -> *mut sys::EVP_PKEY {
        self.ptr
    }

    /// Size of the key in bits (e.g. 256 for P-256, 2048 for RSA-2048).
    #[must_use]
    pub fn bits(&self) -> u32 {
        u32::try_from(unsafe { sys::EVP_PKEY_get_bits(self.ptr) }).unwrap_or(0)
    }

    /// Security strength in bits (e.g. 128 for P-256, 112 for RSA-2048).
    #[must_use]
    pub fn security_bits(&self) -> u32 {
        u32::try_from(unsafe { sys::EVP_PKEY_get_security_bits(self.ptr) }).unwrap_or(0)
    }

    /// Maximum output size in bytes for this key's primary operation.
    ///
    /// For signing keys (RSA, ECDSA, Ed25519) this is the maximum signature
    /// length. For encryption keys (RSA-OAEP) this is the maximum ciphertext
    /// length. Use this to size output buffers before calling signing or
    /// encryption operations.
    ///
    /// Returns `0` if the key does not support a size query.
    #[must_use]
    pub fn max_output_size(&self) -> usize {
        // SAFETY:
        // - self.ptr is non-null (constructor invariant)
        // - no mutable aliasing; &self ensures shared read-only access
        usize::try_from(unsafe { sys::EVP_PKEY_get_size(self.ptr) }).unwrap_or(0)
    }

    /// Return `true` if this key is of the named algorithm (e.g. `c"EC"`, `c"RSA"`).
    #[must_use]
    pub fn is_a(&self, name: &std::ffi::CStr) -> bool {
        unsafe { sys::EVP_PKEY_is_a(self.ptr, name.as_ptr()) == 1 }
    }

    /// Return `true` if this key's public component equals `other`'s.
    ///
    /// Wraps `EVP_PKEY_eq`.  Useful for verifying that a certificate's public
    /// key matches a private key before using them together.
    #[must_use]
    pub fn public_eq<U: HasPublic>(&self, other: &Pkey<U>) -> bool
    where
        T: HasPublic,
    {
        unsafe { sys::EVP_PKEY_eq(self.ptr, other.ptr) == 1 }
    }

    /// Fill the values for a pre-prepared mutable `Params` query array.
    ///
    /// Wraps `EVP_PKEY_get_params`.  The array must already contain the keys
    /// of interest with null data pointers; OpenSSL writes the values in place.
    ///
    /// # Errors
    pub fn get_params(&self, params: &mut crate::params::Params<'_>) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_PKEY_get_params(self.ptr, params.as_mut_ptr()))
    }

    /// DER-encode the public key (`SubjectPublicKeyInfo` format).
    ///
    /// Zero-copy: writes directly into a caller-owned `Vec<u8>` — no OpenSSL
    /// heap allocation occurs.
    ///
    /// # Errors
    ///
    /// Returns `Err` if serialisation fails.
    pub fn public_key_to_der(&self) -> Result<Vec<u8>, ErrorStack>
    where
        T: HasPublic,
    {
        // First call with null to query the DER byte length.
        let len = unsafe { sys::i2d_PUBKEY(self.ptr, std::ptr::null_mut()) };
        if len < 0 {
            return Err(ErrorStack::drain());
        }
        // Allocate our own buffer and write into it.
        // i2d_ advances the out-pointer; our Vec base address is unaffected.
        let mut buf = vec![0u8; usize::try_from(len).unwrap_or(0)];
        let mut out_ptr = buf.as_mut_ptr();
        let written = unsafe { sys::i2d_PUBKEY(self.ptr, std::ptr::addr_of_mut!(out_ptr)) };
        if written < 0 {
            return Err(ErrorStack::drain());
        }
        buf.truncate(usize::try_from(written).unwrap_or(0));
        Ok(buf)
    }

    /// Returns the default digest algorithm name for this key, or `None` if the
    /// key type mandates no external digest (e.g. Ed25519, ML-DSA).
    ///
    /// Wraps `EVP_PKEY_get_default_digest_name`.  The function returns 1 when the
    /// digest is optional and 2 when it is mandatory; both are treated as success.
    /// When the key's algorithm performs its own internal hashing (Ed25519, ML-DSA,
    /// SLH-DSA), OpenSSL writes `"none"` or an empty string — both map to `Ok(None)`.
    ///
    /// Callers should check this before deciding whether to pass a digest to
    /// `DigestSign` / `DigestVerify`: if `default_digest_name()` returns `None`,
    /// pass `digest: None` in `SignInit`; otherwise pass the returned name.
    ///
    /// # Errors
    ///
    /// Returns `Err` if OpenSSL cannot determine the digest.
    pub fn default_digest_name(&self) -> Result<Option<String>, ErrorStack>
    where
        T: HasPublic,
    {
        // SAFETY: self.ptr is a valid non-null EVP_PKEY* (constructor invariant).
        // EVP_PKEY_get_default_digest_name writes a NUL-terminated C string into
        // mdname and returns >= 1 on success.
        let mut buf = [0u8; 64];
        let rc = unsafe {
            sys::EVP_PKEY_get_default_digest_name(
                self.ptr,
                buf.as_mut_ptr().cast::<std::ffi::c_char>(),
                buf.len(),
            )
        };
        if rc < 1 {
            return Err(ErrorStack::drain());
        }
        // Find the NUL terminator.
        let name_bytes = buf
            .iter()
            .position(|&b| b == 0)
            .map_or(&buf[..], |pos| &buf[..pos]);
        // "none", "UNDEF", or empty string means no separate digest.
        // OpenSSL 3.5+ returns "UNDEF" for algorithms like Ed25519 that do
        // their own internal hashing without a caller-supplied digest.
        if name_bytes.is_empty()
            || name_bytes.eq_ignore_ascii_case(b"none")
            || name_bytes.eq_ignore_ascii_case(b"undef")
        {
            return Ok(None);
        }
        let name = std::str::from_utf8(name_bytes)
            .map_err(|_| ErrorStack::drain())?
            .to_owned();
        Ok(Some(name))
    }
}

// ── PEM loading — private key ─────────────────────────────────────────────────

impl Pkey<Private> {
    /// Return the provider-side `keydata` pointer from the `EVP_PKEY` struct.
    ///
    /// This is the `void *keydata` field of `evp_pkey_st`.  It holds the
    /// algorithm-specific key material allocated by the provider's keymgmt
    /// implementation.  The pointer is valid for the lifetime of `self`.
    ///
    /// Only available with the `fips-provider` cargo feature.  Intended for
    /// use inside a FIPS provider when invoking `EVP_SIGNATURE` vtable functions
    /// that require `void *provkey` (= `keydata`).
    ///
    /// # Safety
    ///
    /// The returned pointer points into the internal state of this `Pkey`.
    /// It must not outlive `self` and must not be freed independently.
    /// The field offset is computed by the C compiler for the current target
    /// ABI via a build-time probe; see `native-ossl-sys/build.rs`.
    #[cfg(feature = "fips-provider")]
    pub unsafe fn keydata(&self) -> *mut std::ffi::c_void {
        // SAFETY: self.ptr is a valid non-null EVP_PKEY*.  We read the void* at
        // the ABI-correct byte offset of the `keydata` field in evp_pkey_st,
        // determined at build time by a C offsetof probe.
        self.ptr
            .cast::<u8>()
            .add(native_ossl_sys::fips_internal::EVP_PKEY_KEYDATA_OFFSET)
            .cast::<*mut std::ffi::c_void>()
            .read()
    }

    /// Load a private key from PEM bytes.
    ///
    /// Pass `passphrase = Some(cb)` for encrypted PEM; `None` for unencrypted.
    ///
    /// # Errors
    pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
        let bio = MemBioBuf::new(pem)?;
        let ptr = unsafe {
            sys::PEM_read_bio_PrivateKey(
                bio.as_ptr(),
                std::ptr::null_mut(),
                None,
                std::ptr::null_mut(),
            )
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(unsafe { Pkey::from_ptr(ptr) })
    }

    /// Serialise the private key to PEM (`PKCS#8` `BEGIN PRIVATE KEY`).
    ///
    /// # Errors
    pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack> {
        let mut bio = MemBio::new()?;
        let rc = unsafe {
            sys::PEM_write_bio_PrivateKey(
                bio.as_ptr(),
                self.ptr,
                std::ptr::null(),
                std::ptr::null_mut(),
                0,
                None,
                std::ptr::null_mut(),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(bio.into_vec())
    }

    /// Load a private key from PEM bytes within a specific library context.
    ///
    /// Uses `PEM_read_bio_PrivateKey_ex` so the key's internal algorithm fetch
    /// uses `ctx`'s provider set.  Necessary when the private key is later used
    /// for EVP operations inside an isolated (e.g. FIPS) context.
    ///
    /// # Errors
    pub fn from_pem_in(ctx: &Arc<crate::lib_ctx::LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack> {
        let bio = MemBioBuf::new(pem)?;
        let ptr = unsafe {
            sys::PEM_read_bio_PrivateKey_ex(
                bio.as_ptr(),
                std::ptr::null_mut(),
                None,
                std::ptr::null_mut(),
                ctx.as_ptr(),
                std::ptr::null(),
            )
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(unsafe { Pkey::from_ptr(ptr) })
    }

    /// Load a private key from DER bytes (auto-detecting `PKCS#8` / traditional).
    ///
    /// Zero-copy: the `EVP_PKEY` is decoded from the caller's slice without copying.
    ///
    /// # Errors
    pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
        let bio = MemBioBuf::new(der)?;
        let ptr = unsafe { sys::d2i_PrivateKey_bio(bio.as_ptr(), std::ptr::null_mut()) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(unsafe { Pkey::from_ptr(ptr) })
    }

    /// Load a private key from DER bytes using an explicit library context.
    ///
    /// The key format is detected automatically (`PKCS#8`, traditional, etc.).
    /// Wraps `d2i_AutoPrivateKey_ex` — unlike [`from_der`](Self::from_der) (which uses a
    /// `BIO`), this function passes the raw byte pointer directly to OpenSSL so that
    /// the key's internal algorithm fetch is bound to `ctx`'s provider set.  Use
    /// this when the loaded key will be used for EVP operations inside an isolated
    /// (e.g. FIPS) context.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the DER bytes are malformed or the algorithm is not
    /// available in `ctx`.
    pub fn from_der_in(ctx: &Arc<crate::lib_ctx::LibCtx>, der: &[u8]) -> Result<Self, ErrorStack> {
        // `d2i_AutoPrivateKey_ex` expects a `*mut *const u8` input pointer; it
        // advances the pointer past the consumed bytes but we do not need the
        // updated value, so we bind it to a local that is discarded afterwards.
        let mut ptr = der.as_ptr();
        // SAFETY:
        // - Non-null: `der.as_ptr()` is always valid for a shared slice (even if
        //   zero-length; OpenSSL will reject zero-length DER before dereferencing).
        // - `ctx.as_ptr()` is non-null by `LibCtx` constructor invariant; `ctx`
        //   is kept alive by the `&Arc<LibCtx>` borrow for the entire call.
        // - Lifetime: `ptr` points into `der` which lives for the call duration;
        //   OpenSSL reads at most `der.len()` bytes before returning.
        // - Exclusivity / no data races: `der` is a shared immutable borrow; no
        //   other thread can mutate it.  `ptr` is a local stack copy — OpenSSL's
        //   write to `*pp` (advancing the pointer) touches only this variable,
        //   not the original slice.
        let pkey = unsafe {
            sys::d2i_AutoPrivateKey_ex(
                std::ptr::null_mut(),
                std::ptr::addr_of_mut!(ptr),
                i64::try_from(der.len()).unwrap_or(i64::MAX),
                ctx.as_ptr(),
                std::ptr::null(),
            )
        };
        if pkey.is_null() {
            return Err(ErrorStack::drain());
        }
        // SAFETY: `pkey` is non-null and freshly allocated by OpenSSL; we take
        // exclusive ownership of the reference count.
        Ok(unsafe { Pkey::from_ptr(pkey) })
    }

    /// Load a private key from passphrase-encrypted PEM.
    ///
    /// Passes `passphrase` directly to `PEM_read_bio_PrivateKey` via a
    /// `pem_password_cb`.  Returns `Err` if the key cannot be decrypted or
    /// the PEM is malformed.  For unencrypted PEM use [`from_pem`](Self::from_pem).
    ///
    /// # Errors
    pub fn from_pem_passphrase(pem: &[u8], passphrase: &[u8]) -> Result<Self, ErrorStack> {
        extern "C" fn passwd_cb(
            buf: *mut std::ffi::c_char,
            size: std::ffi::c_int,
            _rwflag: std::ffi::c_int,
            u: *mut std::ffi::c_void,
        ) -> std::ffi::c_int {
            // SAFETY: `u` is `&pw` where `pw: &[u8]` lives on the caller's stack.
            let pw: &[u8] = unsafe { *(u as *const &[u8]) };
            // size is the callback buffer capacity; it is always > 0 per the C contract.
            let max_len = usize::try_from(size).unwrap_or(0);
            let n = pw.len().min(max_len);
            unsafe { std::ptr::copy_nonoverlapping(pw.as_ptr(), buf.cast::<u8>(), n) };
            // n <= max_len == size (as usize), so n fits in i32.
            i32::try_from(n).unwrap()
        }
        let bio = MemBioBuf::new(pem)?;
        let pw: &[u8] = passphrase;
        let ptr = unsafe {
            sys::PEM_read_bio_PrivateKey(
                bio.as_ptr(),
                std::ptr::null_mut(),
                Some(passwd_cb),
                std::ptr::addr_of!(pw).cast::<std::ffi::c_void>().cast_mut(),
            )
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(unsafe { Pkey::from_ptr(ptr) })
    }

    /// Serialise the private key as passphrase-encrypted PKCS#8 PEM
    /// (`BEGIN ENCRYPTED PRIVATE KEY`).
    ///
    /// `cipher` controls the wrapping algorithm
    /// (e.g. `CipherAlg::fetch(c"AES-256-CBC", None)`).
    /// The passphrase is passed directly to OpenSSL via `kstr`/`klen`.
    ///
    /// # Panics
    ///
    /// Panics if `passphrase` is longer than `i32::MAX` bytes.
    ///
    /// # Errors
    pub fn to_pem_encrypted(
        &self,
        cipher: &crate::cipher::CipherAlg,
        passphrase: &[u8],
    ) -> Result<Vec<u8>, ErrorStack> {
        let mut bio = MemBio::new()?;
        let rc = unsafe {
            sys::PEM_write_bio_PKCS8PrivateKey(
                bio.as_ptr(),
                self.ptr,
                cipher.as_ptr(),
                passphrase.as_ptr().cast(),
                i32::try_from(passphrase.len()).expect("passphrase too long"),
                None,
                std::ptr::null_mut(),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(bio.into_vec())
    }

    /// Serialise the private key as unencrypted PKCS#8 DER
    /// (`PrivateKeyInfo` / `OneAsymmetricKey`, RFC 5958).
    ///
    /// Equivalent to writing unencrypted PEM and stripping the base64 wrapper,
    /// but avoids the encode/decode round-trip.  To encrypt the output, use
    /// [`to_pem_encrypted`](Self::to_pem_encrypted) instead.
    ///
    /// # Errors
    pub fn to_pkcs8_der(&self) -> Result<Vec<u8>, ErrorStack> {
        let mut bio = MemBio::new()?;
        let rc = unsafe { sys::i2d_PKCS8PrivateKeyInfo_bio(bio.as_ptr(), self.ptr) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(bio.into_vec())
    }

    /// Load a private key identified by a PKCS#11 URI
    /// (e.g. `pkcs11:token=mytoken;object=mykey;type=private`).
    ///
    /// The `pkcs11` provider must already be loaded into `ctx` before calling
    /// this function — typically via
    /// [`LibCtx::load_pkcs11_provider`](crate::lib_ctx::LibCtx::load_pkcs11_provider).
    ///
    /// Internally, `OSSL_DECODER` dispatches the URI to the pkcs11 provider
    /// which returns a live `EVP_PKEY*` backed by the hardware token.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the URI is syntactically invalid, the pkcs11 provider is
    /// not loaded, or the token / object cannot be found.
    pub fn from_pkcs11_uri(
        ctx: &Arc<crate::lib_ctx::LibCtx>,
        uri: &str,
    ) -> Result<Self, ErrorStack> {
        let uri_cs = std::ffi::CString::new(uri).map_err(|_| ErrorStack::drain())?;
        let uri_bytes = uri_cs.to_bytes();
        let mut pkey: *mut sys::EVP_PKEY = std::ptr::null_mut();
        // Use OSSL_DECODER with keytype=NULL so the pkcs11 provider can resolve
        // the algorithm from the token object.
        // SAFETY: pointers are either null sentinels or valid for the call duration.
        let dctx = unsafe {
            sys::OSSL_DECODER_CTX_new_for_pkey(
                std::ptr::addr_of_mut!(pkey),
                c"PEM".as_ptr(),  // pkcs11 URIs are presented as PEM-style text
                std::ptr::null(), // input_struct: any
                std::ptr::null(), // keytype: let provider decide
                SELECT_DOMAIN_PARAMETERS | 0x01 | 0x02, // KEYPAIR selection
                ctx.as_ptr(),
                std::ptr::null(), // propquery: default
            )
        };
        if dctx.is_null() {
            return Err(ErrorStack::drain());
        }
        let mut ptr = uri_bytes.as_ptr();
        let mut len = uri_bytes.len();
        let rc = unsafe {
            sys::OSSL_DECODER_from_data(
                dctx,
                std::ptr::addr_of_mut!(ptr),
                std::ptr::addr_of_mut!(len),
            )
        };
        unsafe { sys::OSSL_DECODER_CTX_free(dctx) };
        if rc != 1 || pkey.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(unsafe { Pkey::from_ptr(pkey) })
    }
}

// ── PEM loading — public key ──────────────────────────────────────────────────

impl Pkey<Public> {
    /// Load a public key from PEM (`SubjectPublicKeyInfo` or RSA public key).
    ///
    /// # Errors
    pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
        let bio = MemBioBuf::new(pem)?;
        let ptr = unsafe {
            sys::PEM_read_bio_PUBKEY(
                bio.as_ptr(),
                std::ptr::null_mut(),
                None,
                std::ptr::null_mut(),
            )
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(unsafe { Pkey::from_ptr(ptr) })
    }

    /// Load a public key from PEM bytes within a specific library context.
    ///
    /// Uses `PEM_read_bio_PUBKEY_ex` so the key's internal algorithm fetch
    /// uses `ctx`'s provider set.  Necessary when the public key is later used
    /// for EVP operations inside an isolated (e.g. FIPS) context.
    ///
    /// # Errors
    pub fn from_pem_in(ctx: &Arc<crate::lib_ctx::LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack> {
        let bio = MemBioBuf::new(pem)?;
        let ptr = unsafe {
            sys::PEM_read_bio_PUBKEY_ex(
                bio.as_ptr(),
                std::ptr::null_mut(),
                None,
                std::ptr::null_mut(),
                ctx.as_ptr(),
                std::ptr::null(),
            )
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(unsafe { Pkey::from_ptr(ptr) })
    }

    /// Load a public key from DER (`SubjectPublicKeyInfo`).
    ///
    /// # Errors
    pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
        let bio = MemBioBuf::new(der)?;
        let ptr = unsafe { sys::d2i_PUBKEY_bio(bio.as_ptr(), std::ptr::null_mut()) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(unsafe { Pkey::from_ptr(ptr) })
    }

    /// Load a `SubjectPublicKeyInfo` DER public key using an explicit library context.
    ///
    /// Wraps `d2i_PUBKEY_ex` — unlike [`from_der`](Self::from_der) (which uses a
    /// `BIO`), this function passes the raw byte pointer directly so that the
    /// key's internal algorithm fetch is bound to `ctx`'s provider set.  Use
    /// this when the loaded key will be used for EVP operations inside an isolated
    /// (e.g. FIPS) context.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the DER bytes are malformed or the algorithm is not
    /// available in `ctx`.
    pub fn from_der_in(ctx: &Arc<crate::lib_ctx::LibCtx>, der: &[u8]) -> Result<Self, ErrorStack> {
        let mut ptr = der.as_ptr();
        // SAFETY:
        // - Non-null: `der.as_ptr()` is always valid for a shared slice (even if
        //   zero-length; OpenSSL rejects zero-length DER before dereferencing).
        // - `ctx.as_ptr()` is non-null by `LibCtx` constructor invariant; `ctx`
        //   is kept alive by the `&Arc<LibCtx>` borrow for the entire call.
        // - Lifetime: `ptr` points into `der` which lives for the call duration;
        //   OpenSSL reads at most `der.len()` bytes before returning.
        // - Exclusivity / no data races: `der` is a shared immutable borrow; no
        //   other thread can mutate it.  `ptr` is a local stack copy — OpenSSL's
        //   write to `*pp` touches only this variable, not the original slice.
        let pkey = unsafe {
            sys::d2i_PUBKEY_ex(
                std::ptr::null_mut(),
                std::ptr::addr_of_mut!(ptr),
                i64::try_from(der.len()).unwrap_or(i64::MAX),
                ctx.as_ptr(),
                std::ptr::null(),
            )
        };
        if pkey.is_null() {
            return Err(ErrorStack::drain());
        }
        // SAFETY: `pkey` is non-null and freshly allocated by OpenSSL; we take
        // exclusive ownership of the reference count.
        Ok(unsafe { Pkey::from_ptr(pkey) })
    }

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

    /// Decode RFC 3279 `DHDomainParameters` DER (OID 1.2.840.10046.2.1).
    ///
    /// Used for PKINIT DH group negotiation: the KDC advertises domain
    /// parameters as a bare DER blob whose outer OID is `dhpublicnumber`.
    /// `SubjectPublicKeyInfo` DER (from [`from_der`](Self::from_der)) does not
    /// handle this format; this method uses `OSSL_DECODER` instead.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `data` is not a valid DER-encoded `DHDomainParameters`,
    /// or if the DHX algorithm is not available in the default library context.
    pub fn dhx_params_from_der(data: &[u8]) -> Result<Self, ErrorStack> {
        decoder_pkey_from_der(data, c"DHX", std::ptr::null_mut())
    }

    /// Decode RFC 3279 `DHDomainParameters` DER using an explicit library context.
    ///
    /// Like [`dhx_params_from_der`](Self::dhx_params_from_der) but binds the
    /// internal algorithm fetch to `ctx`'s provider set.
    ///
    /// # Errors
    pub fn dhx_params_from_der_in(
        ctx: &Arc<crate::lib_ctx::LibCtx>,
        data: &[u8],
    ) -> Result<Self, ErrorStack> {
        decoder_pkey_from_der(data, c"DHX", ctx.as_ptr())
    }

    /// Decode standalone EC domain parameters DER (without a public key).
    ///
    /// # Errors
    ///
    /// Returns `Err` if `data` is not valid EC domain parameter DER, or if the
    /// EC algorithm is not available in the default library context.
    pub fn ec_params_from_der(data: &[u8]) -> Result<Self, ErrorStack> {
        decoder_pkey_from_der(data, c"EC", std::ptr::null_mut())
    }

    /// Decode standalone EC domain parameters DER using an explicit library context.
    ///
    /// # Errors
    pub fn ec_params_from_der_in(
        ctx: &Arc<crate::lib_ctx::LibCtx>,
        data: &[u8],
    ) -> Result<Self, ErrorStack> {
        decoder_pkey_from_der(data, c"EC", ctx.as_ptr())
    }
}

// Upcast: every `Pkey<Private>` can be viewed as `Pkey<Public>`.
impl From<Pkey<Private>> for Pkey<Public> {
    fn from(k: Pkey<Private>) -> Self {
        unsafe { sys::EVP_PKEY_up_ref(k.ptr) };
        Pkey {
            ptr: k.ptr,
            _role: PhantomData,
        }
    }
}

// ── Key import from OSSL_PARAM ────────────────────────────────────────────────

// EVP_PKEY_fromdata / EVP_PKEY_todata selection constants.
// These must match the macros in <openssl/keymgmt.h>:
//   EVP_PKEY_PUBLIC_KEY = OSSL_KEYMGMT_SELECT_ALL_PARAMETERS | SELECT_PUBLIC_KEY
//                       = (0x04 | 0x80) | 0x02 = 0x86
//   EVP_PKEY_KEYPAIR    = EVP_PKEY_PUBLIC_KEY | SELECT_PRIVATE_KEY
//                       = 0x86 | 0x01 = 0x87
// Using the bare SELECT_PUBLIC_KEY (0x02) would omit domain parameters, which
// breaks EC keys (the curve group is a domain parameter, not a public-key param).
const PKEY_PUBLIC_KEY: i32 = 0x86;
const PKEY_KEYPAIR: i32 = 0x87;

// OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS = 0x04 (from <openssl/keymgmt.h>)
const SELECT_DOMAIN_PARAMETERS: i32 = 0x04;

/// Decode DER-encoded key material of the given `keytype` using `OSSL_DECODER`.
///
/// `libctx` may be null (to use the global default context).
fn decoder_pkey_from_der(
    data: &[u8],
    keytype: &std::ffi::CStr,
    libctx: *mut sys::OSSL_LIB_CTX,
) -> Result<Pkey<Public>, ErrorStack> {
    let mut pkey: *mut sys::EVP_PKEY = std::ptr::null_mut();
    // SAFETY: All pointers are either null (valid sentinel) or valid for the
    // duration of the call. `input_struct` and `propquery` are null to let
    // OpenSSL pick defaults.
    let dctx = unsafe {
        sys::OSSL_DECODER_CTX_new_for_pkey(
            std::ptr::addr_of_mut!(pkey),
            c"DER".as_ptr(),
            std::ptr::null(), // input_struct: any
            keytype.as_ptr(),
            SELECT_DOMAIN_PARAMETERS,
            libctx,
            std::ptr::null(), // propquery: default
        )
    };
    if dctx.is_null() {
        return Err(ErrorStack::drain());
    }
    let mut ptr = data.as_ptr();
    let mut len = data.len();
    // SAFETY: `ptr` points into `data` which lives for the entire call.
    // `OSSL_DECODER_from_data` advances `ptr` and decrements `len` in place;
    // neither write escapes this scope.
    let rc = unsafe {
        sys::OSSL_DECODER_from_data(
            dctx,
            std::ptr::addr_of_mut!(ptr),
            std::ptr::addr_of_mut!(len),
        )
    };
    unsafe { sys::OSSL_DECODER_CTX_free(dctx) };
    if rc != 1 || pkey.is_null() {
        return Err(ErrorStack::drain());
    }
    // SAFETY: `pkey` is freshly allocated by OpenSSL; we take sole ownership.
    Ok(unsafe { Pkey::from_ptr(pkey) })
}

/// Shared implementation for `EVP_PKEY_fromdata`.
fn pkey_fromdata(
    ctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
    pkey_type: &std::ffi::CStr,
    params: &crate::params::Params<'_>,
    selection: i32,
) -> Result<*mut sys::EVP_PKEY, ErrorStack> {
    let libctx = ctx.map_or(std::ptr::null_mut(), |c| c.as_ptr());
    let pctx =
        unsafe { sys::EVP_PKEY_CTX_new_from_name(libctx, pkey_type.as_ptr(), std::ptr::null()) };
    if pctx.is_null() {
        return Err(ErrorStack::drain());
    }
    let rc = unsafe { sys::EVP_PKEY_fromdata_init(pctx) };
    if rc != 1 {
        unsafe { sys::EVP_PKEY_CTX_free(pctx) };
        return Err(ErrorStack::drain());
    }
    let mut pkey: *mut sys::EVP_PKEY = std::ptr::null_mut();
    let rc = unsafe {
        sys::EVP_PKEY_fromdata(
            pctx,
            std::ptr::addr_of_mut!(pkey),
            selection,
            // OSSL_PARAM array is read-only during fromdata; cast is safe.
            params.as_ptr().cast_mut(),
        )
    };
    unsafe { sys::EVP_PKEY_CTX_free(pctx) };
    if rc != 1 || pkey.is_null() {
        return Err(ErrorStack::drain());
    }
    Ok(pkey)
}

/// Shared implementation for `EVP_PKEY_todata`.
fn pkey_todata(
    ptr: *mut sys::EVP_PKEY,
    selection: i32,
) -> Result<crate::params::Params<'static>, ErrorStack> {
    let mut out: *mut sys::OSSL_PARAM = std::ptr::null_mut();
    let rc = unsafe { sys::EVP_PKEY_todata(ptr, selection, std::ptr::addr_of_mut!(out)) };
    if rc != 1 || out.is_null() {
        return Err(ErrorStack::drain());
    }
    // SAFETY: `out` is a freshly allocated OSSL_PARAM array from OpenSSL;
    // Params takes ownership and will free it via OSSL_PARAM_free on drop.
    Ok(unsafe { crate::params::Params::from_owned_ptr(out) })
}

impl Pkey<Private> {
    /// Import a private key pair from an `OSSL_PARAM` array.
    ///
    /// Equivalent to `EVP_PKEY_fromdata` with `EVP_PKEY_KEYPAIR` selection.
    /// Pass `ctx = None` to use the global default library context.
    ///
    /// # Errors
    pub fn from_params(
        ctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
        pkey_type: &std::ffi::CStr,
        params: &crate::params::Params<'_>,
    ) -> Result<Self, ErrorStack> {
        pkey_fromdata(ctx, pkey_type, params, PKEY_KEYPAIR)
            .map(|ptr| unsafe { Pkey::from_ptr(ptr) })
    }

    /// Export all key parameters (private + public) as an owned `OSSL_PARAM` array.
    ///
    /// Uses `EVP_PKEY_KEYPAIR` selection so both private and public material
    /// are included in the returned array.
    ///
    /// # Errors
    pub fn export(&self) -> Result<crate::params::Params<'static>, ErrorStack> {
        pkey_todata(self.ptr, PKEY_KEYPAIR)
    }
}

impl Pkey<Public> {
    /// Import a public key from an `OSSL_PARAM` array.
    ///
    /// Equivalent to `EVP_PKEY_fromdata` with `EVP_PKEY_PUBLIC_KEY` selection.
    /// Pass `ctx = None` to use the global default library context.
    ///
    /// # Errors
    pub fn from_params(
        ctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
        pkey_type: &std::ffi::CStr,
        params: &crate::params::Params<'_>,
    ) -> Result<Self, ErrorStack> {
        pkey_fromdata(ctx, pkey_type, params, PKEY_PUBLIC_KEY)
            .map(|ptr| unsafe { Pkey::from_ptr(ptr) })
    }

    /// Export the public key parameters as an owned `OSSL_PARAM` array.
    ///
    /// Uses `EVP_PKEY_PUBLIC_KEY` selection.
    ///
    /// # Errors
    pub fn export(&self) -> Result<crate::params::Params<'static>, ErrorStack> {
        pkey_todata(self.ptr, PKEY_PUBLIC_KEY)
    }
}

// ── Legacy DER encoding ──────────────────────────────────────────────────────

impl<T: HasPrivate> Pkey<T> {
    /// Encode this private key to legacy raw-key DER (not PKCS#8).
    ///
    /// Wraps `i2d_PrivateKey`.  The output is the algorithm-specific raw key
    /// structure (e.g. `RSAPrivateKey` / RFC 3447 for RSA, `ECPrivateKey` /
    /// RFC 5915 for EC) — **not** the `PrivateKeyInfo` / PKCS#8 wrapper.
    ///
    /// Use this for interoperability with software that requires the legacy
    /// format.  For new code prefer [`to_pkcs8_der`](crate::pkey::Pkey::to_pkcs8_der)
    /// (PKCS#8 / RFC 5958), which is algorithm-agnostic and more widely supported
    /// by modern toolkits.
    ///
    /// # Errors
    ///
    /// Returns `Err` if serialisation fails (e.g. the algorithm does not support
    /// legacy DER export, such as some post-quantum algorithms).
    pub fn to_der_legacy(&self) -> Result<Vec<u8>, ErrorStack> {
        // First call with null to query the required output length.
        // SAFETY:
        // - Non-null: `self.ptr` is non-null by `Pkey` constructor invariant.
        // - Lifetime: `self.ptr` is valid for the duration of `self`; this call
        //   does not store the pointer beyond the call.
        // - Exclusivity / no data races: `&self` ensures no concurrent mutable
        //   access to `self.ptr`; the null second argument means OpenSSL only
        //   reads the key, it does not write through `pp`.
        let len = unsafe { sys::i2d_PrivateKey(self.ptr, std::ptr::null_mut()) };
        if len < 0 {
            return Err(ErrorStack::drain());
        }
        let mut buf = vec![0u8; usize::try_from(len).unwrap_or(0)];
        let mut out_ptr = buf.as_mut_ptr();
        // SAFETY:
        // - Non-null: `self.ptr` is non-null (constructor invariant).
        // - `out_ptr` points into `buf` which has capacity `len` bytes; OpenSSL
        //   writes exactly `len` bytes and advances `out_ptr` past the data.
        // - Lifetime: `buf` is alive for the duration of this call; OpenSSL does
        //   not retain the pointer after returning.
        // - Exclusivity / no data races: `buf` is exclusively owned here; no
        //   other reference to its backing memory exists during the call.
        let written = unsafe { sys::i2d_PrivateKey(self.ptr, std::ptr::addr_of_mut!(out_ptr)) };
        if written < 0 {
            return Err(ErrorStack::drain());
        }
        buf.truncate(usize::try_from(written).unwrap_or(0));
        Ok(buf)
    }
}

// ── KeygenCtx — key generation ────────────────────────────────────────────────

/// Context for generating asymmetric key pairs (`EVP_PKEY_CTX` in keygen mode).
pub struct KeygenCtx {
    ptr: *mut sys::EVP_PKEY_CTX,
}

impl KeygenCtx {
    /// Create a keygen context for the named algorithm.
    ///
    /// Common names: `c"RSA"`, `c"EC"`, `c"ED25519"`, `c"X25519"`.
    ///
    /// # Errors
    pub fn new(name: &std::ffi::CStr) -> Result<Self, ErrorStack> {
        let ptr = unsafe {
            sys::EVP_PKEY_CTX_new_from_name(std::ptr::null_mut(), name.as_ptr(), std::ptr::null())
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        let rc = unsafe { sys::EVP_PKEY_keygen_init(ptr) };
        if rc != 1 {
            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
            return Err(ErrorStack::drain());
        }
        Ok(KeygenCtx { ptr })
    }

    /// Configure parameters before calling `generate`.
    ///
    /// # Errors
    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
        // SAFETY:
        // - self.ptr is non-null (constructor invariant)
        // - params.as_ptr() is valid for the duration of this call
        // - &mut self ensures exclusive access; no concurrent reads or writes
        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ptr, params.as_ptr()))
    }

    /// Retrieve parameter values from this keygen context.
    ///
    /// Build a [`Params`][crate::params::Params] with placeholder values for the
    /// keys you want, call this method, then read back with `params.get_*`.
    ///
    /// Useful for reading algorithm-negotiated parameters after keygen initialisation
    /// (e.g. security strength, EC group name, RSA modulus size).
    ///
    /// # Errors
    ///
    /// Returns `Err` if `EVP_PKEY_CTX_get_params` fails.
    pub fn get_params(&self, params: &mut crate::params::Params<'_>) -> Result<(), ErrorStack> {
        // SAFETY:
        // - self.ptr is non-null (constructor invariant)
        // - params.as_mut_ptr() is valid for the duration of this call
        // - &self ensures no concurrent mutable access to self.ptr
        crate::ossl_call!(sys::EVP_PKEY_CTX_get_params(self.ptr, params.as_mut_ptr()))
    }

    /// Return the security strength of the key operation in bits.
    ///
    /// Available after keygen initialisation; reads `OSSL_PKEY_PARAM_SECURITY_BITS`
    /// (`"security-bits"`).
    ///
    /// # Errors
    ///
    /// Returns `Err` if the context does not support this parameter or is not
    /// sufficiently initialised.
    pub fn security_bits(&self) -> Result<u32, ErrorStack> {
        let mut params = crate::params::ParamBuilder::new()?
            .push_uint(c"security-bits", 0)?
            .build()?;
        // SAFETY: same as get_params — self.ptr is non-null, params pointer is valid.
        crate::ossl_call!(sys::EVP_PKEY_CTX_get_params(self.ptr, params.as_mut_ptr()))?;
        params
            .get_uint(c"security-bits")
            .map_err(|_| crate::error::ErrorStack::drain())
    }

    /// Generate a key pair.
    ///
    /// # Errors
    pub fn generate(&mut self) -> Result<Pkey<Private>, ErrorStack> {
        let mut key: *mut sys::EVP_PKEY = std::ptr::null_mut();
        crate::ossl_call!(sys::EVP_PKEY_keygen(self.ptr, std::ptr::addr_of_mut!(key)))?;
        if key.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(unsafe { Pkey::from_ptr(key) })
    }
}

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

// ── Signer — streaming DigestSign ─────────────────────────────────────────────

/// Parameters for creating a [`Signer`] or [`Verifier`].
#[derive(Default)]
pub struct SignInit<'a> {
    /// Digest algorithm, or `None` for pre-hashed / `EdDSA` one-shot mode.
    pub digest: Option<&'a crate::digest::DigestAlg>,
    /// Optional parameters (e.g. RSA PSS salt length).
    pub params: Option<&'a crate::params::Params<'a>>,
}

/// Streaming `DigestSign` context.
///
/// Call `update` zero or more times, then `finish` to produce the signature.
pub struct Signer {
    ctx: crate::digest::DigestCtx,
    /// The key is kept alive for the duration of the signer.
    _key: Pkey<Private>,
}

impl Signer {
    /// Create a signer.
    ///
    /// # Errors
    pub fn new(key: &Pkey<Private>, init: &SignInit<'_>) -> Result<Self, ErrorStack> {
        let ctx = alloc_digest_ctx()?;
        // Resolve digest name for EVP_DigestSignInit_ex (NULL for Ed25519/one-shot).
        let md_name_ptr = if let Some(d) = init.digest {
            let p = unsafe { sys::OBJ_nid2sn(d.nid()) };
            if p.is_null() {
                return Err(ErrorStack::drain());
            }
            p
        } else {
            std::ptr::null()
        };
        let params_ptr = init
            .params
            .map_or(crate::params::null_params(), crate::params::Params::as_ptr);
        let rc = unsafe {
            sys::EVP_DigestSignInit_ex(
                ctx.as_ptr(),
                std::ptr::null_mut(),
                md_name_ptr,
                std::ptr::null_mut(),
                std::ptr::null(),
                key.ptr,
                params_ptr,
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(Signer {
            ctx,
            _key: key.clone(),
        })
    }

    /// Feed data into the hash.
    ///
    /// # Errors
    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_DigestSignUpdate(
            self.ctx.as_ptr(),
            data.as_ptr().cast(),
            data.len()
        ))
    }

    /// Finalise and return the signature.
    ///
    /// Not supported by pure one-shot algorithms such as Ed25519 — use
    /// [`sign_oneshot`](Self::sign_oneshot) for those.
    ///
    /// # Errors
    pub fn finish(&mut self) -> Result<Vec<u8>, ErrorStack> {
        // First call with null buf to get the required size.
        let mut siglen: usize = 0;
        let rc = unsafe {
            sys::EVP_DigestSignFinal(
                self.ctx.as_ptr(),
                std::ptr::null_mut(),
                std::ptr::addr_of_mut!(siglen),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        let mut sig = vec![0u8; siglen];
        let rc = unsafe {
            sys::EVP_DigestSignFinal(
                self.ctx.as_ptr(),
                sig.as_mut_ptr(),
                std::ptr::addr_of_mut!(siglen),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        sig.truncate(siglen);
        Ok(sig)
    }

    /// One-shot sign over `data`.
    ///
    /// Required for algorithms that do not support streaming (Ed25519, Ed448).
    /// For algorithms that do support streaming, prefer `update` + `finish`.
    ///
    /// # Errors
    pub fn sign_oneshot(&mut self, data: &[u8]) -> Result<Vec<u8>, ErrorStack> {
        // First call: query the required signature length.
        let mut siglen: usize = 0;
        let rc = unsafe {
            sys::EVP_DigestSign(
                self.ctx.as_ptr(),
                std::ptr::null_mut(),
                std::ptr::addr_of_mut!(siglen),
                data.as_ptr(),
                data.len(),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        let mut sig = vec![0u8; siglen];
        let rc = unsafe {
            sys::EVP_DigestSign(
                self.ctx.as_ptr(),
                sig.as_mut_ptr(),
                std::ptr::addr_of_mut!(siglen),
                data.as_ptr(),
                data.len(),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        sig.truncate(siglen);
        Ok(sig)
    }

    /// One-shot sign over `data` into a caller-provided buffer `sig`.
    ///
    /// The buffer must be at least as large as the maximum signature size for
    /// the key (use `EVP_PKEY_get_size` or the algorithm's known fixed length).
    /// For algorithms with a fixed signature size (e.g. ML-DSA, Ed25519), the
    /// caller can pre-allocate the exact size and avoid the null-output query.
    ///
    /// Returns the number of bytes written.  The caller should truncate `sig`
    /// to the returned length if the actual size may differ from the buffer size.
    ///
    /// # Errors
    pub fn sign_into(&mut self, data: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack> {
        let mut siglen = sig.len();
        let rc = unsafe {
            sys::EVP_DigestSign(
                self.ctx.as_ptr(),
                sig.as_mut_ptr(),
                std::ptr::addr_of_mut!(siglen),
                data.as_ptr(),
                data.len(),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(siglen)
    }
}

// ── Verifier — streaming DigestVerify ─────────────────────────────────────────

/// Streaming `DigestVerify` context.
pub struct Verifier {
    ctx: crate::digest::DigestCtx,
    _key: Pkey<Public>,
}

impl Verifier {
    /// Create a verifier.
    ///
    /// # Errors
    pub fn new(key: &Pkey<Public>, init: &SignInit<'_>) -> Result<Self, ErrorStack> {
        let ctx = alloc_digest_ctx()?;
        // Resolve digest name for EVP_DigestVerifyInit_ex (NULL for Ed25519/one-shot).
        let md_name_ptr = if let Some(d) = init.digest {
            let p = unsafe { sys::OBJ_nid2sn(d.nid()) };
            if p.is_null() {
                return Err(ErrorStack::drain());
            }
            p
        } else {
            std::ptr::null()
        };
        let params_ptr = init
            .params
            .map_or(crate::params::null_params(), crate::params::Params::as_ptr);
        let rc = unsafe {
            sys::EVP_DigestVerifyInit_ex(
                ctx.as_ptr(),
                std::ptr::null_mut(),
                md_name_ptr,
                std::ptr::null_mut(),
                std::ptr::null(),
                key.ptr,
                params_ptr,
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(Verifier {
            ctx,
            _key: key.clone(),
        })
    }

    /// Feed data into the hash.
    ///
    /// # Errors
    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_DigestVerifyUpdate(
            self.ctx.as_ptr(),
            data.as_ptr().cast(),
            data.len()
        ))
    }

    /// Verify `signature` against all data fed via `update`.
    ///
    /// Returns `Ok(true)` if valid, `Ok(false)` if the signature is incorrect,
    /// or `Err` on a fatal OpenSSL error.
    ///
    /// Not supported by pure one-shot algorithms such as Ed25519 — use
    /// [`verify_oneshot`](Self::verify_oneshot) for those.
    ///
    /// # Errors
    pub fn verify(&mut self, signature: &[u8]) -> Result<bool, ErrorStack> {
        let rc = unsafe {
            sys::EVP_DigestVerifyFinal(self.ctx.as_ptr(), signature.as_ptr(), signature.len())
        };
        match rc {
            1 => Ok(true),
            0 => Ok(false),
            _ => Err(ErrorStack::drain()),
        }
    }

    /// One-shot verify `signature` over `data`.
    ///
    /// Required for algorithms that do not support streaming (Ed25519, Ed448).
    ///
    /// # Errors
    pub fn verify_oneshot(&mut self, data: &[u8], signature: &[u8]) -> Result<bool, ErrorStack> {
        let rc = unsafe {
            sys::EVP_DigestVerify(
                self.ctx.as_ptr(),
                signature.as_ptr(),
                signature.len(),
                data.as_ptr(),
                data.len(),
            )
        };
        match rc {
            1 => Ok(true),
            0 => Ok(false),
            _ => Err(ErrorStack::drain()),
        }
    }
}

// ── Internal helper: allocate an EVP_MD_CTX for DigestSign/Verify ─────────────

/// Allocate a fresh, algorithm-unbound `EVP_MD_CTX` for use with
/// `EVP_DigestSign*Init_ex` / `EVP_DigestVerify*Init_ex`.
fn alloc_digest_ctx() -> Result<crate::digest::DigestCtx, ErrorStack> {
    let ctx_ptr = unsafe { sys::EVP_MD_CTX_new() };
    if ctx_ptr.is_null() {
        return Err(ErrorStack::drain());
    }
    // SAFETY: DigestCtx takes ownership; ptr is valid and non-null.
    Ok(unsafe { crate::digest::DigestCtx::from_ptr(ctx_ptr) })
}

// ── DeriveCtx — ECDH / DH key agreement ──────────────────────────────────────

/// Asymmetric key-agreement context (`EVP_PKEY_CTX` in derive mode).
pub struct DeriveCtx {
    ptr: *mut sys::EVP_PKEY_CTX,
}

impl DeriveCtx {
    /// Create a derive context from a private key.
    ///
    /// # Errors
    pub fn new(key: &Pkey<Private>) -> Result<Self, ErrorStack> {
        let ptr = unsafe {
            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        crate::ossl_call!(sys::EVP_PKEY_derive_init(ptr)).map_err(|e| {
            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
            e
        })?;
        Ok(DeriveCtx { ptr })
    }

    /// Set the peer's public key.
    ///
    /// # Errors
    pub fn set_peer(&mut self, peer: &Pkey<Public>) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_PKEY_derive_set_peer(self.ptr, peer.ptr))
    }

    /// Derive the shared secret into `out`.
    ///
    /// Returns the number of bytes written.
    ///
    /// # Errors
    pub fn derive(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack> {
        let mut len = out.len();
        crate::ossl_call!(sys::EVP_PKEY_derive(
            self.ptr,
            out.as_mut_ptr(),
            std::ptr::addr_of_mut!(len)
        ))?;
        Ok(len)
    }

    /// Query the required output length (call with empty slice).
    ///
    /// # Errors
    pub fn derive_len(&mut self) -> Result<usize, ErrorStack> {
        let mut len: usize = 0;
        crate::ossl_call!(sys::EVP_PKEY_derive(
            self.ptr,
            std::ptr::null_mut(),
            std::ptr::addr_of_mut!(len)
        ))?;
        Ok(len)
    }
}

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

// ── PkeyEncryptCtx / PkeyDecryptCtx ──────────────────────────────────────────

/// RSA asymmetric encryption context.
pub struct PkeyEncryptCtx {
    ptr: *mut sys::EVP_PKEY_CTX,
}

impl PkeyEncryptCtx {
    /// Create an encryption context from a public key.
    ///
    /// `params` is applied immediately after init if `Some`.  Typical use:
    /// ```ignore
    /// let oaep = ParamBuilder::new()?.push_utf8_string(c"pad-mode", c"oaep")?.build()?;
    /// let ctx = PkeyEncryptCtx::new(&pub_key, Some(&oaep))?;
    /// ```
    ///
    /// # Errors
    pub fn new(
        key: &Pkey<Public>,
        params: Option<&crate::params::Params<'_>>,
    ) -> Result<Self, ErrorStack> {
        let ptr = unsafe {
            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        crate::ossl_call!(sys::EVP_PKEY_encrypt_init(ptr)).map_err(|e| {
            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
            e
        })?;
        let ctx = PkeyEncryptCtx { ptr };
        if let Some(p) = params {
            crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(ctx.ptr, p.as_ptr())).map_err(|e| {
                unsafe { sys::EVP_PKEY_CTX_free(ptr) };
                e
            })?;
        }
        Ok(ctx)
    }

    /// Configure parameters (e.g. RSA padding mode).
    ///
    /// # Errors
    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ptr, params.as_ptr()))
    }

    /// Encrypt `plaintext` into `ciphertext`.
    ///
    /// Returns the number of bytes written.
    ///
    /// # Errors
    pub fn encrypt(
        &mut self,
        plaintext: &[u8],
        ciphertext: &mut [u8],
    ) -> Result<usize, ErrorStack> {
        let mut outlen = ciphertext.len();
        crate::ossl_call!(sys::EVP_PKEY_encrypt(
            self.ptr,
            ciphertext.as_mut_ptr(),
            std::ptr::addr_of_mut!(outlen),
            plaintext.as_ptr(),
            plaintext.len()
        ))?;
        Ok(outlen)
    }

    /// Query the ciphertext length for a given plaintext length.
    ///
    /// # Errors
    pub fn encrypt_len(&mut self, plaintext_len: usize) -> Result<usize, ErrorStack> {
        let mut outlen: usize = 0;
        crate::ossl_call!(sys::EVP_PKEY_encrypt(
            self.ptr,
            std::ptr::null_mut(),
            std::ptr::addr_of_mut!(outlen),
            std::ptr::null(),
            plaintext_len
        ))?;
        Ok(outlen)
    }
}

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

/// RSA asymmetric decryption context.
pub struct PkeyDecryptCtx {
    ptr: *mut sys::EVP_PKEY_CTX,
}

impl PkeyDecryptCtx {
    /// Create a decryption context from a private key.
    ///
    /// `params` is applied immediately after init if `Some`.
    ///
    /// # Errors
    pub fn new(
        key: &Pkey<Private>,
        params: Option<&crate::params::Params<'_>>,
    ) -> Result<Self, ErrorStack> {
        let ptr = unsafe {
            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        crate::ossl_call!(sys::EVP_PKEY_decrypt_init(ptr)).map_err(|e| {
            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
            e
        })?;
        let ctx = PkeyDecryptCtx { ptr };
        if let Some(p) = params {
            crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(ctx.ptr, p.as_ptr())).map_err(|e| {
                unsafe { sys::EVP_PKEY_CTX_free(ptr) };
                e
            })?;
        }
        Ok(ctx)
    }

    /// Configure parameters.
    ///
    /// # Errors
    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ptr, params.as_ptr()))
    }

    /// Decrypt `ciphertext` into `plaintext`.
    ///
    /// Returns the number of bytes written.
    ///
    /// # Errors
    pub fn decrypt(
        &mut self,
        ciphertext: &[u8],
        plaintext: &mut [u8],
    ) -> Result<usize, ErrorStack> {
        let mut outlen = plaintext.len();
        crate::ossl_call!(sys::EVP_PKEY_decrypt(
            self.ptr,
            plaintext.as_mut_ptr(),
            std::ptr::addr_of_mut!(outlen),
            ciphertext.as_ptr(),
            ciphertext.len()
        ))?;
        Ok(outlen)
    }
}

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

// ── EncapCtx / DecapCtx — KEM (OpenSSL 3.2+) ────────────────────────────────

/// KEM encapsulation output.
#[cfg(ossl320)]
pub struct EncapResult {
    /// Wrapped key (encoded shared secret).
    pub wrapped_key: Vec<u8>,
    /// Shared secret (plaintext).
    pub shared_secret: Vec<u8>,
}

/// KEM encapsulation context (recipient's public key).
#[cfg(ossl320)]
pub struct EncapCtx {
    ptr: *mut sys::EVP_PKEY_CTX,
}

#[cfg(ossl320)]
impl EncapCtx {
    /// Create a KEM encapsulation context from the recipient's public key.
    ///
    /// # Errors
    pub fn new(key: &Pkey<Public>) -> Result<Self, ErrorStack> {
        let ptr = unsafe {
            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        crate::ossl_call!(sys::EVP_PKEY_encapsulate_init(ptr, std::ptr::null())).map_err(|e| {
            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
            e
        })?;
        Ok(EncapCtx { ptr })
    }

    /// Perform encapsulation, returning the wrapped key and shared secret.
    ///
    /// # Errors
    pub fn encapsulate(&mut self) -> Result<EncapResult, ErrorStack> {
        let mut wkeylen: usize = 0;
        let mut sslen: usize = 0;
        // Query lengths first.
        let rc = unsafe {
            sys::EVP_PKEY_encapsulate(
                self.ptr,
                std::ptr::null_mut(),
                std::ptr::addr_of_mut!(wkeylen),
                std::ptr::null_mut(),
                std::ptr::addr_of_mut!(sslen),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        let mut wrapped_key = vec![0u8; wkeylen];
        let mut shared_secret = vec![0u8; sslen];
        crate::ossl_call!(sys::EVP_PKEY_encapsulate(
            self.ptr,
            wrapped_key.as_mut_ptr(),
            std::ptr::addr_of_mut!(wkeylen),
            shared_secret.as_mut_ptr(),
            std::ptr::addr_of_mut!(sslen)
        ))?;
        wrapped_key.truncate(wkeylen);
        shared_secret.truncate(sslen);
        Ok(EncapResult {
            wrapped_key,
            shared_secret,
        })
    }
}

#[cfg(ossl320)]
impl Drop for EncapCtx {
    fn drop(&mut self) {
        unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
    }
}

/// KEM decapsulation context (recipient's private key).
#[cfg(ossl320)]
pub struct DecapCtx {
    ptr: *mut sys::EVP_PKEY_CTX,
}

#[cfg(ossl320)]
impl DecapCtx {
    /// Create a KEM decapsulation context from the recipient's private key.
    ///
    /// # Errors
    pub fn new(key: &Pkey<Private>) -> Result<Self, ErrorStack> {
        let ptr = unsafe {
            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        crate::ossl_call!(sys::EVP_PKEY_decapsulate_init(ptr, std::ptr::null())).map_err(|e| {
            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
            e
        })?;
        Ok(DecapCtx { ptr })
    }

    /// Recover the shared secret from a wrapped key.
    ///
    /// # Errors
    pub fn decapsulate(&mut self, wrapped_key: &[u8]) -> Result<Vec<u8>, ErrorStack> {
        let mut sslen: usize = 0;
        // Query output length.
        let rc = unsafe {
            sys::EVP_PKEY_decapsulate(
                self.ptr,
                std::ptr::null_mut(),
                std::ptr::addr_of_mut!(sslen),
                wrapped_key.as_ptr(),
                wrapped_key.len(),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        let mut ss = vec![0u8; sslen];
        crate::ossl_call!(sys::EVP_PKEY_decapsulate(
            self.ptr,
            ss.as_mut_ptr(),
            std::ptr::addr_of_mut!(sslen),
            wrapped_key.as_ptr(),
            wrapped_key.len()
        ))?;
        ss.truncate(sslen);
        Ok(ss)
    }
}

#[cfg(ossl320)]
impl Drop for DecapCtx {
    fn drop(&mut self) {
        unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
    }
}

// ── RawSigner — pre-hashed / no-digest signing ───────────────────────────────

/// Raw (no-digest) signing context wrapping `EVP_PKEY_CTX` after `EVP_PKEY_sign_init`.
///
/// Use this for algorithms where the caller has already hashed the data, such
/// as raw ECDSA (data is the hash) or raw RSA with explicit padding.  For
/// algorithms that hash internally, use [`Signer`] or `MessageSigner`.
///
/// The context is reusable: after a successful [`sign`](Self::sign) call the
/// init state is preserved, so the same padding parameters apply to further
/// sign calls with the same key.
pub struct RawSigner {
    ctx: *mut sys::EVP_PKEY_CTX,
}

// SAFETY: EVP_PKEY_CTX is not thread-safe on a shared pointer, but
// RawSigner owns its ctx exclusively and &mut self enforces single-caller.
unsafe impl Send for RawSigner {}

impl RawSigner {
    /// Create and initialise a sign context (`EVP_PKEY_CTX_new_from_pkey` +
    /// `EVP_PKEY_sign_init`).
    ///
    /// Pass `libctx = Some(ctx)` to restrict provider lookup to that library
    /// context; `None` uses the key's own library context.
    ///
    /// # Errors
    pub fn new(
        key: &Pkey<Private>,
        libctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
    ) -> Result<Self, ErrorStack> {
        let lctx = libctx.map_or(std::ptr::null_mut(), |c| c.as_ptr());
        let ptr = unsafe { sys::EVP_PKEY_CTX_new_from_pkey(lctx, key.ptr, std::ptr::null()) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        crate::ossl_call!(sys::EVP_PKEY_sign_init(ptr)).map_err(|e| {
            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
            e
        })?;
        Ok(RawSigner { ctx: ptr })
    }

    /// Apply parameters after init (e.g. RSA padding mode, salt length).
    ///
    /// # Errors
    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
        // SAFETY:
        // - self.ctx is non-null (constructor invariant)
        // - params.as_ptr() is valid for the duration of this call
        // - &mut self ensures exclusive access; no concurrent reads or writes
        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ctx, params.as_ptr()))
    }

    /// Retrieve parameter values from this sign context.
    ///
    /// Build a [`Params`][crate::params::Params] with placeholder values for the
    /// keys you want, call this method, then read back with `params.get_*`.
    ///
    /// Useful for reading operation-context parameters such as the current RSA
    /// padding mode or signature algorithm identifier after initialisation.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `EVP_PKEY_CTX_get_params` fails.
    pub fn get_params(&self, params: &mut crate::params::Params<'_>) -> Result<(), ErrorStack> {
        // SAFETY:
        // - self.ctx is non-null (constructor invariant)
        // - params.as_mut_ptr() is valid for the duration of this call
        // - &self ensures no concurrent mutable access to self.ctx
        crate::ossl_call!(sys::EVP_PKEY_CTX_get_params(self.ctx, params.as_mut_ptr()))
    }

    /// Return the security strength of the key operation in bits.
    ///
    /// Reads `OSSL_PKEY_PARAM_SECURITY_BITS` (`"security-bits"`) from the sign
    /// context.  Available after `EVP_PKEY_sign_init` on contexts backed by a key.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the context does not support this parameter.
    pub fn security_bits(&self) -> Result<u32, ErrorStack> {
        let mut params = crate::params::ParamBuilder::new()?
            .push_uint(c"security-bits", 0)?
            .build()?;
        // SAFETY: same as get_params — self.ctx is non-null, params pointer is valid.
        crate::ossl_call!(sys::EVP_PKEY_CTX_get_params(self.ctx, params.as_mut_ptr()))?;
        params
            .get_uint(c"security-bits")
            .map_err(|_| crate::error::ErrorStack::drain())
    }

    /// Query the signature output size for the given input length.
    ///
    /// Calls `EVP_PKEY_sign` with a null output pointer — does not consume
    /// the signing state.
    ///
    /// # Errors
    pub fn sign_len(&mut self, tbs_len: usize) -> Result<usize, ErrorStack> {
        let mut siglen: usize = 0;
        crate::ossl_call!(sys::EVP_PKEY_sign(
            self.ctx,
            std::ptr::null_mut(),
            std::ptr::addr_of_mut!(siglen),
            std::ptr::null(),
            tbs_len,
        ))?;
        Ok(siglen)
    }

    /// Sign pre-hashed data into `sig`.  Returns the number of bytes written.
    ///
    /// `sig.len()` must be >= `sign_len(tbs.len())`.
    ///
    /// # Errors
    pub fn sign(&mut self, tbs: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack> {
        let mut siglen = sig.len();
        crate::ossl_call!(sys::EVP_PKEY_sign(
            self.ctx,
            sig.as_mut_ptr(),
            std::ptr::addr_of_mut!(siglen),
            tbs.as_ptr(),
            tbs.len(),
        ))?;
        Ok(siglen)
    }

    /// Sign pre-hashed data, allocating the output buffer.
    ///
    /// Convenience wrapper around [`sign_len`](Self::sign_len) + [`sign`](Self::sign).
    ///
    /// # Errors
    pub fn sign_alloc(&mut self, tbs: &[u8]) -> Result<Vec<u8>, ErrorStack> {
        let siglen = self.sign_len(tbs.len())?;
        let mut sig = vec![0u8; siglen];
        let written = self.sign(tbs, &mut sig)?;
        sig.truncate(written);
        Ok(sig)
    }
}

impl Drop for RawSigner {
    fn drop(&mut self) {
        unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
    }
}

// ── RawVerifier — pre-hashed / no-digest verification ────────────────────────

/// Raw (no-digest) verification context wrapping `EVP_PKEY_CTX` after
/// `EVP_PKEY_verify_init`.
///
/// Mirror of [`RawSigner`] for the verification side.
pub struct RawVerifier {
    ctx: *mut sys::EVP_PKEY_CTX,
}

unsafe impl Send for RawVerifier {}

impl RawVerifier {
    /// Create and initialise a verify context.
    ///
    /// # Errors
    pub fn new<T: HasPublic>(
        key: &Pkey<T>,
        libctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
    ) -> Result<Self, ErrorStack> {
        let lctx = libctx.map_or(std::ptr::null_mut(), |c| c.as_ptr());
        let ptr = unsafe { sys::EVP_PKEY_CTX_new_from_pkey(lctx, key.ptr, std::ptr::null()) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        crate::ossl_call!(sys::EVP_PKEY_verify_init(ptr)).map_err(|e| {
            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
            e
        })?;
        Ok(RawVerifier { ctx: ptr })
    }

    /// Apply parameters after init (e.g. RSA padding mode).
    ///
    /// # Errors
    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ctx, params.as_ptr()))
    }

    /// Verify `sig` against pre-hashed `tbs`.  Returns `Ok(())` if valid.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the signature is invalid or on any OpenSSL error.
    pub fn verify(&mut self, tbs: &[u8], sig: &[u8]) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_PKEY_verify(
            self.ctx,
            sig.as_ptr(),
            sig.len(),
            tbs.as_ptr(),
            tbs.len(),
        ))
    }
}

impl Drop for RawVerifier {
    fn drop(&mut self) {
        unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
    }
}

// ── SigAlg — EVP_SIGNATURE algorithm descriptor (OpenSSL 3.2+) ──────────────

/// Algorithm descriptor for `EVP_SIGNATURE` (OpenSSL 3.2+).
///
/// Mirrors `DigestAlg` / `CipherAlg` / `MacAlg` in naming and lifecycle.
/// Used with [`MessageSigner`] and [`MessageVerifier`] for algorithms such
/// as ML-DSA, SLH-DSA, and Ed25519/Ed448 with context strings.
#[cfg(ossl320)]
pub struct SigAlg {
    ptr: *mut sys::EVP_SIGNATURE,
}

// SAFETY: EVP_SIGNATURE is reference-counted.
#[cfg(ossl320)]
unsafe impl Send for SigAlg {}
#[cfg(ossl320)]
unsafe impl Sync for SigAlg {}

#[cfg(ossl320)]
impl SigAlg {
    /// Fetch an `EVP_SIGNATURE` by name from the default library context.
    ///
    /// Example names: `c"ML-DSA-44"`, `c"ED25519"`, `c"SLH-DSA-SHA2-128s"`.
    ///
    /// # Errors
    pub fn fetch(
        name: &std::ffi::CStr,
        props: Option<&std::ffi::CStr>,
    ) -> Result<Self, ErrorStack> {
        let props_ptr = props.map_or(std::ptr::null(), std::ffi::CStr::as_ptr);
        let ptr =
            unsafe { sys::EVP_SIGNATURE_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(SigAlg { ptr })
    }

    /// Fetch an `EVP_SIGNATURE` by name within a specific library context.
    ///
    /// # Errors
    pub fn fetch_in(
        ctx: &Arc<crate::lib_ctx::LibCtx>,
        name: &std::ffi::CStr,
        props: Option<&std::ffi::CStr>,
    ) -> Result<Self, ErrorStack> {
        let props_ptr = props.map_or(std::ptr::null(), std::ffi::CStr::as_ptr);
        let ptr = unsafe { sys::EVP_SIGNATURE_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(SigAlg { ptr })
    }
}

#[cfg(ossl320)]
impl Clone for SigAlg {
    fn clone(&self) -> Self {
        unsafe { sys::EVP_SIGNATURE_up_ref(self.ptr) };
        SigAlg { ptr: self.ptr }
    }
}

#[cfg(ossl320)]
impl Drop for SigAlg {
    fn drop(&mut self) {
        unsafe { sys::EVP_SIGNATURE_free(self.ptr) };
    }
}

// ── MessageSigner — EVP_PKEY_sign_message_* streaming sign (OpenSSL 3.2+) ────

/// Stateful signing context using `EVP_PKEY_sign_message_*` (OpenSSL 3.2+).
///
/// Used for algorithms that do not use a separate internal digest (`ML-DSA`,
/// `SLH-DSA`, `Ed25519` with context strings).  Unlike [`Signer`], the
/// algorithm is specified as a [`SigAlg`] rather than a digest name.
///
/// Call [`update`](Self::update) zero or more times (if the algorithm supports
/// streaming — check with [`supports_streaming`](Self::supports_streaming)),
/// then [`finish`](Self::finish) to produce the signature.  For algorithms
/// that only support one-shot operation, use [`sign_oneshot`](Self::sign_oneshot).
#[cfg(ossl320)]
pub struct MessageSigner {
    ctx: *mut sys::EVP_PKEY_CTX,
}

#[cfg(ossl320)]
unsafe impl Send for MessageSigner {}

#[cfg(ossl320)]
impl MessageSigner {
    /// Create and initialise a message-sign context.
    ///
    /// `alg` is consumed by the init call; pass a clone if you need to reuse it.
    /// `params` sets algorithm-specific options (e.g. context string for Ed25519).
    ///
    /// # Errors
    pub fn new(
        key: &Pkey<Private>,
        alg: &mut SigAlg,
        params: Option<&crate::params::Params<'_>>,
    ) -> Result<Self, ErrorStack> {
        let ptr = unsafe {
            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
        crate::ossl_call!(sys::EVP_PKEY_sign_message_init(ptr, alg.ptr, params_ptr)).map_err(
            |e| {
                unsafe { sys::EVP_PKEY_CTX_free(ptr) };
                e
            },
        )?;
        Ok(MessageSigner { ctx: ptr })
    }

    /// Probe whether this algorithm backend supports incremental `update` calls.
    ///
    /// Calls `EVP_PKEY_sign_message_update` with an empty input, bracketed by
    /// `ERR_set_mark` / `ERR_pop_to_mark` so that a failure does not leave
    /// entries on the error queue.  Returns `true` if streaming is supported.
    ///
    /// If this returns `false`, use [`sign_oneshot`](Self::sign_oneshot) instead.
    pub fn supports_streaming(&mut self) -> bool {
        // Probe: feed 0 bytes and see if the algorithm accepts it.
        // ERR mark/pop ensures the error queue is clean regardless of outcome.
        unsafe { sys::ERR_set_mark() };
        let probe: [u8; 0] = [];
        let rc = unsafe { sys::EVP_PKEY_sign_message_update(self.ctx, probe.as_ptr(), 0) };
        unsafe { sys::ERR_pop_to_mark() };
        rc == 1
    }

    /// Feed `data` into the signing operation.
    ///
    /// Returns `Err` if the algorithm does not support streaming — use
    /// [`sign_oneshot`](Self::sign_oneshot) in that case.
    ///
    /// # Errors
    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_PKEY_sign_message_update(
            self.ctx,
            data.as_ptr(),
            data.len(),
        ))
    }

    /// Query the signature output length.
    ///
    /// Calls `EVP_PKEY_sign_message_final` with a null buffer — does not
    /// consume the signing state.
    ///
    /// # Errors
    pub fn sig_len(&mut self) -> Result<usize, ErrorStack> {
        let mut siglen: usize = 0;
        crate::ossl_call!(sys::EVP_PKEY_sign_message_final(
            self.ctx,
            std::ptr::null_mut(),
            std::ptr::addr_of_mut!(siglen),
        ))?;
        Ok(siglen)
    }

    /// Finalise and produce the signature into `sig`.
    ///
    /// Consumes `self` because the context is finalised.  Call
    /// [`sig_len`](Self::sig_len) first to size the buffer.  Returns the
    /// number of bytes written.
    ///
    /// # Errors
    pub fn finish(self, sig: &mut [u8]) -> Result<usize, ErrorStack> {
        let mut siglen = sig.len();
        let rc = unsafe {
            sys::EVP_PKEY_sign_message_final(
                self.ctx,
                sig.as_mut_ptr(),
                std::ptr::addr_of_mut!(siglen),
            )
        };
        // self is dropped here → EVP_PKEY_CTX_free via Drop.
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(siglen)
    }

    /// One-shot sign: feed `data` then finalise into `sig`.
    ///
    /// Consumes `self`.  Use this for algorithms that do not support
    /// streaming (`supports_streaming` returns `false`).
    ///
    /// # Errors
    pub fn sign_oneshot(self, data: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack> {
        // Feed all data, then finalise.  Both ops share the same ctx.
        let rc_upd =
            unsafe { sys::EVP_PKEY_sign_message_update(self.ctx, data.as_ptr(), data.len()) };
        if rc_upd != 1 {
            // self dropped here → ctx freed.
            return Err(ErrorStack::drain());
        }
        let mut siglen = sig.len();
        let rc_fin = unsafe {
            sys::EVP_PKEY_sign_message_final(
                self.ctx,
                sig.as_mut_ptr(),
                std::ptr::addr_of_mut!(siglen),
            )
        };
        // self dropped here → ctx freed.
        if rc_fin != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(siglen)
    }

    /// One-shot sign over `data` using `EVP_PKEY_sign`.
    ///
    /// The context must have been initialised with `EVP_PKEY_sign_message_init`
    /// (this type's constructor); `EVP_PKEY_sign` accepts both
    /// `EVP_PKEY_OP_SIGN` and `EVP_PKEY_OP_SIGNMSG` operation modes.
    ///
    /// When `sig` is `None` the call is a **cheap length query**: for ML-DSA
    /// and other algorithms with a fixed output size, no cryptographic
    /// computation is performed.  When `sig` is `Some(buf)` the signature is
    /// written and the number of bytes actually written is returned.
    ///
    /// The context is *not* consumed so the same `MessageSigner` may be reused
    /// across a size-query + actual-sign pair without re-initialisation.
    ///
    /// Contrast with [`sign_oneshot`](Self::sign_oneshot): `sign_oneshot`
    /// consumes `self` and always writes a signature; `sign` borrows `self`,
    /// can query the required length cheaply (pass `sig = None`), and can be
    /// called multiple times on the same context.
    ///
    /// # Errors
    pub fn sign(&mut self, data: &[u8], sig: Option<&mut [u8]>) -> Result<usize, ErrorStack> {
        let (sig_ptr, mut siglen) = match sig {
            Some(buf) => (buf.as_mut_ptr(), buf.len()),
            None => (std::ptr::null_mut(), 0usize),
        };
        crate::ossl_call!(sys::EVP_PKEY_sign(
            self.ctx,
            sig_ptr,
            std::ptr::addr_of_mut!(siglen),
            data.as_ptr(),
            data.len(),
        ))?;
        Ok(siglen)
    }
}

#[cfg(ossl320)]
impl Drop for MessageSigner {
    fn drop(&mut self) {
        unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
    }
}

// ── MessageVerifier — EVP_PKEY_verify_message_* streaming verify (OpenSSL 3.2+)

/// Stateful verification context using `EVP_PKEY_verify_message_*` (OpenSSL 3.2+).
///
/// Mirror of [`MessageSigner`] for the verification side.
///
/// For streaming mode: call [`set_signature`](Self::set_signature), then
/// [`update`](Self::update) zero or more times, then [`finish`](Self::finish).
/// For one-shot: call [`verify_oneshot`](Self::verify_oneshot).
#[cfg(ossl320)]
pub struct MessageVerifier {
    ctx: *mut sys::EVP_PKEY_CTX,
}

#[cfg(ossl320)]
unsafe impl Send for MessageVerifier {}

#[cfg(ossl320)]
impl MessageVerifier {
    /// Create and initialise a message-verify context.
    ///
    /// # Errors
    pub fn new<T: HasPublic>(
        key: &Pkey<T>,
        alg: &mut SigAlg,
        params: Option<&crate::params::Params<'_>>,
    ) -> Result<Self, ErrorStack> {
        let ptr = unsafe {
            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
        };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
        crate::ossl_call!(sys::EVP_PKEY_verify_message_init(ptr, alg.ptr, params_ptr)).map_err(
            |e| {
                unsafe { sys::EVP_PKEY_CTX_free(ptr) };
                e
            },
        )?;
        Ok(MessageVerifier { ctx: ptr })
    }

    /// Apply parameters after init.
    ///
    /// # Errors
    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ctx, params.as_ptr()))
    }

    /// Supply the signature to verify against (required before streaming `finish`).
    ///
    /// Calls `EVP_PKEY_CTX_set_signature`.  Not needed for
    /// [`verify_oneshot`](Self::verify_oneshot) which sets it internally.
    ///
    /// # Errors
    pub fn set_signature(&mut self, sig: &[u8]) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_PKEY_CTX_set_signature(
            self.ctx,
            sig.as_ptr(),
            sig.len()
        ))
    }

    /// Probe whether this algorithm supports incremental `update` calls.
    ///
    /// Uses the same `ERR_set_mark` / `ERR_pop_to_mark` probe as
    /// [`MessageSigner::supports_streaming`].
    pub fn supports_streaming(&mut self) -> bool {
        unsafe { sys::ERR_set_mark() };
        let probe: [u8; 0] = [];
        let rc = unsafe { sys::EVP_PKEY_verify_message_update(self.ctx, probe.as_ptr(), 0) };
        unsafe { sys::ERR_pop_to_mark() };
        rc == 1
    }

    /// Feed `data` into the verification operation.
    ///
    /// # Errors
    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
        crate::ossl_call!(sys::EVP_PKEY_verify_message_update(
            self.ctx,
            data.as_ptr(),
            data.len(),
        ))
    }

    /// Finalise and verify.
    ///
    /// The signature must have been set via [`set_signature`](Self::set_signature).
    /// Consumes `self`.  Returns `Ok(())` if the signature is valid.
    ///
    /// # Errors
    pub fn finish(self) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::EVP_PKEY_verify_message_final(self.ctx) };
        // self dropped here → ctx freed.
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// One-shot verify `sig` over `data`.
    ///
    /// Sets the signature, feeds all data, and finalises.  Consumes `self`.
    ///
    /// # Errors
    pub fn verify_oneshot(self, data: &[u8], sig: &[u8]) -> Result<(), ErrorStack> {
        let rc_set = unsafe { sys::EVP_PKEY_CTX_set_signature(self.ctx, sig.as_ptr(), sig.len()) };
        if rc_set != 1 {
            return Err(ErrorStack::drain());
        }
        let rc_upd =
            unsafe { sys::EVP_PKEY_verify_message_update(self.ctx, data.as_ptr(), data.len()) };
        if rc_upd != 1 {
            return Err(ErrorStack::drain());
        }
        let rc_fin = unsafe { sys::EVP_PKEY_verify_message_final(self.ctx) };
        // self dropped here → ctx freed.
        if rc_fin != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// One-shot verify `sig` over `data` using `EVP_PKEY_verify`.
    ///
    /// The context must have been initialised with `EVP_PKEY_verify_message_init`
    /// (this type's constructor); `EVP_PKEY_verify` accepts both
    /// `EVP_PKEY_OP_VERIFY` and `EVP_PKEY_OP_VERIFYMSG` operation modes.
    ///
    /// Returns `Ok(true)` if the signature verifies, `Ok(false)` if it does
    /// not.  Fatal protocol or library errors are returned as `Err`.
    ///
    /// The context is *not* consumed and may be reused for further verifications.
    ///
    /// Contrast with [`verify_oneshot`](Self::verify_oneshot): `verify_oneshot`
    /// consumes `self`; `verify` borrows `self` and may be called repeatedly
    /// without re-creating the context.
    ///
    /// # Errors
    pub fn verify(&mut self, data: &[u8], sig: &[u8]) -> Result<bool, ErrorStack> {
        let rc = unsafe {
            sys::EVP_PKEY_verify(self.ctx, sig.as_ptr(), sig.len(), data.as_ptr(), data.len())
        };
        match rc {
            1 => Ok(true),
            0 => Ok(false),
            _ => Err(ErrorStack::drain()),
        }
    }
}

#[cfg(ossl320)]
impl Drop for MessageVerifier {
    fn drop(&mut self) {
        unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
    }
}

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

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

    /// Generate an Ed25519 key pair; sign and verify "hello world".
    ///
    /// Ed25519 is a one-shot algorithm — uses `sign_oneshot` / `verify_oneshot`.
    #[test]
    fn ed25519_sign_verify() {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let priv_key = kgen.generate().unwrap();
        let pub_key = Pkey::<Public>::from(priv_key.clone());

        let msg = b"hello world";
        let init = SignInit::default();

        let mut signer = Signer::new(&priv_key, &init).unwrap();
        let sig = signer.sign_oneshot(msg).unwrap();
        assert!(!sig.is_empty());

        let mut verifier = Verifier::new(&pub_key, &init).unwrap();
        assert!(verifier.verify_oneshot(msg, &sig).unwrap());
    }

    /// Tampered message must fail verification.
    #[test]
    fn ed25519_verify_wrong_msg_fails() {
        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 signer = Signer::new(&priv_key, &SignInit::default()).unwrap();
        let sig = signer.sign_oneshot(b"correct").unwrap();

        let mut verifier = Verifier::new(&pub_key, &SignInit::default()).unwrap();
        assert!(!verifier.verify_oneshot(b"tampered", &sig).unwrap());
    }

    /// Generate an X25519 key pair; perform ECDH derive and confirm length.
    #[test]
    fn x25519_derive() {
        let mut kgen_a = KeygenCtx::new(c"X25519").unwrap();
        let priv_a = kgen_a.generate().unwrap();

        let mut kgen_b = KeygenCtx::new(c"X25519").unwrap();
        let priv_b = kgen_b.generate().unwrap();
        let pub_b = Pkey::<Public>::from(priv_b);

        let mut derive = DeriveCtx::new(&priv_a).unwrap();
        derive.set_peer(&pub_b).unwrap();
        let len = derive.derive_len().unwrap();
        assert_eq!(len, 32); // X25519 shared secret is always 32 bytes

        let mut ss = vec![0u8; len];
        let n = derive.derive(&mut ss).unwrap();
        assert_eq!(n, 32);
        assert_ne!(ss, [0u8; 32]);
    }

    /// Round-trip through PEM: generate Ed25519, export, re-import, verify key equality.
    #[test]
    fn pem_round_trip() {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let priv_key = kgen.generate().unwrap();

        let pem = priv_key.to_pem().unwrap();
        assert!(!pem.is_empty());
        assert!(pem.starts_with(b"-----BEGIN"));

        let priv_key2 = Pkey::<Private>::from_pem(&pem).unwrap();
        assert_eq!(priv_key.bits(), priv_key2.bits());
        assert!(priv_key.is_a(c"ED25519"));
    }

    /// Key metadata: Ed25519 should report 256 bits.
    #[test]
    fn ed25519_metadata() {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let key = kgen.generate().unwrap();
        assert_eq!(key.bits(), 256);
        assert_eq!(key.security_bits(), 128);
        assert!(key.is_a(c"ED25519"));
    }

    /// ML-DSA-44 sign and verify using `MessageSigner::sign` / `MessageVerifier::verify`.
    #[cfg(ossl320)]
    #[test]
    fn ml_dsa_44_sign_verify() {
        let mut kgen = KeygenCtx::new(c"ML-DSA-44").unwrap();
        let priv_key = kgen.generate().unwrap();
        let pub_key = Pkey::<Public>::from(priv_key.clone());

        let msg = b"hello ML-DSA-44";

        let mut alg = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
        let mut signer = MessageSigner::new(&priv_key, &mut alg, None).unwrap();
        // NULL size query must be cheap (FIPS 204 fixed 2420 bytes).
        let sig_len = signer.sign(msg, None).unwrap();
        assert_eq!(sig_len, 2420);
        let mut sig = vec![0u8; sig_len];
        let written = signer.sign(msg, Some(&mut sig)).unwrap();
        assert_eq!(written, 2420);

        let mut alg2 = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
        let mut ver = MessageVerifier::new(&pub_key, &mut alg2, None).unwrap();
        assert!(ver.verify(msg, &sig).unwrap());

        // Tampered signature must fail.
        let mut bad = sig.clone();
        bad[0] ^= 0xff;
        assert!(!ver.verify(msg, &bad).unwrap());
    }

    /// ML-DSA-44 sign with a context string (FIPS 204 domain separation).
    #[cfg(ossl320)]
    #[test]
    fn ml_dsa_44_sign_with_context() {
        let mut kgen = KeygenCtx::new(c"ML-DSA-44").unwrap();
        let priv_key = kgen.generate().unwrap();
        let pub_key = Pkey::<Public>::from(priv_key.clone());

        let msg = b"signed with context";
        let ctx_bytes = b"my-app-domain";

        let ctx_params = crate::params::ParamBuilder::new()
            .unwrap()
            .push_octet_slice(c"context-string", ctx_bytes)
            .unwrap()
            .build()
            .unwrap();

        let mut alg = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
        let mut signer = MessageSigner::new(&priv_key, &mut alg, Some(&ctx_params)).unwrap();
        let sig_len = signer.sign(msg, None).unwrap();
        let mut sig = vec![0u8; sig_len];
        signer.sign(msg, Some(&mut sig)).unwrap();

        // Same context: verify must succeed.
        let mut alg2 = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
        let mut ver = MessageVerifier::new(&pub_key, &mut alg2, Some(&ctx_params)).unwrap();
        assert!(ver.verify(msg, &sig).unwrap());

        // Different context: verify must fail.
        let other_params = crate::params::ParamBuilder::new()
            .unwrap()
            .push_octet_slice(c"context-string", b"other-domain")
            .unwrap()
            .build()
            .unwrap();
        let mut alg3 = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
        let mut ver_bad = MessageVerifier::new(&pub_key, &mut alg3, Some(&other_params)).unwrap();
        assert!(!ver_bad.verify(msg, &sig).unwrap());
    }

    /// `RawSigner` / `RawVerifier` round-trip with P-256 ECDSA over a pre-hashed digest.
    #[test]
    fn ecdsa_p256_raw_sign_verify() {
        // Generate P-256 key.
        let mut kgen = KeygenCtx::new(c"EC").unwrap();
        let params = crate::params::ParamBuilder::new()
            .unwrap()
            .push_utf8_string(c"group", c"P-256")
            .unwrap()
            .build()
            .unwrap();
        kgen.set_params(&params).unwrap();
        let priv_key = kgen.generate().unwrap();
        let pub_key = Pkey::<Public>::from(priv_key.clone());

        // SHA-256 hash of the message (32 bytes — the "pre-hash").
        let tbs: [u8; 32] = *b"0123456789abcdef0123456789abcdef";

        let mut signer = RawSigner::new(&priv_key, None).unwrap();
        let sig = signer.sign_alloc(&tbs).unwrap();
        assert!(!sig.is_empty());

        let mut verifier = RawVerifier::new(&pub_key, None).unwrap();
        verifier.verify(&tbs, &sig).unwrap();
    }

    /// `RawVerifier` rejects a tampered signature.
    #[test]
    fn ecdsa_p256_raw_verify_tampered_fails() {
        let mut kgen = KeygenCtx::new(c"EC").unwrap();
        let params = crate::params::ParamBuilder::new()
            .unwrap()
            .push_utf8_string(c"group", c"P-256")
            .unwrap()
            .build()
            .unwrap();
        kgen.set_params(&params).unwrap();
        let priv_key = kgen.generate().unwrap();
        let pub_key = Pkey::<Public>::from(priv_key.clone());

        let tbs: [u8; 32] = *b"0123456789abcdef0123456789abcdef";
        let mut signer = RawSigner::new(&priv_key, None).unwrap();
        let mut sig = signer.sign_alloc(&tbs).unwrap();
        // Flip a byte in the signature.
        if let Some(b) = sig.last_mut() {
            *b ^= 0xff;
        }

        let mut verifier = RawVerifier::new(&pub_key, None).unwrap();
        assert!(verifier.verify(&tbs, &sig).is_err());
    }

    /// `SigAlg`: fetch, clone, drop — verifies `EVP_SIGNATURE` lifecycle.
    ///
    /// Does not attempt `sign_message_final`: OpenSSL 3.5's built-in ML-DSA
    /// provider implements only the one-shot `EVP_DigestSign` path, not
    /// `OSSL_FUNC_SIGNATURE_SIGN_MESSAGE_FINAL`.  Actual ML-DSA signing is
    /// done via Signer (`DigestSign` with NULL digest).
    #[cfg(ossl320)]
    #[test]
    fn sig_alg_fetch_clone_drop() {
        let alg = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
        let alg2 = alg.clone();
        drop(alg);
        drop(alg2); // both up_ref / free paths exercised
    }

    /// `MessageSigner` construction and `supports_streaming` probe for Ed25519.
    ///
    /// Ed25519 supports `EVP_PKEY_sign_message_init`.  The streaming probe
    /// uses `ERR_set_mark` / `ERR_pop_to_mark` so it cannot leave error state.
    #[cfg(ossl320)]
    #[test]
    fn message_signer_construction_and_streaming_probe() {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let priv_key = kgen.generate().unwrap();

        let mut alg = SigAlg::fetch(c"ED25519", None).unwrap();
        let mut signer = MessageSigner::new(&priv_key, &mut alg, None).unwrap();

        // Probe must not crash or leave the error queue dirty.
        let _streaming = signer.supports_streaming();
        // Error queue must be empty after the probe.
        assert_eq!(crate::error::ErrorStack::drain().errors().count(), 0);
    }

    /// `MessageVerifier` construction for Ed25519.
    #[cfg(ossl320)]
    #[test]
    fn message_verifier_construction() {
        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 alg = SigAlg::fetch(c"ED25519", None).unwrap();
        let _verifier = MessageVerifier::new(&pub_key, &mut alg, None).unwrap();
    }

    /// Encrypted PEM round-trip: generate → `to_pem_encrypted` → `from_pem_passphrase`.
    #[test]
    fn encrypted_pem_round_trip() {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let key = kgen.generate().unwrap();

        let cipher = crate::cipher::CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
        let pem = key.to_pem_encrypted(&cipher, b"s3cret").unwrap();
        assert!(pem.starts_with(b"-----BEGIN ENCRYPTED PRIVATE KEY-----"));

        let key2 = Pkey::<Private>::from_pem_passphrase(&pem, b"s3cret").unwrap();
        assert!(key.public_eq(&key2));
    }

    /// Wrong passphrase must return an error, not silently load a garbage key.
    #[test]
    fn encrypted_pem_wrong_passphrase_fails() {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let key = kgen.generate().unwrap();

        let cipher = crate::cipher::CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
        let pem = key.to_pem_encrypted(&cipher, b"correct").unwrap();
        assert!(Pkey::<Private>::from_pem_passphrase(&pem, b"wrong").is_err());
    }

    /// PKCS#8 DER round-trip: generate → `to_pkcs8_der` → `from_der`.
    #[test]
    fn pkcs8_der_round_trip() {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let key = kgen.generate().unwrap();

        let der = key.to_pkcs8_der().unwrap();
        assert!(!der.is_empty());

        let key2 = Pkey::<Private>::from_der(&der).unwrap();
        assert!(key.public_eq(&key2));
    }

    #[test]
    fn pkey_max_output_size_ed25519() {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let key = kgen.generate().unwrap();
        // Ed25519 produces a fixed 64-byte signature.
        assert_eq!(key.max_output_size(), 64);
    }

    #[test]
    fn pkey_max_output_size_rsa() {
        let mut kgen = KeygenCtx::new(c"RSA").unwrap();
        let key = kgen.generate().unwrap();
        // RSA-2048: max output == key size in bytes (256).
        let size = key.max_output_size();
        assert!(size > 0, "RSA key must report non-zero max output size");
        assert_eq!(size, key.bits() as usize / 8);
    }

    /// `KeygenCtx::set_params` — set RSA bit length via params, generate, verify size.
    ///
    /// OpenSSL default RSA keygen produces 2048-bit keys; here we explicitly set
    /// `bits = 3072` and verify the generated key carries that size.
    #[test]
    fn pkey_ctx_set_params_rsa() {
        let mut kgen = KeygenCtx::new(c"RSA").unwrap();
        let params = crate::params::ParamBuilder::new()
            .unwrap()
            .push_uint(c"bits", 3072)
            .unwrap()
            .build()
            .unwrap();
        kgen.set_params(&params).unwrap();
        let key = kgen.generate().unwrap();
        assert_eq!(key.bits(), 3072, "generated RSA key must be 3072 bits");
    }

    /// `KeygenCtx::security_bits` — Ed25519 key object reports 128 security bits.
    ///
    /// `security_bits()` reads `security-bits` from the key context.  For Ed25519,
    /// the well-known security strength is 128 bits.  This test uses the
    /// `Pkey::security_bits` method (which wraps `EVP_PKEY_get_security_bits`)
    /// and the `KeygenCtx::security_bits` helper.  The keygen context method
    /// internally calls `EVP_PKEY_CTX_get_params`; the result depends on what the
    /// underlying provider exposes on a keygen context.
    #[test]
    fn pkey_ctx_security_bits() {
        // Verify that the key object itself reports the correct security level.
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let key = kgen.generate().unwrap();
        assert_eq!(
            key.security_bits(),
            128,
            "Ed25519 key must report 128 security bits"
        );

        // KeygenCtx::security_bits() is a best-effort getter; it may return Err if
        // the provider does not expose security-bits on a keygen context.  We just
        // verify it does not panic.
        let _ = KeygenCtx::new(c"ED25519").unwrap().security_bits();
    }

    /// `RawSigner::get_params` — read `pad-mode` from an RSA sign context.
    ///
    /// `EVP_PKEY_CTX_get_params` is most useful on operation (sign/verify)
    /// contexts where OpenSSL exposes per-operation configuration parameters.
    /// RSA sign contexts expose `pad-mode` as both gettable and settable.
    ///
    /// The placeholder string must be large enough to receive the returned value;
    /// we use a 32-char placeholder to accommodate all padding mode names.
    #[test]
    fn pkey_ctx_get_params_rsa_sign() {
        let mut kgen = KeygenCtx::new(c"RSA").unwrap();
        let key = kgen.generate().unwrap();
        let signer = RawSigner::new(&key, None).unwrap();

        // Query the default RSA padding mode; OpenSSL defaults to "pkcs1".
        // Provide a placeholder string of length >= the longest padding mode name.
        let placeholder = c"00000000000000000000000000000000"; // 32 chars
        let mut params = crate::params::ParamBuilder::new()
            .unwrap()
            .push_utf8_string(c"pad-mode", placeholder)
            .unwrap()
            .build()
            .unwrap();
        signer.get_params(&mut params).unwrap();
        // The returned pad-mode must be a non-empty string.
        let mode = params.get_utf8_string(c"pad-mode").unwrap();
        assert!(
            !mode.to_bytes().is_empty(),
            "RSA pad-mode must be non-empty"
        );
    }

    /// `Pkey<Public>::from_pem_in` loads a public key within an explicit `LibCtx`.
    #[test]
    fn pubkey_from_pem_in_roundtrip() {
        // Generate an EC key and export the public half to PEM.
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let priv_key = kgen.generate().unwrap();
        let pub_pem = Pkey::<Public>::from(priv_key).to_pem().unwrap();

        // Re-import via from_pem_in using the default lib ctx.
        let lib_ctx = Arc::new(crate::lib_ctx::LibCtx::new().unwrap());
        let pub_key = Pkey::<Public>::from_pem_in(&lib_ctx, &pub_pem).unwrap();

        // Sanity: key is usable for verification.
        assert!(!pub_key.to_pem().unwrap().is_empty());
    }

    /// Ed25519 has no separate digest — `default_digest_name()` must return `Ok(None)`.
    #[test]
    fn pkey_default_digest_name_ed25519() {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let key = kgen.generate().unwrap();
        let result = key.default_digest_name().unwrap();
        assert_eq!(
            result, None,
            "Ed25519 performs its own internal hashing; expected no external digest"
        );
    }

    /// RSA with a default digest should return `Ok(Some(name))` with a non-empty name.
    #[test]
    fn pkey_default_digest_name_rsa() {
        let mut kgen = KeygenCtx::new(c"RSA").unwrap();
        let key = kgen.generate().unwrap();
        let result = key.default_digest_name().unwrap();
        assert!(
            result.is_some(),
            "RSA key should have a default digest name; got None"
        );
        let name = result.unwrap();
        assert!(
            !name.is_empty(),
            "RSA default digest name must not be empty"
        );
        // OpenSSL 3.x reports SHA256 (or SHA-256) as the RSA default digest.
        assert!(
            name.to_ascii_uppercase().contains("SHA"),
            "Expected a SHA-family digest name, got: {name}"
        );
    }

    /// `Pkey::<Private>::from_der_in` — round-trip through `to_der` (PKCS#8) and
    /// back via the LibCtx-aware `from_der_in`, using an Ed25519 key.
    ///
    /// Verifies that the reloaded key's public component matches the original.
    #[test]
    fn pkey_private_from_der_in_ed25519() {
        let ctx = Arc::new(crate::lib_ctx::LibCtx::new().unwrap());

        let key = KeygenCtx::new(c"ED25519").unwrap().generate().unwrap();

        // Serialise to PKCS#8 DER (the format auto-detected by from_der_in).
        let der = key.to_pkcs8_der().unwrap();
        assert!(!der.is_empty());

        // Reload via the LibCtx-aware path.
        let key2 = Pkey::<Private>::from_der_in(&ctx, &der).unwrap();

        // Public components must be identical.
        assert!(
            key.public_eq(&key2),
            "reloaded private key must equal original"
        );
    }

    /// `Pkey::<Public>::from_der_in` — round-trip through `public_key_to_der`
    /// (`SubjectPublicKeyInfo`) and back via the LibCtx-aware `from_der_in`.
    #[test]
    fn pkey_public_from_der_in_ed25519() {
        let ctx = Arc::new(crate::lib_ctx::LibCtx::new().unwrap());

        let priv_key = KeygenCtx::new(c"ED25519").unwrap().generate().unwrap();
        let pub_key = Pkey::<Public>::from(priv_key.clone());

        // Serialise the public key to SubjectPublicKeyInfo DER.
        let der = pub_key.public_key_to_der().unwrap();
        assert!(!der.is_empty());

        // Reload via the LibCtx-aware path.
        let pub_key2 = Pkey::<Public>::from_der_in(&ctx, &der).unwrap();

        // Must match the original private key's public component.
        assert!(
            priv_key.public_eq(&pub_key2),
            "reloaded public key must equal original"
        );
    }

    /// `to_der_legacy` — generate an RSA key and verify that the legacy DER
    /// output is non-empty (RSA has a well-defined `RSAPrivateKey` legacy format).
    #[test]
    fn pkey_to_der_legacy_produces_non_empty() {
        let key = KeygenCtx::new(c"RSA").unwrap().generate().unwrap();

        let der = key.to_der_legacy().unwrap();
        assert!(!der.is_empty(), "legacy DER for RSA must be non-empty");
    }
}
