From f3c06ff354001a388dd3eb2b7d309747b9e2107e Mon Sep 17 00:00:00 2001 From: Sietse Ringers Date: Thu, 20 Aug 2026 09:55:29 +0200 Subject: [PATCH 1/2] Add Mechanism::AesCtr Signed-off-by: Sietse Ringers --- cryptoki/src/mechanism/aes_ctr.rs | 45 +++++++++++++++++++++++++++++++ cryptoki/src/mechanism/mod.rs | 10 +++++++ cryptoki/tests/basic.rs | 40 +++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 cryptoki/src/mechanism/aes_ctr.rs diff --git a/cryptoki/src/mechanism/aes_ctr.rs b/cryptoki/src/mechanism/aes_ctr.rs new file mode 100644 index 00000000..a7d0d32d --- /dev/null +++ b/cryptoki/src/mechanism/aes_ctr.rs @@ -0,0 +1,45 @@ +// Copyright 2026 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +//! AES-CTR mechanism types + +use crate::error::Error; +use cryptoki_sys::*; + +/// Parameters for AES-CTR. +/// +/// This structure wraps a `CK_AES_CTR_PARAMS` structure. +#[derive(Debug, Clone, Copy)] +#[repr(transparent)] +pub struct AesCtrParams { + inner: CK_AES_CTR_PARAMS, +} + +impl AesCtrParams { + /// Construct AES-CTR parameters. + /// + /// # Arguments + /// + /// `counter_bits` - The number of least significant bits of `block` that + /// make up the counter, i.e. that are incremented for every block that is + /// processed. Must be between 1 and 128. + /// + /// `block` - The initial value of the counter block: the nonce, followed by + /// the initial value of the counter in the `counter_bits` least significant + /// bits. + /// + /// # Errors + /// This function returns [`Error::InvalidValue`] if `counter_bits` is zero + /// or larger than the AES block size of 128 bits. + pub fn new(counter_bits: u8, block: [u8; 16]) -> Result { + if counter_bits == 0 || counter_bits > 128 { + return Err(Error::InvalidValue); + } + + Ok(AesCtrParams { + inner: CK_AES_CTR_PARAMS { + ulCounterBits: counter_bits.into(), + cb: block, + }, + }) + } +} diff --git a/cryptoki/src/mechanism/mod.rs b/cryptoki/src/mechanism/mod.rs index d3b7b5b3..5db0e73b 100644 --- a/cryptoki/src/mechanism/mod.rs +++ b/cryptoki/src/mechanism/mod.rs @@ -3,6 +3,7 @@ //! Data types for mechanisms pub mod aead; +pub mod aes_ctr; pub mod dsa; pub mod eddsa; pub mod ekdf; @@ -1053,6 +1054,13 @@ pub enum Mechanism<'a> { /// the plaintext to be recovered from the ciphertext. Therefore no length /// should be provided when unwrapping keys with this mechanism. AesCbcPad([u8; 16]), + /// AES-CTR mechanism + /// + /// The parameter to this mechanism is the counter block, together with the + /// number of bits of it that make up the counter. + /// + /// The plaintext may be any size; the ciphertext has the same length. + AesCtr(aes_ctr::AesCtrParams), /// AES in ECB mode AesEcb, /// AES key wrap @@ -1336,6 +1344,7 @@ impl Mechanism<'_> { Mechanism::AesEcb => MechanismType::AES_ECB, Mechanism::AesCbc(_) => MechanismType::AES_CBC, Mechanism::AesCbcPad(_) => MechanismType::AES_CBC_PAD, + Mechanism::AesCtr(_) => MechanismType::AES_CTR, Mechanism::AesKeyWrap => MechanismType::AES_KEY_WRAP, Mechanism::AesKeyWrapPad => MechanismType::AES_KEY_WRAP_PAD, Mechanism::AesGcm(_) => MechanismType::AES_GCM, @@ -1461,6 +1470,7 @@ impl From<&Mechanism<'_>> for CK_MECHANISM { Mechanism::AesCbc(params) | Mechanism::AesCbcPad(params) => { make_mechanism(mechanism, params) } + Mechanism::AesCtr(params) => make_mechanism(mechanism, params), Mechanism::AesCbcEncryptData(params) => make_mechanism(mechanism, params), Mechanism::DesCbc(params) | Mechanism::Des3Cbc(params) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index 553fb965..0aa246b3 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -27,6 +27,7 @@ use std::collections::HashMap; use std::num::NonZeroUsize; use std::thread; +use cryptoki::mechanism::aes_ctr::AesCtrParams; use cryptoki::mechanism::ekdf::AesCbcDeriveParams; use testresult::TestResult; @@ -2332,6 +2333,45 @@ fn aes_cbc_pad_encrypt() -> TestResult { Ok(()) } +#[test] +#[serial] +fn aes_ctr_encrypt() -> TestResult { + // Encrypt two blocks of zeros with AES-128-CTR, with a zero key and a zero + // counter block + let key = vec![0; 16]; + let plain = [0; 32]; + let expected_cipher = [ + 0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b, 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34, 0x2b, + 0x2e, 0x58, 0xe2, 0xfc, 0xce, 0xfa, 0x7e, 0x30, 0x61, 0x36, 0x7f, 0x1d, 0x57, 0xa4, 0xe7, + 0x45, 0x5a, + ]; + + let (pkcs11, slot) = init_pins(); + let session = pkcs11.open_rw_session(slot)?; + session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?; + + let template = [ + Attribute::Class(ObjectClass::SECRET_KEY), + Attribute::KeyType(KeyType::AES), + Attribute::Value(key), + Attribute::Encrypt(true), + Attribute::Decrypt(true), + ]; + let key_handle = session.create_object(&template)?; + let mechanism = Mechanism::AesCtr(AesCtrParams::new(128, [0; 16])?); + let cipher = session.encrypt(&mechanism, key_handle, &plain)?; + assert_eq!(expected_cipher[..], cipher[..]); + + // In CTR mode decryption is the same operation as encryption. + let decrypted = session.decrypt(&mechanism, key_handle, &cipher)?; + assert_eq!(plain[..], decrypted[..]); + + session.close()?; + pkcs11.finalize()?; + + Ok(()) +} + #[test] #[serial] fn update_attributes_key() -> TestResult { From 116abfe1c7165041ad94dbd63a67febf6a3335b2 Mon Sep 17 00:00:00 2001 From: Sietse Ringers Date: Thu, 20 Aug 2026 14:22:42 +0200 Subject: [PATCH 2/2] Update cryptoki/src/mechanism/aes_ctr.rs Add empty line after header in rustdoc Co-authored-by: Wiktor Kwapisiewicz Signed-off-by: Sietse Ringers --- cryptoki/src/mechanism/aes_ctr.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cryptoki/src/mechanism/aes_ctr.rs b/cryptoki/src/mechanism/aes_ctr.rs index a7d0d32d..44495943 100644 --- a/cryptoki/src/mechanism/aes_ctr.rs +++ b/cryptoki/src/mechanism/aes_ctr.rs @@ -28,6 +28,7 @@ impl AesCtrParams { /// bits. /// /// # Errors + /// /// This function returns [`Error::InvalidValue`] if `counter_bits` is zero /// or larger than the AES block size of 128 bits. pub fn new(counter_bits: u8, block: [u8; 16]) -> Result {