Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Asymmetric Keys

Key Visibility Typestate

All asymmetric key material is held in Pkey<T>, where T is a zero-sized marker:

Private  →  HasPrivate (also HasPublic + HasParams)
Public   →  HasPublic  (also HasParams)
Params   →  HasParams  (DH/DSA parameters only; no key material)

A Pkey<Private> can be used anywhere a Pkey<Public> is accepted via the trait hierarchy — no explicit conversion is needed for operations that only require the public component. However, you cannot extract a Pkey<Private> from a Pkey<Public>.

Pkey<T> — Key Container

#![allow(unused)]
fn main() {
pub struct Pkey<T> { /* EVP_PKEY* */ }

impl<T: HasParams> Pkey<T> {
    /// Key size in bits (e.g. 2048 for RSA-2048, 256 for P-256).
    pub fn bits(&self) -> u32;

    /// Security level in bits.
    pub fn security_bits(&self) -> u32;

    /// Maximum output size in bytes (signature length for signing keys, ciphertext for encryption).
    /// Use this to size output buffers before signing. Returns 0 if not applicable.
    pub fn max_output_size(&self) -> usize;

    /// Check algorithm type by name (e.g. c"RSA", c"EC", c"ED25519").
    pub fn is_a(&self, name: &CStr) -> bool;

    /// Compare public components of two keys for equality.
    pub fn public_eq<U: HasPublic>(&self, other: &Pkey<U>) -> bool
    where T: HasPublic;

    /// Export public key as DER (SubjectPublicKeyInfo).
    pub fn public_key_to_der(&self) -> Result<Vec<u8>, ErrorStack>
    where T: HasPublic;

    /// Fill values into a pre-prepared mutable query array.
    /// Wraps EVP_PKEY_get_params. Works for all key roles.
    pub fn get_params(&self, params: &mut Params<'_>) -> Result<(), ErrorStack>;

    /// Returns the default digest algorithm name for this key, or None if the
    /// key type mandates no external digest (e.g. Ed25519, ML-DSA, SLH-DSA).
    ///
    /// Wraps EVP_PKEY_get_default_digest_name. Both return-value 1 (optional digest)
    /// and 2 (mandatory digest) are treated as success. "none" or empty string
    /// maps to None.
    ///
    /// Requires T: HasPublic (i.e. Pkey<Public> or Pkey<Private>).
    pub fn default_digest_name(&self) -> Result<Option<String>, ErrorStack>
    where T: HasPublic;
}
}

Pkey<T> is Clone (via EVP_PKEY_up_ref) and Send + Sync.

Key Loading

Private Key

#![allow(unused)]
fn main() {
impl Pkey<Private> {
    /// Load from PEM (PKCS#8 or traditional format).
    pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack>;

    /// Load from PEM using an explicit library context (FIPS isolation).
    pub fn from_pem_in(ctx: &Arc<LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack>;

    /// Load from passphrase-encrypted PEM.
    /// Passes `passphrase` to OpenSSL via a `pem_password_cb`.
    /// Use `from_pem` for unencrypted PEM.
    pub fn from_pem_passphrase(pem: &[u8], passphrase: &[u8]) -> Result<Self, ErrorStack>;

    /// Load from DER (PKCS#8 or traditional, auto-detected).
    /// Uses MemBioBuf — reads directly from caller's slice without copying.
    pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack>;

    /// Load from DER bytes using an explicit library context.
    /// Format is auto-detected (PKCS#8, traditional, etc.) via d2i_AutoPrivateKey_ex.
    /// Use when the key will be used with EVP operations in an isolated (e.g. FIPS) context.
    pub fn from_der_in(ctx: &Arc<LibCtx>, der: &[u8]) -> Result<Self, ErrorStack>;

    /// Encode this private key to legacy raw-key DER (not PKCS#8).
    /// Wraps i2d_PrivateKey. Output is the algorithm-native structure
    /// (e.g. RSAPrivateKey for RSA, ECPrivateKey for EC).
    /// For new code prefer to_pkcs8_der() which is algorithm-agnostic.
    pub fn to_der_legacy(&self) -> Result<Vec<u8>, ErrorStack>;

    /// Serialize to PEM (PKCS#8 `BEGIN PRIVATE KEY` format).
    pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack>;

    /// Serialize to passphrase-encrypted PKCS#8 PEM (`BEGIN ENCRYPTED PRIVATE KEY`).
    /// `cipher` controls the wrapping algorithm (e.g. `CipherAlg::fetch(c"AES-256-CBC", None)`).
    pub fn to_pem_encrypted(&self, cipher: &CipherAlg, passphrase: &[u8]) -> Result<Vec<u8>, ErrorStack>;

    /// Serialize to unencrypted PKCS#8 DER (`PrivateKeyInfo` / RFC 5958).
    /// Avoids the base64 round-trip of `to_pem`. Use `to_pem_encrypted` for an encrypted form.
    pub fn to_pkcs8_der(&self) -> Result<Vec<u8>, ErrorStack>;
}
}

Public Key

#![allow(unused)]
fn main() {
impl Pkey<Public> {
    /// Load from PEM (SubjectPublicKeyInfo or RSA public key format).
    pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack>;

    /// Load from PEM using an explicit library context (FIPS isolation).
    ///
    /// Uses PEM_read_bio_PUBKEY_ex so the key's internal algorithm fetch
    /// uses ctx's provider set. Necessary when the public key is later used
    /// for EVP operations inside an isolated (e.g. FIPS) context.
    pub fn from_pem_in(ctx: &Arc<LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack>;

    /// Load from DER (SubjectPublicKeyInfo).
    pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack>;

    /// Load SubjectPublicKeyInfo DER using an explicit library context.
    /// Wraps d2i_PUBKEY_ex so the key's algorithm fetch is bound to ctx's
    /// provider set. Use when the key will be used with EVP operations in
    /// an isolated (e.g. FIPS) context.
    pub fn from_der_in(ctx: &Arc<LibCtx>, der: &[u8]) -> Result<Self, ErrorStack>;

    /// Serialize to PEM.
    pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack>;
}
}

Both from_der variants take &[u8] (not a cursor). The full slice is consumed.

Key Import and Export via OSSL_PARAM

These methods load or dump raw key material through OSSL_PARAM arrays. They are the basis for interoperability with external key stores, HSMs, and protocol implementations that work with raw key components (e.g. RSA modulus/exponent, EC coordinates, raw Ed25519 seed bytes).

Import

#![allow(unused)]
fn main() {
impl Pkey<Private> {
    /// Import a private key pair from an OSSL_PARAM array.
    ///
    /// Wraps EVP_PKEY_fromdata with KEYPAIR selection.
    /// Pass ctx = None to use the global default library context.
    pub fn from_params(
        ctx: Option<&Arc<LibCtx>>,
        pkey_type: &CStr,
        params: &Params<'_>,
    ) -> Result<Self, ErrorStack>;
}

impl Pkey<Public> {
    /// Import a public key from an OSSL_PARAM array.
    ///
    /// Wraps EVP_PKEY_fromdata with PUBLIC_KEY selection.
    pub fn from_params(
        ctx: Option<&Arc<LibCtx>>,
        pkey_type: &CStr,
        params: &Params<'_>,
    ) -> Result<Self, ErrorStack>;
}
}

Export

#![allow(unused)]
fn main() {
impl Pkey<Private> {
    /// Export all key parameters (private + public) as an owned OSSL_PARAM array.
    ///
    /// Wraps EVP_PKEY_todata with KEYPAIR selection.
    /// The returned Params<'static> is owned by the caller.
    pub fn export(&self) -> Result<Params<'static>, ErrorStack>;
}

impl Pkey<Public> {
    /// Export the public key parameters as an owned OSSL_PARAM array.
    ///
    /// Wraps EVP_PKEY_todata with PUBLIC_KEY selection.
    pub fn export(&self) -> Result<Params<'static>, ErrorStack>;
}
}

Targeted Parameter Query

#![allow(unused)]
fn main() {
impl<T: HasParams> Pkey<T> {
    /// Fill values into a pre-prepared mutable query array.
    ///
    /// Wraps EVP_PKEY_get_params. The array must already contain the parameter
    /// names of interest (built via ParamBuilder); OpenSSL writes the values
    /// in place.
    pub fn get_params(&self, params: &mut Params<'_>) -> Result<(), ErrorStack>;
}
}

get_params is more efficient than export when only a subset of parameters is needed, because OpenSSL only copies the requested fields.

Key Generation

#![allow(unused)]
fn main() {
pub struct KeygenCtx { /* EVP_PKEY_CTX in keygen mode */ }

impl KeygenCtx {
    /// Create a keygen context for the named algorithm.
    /// Calls EVP_PKEY_CTX_new_from_name + EVP_PKEY_keygen_init.
    ///
    /// Common names: c"RSA", c"EC", c"ED25519", c"X25519",
    ///               c"ML-KEM-768", c"ML-DSA-65".
    pub fn new(name: &CStr) -> Result<Self, ErrorStack>;

    /// Set keygen parameters (e.g. RSA bit length, EC curve name).
    /// Wraps EVP_PKEY_CTX_set_params.
    pub fn set_params(&mut self, params: &Params<'_>) -> Result<(), ErrorStack>;

    /// Retrieve parameter values from this keygen context.
    ///
    /// Build a Params with placeholder values for the keys you want,
    /// call this method, then read back with params.get_*.
    ///
    /// Useful for reading algorithm-negotiated parameters after keygen
    /// initialisation (e.g. security strength, EC group name, RSA modulus size).
    /// Wraps EVP_PKEY_CTX_get_params.
    pub fn get_params(&self, params: &mut Params<'_>) -> Result<(), ErrorStack>;

    /// Return the security strength of the key operation in bits.
    ///
    /// Available after keygen initialisation; reads OSSL_PKEY_PARAM_SECURITY_BITS
    /// ("security-bits"). Convenience wrapper around get_params.
    pub fn security_bits(&self) -> Result<u32, ErrorStack>;

    /// Generate the key pair.
    pub fn generate(&mut self) -> Result<Pkey<Private>, ErrorStack>;
}
}

There are no convenience methods like generate_rsa(). Use KeygenCtx::new directly.

Signing

#![allow(unused)]
fn main() {
/// Parameters for creating a Signer or Verifier.
#[derive(Default)]
pub struct SignInit<'a> {
    /// Digest algorithm, or None for EdDSA (Ed25519, Ed448).
    pub digest: Option<&'a DigestAlg>,
    /// Optional parameters (e.g. RSA PSS salt length).
    pub params: Option<&'a Params<'a>>,
}
}

Signer

#![allow(unused)]
fn main() {
pub struct Signer { ctx: DigestCtx, _key: Pkey<Private> }

impl Signer {
    /// Create a signing context. The key is cloned internally (EVP_PKEY_up_ref).
    pub fn new(key: &Pkey<Private>, init: &SignInit<'_>) -> Result<Self, ErrorStack>;

    /// Feed data. May be called multiple times.
    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack>;

    /// Finalise: returns the signature as Vec<u8>.
    /// Queries size then writes — always allocates.
    pub fn finish(&mut self) -> Result<Vec<u8>, ErrorStack>;

    /// One-shot sign over `data`. Required for Ed25519 / Ed448.
    pub fn sign_oneshot(&mut self, data: &[u8]) -> Result<Vec<u8>, ErrorStack>;
}
}

Verifier

#![allow(unused)]
fn main() {
pub struct Verifier { ctx: DigestCtx, _key: Pkey<Public> }

impl Verifier {
    pub fn new(key: &Pkey<Public>, init: &SignInit<'_>) -> Result<Self, ErrorStack>;
    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack>;

    /// Returns Ok(true) if valid, Ok(false) if signature is wrong,
    /// Err on a fatal OpenSSL error.
    pub fn verify(&mut self, signature: &[u8]) -> Result<bool, ErrorStack>;

    /// One-shot verify. Required for Ed25519 / Ed448.
    pub fn verify_oneshot(&mut self, data: &[u8], sig: &[u8]) -> Result<bool, ErrorStack>;
}
}

Raw (No-Digest) Signing — RawSigner / RawVerifier

RawSigner and RawVerifier wrap EVP_PKEY_CTX after EVP_PKEY_sign_init / EVP_PKEY_verify_init. They operate directly on pre-hashed data — the caller is responsible for hashing the message before calling sign or verify.

Use these types for:

  • ECDSA over a pre-computed hash (e.g. SHA-256 digest passed as the TBS input)
  • Raw RSA with explicit padding configuration

For algorithms that hash internally (Ed25519, RSA-PSS with automatic hashing), use Signer / Verifier or MessageSigner / MessageVerifier instead.

RawSigner

#![allow(unused)]
fn main() {
pub struct RawSigner { /* EVP_PKEY_CTX after sign_init */ }

impl RawSigner {
    /// Create and initialise a sign context.
    ///
    /// Pass libctx = Some(ctx) to restrict provider lookup to that library
    /// context; None uses the key's own library context.
    pub fn new(
        key: &Pkey<Private>,
        libctx: Option<&Arc<LibCtx>>,
    ) -> Result<Self, ErrorStack>;

    /// Apply parameters after init (e.g. RSA padding mode, PSS salt length).
    pub fn set_params(&mut self, params: &Params<'_>) -> Result<(), ErrorStack>;

    /// Query the signature output size for the given input length.
    /// Null-probe via EVP_PKEY_sign — does not consume the signing state.
    pub fn sign_len(&mut self, tbs_len: usize) -> Result<usize, ErrorStack>;

    /// Sign pre-hashed data into sig. Returns the number of bytes written.
    /// sig.len() must be >= sign_len(tbs.len()).
    pub fn sign(&mut self, tbs: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack>;

    /// Convenience: sign_len + sign with an allocated output Vec.
    pub fn sign_alloc(&mut self, tbs: &[u8]) -> Result<Vec<u8>, ErrorStack>;
}
}

The context is reusable across multiple sign calls — the padding parameters set via set_params remain in effect until the RawSigner is dropped.

RawVerifier

#![allow(unused)]
fn main() {
pub struct RawVerifier { /* EVP_PKEY_CTX after verify_init */ }

impl RawVerifier {
    /// Create and initialise a verify context.
    ///
    /// Accepts any key with public material (Pkey<Public> or Pkey<Private>).
    pub fn new<T: HasPublic>(
        key: &Pkey<T>,
        libctx: Option<&Arc<LibCtx>>,
    ) -> Result<Self, ErrorStack>;

    /// Apply parameters after init (e.g. RSA padding mode).
    pub fn set_params(&mut self, params: &Params<'_>) -> Result<(), ErrorStack>;

    /// Verify sig against pre-hashed tbs. Returns Ok(()) if valid.
    /// Returns Err if the signature is invalid or on any OpenSSL error.
    pub fn verify(&mut self, tbs: &[u8], sig: &[u8]) -> Result<(), ErrorStack>;
}
}

Note that RawVerifier::verify returns Result<()> — unlike Verifier::verify which returns Result<bool>, an invalid raw signature is an error rather than Ok(false). This reflects the EVP_PKEY_verify API convention.

SigAlg — Algorithm Descriptor

SigAlg is an EVP_SIGNATURE algorithm descriptor. It mirrors DigestAlg, CipherAlg, and MacAlg in naming and lifecycle. SigAlg is needed only when constructing MessageSigner or MessageVerifier; the older Signer/Verifier types use DigestAlg instead.

#![allow(unused)]
fn main() {
pub struct SigAlg { /* EVP_SIGNATURE* */ }

impl SigAlg {
    /// Fetch from the default library context.
    ///
    /// Example names: c"ML-DSA-44", c"ED25519", c"SLH-DSA-SHA2-128s".
    pub fn fetch(name: &CStr, props: Option<&CStr>) -> Result<Self, ErrorStack>;

    /// Fetch within a specific library context.
    pub fn fetch_in(ctx: &Arc<LibCtx>, name: &CStr, props: Option<&CStr>) -> Result<Self, ErrorStack>;
}
}

SigAlg is Clone (via EVP_SIGNATURE_up_ref) and Send + Sync. Drop calls EVP_SIGNATURE_free. Cloning is cheap — it increments a reference count inside OpenSSL.

Message Signing — MessageSigner / MessageVerifier

MessageSigner and MessageVerifier wrap EVP_PKEY_sign_message_* / EVP_PKEY_verify_message_* (OpenSSL 3.2+, present at the 3.5 minimum floor).

These types differ from Signer/Verifier in two ways:

  1. The algorithm is specified as a SigAlg rather than a DigestAlg. This enables post-quantum and other algorithms that do not separate hashing from signing at the API level.
  2. finish and sign_oneshot are consuming (self by value) — the context cannot be reused after finalisation.

MessageSigner

#![allow(unused)]
fn main() {
pub struct MessageSigner { /* EVP_PKEY_CTX after sign_message_init */ }

impl MessageSigner {
    /// Create and initialise a message-sign context.
    ///
    /// alg is consumed by EVP_PKEY_sign_message_init; pass alg.clone() if
    /// you need to keep the SigAlg alive.
    /// params sets algorithm-specific options (e.g. context string for Ed25519).
    pub fn new(
        key: &Pkey<Private>,
        alg: &mut SigAlg,
        params: Option<&Params<'_>>,
    ) -> Result<Self, ErrorStack>;

    /// Probe whether this algorithm backend supports incremental update calls.
    ///
    /// Uses ERR_set_mark / ERR_pop_to_mark so the error queue is clean
    /// regardless of the outcome.
    pub fn supports_streaming(&mut self) -> bool;

    /// Feed data into the signing operation.
    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack>;

    /// Query the signature output length (null-probe — does not finalise).
    pub fn sig_len(&mut self) -> Result<usize, ErrorStack>;

    /// Finalise and produce the signature into sig. Consuming.
    /// Returns the number of bytes written.
    pub fn finish(self, sig: &mut [u8]) -> Result<usize, ErrorStack>;

    /// One-shot: update + finish in a single call. Consuming.
    pub fn sign_oneshot(self, data: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack>;
}
}

MessageVerifier

#![allow(unused)]
fn main() {
pub struct MessageVerifier { /* EVP_PKEY_CTX after verify_message_init */ }

impl MessageVerifier {
    /// Create and initialise a message-verify context.
    ///
    /// Accepts any key with public material.
    pub fn new<T: HasPublic>(
        key: &Pkey<T>,
        alg: &mut SigAlg,
        params: Option<&Params<'_>>,
    ) -> Result<Self, ErrorStack>;

    /// Apply parameters after init.
    pub fn set_params(&mut self, params: &Params<'_>) -> Result<(), ErrorStack>;

    /// Supply the signature to verify against. Required before streaming finish.
    /// Not needed for verify_oneshot.
    pub fn set_signature(&mut self, sig: &[u8]) -> Result<(), ErrorStack>;

    /// Probe whether this algorithm backend supports incremental update calls.
    pub fn supports_streaming(&mut self) -> bool;

    /// Feed data into the verification operation.
    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack>;

    /// Finalise and verify. Signature must be set via set_signature. Consuming.
    /// Returns Ok(()) if the signature is valid.
    pub fn finish(self) -> Result<(), ErrorStack>;

    /// One-shot: set signature, feed data, finalise. Consuming.
    pub fn verify_oneshot(self, data: &[u8], sig: &[u8]) -> Result<(), ErrorStack>;
}
}

ML-DSA Provider Caveat

OpenSSL 3.5’s built-in ML-DSA provider implements OSSL_FUNC_SIGNATURE_SIGN_MESSAGE (the one-shot EVP_DigestSign path) but not OSSL_FUNC_SIGNATURE_SIGN_MESSAGE_FINAL. As a result:

  • MessageSigner::supports_streaming() returns false for ML-DSA with the default provider.
  • MessageSigner::finish() will fail for ML-DSA with the default provider.
  • ML-DSA signing with the default provider must use Signer with SignInit { digest: None, .. } (the EVP_DigestSign one-shot path).

MessageSigner / MessageVerifier work correctly for algorithms whose provider implements the full streaming path (e.g. Ed25519 with context strings in a provider that supports it).

Key Agreement (ECDH, X25519)

#![allow(unused)]
fn main() {
pub struct DeriveCtx { /* EVP_PKEY_CTX in derive mode */ }

impl DeriveCtx {
    /// Create from a private key. Calls EVP_PKEY_derive_init.
    pub fn new(key: &Pkey<Private>) -> Result<Self, ErrorStack>;

    /// Set the peer's public key.
    pub fn set_peer(&mut self, peer: &Pkey<Public>) -> Result<(), ErrorStack>;

    /// Query the shared secret length.
    pub fn derive_len(&mut self) -> Result<usize, ErrorStack>;

    /// Derive into caller-provided buffer.
    pub fn derive(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack>;
}
}

RSA Encryption

#![allow(unused)]
fn main() {
pub struct PkeyEncryptCtx { /* EVP_PKEY_CTX in encrypt mode */ }

impl PkeyEncryptCtx {
    /// Create from a public key. params sets padding mode (e.g. OAEP).
    pub fn new(key: &Pkey<Public>, params: Option<&Params<'_>>) -> Result<Self, ErrorStack>;

    /// Query ciphertext length for a given plaintext length.
    pub fn encrypt_len(&mut self, plaintext_len: usize) -> Result<usize, ErrorStack>;

    /// Encrypt plaintext into ciphertext. Returns bytes written.
    pub fn encrypt(&mut self, plaintext: &[u8], ciphertext: &mut [u8])
        -> Result<usize, ErrorStack>;
}

pub struct PkeyDecryptCtx { /* EVP_PKEY_CTX in decrypt mode */ }

impl PkeyDecryptCtx {
    pub fn new(key: &Pkey<Private>, params: Option<&Params<'_>>) -> Result<Self, ErrorStack>;
    pub fn decrypt(&mut self, ciphertext: &[u8], plaintext: &mut [u8])
        -> Result<usize, ErrorStack>;
}
}

ML-KEM Key Encapsulation

#![allow(unused)]
fn main() {
pub struct EncapResult {
    pub wrapped_key:   Vec<u8>,   // the encapsulated key (ciphertext)
    pub shared_secret: Vec<u8>,   // the shared secret
}

pub struct EncapCtx { /* EVP_PKEY_CTX in encapsulate mode */ }

impl EncapCtx {
    pub fn new(key: &Pkey<Public>) -> Result<Self, ErrorStack>;

    /// Encapsulate: generates a fresh shared secret and its encapsulation.
    /// Both outputs are returned as owned Vecs.
    pub fn encapsulate(&mut self) -> Result<EncapResult, ErrorStack>;
}

pub struct DecapCtx { /* EVP_PKEY_CTX in decapsulate mode */ }

impl DecapCtx {
    pub fn new(key: &Pkey<Private>) -> Result<Self, ErrorStack>;

    /// Recover the shared secret from a wrapped key.
    pub fn decapsulate(&mut self, wrapped_key: &[u8]) -> Result<Vec<u8>, ErrorStack>;
}
}

Examples

Generate RSA-4096 Key Pair

#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, Pkey, Private};
use native_ossl::params::ParamBuilder;

let mut ctx = KeygenCtx::new(c"RSA")?;

let params = ParamBuilder::new()?
    .push_uint(c"bits", 4096)?
    .build()?;
ctx.set_params(&params)?;

let key: Pkey<Private> = ctx.generate()?;
assert!(key.is_a(c"RSA"));
assert_eq!(key.bits(), 4096);
}

Generate Ed25519 Key and Sign

#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, Signer, Verifier, SignInit};

let key = KeygenCtx::new(c"ED25519")?.generate()?;
let pub_key = Pkey::<native_ossl::pkey::Public>::from_pem(&key.to_pem()?)?;

let init = SignInit::default();  // digest = None for Ed25519

let mut signer = Signer::new(&key, &init)?;
let sig = signer.sign_oneshot(b"message to sign")?;

let mut verifier = Verifier::new(&pub_key, &init)?;
assert!(verifier.verify_oneshot(b"message to sign", &sig)?);
}

ECDSA Sign with SHA-256

#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, Signer, Verifier, SignInit};
use native_ossl::digest::DigestAlg;
use native_ossl::params::ParamBuilder;

// Generate P-256 key
let mut ctx = KeygenCtx::new(c"EC")?;
let curve_params = ParamBuilder::new()?
    .push_utf8_string(c"group", c"P-256")?
    .build()?;
ctx.set_params(&curve_params)?;
let key = ctx.generate()?;

let sha256 = DigestAlg::fetch(c"SHA2-256", None)?;
let init = SignInit { digest: Some(&sha256), params: None };

let mut signer = Signer::new(&key, &init)?;
signer.update(b"hello ")?;
signer.update(b"world")?;
let sig = signer.finish()?;
}

Pre-Hashed ECDSA with RawSigner

When the message digest is computed separately (e.g. already SHA-256 hashed), use RawSigner to sign the hash directly without re-hashing:

#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, RawSigner, RawVerifier};
use native_ossl::params::ParamBuilder;

// Generate P-256 key
let mut ctx = KeygenCtx::new(c"EC")?;
let curve_params = ParamBuilder::new()?
    .push_utf8_string(c"group", c"P-256")?
    .build()?;
ctx.set_params(&curve_params)?;
let priv_key = ctx.generate()?;
let pub_key = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from(priv_key.clone());

// The caller provides the pre-computed SHA-256 hash (32 bytes).
let tbs: [u8; 32] = compute_sha256(b"my message");

// Sign the hash. None = use the key's own library context.
let mut signer = RawSigner::new(&priv_key, None)?;
let sig = signer.sign_alloc(&tbs)?;

// Verify the signature against the same hash.
let mut verifier = RawVerifier::new(&pub_key, None)?;
verifier.verify(&tbs, &sig)?;  // Ok(()) means valid
}

To configure RSA PSS padding after construction, use set_params:

#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, RawSigner};
use native_ossl::params::ParamBuilder;

let key = KeygenCtx::new(c"RSA")?.generate()?;
let mut signer = RawSigner::new(&key, None)?;

let pss_params = ParamBuilder::new()?
    .push_utf8_ptr(c"pad-mode", c"pss")?
    .push_int(c"saltlen", 32)?
    .build()?;
signer.set_params(&pss_params)?;

let mut sig_buf = vec![0u8; signer.sign_len(32)?];
let n = signer.sign(&my_hash, &mut sig_buf)?;
sig_buf.truncate(n);
}

MessageSigner / MessageVerifier with Streaming Probe

#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, SigAlg, MessageSigner, MessageVerifier};

let key = KeygenCtx::new(c"ED25519")?.generate()?;
let pub_key = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from(key.clone());

let mut alg = SigAlg::fetch(c"ED25519", None)?;

// Construction takes alg by &mut — pass alg.clone() if you need to reuse it.
let mut signer = MessageSigner::new(&key, &mut alg, None)?;

if signer.supports_streaming() {
    // Algorithm supports incremental update calls.
    signer.update(b"chunk one")?;
    signer.update(b"chunk two")?;
    let siglen = signer.sig_len()?;  // query before allocating
    let mut sig = vec![0u8; siglen];
    let n = signer.finish(sig.as_mut_slice())?;   // consuming
    sig.truncate(n);

    // Verify using the streaming path.
    let mut alg2 = SigAlg::fetch(c"ED25519", None)?;
    let mut verifier = MessageVerifier::new(&pub_key, &mut alg2, None)?;
    verifier.set_signature(&sig)?;
    verifier.update(b"chunk one")?;
    verifier.update(b"chunk two")?;
    verifier.finish()?;  // Ok(()) means valid; consuming
} else {
    // Algorithm only supports one-shot operation (e.g. ML-DSA with default provider).
    let siglen = signer.sig_len()?;
    let mut sig = vec![0u8; siglen];
    let data = b"full message";
    let n = signer.sign_oneshot(data, &mut sig)?;  // consuming
    sig.truncate(n);

    let mut alg2 = SigAlg::fetch(c"ED25519", None)?;
    let verifier = MessageVerifier::new(&pub_key, &mut alg2, None)?;
    verifier.verify_oneshot(data, &sig)?;  // consuming
}
}

RSA-OAEP Encrypt / Decrypt

#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, PkeyEncryptCtx, PkeyDecryptCtx};
use native_ossl::params::ParamBuilder;

let key = KeygenCtx::new(c"RSA")?.generate()?;

let oaep = ParamBuilder::new()?
    .push_utf8_ptr(c"pad-mode",    c"oaep")?
    .push_utf8_ptr(c"oaep-digest", c"SHA-256")?
    .build()?;

let pub_pem = key.to_pem()?;  // in practice load from file
let pub_key = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from_pem(&pub_pem)?;

let mut enc = PkeyEncryptCtx::new(&pub_key, Some(&oaep))?;
let ct_len = enc.encrypt_len(b"secret".len())?;
let mut ciphertext = vec![0u8; ct_len];
let n = enc.encrypt(b"secret", &mut ciphertext)?;
ciphertext.truncate(n);

let mut dec = PkeyDecryptCtx::new(&key, Some(&oaep))?;
let mut plaintext = vec![0u8; ciphertext.len()];
let m = dec.decrypt(&ciphertext, &mut plaintext)?;
plaintext.truncate(m);
assert_eq!(&plaintext, b"secret");
}

OSSL_PARAM Round-Trip: Generate → Export → Import

Export a generated EC P-256 key pair to raw parameters and re-import it as a fresh Pkey<Private>. The public components of both keys must match.

use native_ossl::pkey::{KeygenCtx, Pkey, Private, Public};
use native_ossl::params::ParamBuilder;

// 1. Generate a P-256 key pair.
let mut ctx = KeygenCtx::new(c"EC")?;
let curve = ParamBuilder::new()?
    .push_utf8_string(c"group", c"P-256")?
    .build()?;
ctx.set_params(&curve)?;
let original: Pkey<Private> = ctx.generate()?;

// 2. Export all key material (private + public) to a Params array.
let exported = original.export()?;

// 3. Inspect individual parameters.
//    get_bn allocates a Vec<u8> of big-endian bytes.
if exported.has_param(c"priv") {
    let priv_bytes = exported.get_bn(c"priv")?;
    println!("private scalar: {} bytes", priv_bytes.len());
}

// 4. Re-import as a new private key.
let recovered = Pkey::<Private>::from_params(None, c"EC", &exported)?;
assert!(recovered.is_a(c"EC"));

// 5. Verify that the public components match.
let pub_original  = Pkey::<Public>::from(original.clone());
let pub_recovered = Pkey::<Public>::from(recovered);
assert!(pub_original.public_eq(&pub_recovered));

Targeted Parameter Query with get_params

Fetch only the RSA public exponent without exporting the full key:

use native_ossl::params::ParamBuilder;

// Build a query array with a placeholder for the "e" parameter.
// The zero-length octet slice signals to OpenSSL that this is a query slot.
let mut query = ParamBuilder::new()?
    .push_octet_slice(c"e", &[])?
    .build()?;

key.get_params(&mut query)?;

// get_bn allocates a Vec<u8> of the BIGNUM value.
let e_bytes = query.get_bn(c"e")?;
println!("public exponent: {:?}", e_bytes);

Load a Public Key in a FIPS Library Context

use native_ossl::lib_ctx::LibCtx;
use native_ossl::pkey::{Pkey, Public};
use std::sync::Arc;

// Load the FIPS provider into an isolated context.
let fips_ctx = Arc::new(LibCtx::new()?);
// fips_ctx.load_provider(...)?; — load your FIPS provider here

let pub_pem = std::fs::read("peer_pubkey.pem")?;
let pub_key = Pkey::<Public>::from_pem_in(&fips_ctx, &pub_pem)?;
// All subsequent operations on pub_key use the FIPS provider.

ML-KEM-768 (Post-Quantum KEM)

#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, EncapCtx, DecapCtx};

// Generate ML-KEM-768 key pair (recipient)
let key = KeygenCtx::new(c"ML-KEM-768")?.generate()?;
let pub_pem = key.to_pem()?;
let pub_key = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from_pem(&pub_pem)?;

// Sender encapsulates
let mut enc = EncapCtx::new(&pub_key)?;
let result = enc.encapsulate()?;

// Recipient decapsulates
let mut dec = DecapCtx::new(&key)?;
let shared = dec.decapsulate(&result.wrapped_key)?;

assert_eq!(shared, result.shared_secret);
}

Design Notes

  • max_output_size() returns usize, not Result<usize> — it mirrors the bits() / security_bits() pattern. It returns 0 rather than an error when the key does not support a size query (e.g., for key-agreement-only keys). For RSA the value equals bits() / 8; for Ed25519 it is always 64.
  • No convenience constructors — there are no generate_rsa(), generate_ed25519(), etc. shorthand methods. Use KeygenCtx::new(c"ED25519")?.generate() directly.
  • Signer/Verifier hold an owned clone of the key via _key: Pkey<T>. There is no lifetime parameter — the key is cloned at new() via EVP_PKEY_up_ref.
  • Verifier::verify returns Result<bool>Ok(false) means the signature was well-formed but incorrect; Err means a fatal operational error.
  • RawVerifier::verify returns Result<()>Err covers both invalid signatures and operational errors, matching the EVP_PKEY_verify API convention.
  • from_der uses MemBioBuf — reads directly from the caller’s slice without copying. The whole slice is consumed; there is no cursor-advancement API.
  • from_der_in uses a raw pointer pair, not a BIOd2i_AutoPrivateKey_ex / d2i_PUBKEY_ex take (const unsigned char**, long) directly. Passing the slice pointer lets OpenSSL bind the key’s internal algorithm fetch to the supplied LibCtx, which is necessary for FIPS-isolated operations. The format for Pkey<Private>::from_der_in is auto-detected (PKCS#8 or traditional); use it when you have DER bytes of unknown provenance and need FIPS isolation. Use the non-_in variants when the global provider context is sufficient.
  • to_der_legacy vs. to_pkcs8_derto_pkcs8_der (wraps i2d_PKCS8PrivateKeyInfo) produces a PKCS#8 PrivateKeyInfo / RFC 5958 structure that is algorithm-agnostic and the recommended format for all new code. to_der_legacy (wraps i2d_PrivateKey) produces the algorithm-specific raw structure (e.g. RSAPrivateKey per RFC 3447, ECPrivateKey per RFC 5915). Use to_der_legacy only for interoperability with legacy software that does not support PKCS#8; some algorithms (e.g. post-quantum) may not support the legacy format at all and will return an error.
  • Pkey<Public>::from_pem_in uses PEM_read_bio_PUBKEY_ex — unlike X509::from_pem_in (which ignores its ctx argument because no libctx-aware variant exists in OpenSSL 3.5), Pkey<Public>::from_pem_in genuinely binds the key’s algorithm fetch to the supplied LibCtx. Use it when the public key will be used for operations in an isolated provider context.
  • from_params selectionPkey<Private>::from_params uses KEYPAIR selection (both private and public material required); Pkey<Public>::from_params uses PUBLIC_KEY selection. Passing private-key parameters to Pkey<Public>::from_params is safe — OpenSSL ignores the private fields.
  • export returns Params<'static> — the array is allocated by OpenSSL and owned entirely by the returned Params value. There are no lifetime constraints from the source key; the Pkey may be dropped before the exported Params.
  • get_params vs. export — use get_params when you need only a subset of fields (more efficient); use export when you need a complete dump or plan to pass the Params array to from_params.
  • RawSigner is reusable — padding parameters configured via set_params persist across multiple sign calls on the same instance.
  • MessageSigner/MessageVerifier are consumingfinish and sign_oneshot take self by value; the context is freed at the end of the call.
  • SigAlg taken by &mutEVP_PKEY_sign_message_init modifies the algorithm object internally. Pass alg.clone() if you need the SigAlg to remain usable after constructing a MessageSigner or MessageVerifier.
  • ML-DSA streaming caveat — OpenSSL 3.5’s built-in ML-DSA provider does not implement OSSL_FUNC_SIGNATURE_SIGN_MESSAGE_FINAL. Use Signer for ML-DSA signing with the default provider.
  • KeygenCtx::set_params vs constructor paramsset_params may be called any number of times before generate to layer parameter changes. Each call applies incrementally; earlier settings are not erased unless the same key is overwritten.
  • KeygenCtx::get_params getter pattern — build a Params array with placeholder values (e.g. push_uint(c"security-bits", 0)), call get_params, then read back the filled values with params.get_uint / params.get_utf8_string etc. The placeholder value is overwritten by OpenSSL; only the key name matters for the query. For string parameters, the placeholder must be long enough to receive the returned value.
  • KeygenCtx::get_params vs RawSigner::get_params — keygen contexts expose few gettable parameters (algorithm-specific; most algorithms expose none). Operation contexts (sign/verify) expose more: RSA exposes pad-mode, digest, mgf1-digest, saltlen, and algorithm-id. Use RawSigner::get_params to inspect or verify per-operation settings after EVP_PKEY_sign_init.
  • KeygenCtx::get_params vs Pkey::get_paramsKeygenCtx::get_params reads from the context (available immediately after keygen_init, before a key exists); Pkey::get_params reads from the generated key object (available only after generate). Use the key-object variant for security-bits and other key properties.
  • default_digest_name() returns None for pre-hash algorithms — For Ed25519, ML-DSA, and SLH-DSA the key type performs all hashing internally and there is no separate digest step. default_digest_name() returns None in that case, and callers should pass digest: None in SignInit (or omit the digest argument entirely). For RSA and ECDSA keys it returns a Some(name) (typically "SHA256") which can be fetched via DigestAlg::fetch and passed to Signer::new or DigestSignInit_ex. Check this value before deciding whether to supply a digest to DigestSign — supplying a digest to Ed25519 is an error.