From 7e0d44596ca35026db7e3e997b9360ab0e222710 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 5 May 2025 21:11:57 +0200 Subject: [PATCH 01/12] tests: Reset pin to the original value Signed-off-by: Jakub Jelen --- cryptoki/tests/basic.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index 6f54db3b..e4243405 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -1157,6 +1157,9 @@ fn set_pin_test() -> TestResult { session.logout()?; session.login(UserType::User, Some(&new_user_pin))?; + // return it back + session.set_pin(&new_user_pin, &user_pin)?; + session.logout()?; Ok(()) } From c6f9048cb63ada6ef3eab55fe04208c27cbf42a6 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 5 May 2025 21:11:27 +0200 Subject: [PATCH 02/12] tests: Allow more configuration for test token Signed-off-by: Jakub Jelen --- cryptoki/tests/basic.rs | 2 +- cryptoki/tests/common.rs | 50 +++++++++++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index e4243405..d08a55c1 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -1200,7 +1200,7 @@ fn get_attribute_info_test() -> TestResult { session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?; let pub_attribs = vec![AttributeType::PublicExponent, AttributeType::Modulus]; - let mut priv_attribs = [ + let priv_attribs = [ AttributeType::PublicExponent, AttributeType::Modulus, AttributeType::PrivateExponent, diff --git a/cryptoki/tests/common.rs b/cryptoki/tests/common.rs index 65be5c60..d8714b6d 100644 --- a/cryptoki/tests/common.rs +++ b/cryptoki/tests/common.rs @@ -11,6 +11,17 @@ pub static USER_PIN: &str = "fedcba"; // The default SO pin pub static SO_PIN: &str = "abcdef"; +fn get_token_label() -> Option { + env::var("TEST_TOKEN_LABEL").ok() +} + +fn skip_token_init() -> bool { + match env::var("TEST_SKIP_TOKEN_INIT") { + Ok(s) => s == "1", + Err(_) => false, + } +} + fn get_pkcs11_path() -> String { env::var("TEST_PKCS11_MODULE") .unwrap_or_else(|_| "/usr/local/lib/softhsm/libsofthsm2.so".to_string()) @@ -24,24 +35,41 @@ pub fn get_pkcs11() -> Pkcs11 { Pkcs11::new(get_pkcs11_path()).unwrap() } +fn get_slot(pkcs11: &Pkcs11) -> Slot { + // find a slot, get the first one or one with name specified in the environment variable + let mut slots = pkcs11.get_slots_with_token().unwrap(); + match get_token_label() { + None => slots.remove(0), + Some(label) => { + for s in slots { + let ti = pkcs11.get_token_info(s).unwrap(); + if ti.label() == label { + return s; + } + } + panic!("No token with Token Label `{label}` found"); + } + } +} + pub fn init_pins() -> (Pkcs11, Slot) { let pkcs11 = get_pkcs11(); // initialize the library pkcs11.initialize(CInitializeArgs::OsThreads).unwrap(); - // find a slot, get the first one - let slot = pkcs11.get_slots_with_token().unwrap().remove(0); - - let so_pin = AuthPin::new(SO_PIN.into()); - pkcs11.init_token(slot, &so_pin, "Test Token").unwrap(); + let slot = get_slot(&pkcs11); - { - // open a session - let session = pkcs11.open_rw_session(slot).unwrap(); - // log in the session - session.login(UserType::So, Some(&so_pin)).unwrap(); - session.init_pin(&AuthPin::new(USER_PIN.into())).unwrap(); + if !skip_token_init() { + let so_pin = AuthPin::new(SO_PIN.into()); + let _ = pkcs11.init_token(slot, &so_pin, "Test Token"); + { + // open a session + let session = pkcs11.open_rw_session(slot).unwrap(); + // log in the session + session.login(UserType::So, Some(&so_pin)).unwrap(); + session.init_pin(&AuthPin::new(USER_PIN.into())).unwrap(); + } } (pkcs11, slot) From e45e70d929feed68f57df80833fb415abade2f86 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 26 May 2025 17:06:57 +0200 Subject: [PATCH 03/12] tests: Softokn reports different error for UniqueId Signed-off-by: Jakub Jelen --- cryptoki/tests/basic.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index d08a55c1..0217c2dc 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -3700,13 +3700,7 @@ fn unique_id() -> TestResult { ]; let res = session.create_object(&key_template); assert!(res.is_err()); - assert!(matches!( - res, - Err(Error::Pkcs11( - RvError::AttributeTypeInvalid, - Function::CreateObject - )) - )); + assert!(matches!(res, Err(Error::Pkcs11(_, Function::CreateObject)))); let generate_template = vec![ Attribute::Token(true), From 95cd2aa642d3a22109b82112034f8dd5c37d90bb Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 26 May 2025 17:18:48 +0200 Subject: [PATCH 04/12] encryption/decryption: Workaround tokens finalizing operation when there is nothing to return The NSS softokn finalizes the multipart operation even though the NULL return argument was passed. Signed-off-by: Jakub Jelen --- cryptoki/src/session/decryption.rs | 6 ++++++ cryptoki/src/session/encryption.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/cryptoki/src/session/decryption.rs b/cryptoki/src/session/decryption.rs index 6d4358df..5fb37423 100644 --- a/cryptoki/src/session/decryption.rs +++ b/cryptoki/src/session/decryption.rs @@ -125,6 +125,12 @@ impl Session { .into_result(Function::DecryptFinal)?; } + // Some pkcs11 modules might finalize the operation when there + // no more output even if we pass in NULL. + if data_len == 0 { + return Ok(Vec::new()); + } + let mut data = vec![0; data_len.try_into()?]; unsafe { diff --git a/cryptoki/src/session/encryption.rs b/cryptoki/src/session/encryption.rs index e47cfefd..cc188ee0 100644 --- a/cryptoki/src/session/encryption.rs +++ b/cryptoki/src/session/encryption.rs @@ -124,6 +124,12 @@ impl Session { .into_result(Function::EncryptFinal)?; } + // Some pkcs11 modules might finalize the operation when there + // no more output even if we pass in NULL. + if encrypted_data_len == 0 { + return Ok(Vec::new()); + } + let mut encrypted_data = vec![0; encrypted_data_len.try_into()?]; unsafe { From 6ad78258507108993b51a48e8dbbe227eb3cfa4e Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 26 May 2025 17:20:50 +0200 Subject: [PATCH 05/12] tests: Delete keys to avoid search mismatch on rerun Signed-off-by: Jakub Jelen --- cryptoki/tests/basic.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index 0217c2dc..13a3ab28 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -2105,6 +2105,9 @@ fn generate_generic_secret_key() -> TestResult { let attributes_result = session.find_objects(&[key_label])?.remove(0); assert_eq!(key, attributes_result); + // Delete keys + session.destroy_object(key)?; + Ok(()) } @@ -2161,6 +2164,9 @@ fn ekdf_aes_cbc_encrypt_data() -> TestResult { session.find_objects(&[derived_key_label])?.remove(0) ); + // delete keys + session.destroy_object(master_key)?; + session.destroy_object(derived_key)?; Ok(()) } From 5a732fe6050e6cad559ba50911afdea71e469c8a Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 26 May 2025 17:30:13 +0200 Subject: [PATCH 06/12] tests: Use smaller symmetric key to work with Softokn Signed-off-by: Jakub Jelen --- cryptoki/tests/basic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index 13a3ab28..e824c6fa 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -2097,7 +2097,7 @@ fn generate_generic_secret_key() -> TestResult { Attribute::Token(true), Attribute::Sensitive(true), Attribute::Private(true), - Attribute::ValueLen(512.into()), + Attribute::ValueLen(256.into()), key_label.clone(), ]; From 27fce37e6a2d007b4a65f95b95e5dea530055368 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 26 May 2025 18:01:15 +0200 Subject: [PATCH 07/12] Softokn does not have SO user and PIN Signed-off-by: Jakub Jelen --- cryptoki/tests/basic.rs | 26 ++++++++++++++++---------- cryptoki/tests/common.rs | 4 ++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index e824c6fa..5fb06ab7 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 mod common; -use crate::common::{get_pkcs11, is_softhsm, SO_PIN, USER_PIN}; +use crate::common::{get_pkcs11, is_softhsm, is_softokn, SO_PIN, USER_PIN}; use common::init_pins; use cryptoki::context::Function; use cryptoki::error::{Error, RvError}; @@ -1086,7 +1086,11 @@ fn get_session_info_test() -> TestResult { if let Err(cryptoki::error::Error::Pkcs11(rv_error, _)) = session.login(UserType::So, Some(&AuthPin::new(SO_PIN.into()))) { - assert_eq!(rv_error, RvError::SessionReadOnlyExists) + if is_softokn() { + assert_eq!(rv_error, RvError::UserTypeInvalid) + } else { + assert_eq!(rv_error, RvError::SessionReadOnlyExists) + } } else { panic!("Should error when attempting to log in as CKU_SO on a read-only session"); } @@ -1107,14 +1111,16 @@ fn get_session_info_test() -> TestResult { assert_eq!(session_info.slot_id(), slot); assert!(matches!(session_info.session_state(), SessionState::RwUser,)); session.logout()?; - session.login(UserType::So, Some(&AuthPin::new(SO_PIN.into())))?; - let session_info = session.get_session_info()?; - assert!(session_info.read_write()); - assert_eq!(session_info.slot_id(), slot); - assert!(matches!( - session_info.session_state(), - SessionState::RwSecurityOfficer - )); + if !is_softokn() { + session.login(UserType::So, Some(&AuthPin::new(SO_PIN.into())))?; + let session_info = session.get_session_info()?; + assert!(session_info.read_write()); + assert_eq!(session_info.slot_id(), slot); + assert!(matches!( + session_info.session_state(), + SessionState::RwSecurityOfficer + )); + } Ok(()) } diff --git a/cryptoki/tests/common.rs b/cryptoki/tests/common.rs index d8714b6d..3d114a59 100644 --- a/cryptoki/tests/common.rs +++ b/cryptoki/tests/common.rs @@ -31,6 +31,10 @@ pub fn is_softhsm() -> bool { get_pkcs11_path().contains("softhsm") } +pub fn is_softokn() -> bool { + get_pkcs11_path().contains("softokn") +} + pub fn get_pkcs11() -> Pkcs11 { Pkcs11::new(get_pkcs11_path()).unwrap() } From cb6eba15d474a78467b70d87da85b4d93b5b1c2d Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 26 May 2025 18:02:54 +0200 Subject: [PATCH 08/12] tests: Explicitly set attributes that are checked for KBKDF Signed-off-by: Jakub Jelen --- cryptoki/tests/basic.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index 5fb06ab7..2cc8c067 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -2517,6 +2517,8 @@ fn kbkdf_additional_keys_counter_mode() -> TestResult { Attribute::ValueLen((AES128_BLOCK_SIZE as u64).into()), Attribute::Sign(true), Attribute::Verify(true), + Attribute::Encrypt(false), + Attribute::Decrypt(false), ], vec![ Attribute::Token(true), @@ -2525,6 +2527,8 @@ fn kbkdf_additional_keys_counter_mode() -> TestResult { Attribute::KeyType(KeyType::GENERIC_SECRET), Attribute::ValueLen(1.into()), Attribute::Derive(true), + Attribute::Encrypt(false), + Attribute::Decrypt(false), ], ]; @@ -2601,6 +2605,8 @@ fn kbkdf_additional_keys_counter_mode() -> TestResult { Attribute::Sign(true), Attribute::Verify(true), Attribute::Derive(false), + Attribute::Encrypt(false), + Attribute::Decrypt(false), ], vec![ Attribute::Class(ObjectClass::SECRET_KEY), @@ -2611,6 +2617,8 @@ fn kbkdf_additional_keys_counter_mode() -> TestResult { Attribute::Sign(false), Attribute::Verify(false), Attribute::Derive(true), + Attribute::Encrypt(false), + Attribute::Decrypt(false), ], ]; @@ -2618,7 +2626,10 @@ fn kbkdf_additional_keys_counter_mode() -> TestResult { let have_attributes = session.get_attributes(*key, &attributes_to_check)?; for (value_wanted, value_have) in wanted_attributes.iter().zip(have_attributes.iter()) { - assert_eq!(value_wanted, value_have); + assert_eq!( + value_wanted, value_have, + "The generated key {key} has unexpected attribute value" + ); } } @@ -2673,6 +2684,8 @@ fn kbkdf_additional_keys_feedback_mode() -> TestResult { Attribute::ValueLen((AES128_BLOCK_SIZE as u64).into()), Attribute::Sign(true), Attribute::Verify(true), + Attribute::Encrypt(false), + Attribute::Decrypt(false), ], vec![ Attribute::Token(true), @@ -2681,6 +2694,8 @@ fn kbkdf_additional_keys_feedback_mode() -> TestResult { Attribute::KeyType(KeyType::GENERIC_SECRET), Attribute::ValueLen(1.into()), Attribute::Derive(true), + Attribute::Encrypt(false), + Attribute::Decrypt(false), ], ]; From db9a7dda27b8b3c86a129cfa0a5575b40b11f36f Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 27 May 2025 13:16:41 +0200 Subject: [PATCH 09/12] tests: Adjust inuque_id test for softokn Signed-off-by: Jakub Jelen --- cryptoki/tests/basic.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index 2cc8c067..af5de6f8 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -3740,8 +3740,9 @@ fn unique_id() -> TestResult { // we can get the UniqueId attribute let attrs = session.get_attributes(key, &[AttributeType::UniqueId])?; - if is_softhsm() { + if is_softhsm() || is_softokn() { // SoftHSM does not support this attribute at all + // Softkn does not expose this attribute assert_eq!(attrs.len(), 0); } else { assert!(matches!(attrs.first(), Some(Attribute::UniqueId(_)))); @@ -3751,8 +3752,9 @@ fn unique_id() -> TestResult { let update_template = vec![Attribute::UniqueId(vec![0x01, 0x02, 0x03])]; let res = session.update_attributes(key, &update_template); assert!(res.is_err()); - if is_softhsm() { + if is_softhsm() || is_softokn() { // SoftHSM does not support this attribute at all + // Softtokn supports it internally, but does not expose it assert!(matches!( res, Err(Error::Pkcs11( From dd25e7ab539a8f8b6f266ec16c8437422bb1e9a5 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 26 May 2025 18:45:23 +0200 Subject: [PATCH 10/12] tests: Softokn (correctly) requires PublicExponent when generating a key Signed-off-by: Jakub Jelen --- cryptoki/tests/basic.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index af5de6f8..75fd4100 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -1988,7 +1988,9 @@ fn rsa_pkcs_oaep_empty() -> TestResult { let session = pkcs11.open_rw_session(slot)?; session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?; + let public_exponent: Vec = vec![0x01, 0x00, 0x01]; let pub_key_template = [ + Attribute::PublicExponent(public_exponent), Attribute::ModulusBits(2048.into()), Attribute::Encrypt(true), ]; @@ -2027,7 +2029,9 @@ fn rsa_pkcs_oaep_with_data() -> TestResult { let session = pkcs11.open_rw_session(slot)?; session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?; + let public_exponent: Vec = vec![0x01, 0x00, 0x01]; let pub_key_template = [ + Attribute::PublicExponent(public_exponent), Attribute::ModulusBits(2048.into()), Attribute::Encrypt(true), ]; From e55706f9fe234a80670ae135c209bbda4bd47d6f Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 27 May 2025 13:06:09 +0200 Subject: [PATCH 11/12] tests: Adjust copy test for Softokn Signed-off-by: Jakub Jelen --- cryptoki/tests/basic.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index 75fd4100..f3f50b13 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -1501,10 +1501,15 @@ fn session_copy_object() -> TestResult { let copy = rw_session.copy_object(object, ©_template)?; rw_session.destroy_object(copy)?; - // try the copy with the insecure template. It should fail. Returning CKR_OK is considered a failure. - rw_session - .copy_object(object, &insecure_copy_template) - .unwrap_err(); + let res = rw_session.copy_object(object, &insecure_copy_template); + if is_softokn() { + // Softokn considers all keys nonextractable + let copy = res?; + rw_session.destroy_object(copy)?; + } else { + // try the copy with the insecure template. It should fail. Returning CKR_OK is considered a failure. + res.unwrap_err(); + } // delete keys rw_session.destroy_object(object)?; From 9a82d86231fe0bb2728e8611ccd79870354e4d3a Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 5 May 2025 19:18:30 +0200 Subject: [PATCH 12/12] Run CI against NSS softokn Signed-off-by: Jakub Jelen --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98dee93d..76f52b74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,32 @@ jobs: RUST_BACKTRACE=1 cargo test + tests-softokn: + name: Run tests against Softokn + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - name: Install dependencies + run: sudo apt-get -yq install libnss3 libnss3-tools libnss3-dev + - name: Test script + env: + NSS_LIB_PARAMS: configDir=/tmp/nssdb/ + TEST_PKCS11_MODULE: /usr/lib/x86_64-linux-gnu/libsoftokn3.so + TEST_TOKEN_LABEL: NSS Certificate DB + TEST_SKIP_TOKEN_INIT: 1 + run: | + mkdir /tmp/nssdb/ && + echo "fedcba" > /tmp/nssdb/pinfile && + certutil -N -d /tmp/nssdb/ -f /tmp/nssdb/pinfile && + RUST_BACKTRACE=1 cargo build && + RUST_BACKTRACE=1 cargo build --all-features && + RUST_BACKTRACE=1 cargo test + + build-msrv: name: MSRV - Execute CI script runs-on: ubuntu-latest