From 489e10dc4721c7a68ae5df8dbe7a58f3a1c70a55 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Wed, 21 Jan 2026 15:16:19 +0100 Subject: [PATCH] Implement 'sign[_final]_into()' These '_into()' variants can be used when the caller knows the expected buffer size, and has allocated a buffer for the signature themselves. It requires fewer calls into the underlying library and can save on heap allocations. Signed-off-by: arya dradjica --- cryptoki/src/session/signing_macing.rs | 155 ++++++++++++++++++++++--- cryptoki/tests/basic.rs | 35 ++++++ 2 files changed, 175 insertions(+), 15 deletions(-) diff --git a/cryptoki/src/session/signing_macing.rs b/cryptoki/src/session/signing_macing.rs index 0927c5c4..f4e80a04 100644 --- a/cryptoki/src/session/signing_macing.rs +++ b/cryptoki/src/session/signing_macing.rs @@ -3,6 +3,8 @@ //! Signing and authentication functions use crate::context::Function; +#[cfg(doc)] +use crate::error::RvError; use crate::error::{Result, Rv}; use crate::mechanism::Mechanism; use crate::object::ObjectHandle; @@ -10,22 +12,29 @@ use crate::session::Session; use cryptoki_sys::*; use std::convert::TryInto; +/// # Generating and Verifying Signatures +/// +/// Several functions are provided for signing data and verifying signatures. +/// This includes message authentication codes (MACs). The signed data can be +/// provided in one-shot and streaming modes. impl Session { - /// Sign data in single-part + /// Sign data in one-shot mode. + /// + /// `data` should be a byte sequence representing the input message. It will + /// be signed using the specified key, and the resulting signature will be + /// returned in a `Vec`. + /// + /// Use [`Self::sign_into()`] if (an upper bound for) the size of the + /// signature is known, to avoid the heap allocation of `Vec`. Use + /// [`Self::sign_init()`] etc. if the input data is being streamed (i.e. it + /// is not all immediately available). pub fn sign(&self, mechanism: &Mechanism, key: ObjectHandle, data: &[u8]) -> Result> { - let mut mechanism: CK_MECHANISM = mechanism.into(); let mut signature_len = 0; - unsafe { - Rv::from(get_pkcs11!(self.client(), C_SignInit)( - self.handle(), - &mut mechanism as CK_MECHANISM_PTR, - key.handle(), - )) - .into_result(Function::SignInit)?; - } + // Initialize the signing operation. + self.sign_init(mechanism, key)?; - // Get the output buffer length + // Get the output buffer length. unsafe { Rv::from(get_pkcs11!(self.client(), C_Sign)( self.handle(), @@ -37,9 +46,10 @@ impl Session { .into_result(Function::Sign)?; } + // Allocate the output buffer. let mut signature = vec![0; signature_len.try_into()?]; - //TODO: we should add a new error instead of those unwrap! + // Perform the actual signing. unsafe { Rv::from(get_pkcs11!(self.client(), C_Sign)( self.handle(), @@ -51,11 +61,68 @@ impl Session { .into_result(Function::Sign)?; } + // Limit the output buffer to the size of the generated signature. signature.truncate(signature_len.try_into()?); Ok(signature) } + /// Sign data into the given buffer. + /// + /// `data` should be a byte sequence representing the input message. It will + /// be signed using the specified key, and the resulting signature will be + /// written to the buffer `sig`. + /// + /// `sig` should be large enough to store the signature. The number of + /// filled bytes will be returned, such that `sig[0..size]` contains the + /// prepared signature. `sig[size..]` might be modified. + /// + /// Use [`Self::sign()`] if (an upper bound for) the size of the signature + /// is not known. Use [`Self::sign_init()`] etc. if the input data is being + /// streamed (i.e. it is not all immediately available). + /// + /// ## Errors + /// + /// Returns [`RvError::BufferTooSmall`] if the generated signature does not + /// fit in `sig`. `sig` might be modified. The size of the actual signature + /// is **not** returned. This method should only be used if the caller knows + /// an upper bound for the signature size. + pub fn sign_into( + &self, + mechanism: &Mechanism, + key: ObjectHandle, + data: &[u8], + sig: &mut [u8], + ) -> Result { + // The size of the signature buffer, into which 'C_Sign' will write the + // size of the generated signature. + let sig_buf_len = sig.len().try_into()?; + let mut sig_len = sig_buf_len; + + // Initialize the signing operation. + self.sign_init(mechanism, key)?; + + // Perform the actual signing. + unsafe { + Rv::from(get_pkcs11!(self.client(), C_Sign)( + self.handle(), + data.as_ptr() as *mut u8, + data.len().try_into()?, + sig.as_mut_ptr(), + &mut sig_len, + )) + .into_result(Function::Sign)?; + } + + assert!( + sig_len <= sig_buf_len, + "'C_Sign' succeeded but increased 'sig_len', possibly indicating out-of-bounds accesses" + ); + + // NOTE: As checked above, 'sig_len <= sig_buf_len <= usize::MAX'. + Ok(sig_len as usize) + } + /// Starts new multi-part signing operation pub fn sign_init(&self, mechanism: &Mechanism, key: ObjectHandle) -> Result<()> { let mut mechanism: CK_MECHANISM = mechanism.into(); @@ -87,12 +154,20 @@ impl Session { Ok(()) } - /// Finalizes ongoing multi-part signing operation, - /// returning the signature + /// Complete an ongoing streaming signing operation. + /// + /// This must be preceded by [`Self::sign_init()`] and zero or more calls + /// to [`Self::sign_update()`]. This method will terminate the multi-part + /// signing operation. The resulting signature will be returned in a `Vec`. + /// + /// Use [`Self::sign_final_into()`] if (an upper bound for) the size of + /// the signature is known, to avoid the heap allocation of `Vec`. Use + /// [`Self::sign()`] if the input data is entirely available in a single + /// buffer (i.e. does not have to be streamed). pub fn sign_final(&self) -> Result> { let mut signature_len = 0; - // Get the output buffer length + // Get the output buffer length. unsafe { Rv::from(get_pkcs11!(self.client(), C_SignFinal)( self.handle(), @@ -102,8 +177,10 @@ impl Session { .into_result(Function::SignFinal)?; } + // Allocate the output buffer. let mut signature = vec![0; signature_len.try_into()?]; + // Perform the actual signing. unsafe { Rv::from(get_pkcs11!(self.client(), C_SignFinal)( self.handle(), @@ -113,11 +190,59 @@ impl Session { .into_result(Function::SignFinal)?; } + // Limit the output buffer to the size of the generated signature. signature.truncate(signature_len.try_into()?); Ok(signature) } + /// Complete an ongoing multi-part signing operation, writing the signature + /// into the given buffer. + /// + /// This must be preceded by [`Self::sign_init()`] and zero or more calls + /// to [`Self::sign_update()`]. This method will terminate the multi-part + /// signing operation, and write the signature to the buffer `sig`. + /// + /// `sig` should be large enough to store the signature. The number of + /// filled bytes will be returned, such that `sig[0..size]` contains the + /// prepared signature. `sig[size..]` might be modified. + /// + /// Use [`Self::sign_final()`] if (an upper bound for) the size of the + /// signature is not known. Use [`Self::sign_into()`] if the input data + /// is entirely available in a single buffer (i.e. does not have to be + /// streamed). + /// + /// ## Errors + /// + /// Returns [`RvError::BufferTooSmall`] if the generated signature does not + /// fit in `sig`. `sig` might be modified. The size of the actual signature + /// is **not** returned. This method should only be used if the caller knows + /// an upper bound for the signature size. + pub fn sign_final_into(&self, sig: &mut [u8]) -> Result { + // The size of the signature buffer, into which 'C_SignFinal' will write + // the size of the generated signature. + let sig_buf_len = sig.len().try_into()?; + let mut sig_len = sig_buf_len; + + // Perform the underlying finalization. + unsafe { + Rv::from(get_pkcs11!(self.client(), C_SignFinal)( + self.handle(), + sig.as_mut_ptr(), + &mut sig_len, + )) + .into_result(Function::SignFinal)?; + } + + assert!( + sig_len <= sig_buf_len, + "'C_SignFinal' succeeded but increased 'sig_len', possibly indicating out-of-bounds accesses" + ); + + // NOTE: As checked above, 'sig_len <= sig_buf_len <= usize::MAX'. + Ok(sig_len as usize) + } + /// Verify data in single-part pub fn verify( &self, diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index 15cf938e..1d013cb3 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -75,6 +75,13 @@ fn sign_verify() -> TestResult { // verify the signature session.verify(&Mechanism::RsaPkcs, public, &data, &signature)?; + // sign into a user-provided buffer with it + let mut signature = [0u8; 2048 / 8]; + let sig_len = session.sign_into(&Mechanism::RsaPkcs, private, &data, &mut signature)?; + + // verify the signature + session.verify(&Mechanism::RsaPkcs, public, &data, &signature[..sig_len])?; + // delete keys session.destroy_object(public)?; session.destroy_object(private)?; @@ -122,6 +129,12 @@ fn sign_verify_eddsa() -> TestResult { session.verify(&Mechanism::Eddsa(params), public, &data, &signature)?; + let mut signature = [0u8; 64]; + let sig_len = session.sign_into(&Mechanism::Eddsa(params), private, &data, &mut signature)?; + assert_eq!(sig_len, 64); + + session.verify(&Mechanism::Eddsa(params), public, &data, &signature)?; + session.destroy_object(public)?; session.destroy_object(private)?; @@ -177,6 +190,13 @@ fn sign_verify_eddsa_with_ed25519_schemes() -> TestResult { let signature = session.sign(&Mechanism::Eddsa(params), private, &data)?; session.verify(&Mechanism::Eddsa(params), public, &data, &signature)?; + + let mut signature = [0u8; 64]; + let sig_len = + session.sign_into(&Mechanism::Eddsa(params), private, &data, &mut signature)?; + assert_eq!(sig_len, 64); + + session.verify(&Mechanism::Eddsa(params), public, &data, &signature)?; } session.destroy_object(public)?; @@ -285,6 +305,21 @@ fn sign_verify_multipart() -> TestResult { } session.verify_final(&signature)?; + // Sign data into a user-provided buffer + session.sign_init(&Mechanism::Sha256RsaPkcs, priv_key)?; + for part in data.chunks(3) { + session.sign_update(part)?; + } + let mut signature = [0u8; 2048 / 8]; + let sig_len = session.sign_final_into(&mut signature)?; + + // Verify signature from the user-provided buffer + session.verify_init(&Mechanism::Sha256RsaPkcs, pub_key)?; + for part in data.chunks(3) { + session.verify_update(part)?; + } + session.verify_final(&signature[..sig_len])?; + // Delete keys session.destroy_object(pub_key)?; session.destroy_object(priv_key)?;