v0.5.2-pre.006-fix-010

This commit is contained in:
2026-08-11 12:03:35 +02:00
parent 1ee1d0297d
commit 56572cec40
67 changed files with 2484 additions and 539 deletions

View File

@@ -0,0 +1,90 @@
// file: ks-wallet-demo-scenarios/tests/password_lifecycle.rs
// version: 1
//! External ordered password lifecycle scenario.
const PASSWORD_A: &str = "ks-wallet-demo-password-A";
const PASSWORD_B: &str = "ks-wallet-demo-password-B";
const CHALLENGE_B: &[u8] = b"ks-wallet-demo-scenarios password-B signing proof";
const CHALLENGE_A_RESTORED: &[u8] = b"ks-wallet-demo-scenarios restored password-A signing proof";
fn make_directory_private(path: &std::path::Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700)).unwrap_or_else(
|error| panic!("wallet scenario directory permissions must be set: {error}"),
);
}
}
fn password(value: &str) -> ks_wallet::WalletPassword {
return ks_wallet::WalletPassword::new(value.to_owned())
.unwrap_or_else(|error| panic!("scenario password must be accepted: {error}"));
}
#[tokio::test]
async fn password_rotation_runs_a_to_b_rejects_a_then_restores_a() {
let directory = tempfile::tempdir()
.unwrap_or_else(|error| panic!("temporary scenario directory must exist: {error}"));
make_directory_private(directory.path());
let manager = ks_wallet::WalletManager::new(directory.path())
.unwrap_or_else(|error| panic!("wallet manager must be created: {error}"));
let alias = ks_wallet::WalletAlias::parse("password-lifecycle")
.unwrap_or_else(|error| panic!("scenario alias must be valid: {error}"));
let created = manager
.create(alias.clone(), password(PASSWORD_A))
.await
.unwrap_or_else(|error| panic!("scenario wallet creation must succeed: {error}"));
let initial_public_key = created.public_key();
created.lock();
let rotated_to_b = ks_wallet_demo_scenarios::rotate_wallet_password(
&manager,
&alias,
password(PASSWORD_A),
password(PASSWORD_B),
)
.await
.unwrap_or_else(|error| panic!("password A to B rotation must succeed: {error}"));
assert_eq!(rotated_to_b.identity.public_key, initial_public_key);
let rejected_a = ks_wallet_demo_scenarios::expect_wallet_password_rejected(
&manager,
&alias,
password(PASSWORD_A),
)
.await
.unwrap_or_else(|error| panic!("old password A must be rejected: {error}"));
assert_eq!(rejected_a.identity.public_key, initial_public_key);
assert_eq!(rejected_a.rejection_code, "wallet_native_authentication_failed");
let signed_with_b = ks_wallet_demo_scenarios::unlock_and_sign_wallet(
&manager,
&alias,
password(PASSWORD_B),
CHALLENGE_B,
)
.await
.unwrap_or_else(|error| panic!("password B must unlock and sign: {error}"));
assert_eq!(signed_with_b.identity.public_key, initial_public_key);
assert_eq!(signed_with_b.message_length, CHALLENGE_B.len());
assert!(!signed_with_b.signature.is_empty());
let restored_to_a = ks_wallet_demo_scenarios::rotate_wallet_password(
&manager,
&alias,
password(PASSWORD_B),
password(PASSWORD_A),
)
.await
.unwrap_or_else(|error| panic!("password B to A restoration must succeed: {error}"));
assert_eq!(restored_to_a.identity.public_key, initial_public_key);
let signed_with_restored_a = ks_wallet_demo_scenarios::unlock_and_sign_wallet(
&manager,
&alias,
password(PASSWORD_A),
CHALLENGE_A_RESTORED,
)
.await
.unwrap_or_else(|error| panic!("restored password A must unlock and sign: {error}"));
assert_eq!(signed_with_restored_a.identity.public_key, initial_public_key);
assert_eq!(signed_with_restored_a.message_length, CHALLENGE_A_RESTORED.len());
assert!(!signed_with_restored_a.signature.is_empty());
}