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 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 { diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index 6f54db3b..f3f50b13 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(()) } @@ -1157,6 +1163,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(()) } @@ -1197,7 +1206,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, @@ -1492,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)?; @@ -1979,7 +1993,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), ]; @@ -2018,7 +2034,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), ]; @@ -2094,7 +2112,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(), ]; @@ -2102,6 +2120,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(()) } @@ -2158,6 +2179,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(()) } @@ -2502,6 +2526,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), @@ -2510,6 +2536,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), ], ]; @@ -2586,6 +2614,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), @@ -2596,6 +2626,8 @@ fn kbkdf_additional_keys_counter_mode() -> TestResult { Attribute::Sign(false), Attribute::Verify(false), Attribute::Derive(true), + Attribute::Encrypt(false), + Attribute::Decrypt(false), ], ]; @@ -2603,7 +2635,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" + ); } } @@ -2658,6 +2693,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), @@ -2666,6 +2703,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), ], ]; @@ -3697,13 +3736,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), @@ -3716,8 +3749,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(_)))); @@ -3727,8 +3761,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( diff --git a/cryptoki/tests/common.rs b/cryptoki/tests/common.rs index 65be5c60..3d114a59 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()) @@ -20,28 +31,49 @@ 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() } +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)