Files
khadhroony-bot3/ks-wallet/tests/native_password.rs
2026-08-10 16:43:19 +02:00

174 lines
7.7 KiB
Rust

// file: ks-wallet/tests/native_password.rs
// version: 1
//! External password-protected native wallet lifecycle tests.
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 fixture directory permissions must be set: {error}"),
);
}
}
fn make_file_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(0o600))
.unwrap_or_else(|error| panic!("wallet fixture 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!("test password must be accepted: {error}"));
}
#[tokio::test]
async fn native_wallet_password_lifecycle_preserves_keypair_and_supports_external_open() {
let directory = tempfile::tempdir()
.unwrap_or_else(|error| panic!("temporary directory must exist: {error}"));
make_directory_private(directory.path());
let manager = ks_wallet::WalletManager::new(directory.path())
.unwrap_or_else(|error| panic!("manager must be created: {error}"));
let alias = ks_wallet::WalletAlias::parse("protected")
.unwrap_or_else(|error| panic!("alias must be valid: {error}"));
let created = manager
.create(alias.clone(), password("initial-password"))
.await
.unwrap_or_else(|error| panic!("native wallet creation must succeed: {error}"));
let public_key = created.public_key();
let signature = created
.sign_message(b"ks-wallet password lifecycle")
.unwrap_or_else(|error| panic!("unlocked wallet must sign: {error}"));
assert!(!signature.is_empty());
created.lock();
let handle = manager
.lookup(&alias)
.await
.unwrap_or_else(|error| panic!("lookup must succeed: {error}"))
.unwrap_or_else(|| panic!("created wallet must be discoverable"));
assert_eq!(handle.public_key(), public_key);
let external_directory = tempfile::tempdir()
.unwrap_or_else(|error| panic!("external directory must exist: {error}"));
let external_path = external_directory.path().join("protected.kswallet");
std::fs::copy(directory.path().join("protected.kswallet"), &external_path)
.unwrap_or_else(|error| panic!("external fixture copy must succeed: {error}"));
make_file_private(&external_path);
let external_handle = manager
.inspect_file(&external_path)
.await
.unwrap_or_else(|error| panic!("external inspection must succeed: {error}"));
let external = manager
.unlock_file(&external_handle, password("initial-password"))
.await
.unwrap_or_else(|error| panic!("external wallet unlock must succeed: {error}"));
assert_eq!(external.public_key(), public_key);
external.lock();
let changed = manager
.change_password(&alias, password("initial-password"), password("replacement-password"))
.await
.unwrap_or_else(|error| panic!("password change must succeed: {error}"));
assert_eq!(changed.public_key(), public_key);
let old_password_error = manager
.unlock(&alias, password("initial-password"))
.await
.err()
.unwrap_or_else(|| panic!("old password must no longer unlock the wallet"));
assert_eq!(old_password_error.code(), "wallet_native_authentication_failed");
assert!(!old_password_error.to_string().contains("initial-password"));
let reopened = manager
.unlock(&alias, password("replacement-password"))
.await
.unwrap_or_else(|error| panic!("replacement password must unlock: {error}"));
assert_eq!(reopened.public_key(), public_key);
reopened.lock();
let bytes = std::fs::read(directory.path().join("protected.kswallet"))
.unwrap_or_else(|error| panic!("native wallet must be readable for canary check: {error}"));
assert!(
!bytes
.windows("initial-password".len())
.any(|window| return window == b"initial-password")
);
assert!(
!bytes
.windows("replacement-password".len())
.any(|window| return window == b"replacement-password")
);
}
#[tokio::test]
async fn native_creation_is_private_and_refuses_overwrite() {
let directory = tempfile::tempdir()
.unwrap_or_else(|error| panic!("temporary directory must exist: {error}"));
make_directory_private(directory.path());
let manager = ks_wallet::WalletManager::new(directory.path())
.unwrap_or_else(|error| panic!("manager must be created: {error}"));
let alias = ks_wallet::WalletAlias::parse("no-overwrite")
.unwrap_or_else(|error| panic!("alias must be valid: {error}"));
let created = manager
.create(alias.clone(), password("first-password"))
.await
.unwrap_or_else(|error| panic!("native wallet creation must succeed: {error}"));
let public_key = created.public_key();
created.lock();
let path = directory.path().join("no-overwrite.kswallet");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
let metadata = std::fs::metadata(&path)
.unwrap_or_else(|error| panic!("native wallet metadata must be readable: {error}"));
assert_eq!(metadata.permissions().mode() & 0o777, 0o600);
}
let error = manager
.create(alias.clone(), password("second-password"))
.await
.err()
.unwrap_or_else(|| panic!("duplicate native wallet creation must fail"));
assert_eq!(error.code(), "wallet_native_already_exists");
let handle = manager
.lookup(&alias)
.await
.unwrap_or_else(|error| panic!("existing native wallet lookup must succeed: {error}"))
.unwrap_or_else(|| panic!("existing native wallet must remain discoverable"));
assert_eq!(handle.public_key(), public_key);
}
#[tokio::test]
async fn authenticated_header_tampering_fails_before_signing_capability() {
let directory = tempfile::tempdir()
.unwrap_or_else(|error| panic!("temporary directory must exist: {error}"));
make_directory_private(directory.path());
let manager = ks_wallet::WalletManager::new(directory.path())
.unwrap_or_else(|error| panic!("manager must be created: {error}"));
let alias = ks_wallet::WalletAlias::parse("tamper")
.unwrap_or_else(|error| panic!("alias must be valid: {error}"));
let created = manager
.create(alias.clone(), password("tamper-password"))
.await
.unwrap_or_else(|error| panic!("native wallet creation must succeed: {error}"));
created.lock();
let path = directory.path().join("tamper.kswallet");
let mut bytes = std::fs::read(&path)
.unwrap_or_else(|error| panic!("native wallet fixture must be readable: {error}"));
bytes[40] ^= 0x01;
std::fs::write(&path, bytes)
.unwrap_or_else(|error| panic!("tampered fixture must be writable: {error}"));
make_file_private(&path);
let handle = manager
.lookup(&alias)
.await
.unwrap_or_else(|error| panic!("structural lookup must succeed: {error}"))
.unwrap_or_else(|| panic!("tampered wallet must remain structurally discoverable"));
let error = manager
.unlock_file(&handle, password("tamper-password"))
.await
.err()
.unwrap_or_else(|| panic!("authenticated header tampering must fail"));
assert_eq!(error.code(), "wallet_native_authentication_failed");
assert!(!error.to_string().contains("tamper-password"));
}