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::bio::{MemBio, MemBioBuf};
14use crate::error::ErrorStack;
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 {
75 ptr: self.ptr,
76 _role: PhantomData,
77 }
78 }
79}
80
81impl<T> Drop for Pkey<T> {
82 fn drop(&mut self) {
83 unsafe { sys::EVP_PKEY_free(self.ptr) };
84 }
85}
86
87impl<T: HasParams> Pkey<T> {
88 /// Construct from a raw (owned) `EVP_PKEY*`.
89 ///
90 /// # Safety
91 ///
92 /// `ptr` must be a valid, non-null `EVP_PKEY*` that the caller is giving up ownership of.
93 #[must_use]
94 pub unsafe fn from_ptr(ptr: *mut sys::EVP_PKEY) -> Self {
95 Pkey {
96 ptr,
97 _role: PhantomData,
98 }
99 }
100
101 /// Raw `EVP_PKEY*` pointer valid for the lifetime of `self`.
102 #[must_use]
103 pub fn as_ptr(&self) -> *mut sys::EVP_PKEY {
104 self.ptr
105 }
106
107 /// Size of the key in bits (e.g. 256 for P-256, 2048 for RSA-2048).
108 #[must_use]
109 pub fn bits(&self) -> u32 {
110 u32::try_from(unsafe { sys::EVP_PKEY_get_bits(self.ptr) }).unwrap_or(0)
111 }
112
113 /// Security strength in bits (e.g. 128 for P-256, 112 for RSA-2048).
114 #[must_use]
115 pub fn security_bits(&self) -> u32 {
116 u32::try_from(unsafe { sys::EVP_PKEY_get_security_bits(self.ptr) }).unwrap_or(0)
117 }
118
119 /// Maximum output size in bytes for this key's primary operation.
120 ///
121 /// For signing keys (RSA, ECDSA, Ed25519) this is the maximum signature
122 /// length. For encryption keys (RSA-OAEP) this is the maximum ciphertext
123 /// length. Use this to size output buffers before calling signing or
124 /// encryption operations.
125 ///
126 /// Returns `0` if the key does not support a size query.
127 #[must_use]
128 pub fn max_output_size(&self) -> usize {
129 // SAFETY:
130 // - self.ptr is non-null (constructor invariant)
131 // - no mutable aliasing; &self ensures shared read-only access
132 usize::try_from(unsafe { sys::EVP_PKEY_get_size(self.ptr) }).unwrap_or(0)
133 }
134
135 /// Return `true` if this key is of the named algorithm (e.g. `c"EC"`, `c"RSA"`).
136 #[must_use]
137 pub fn is_a(&self, name: &std::ffi::CStr) -> bool {
138 unsafe { sys::EVP_PKEY_is_a(self.ptr, name.as_ptr()) == 1 }
139 }
140
141 /// Return `true` if this key's public component equals `other`'s.
142 ///
143 /// Wraps `EVP_PKEY_eq`. Useful for verifying that a certificate's public
144 /// key matches a private key before using them together.
145 #[must_use]
146 pub fn public_eq<U: HasPublic>(&self, other: &Pkey<U>) -> bool
147 where
148 T: HasPublic,
149 {
150 unsafe { sys::EVP_PKEY_eq(self.ptr, other.ptr) == 1 }
151 }
152
153 /// Fill the values for a pre-prepared mutable `Params` query array.
154 ///
155 /// Wraps `EVP_PKEY_get_params`. The array must already contain the keys
156 /// of interest with null data pointers; OpenSSL writes the values in place.
157 ///
158 /// # Errors
159 pub fn get_params(&self, params: &mut crate::params::Params<'_>) -> Result<(), ErrorStack> {
160 crate::ossl_call!(sys::EVP_PKEY_get_params(self.ptr, params.as_mut_ptr()))
161 }
162
163 /// DER-encode the public key (`SubjectPublicKeyInfo` format).
164 ///
165 /// Zero-copy: writes directly into a caller-owned `Vec<u8>` — no OpenSSL
166 /// heap allocation occurs.
167 ///
168 /// # Errors
169 ///
170 /// Returns `Err` if serialisation fails.
171 pub fn public_key_to_der(&self) -> Result<Vec<u8>, ErrorStack>
172 where
173 T: HasPublic,
174 {
175 // First call with null to query the DER byte length.
176 let len = unsafe { sys::i2d_PUBKEY(self.ptr, std::ptr::null_mut()) };
177 if len < 0 {
178 return Err(ErrorStack::drain());
179 }
180 // Allocate our own buffer and write into it.
181 // i2d_ advances the out-pointer; our Vec base address is unaffected.
182 let mut buf = vec![0u8; usize::try_from(len).unwrap_or(0)];
183 let mut out_ptr = buf.as_mut_ptr();
184 let written = unsafe { sys::i2d_PUBKEY(self.ptr, std::ptr::addr_of_mut!(out_ptr)) };
185 if written < 0 {
186 return Err(ErrorStack::drain());
187 }
188 buf.truncate(usize::try_from(written).unwrap_or(0));
189 Ok(buf)
190 }
191
192 /// Returns the default digest algorithm name for this key, or `None` if the
193 /// key type mandates no external digest (e.g. Ed25519, ML-DSA).
194 ///
195 /// Wraps `EVP_PKEY_get_default_digest_name`. The function returns 1 when the
196 /// digest is optional and 2 when it is mandatory; both are treated as success.
197 /// When the key's algorithm performs its own internal hashing (Ed25519, ML-DSA,
198 /// SLH-DSA), OpenSSL writes `"none"` or an empty string — both map to `Ok(None)`.
199 ///
200 /// Callers should check this before deciding whether to pass a digest to
201 /// `DigestSign` / `DigestVerify`: if `default_digest_name()` returns `None`,
202 /// pass `digest: None` in `SignInit`; otherwise pass the returned name.
203 ///
204 /// # Errors
205 ///
206 /// Returns `Err` if OpenSSL cannot determine the digest.
207 pub fn default_digest_name(&self) -> Result<Option<String>, ErrorStack>
208 where
209 T: HasPublic,
210 {
211 // SAFETY: self.ptr is a valid non-null EVP_PKEY* (constructor invariant).
212 // EVP_PKEY_get_default_digest_name writes a NUL-terminated C string into
213 // mdname and returns >= 1 on success.
214 let mut buf = [0u8; 64];
215 let rc = unsafe {
216 sys::EVP_PKEY_get_default_digest_name(
217 self.ptr,
218 buf.as_mut_ptr().cast::<std::ffi::c_char>(),
219 buf.len(),
220 )
221 };
222 if rc < 1 {
223 return Err(ErrorStack::drain());
224 }
225 // Find the NUL terminator.
226 let name_bytes = buf
227 .iter()
228 .position(|&b| b == 0)
229 .map_or(&buf[..], |pos| &buf[..pos]);
230 // "none", "UNDEF", or empty string means no separate digest.
231 // OpenSSL 3.5+ returns "UNDEF" for algorithms like Ed25519 that do
232 // their own internal hashing without a caller-supplied digest.
233 if name_bytes.is_empty()
234 || name_bytes.eq_ignore_ascii_case(b"none")
235 || name_bytes.eq_ignore_ascii_case(b"undef")
236 {
237 return Ok(None);
238 }
239 let name = std::str::from_utf8(name_bytes)
240 .map_err(|_| ErrorStack::drain())?
241 .to_owned();
242 Ok(Some(name))
243 }
244}
245
246// ── PEM loading — private key ─────────────────────────────────────────────────
247
248impl Pkey<Private> {
249 /// Return the provider-side `keydata` pointer from the `EVP_PKEY` struct.
250 ///
251 /// This is the `void *keydata` field of `evp_pkey_st`. It holds the
252 /// algorithm-specific key material allocated by the provider's keymgmt
253 /// implementation. The pointer is valid for the lifetime of `self`.
254 ///
255 /// Only available with the `fips-provider` cargo feature. Intended for
256 /// use inside a FIPS provider when invoking `EVP_SIGNATURE` vtable functions
257 /// that require `void *provkey` (= `keydata`).
258 ///
259 /// # Safety
260 ///
261 /// The returned pointer points into the internal state of this `Pkey`.
262 /// It must not outlive `self` and must not be freed independently.
263 /// The field offset is computed by the C compiler for the current target
264 /// ABI via a build-time probe; see `native-ossl-sys/build.rs`.
265 #[cfg(feature = "fips-provider")]
266 pub unsafe fn keydata(&self) -> *mut std::ffi::c_void {
267 // SAFETY: self.ptr is a valid non-null EVP_PKEY*. We read the void* at
268 // the ABI-correct byte offset of the `keydata` field in evp_pkey_st,
269 // determined at build time by a C offsetof probe.
270 self.ptr
271 .cast::<u8>()
272 .add(native_ossl_sys::fips_internal::EVP_PKEY_KEYDATA_OFFSET)
273 .cast::<*mut std::ffi::c_void>()
274 .read()
275 }
276
277 /// Load a private key from PEM bytes.
278 ///
279 /// Pass `passphrase = Some(cb)` for encrypted PEM; `None` for unencrypted.
280 ///
281 /// # Errors
282 pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
283 let bio = MemBioBuf::new(pem)?;
284 let ptr = unsafe {
285 sys::PEM_read_bio_PrivateKey(
286 bio.as_ptr(),
287 std::ptr::null_mut(),
288 None,
289 std::ptr::null_mut(),
290 )
291 };
292 if ptr.is_null() {
293 return Err(ErrorStack::drain());
294 }
295 Ok(unsafe { Pkey::from_ptr(ptr) })
296 }
297
298 /// Serialise the private key to PEM (`PKCS#8` `BEGIN PRIVATE KEY`).
299 ///
300 /// # Errors
301 pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack> {
302 let mut bio = MemBio::new()?;
303 let rc = unsafe {
304 sys::PEM_write_bio_PrivateKey(
305 bio.as_ptr(),
306 self.ptr,
307 std::ptr::null(),
308 std::ptr::null_mut(),
309 0,
310 None,
311 std::ptr::null_mut(),
312 )
313 };
314 if rc != 1 {
315 return Err(ErrorStack::drain());
316 }
317 Ok(bio.into_vec())
318 }
319
320 /// Load a private key from PEM bytes within a specific library context.
321 ///
322 /// Uses `PEM_read_bio_PrivateKey_ex` so the key's internal algorithm fetch
323 /// uses `ctx`'s provider set. Necessary when the private key is later used
324 /// for EVP operations inside an isolated (e.g. FIPS) context.
325 ///
326 /// # Errors
327 pub fn from_pem_in(ctx: &Arc<crate::lib_ctx::LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack> {
328 let bio = MemBioBuf::new(pem)?;
329 let ptr = unsafe {
330 sys::PEM_read_bio_PrivateKey_ex(
331 bio.as_ptr(),
332 std::ptr::null_mut(),
333 None,
334 std::ptr::null_mut(),
335 ctx.as_ptr(),
336 std::ptr::null(),
337 )
338 };
339 if ptr.is_null() {
340 return Err(ErrorStack::drain());
341 }
342 Ok(unsafe { Pkey::from_ptr(ptr) })
343 }
344
345 /// Load a private key from DER bytes (auto-detecting `PKCS#8` / traditional).
346 ///
347 /// Zero-copy: the `EVP_PKEY` is decoded from the caller's slice without copying.
348 ///
349 /// # Errors
350 pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
351 let bio = MemBioBuf::new(der)?;
352 let ptr = unsafe { sys::d2i_PrivateKey_bio(bio.as_ptr(), std::ptr::null_mut()) };
353 if ptr.is_null() {
354 return Err(ErrorStack::drain());
355 }
356 Ok(unsafe { Pkey::from_ptr(ptr) })
357 }
358
359 /// Load a private key from DER bytes using an explicit library context.
360 ///
361 /// The key format is detected automatically (`PKCS#8`, traditional, etc.).
362 /// Wraps `d2i_AutoPrivateKey_ex` — unlike [`from_der`](Self::from_der) (which uses a
363 /// `BIO`), this function passes the raw byte pointer directly to OpenSSL so that
364 /// the key's internal algorithm fetch is bound to `ctx`'s provider set. Use
365 /// this when the loaded key will be used for EVP operations inside an isolated
366 /// (e.g. FIPS) context.
367 ///
368 /// # Errors
369 ///
370 /// Returns `Err` if the DER bytes are malformed or the algorithm is not
371 /// available in `ctx`.
372 pub fn from_der_in(ctx: &Arc<crate::lib_ctx::LibCtx>, der: &[u8]) -> Result<Self, ErrorStack> {
373 // `d2i_AutoPrivateKey_ex` expects a `*mut *const u8` input pointer; it
374 // advances the pointer past the consumed bytes but we do not need the
375 // updated value, so we bind it to a local that is discarded afterwards.
376 let mut ptr = der.as_ptr();
377 // SAFETY:
378 // - Non-null: `der.as_ptr()` is always valid for a shared slice (even if
379 // zero-length; OpenSSL will reject zero-length DER before dereferencing).
380 // - `ctx.as_ptr()` is non-null by `LibCtx` constructor invariant; `ctx`
381 // is kept alive by the `&Arc<LibCtx>` borrow for the entire call.
382 // - Lifetime: `ptr` points into `der` which lives for the call duration;
383 // OpenSSL reads at most `der.len()` bytes before returning.
384 // - Exclusivity / no data races: `der` is a shared immutable borrow; no
385 // other thread can mutate it. `ptr` is a local stack copy — OpenSSL's
386 // write to `*pp` (advancing the pointer) touches only this variable,
387 // not the original slice.
388 let pkey = unsafe {
389 sys::d2i_AutoPrivateKey_ex(
390 std::ptr::null_mut(),
391 std::ptr::addr_of_mut!(ptr),
392 i64::try_from(der.len()).unwrap_or(i64::MAX),
393 ctx.as_ptr(),
394 std::ptr::null(),
395 )
396 };
397 if pkey.is_null() {
398 return Err(ErrorStack::drain());
399 }
400 // SAFETY: `pkey` is non-null and freshly allocated by OpenSSL; we take
401 // exclusive ownership of the reference count.
402 Ok(unsafe { Pkey::from_ptr(pkey) })
403 }
404
405 /// Load a private key from passphrase-encrypted PEM.
406 ///
407 /// Passes `passphrase` directly to `PEM_read_bio_PrivateKey` via a
408 /// `pem_password_cb`. Returns `Err` if the key cannot be decrypted or
409 /// the PEM is malformed. For unencrypted PEM use [`from_pem`](Self::from_pem).
410 ///
411 /// # Errors
412 pub fn from_pem_passphrase(pem: &[u8], passphrase: &[u8]) -> Result<Self, ErrorStack> {
413 extern "C" fn passwd_cb(
414 buf: *mut std::ffi::c_char,
415 size: std::ffi::c_int,
416 _rwflag: std::ffi::c_int,
417 u: *mut std::ffi::c_void,
418 ) -> std::ffi::c_int {
419 // SAFETY: `u` is `&pw` where `pw: &[u8]` lives on the caller's stack.
420 let pw: &[u8] = unsafe { *(u as *const &[u8]) };
421 // size is the callback buffer capacity; it is always > 0 per the C contract.
422 let max_len = usize::try_from(size).unwrap_or(0);
423 let n = pw.len().min(max_len);
424 unsafe { std::ptr::copy_nonoverlapping(pw.as_ptr(), buf.cast::<u8>(), n) };
425 // n <= max_len == size (as usize), so n fits in i32.
426 i32::try_from(n).unwrap()
427 }
428 let bio = MemBioBuf::new(pem)?;
429 let pw: &[u8] = passphrase;
430 let ptr = unsafe {
431 sys::PEM_read_bio_PrivateKey(
432 bio.as_ptr(),
433 std::ptr::null_mut(),
434 Some(passwd_cb),
435 std::ptr::addr_of!(pw).cast::<std::ffi::c_void>().cast_mut(),
436 )
437 };
438 if ptr.is_null() {
439 return Err(ErrorStack::drain());
440 }
441 Ok(unsafe { Pkey::from_ptr(ptr) })
442 }
443
444 /// Serialise the private key as passphrase-encrypted PKCS#8 PEM
445 /// (`BEGIN ENCRYPTED PRIVATE KEY`).
446 ///
447 /// `cipher` controls the wrapping algorithm
448 /// (e.g. `CipherAlg::fetch(c"AES-256-CBC", None)`).
449 /// The passphrase is passed directly to OpenSSL via `kstr`/`klen`.
450 ///
451 /// # Panics
452 ///
453 /// Panics if `passphrase` is longer than `i32::MAX` bytes.
454 ///
455 /// # Errors
456 pub fn to_pem_encrypted(
457 &self,
458 cipher: &crate::cipher::CipherAlg,
459 passphrase: &[u8],
460 ) -> Result<Vec<u8>, ErrorStack> {
461 let mut bio = MemBio::new()?;
462 let rc = unsafe {
463 sys::PEM_write_bio_PKCS8PrivateKey(
464 bio.as_ptr(),
465 self.ptr,
466 cipher.as_ptr(),
467 passphrase.as_ptr().cast(),
468 i32::try_from(passphrase.len()).expect("passphrase too long"),
469 None,
470 std::ptr::null_mut(),
471 )
472 };
473 if rc != 1 {
474 return Err(ErrorStack::drain());
475 }
476 Ok(bio.into_vec())
477 }
478
479 /// Serialise the private key as unencrypted PKCS#8 DER
480 /// (`PrivateKeyInfo` / `OneAsymmetricKey`, RFC 5958).
481 ///
482 /// Equivalent to writing unencrypted PEM and stripping the base64 wrapper,
483 /// but avoids the encode/decode round-trip. To encrypt the output, use
484 /// [`to_pem_encrypted`](Self::to_pem_encrypted) instead.
485 ///
486 /// # Errors
487 pub fn to_pkcs8_der(&self) -> Result<Vec<u8>, ErrorStack> {
488 let mut bio = MemBio::new()?;
489 let rc = unsafe { sys::i2d_PKCS8PrivateKeyInfo_bio(bio.as_ptr(), self.ptr) };
490 if rc != 1 {
491 return Err(ErrorStack::drain());
492 }
493 Ok(bio.into_vec())
494 }
495}
496
497// ── PEM loading — public key ──────────────────────────────────────────────────
498
499impl Pkey<Public> {
500 /// Load a public key from PEM (`SubjectPublicKeyInfo` or RSA public key).
501 ///
502 /// # Errors
503 pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
504 let bio = MemBioBuf::new(pem)?;
505 let ptr = unsafe {
506 sys::PEM_read_bio_PUBKEY(
507 bio.as_ptr(),
508 std::ptr::null_mut(),
509 None,
510 std::ptr::null_mut(),
511 )
512 };
513 if ptr.is_null() {
514 return Err(ErrorStack::drain());
515 }
516 Ok(unsafe { Pkey::from_ptr(ptr) })
517 }
518
519 /// Load a public key from PEM bytes within a specific library context.
520 ///
521 /// Uses `PEM_read_bio_PUBKEY_ex` so the key's internal algorithm fetch
522 /// uses `ctx`'s provider set. Necessary when the public key is later used
523 /// for EVP operations inside an isolated (e.g. FIPS) context.
524 ///
525 /// # Errors
526 pub fn from_pem_in(ctx: &Arc<crate::lib_ctx::LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack> {
527 let bio = MemBioBuf::new(pem)?;
528 let ptr = unsafe {
529 sys::PEM_read_bio_PUBKEY_ex(
530 bio.as_ptr(),
531 std::ptr::null_mut(),
532 None,
533 std::ptr::null_mut(),
534 ctx.as_ptr(),
535 std::ptr::null(),
536 )
537 };
538 if ptr.is_null() {
539 return Err(ErrorStack::drain());
540 }
541 Ok(unsafe { Pkey::from_ptr(ptr) })
542 }
543
544 /// Load a public key from DER (`SubjectPublicKeyInfo`).
545 ///
546 /// # Errors
547 pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
548 let bio = MemBioBuf::new(der)?;
549 let ptr = unsafe { sys::d2i_PUBKEY_bio(bio.as_ptr(), std::ptr::null_mut()) };
550 if ptr.is_null() {
551 return Err(ErrorStack::drain());
552 }
553 Ok(unsafe { Pkey::from_ptr(ptr) })
554 }
555
556 /// Load a `SubjectPublicKeyInfo` DER public key using an explicit library context.
557 ///
558 /// Wraps `d2i_PUBKEY_ex` — unlike [`from_der`](Self::from_der) (which uses a
559 /// `BIO`), this function passes the raw byte pointer directly so that the
560 /// key's internal algorithm fetch is bound to `ctx`'s provider set. Use
561 /// this when the loaded key will be used for EVP operations inside an isolated
562 /// (e.g. FIPS) context.
563 ///
564 /// # Errors
565 ///
566 /// Returns `Err` if the DER bytes are malformed or the algorithm is not
567 /// available in `ctx`.
568 pub fn from_der_in(ctx: &Arc<crate::lib_ctx::LibCtx>, der: &[u8]) -> Result<Self, ErrorStack> {
569 let mut ptr = der.as_ptr();
570 // SAFETY:
571 // - Non-null: `der.as_ptr()` is always valid for a shared slice (even if
572 // zero-length; OpenSSL rejects zero-length DER before dereferencing).
573 // - `ctx.as_ptr()` is non-null by `LibCtx` constructor invariant; `ctx`
574 // is kept alive by the `&Arc<LibCtx>` borrow for the entire call.
575 // - Lifetime: `ptr` points into `der` which lives for the call duration;
576 // OpenSSL reads at most `der.len()` bytes before returning.
577 // - Exclusivity / no data races: `der` is a shared immutable borrow; no
578 // other thread can mutate it. `ptr` is a local stack copy — OpenSSL's
579 // write to `*pp` touches only this variable, not the original slice.
580 let pkey = unsafe {
581 sys::d2i_PUBKEY_ex(
582 std::ptr::null_mut(),
583 std::ptr::addr_of_mut!(ptr),
584 i64::try_from(der.len()).unwrap_or(i64::MAX),
585 ctx.as_ptr(),
586 std::ptr::null(),
587 )
588 };
589 if pkey.is_null() {
590 return Err(ErrorStack::drain());
591 }
592 // SAFETY: `pkey` is non-null and freshly allocated by OpenSSL; we take
593 // exclusive ownership of the reference count.
594 Ok(unsafe { Pkey::from_ptr(pkey) })
595 }
596
597 /// Serialise the public key to PEM.
598 ///
599 /// # Errors
600 pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack> {
601 let mut bio = MemBio::new()?;
602 let rc = unsafe { sys::PEM_write_bio_PUBKEY(bio.as_ptr(), self.ptr) };
603 if rc != 1 {
604 return Err(ErrorStack::drain());
605 }
606 Ok(bio.into_vec())
607 }
608}
609
610// Upcast: every `Pkey<Private>` can be viewed as `Pkey<Public>`.
611impl From<Pkey<Private>> for Pkey<Public> {
612 fn from(k: Pkey<Private>) -> Self {
613 unsafe { sys::EVP_PKEY_up_ref(k.ptr) };
614 Pkey {
615 ptr: k.ptr,
616 _role: PhantomData,
617 }
618 }
619}
620
621// ── Key import from OSSL_PARAM ────────────────────────────────────────────────
622
623// EVP_PKEY_fromdata / EVP_PKEY_todata selection constants.
624// These must match the macros in <openssl/keymgmt.h>:
625// EVP_PKEY_PUBLIC_KEY = OSSL_KEYMGMT_SELECT_ALL_PARAMETERS | SELECT_PUBLIC_KEY
626// = (0x04 | 0x80) | 0x02 = 0x86
627// EVP_PKEY_KEYPAIR = EVP_PKEY_PUBLIC_KEY | SELECT_PRIVATE_KEY
628// = 0x86 | 0x01 = 0x87
629// Using the bare SELECT_PUBLIC_KEY (0x02) would omit domain parameters, which
630// breaks EC keys (the curve group is a domain parameter, not a public-key param).
631const PKEY_PUBLIC_KEY: i32 = 0x86;
632const PKEY_KEYPAIR: i32 = 0x87;
633
634/// Shared implementation for `EVP_PKEY_fromdata`.
635fn pkey_fromdata(
636 ctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
637 pkey_type: &std::ffi::CStr,
638 params: &crate::params::Params<'_>,
639 selection: i32,
640) -> Result<*mut sys::EVP_PKEY, ErrorStack> {
641 let libctx = ctx.map_or(std::ptr::null_mut(), |c| c.as_ptr());
642 let pctx =
643 unsafe { sys::EVP_PKEY_CTX_new_from_name(libctx, pkey_type.as_ptr(), std::ptr::null()) };
644 if pctx.is_null() {
645 return Err(ErrorStack::drain());
646 }
647 let rc = unsafe { sys::EVP_PKEY_fromdata_init(pctx) };
648 if rc != 1 {
649 unsafe { sys::EVP_PKEY_CTX_free(pctx) };
650 return Err(ErrorStack::drain());
651 }
652 let mut pkey: *mut sys::EVP_PKEY = std::ptr::null_mut();
653 let rc = unsafe {
654 sys::EVP_PKEY_fromdata(
655 pctx,
656 std::ptr::addr_of_mut!(pkey),
657 selection,
658 // OSSL_PARAM array is read-only during fromdata; cast is safe.
659 params.as_ptr().cast_mut(),
660 )
661 };
662 unsafe { sys::EVP_PKEY_CTX_free(pctx) };
663 if rc != 1 || pkey.is_null() {
664 return Err(ErrorStack::drain());
665 }
666 Ok(pkey)
667}
668
669/// Shared implementation for `EVP_PKEY_todata`.
670fn pkey_todata(
671 ptr: *mut sys::EVP_PKEY,
672 selection: i32,
673) -> Result<crate::params::Params<'static>, ErrorStack> {
674 let mut out: *mut sys::OSSL_PARAM = std::ptr::null_mut();
675 let rc = unsafe { sys::EVP_PKEY_todata(ptr, selection, std::ptr::addr_of_mut!(out)) };
676 if rc != 1 || out.is_null() {
677 return Err(ErrorStack::drain());
678 }
679 // SAFETY: `out` is a freshly allocated OSSL_PARAM array from OpenSSL;
680 // Params takes ownership and will free it via OSSL_PARAM_free on drop.
681 Ok(unsafe { crate::params::Params::from_owned_ptr(out) })
682}
683
684impl Pkey<Private> {
685 /// Import a private key pair from an `OSSL_PARAM` array.
686 ///
687 /// Equivalent to `EVP_PKEY_fromdata` with `EVP_PKEY_KEYPAIR` selection.
688 /// Pass `ctx = None` to use the global default library context.
689 ///
690 /// # Errors
691 pub fn from_params(
692 ctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
693 pkey_type: &std::ffi::CStr,
694 params: &crate::params::Params<'_>,
695 ) -> Result<Self, ErrorStack> {
696 pkey_fromdata(ctx, pkey_type, params, PKEY_KEYPAIR)
697 .map(|ptr| unsafe { Pkey::from_ptr(ptr) })
698 }
699
700 /// Export all key parameters (private + public) as an owned `OSSL_PARAM` array.
701 ///
702 /// Uses `EVP_PKEY_KEYPAIR` selection so both private and public material
703 /// are included in the returned array.
704 ///
705 /// # Errors
706 pub fn export(&self) -> Result<crate::params::Params<'static>, ErrorStack> {
707 pkey_todata(self.ptr, PKEY_KEYPAIR)
708 }
709}
710
711impl Pkey<Public> {
712 /// Import a public key from an `OSSL_PARAM` array.
713 ///
714 /// Equivalent to `EVP_PKEY_fromdata` with `EVP_PKEY_PUBLIC_KEY` selection.
715 /// Pass `ctx = None` to use the global default library context.
716 ///
717 /// # Errors
718 pub fn from_params(
719 ctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
720 pkey_type: &std::ffi::CStr,
721 params: &crate::params::Params<'_>,
722 ) -> Result<Self, ErrorStack> {
723 pkey_fromdata(ctx, pkey_type, params, PKEY_PUBLIC_KEY)
724 .map(|ptr| unsafe { Pkey::from_ptr(ptr) })
725 }
726
727 /// Export the public key parameters as an owned `OSSL_PARAM` array.
728 ///
729 /// Uses `EVP_PKEY_PUBLIC_KEY` selection.
730 ///
731 /// # Errors
732 pub fn export(&self) -> Result<crate::params::Params<'static>, ErrorStack> {
733 pkey_todata(self.ptr, PKEY_PUBLIC_KEY)
734 }
735}
736
737// ── Legacy DER encoding ──────────────────────────────────────────────────────
738
739impl<T: HasPrivate> Pkey<T> {
740 /// Encode this private key to legacy raw-key DER (not PKCS#8).
741 ///
742 /// Wraps `i2d_PrivateKey`. The output is the algorithm-specific raw key
743 /// structure (e.g. `RSAPrivateKey` / RFC 3447 for RSA, `ECPrivateKey` /
744 /// RFC 5915 for EC) — **not** the `PrivateKeyInfo` / PKCS#8 wrapper.
745 ///
746 /// Use this for interoperability with software that requires the legacy
747 /// format. For new code prefer [`to_pkcs8_der`](crate::pkey::Pkey::to_pkcs8_der)
748 /// (PKCS#8 / RFC 5958), which is algorithm-agnostic and more widely supported
749 /// by modern toolkits.
750 ///
751 /// # Errors
752 ///
753 /// Returns `Err` if serialisation fails (e.g. the algorithm does not support
754 /// legacy DER export, such as some post-quantum algorithms).
755 pub fn to_der_legacy(&self) -> Result<Vec<u8>, ErrorStack> {
756 // First call with null to query the required output length.
757 // SAFETY:
758 // - Non-null: `self.ptr` is non-null by `Pkey` constructor invariant.
759 // - Lifetime: `self.ptr` is valid for the duration of `self`; this call
760 // does not store the pointer beyond the call.
761 // - Exclusivity / no data races: `&self` ensures no concurrent mutable
762 // access to `self.ptr`; the null second argument means OpenSSL only
763 // reads the key, it does not write through `pp`.
764 let len = unsafe { sys::i2d_PrivateKey(self.ptr, std::ptr::null_mut()) };
765 if len < 0 {
766 return Err(ErrorStack::drain());
767 }
768 let mut buf = vec![0u8; usize::try_from(len).unwrap_or(0)];
769 let mut out_ptr = buf.as_mut_ptr();
770 // SAFETY:
771 // - Non-null: `self.ptr` is non-null (constructor invariant).
772 // - `out_ptr` points into `buf` which has capacity `len` bytes; OpenSSL
773 // writes exactly `len` bytes and advances `out_ptr` past the data.
774 // - Lifetime: `buf` is alive for the duration of this call; OpenSSL does
775 // not retain the pointer after returning.
776 // - Exclusivity / no data races: `buf` is exclusively owned here; no
777 // other reference to its backing memory exists during the call.
778 let written = unsafe { sys::i2d_PrivateKey(self.ptr, std::ptr::addr_of_mut!(out_ptr)) };
779 if written < 0 {
780 return Err(ErrorStack::drain());
781 }
782 buf.truncate(usize::try_from(written).unwrap_or(0));
783 Ok(buf)
784 }
785}
786
787// ── KeygenCtx — key generation ────────────────────────────────────────────────
788
789/// Context for generating asymmetric key pairs (`EVP_PKEY_CTX` in keygen mode).
790pub struct KeygenCtx {
791 ptr: *mut sys::EVP_PKEY_CTX,
792}
793
794impl KeygenCtx {
795 /// Create a keygen context for the named algorithm.
796 ///
797 /// Common names: `c"RSA"`, `c"EC"`, `c"ED25519"`, `c"X25519"`.
798 ///
799 /// # Errors
800 pub fn new(name: &std::ffi::CStr) -> Result<Self, ErrorStack> {
801 let ptr = unsafe {
802 sys::EVP_PKEY_CTX_new_from_name(std::ptr::null_mut(), name.as_ptr(), std::ptr::null())
803 };
804 if ptr.is_null() {
805 return Err(ErrorStack::drain());
806 }
807 let rc = unsafe { sys::EVP_PKEY_keygen_init(ptr) };
808 if rc != 1 {
809 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
810 return Err(ErrorStack::drain());
811 }
812 Ok(KeygenCtx { ptr })
813 }
814
815 /// Configure parameters before calling `generate`.
816 ///
817 /// # Errors
818 pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
819 // SAFETY:
820 // - self.ptr is non-null (constructor invariant)
821 // - params.as_ptr() is valid for the duration of this call
822 // - &mut self ensures exclusive access; no concurrent reads or writes
823 crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ptr, params.as_ptr()))
824 }
825
826 /// Retrieve parameter values from this keygen context.
827 ///
828 /// Build a [`Params`][crate::params::Params] with placeholder values for the
829 /// keys you want, call this method, then read back with `params.get_*`.
830 ///
831 /// Useful for reading algorithm-negotiated parameters after keygen initialisation
832 /// (e.g. security strength, EC group name, RSA modulus size).
833 ///
834 /// # Errors
835 ///
836 /// Returns `Err` if `EVP_PKEY_CTX_get_params` fails.
837 pub fn get_params(&self, params: &mut crate::params::Params<'_>) -> Result<(), ErrorStack> {
838 // SAFETY:
839 // - self.ptr is non-null (constructor invariant)
840 // - params.as_mut_ptr() is valid for the duration of this call
841 // - &self ensures no concurrent mutable access to self.ptr
842 crate::ossl_call!(sys::EVP_PKEY_CTX_get_params(self.ptr, params.as_mut_ptr()))
843 }
844
845 /// Return the security strength of the key operation in bits.
846 ///
847 /// Available after keygen initialisation; reads `OSSL_PKEY_PARAM_SECURITY_BITS`
848 /// (`"security-bits"`).
849 ///
850 /// # Errors
851 ///
852 /// Returns `Err` if the context does not support this parameter or is not
853 /// sufficiently initialised.
854 pub fn security_bits(&self) -> Result<u32, ErrorStack> {
855 let mut params = crate::params::ParamBuilder::new()?
856 .push_uint(c"security-bits", 0)?
857 .build()?;
858 // SAFETY: same as get_params — self.ptr is non-null, params pointer is valid.
859 crate::ossl_call!(sys::EVP_PKEY_CTX_get_params(self.ptr, params.as_mut_ptr()))?;
860 params
861 .get_uint(c"security-bits")
862 .map_err(|_| crate::error::ErrorStack::drain())
863 }
864
865 /// Generate a key pair.
866 ///
867 /// # Errors
868 pub fn generate(&mut self) -> Result<Pkey<Private>, ErrorStack> {
869 let mut key: *mut sys::EVP_PKEY = std::ptr::null_mut();
870 crate::ossl_call!(sys::EVP_PKEY_keygen(self.ptr, std::ptr::addr_of_mut!(key)))?;
871 if key.is_null() {
872 return Err(ErrorStack::drain());
873 }
874 Ok(unsafe { Pkey::from_ptr(key) })
875 }
876}
877
878impl Drop for KeygenCtx {
879 fn drop(&mut self) {
880 unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
881 }
882}
883
884// ── Signer — streaming DigestSign ─────────────────────────────────────────────
885
886/// Parameters for creating a [`Signer`] or [`Verifier`].
887#[derive(Default)]
888pub struct SignInit<'a> {
889 /// Digest algorithm, or `None` for pre-hashed / `EdDSA` one-shot mode.
890 pub digest: Option<&'a crate::digest::DigestAlg>,
891 /// Optional parameters (e.g. RSA PSS salt length).
892 pub params: Option<&'a crate::params::Params<'a>>,
893}
894
895/// Streaming `DigestSign` context.
896///
897/// Call `update` zero or more times, then `finish` to produce the signature.
898pub struct Signer {
899 ctx: crate::digest::DigestCtx,
900 /// The key is kept alive for the duration of the signer.
901 _key: Pkey<Private>,
902}
903
904impl Signer {
905 /// Create a signer.
906 ///
907 /// # Errors
908 pub fn new(key: &Pkey<Private>, init: &SignInit<'_>) -> Result<Self, ErrorStack> {
909 let ctx = alloc_digest_ctx()?;
910 // Resolve digest name for EVP_DigestSignInit_ex (NULL for Ed25519/one-shot).
911 let md_name_ptr = if let Some(d) = init.digest {
912 let p = unsafe { sys::OBJ_nid2sn(d.nid()) };
913 if p.is_null() {
914 return Err(ErrorStack::drain());
915 }
916 p
917 } else {
918 std::ptr::null()
919 };
920 let params_ptr = init
921 .params
922 .map_or(crate::params::null_params(), crate::params::Params::as_ptr);
923 let rc = unsafe {
924 sys::EVP_DigestSignInit_ex(
925 ctx.as_ptr(),
926 std::ptr::null_mut(),
927 md_name_ptr,
928 std::ptr::null_mut(),
929 std::ptr::null(),
930 key.ptr,
931 params_ptr,
932 )
933 };
934 if rc != 1 {
935 return Err(ErrorStack::drain());
936 }
937 Ok(Signer {
938 ctx,
939 _key: key.clone(),
940 })
941 }
942
943 /// Feed data into the hash.
944 ///
945 /// # Errors
946 pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
947 crate::ossl_call!(sys::EVP_DigestSignUpdate(
948 self.ctx.as_ptr(),
949 data.as_ptr().cast(),
950 data.len()
951 ))
952 }
953
954 /// Finalise and return the signature.
955 ///
956 /// Not supported by pure one-shot algorithms such as Ed25519 — use
957 /// [`sign_oneshot`](Self::sign_oneshot) for those.
958 ///
959 /// # Errors
960 pub fn finish(&mut self) -> Result<Vec<u8>, ErrorStack> {
961 // First call with null buf to get the required size.
962 let mut siglen: usize = 0;
963 let rc = unsafe {
964 sys::EVP_DigestSignFinal(
965 self.ctx.as_ptr(),
966 std::ptr::null_mut(),
967 std::ptr::addr_of_mut!(siglen),
968 )
969 };
970 if rc != 1 {
971 return Err(ErrorStack::drain());
972 }
973 let mut sig = vec![0u8; siglen];
974 let rc = unsafe {
975 sys::EVP_DigestSignFinal(
976 self.ctx.as_ptr(),
977 sig.as_mut_ptr(),
978 std::ptr::addr_of_mut!(siglen),
979 )
980 };
981 if rc != 1 {
982 return Err(ErrorStack::drain());
983 }
984 sig.truncate(siglen);
985 Ok(sig)
986 }
987
988 /// One-shot sign over `data`.
989 ///
990 /// Required for algorithms that do not support streaming (Ed25519, Ed448).
991 /// For algorithms that do support streaming, prefer `update` + `finish`.
992 ///
993 /// # Errors
994 pub fn sign_oneshot(&mut self, data: &[u8]) -> Result<Vec<u8>, ErrorStack> {
995 // First call: query the required signature length.
996 let mut siglen: usize = 0;
997 let rc = unsafe {
998 sys::EVP_DigestSign(
999 self.ctx.as_ptr(),
1000 std::ptr::null_mut(),
1001 std::ptr::addr_of_mut!(siglen),
1002 data.as_ptr(),
1003 data.len(),
1004 )
1005 };
1006 if rc != 1 {
1007 return Err(ErrorStack::drain());
1008 }
1009 let mut sig = vec![0u8; siglen];
1010 let rc = unsafe {
1011 sys::EVP_DigestSign(
1012 self.ctx.as_ptr(),
1013 sig.as_mut_ptr(),
1014 std::ptr::addr_of_mut!(siglen),
1015 data.as_ptr(),
1016 data.len(),
1017 )
1018 };
1019 if rc != 1 {
1020 return Err(ErrorStack::drain());
1021 }
1022 sig.truncate(siglen);
1023 Ok(sig)
1024 }
1025
1026 /// One-shot sign over `data` into a caller-provided buffer `sig`.
1027 ///
1028 /// The buffer must be at least as large as the maximum signature size for
1029 /// the key (use `EVP_PKEY_get_size` or the algorithm's known fixed length).
1030 /// For algorithms with a fixed signature size (e.g. ML-DSA, Ed25519), the
1031 /// caller can pre-allocate the exact size and avoid the null-output query.
1032 ///
1033 /// Returns the number of bytes written. The caller should truncate `sig`
1034 /// to the returned length if the actual size may differ from the buffer size.
1035 ///
1036 /// # Errors
1037 pub fn sign_into(&mut self, data: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack> {
1038 let mut siglen = sig.len();
1039 let rc = unsafe {
1040 sys::EVP_DigestSign(
1041 self.ctx.as_ptr(),
1042 sig.as_mut_ptr(),
1043 std::ptr::addr_of_mut!(siglen),
1044 data.as_ptr(),
1045 data.len(),
1046 )
1047 };
1048 if rc != 1 {
1049 return Err(ErrorStack::drain());
1050 }
1051 Ok(siglen)
1052 }
1053}
1054
1055// ── Verifier — streaming DigestVerify ─────────────────────────────────────────
1056
1057/// Streaming `DigestVerify` context.
1058pub struct Verifier {
1059 ctx: crate::digest::DigestCtx,
1060 _key: Pkey<Public>,
1061}
1062
1063impl Verifier {
1064 /// Create a verifier.
1065 ///
1066 /// # Errors
1067 pub fn new(key: &Pkey<Public>, init: &SignInit<'_>) -> Result<Self, ErrorStack> {
1068 let ctx = alloc_digest_ctx()?;
1069 // Resolve digest name for EVP_DigestVerifyInit_ex (NULL for Ed25519/one-shot).
1070 let md_name_ptr = if let Some(d) = init.digest {
1071 let p = unsafe { sys::OBJ_nid2sn(d.nid()) };
1072 if p.is_null() {
1073 return Err(ErrorStack::drain());
1074 }
1075 p
1076 } else {
1077 std::ptr::null()
1078 };
1079 let params_ptr = init
1080 .params
1081 .map_or(crate::params::null_params(), crate::params::Params::as_ptr);
1082 let rc = unsafe {
1083 sys::EVP_DigestVerifyInit_ex(
1084 ctx.as_ptr(),
1085 std::ptr::null_mut(),
1086 md_name_ptr,
1087 std::ptr::null_mut(),
1088 std::ptr::null(),
1089 key.ptr,
1090 params_ptr,
1091 )
1092 };
1093 if rc != 1 {
1094 return Err(ErrorStack::drain());
1095 }
1096 Ok(Verifier {
1097 ctx,
1098 _key: key.clone(),
1099 })
1100 }
1101
1102 /// Feed data into the hash.
1103 ///
1104 /// # Errors
1105 pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
1106 crate::ossl_call!(sys::EVP_DigestVerifyUpdate(
1107 self.ctx.as_ptr(),
1108 data.as_ptr().cast(),
1109 data.len()
1110 ))
1111 }
1112
1113 /// Verify `signature` against all data fed via `update`.
1114 ///
1115 /// Returns `Ok(true)` if valid, `Ok(false)` if the signature is incorrect,
1116 /// or `Err` on a fatal OpenSSL error.
1117 ///
1118 /// Not supported by pure one-shot algorithms such as Ed25519 — use
1119 /// [`verify_oneshot`](Self::verify_oneshot) for those.
1120 ///
1121 /// # Errors
1122 pub fn verify(&mut self, signature: &[u8]) -> Result<bool, ErrorStack> {
1123 let rc = unsafe {
1124 sys::EVP_DigestVerifyFinal(self.ctx.as_ptr(), signature.as_ptr(), signature.len())
1125 };
1126 match rc {
1127 1 => Ok(true),
1128 0 => Ok(false),
1129 _ => Err(ErrorStack::drain()),
1130 }
1131 }
1132
1133 /// One-shot verify `signature` over `data`.
1134 ///
1135 /// Required for algorithms that do not support streaming (Ed25519, Ed448).
1136 ///
1137 /// # Errors
1138 pub fn verify_oneshot(&mut self, data: &[u8], signature: &[u8]) -> Result<bool, ErrorStack> {
1139 let rc = unsafe {
1140 sys::EVP_DigestVerify(
1141 self.ctx.as_ptr(),
1142 signature.as_ptr(),
1143 signature.len(),
1144 data.as_ptr(),
1145 data.len(),
1146 )
1147 };
1148 match rc {
1149 1 => Ok(true),
1150 0 => Ok(false),
1151 _ => Err(ErrorStack::drain()),
1152 }
1153 }
1154}
1155
1156// ── Internal helper: allocate an EVP_MD_CTX for DigestSign/Verify ─────────────
1157
1158/// Allocate a fresh, algorithm-unbound `EVP_MD_CTX` for use with
1159/// `EVP_DigestSign*Init_ex` / `EVP_DigestVerify*Init_ex`.
1160fn alloc_digest_ctx() -> Result<crate::digest::DigestCtx, ErrorStack> {
1161 let ctx_ptr = unsafe { sys::EVP_MD_CTX_new() };
1162 if ctx_ptr.is_null() {
1163 return Err(ErrorStack::drain());
1164 }
1165 // SAFETY: DigestCtx takes ownership; ptr is valid and non-null.
1166 Ok(unsafe { crate::digest::DigestCtx::from_ptr(ctx_ptr) })
1167}
1168
1169// ── DeriveCtx — ECDH / DH key agreement ──────────────────────────────────────
1170
1171/// Asymmetric key-agreement context (`EVP_PKEY_CTX` in derive mode).
1172pub struct DeriveCtx {
1173 ptr: *mut sys::EVP_PKEY_CTX,
1174}
1175
1176impl DeriveCtx {
1177 /// Create a derive context from a private key.
1178 ///
1179 /// # Errors
1180 pub fn new(key: &Pkey<Private>) -> Result<Self, ErrorStack> {
1181 let ptr = unsafe {
1182 sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
1183 };
1184 if ptr.is_null() {
1185 return Err(ErrorStack::drain());
1186 }
1187 crate::ossl_call!(sys::EVP_PKEY_derive_init(ptr)).map_err(|e| {
1188 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1189 e
1190 })?;
1191 Ok(DeriveCtx { ptr })
1192 }
1193
1194 /// Set the peer's public key.
1195 ///
1196 /// # Errors
1197 pub fn set_peer(&mut self, peer: &Pkey<Public>) -> Result<(), ErrorStack> {
1198 crate::ossl_call!(sys::EVP_PKEY_derive_set_peer(self.ptr, peer.ptr))
1199 }
1200
1201 /// Derive the shared secret into `out`.
1202 ///
1203 /// Returns the number of bytes written.
1204 ///
1205 /// # Errors
1206 pub fn derive(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack> {
1207 let mut len = out.len();
1208 crate::ossl_call!(sys::EVP_PKEY_derive(
1209 self.ptr,
1210 out.as_mut_ptr(),
1211 std::ptr::addr_of_mut!(len)
1212 ))?;
1213 Ok(len)
1214 }
1215
1216 /// Query the required output length (call with empty slice).
1217 ///
1218 /// # Errors
1219 pub fn derive_len(&mut self) -> Result<usize, ErrorStack> {
1220 let mut len: usize = 0;
1221 crate::ossl_call!(sys::EVP_PKEY_derive(
1222 self.ptr,
1223 std::ptr::null_mut(),
1224 std::ptr::addr_of_mut!(len)
1225 ))?;
1226 Ok(len)
1227 }
1228}
1229
1230impl Drop for DeriveCtx {
1231 fn drop(&mut self) {
1232 unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
1233 }
1234}
1235
1236// ── PkeyEncryptCtx / PkeyDecryptCtx ──────────────────────────────────────────
1237
1238/// RSA asymmetric encryption context.
1239pub struct PkeyEncryptCtx {
1240 ptr: *mut sys::EVP_PKEY_CTX,
1241}
1242
1243impl PkeyEncryptCtx {
1244 /// Create an encryption context from a public key.
1245 ///
1246 /// `params` is applied immediately after init if `Some`. Typical use:
1247 /// ```ignore
1248 /// let oaep = ParamBuilder::new()?.push_utf8_string(c"pad-mode", c"oaep")?.build()?;
1249 /// let ctx = PkeyEncryptCtx::new(&pub_key, Some(&oaep))?;
1250 /// ```
1251 ///
1252 /// # Errors
1253 pub fn new(
1254 key: &Pkey<Public>,
1255 params: Option<&crate::params::Params<'_>>,
1256 ) -> Result<Self, ErrorStack> {
1257 let ptr = unsafe {
1258 sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
1259 };
1260 if ptr.is_null() {
1261 return Err(ErrorStack::drain());
1262 }
1263 crate::ossl_call!(sys::EVP_PKEY_encrypt_init(ptr)).map_err(|e| {
1264 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1265 e
1266 })?;
1267 let ctx = PkeyEncryptCtx { ptr };
1268 if let Some(p) = params {
1269 crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(ctx.ptr, p.as_ptr())).map_err(|e| {
1270 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1271 e
1272 })?;
1273 }
1274 Ok(ctx)
1275 }
1276
1277 /// Configure parameters (e.g. RSA padding mode).
1278 ///
1279 /// # Errors
1280 pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
1281 crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ptr, params.as_ptr()))
1282 }
1283
1284 /// Encrypt `plaintext` into `ciphertext`.
1285 ///
1286 /// Returns the number of bytes written.
1287 ///
1288 /// # Errors
1289 pub fn encrypt(
1290 &mut self,
1291 plaintext: &[u8],
1292 ciphertext: &mut [u8],
1293 ) -> Result<usize, ErrorStack> {
1294 let mut outlen = ciphertext.len();
1295 crate::ossl_call!(sys::EVP_PKEY_encrypt(
1296 self.ptr,
1297 ciphertext.as_mut_ptr(),
1298 std::ptr::addr_of_mut!(outlen),
1299 plaintext.as_ptr(),
1300 plaintext.len()
1301 ))?;
1302 Ok(outlen)
1303 }
1304
1305 /// Query the ciphertext length for a given plaintext length.
1306 ///
1307 /// # Errors
1308 pub fn encrypt_len(&mut self, plaintext_len: usize) -> Result<usize, ErrorStack> {
1309 let mut outlen: usize = 0;
1310 crate::ossl_call!(sys::EVP_PKEY_encrypt(
1311 self.ptr,
1312 std::ptr::null_mut(),
1313 std::ptr::addr_of_mut!(outlen),
1314 std::ptr::null(),
1315 plaintext_len
1316 ))?;
1317 Ok(outlen)
1318 }
1319}
1320
1321impl Drop for PkeyEncryptCtx {
1322 fn drop(&mut self) {
1323 unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
1324 }
1325}
1326
1327/// RSA asymmetric decryption context.
1328pub struct PkeyDecryptCtx {
1329 ptr: *mut sys::EVP_PKEY_CTX,
1330}
1331
1332impl PkeyDecryptCtx {
1333 /// Create a decryption context from a private key.
1334 ///
1335 /// `params` is applied immediately after init if `Some`.
1336 ///
1337 /// # Errors
1338 pub fn new(
1339 key: &Pkey<Private>,
1340 params: Option<&crate::params::Params<'_>>,
1341 ) -> Result<Self, ErrorStack> {
1342 let ptr = unsafe {
1343 sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
1344 };
1345 if ptr.is_null() {
1346 return Err(ErrorStack::drain());
1347 }
1348 crate::ossl_call!(sys::EVP_PKEY_decrypt_init(ptr)).map_err(|e| {
1349 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1350 e
1351 })?;
1352 let ctx = PkeyDecryptCtx { ptr };
1353 if let Some(p) = params {
1354 crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(ctx.ptr, p.as_ptr())).map_err(|e| {
1355 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1356 e
1357 })?;
1358 }
1359 Ok(ctx)
1360 }
1361
1362 /// Configure parameters.
1363 ///
1364 /// # Errors
1365 pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
1366 crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ptr, params.as_ptr()))
1367 }
1368
1369 /// Decrypt `ciphertext` into `plaintext`.
1370 ///
1371 /// Returns the number of bytes written.
1372 ///
1373 /// # Errors
1374 pub fn decrypt(
1375 &mut self,
1376 ciphertext: &[u8],
1377 plaintext: &mut [u8],
1378 ) -> Result<usize, ErrorStack> {
1379 let mut outlen = plaintext.len();
1380 crate::ossl_call!(sys::EVP_PKEY_decrypt(
1381 self.ptr,
1382 plaintext.as_mut_ptr(),
1383 std::ptr::addr_of_mut!(outlen),
1384 ciphertext.as_ptr(),
1385 ciphertext.len()
1386 ))?;
1387 Ok(outlen)
1388 }
1389}
1390
1391impl Drop for PkeyDecryptCtx {
1392 fn drop(&mut self) {
1393 unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
1394 }
1395}
1396
1397// ── EncapCtx / DecapCtx — KEM (OpenSSL 3.2+) ────────────────────────────────
1398
1399/// KEM encapsulation output.
1400#[cfg(ossl320)]
1401pub struct EncapResult {
1402 /// Wrapped key (encoded shared secret).
1403 pub wrapped_key: Vec<u8>,
1404 /// Shared secret (plaintext).
1405 pub shared_secret: Vec<u8>,
1406}
1407
1408/// KEM encapsulation context (recipient's public key).
1409#[cfg(ossl320)]
1410pub struct EncapCtx {
1411 ptr: *mut sys::EVP_PKEY_CTX,
1412}
1413
1414#[cfg(ossl320)]
1415impl EncapCtx {
1416 /// Create a KEM encapsulation context from the recipient's public key.
1417 ///
1418 /// # Errors
1419 pub fn new(key: &Pkey<Public>) -> Result<Self, ErrorStack> {
1420 let ptr = unsafe {
1421 sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
1422 };
1423 if ptr.is_null() {
1424 return Err(ErrorStack::drain());
1425 }
1426 crate::ossl_call!(sys::EVP_PKEY_encapsulate_init(ptr, std::ptr::null())).map_err(|e| {
1427 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1428 e
1429 })?;
1430 Ok(EncapCtx { ptr })
1431 }
1432
1433 /// Perform encapsulation, returning the wrapped key and shared secret.
1434 ///
1435 /// # Errors
1436 pub fn encapsulate(&mut self) -> Result<EncapResult, ErrorStack> {
1437 let mut wkeylen: usize = 0;
1438 let mut sslen: usize = 0;
1439 // Query lengths first.
1440 let rc = unsafe {
1441 sys::EVP_PKEY_encapsulate(
1442 self.ptr,
1443 std::ptr::null_mut(),
1444 std::ptr::addr_of_mut!(wkeylen),
1445 std::ptr::null_mut(),
1446 std::ptr::addr_of_mut!(sslen),
1447 )
1448 };
1449 if rc != 1 {
1450 return Err(ErrorStack::drain());
1451 }
1452 let mut wrapped_key = vec![0u8; wkeylen];
1453 let mut shared_secret = vec![0u8; sslen];
1454 crate::ossl_call!(sys::EVP_PKEY_encapsulate(
1455 self.ptr,
1456 wrapped_key.as_mut_ptr(),
1457 std::ptr::addr_of_mut!(wkeylen),
1458 shared_secret.as_mut_ptr(),
1459 std::ptr::addr_of_mut!(sslen)
1460 ))?;
1461 wrapped_key.truncate(wkeylen);
1462 shared_secret.truncate(sslen);
1463 Ok(EncapResult {
1464 wrapped_key,
1465 shared_secret,
1466 })
1467 }
1468}
1469
1470#[cfg(ossl320)]
1471impl Drop for EncapCtx {
1472 fn drop(&mut self) {
1473 unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
1474 }
1475}
1476
1477/// KEM decapsulation context (recipient's private key).
1478#[cfg(ossl320)]
1479pub struct DecapCtx {
1480 ptr: *mut sys::EVP_PKEY_CTX,
1481}
1482
1483#[cfg(ossl320)]
1484impl DecapCtx {
1485 /// Create a KEM decapsulation context from the recipient's private key.
1486 ///
1487 /// # Errors
1488 pub fn new(key: &Pkey<Private>) -> Result<Self, ErrorStack> {
1489 let ptr = unsafe {
1490 sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
1491 };
1492 if ptr.is_null() {
1493 return Err(ErrorStack::drain());
1494 }
1495 crate::ossl_call!(sys::EVP_PKEY_decapsulate_init(ptr, std::ptr::null())).map_err(|e| {
1496 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1497 e
1498 })?;
1499 Ok(DecapCtx { ptr })
1500 }
1501
1502 /// Recover the shared secret from a wrapped key.
1503 ///
1504 /// # Errors
1505 pub fn decapsulate(&mut self, wrapped_key: &[u8]) -> Result<Vec<u8>, ErrorStack> {
1506 let mut sslen: usize = 0;
1507 // Query output length.
1508 let rc = unsafe {
1509 sys::EVP_PKEY_decapsulate(
1510 self.ptr,
1511 std::ptr::null_mut(),
1512 std::ptr::addr_of_mut!(sslen),
1513 wrapped_key.as_ptr(),
1514 wrapped_key.len(),
1515 )
1516 };
1517 if rc != 1 {
1518 return Err(ErrorStack::drain());
1519 }
1520 let mut ss = vec![0u8; sslen];
1521 crate::ossl_call!(sys::EVP_PKEY_decapsulate(
1522 self.ptr,
1523 ss.as_mut_ptr(),
1524 std::ptr::addr_of_mut!(sslen),
1525 wrapped_key.as_ptr(),
1526 wrapped_key.len()
1527 ))?;
1528 ss.truncate(sslen);
1529 Ok(ss)
1530 }
1531}
1532
1533#[cfg(ossl320)]
1534impl Drop for DecapCtx {
1535 fn drop(&mut self) {
1536 unsafe { sys::EVP_PKEY_CTX_free(self.ptr) };
1537 }
1538}
1539
1540// ── RawSigner — pre-hashed / no-digest signing ───────────────────────────────
1541
1542/// Raw (no-digest) signing context wrapping `EVP_PKEY_CTX` after `EVP_PKEY_sign_init`.
1543///
1544/// Use this for algorithms where the caller has already hashed the data, such
1545/// as raw ECDSA (data is the hash) or raw RSA with explicit padding. For
1546/// algorithms that hash internally, use [`Signer`] or `MessageSigner`.
1547///
1548/// The context is reusable: after a successful [`sign`](Self::sign) call the
1549/// init state is preserved, so the same padding parameters apply to further
1550/// sign calls with the same key.
1551pub struct RawSigner {
1552 ctx: *mut sys::EVP_PKEY_CTX,
1553}
1554
1555// SAFETY: EVP_PKEY_CTX is not thread-safe on a shared pointer, but
1556// RawSigner owns its ctx exclusively and &mut self enforces single-caller.
1557unsafe impl Send for RawSigner {}
1558
1559impl RawSigner {
1560 /// Create and initialise a sign context (`EVP_PKEY_CTX_new_from_pkey` +
1561 /// `EVP_PKEY_sign_init`).
1562 ///
1563 /// Pass `libctx = Some(ctx)` to restrict provider lookup to that library
1564 /// context; `None` uses the key's own library context.
1565 ///
1566 /// # Errors
1567 pub fn new(
1568 key: &Pkey<Private>,
1569 libctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
1570 ) -> Result<Self, ErrorStack> {
1571 let lctx = libctx.map_or(std::ptr::null_mut(), |c| c.as_ptr());
1572 let ptr = unsafe { sys::EVP_PKEY_CTX_new_from_pkey(lctx, key.ptr, std::ptr::null()) };
1573 if ptr.is_null() {
1574 return Err(ErrorStack::drain());
1575 }
1576 crate::ossl_call!(sys::EVP_PKEY_sign_init(ptr)).map_err(|e| {
1577 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1578 e
1579 })?;
1580 Ok(RawSigner { ctx: ptr })
1581 }
1582
1583 /// Apply parameters after init (e.g. RSA padding mode, salt length).
1584 ///
1585 /// # Errors
1586 pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
1587 // SAFETY:
1588 // - self.ctx is non-null (constructor invariant)
1589 // - params.as_ptr() is valid for the duration of this call
1590 // - &mut self ensures exclusive access; no concurrent reads or writes
1591 crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ctx, params.as_ptr()))
1592 }
1593
1594 /// Retrieve parameter values from this sign context.
1595 ///
1596 /// Build a [`Params`][crate::params::Params] with placeholder values for the
1597 /// keys you want, call this method, then read back with `params.get_*`.
1598 ///
1599 /// Useful for reading operation-context parameters such as the current RSA
1600 /// padding mode or signature algorithm identifier after initialisation.
1601 ///
1602 /// # Errors
1603 ///
1604 /// Returns `Err` if `EVP_PKEY_CTX_get_params` fails.
1605 pub fn get_params(&self, params: &mut crate::params::Params<'_>) -> Result<(), ErrorStack> {
1606 // SAFETY:
1607 // - self.ctx is non-null (constructor invariant)
1608 // - params.as_mut_ptr() is valid for the duration of this call
1609 // - &self ensures no concurrent mutable access to self.ctx
1610 crate::ossl_call!(sys::EVP_PKEY_CTX_get_params(self.ctx, params.as_mut_ptr()))
1611 }
1612
1613 /// Return the security strength of the key operation in bits.
1614 ///
1615 /// Reads `OSSL_PKEY_PARAM_SECURITY_BITS` (`"security-bits"`) from the sign
1616 /// context. Available after `EVP_PKEY_sign_init` on contexts backed by a key.
1617 ///
1618 /// # Errors
1619 ///
1620 /// Returns `Err` if the context does not support this parameter.
1621 pub fn security_bits(&self) -> Result<u32, ErrorStack> {
1622 let mut params = crate::params::ParamBuilder::new()?
1623 .push_uint(c"security-bits", 0)?
1624 .build()?;
1625 // SAFETY: same as get_params — self.ctx is non-null, params pointer is valid.
1626 crate::ossl_call!(sys::EVP_PKEY_CTX_get_params(self.ctx, params.as_mut_ptr()))?;
1627 params
1628 .get_uint(c"security-bits")
1629 .map_err(|_| crate::error::ErrorStack::drain())
1630 }
1631
1632 /// Query the signature output size for the given input length.
1633 ///
1634 /// Calls `EVP_PKEY_sign` with a null output pointer — does not consume
1635 /// the signing state.
1636 ///
1637 /// # Errors
1638 pub fn sign_len(&mut self, tbs_len: usize) -> Result<usize, ErrorStack> {
1639 let mut siglen: usize = 0;
1640 crate::ossl_call!(sys::EVP_PKEY_sign(
1641 self.ctx,
1642 std::ptr::null_mut(),
1643 std::ptr::addr_of_mut!(siglen),
1644 std::ptr::null(),
1645 tbs_len,
1646 ))?;
1647 Ok(siglen)
1648 }
1649
1650 /// Sign pre-hashed data into `sig`. Returns the number of bytes written.
1651 ///
1652 /// `sig.len()` must be >= `sign_len(tbs.len())`.
1653 ///
1654 /// # Errors
1655 pub fn sign(&mut self, tbs: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack> {
1656 let mut siglen = sig.len();
1657 crate::ossl_call!(sys::EVP_PKEY_sign(
1658 self.ctx,
1659 sig.as_mut_ptr(),
1660 std::ptr::addr_of_mut!(siglen),
1661 tbs.as_ptr(),
1662 tbs.len(),
1663 ))?;
1664 Ok(siglen)
1665 }
1666
1667 /// Sign pre-hashed data, allocating the output buffer.
1668 ///
1669 /// Convenience wrapper around [`sign_len`](Self::sign_len) + [`sign`](Self::sign).
1670 ///
1671 /// # Errors
1672 pub fn sign_alloc(&mut self, tbs: &[u8]) -> Result<Vec<u8>, ErrorStack> {
1673 let siglen = self.sign_len(tbs.len())?;
1674 let mut sig = vec![0u8; siglen];
1675 let written = self.sign(tbs, &mut sig)?;
1676 sig.truncate(written);
1677 Ok(sig)
1678 }
1679}
1680
1681impl Drop for RawSigner {
1682 fn drop(&mut self) {
1683 unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
1684 }
1685}
1686
1687// ── RawVerifier — pre-hashed / no-digest verification ────────────────────────
1688
1689/// Raw (no-digest) verification context wrapping `EVP_PKEY_CTX` after
1690/// `EVP_PKEY_verify_init`.
1691///
1692/// Mirror of [`RawSigner`] for the verification side.
1693pub struct RawVerifier {
1694 ctx: *mut sys::EVP_PKEY_CTX,
1695}
1696
1697unsafe impl Send for RawVerifier {}
1698
1699impl RawVerifier {
1700 /// Create and initialise a verify context.
1701 ///
1702 /// # Errors
1703 pub fn new<T: HasPublic>(
1704 key: &Pkey<T>,
1705 libctx: Option<&Arc<crate::lib_ctx::LibCtx>>,
1706 ) -> Result<Self, ErrorStack> {
1707 let lctx = libctx.map_or(std::ptr::null_mut(), |c| c.as_ptr());
1708 let ptr = unsafe { sys::EVP_PKEY_CTX_new_from_pkey(lctx, key.ptr, std::ptr::null()) };
1709 if ptr.is_null() {
1710 return Err(ErrorStack::drain());
1711 }
1712 crate::ossl_call!(sys::EVP_PKEY_verify_init(ptr)).map_err(|e| {
1713 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1714 e
1715 })?;
1716 Ok(RawVerifier { ctx: ptr })
1717 }
1718
1719 /// Apply parameters after init (e.g. RSA padding mode).
1720 ///
1721 /// # Errors
1722 pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
1723 crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ctx, params.as_ptr()))
1724 }
1725
1726 /// Verify `sig` against pre-hashed `tbs`. Returns `Ok(())` if valid.
1727 ///
1728 /// # Errors
1729 ///
1730 /// Returns `Err` if the signature is invalid or on any OpenSSL error.
1731 pub fn verify(&mut self, tbs: &[u8], sig: &[u8]) -> Result<(), ErrorStack> {
1732 crate::ossl_call!(sys::EVP_PKEY_verify(
1733 self.ctx,
1734 sig.as_ptr(),
1735 sig.len(),
1736 tbs.as_ptr(),
1737 tbs.len(),
1738 ))
1739 }
1740}
1741
1742impl Drop for RawVerifier {
1743 fn drop(&mut self) {
1744 unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
1745 }
1746}
1747
1748// ── SigAlg — EVP_SIGNATURE algorithm descriptor (OpenSSL 3.2+) ──────────────
1749
1750/// Algorithm descriptor for `EVP_SIGNATURE` (OpenSSL 3.2+).
1751///
1752/// Mirrors `DigestAlg` / `CipherAlg` / `MacAlg` in naming and lifecycle.
1753/// Used with [`MessageSigner`] and [`MessageVerifier`] for algorithms such
1754/// as ML-DSA, SLH-DSA, and Ed25519/Ed448 with context strings.
1755#[cfg(ossl320)]
1756pub struct SigAlg {
1757 ptr: *mut sys::EVP_SIGNATURE,
1758}
1759
1760// SAFETY: EVP_SIGNATURE is reference-counted.
1761#[cfg(ossl320)]
1762unsafe impl Send for SigAlg {}
1763#[cfg(ossl320)]
1764unsafe impl Sync for SigAlg {}
1765
1766#[cfg(ossl320)]
1767impl SigAlg {
1768 /// Fetch an `EVP_SIGNATURE` by name from the default library context.
1769 ///
1770 /// Example names: `c"ML-DSA-44"`, `c"ED25519"`, `c"SLH-DSA-SHA2-128s"`.
1771 ///
1772 /// # Errors
1773 pub fn fetch(
1774 name: &std::ffi::CStr,
1775 props: Option<&std::ffi::CStr>,
1776 ) -> Result<Self, ErrorStack> {
1777 let props_ptr = props.map_or(std::ptr::null(), std::ffi::CStr::as_ptr);
1778 let ptr =
1779 unsafe { sys::EVP_SIGNATURE_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr) };
1780 if ptr.is_null() {
1781 return Err(ErrorStack::drain());
1782 }
1783 Ok(SigAlg { ptr })
1784 }
1785
1786 /// Fetch an `EVP_SIGNATURE` by name within a specific library context.
1787 ///
1788 /// # Errors
1789 pub fn fetch_in(
1790 ctx: &Arc<crate::lib_ctx::LibCtx>,
1791 name: &std::ffi::CStr,
1792 props: Option<&std::ffi::CStr>,
1793 ) -> Result<Self, ErrorStack> {
1794 let props_ptr = props.map_or(std::ptr::null(), std::ffi::CStr::as_ptr);
1795 let ptr = unsafe { sys::EVP_SIGNATURE_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr) };
1796 if ptr.is_null() {
1797 return Err(ErrorStack::drain());
1798 }
1799 Ok(SigAlg { ptr })
1800 }
1801}
1802
1803#[cfg(ossl320)]
1804impl Clone for SigAlg {
1805 fn clone(&self) -> Self {
1806 unsafe { sys::EVP_SIGNATURE_up_ref(self.ptr) };
1807 SigAlg { ptr: self.ptr }
1808 }
1809}
1810
1811#[cfg(ossl320)]
1812impl Drop for SigAlg {
1813 fn drop(&mut self) {
1814 unsafe { sys::EVP_SIGNATURE_free(self.ptr) };
1815 }
1816}
1817
1818// ── MessageSigner — EVP_PKEY_sign_message_* streaming sign (OpenSSL 3.2+) ────
1819
1820/// Stateful signing context using `EVP_PKEY_sign_message_*` (OpenSSL 3.2+).
1821///
1822/// Used for algorithms that do not use a separate internal digest (`ML-DSA`,
1823/// `SLH-DSA`, `Ed25519` with context strings). Unlike [`Signer`], the
1824/// algorithm is specified as a [`SigAlg`] rather than a digest name.
1825///
1826/// Call [`update`](Self::update) zero or more times (if the algorithm supports
1827/// streaming — check with [`supports_streaming`](Self::supports_streaming)),
1828/// then [`finish`](Self::finish) to produce the signature. For algorithms
1829/// that only support one-shot operation, use [`sign_oneshot`](Self::sign_oneshot).
1830#[cfg(ossl320)]
1831pub struct MessageSigner {
1832 ctx: *mut sys::EVP_PKEY_CTX,
1833}
1834
1835#[cfg(ossl320)]
1836unsafe impl Send for MessageSigner {}
1837
1838#[cfg(ossl320)]
1839impl MessageSigner {
1840 /// Create and initialise a message-sign context.
1841 ///
1842 /// `alg` is consumed by the init call; pass a clone if you need to reuse it.
1843 /// `params` sets algorithm-specific options (e.g. context string for Ed25519).
1844 ///
1845 /// # Errors
1846 pub fn new(
1847 key: &Pkey<Private>,
1848 alg: &mut SigAlg,
1849 params: Option<&crate::params::Params<'_>>,
1850 ) -> Result<Self, ErrorStack> {
1851 let ptr = unsafe {
1852 sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
1853 };
1854 if ptr.is_null() {
1855 return Err(ErrorStack::drain());
1856 }
1857 let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
1858 crate::ossl_call!(sys::EVP_PKEY_sign_message_init(ptr, alg.ptr, params_ptr)).map_err(
1859 |e| {
1860 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
1861 e
1862 },
1863 )?;
1864 Ok(MessageSigner { ctx: ptr })
1865 }
1866
1867 /// Probe whether this algorithm backend supports incremental `update` calls.
1868 ///
1869 /// Calls `EVP_PKEY_sign_message_update` with an empty input, bracketed by
1870 /// `ERR_set_mark` / `ERR_pop_to_mark` so that a failure does not leave
1871 /// entries on the error queue. Returns `true` if streaming is supported.
1872 ///
1873 /// If this returns `false`, use [`sign_oneshot`](Self::sign_oneshot) instead.
1874 pub fn supports_streaming(&mut self) -> bool {
1875 // Probe: feed 0 bytes and see if the algorithm accepts it.
1876 // ERR mark/pop ensures the error queue is clean regardless of outcome.
1877 unsafe { sys::ERR_set_mark() };
1878 let probe: [u8; 0] = [];
1879 let rc = unsafe { sys::EVP_PKEY_sign_message_update(self.ctx, probe.as_ptr(), 0) };
1880 unsafe { sys::ERR_pop_to_mark() };
1881 rc == 1
1882 }
1883
1884 /// Feed `data` into the signing operation.
1885 ///
1886 /// Returns `Err` if the algorithm does not support streaming — use
1887 /// [`sign_oneshot`](Self::sign_oneshot) in that case.
1888 ///
1889 /// # Errors
1890 pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
1891 crate::ossl_call!(sys::EVP_PKEY_sign_message_update(
1892 self.ctx,
1893 data.as_ptr(),
1894 data.len(),
1895 ))
1896 }
1897
1898 /// Query the signature output length.
1899 ///
1900 /// Calls `EVP_PKEY_sign_message_final` with a null buffer — does not
1901 /// consume the signing state.
1902 ///
1903 /// # Errors
1904 pub fn sig_len(&mut self) -> Result<usize, ErrorStack> {
1905 let mut siglen: usize = 0;
1906 crate::ossl_call!(sys::EVP_PKEY_sign_message_final(
1907 self.ctx,
1908 std::ptr::null_mut(),
1909 std::ptr::addr_of_mut!(siglen),
1910 ))?;
1911 Ok(siglen)
1912 }
1913
1914 /// Finalise and produce the signature into `sig`.
1915 ///
1916 /// Consumes `self` because the context is finalised. Call
1917 /// [`sig_len`](Self::sig_len) first to size the buffer. Returns the
1918 /// number of bytes written.
1919 ///
1920 /// # Errors
1921 pub fn finish(self, sig: &mut [u8]) -> Result<usize, ErrorStack> {
1922 let mut siglen = sig.len();
1923 let rc = unsafe {
1924 sys::EVP_PKEY_sign_message_final(
1925 self.ctx,
1926 sig.as_mut_ptr(),
1927 std::ptr::addr_of_mut!(siglen),
1928 )
1929 };
1930 // self is dropped here → EVP_PKEY_CTX_free via Drop.
1931 if rc != 1 {
1932 return Err(ErrorStack::drain());
1933 }
1934 Ok(siglen)
1935 }
1936
1937 /// One-shot sign: feed `data` then finalise into `sig`.
1938 ///
1939 /// Consumes `self`. Use this for algorithms that do not support
1940 /// streaming (`supports_streaming` returns `false`).
1941 ///
1942 /// # Errors
1943 pub fn sign_oneshot(self, data: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack> {
1944 // Feed all data, then finalise. Both ops share the same ctx.
1945 let rc_upd =
1946 unsafe { sys::EVP_PKEY_sign_message_update(self.ctx, data.as_ptr(), data.len()) };
1947 if rc_upd != 1 {
1948 // self dropped here → ctx freed.
1949 return Err(ErrorStack::drain());
1950 }
1951 let mut siglen = sig.len();
1952 let rc_fin = unsafe {
1953 sys::EVP_PKEY_sign_message_final(
1954 self.ctx,
1955 sig.as_mut_ptr(),
1956 std::ptr::addr_of_mut!(siglen),
1957 )
1958 };
1959 // self dropped here → ctx freed.
1960 if rc_fin != 1 {
1961 return Err(ErrorStack::drain());
1962 }
1963 Ok(siglen)
1964 }
1965
1966 /// One-shot sign over `data` using `EVP_PKEY_sign`.
1967 ///
1968 /// The context must have been initialised with `EVP_PKEY_sign_message_init`
1969 /// (this type's constructor); `EVP_PKEY_sign` accepts both
1970 /// `EVP_PKEY_OP_SIGN` and `EVP_PKEY_OP_SIGNMSG` operation modes.
1971 ///
1972 /// When `sig` is `None` the call is a **cheap length query**: for ML-DSA
1973 /// and other algorithms with a fixed output size, no cryptographic
1974 /// computation is performed. When `sig` is `Some(buf)` the signature is
1975 /// written and the number of bytes actually written is returned.
1976 ///
1977 /// The context is *not* consumed so the same `MessageSigner` may be reused
1978 /// across a size-query + actual-sign pair without re-initialisation.
1979 ///
1980 /// Contrast with [`sign_oneshot`](Self::sign_oneshot): `sign_oneshot`
1981 /// consumes `self` and always writes a signature; `sign` borrows `self`,
1982 /// can query the required length cheaply (pass `sig = None`), and can be
1983 /// called multiple times on the same context.
1984 ///
1985 /// # Errors
1986 pub fn sign(&mut self, data: &[u8], sig: Option<&mut [u8]>) -> Result<usize, ErrorStack> {
1987 let (sig_ptr, mut siglen) = match sig {
1988 Some(buf) => (buf.as_mut_ptr(), buf.len()),
1989 None => (std::ptr::null_mut(), 0usize),
1990 };
1991 crate::ossl_call!(sys::EVP_PKEY_sign(
1992 self.ctx,
1993 sig_ptr,
1994 std::ptr::addr_of_mut!(siglen),
1995 data.as_ptr(),
1996 data.len(),
1997 ))?;
1998 Ok(siglen)
1999 }
2000}
2001
2002#[cfg(ossl320)]
2003impl Drop for MessageSigner {
2004 fn drop(&mut self) {
2005 unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
2006 }
2007}
2008
2009// ── MessageVerifier — EVP_PKEY_verify_message_* streaming verify (OpenSSL 3.2+)
2010
2011/// Stateful verification context using `EVP_PKEY_verify_message_*` (OpenSSL 3.2+).
2012///
2013/// Mirror of [`MessageSigner`] for the verification side.
2014///
2015/// For streaming mode: call [`set_signature`](Self::set_signature), then
2016/// [`update`](Self::update) zero or more times, then [`finish`](Self::finish).
2017/// For one-shot: call [`verify_oneshot`](Self::verify_oneshot).
2018#[cfg(ossl320)]
2019pub struct MessageVerifier {
2020 ctx: *mut sys::EVP_PKEY_CTX,
2021}
2022
2023#[cfg(ossl320)]
2024unsafe impl Send for MessageVerifier {}
2025
2026#[cfg(ossl320)]
2027impl MessageVerifier {
2028 /// Create and initialise a message-verify context.
2029 ///
2030 /// # Errors
2031 pub fn new<T: HasPublic>(
2032 key: &Pkey<T>,
2033 alg: &mut SigAlg,
2034 params: Option<&crate::params::Params<'_>>,
2035 ) -> Result<Self, ErrorStack> {
2036 let ptr = unsafe {
2037 sys::EVP_PKEY_CTX_new_from_pkey(std::ptr::null_mut(), key.ptr, std::ptr::null())
2038 };
2039 if ptr.is_null() {
2040 return Err(ErrorStack::drain());
2041 }
2042 let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
2043 crate::ossl_call!(sys::EVP_PKEY_verify_message_init(ptr, alg.ptr, params_ptr)).map_err(
2044 |e| {
2045 unsafe { sys::EVP_PKEY_CTX_free(ptr) };
2046 e
2047 },
2048 )?;
2049 Ok(MessageVerifier { ctx: ptr })
2050 }
2051
2052 /// Apply parameters after init.
2053 ///
2054 /// # Errors
2055 pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
2056 crate::ossl_call!(sys::EVP_PKEY_CTX_set_params(self.ctx, params.as_ptr()))
2057 }
2058
2059 /// Supply the signature to verify against (required before streaming `finish`).
2060 ///
2061 /// Calls `EVP_PKEY_CTX_set_signature`. Not needed for
2062 /// [`verify_oneshot`](Self::verify_oneshot) which sets it internally.
2063 ///
2064 /// # Errors
2065 pub fn set_signature(&mut self, sig: &[u8]) -> Result<(), ErrorStack> {
2066 crate::ossl_call!(sys::EVP_PKEY_CTX_set_signature(
2067 self.ctx,
2068 sig.as_ptr(),
2069 sig.len()
2070 ))
2071 }
2072
2073 /// Probe whether this algorithm supports incremental `update` calls.
2074 ///
2075 /// Uses the same `ERR_set_mark` / `ERR_pop_to_mark` probe as
2076 /// [`MessageSigner::supports_streaming`].
2077 pub fn supports_streaming(&mut self) -> bool {
2078 unsafe { sys::ERR_set_mark() };
2079 let probe: [u8; 0] = [];
2080 let rc = unsafe { sys::EVP_PKEY_verify_message_update(self.ctx, probe.as_ptr(), 0) };
2081 unsafe { sys::ERR_pop_to_mark() };
2082 rc == 1
2083 }
2084
2085 /// Feed `data` into the verification operation.
2086 ///
2087 /// # Errors
2088 pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
2089 crate::ossl_call!(sys::EVP_PKEY_verify_message_update(
2090 self.ctx,
2091 data.as_ptr(),
2092 data.len(),
2093 ))
2094 }
2095
2096 /// Finalise and verify.
2097 ///
2098 /// The signature must have been set via [`set_signature`](Self::set_signature).
2099 /// Consumes `self`. Returns `Ok(())` if the signature is valid.
2100 ///
2101 /// # Errors
2102 pub fn finish(self) -> Result<(), ErrorStack> {
2103 let rc = unsafe { sys::EVP_PKEY_verify_message_final(self.ctx) };
2104 // self dropped here → ctx freed.
2105 if rc != 1 {
2106 return Err(ErrorStack::drain());
2107 }
2108 Ok(())
2109 }
2110
2111 /// One-shot verify `sig` over `data`.
2112 ///
2113 /// Sets the signature, feeds all data, and finalises. Consumes `self`.
2114 ///
2115 /// # Errors
2116 pub fn verify_oneshot(self, data: &[u8], sig: &[u8]) -> Result<(), ErrorStack> {
2117 let rc_set = unsafe { sys::EVP_PKEY_CTX_set_signature(self.ctx, sig.as_ptr(), sig.len()) };
2118 if rc_set != 1 {
2119 return Err(ErrorStack::drain());
2120 }
2121 let rc_upd =
2122 unsafe { sys::EVP_PKEY_verify_message_update(self.ctx, data.as_ptr(), data.len()) };
2123 if rc_upd != 1 {
2124 return Err(ErrorStack::drain());
2125 }
2126 let rc_fin = unsafe { sys::EVP_PKEY_verify_message_final(self.ctx) };
2127 // self dropped here → ctx freed.
2128 if rc_fin != 1 {
2129 return Err(ErrorStack::drain());
2130 }
2131 Ok(())
2132 }
2133
2134 /// One-shot verify `sig` over `data` using `EVP_PKEY_verify`.
2135 ///
2136 /// The context must have been initialised with `EVP_PKEY_verify_message_init`
2137 /// (this type's constructor); `EVP_PKEY_verify` accepts both
2138 /// `EVP_PKEY_OP_VERIFY` and `EVP_PKEY_OP_VERIFYMSG` operation modes.
2139 ///
2140 /// Returns `Ok(true)` if the signature verifies, `Ok(false)` if it does
2141 /// not. Fatal protocol or library errors are returned as `Err`.
2142 ///
2143 /// The context is *not* consumed and may be reused for further verifications.
2144 ///
2145 /// Contrast with [`verify_oneshot`](Self::verify_oneshot): `verify_oneshot`
2146 /// consumes `self`; `verify` borrows `self` and may be called repeatedly
2147 /// without re-creating the context.
2148 ///
2149 /// # Errors
2150 pub fn verify(&mut self, data: &[u8], sig: &[u8]) -> Result<bool, ErrorStack> {
2151 let rc = unsafe {
2152 sys::EVP_PKEY_verify(self.ctx, sig.as_ptr(), sig.len(), data.as_ptr(), data.len())
2153 };
2154 match rc {
2155 1 => Ok(true),
2156 0 => Ok(false),
2157 _ => Err(ErrorStack::drain()),
2158 }
2159 }
2160}
2161
2162#[cfg(ossl320)]
2163impl Drop for MessageVerifier {
2164 fn drop(&mut self) {
2165 unsafe { sys::EVP_PKEY_CTX_free(self.ctx) };
2166 }
2167}
2168
2169// ── Tests ─────────────────────────────────────────────────────────────────────
2170
2171#[cfg(test)]
2172mod tests {
2173 use super::*;
2174
2175 /// Generate an Ed25519 key pair; sign and verify "hello world".
2176 ///
2177 /// Ed25519 is a one-shot algorithm — uses `sign_oneshot` / `verify_oneshot`.
2178 #[test]
2179 fn ed25519_sign_verify() {
2180 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2181 let priv_key = kgen.generate().unwrap();
2182 let pub_key = Pkey::<Public>::from(priv_key.clone());
2183
2184 let msg = b"hello world";
2185 let init = SignInit::default();
2186
2187 let mut signer = Signer::new(&priv_key, &init).unwrap();
2188 let sig = signer.sign_oneshot(msg).unwrap();
2189 assert!(!sig.is_empty());
2190
2191 let mut verifier = Verifier::new(&pub_key, &init).unwrap();
2192 assert!(verifier.verify_oneshot(msg, &sig).unwrap());
2193 }
2194
2195 /// Tampered message must fail verification.
2196 #[test]
2197 fn ed25519_verify_wrong_msg_fails() {
2198 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2199 let priv_key = kgen.generate().unwrap();
2200 let pub_key = Pkey::<Public>::from(priv_key.clone());
2201
2202 let mut signer = Signer::new(&priv_key, &SignInit::default()).unwrap();
2203 let sig = signer.sign_oneshot(b"correct").unwrap();
2204
2205 let mut verifier = Verifier::new(&pub_key, &SignInit::default()).unwrap();
2206 assert!(!verifier.verify_oneshot(b"tampered", &sig).unwrap());
2207 }
2208
2209 /// Generate an X25519 key pair; perform ECDH derive and confirm length.
2210 #[test]
2211 fn x25519_derive() {
2212 let mut kgen_a = KeygenCtx::new(c"X25519").unwrap();
2213 let priv_a = kgen_a.generate().unwrap();
2214
2215 let mut kgen_b = KeygenCtx::new(c"X25519").unwrap();
2216 let priv_b = kgen_b.generate().unwrap();
2217 let pub_b = Pkey::<Public>::from(priv_b);
2218
2219 let mut derive = DeriveCtx::new(&priv_a).unwrap();
2220 derive.set_peer(&pub_b).unwrap();
2221 let len = derive.derive_len().unwrap();
2222 assert_eq!(len, 32); // X25519 shared secret is always 32 bytes
2223
2224 let mut ss = vec![0u8; len];
2225 let n = derive.derive(&mut ss).unwrap();
2226 assert_eq!(n, 32);
2227 assert_ne!(ss, [0u8; 32]);
2228 }
2229
2230 /// Round-trip through PEM: generate Ed25519, export, re-import, verify key equality.
2231 #[test]
2232 fn pem_round_trip() {
2233 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2234 let priv_key = kgen.generate().unwrap();
2235
2236 let pem = priv_key.to_pem().unwrap();
2237 assert!(!pem.is_empty());
2238 assert!(pem.starts_with(b"-----BEGIN"));
2239
2240 let priv_key2 = Pkey::<Private>::from_pem(&pem).unwrap();
2241 assert_eq!(priv_key.bits(), priv_key2.bits());
2242 assert!(priv_key.is_a(c"ED25519"));
2243 }
2244
2245 /// Key metadata: Ed25519 should report 256 bits.
2246 #[test]
2247 fn ed25519_metadata() {
2248 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2249 let key = kgen.generate().unwrap();
2250 assert_eq!(key.bits(), 256);
2251 assert_eq!(key.security_bits(), 128);
2252 assert!(key.is_a(c"ED25519"));
2253 }
2254
2255 /// ML-DSA-44 sign and verify using `MessageSigner::sign` / `MessageVerifier::verify`.
2256 #[cfg(ossl320)]
2257 #[test]
2258 fn ml_dsa_44_sign_verify() {
2259 let mut kgen = KeygenCtx::new(c"ML-DSA-44").unwrap();
2260 let priv_key = kgen.generate().unwrap();
2261 let pub_key = Pkey::<Public>::from(priv_key.clone());
2262
2263 let msg = b"hello ML-DSA-44";
2264
2265 let mut alg = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
2266 let mut signer = MessageSigner::new(&priv_key, &mut alg, None).unwrap();
2267 // NULL size query must be cheap (FIPS 204 fixed 2420 bytes).
2268 let sig_len = signer.sign(msg, None).unwrap();
2269 assert_eq!(sig_len, 2420);
2270 let mut sig = vec![0u8; sig_len];
2271 let written = signer.sign(msg, Some(&mut sig)).unwrap();
2272 assert_eq!(written, 2420);
2273
2274 let mut alg2 = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
2275 let mut ver = MessageVerifier::new(&pub_key, &mut alg2, None).unwrap();
2276 assert!(ver.verify(msg, &sig).unwrap());
2277
2278 // Tampered signature must fail.
2279 let mut bad = sig.clone();
2280 bad[0] ^= 0xff;
2281 assert!(!ver.verify(msg, &bad).unwrap());
2282 }
2283
2284 /// ML-DSA-44 sign with a context string (FIPS 204 domain separation).
2285 #[cfg(ossl320)]
2286 #[test]
2287 fn ml_dsa_44_sign_with_context() {
2288 let mut kgen = KeygenCtx::new(c"ML-DSA-44").unwrap();
2289 let priv_key = kgen.generate().unwrap();
2290 let pub_key = Pkey::<Public>::from(priv_key.clone());
2291
2292 let msg = b"signed with context";
2293 let ctx_bytes = b"my-app-domain";
2294
2295 let ctx_params = crate::params::ParamBuilder::new()
2296 .unwrap()
2297 .push_octet_slice(c"context-string", ctx_bytes)
2298 .unwrap()
2299 .build()
2300 .unwrap();
2301
2302 let mut alg = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
2303 let mut signer = MessageSigner::new(&priv_key, &mut alg, Some(&ctx_params)).unwrap();
2304 let sig_len = signer.sign(msg, None).unwrap();
2305 let mut sig = vec![0u8; sig_len];
2306 signer.sign(msg, Some(&mut sig)).unwrap();
2307
2308 // Same context: verify must succeed.
2309 let mut alg2 = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
2310 let mut ver = MessageVerifier::new(&pub_key, &mut alg2, Some(&ctx_params)).unwrap();
2311 assert!(ver.verify(msg, &sig).unwrap());
2312
2313 // Different context: verify must fail.
2314 let other_params = crate::params::ParamBuilder::new()
2315 .unwrap()
2316 .push_octet_slice(c"context-string", b"other-domain")
2317 .unwrap()
2318 .build()
2319 .unwrap();
2320 let mut alg3 = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
2321 let mut ver_bad = MessageVerifier::new(&pub_key, &mut alg3, Some(&other_params)).unwrap();
2322 assert!(!ver_bad.verify(msg, &sig).unwrap());
2323 }
2324
2325 /// `RawSigner` / `RawVerifier` round-trip with P-256 ECDSA over a pre-hashed digest.
2326 #[test]
2327 fn ecdsa_p256_raw_sign_verify() {
2328 // Generate P-256 key.
2329 let mut kgen = KeygenCtx::new(c"EC").unwrap();
2330 let params = crate::params::ParamBuilder::new()
2331 .unwrap()
2332 .push_utf8_string(c"group", c"P-256")
2333 .unwrap()
2334 .build()
2335 .unwrap();
2336 kgen.set_params(¶ms).unwrap();
2337 let priv_key = kgen.generate().unwrap();
2338 let pub_key = Pkey::<Public>::from(priv_key.clone());
2339
2340 // SHA-256 hash of the message (32 bytes — the "pre-hash").
2341 let tbs: [u8; 32] = *b"0123456789abcdef0123456789abcdef";
2342
2343 let mut signer = RawSigner::new(&priv_key, None).unwrap();
2344 let sig = signer.sign_alloc(&tbs).unwrap();
2345 assert!(!sig.is_empty());
2346
2347 let mut verifier = RawVerifier::new(&pub_key, None).unwrap();
2348 verifier.verify(&tbs, &sig).unwrap();
2349 }
2350
2351 /// `RawVerifier` rejects a tampered signature.
2352 #[test]
2353 fn ecdsa_p256_raw_verify_tampered_fails() {
2354 let mut kgen = KeygenCtx::new(c"EC").unwrap();
2355 let params = crate::params::ParamBuilder::new()
2356 .unwrap()
2357 .push_utf8_string(c"group", c"P-256")
2358 .unwrap()
2359 .build()
2360 .unwrap();
2361 kgen.set_params(¶ms).unwrap();
2362 let priv_key = kgen.generate().unwrap();
2363 let pub_key = Pkey::<Public>::from(priv_key.clone());
2364
2365 let tbs: [u8; 32] = *b"0123456789abcdef0123456789abcdef";
2366 let mut signer = RawSigner::new(&priv_key, None).unwrap();
2367 let mut sig = signer.sign_alloc(&tbs).unwrap();
2368 // Flip a byte in the signature.
2369 if let Some(b) = sig.last_mut() {
2370 *b ^= 0xff;
2371 }
2372
2373 let mut verifier = RawVerifier::new(&pub_key, None).unwrap();
2374 assert!(verifier.verify(&tbs, &sig).is_err());
2375 }
2376
2377 /// `SigAlg`: fetch, clone, drop — verifies `EVP_SIGNATURE` lifecycle.
2378 ///
2379 /// Does not attempt `sign_message_final`: OpenSSL 3.5's built-in ML-DSA
2380 /// provider implements only the one-shot `EVP_DigestSign` path, not
2381 /// `OSSL_FUNC_SIGNATURE_SIGN_MESSAGE_FINAL`. Actual ML-DSA signing is
2382 /// done via Signer (`DigestSign` with NULL digest).
2383 #[cfg(ossl320)]
2384 #[test]
2385 fn sig_alg_fetch_clone_drop() {
2386 let alg = SigAlg::fetch(c"ML-DSA-44", None).unwrap();
2387 let alg2 = alg.clone();
2388 drop(alg);
2389 drop(alg2); // both up_ref / free paths exercised
2390 }
2391
2392 /// `MessageSigner` construction and `supports_streaming` probe for Ed25519.
2393 ///
2394 /// Ed25519 supports `EVP_PKEY_sign_message_init`. The streaming probe
2395 /// uses `ERR_set_mark` / `ERR_pop_to_mark` so it cannot leave error state.
2396 #[cfg(ossl320)]
2397 #[test]
2398 fn message_signer_construction_and_streaming_probe() {
2399 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2400 let priv_key = kgen.generate().unwrap();
2401
2402 let mut alg = SigAlg::fetch(c"ED25519", None).unwrap();
2403 let mut signer = MessageSigner::new(&priv_key, &mut alg, None).unwrap();
2404
2405 // Probe must not crash or leave the error queue dirty.
2406 let _streaming = signer.supports_streaming();
2407 // Error queue must be empty after the probe.
2408 assert_eq!(crate::error::ErrorStack::drain().errors().count(), 0);
2409 }
2410
2411 /// `MessageVerifier` construction for Ed25519.
2412 #[cfg(ossl320)]
2413 #[test]
2414 fn message_verifier_construction() {
2415 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2416 let priv_key = kgen.generate().unwrap();
2417 let pub_key = Pkey::<Public>::from(priv_key.clone());
2418
2419 let mut alg = SigAlg::fetch(c"ED25519", None).unwrap();
2420 let _verifier = MessageVerifier::new(&pub_key, &mut alg, None).unwrap();
2421 }
2422
2423 /// Encrypted PEM round-trip: generate → `to_pem_encrypted` → `from_pem_passphrase`.
2424 #[test]
2425 fn encrypted_pem_round_trip() {
2426 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2427 let key = kgen.generate().unwrap();
2428
2429 let cipher = crate::cipher::CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
2430 let pem = key.to_pem_encrypted(&cipher, b"s3cret").unwrap();
2431 assert!(pem.starts_with(b"-----BEGIN ENCRYPTED PRIVATE KEY-----"));
2432
2433 let key2 = Pkey::<Private>::from_pem_passphrase(&pem, b"s3cret").unwrap();
2434 assert!(key.public_eq(&key2));
2435 }
2436
2437 /// Wrong passphrase must return an error, not silently load a garbage key.
2438 #[test]
2439 fn encrypted_pem_wrong_passphrase_fails() {
2440 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2441 let key = kgen.generate().unwrap();
2442
2443 let cipher = crate::cipher::CipherAlg::fetch(c"AES-256-CBC", None).unwrap();
2444 let pem = key.to_pem_encrypted(&cipher, b"correct").unwrap();
2445 assert!(Pkey::<Private>::from_pem_passphrase(&pem, b"wrong").is_err());
2446 }
2447
2448 /// PKCS#8 DER round-trip: generate → `to_pkcs8_der` → `from_der`.
2449 #[test]
2450 fn pkcs8_der_round_trip() {
2451 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2452 let key = kgen.generate().unwrap();
2453
2454 let der = key.to_pkcs8_der().unwrap();
2455 assert!(!der.is_empty());
2456
2457 let key2 = Pkey::<Private>::from_der(&der).unwrap();
2458 assert!(key.public_eq(&key2));
2459 }
2460
2461 #[test]
2462 fn pkey_max_output_size_ed25519() {
2463 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2464 let key = kgen.generate().unwrap();
2465 // Ed25519 produces a fixed 64-byte signature.
2466 assert_eq!(key.max_output_size(), 64);
2467 }
2468
2469 #[test]
2470 fn pkey_max_output_size_rsa() {
2471 let mut kgen = KeygenCtx::new(c"RSA").unwrap();
2472 let key = kgen.generate().unwrap();
2473 // RSA-2048: max output == key size in bytes (256).
2474 let size = key.max_output_size();
2475 assert!(size > 0, "RSA key must report non-zero max output size");
2476 assert_eq!(size, key.bits() as usize / 8);
2477 }
2478
2479 /// `KeygenCtx::set_params` — set RSA bit length via params, generate, verify size.
2480 ///
2481 /// OpenSSL default RSA keygen produces 2048-bit keys; here we explicitly set
2482 /// `bits = 3072` and verify the generated key carries that size.
2483 #[test]
2484 fn pkey_ctx_set_params_rsa() {
2485 let mut kgen = KeygenCtx::new(c"RSA").unwrap();
2486 let params = crate::params::ParamBuilder::new()
2487 .unwrap()
2488 .push_uint(c"bits", 3072)
2489 .unwrap()
2490 .build()
2491 .unwrap();
2492 kgen.set_params(¶ms).unwrap();
2493 let key = kgen.generate().unwrap();
2494 assert_eq!(key.bits(), 3072, "generated RSA key must be 3072 bits");
2495 }
2496
2497 /// `KeygenCtx::security_bits` — Ed25519 key object reports 128 security bits.
2498 ///
2499 /// `security_bits()` reads `security-bits` from the key context. For Ed25519,
2500 /// the well-known security strength is 128 bits. This test uses the
2501 /// `Pkey::security_bits` method (which wraps `EVP_PKEY_get_security_bits`)
2502 /// and the `KeygenCtx::security_bits` helper. The keygen context method
2503 /// internally calls `EVP_PKEY_CTX_get_params`; the result depends on what the
2504 /// underlying provider exposes on a keygen context.
2505 #[test]
2506 fn pkey_ctx_security_bits() {
2507 // Verify that the key object itself reports the correct security level.
2508 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2509 let key = kgen.generate().unwrap();
2510 assert_eq!(
2511 key.security_bits(),
2512 128,
2513 "Ed25519 key must report 128 security bits"
2514 );
2515
2516 // KeygenCtx::security_bits() is a best-effort getter; it may return Err if
2517 // the provider does not expose security-bits on a keygen context. We just
2518 // verify it does not panic.
2519 let _ = KeygenCtx::new(c"ED25519").unwrap().security_bits();
2520 }
2521
2522 /// `RawSigner::get_params` — read `pad-mode` from an RSA sign context.
2523 ///
2524 /// `EVP_PKEY_CTX_get_params` is most useful on operation (sign/verify)
2525 /// contexts where OpenSSL exposes per-operation configuration parameters.
2526 /// RSA sign contexts expose `pad-mode` as both gettable and settable.
2527 ///
2528 /// The placeholder string must be large enough to receive the returned value;
2529 /// we use a 32-char placeholder to accommodate all padding mode names.
2530 #[test]
2531 fn pkey_ctx_get_params_rsa_sign() {
2532 let mut kgen = KeygenCtx::new(c"RSA").unwrap();
2533 let key = kgen.generate().unwrap();
2534 let signer = RawSigner::new(&key, None).unwrap();
2535
2536 // Query the default RSA padding mode; OpenSSL defaults to "pkcs1".
2537 // Provide a placeholder string of length >= the longest padding mode name.
2538 let placeholder = c"00000000000000000000000000000000"; // 32 chars
2539 let mut params = crate::params::ParamBuilder::new()
2540 .unwrap()
2541 .push_utf8_string(c"pad-mode", placeholder)
2542 .unwrap()
2543 .build()
2544 .unwrap();
2545 signer.get_params(&mut params).unwrap();
2546 // The returned pad-mode must be a non-empty string.
2547 let mode = params.get_utf8_string(c"pad-mode").unwrap();
2548 assert!(
2549 !mode.to_bytes().is_empty(),
2550 "RSA pad-mode must be non-empty"
2551 );
2552 }
2553
2554 /// `Pkey<Public>::from_pem_in` loads a public key within an explicit `LibCtx`.
2555 #[test]
2556 fn pubkey_from_pem_in_roundtrip() {
2557 // Generate an EC key and export the public half to PEM.
2558 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2559 let priv_key = kgen.generate().unwrap();
2560 let pub_pem = Pkey::<Public>::from(priv_key).to_pem().unwrap();
2561
2562 // Re-import via from_pem_in using the default lib ctx.
2563 let lib_ctx = Arc::new(crate::lib_ctx::LibCtx::new().unwrap());
2564 let pub_key = Pkey::<Public>::from_pem_in(&lib_ctx, &pub_pem).unwrap();
2565
2566 // Sanity: key is usable for verification.
2567 assert!(!pub_key.to_pem().unwrap().is_empty());
2568 }
2569
2570 /// Ed25519 has no separate digest — `default_digest_name()` must return `Ok(None)`.
2571 #[test]
2572 fn pkey_default_digest_name_ed25519() {
2573 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2574 let key = kgen.generate().unwrap();
2575 let result = key.default_digest_name().unwrap();
2576 assert_eq!(
2577 result, None,
2578 "Ed25519 performs its own internal hashing; expected no external digest"
2579 );
2580 }
2581
2582 /// RSA with a default digest should return `Ok(Some(name))` with a non-empty name.
2583 #[test]
2584 fn pkey_default_digest_name_rsa() {
2585 let mut kgen = KeygenCtx::new(c"RSA").unwrap();
2586 let key = kgen.generate().unwrap();
2587 let result = key.default_digest_name().unwrap();
2588 assert!(
2589 result.is_some(),
2590 "RSA key should have a default digest name; got None"
2591 );
2592 let name = result.unwrap();
2593 assert!(
2594 !name.is_empty(),
2595 "RSA default digest name must not be empty"
2596 );
2597 // OpenSSL 3.x reports SHA256 (or SHA-256) as the RSA default digest.
2598 assert!(
2599 name.to_ascii_uppercase().contains("SHA"),
2600 "Expected a SHA-family digest name, got: {name}"
2601 );
2602 }
2603
2604 /// `Pkey::<Private>::from_der_in` — round-trip through `to_der` (PKCS#8) and
2605 /// back via the LibCtx-aware `from_der_in`, using an Ed25519 key.
2606 ///
2607 /// Verifies that the reloaded key's public component matches the original.
2608 #[test]
2609 fn pkey_private_from_der_in_ed25519() {
2610 let ctx = Arc::new(crate::lib_ctx::LibCtx::new().unwrap());
2611
2612 let key = KeygenCtx::new(c"ED25519").unwrap().generate().unwrap();
2613
2614 // Serialise to PKCS#8 DER (the format auto-detected by from_der_in).
2615 let der = key.to_pkcs8_der().unwrap();
2616 assert!(!der.is_empty());
2617
2618 // Reload via the LibCtx-aware path.
2619 let key2 = Pkey::<Private>::from_der_in(&ctx, &der).unwrap();
2620
2621 // Public components must be identical.
2622 assert!(
2623 key.public_eq(&key2),
2624 "reloaded private key must equal original"
2625 );
2626 }
2627
2628 /// `Pkey::<Public>::from_der_in` — round-trip through `public_key_to_der`
2629 /// (`SubjectPublicKeyInfo`) and back via the LibCtx-aware `from_der_in`.
2630 #[test]
2631 fn pkey_public_from_der_in_ed25519() {
2632 let ctx = Arc::new(crate::lib_ctx::LibCtx::new().unwrap());
2633
2634 let priv_key = KeygenCtx::new(c"ED25519").unwrap().generate().unwrap();
2635 let pub_key = Pkey::<Public>::from(priv_key.clone());
2636
2637 // Serialise the public key to SubjectPublicKeyInfo DER.
2638 let der = pub_key.public_key_to_der().unwrap();
2639 assert!(!der.is_empty());
2640
2641 // Reload via the LibCtx-aware path.
2642 let pub_key2 = Pkey::<Public>::from_der_in(&ctx, &der).unwrap();
2643
2644 // Must match the original private key's public component.
2645 assert!(
2646 priv_key.public_eq(&pub_key2),
2647 "reloaded public key must equal original"
2648 );
2649 }
2650
2651 /// `to_der_legacy` — generate an RSA key and verify that the legacy DER
2652 /// output is non-empty (RSA has a well-defined `RSAPrivateKey` legacy format).
2653 #[test]
2654 fn pkey_to_der_legacy_produces_non_empty() {
2655 let key = KeygenCtx::new(c"RSA").unwrap().generate().unwrap();
2656
2657 let der = key.to_der_legacy().unwrap();
2658 assert!(!der.is_empty(), "legacy DER for RSA must be non-empty");
2659 }
2660}