Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cryptoki/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
161 changes: 161 additions & 0 deletions cryptoki/tests/wycheproof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,3 +485,164 @@ 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(_)) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that actually happen with some of the test cases? Maybe it would be safe to double-check those and filter-out on invalid cases we do not support?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think most of the Invalid tasks have BadPadding flag. which can be used to filter it more precisely.

https://github.com/randombit/wycheproof-rs/blob/master/src/data/aes_cbc_pkcs5_test.json#L424

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(_)) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move the | condition on Acceptable above and move it here like:

// Acceptable tests can go either way
(wycheproof::TestResult::Acceptable, Ok(_) | Err(_)) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going through the source file for this input, I do not see any tests marked Acceptable:

https://github.com/randombit/wycheproof-rs/blob/master/src/data/aes_cbc_pkcs5_test.json

Could we prune the branches that are not executed?

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
);

// 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(())
}
Loading