From d7f3f71c609a9473656f1ff8cc6dd4be79c557ab Mon Sep 17 00:00:00 2001 From: James Eilers Date: Thu, 7 May 2026 14:12:47 -0500 Subject: [PATCH 1/2] test: Add Wycheproof-based AES-CBC-PKCS5 tests Implements comprehensive AES-CBC-PKCS5 testing using official Wycheproof test vectors from Google. Tests 216 valid cryptographic operations across multiple key sizes (128/192/256-bit) with PKCS#7 padding. Fixes #187 Signed-off-by: James Eilers --- cryptoki/Cargo.toml | 2 +- cryptoki/tests/wycheproof.rs | 148 +++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/cryptoki/Cargo.toml b/cryptoki/Cargo.toml index 24c6bc24..c67669ee 100644 --- a/cryptoki/Cargo.toml +++ b/cryptoki/Cargo.toml @@ -23,7 +23,7 @@ secrecy = "0.10.3" hex = "0.4.3" serial_test = "3.2.0" testresult = "0.4.1" -wycheproof = { version = "0.6.0", features = ["aead"] } +wycheproof = { version = "0.6.0", features = ["aead", "rsa_sig", "cipher"] } [features] generate-bindings = ["cryptoki-sys/generate-bindings"] diff --git a/cryptoki/tests/wycheproof.rs b/cryptoki/tests/wycheproof.rs index 3e3169c2..6ba2f9eb 100644 --- a/cryptoki/tests/wycheproof.rs +++ b/cryptoki/tests/wycheproof.rs @@ -485,3 +485,151 @@ fn aes_gcm_message_wycheproof() -> TestResult { Ok(()) } + +/// Test AES-CBC with PKCS5 padding using Wycheproof test vectors +#[test] +#[serial] +fn aes_cbc_pkcs5_wycheproof() -> TestResult { + let (pkcs11, slot) = init_pins(); + let session = pkcs11.open_rw_session(slot)?; + session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?; + + // Load Wycheproof AES-CBC-PKCS5 test vectors + let test_set = wycheproof::cipher::TestSet::load(wycheproof::cipher::TestName::AesCbcPkcs5)?; + + let mut passed = 0; + let mut failed = 0; + let mut skipped = 0; + + for test_group in &test_set.test_groups { + let key_size = test_group.key_size; + + // Only test key sizes we support (128, 192, 256 bits) + if ![128, 192, 256].contains(&key_size) { + skipped += test_group.tests.len(); + continue; + } + + for test in &test_group.tests { + // Skip tests with nonce (IV) sizes other than 16 bytes (AES block size) + if test.nonce.len() != 16 { + skipped += 1; + continue; + } + + // Import the test key + let key_template = vec![ + Attribute::Class(cryptoki::object::ObjectClass::SECRET_KEY), + Attribute::KeyType(cryptoki::object::KeyType::AES), + Attribute::Token(false), + Attribute::Sensitive(false), + Attribute::Extractable(true), + Attribute::Encrypt(true), + Attribute::Decrypt(true), + Attribute::Value(test.key.to_vec()), + ]; + + let key = match session.create_object(&key_template) { + Ok(k) => k, + Err(e) => { + eprintln!("Test {}: Failed to create key: {:?}", test.tc_id, e); + failed += 1; + continue; + } + }; + + // Convert IV to fixed-size array for AesCbcPad + let mut iv = [0u8; 16]; + iv.copy_from_slice(&test.nonce[0..16]); + + // Test encryption + let mechanism = Mechanism::AesCbcPad(iv); + let encrypt_result = session.encrypt(&mechanism, key, &test.pt[..]); + + match (&test.result, encrypt_result) { + // Valid test should succeed + (wycheproof::TestResult::Valid, Ok(ciphertext)) => { + if ciphertext == &test.ct[..] { + println!( + "✓ Test {}: {:?} - Key: {}-bit, IV: {}, PT: {}, CT: {}", + test.tc_id, + test.result, + key_size, + test.nonce.len(), + test.pt.len(), + test.ct.len() + ); + passed += 1; + } else { + eprintln!( + "✗ Test {}: Encryption output mismatch (expected valid)", + test.tc_id + ); + eprintln!( + " Key size: {}, IV len: {}, PT len: {}, CT len: {}", + key_size, + test.nonce.len(), + test.pt.len(), + test.ct.len() + ); + eprintln!(" Expected: {}", hex::encode(&test.ct[..])); + eprintln!(" Got: {}", hex::encode(&ciphertext)); + failed += 1; + } + } + // Invalid/Acceptable tests may fail - this is good + (wycheproof::TestResult::Invalid | wycheproof::TestResult::Acceptable, Err(_)) => { + println!( + "✓ Test {}: {:?} (expected failure) - Key: {}-bit, IV: {}, PT: {}", + test.tc_id, test.result, key_size, test.nonce.len(), test.pt.len() + ); + passed += 1; + } + // Invalid test that succeeded - Note: HSM may not catch all invalid cases + (wycheproof::TestResult::Invalid, Ok(_)) => { + println!( + "✓ Test {}: {:?} (HSM accepted, which is OK) - Key: {}-bit, IV: {}, PT: {}", + test.tc_id, test.result, key_size, test.nonce.len(), test.pt.len() + ); + passed += 1; + } + // Valid test that failed - this shouldn't happen + (wycheproof::TestResult::Valid, Err(e)) => { + eprintln!("✗ Test {}: Valid test FAILED: {:?}", test.tc_id, e); + eprintln!( + " Key size: {}, IV len: {}, PT len: {}, CT len: {}", + key_size, + test.nonce.len(), + test.pt.len(), + test.ct.len() + ); + failed += 1; + } + // Acceptable tests can go either way + (wycheproof::TestResult::Acceptable, Ok(_)) => { + println!( + "✓ Test {}: {:?} (HSM accepted) - Key: {}-bit, IV: {}, PT: {}", + test.tc_id, test.result, key_size, test.nonce.len(), test.pt.len() + ); + passed += 1; + } + } + + // Clean up + let _ = session.destroy_object(key); + } + } + + println!( + "AES-CBC-PKCS5 Wycheproof results: {} passed, {} failed, {} skipped", + passed, failed, skipped + ); + + // The main requirement is that Valid tests pass + assert_eq!(failed, 0, "Some valid Wycheproof tests failed"); + + session.close()?; + pkcs11.finalize()?; + + Ok(()) +} From c45bc6d5b69a2f4f1ca2348a4d6630f3c7cb889c Mon Sep 17 00:00:00 2001 From: James Eilers Date: Fri, 8 May 2026 20:38:32 -0500 Subject: [PATCH 2/2] Fix: Reorder cleanup before assert to ensure resources are freed Move session.close() and pkcs11.finalize() before the assert so that if the assert panics, resources are guaranteed to be cleaned up properly. Signed-off-by: James Eilers --- cryptoki/tests/wycheproof.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/cryptoki/tests/wycheproof.rs b/cryptoki/tests/wycheproof.rs index 6ba2f9eb..aad939e1 100644 --- a/cryptoki/tests/wycheproof.rs +++ b/cryptoki/tests/wycheproof.rs @@ -549,7 +549,7 @@ fn aes_cbc_pkcs5_wycheproof() -> TestResult { match (&test.result, encrypt_result) { // Valid test should succeed (wycheproof::TestResult::Valid, Ok(ciphertext)) => { - if ciphertext == &test.ct[..] { + if ciphertext == test.ct[..] { println!( "✓ Test {}: {:?} - Key: {}-bit, IV: {}, PT: {}, CT: {}", test.tc_id, @@ -581,7 +581,11 @@ fn aes_cbc_pkcs5_wycheproof() -> TestResult { (wycheproof::TestResult::Invalid | wycheproof::TestResult::Acceptable, Err(_)) => { println!( "✓ Test {}: {:?} (expected failure) - Key: {}-bit, IV: {}, PT: {}", - test.tc_id, test.result, key_size, test.nonce.len(), test.pt.len() + test.tc_id, + test.result, + key_size, + test.nonce.len(), + test.pt.len() ); passed += 1; } @@ -589,7 +593,11 @@ fn aes_cbc_pkcs5_wycheproof() -> TestResult { (wycheproof::TestResult::Invalid, Ok(_)) => { println!( "✓ Test {}: {:?} (HSM accepted, which is OK) - Key: {}-bit, IV: {}, PT: {}", - test.tc_id, test.result, key_size, test.nonce.len(), test.pt.len() + test.tc_id, + test.result, + key_size, + test.nonce.len(), + test.pt.len() ); passed += 1; } @@ -609,7 +617,11 @@ fn aes_cbc_pkcs5_wycheproof() -> TestResult { (wycheproof::TestResult::Acceptable, Ok(_)) => { println!( "✓ Test {}: {:?} (HSM accepted) - Key: {}-bit, IV: {}, PT: {}", - test.tc_id, test.result, key_size, test.nonce.len(), test.pt.len() + test.tc_id, + test.result, + key_size, + test.nonce.len(), + test.pt.len() ); passed += 1; } @@ -625,11 +637,12 @@ fn aes_cbc_pkcs5_wycheproof() -> TestResult { passed, failed, skipped ); - // The main requirement is that Valid tests pass - assert_eq!(failed, 0, "Some valid Wycheproof tests failed"); - + // Always clean up resources before asserting, so if assert panics, cleanup still happened session.close()?; pkcs11.finalize()?; + // The main requirement is that Valid tests pass + assert_eq!(failed, 0, "Some valid Wycheproof tests failed"); + Ok(()) }