Skip to main content

native_ossl/
pkey.rs

1//! `Pkey<T>` — asymmetric key container and operations.
2//!
3//! Phase 5 delivers key loading/serialisation (5.1), keygen (5.2),
4//! sign/verify (5.3), derive (5.4), asymmetric encrypt/decrypt (5.5),
5//! and KEM encapsulate/decapsulate (5.6).
6//!
7//! # Type-state markers
8//!
9//! `Pkey<Private>`, `Pkey<Public>`, and `Pkey<Params>` statically prevent
10//! misuse (e.g. signing with a public key).  `HasPrivate: HasPublic` means
11//! every `Pkey<Private>` can also be used wherever `Pkey<Public>` is needed.
12
13use crate::error::ErrorStack;
14use crate::bio::{MemBio, MemBioBuf};
15use native_ossl_sys as sys;
16use std::marker::PhantomData;
17use std::sync::Arc;
18
19// ── Marker types and sealed trait hierarchy ───────────────────────────────────
20
21/// Marker: key holds public key material only.
22pub struct Public;
23/// Marker: key holds public + private key material.
24pub struct Private;
25/// Marker: key holds PKEY parameters only (e.g. EC group with no key).
26pub struct Params;
27
28mod sealed {
29    /// Sealed base: any key role.
30    pub trait HasParams {}
31    impl HasParams for super::Public {}
32    impl HasParams for super::Private {}
33    impl HasParams for super::Params {}
34
35    /// Sealed: key has public material.
36    pub trait HasPublic: HasParams {}
37    impl HasPublic for super::Public {}
38    impl HasPublic for super::Private {}
39
40    /// Sealed: key has private material.
41    pub trait HasPrivate: HasPublic {}
42    impl HasPrivate for super::Private {}
43}
44
45/// All key markers satisfy this bound.
46pub trait HasParams: sealed::HasParams {}
47impl<T: sealed::HasParams> HasParams for T {}
48
49/// Public key material is accessible (both `Public` and `Private` keys).
50pub trait HasPublic: sealed::HasPublic {}
51impl<T: sealed::HasPublic> HasPublic for T {}
52
53/// Private key material is accessible.
54pub trait HasPrivate: sealed::HasPrivate {}
55impl<T: sealed::HasPrivate> HasPrivate for T {}
56
57// ── Pkey<T> — key container ───────────────────────────────────────────────────
58
59/// An asymmetric key (`EVP_PKEY*`) with a compile-time role marker.
60///
61/// Cloneable via `EVP_PKEY_up_ref`; wrapping in `Arc<Pkey<T>>` is safe.
62pub struct Pkey<T> {
63    ptr: *mut sys::EVP_PKEY,
64    _role: PhantomData<T>,
65}
66
67// SAFETY: `EVP_PKEY` is reference-counted.
68unsafe impl<T> Send for Pkey<T> {}
69unsafe impl<T> Sync for Pkey<T> {}
70
71impl<T> Clone for Pkey<T> {
72    fn clone(&self) -> Self {
73        unsafe { sys::EVP_PKEY_up_ref(self.ptr) };
74        Pkey { ptr: self.ptr, _role: PhantomData }
75    }
76}
77
78impl<T> Drop for Pkey<T> {
79    fn drop(&mut self) {
80        unsafe { sys::EVP_PKEY_free(self.ptr) };
81    }
82}
83
84impl<T: HasParams> Pkey<T> {
85    /// Construct from a raw (owned) `EVP_PKEY*`.
86    ///
87    /// # Safety
88    ///
89    /// `ptr` must be a valid, non-null `EVP_PKEY*` that the caller is giving up ownership of.
90    #[must_use]
91    pub unsafe fn from_ptr(ptr: *mut sys::EVP_PKEY) -> Self {
92        Pkey { ptr, _role: PhantomData }
93    }
94
95    /// Raw `EVP_PKEY*` pointer valid for the lifetime of `self`.
96    #[must_use]
97    pub fn as_ptr(&self) -> *mut sys::EVP_PKEY {
98        self.ptr
99    }
100
101
102    /// Size of the key in bits (e.g. 256 for P-256, 2048 for RSA-2048).
103    #[must_use]
104    pub fn bits(&self) -> u32 {
105        u32::try_from(unsafe { sys::EVP_PKEY_get_bits(self.ptr) }).unwrap_or(0)
106    }
107
108    /// Security strength in bits (e.g. 128 for P-256, 112 for RSA-2048).
109    #[must_use]
110    pub fn security_bits(&self) -> u32 {
111        u32::try_from(unsafe { sys::EVP_PKEY_get_security_bits(self.ptr) }).unwrap_or(0)
112    }
113
114    /// Return `true` if this key is of the named algorithm (e.g. `c"EC"`, `c"RSA"`).
115    #[must_use]
116    pub fn is_a(&self, name: &std::ffi::CStr) -> bool {
117        unsafe { sys::EVP_PKEY_is_a(self.ptr, name.as_ptr()) == 1 }
118    }
119
120    /// Return `true` if this key's public component equals `other`'s.
121    ///
122    /// Wraps `EVP_PKEY_eq`.  Useful for verifying that a certificate's public
123    /// key matches a private key before using them together.
124    #[must_use]
125    pub fn public_eq<U: HasPublic>(&self, other: &Pkey<U>) -> bool
126    where
127        T: HasPublic,
128    {
129        unsafe { sys::EVP_PKEY_eq(self.ptr, other.ptr) == 1 }
130    }
131
132    /// Fill the values for a pre-prepared mutable `Params` query array.
133    ///
134    /// Wraps `EVP_PKEY_get_params`.  The array must already contain the keys
135    /// of interest with null data pointers; OpenSSL writes the values in place.
136    ///
137    /// # Errors
138    pub fn get_params(&self, params: &mut crate::params::Params<'_>) -> Result<(), ErrorStack> {
139        crate::ossl_call!(sys::EVP_PKEY_get_params(self.ptr, params.as_mut_ptr()))
140    }
141
142    /// DER-encode the public key (`SubjectPublicKeyInfo` format).
143    ///
144    /// Zero-copy: writes directly into a caller-owned `Vec<u8>` — no OpenSSL
145    /// heap allocation occurs.
146    ///
147    /// # Errors
148    ///
149    /// Returns `Err` if serialisation fails.
150    pub fn public_key_to_der(&self) -> Result<Vec<u8>, ErrorStack>
151    where
152        T: HasPublic,
153    {
154        // First call with null to query the DER byte length.
155        let len = unsafe { sys::i2d_PUBKEY(self.ptr, std::ptr::null_mut()) };
156        if len < 0 {
157            return Err(ErrorStack::drain());
158        }
159        // Allocate our own buffer and write into it.
160        // i2d_ advances the out-pointer; our Vec base address is unaffected.
161        let mut buf = vec![0u8; usize::try_from(len).unwrap_or(0)];
162        let mut out_ptr = buf.as_mut_ptr();
163        let written = unsafe { sys::i2d_PUBKEY(self.ptr, std::ptr::addr_of_mut!(out_ptr)) };
164        if written < 0 {
165            return Err(ErrorStack::drain());
166        }
167        buf.truncate(usize::try_from(written).unwrap_or(0));
168        Ok(buf)
169    }
170}
171
172// ── PEM loading — private key ─────────────────────────────────────────────────
173
174impl Pkey<Private> {
175    /// Return the provider-side `keydata` pointer from the `EVP_PKEY` struct.
176    ///
177    /// This is the `void *keydata` field of `evp_pkey_st`.  It holds the
178    /// algorithm-specific key material allocated by the provider's keymgmt
179    /// implementation.  The pointer is valid for the lifetime of `self`.
180    ///
181    /// Only available with the `fips-provider` cargo feature.  Intended for
182    /// use inside a FIPS provider when invoking `EVP_SIGNATURE` vtable functions
183    /// that require `void *provkey` (= `keydata`).
184    ///
185    /// # Safety
186    ///
187    /// The returned pointer points into the internal state of this `Pkey`.
188    /// It must not outlive `self` and must not be freed independently.
189    /// The field offset is computed by the C compiler for the current target
190    /// ABI via a build-time probe; see `native-ossl-sys/build.rs`.
191    #[cfg(feature = "fips-provider")]
192    pub unsafe fn keydata(&self) -> *mut std::ffi::c_void {
193        // SAFETY: self.ptr is a valid non-null EVP_PKEY*.  We read the void* at
194        // the ABI-correct byte offset of the `keydata` field in evp_pkey_st,
195        // determined at build time by a C offsetof probe.
196        self.ptr
197            .cast::<u8>()
198            .add(native_ossl_sys::fips_internal::EVP_PKEY_KEYDATA_OFFSET)
199            .cast::<*mut std::ffi::c_void>()
200            .read()
201    }
202
203    /// Load a private key from PEM bytes.
204    ///
205    /// Pass `passphrase = Some(cb)` for encrypted PEM; `None` for unencrypted.
206    ///
207    /// # Errors
208    pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
209        let bio = MemBioBuf::new(pem)?;
210        let ptr = unsafe {
211            sys::PEM_read_bio_PrivateKey(bio.as_ptr(), std::ptr::null_mut(), None, std::ptr::null_mut())
212        };
213        if ptr.is_null() {
214            return Err(ErrorStack::drain());
215        }
216        Ok(unsafe { Pkey::from_ptr(ptr) })
217    }
218
219    /// Serialise the private key to PEM (`PKCS#8` `BEGIN PRIVATE KEY`).
220    ///
221    /// # Errors
222    pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack> {
223        let mut bio = MemBio::new()?;
224        let rc = unsafe {
225            sys::PEM_write_bio_PrivateKey(
226                bio.as_ptr(),
227                self.ptr,
228                std::ptr::null(),
229                std::ptr::null_mut(),
230                0,
231                None,
232                std::ptr::null_mut(),
233            )
234        };
235        if rc != 1 {
236            return Err(ErrorStack::drain());
237        }
238        Ok(bio.into_vec())
239    }
240
241    /// Load a private key from PEM bytes within a specific library context.
242    ///
243    /// Uses `PEM_read_bio_PrivateKey_ex` so the key's internal algorithm fetch
244    /// uses `ctx`'s provider set.  Necessary when the private key is later used
245    /// for EVP operations inside an isolated (e.g. FIPS) context.
246    ///
247    /// # Errors
248    pub fn from_pem_in(ctx: &Arc<crate::lib_ctx::LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack> {
249        let bio = MemBioBuf::new(pem)?;
250        let ptr = unsafe {
251            sys::PEM_read_bio_PrivateKey_ex(
252                bio.as_ptr(),
253                std::ptr::null_mut(),
254                None,
255                std::ptr::null_mut(),
256                ctx.as_ptr(),
257                std::ptr::null(),
258            )
259        };
260        if ptr.is_null() {
261            return Err(ErrorStack::drain());
262        }
263        Ok(unsafe { Pkey::from_ptr(ptr) })
264    }
265
266    /// Load a private key from DER bytes (auto-detecting `PKCS#8` / traditional).
267    ///
268    /// Zero-copy: the `EVP_PKEY` is decoded from the caller's slice without copying.
269    ///
270    /// # Errors
271    pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
272        let bio = MemBioBuf::new(der)?;
273        let ptr = unsafe { sys::d2i_PrivateKey_bio(bio.as_ptr(), std::ptr::null_mut()) };
274        if ptr.is_null() {
275            return Err(ErrorStack::drain());
276        }
277        Ok(unsafe { Pkey::from_ptr(ptr) })
278    }
279}
280
281// ── PEM loading — public key ──────────────────────────────────────────────────
282
283impl Pkey<Public> {
284    /// Load a public key from PEM (`SubjectPublicKeyInfo` or RSA public key).
285    ///
286    /// # Errors
287    pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
288        let bio = MemBioBuf::new(pem)?;
289        let ptr = unsafe {
290            sys::PEM_read_bio_PUBKEY(bio.as_ptr(), std::ptr::null_mut(), None, std::ptr::null_mut())
291        };
292        if ptr.is_null() {
293            return Err(ErrorStack::drain());
294        }
295        Ok(unsafe { Pkey::from_ptr(ptr) })
296    }
297
298    /// Load a public key from PEM bytes within a specific library context.
299    ///
300    /// Uses `PEM_read_bio_PUBKEY_ex` so the key's internal algorithm fetch
301    /// uses `ctx`'s provider set.  Necessary when the public key is later used
302    /// for EVP operations inside an isolated (e.g. FIPS) context.
303    ///
304    /// # Errors
305    pub fn from_pem_in(ctx: &Arc<crate::lib_ctx::LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack> {
306        let bio = MemBioBuf::new(pem)?;
307        let ptr = unsafe {
308            sys::PEM_read_bio_PUBKEY_ex(
309                bio.as_ptr(),
310                std::ptr::null_mut(),
311                None,
312                std::ptr::null_mut(),
313                ctx.as_ptr(),
314                std::ptr::null(),
315            )
316        };
317        if ptr.is_null() {
318            return Err(ErrorStack::drain());
319        }
320        Ok(unsafe { Pkey::from_ptr(ptr) })
321    }
322
323    /// Load a public key from DER (`SubjectPublicKeyInfo`).
324    ///
325    /// # Errors
326    pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
327        let bio = MemBioBuf::new(der)?;
328        let ptr = unsafe { sys::d2i_PUBKEY_bio(bio.as_ptr(), std::ptr::null_mut()) };
329        if ptr.is_null() {
330            return Err(ErrorStack::drain());
331        }
332        Ok(unsafe { Pkey::from_ptr(ptr) })
333    }
334
335    /// Serialise the public key to PEM.
336    ///
337    /// # Errors
338    pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack> {
339        let mut bio = MemBio::new()?;
340        let rc = unsafe { sys::PEM_write_bio_PUBKEY(bio.as_ptr(), self.ptr) };
341        if rc != 1 {
342            return Err(ErrorStack::drain());
343        }
344        Ok(bio.into_vec())
345    }
346}
347
348// Upcast: every `Pkey<Private>` can be viewed as `Pkey<Public>`.
349impl From<Pkey<Private>> for Pkey<Public> {
350    fn from(k: Pkey<Private>) -> Self {
351        unsafe { sys::EVP_PKEY_up_ref(k.ptr) };
352        Pkey { ptr: k.ptr, _role: PhantomData }
353    }
354}
355
356// ── Key import from OSSL_PARAM ────────────────────────────────────────────────
357
358// EVP_PKEY_fromdata / EVP_PKEY_todata selection constants.
359const PKEY_PUBLIC_KEY: i32 = 2;
360const PKEY_KEYPAIR: i32 = 3;
361
362/// Shared implementation for `EVP_PKEY_fromdata`.
363fn pkey_fromdata(
364    ctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
365    pkey_type: &std::ffi::CStr,
366    params: &crate::params::Params<'_>,
367    selection: i32,
368) -> Result<*mut sys::EVP_PKEY, ErrorStack> {
369    let libctx = ctx.map_or(std::ptr::null_mut(), |c| c.as_ptr());
370    let pctx = unsafe {
371        sys::EVP_PKEY_CTX_new_from_name(libctx, pkey_type.as_ptr(), std::ptr::null())
372    };
373    if pctx.is_null() {
374        return Err(ErrorStack::drain());
375    }
376    let rc = unsafe { sys::EVP_PKEY_fromdata_init(pctx) };
377    if rc != 1 {
378        unsafe { sys::EVP_PKEY_CTX_free(pctx) };
379        return Err(ErrorStack::drain());
380    }
381    let mut pkey: *mut sys::EVP_PKEY = std::ptr::null_mut();
382    let rc = unsafe {
383        sys::EVP_PKEY_fromdata(
384            pctx,
385            std::ptr::addr_of_mut!(pkey),
386            selection,
387            // OSSL_PARAM array is read-only during fromdata; cast is safe.
388            params.as_ptr().cast_mut(),
389        )
390    };
391    unsafe { sys::EVP_PKEY_CTX_free(pctx) };
392    if rc != 1 || pkey.is_null() {
393        return Err(ErrorStack::drain());
394    }
395    Ok(pkey)
396}
397
398/// Shared implementation for `EVP_PKEY_todata`.
399fn pkey_todata(
400    ptr: *mut sys::EVP_PKEY,
401    selection: i32,
402) -> Result<crate::params::Params<'static>, ErrorStack> {
403    let mut out: *mut sys::OSSL_PARAM = std::ptr::null_mut();
404    let rc = unsafe { sys::EVP_PKEY_todata(ptr, selection, std::ptr::addr_of_mut!(out)) };
405    if rc != 1 || out.is_null() {
406        return Err(ErrorStack::drain());
407    }
408    // SAFETY: `out` is a freshly allocated OSSL_PARAM array from OpenSSL;
409    // Params takes ownership and will free it via OSSL_PARAM_free on drop.
410    Ok(unsafe { crate::params::Params::from_owned_ptr(out) })
411}
412
413impl Pkey<Private> {
414    /// Import a private key pair from an `OSSL_PARAM` array.
415    ///
416    /// Equivalent to `EVP_PKEY_fromdata` with `EVP_PKEY_KEYPAIR` selection.
417    /// Pass `ctx = None` to use the global default library context.
418    ///
419    /// # Errors
420    pub fn from_params(
421        ctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
422        pkey_type: &std::ffi::CStr,
423        params: &crate::params::Params<'_>,
424    ) -> Result<Self, ErrorStack> {
425        pkey_fromdata(ctx, pkey_type, params, PKEY_KEYPAIR)
426            .map(|ptr| unsafe { Pkey::from_ptr(ptr) })
427    }
428
429    /// Export all key parameters (private + public) as an owned `OSSL_PARAM` array.
430    ///
431    /// Uses `EVP_PKEY_KEYPAIR` selection so both private and public material
432    /// are included in the returned array.
433    ///
434    /// # Errors
435    pub fn export(&self) -> Result<crate::params::Params<'static>, ErrorStack> {
436        pkey_todata(self.ptr, PKEY_KEYPAIR)
437    }
438}
439
440impl Pkey<Public> {
441    /// Import a public key from an `OSSL_PARAM` array.
442    ///
443    /// Equivalent to `EVP_PKEY_fromdata` with `EVP_PKEY_PUBLIC_KEY` selection.
444    /// Pass `ctx = None` to use the global default library context.
445    ///
446    /// # Errors
447    pub fn from_params(
448        ctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
449        pkey_type: &std::ffi::CStr,
450        params: &crate::params::Params<'_>,
451    ) -> Result<Self, ErrorStack> {
452        pkey_fromdata(ctx, pkey_type, params, PKEY_PUBLIC_KEY)
453            .map(|ptr| unsafe { Pkey::from_ptr(ptr) })
454    }
455
456    /// Export the public key parameters as an owned `OSSL_PARAM` array.
457    ///
458    /// Uses `EVP_PKEY_PUBLIC_KEY` selection.
459    ///
460    /// # Errors
461    pub fn export(&self) -> Result<crate::params::Params<'static>, ErrorStack> {
462        pkey_todata(self.ptr, PKEY_PUBLIC_KEY)
463    }
464}
465
466// ── KeygenCtx — key generation ────────────────────────────────────────────────
467
468/// Context for generating asymmetric key pairs (`EVP_PKEY_CTX` in keygen mode).
469pub struct KeygenCtx {
470    ptr: *mut sys::EVP_PKEY_CTX,
471}
472
473impl KeygenCtx {
474    /// Create a keygen context for the named algorithm.
475    ///
476    /// Common names: `c"RSA"`, `c"EC"`, `c"ED25519"`, `c"X25519"`.
477    ///
478    /// # Errors
479    pub fn new(name: &std::ffi::CStr) -> Result<Self, ErrorStack> {
480        let ptr = unsafe {
481            sys::EVP_PKEY_CTX_new_from_name(std::ptr::null_mut(), name.as_ptr(), std::ptr::null())
482        };
483        if ptr.is_null() {
484            return Err(ErrorStack::drain());
485        }
486        let rc = unsafe { sys::EVP_PKEY_keygen_init(ptr) };
487        if rc != 1 {
488            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
489            return Err(ErrorStack::drain());
490        }
491        Ok(KeygenCtx { ptr })
492    }
493
494    /// Configure parameters before calling `generate`.
495    ///
496    /// # Errors
497    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
498        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ptr, params.as_ptr()))
499    }
500
501    /// Generate a key pair.
502    ///
503    /// # Errors
504    pub fn generate(&mut self) -> Result<Pkey<Private>, ErrorStack> {
505        let mut key: *mut sys::EVP_PKEY = std::ptr::null_mut();
506        crate::ossl_call!(sys::EVP_PKEY_keygen(self.ptr, std::ptr::addr_of_mut!(key)))?;
507        if key.is_null() {
508            return Err(ErrorStack::drain());
509        }
510        Ok(unsafe { Pkey::from_ptr(key) })
511    }
512}
513
514impl Drop for KeygenCtx {
515    fn drop(&mut self) {
516        unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
517    }
518}
519
520// ── Signer — streaming DigestSign ─────────────────────────────────────────────
521
522/// Parameters for creating a [`Signer`] or [`Verifier`].
523#[derive(Default)]
524pub struct SignInit<'a> {
525    /// Digest algorithm, or `None` for pre-hashed / `EdDSA` one-shot mode.
526    pub digest: Option<&'a crate::digest::DigestAlg>,
527    /// Optional parameters (e.g. RSA PSS salt length).
528    pub params: Option<&'a crate::params::Params<'a>>,
529}
530
531/// Streaming `DigestSign` context.
532///
533/// Call `update` zero or more times, then `finish` to produce the signature.
534pub struct Signer {
535    ctx: crate::digest::DigestCtx,
536    /// The key is kept alive for the duration of the signer.
537    _key: Pkey<Private>,
538}
539
540impl Signer {
541    /// Create a signer.
542    ///
543    /// # Errors
544    pub fn new(key: &Pkey<Private>, init: &SignInit<'_>) -> Result<Self, ErrorStack> {
545        let ctx = alloc_digest_ctx()?;
546        // Resolve digest name for EVP_DigestSignInit_ex (NULL for Ed25519/one-shot).
547        let md_name_ptr = if let Some(d) = init.digest {
548            let p = unsafe { sys::OBJ_nid2sn(d.nid()) };
549            if p.is_null() {
550                return Err(ErrorStack::drain());
551            }
552            p
553        } else {
554            std::ptr::null()
555        };
556        let params_ptr = init.params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
557        let rc = unsafe {
558            sys::EVP_DigestSignInit_ex(
559                ctx.as_ptr(),
560                std::ptr::null_mut(),
561                md_name_ptr,
562                std::ptr::null_mut(),
563                std::ptr::null(),
564                key.ptr,
565                params_ptr,
566            )
567        };
568        if rc != 1 {
569            return Err(ErrorStack::drain());
570        }
571        Ok(Signer { ctx, _key: key.clone() })
572    }
573
574    /// Feed data into the hash.
575    ///
576    /// # Errors
577    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
578        crate::ossl_call!(sys::EVP_DigestSignUpdate(self.ctx.as_ptr(), data.as_ptr().cast(), data.len()))
579    }
580
581    /// Finalise and return the signature.
582    ///
583    /// Not supported by pure one-shot algorithms such as Ed25519 — use
584    /// [`sign_oneshot`](Self::sign_oneshot) for those.
585    ///
586    /// # Errors
587    pub fn finish(&mut self) -> Result<Vec<u8>, ErrorStack> {
588        // First call with null buf to get the required size.
589        let mut siglen: usize = 0;
590        let rc = unsafe {
591            sys::EVP_DigestSignFinal(
592                self.ctx.as_ptr(),
593                std::ptr::null_mut(),
594                std::ptr::addr_of_mut!(siglen),
595            )
596        };
597        if rc != 1 {
598            return Err(ErrorStack::drain());
599        }
600        let mut sig = vec![0u8; siglen];
601        let rc = unsafe {
602            sys::EVP_DigestSignFinal(
603                self.ctx.as_ptr(),
604                sig.as_mut_ptr(),
605                std::ptr::addr_of_mut!(siglen),
606            )
607        };
608        if rc != 1 {
609            return Err(ErrorStack::drain());
610        }
611        sig.truncate(siglen);
612        Ok(sig)
613    }
614
615    /// One-shot sign over `data`.
616    ///
617    /// Required for algorithms that do not support streaming (Ed25519, Ed448).
618    /// For algorithms that do support streaming, prefer `update` + `finish`.
619    ///
620    /// # Errors
621    pub fn sign_oneshot(&mut self, data: &[u8]) -> Result<Vec<u8>, ErrorStack> {
622        // First call: query the required signature length.
623        let mut siglen: usize = 0;
624        let rc = unsafe {
625            sys::EVP_DigestSign(
626                self.ctx.as_ptr(),
627                std::ptr::null_mut(),
628                std::ptr::addr_of_mut!(siglen),
629                data.as_ptr(),
630                data.len(),
631            )
632        };
633        if rc != 1 {
634            return Err(ErrorStack::drain());
635        }
636        let mut sig = vec![0u8; siglen];
637        let rc = unsafe {
638            sys::EVP_DigestSign(
639                self.ctx.as_ptr(),
640                sig.as_mut_ptr(),
641                std::ptr::addr_of_mut!(siglen),
642                data.as_ptr(),
643                data.len(),
644            )
645        };
646        if rc != 1 {
647            return Err(ErrorStack::drain());
648        }
649        sig.truncate(siglen);
650        Ok(sig)
651    }
652}
653
654// ── Verifier — streaming DigestVerify ─────────────────────────────────────────
655
656/// Streaming `DigestVerify` context.
657pub struct Verifier {
658    ctx: crate::digest::DigestCtx,
659    _key: Pkey<Public>,
660}
661
662impl Verifier {
663    /// Create a verifier.
664    ///
665    /// # Errors
666    pub fn new(key: &Pkey<Public>, init: &SignInit<'_>) -> Result<Self, ErrorStack> {
667        let ctx = alloc_digest_ctx()?;
668        // Resolve digest name for EVP_DigestVerifyInit_ex (NULL for Ed25519/one-shot).
669        let md_name_ptr = if let Some(d) = init.digest {
670            let p = unsafe { sys::OBJ_nid2sn(d.nid()) };
671            if p.is_null() {
672                return Err(ErrorStack::drain());
673            }
674            p
675        } else {
676            std::ptr::null()
677        };
678        let params_ptr = init.params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
679        let rc = unsafe {
680            sys::EVP_DigestVerifyInit_ex(
681                ctx.as_ptr(),
682                std::ptr::null_mut(),
683                md_name_ptr,
684                std::ptr::null_mut(),
685                std::ptr::null(),
686                key.ptr,
687                params_ptr,
688            )
689        };
690        if rc != 1 {
691            return Err(ErrorStack::drain());
692        }
693        Ok(Verifier { ctx, _key: key.clone() })
694    }
695
696    /// Feed data into the hash.
697    ///
698    /// # Errors
699    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
700        crate::ossl_call!(sys::EVP_DigestVerifyUpdate(self.ctx.as_ptr(), data.as_ptr().cast(), data.len()))
701    }
702
703    /// Verify `signature` against all data fed via `update`.
704    ///
705    /// Returns `Ok(true)` if valid, `Ok(false)` if the signature is incorrect,
706    /// or `Err` on a fatal OpenSSL error.
707    ///
708    /// Not supported by pure one-shot algorithms such as Ed25519 — use
709    /// [`verify_oneshot`](Self::verify_oneshot) for those.
710    ///
711    /// # Errors
712    pub fn verify(&mut self, signature: &[u8]) -> Result<bool, ErrorStack> {
713        let rc = unsafe {
714            sys::EVP_DigestVerifyFinal(
715                self.ctx.as_ptr(),
716                signature.as_ptr(),
717                signature.len(),
718            )
719        };
720        match rc {
721            1 => Ok(true),
722            0 => Ok(false),
723            _ => Err(ErrorStack::drain()),
724        }
725    }
726
727    /// One-shot verify `signature` over `data`.
728    ///
729    /// Required for algorithms that do not support streaming (Ed25519, Ed448).
730    ///
731    /// # Errors
732    pub fn verify_oneshot(&mut self, data: &[u8], signature: &[u8]) -> Result<bool, ErrorStack> {
733        let rc = unsafe {
734            sys::EVP_DigestVerify(
735                self.ctx.as_ptr(),
736                signature.as_ptr(),
737                signature.len(),
738                data.as_ptr(),
739                data.len(),
740            )
741        };
742        match rc {
743            1 => Ok(true),
744            0 => Ok(false),
745            _ => Err(ErrorStack::drain()),
746        }
747    }
748}
749
750// ── Internal helper: allocate an EVP_MD_CTX for DigestSign/Verify ─────────────
751
752/// Allocate a fresh, algorithm-unbound `EVP_MD_CTX` for use with
753/// `EVP_DigestSign*Init_ex` / `EVP_DigestVerify*Init_ex`.
754fn alloc_digest_ctx() -> Result<crate::digest::DigestCtx, ErrorStack> {
755    let ctx_ptr = unsafe { sys::EVP_MD_CTX_new() };
756    if ctx_ptr.is_null() {
757        return Err(ErrorStack::drain());
758    }
759    // SAFETY: DigestCtx takes ownership; ptr is valid and non-null.
760    Ok(unsafe { crate::digest::DigestCtx::from_ptr(ctx_ptr) })
761}
762
763// ── DeriveCtx — ECDH / DH key agreement ──────────────────────────────────────
764
765/// Asymmetric key-agreement context (`EVP_PKEY_CTX` in derive mode).
766pub struct DeriveCtx {
767    ptr: *mut sys::EVP_PKEY_CTX,
768}
769
770impl DeriveCtx {
771    /// Create a derive context from a private key.
772    ///
773    /// # Errors
774    pub fn new(key: &Pkey<Private>) -> Result<Self, ErrorStack> {
775        let ptr = unsafe {
776            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
777        };
778        if ptr.is_null() {
779            return Err(ErrorStack::drain());
780        }
781        crate::ossl_call!(sys::EVP_PKEY_derive_init(ptr)).map_err(|e| {
782            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
783            e
784        })?;
785        Ok(DeriveCtx { ptr })
786    }
787
788    /// Set the peer's public key.
789    ///
790    /// # Errors
791    pub fn set_peer(&mut self, peer: &Pkey<Public>) -> Result<(), ErrorStack> {
792        crate::ossl_call!(sys::EVP_PKEY_derive_set_peer(self.ptr, peer.ptr))
793    }
794
795    /// Derive the shared secret into `out`.
796    ///
797    /// Returns the number of bytes written.
798    ///
799    /// # Errors
800    pub fn derive(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack> {
801        let mut len = out.len();
802        crate::ossl_call!(sys::EVP_PKEY_derive(
803            self.ptr,
804            out.as_mut_ptr(),
805            std::ptr::addr_of_mut!(len)
806        ))?;
807        Ok(len)
808    }
809
810    /// Query the required output length (call with empty slice).
811    ///
812    /// # Errors
813    pub fn derive_len(&mut self) -> Result<usize, ErrorStack> {
814        let mut len: usize = 0;
815        crate::ossl_call!(sys::EVP_PKEY_derive(
816            self.ptr,
817            std::ptr::null_mut(),
818            std::ptr::addr_of_mut!(len)
819        ))?;
820        Ok(len)
821    }
822}
823
824impl Drop for DeriveCtx {
825    fn drop(&mut self) {
826        unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
827    }
828}
829
830// ── PkeyEncryptCtx / PkeyDecryptCtx ──────────────────────────────────────────
831
832/// RSA asymmetric encryption context.
833pub struct PkeyEncryptCtx {
834    ptr: *mut sys::EVP_PKEY_CTX,
835}
836
837impl PkeyEncryptCtx {
838    /// Create an encryption context from a public key.
839    ///
840    /// `params` is applied immediately after init if `Some`.  Typical use:
841    /// ```ignore
842    /// let oaep = ParamBuilder::new()?.push_utf8_string(c"pad-mode", c"oaep")?.build()?;
843    /// let ctx = PkeyEncryptCtx::new(&pub_key, Some(&oaep))?;
844    /// ```
845    ///
846    /// # Errors
847    pub fn new(key: &Pkey<Public>, params: Option<&crate::params::Params<'_>>) -> Result<Self, ErrorStack> {
848        let ptr = unsafe {
849            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
850        };
851        if ptr.is_null() {
852            return Err(ErrorStack::drain());
853        }
854        crate::ossl_call!(sys::EVP_PKEY_encrypt_init(ptr)).map_err(|e| {
855            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
856            e
857        })?;
858        let ctx = PkeyEncryptCtx { ptr };
859        if let Some(p) = params {
860            crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(ctx.ptr, p.as_ptr()))
861                .map_err(|e| { unsafe { sys::EVP_PKEY_CTX_free(ptr) }; e })?;
862        }
863        Ok(ctx)
864    }
865
866    /// Configure parameters (e.g. RSA padding mode).
867    ///
868    /// # Errors
869    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
870        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ptr, params.as_ptr()))
871    }
872
873    /// Encrypt `plaintext` into `ciphertext`.
874    ///
875    /// Returns the number of bytes written.
876    ///
877    /// # Errors
878    pub fn encrypt(&mut self, plaintext: &[u8], ciphertext: &mut [u8]) -> Result<usize, ErrorStack> {
879        let mut outlen = ciphertext.len();
880        crate::ossl_call!(sys::EVP_PKEY_encrypt(
881            self.ptr,
882            ciphertext.as_mut_ptr(),
883            std::ptr::addr_of_mut!(outlen),
884            plaintext.as_ptr(),
885            plaintext.len()
886        ))?;
887        Ok(outlen)
888    }
889
890    /// Query the ciphertext length for a given plaintext length.
891    ///
892    /// # Errors
893    pub fn encrypt_len(&mut self, plaintext_len: usize) -> Result<usize, ErrorStack> {
894        let mut outlen: usize = 0;
895        crate::ossl_call!(sys::EVP_PKEY_encrypt(
896            self.ptr,
897            std::ptr::null_mut(),
898            std::ptr::addr_of_mut!(outlen),
899            std::ptr::null(),
900            plaintext_len
901        ))?;
902        Ok(outlen)
903    }
904}
905
906impl Drop for PkeyEncryptCtx {
907    fn drop(&mut self) {
908        unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
909    }
910}
911
912/// RSA asymmetric decryption context.
913pub struct PkeyDecryptCtx {
914    ptr: *mut sys::EVP_PKEY_CTX,
915}
916
917impl PkeyDecryptCtx {
918    /// Create a decryption context from a private key.
919    ///
920    /// `params` is applied immediately after init if `Some`.
921    ///
922    /// # Errors
923    pub fn new(key: &Pkey<Private>, params: Option<&crate::params::Params<'_>>) -> Result<Self, ErrorStack> {
924        let ptr = unsafe {
925            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
926        };
927        if ptr.is_null() {
928            return Err(ErrorStack::drain());
929        }
930        crate::ossl_call!(sys::EVP_PKEY_decrypt_init(ptr)).map_err(|e| {
931            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
932            e
933        })?;
934        let ctx = PkeyDecryptCtx { ptr };
935        if let Some(p) = params {
936            crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(ctx.ptr, p.as_ptr()))
937                .map_err(|e| { unsafe { sys::EVP_PKEY_CTX_free(ptr) }; e })?;
938        }
939        Ok(ctx)
940    }
941
942    /// Configure parameters.
943    ///
944    /// # Errors
945    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
946        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ptr, params.as_ptr()))
947    }
948
949    /// Decrypt `ciphertext` into `plaintext`.
950    ///
951    /// Returns the number of bytes written.
952    ///
953    /// # Errors
954    pub fn decrypt(&mut self, ciphertext: &[u8], plaintext: &mut [u8]) -> Result<usize, ErrorStack> {
955        let mut outlen = plaintext.len();
956        crate::ossl_call!(sys::EVP_PKEY_decrypt(
957            self.ptr,
958            plaintext.as_mut_ptr(),
959            std::ptr::addr_of_mut!(outlen),
960            ciphertext.as_ptr(),
961            ciphertext.len()
962        ))?;
963        Ok(outlen)
964    }
965}
966
967impl Drop for PkeyDecryptCtx {
968    fn drop(&mut self) {
969        unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
970    }
971}
972
973// ── EncapCtx / DecapCtx — KEM (OpenSSL 3.2+) ────────────────────────────────
974
975/// KEM encapsulation output.
976#[cfg(ossl320)]
977pub struct EncapResult {
978    /// Wrapped key (encoded shared secret).
979    pub wrapped_key: Vec<u8>,
980    /// Shared secret (plaintext).
981    pub shared_secret: Vec<u8>,
982}
983
984/// KEM encapsulation context (recipient's public key).
985#[cfg(ossl320)]
986pub struct EncapCtx {
987    ptr: *mut sys::EVP_PKEY_CTX,
988}
989
990#[cfg(ossl320)]
991impl EncapCtx {
992    /// Create a KEM encapsulation context from the recipient's public key.
993    ///
994    /// # Errors
995    pub fn new(key: &Pkey<Public>) -> Result<Self, ErrorStack> {
996        let ptr = unsafe {
997            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
998        };
999        if ptr.is_null() {
1000            return Err(ErrorStack::drain());
1001        }
1002        crate::ossl_call!(sys::EVP_PKEY_encapsulate_init(ptr, std::ptr::null())).map_err(|e| {
1003            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1004            e
1005        })?;
1006        Ok(EncapCtx { ptr })
1007    }
1008
1009    /// Perform encapsulation, returning the wrapped key and shared secret.
1010    ///
1011    /// # Errors
1012    pub fn encapsulate(&mut self) -> Result<EncapResult, ErrorStack> {
1013        let mut wkeylen: usize = 0;
1014        let mut sslen: usize = 0;
1015        // Query lengths first.
1016        let rc = unsafe {
1017            sys::EVP_PKEY_encapsulate(
1018                self.ptr,
1019                std::ptr::null_mut(),
1020                std::ptr::addr_of_mut!(wkeylen),
1021                std::ptr::null_mut(),
1022                std::ptr::addr_of_mut!(sslen),
1023            )
1024        };
1025        if rc != 1 {
1026            return Err(ErrorStack::drain());
1027        }
1028        let mut wrapped_key = vec![0u8; wkeylen];
1029        let mut shared_secret = vec![0u8; sslen];
1030        crate::ossl_call!(sys::EVP_PKEY_encapsulate(
1031            self.ptr,
1032            wrapped_key.as_mut_ptr(),
1033            std::ptr::addr_of_mut!(wkeylen),
1034            shared_secret.as_mut_ptr(),
1035            std::ptr::addr_of_mut!(sslen)
1036        ))?;
1037        wrapped_key.truncate(wkeylen);
1038        shared_secret.truncate(sslen);
1039        Ok(EncapResult { wrapped_key, shared_secret })
1040    }
1041}
1042
1043#[cfg(ossl320)]
1044impl Drop for EncapCtx {
1045    fn drop(&mut self) {
1046        unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
1047    }
1048}
1049
1050/// KEM decapsulation context (recipient's private key).
1051#[cfg(ossl320)]
1052pub struct DecapCtx {
1053    ptr: *mut sys::EVP_PKEY_CTX,
1054}
1055
1056#[cfg(ossl320)]
1057impl DecapCtx {
1058    /// Create a KEM decapsulation context from the recipient's private key.
1059    ///
1060    /// # Errors
1061    pub fn new(key: &Pkey<Private>) -> Result<Self, ErrorStack> {
1062        let ptr = unsafe {
1063            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
1064        };
1065        if ptr.is_null() {
1066            return Err(ErrorStack::drain());
1067        }
1068        crate::ossl_call!(sys::EVP_PKEY_decapsulate_init(ptr, std::ptr::null())).map_err(|e| {
1069            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1070            e
1071        })?;
1072        Ok(DecapCtx { ptr })
1073    }
1074
1075    /// Recover the shared secret from a wrapped key.
1076    ///
1077    /// # Errors
1078    pub fn decapsulate(&mut self, wrapped_key: &[u8]) -> Result<Vec<u8>, ErrorStack> {
1079        let mut sslen: usize = 0;
1080        // Query output length.
1081        let rc = unsafe {
1082            sys::EVP_PKEY_decapsulate(
1083                self.ptr,
1084                std::ptr::null_mut(),
1085                std::ptr::addr_of_mut!(sslen),
1086                wrapped_key.as_ptr(),
1087                wrapped_key.len(),
1088            )
1089        };
1090        if rc != 1 {
1091            return Err(ErrorStack::drain());
1092        }
1093        let mut ss = vec![0u8; sslen];
1094        crate::ossl_call!(sys::EVP_PKEY_decapsulate(
1095            self.ptr,
1096            ss.as_mut_ptr(),
1097            std::ptr::addr_of_mut!(sslen),
1098            wrapped_key.as_ptr(),
1099            wrapped_key.len()
1100        ))?;
1101        ss.truncate(sslen);
1102        Ok(ss)
1103    }
1104}
1105
1106#[cfg(ossl320)]
1107impl Drop for DecapCtx {
1108    fn drop(&mut self) {
1109        unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
1110    }
1111}
1112
1113// ── RawSigner — pre-hashed / no-digest signing ───────────────────────────────
1114
1115/// Raw (no-digest) signing context wrapping `EVP_PKEY_CTX` after `EVP_PKEY_sign_init`.
1116///
1117/// Use this for algorithms where the caller has already hashed the data, such
1118/// as raw ECDSA (data is the hash) or raw RSA with explicit padding.  For
1119/// algorithms that hash internally, use [`Signer`] or [`MessageSigner`].
1120///
1121/// The context is reusable: after a successful [`sign`](Self::sign) call the
1122/// init state is preserved, so the same padding parameters apply to further
1123/// sign calls with the same key.
1124pub struct RawSigner {
1125    ctx: *mut sys::EVP_PKEY_CTX,
1126}
1127
1128// SAFETY: EVP_PKEY_CTX is not thread-safe on a shared pointer, but
1129// RawSigner owns its ctx exclusively and &mut self enforces single-caller.
1130unsafe impl Send for RawSigner {}
1131
1132impl RawSigner {
1133    /// Create and initialise a sign context (`EVP_PKEY_CTX_new_from_pkey` +
1134    /// `EVP_PKEY_sign_init`).
1135    ///
1136    /// Pass `libctx = Some(ctx)` to restrict provider lookup to that library
1137    /// context; `None` uses the key's own library context.
1138    ///
1139    /// # Errors
1140    pub fn new(
1141        key: &Pkey<Private>,
1142        libctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
1143    ) -> Result<Self, ErrorStack> {
1144        let lctx = libctx.map_or(std::ptr::null_mut(), |c| c.as_ptr());
1145        let ptr = unsafe {
1146            sys::EVP_PKEY_CTX_new_from_pkey(lctx, key.ptr, std::ptr::null())
1147        };
1148        if ptr.is_null() {
1149            return Err(ErrorStack::drain());
1150        }
1151        crate::ossl_call!(sys::EVP_PKEY_sign_init(ptr)).map_err(|e| {
1152            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1153            e
1154        })?;
1155        Ok(RawSigner { ctx: ptr })
1156    }
1157
1158    /// Apply parameters after init (e.g. RSA padding mode, salt length).
1159    ///
1160    /// # Errors
1161    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
1162        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ctx, params.as_ptr()))
1163    }
1164
1165    /// Query the signature output size for the given input length.
1166    ///
1167    /// Calls `EVP_PKEY_sign` with a null output pointer — does not consume
1168    /// the signing state.
1169    ///
1170    /// # Errors
1171    pub fn sign_len(&mut self, tbs_len: usize) -> Result<usize, ErrorStack> {
1172        let mut siglen: usize = 0;
1173        crate::ossl_call!(sys::EVP_PKEY_sign(
1174            self.ctx,
1175            std::ptr::null_mut(),
1176            std::ptr::addr_of_mut!(siglen),
1177            std::ptr::null(),
1178            tbs_len,
1179        ))?;
1180        Ok(siglen)
1181    }
1182
1183    /// Sign pre-hashed data into `sig`.  Returns the number of bytes written.
1184    ///
1185    /// `sig.len()` must be >= `sign_len(tbs.len())`.
1186    ///
1187    /// # Errors
1188    pub fn sign(&mut self, tbs: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack> {
1189        let mut siglen = sig.len();
1190        crate::ossl_call!(sys::EVP_PKEY_sign(
1191            self.ctx,
1192            sig.as_mut_ptr(),
1193            std::ptr::addr_of_mut!(siglen),
1194            tbs.as_ptr(),
1195            tbs.len(),
1196        ))?;
1197        Ok(siglen)
1198    }
1199
1200    /// Sign pre-hashed data, allocating the output buffer.
1201    ///
1202    /// Convenience wrapper around [`sign_len`](Self::sign_len) + [`sign`](Self::sign).
1203    ///
1204    /// # Errors
1205    pub fn sign_alloc(&mut self, tbs: &[u8]) -> Result<Vec<u8>, ErrorStack> {
1206        let siglen = self.sign_len(tbs.len())?;
1207        let mut sig = vec![0u8; siglen];
1208        let written = self.sign(tbs, &mut sig)?;
1209        sig.truncate(written);
1210        Ok(sig)
1211    }
1212}
1213
1214impl Drop for RawSigner {
1215    fn drop(&mut self) {
1216        unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
1217    }
1218}
1219
1220// ── RawVerifier — pre-hashed / no-digest verification ────────────────────────
1221
1222/// Raw (no-digest) verification context wrapping `EVP_PKEY_CTX` after
1223/// `EVP_PKEY_verify_init`.
1224///
1225/// Mirror of [`RawSigner`] for the verification side.
1226pub struct RawVerifier {
1227    ctx: *mut sys::EVP_PKEY_CTX,
1228}
1229
1230unsafe impl Send for RawVerifier {}
1231
1232impl RawVerifier {
1233    /// Create and initialise a verify context.
1234    ///
1235    /// # Errors
1236    pub fn new<T: HasPublic>(
1237        key: &Pkey<T>,
1238        libctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
1239    ) -> Result<Self, ErrorStack> {
1240        let lctx = libctx.map_or(std::ptr::null_mut(), |c| c.as_ptr());
1241        let ptr = unsafe {
1242            sys::EVP_PKEY_CTX_new_from_pkey(lctx, key.ptr, std::ptr::null())
1243        };
1244        if ptr.is_null() {
1245            return Err(ErrorStack::drain());
1246        }
1247        crate::ossl_call!(sys::EVP_PKEY_verify_init(ptr)).map_err(|e| {
1248            unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1249            e
1250        })?;
1251        Ok(RawVerifier { ctx: ptr })
1252    }
1253
1254    /// Apply parameters after init (e.g. RSA padding mode).
1255    ///
1256    /// # Errors
1257    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
1258        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ctx, params.as_ptr()))
1259    }
1260
1261    /// Verify `sig` against pre-hashed `tbs`.  Returns `Ok(())` if valid.
1262    ///
1263    /// # Errors
1264    ///
1265    /// Returns `Err` if the signature is invalid or on any OpenSSL error.
1266    pub fn verify(&mut self, tbs: &[u8], sig: &[u8]) -> Result<(), ErrorStack> {
1267        crate::ossl_call!(sys::EVP_PKEY_verify(
1268            self.ctx,
1269            sig.as_ptr(),
1270            sig.len(),
1271            tbs.as_ptr(),
1272            tbs.len(),
1273        ))
1274    }
1275}
1276
1277impl Drop for RawVerifier {
1278    fn drop(&mut self) {
1279        unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
1280    }
1281}
1282
1283// ── SigAlg — EVP_SIGNATURE algorithm descriptor (OpenSSL 3.2+) ──────────────
1284
1285/// Algorithm descriptor for `EVP_SIGNATURE` (OpenSSL 3.2+).
1286///
1287/// Mirrors `DigestAlg` / `CipherAlg` / `MacAlg` in naming and lifecycle.
1288/// Used with [`MessageSigner`] and [`MessageVerifier`] for algorithms such
1289/// as ML-DSA, SLH-DSA, and Ed25519/Ed448 with context strings.
1290#[cfg(ossl320)]
1291pub struct SigAlg {
1292    ptr: *mut sys::EVP_SIGNATURE,
1293}
1294
1295// SAFETY: EVP_SIGNATURE is reference-counted.
1296#[cfg(ossl320)]
1297unsafe impl Send for SigAlg {}
1298#[cfg(ossl320)]
1299unsafe impl Sync for SigAlg {}
1300
1301#[cfg(ossl320)]
1302impl SigAlg {
1303    /// Fetch an `EVP_SIGNATURE` by name from the default library context.
1304    ///
1305    /// Example names: `c"ML-DSA-44"`, `c"ED25519"`, `c"SLH-DSA-SHA2-128s"`.
1306    ///
1307    /// # Errors
1308    pub fn fetch(name: &std::ffi::CStr, props: Option<&std::ffi::CStr>) -> Result<Self, ErrorStack> {
1309        let props_ptr = props.map_or(std::ptr::null(), |p| p.as_ptr());
1310        let ptr = unsafe {
1311            sys::EVP_SIGNATURE_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr)
1312        };
1313        if ptr.is_null() {
1314            return Err(ErrorStack::drain());
1315        }
1316        Ok(SigAlg { ptr })
1317    }
1318
1319    /// Fetch an `EVP_SIGNATURE` by name within a specific library context.
1320    ///
1321    /// # Errors
1322    pub fn fetch_in(
1323        ctx: &Arc<crate::lib_ctx::LibCtx>,
1324        name: &std::ffi::CStr,
1325        props: Option<&std::ffi::CStr>,
1326    ) -> Result<Self, ErrorStack> {
1327        let props_ptr = props.map_or(std::ptr::null(), |p| p.as_ptr());
1328        let ptr = unsafe {
1329            sys::EVP_SIGNATURE_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr)
1330        };
1331        if ptr.is_null() {
1332            return Err(ErrorStack::drain());
1333        }
1334        Ok(SigAlg { ptr })
1335    }
1336}
1337
1338#[cfg(ossl320)]
1339impl Clone for SigAlg {
1340    fn clone(&self) -> Self {
1341        unsafe { sys::EVP_SIGNATURE_up_ref(self.ptr) };
1342        SigAlg { ptr: self.ptr }
1343    }
1344}
1345
1346#[cfg(ossl320)]
1347impl Drop for SigAlg {
1348    fn drop(&mut self) {
1349        unsafe { sys::EVP_SIGNATURE_free(self.ptr) };
1350    }
1351}
1352
1353// ── MessageSigner — EVP_PKEY_sign_message_* streaming sign (OpenSSL 3.2+) ────
1354
1355/// Stateful signing context using `EVP_PKEY_sign_message_*` (OpenSSL 3.2+).
1356///
1357/// Used for algorithms that do not use a separate internal digest (`ML-DSA`,
1358/// `SLH-DSA`, `Ed25519` with context strings).  Unlike [`Signer`], the
1359/// algorithm is specified as a [`SigAlg`] rather than a digest name.
1360///
1361/// Call [`update`](Self::update) zero or more times (if the algorithm supports
1362/// streaming — check with [`supports_streaming`](Self::supports_streaming)),
1363/// then [`finish`](Self::finish) to produce the signature.  For algorithms
1364/// that only support one-shot operation, use [`sign_oneshot`](Self::sign_oneshot).
1365#[cfg(ossl320)]
1366pub struct MessageSigner {
1367    ctx: *mut sys::EVP_PKEY_CTX,
1368}
1369
1370#[cfg(ossl320)]
1371unsafe impl Send for MessageSigner {}
1372
1373#[cfg(ossl320)]
1374impl MessageSigner {
1375    /// Create and initialise a message-sign context.
1376    ///
1377    /// `alg` is consumed by the init call; pass a clone if you need to reuse it.
1378    /// `params` sets algorithm-specific options (e.g. context string for Ed25519).
1379    ///
1380    /// # Errors
1381    pub fn new(
1382        key: &Pkey<Private>,
1383        alg: &mut SigAlg,
1384        params: Option<&crate::params::Params<'_>>,
1385    ) -> Result<Self, ErrorStack> {
1386        let ptr = unsafe {
1387            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
1388        };
1389        if ptr.is_null() {
1390            return Err(ErrorStack::drain());
1391        }
1392        let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
1393        crate::ossl_call!(sys::EVP_PKEY_sign_message_init(ptr, alg.ptr, params_ptr))
1394            .map_err(|e| {
1395                unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1396                e
1397            })?;
1398        Ok(MessageSigner { ctx: ptr })
1399    }
1400
1401    /// Probe whether this algorithm backend supports incremental `update` calls.
1402    ///
1403    /// Calls `EVP_PKEY_sign_message_update` with an empty input, bracketed by
1404    /// `ERR_set_mark` / `ERR_pop_to_mark` so that a failure does not leave
1405    /// entries on the error queue.  Returns `true` if streaming is supported.
1406    ///
1407    /// If this returns `false`, use [`sign_oneshot`](Self::sign_oneshot) instead.
1408    pub fn supports_streaming(&mut self) -> bool {
1409        // Probe: feed 0 bytes and see if the algorithm accepts it.
1410        // ERR mark/pop ensures the error queue is clean regardless of outcome.
1411        unsafe { sys::ERR_set_mark() };
1412        let probe: [u8; 0] = [];
1413        let rc = unsafe {
1414            sys::EVP_PKEY_sign_message_update(self.ctx, probe.as_ptr(), 0)
1415        };
1416        unsafe { sys::ERR_pop_to_mark() };
1417        rc == 1
1418    }
1419
1420    /// Feed `data` into the signing operation.
1421    ///
1422    /// Returns `Err` if the algorithm does not support streaming — use
1423    /// [`sign_oneshot`](Self::sign_oneshot) in that case.
1424    ///
1425    /// # Errors
1426    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
1427        crate::ossl_call!(sys::EVP_PKEY_sign_message_update(
1428            self.ctx,
1429            data.as_ptr(),
1430            data.len(),
1431        ))
1432    }
1433
1434    /// Query the signature output length.
1435    ///
1436    /// Calls `EVP_PKEY_sign_message_final` with a null buffer — does not
1437    /// consume the signing state.
1438    ///
1439    /// # Errors
1440    pub fn sig_len(&mut self) -> Result<usize, ErrorStack> {
1441        let mut siglen: usize = 0;
1442        crate::ossl_call!(sys::EVP_PKEY_sign_message_final(
1443            self.ctx,
1444            std::ptr::null_mut(),
1445            std::ptr::addr_of_mut!(siglen),
1446        ))?;
1447        Ok(siglen)
1448    }
1449
1450    /// Finalise and produce the signature into `sig`.
1451    ///
1452    /// Consumes `self` because the context is finalised.  Call
1453    /// [`sig_len`](Self::sig_len) first to size the buffer.  Returns the
1454    /// number of bytes written.
1455    ///
1456    /// # Errors
1457    pub fn finish(self, sig: &mut [u8]) -> Result<usize, ErrorStack> {
1458        let mut siglen = sig.len();
1459        let rc = unsafe {
1460            sys::EVP_PKEY_sign_message_final(
1461                self.ctx,
1462                sig.as_mut_ptr(),
1463                std::ptr::addr_of_mut!(siglen),
1464            )
1465        };
1466        // self is dropped here → EVP_PKEY_CTX_free via Drop.
1467        if rc != 1 {
1468            return Err(ErrorStack::drain());
1469        }
1470        Ok(siglen)
1471    }
1472
1473    /// One-shot sign: feed `data` then finalise into `sig`.
1474    ///
1475    /// Consumes `self`.  Use this for algorithms that do not support
1476    /// streaming (`supports_streaming` returns `false`).
1477    ///
1478    /// # Errors
1479    pub fn sign_oneshot(self, data: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack> {
1480        // Feed all data, then finalise.  Both ops share the same ctx.
1481        let rc_upd = unsafe {
1482            sys::EVP_PKEY_sign_message_update(self.ctx, data.as_ptr(), data.len())
1483        };
1484        if rc_upd != 1 {
1485            // self dropped here → ctx freed.
1486            return Err(ErrorStack::drain());
1487        }
1488        let mut siglen = sig.len();
1489        let rc_fin = unsafe {
1490            sys::EVP_PKEY_sign_message_final(
1491                self.ctx,
1492                sig.as_mut_ptr(),
1493                std::ptr::addr_of_mut!(siglen),
1494            )
1495        };
1496        // self dropped here → ctx freed.
1497        if rc_fin != 1 {
1498            return Err(ErrorStack::drain());
1499        }
1500        Ok(siglen)
1501    }
1502}
1503
1504#[cfg(ossl320)]
1505impl Drop for MessageSigner {
1506    fn drop(&mut self) {
1507        unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
1508    }
1509}
1510
1511// ── MessageVerifier — EVP_PKEY_verify_message_* streaming verify (OpenSSL 3.2+)
1512
1513/// Stateful verification context using `EVP_PKEY_verify_message_*` (OpenSSL 3.2+).
1514///
1515/// Mirror of [`MessageSigner`] for the verification side.
1516///
1517/// For streaming mode: call [`set_signature`](Self::set_signature), then
1518/// [`update`](Self::update) zero or more times, then [`finish`](Self::finish).
1519/// For one-shot: call [`verify_oneshot`](Self::verify_oneshot).
1520#[cfg(ossl320)]
1521pub struct MessageVerifier {
1522    ctx: *mut sys::EVP_PKEY_CTX,
1523}
1524
1525#[cfg(ossl320)]
1526unsafe impl Send for MessageVerifier {}
1527
1528#[cfg(ossl320)]
1529impl MessageVerifier {
1530    /// Create and initialise a message-verify context.
1531    ///
1532    /// # Errors
1533    pub fn new<T: HasPublic>(
1534        key: &Pkey<T>,
1535        alg: &mut SigAlg,
1536        params: Option<&crate::params::Params<'_>>,
1537    ) -> Result<Self, ErrorStack> {
1538        let ptr = unsafe {
1539            sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
1540        };
1541        if ptr.is_null() {
1542            return Err(ErrorStack::drain());
1543        }
1544        let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
1545        crate::ossl_call!(sys::EVP_PKEY_verify_message_init(ptr, alg.ptr, params_ptr))
1546            .map_err(|e| {
1547                unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1548                e
1549            })?;
1550        Ok(MessageVerifier { ctx: ptr })
1551    }
1552
1553    /// Apply parameters after init.
1554    ///
1555    /// # Errors
1556    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
1557        crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ctx, params.as_ptr()))
1558    }
1559
1560    /// Supply the signature to verify against (required before streaming `finish`).
1561    ///
1562    /// Calls `EVP_PKEY_CTX_set_signature`.  Not needed for
1563    /// [`verify_oneshot`](Self::verify_oneshot) which sets it internally.
1564    ///
1565    /// # Errors
1566    pub fn set_signature(&mut self, sig: &[u8]) -> Result<(), ErrorStack> {
1567        crate::ossl_call!(sys::EVP_PKEY_CTX_set_signature(self.ctx, sig.as_ptr(), sig.len()))
1568    }
1569
1570    /// Probe whether this algorithm supports incremental `update` calls.
1571    ///
1572    /// Uses the same `ERR_set_mark` / `ERR_pop_to_mark` probe as
1573    /// [`MessageSigner::supports_streaming`].
1574    pub fn supports_streaming(&mut self) -> bool {
1575        unsafe { sys::ERR_set_mark() };
1576        let probe: [u8; 0] = [];
1577        let rc = unsafe {
1578            sys::EVP_PKEY_verify_message_update(self.ctx, probe.as_ptr(), 0)
1579        };
1580        unsafe { sys::ERR_pop_to_mark() };
1581        rc == 1
1582    }
1583
1584    /// Feed `data` into the verification operation.
1585    ///
1586    /// # Errors
1587    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
1588        crate::ossl_call!(sys::EVP_PKEY_verify_message_update(
1589            self.ctx,
1590            data.as_ptr(),
1591            data.len(),
1592        ))
1593    }
1594
1595    /// Finalise and verify.
1596    ///
1597    /// The signature must have been set via [`set_signature`](Self::set_signature).
1598    /// Consumes `self`.  Returns `Ok(())` if the signature is valid.
1599    ///
1600    /// # Errors
1601    pub fn finish(self) -> Result<(), ErrorStack> {
1602        let rc = unsafe { sys::EVP_PKEY_verify_message_final(self.ctx) };
1603        // self dropped here → ctx freed.
1604        if rc != 1 {
1605            return Err(ErrorStack::drain());
1606        }
1607        Ok(())
1608    }
1609
1610    /// One-shot verify `sig` over `data`.
1611    ///
1612    /// Sets the signature, feeds all data, and finalises.  Consumes `self`.
1613    ///
1614    /// # Errors
1615    pub fn verify_oneshot(self, data: &[u8], sig: &[u8]) -> Result<(), ErrorStack> {
1616        let rc_set = unsafe {
1617            sys::EVP_PKEY_CTX_set_signature(self.ctx, sig.as_ptr(), sig.len())
1618        };
1619        if rc_set != 1 {
1620            return Err(ErrorStack::drain());
1621        }
1622        let rc_upd = unsafe {
1623            sys::EVP_PKEY_verify_message_update(self.ctx, data.as_ptr(), data.len())
1624        };
1625        if rc_upd != 1 {
1626            return Err(ErrorStack::drain());
1627        }
1628        let rc_fin = unsafe { sys::EVP_PKEY_verify_message_final(self.ctx) };
1629        // self dropped here → ctx freed.
1630        if rc_fin != 1 {
1631            return Err(ErrorStack::drain());
1632        }
1633        Ok(())
1634    }
1635}
1636
1637#[cfg(ossl320)]
1638impl Drop for MessageVerifier {
1639    fn drop(&mut self) {
1640        unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
1641    }
1642}
1643
1644// ── Tests ─────────────────────────────────────────────────────────────────────
1645
1646#[cfg(test)]
1647mod tests {
1648    use super::*;
1649
1650    /// Generate an Ed25519 key pair; sign and verify "hello world".
1651    ///
1652    /// Ed25519 is a one-shot algorithm — uses `sign_oneshot` / `verify_oneshot`.
1653    #[test]
1654    fn ed25519_sign_verify() {
1655        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
1656        let priv_key = kgen.generate().unwrap();
1657        let pub_key = Pkey::<Public>::from(priv_key.clone());
1658
1659        let msg = b"hello world";
1660        let init = SignInit::default();
1661
1662        let mut signer = Signer::new(&priv_key, &init).unwrap();
1663        let sig = signer.sign_oneshot(msg).unwrap();
1664        assert!(!sig.is_empty());
1665
1666        let mut verifier = Verifier::new(&pub_key, &init).unwrap();
1667        assert!(verifier.verify_oneshot(msg, &sig).unwrap());
1668    }
1669
1670    /// Tampered message must fail verification.
1671    #[test]
1672    fn ed25519_verify_wrong_msg_fails() {
1673        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
1674        let priv_key = kgen.generate().unwrap();
1675        let pub_key = Pkey::<Public>::from(priv_key.clone());
1676
1677        let mut signer = Signer::new(&priv_key, &SignInit::default()).unwrap();
1678        let sig = signer.sign_oneshot(b"correct").unwrap();
1679
1680        let mut verifier = Verifier::new(&pub_key, &SignInit::default()).unwrap();
1681        assert!(!verifier.verify_oneshot(b"tampered", &sig).unwrap());
1682    }
1683
1684    /// Generate an X25519 key pair; perform ECDH derive and confirm length.
1685    #[test]
1686    fn x25519_derive() {
1687        let mut kgen_a = KeygenCtx::new(c"X25519").unwrap();
1688        let priv_a = kgen_a.generate().unwrap();
1689
1690        let mut kgen_b = KeygenCtx::new(c"X25519").unwrap();
1691        let priv_b = kgen_b.generate().unwrap();
1692        let pub_b = Pkey::<Public>::from(priv_b);
1693
1694        let mut derive = DeriveCtx::new(&priv_a).unwrap();
1695        derive.set_peer(&pub_b).unwrap();
1696        let len = derive.derive_len().unwrap();
1697        assert_eq!(len, 32); // X25519 shared secret is always 32 bytes
1698
1699        let mut ss = vec![0u8; len];
1700        let n = derive.derive(&mut ss).unwrap();
1701        assert_eq!(n, 32);
1702        assert_ne!(ss, [0u8; 32]);
1703    }
1704
1705    /// Round-trip through PEM: generate Ed25519, export, re-import, verify key equality.
1706    #[test]
1707    fn pem_round_trip() {
1708        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
1709        let priv_key = kgen.generate().unwrap();
1710
1711        let pem = priv_key.to_pem().unwrap();
1712        assert!(!pem.is_empty());
1713        assert!(pem.starts_with(b"-----BEGIN"));
1714
1715        let priv_key2 = Pkey::<Private>::from_pem(&pem).unwrap();
1716        assert_eq!(priv_key.bits(), priv_key2.bits());
1717        assert!(priv_key.is_a(c"ED25519"));
1718    }
1719
1720    /// Key metadata: Ed25519 should report 256 bits.
1721    #[test]
1722    fn ed25519_metadata() {
1723        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
1724        let key = kgen.generate().unwrap();
1725        assert_eq!(key.bits(), 256);
1726        assert_eq!(key.security_bits(), 128);
1727        assert!(key.is_a(c"ED25519"));
1728    }
1729
1730    /// RawSigner / RawVerifier round-trip with P-256 ECDSA over a pre-hashed digest.
1731    #[test]
1732    fn ecdsa_p256_raw_sign_verify() {
1733        // Generate P-256 key.
1734        let mut kgen = KeygenCtx::new(c"EC").unwrap();
1735        let params = crate::params::ParamBuilder::new().unwrap()
1736            .push_utf8_string(c"group", c"P-256").unwrap()
1737            .build().unwrap();
1738        kgen.set_params(&params).unwrap();
1739        let priv_key = kgen.generate().unwrap();
1740        let pub_key = Pkey::<Public>::from(priv_key.clone());
1741
1742        // SHA-256 hash of the message (32 bytes — the "pre-hash").
1743        let tbs: [u8; 32] = *b"0123456789abcdef0123456789abcdef";
1744
1745        let mut signer = RawSigner::new(&priv_key, None).unwrap();
1746        let sig = signer.sign_alloc(&tbs).unwrap();
1747        assert!(!sig.is_empty());
1748
1749        let mut verifier = RawVerifier::new(&pub_key, None).unwrap();
1750        verifier.verify(&tbs, &sig).unwrap();
1751    }
1752
1753    /// RawVerifier rejects a tampered signature.
1754    #[test]
1755    fn ecdsa_p256_raw_verify_tampered_fails() {
1756        let mut kgen = KeygenCtx::new(c"EC").unwrap();
1757        let params = crate::params::ParamBuilder::new().unwrap()
1758            .push_utf8_string(c"group", c"P-256").unwrap()
1759            .build().unwrap();
1760        kgen.set_params(&params).unwrap();
1761        let priv_key = kgen.generate().unwrap();
1762        let pub_key = Pkey::<Public>::from(priv_key.clone());
1763
1764        let tbs: [u8; 32] = *b"0123456789abcdef0123456789abcdef";
1765        let mut signer = RawSigner::new(&priv_key, None).unwrap();
1766        let mut sig = signer.sign_alloc(&tbs).unwrap();
1767        // Flip a byte in the signature.
1768        if let Some(b) = sig.last_mut() { *b ^= 0xff; }
1769
1770        let mut verifier = RawVerifier::new(&pub_key, None).unwrap();
1771        assert!(verifier.verify(&tbs, &sig).is_err());
1772    }
1773
1774    /// SigAlg: fetch, clone, drop — verifies EVP_SIGNATURE lifecycle.
1775    ///
1776    /// Does not attempt sign_message_final: OpenSSL 3.5's built-in ML-DSA
1777    /// provider implements only the one-shot EVP_DigestSign path, not
1778    /// OSSL_FUNC_SIGNATURE_SIGN_MESSAGE_FINAL.  Actual ML-DSA signing is
1779    /// done via Signer (DigestSign with NULL digest).
1780    #[cfg(ossl320)]
1781    #[test]
1782    fn sig_alg_fetch_clone_drop() {
1783        let alg = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
1784        let alg2 = alg.clone();
1785        drop(alg);
1786        drop(alg2); // both up_ref / free paths exercised
1787    }
1788
1789    /// MessageSigner construction and supports_streaming probe for Ed25519.
1790    ///
1791    /// Ed25519 supports EVP_PKEY_sign_message_init.  The streaming probe
1792    /// uses ERR_set_mark / ERR_pop_to_mark so it cannot leave error state.
1793    #[cfg(ossl320)]
1794    #[test]
1795    fn message_signer_construction_and_streaming_probe() {
1796        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
1797        let priv_key = kgen.generate().unwrap();
1798
1799        let mut alg = SigAlg::fetch(c"ED25519", None).unwrap();
1800        let mut signer = MessageSigner::new(&priv_key, &mut alg, None).unwrap();
1801
1802        // Probe must not crash or leave the error queue dirty.
1803        let _streaming = signer.supports_streaming();
1804        // Error queue must be empty after the probe.
1805        assert_eq!(crate::error::ErrorStack::drain().errors().count(), 0);
1806    }
1807
1808    /// MessageVerifier construction for Ed25519.
1809    #[cfg(ossl320)]
1810    #[test]
1811    fn message_verifier_construction() {
1812        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
1813        let priv_key = kgen.generate().unwrap();
1814        let pub_key = Pkey::<Public>::from(priv_key.clone());
1815
1816        let mut alg = SigAlg::fetch(c"ED25519", None).unwrap();
1817        let _verifier = MessageVerifier::new(&pub_key, &mut alg, None).unwrap();
1818    }
1819
1820    /// `Pkey<Public>::from_pem_in` loads a public key within an explicit `LibCtx`.
1821    #[test]
1822    fn pubkey_from_pem_in_roundtrip() {
1823        // Generate an EC key and export the public half to PEM.
1824        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
1825        let priv_key = kgen.generate().unwrap();
1826        let pub_pem = Pkey::<Public>::from(priv_key).to_pem().unwrap();
1827
1828        // Re-import via from_pem_in using the default lib ctx.
1829        let lib_ctx = Arc::new(crate::lib_ctx::LibCtx::new().unwrap());
1830        let pub_key = Pkey::<Public>::from_pem_in(&lib_ctx, &pub_pem).unwrap();
1831
1832        // Sanity: key is usable for verification.
1833        assert!(!pub_key.to_pem().unwrap().is_empty());
1834    }
1835}