feat(cli): add passwd command for instant master password changes

This commit is contained in:
2026-09-07 17:29:50 +02:00
parent 58336fc059
commit a9c3dd25a3
5 changed files with 242 additions and 1 deletions
+91
View File
@@ -158,3 +158,94 @@ async fn test_sanctum_full_container_lifecycle() {
assert!(container_path.exists());
let _ = std::fs::remove_file(&container_path);
}
#[tokio::test]
async fn test_sanctum_password_change() {
let temp_dir = std::env::temp_dir();
let container_path: PathBuf = temp_dir.join(format!("test_sanctum_passwd_{}.sanctum", std::process::id()));
if container_path.exists() {
let _ = std::fs::remove_file(&container_path);
}
let password_v1 = "InitialSecret123!";
let password_v2 = "NewSecret456!";
// 1. Initialisierung mit Passwort v1
let salt_v1 = generate_salt();
let kdf_params = KdfParams {
memory_cost: 1024,
time_cost: 1,
parallelism: 1,
};
let kek_v1 = derive_kek(password_v1, &salt_v1, &kdf_params).expect("KEK v1");
let original_dek = generate_dek();
let (wrapped_dek_v1, nonce_v1, tag_v1) =
wrap_dek(&kek_v1, &original_dek).expect("Wrap DEK v1");
let db = Database::open(&container_path).expect("Open database");
db.init_schema(&salt_v1, &kdf_params, &wrapped_dek_v1, &nonce_v1, &tag_v1)
.expect("Init schema");
db.checkpoint().expect("Checkpoint");
// Datei im Container mit DEK anlegen
let fs_v1 = SanctumFs::new(db.clone(), original_dek.clone());
let file_path = DavPath::new("/secret.txt").unwrap();
let file_data = b"Verschluesselte Geheimdaten vor der Passwortaenderung!";
let mut opts_write = OpenOptions::default();
opts_write.write = true;
opts_write.create_new = true;
let mut write_file = fs_v1
.open(&file_path, opts_write)
.await
.expect("Open write");
write_file
.write_bytes(Bytes::from_static(file_data))
.await
.expect("Write bytes");
write_file.flush().await.expect("Flush");
drop(write_file);
drop(fs_v1);
db.checkpoint().expect("Checkpoint");
// 2. Passwortwechsel durchführen
let meta_before = db.read_meta().expect("Read meta");
let old_kek = derive_kek(password_v1, &meta_before.kdf_salt, &meta_before.kdf_params).expect("Derive old KEK");
let recovered_dek = unwrap_dek(&old_kek, &meta_before.wrapped_dek, &meta_before.header_nonce, &meta_before.header_tag)
.expect("Unwrap with old password");
let salt_v2 = generate_salt();
let kek_v2 = derive_kek(password_v2, &salt_v2, &kdf_params).expect("Derive new KEK");
let (wrapped_dek_v2, nonce_v2, tag_v2) = wrap_dek(&kek_v2, &recovered_dek).expect("Wrap with new KEK");
db.update_meta_keys(&salt_v2, &kdf_params, &wrapped_dek_v2, &nonce_v2, &tag_v2).expect("Update meta keys");
db.checkpoint().expect("Checkpoint");
// 3. Verifikation: Altes Passwort darf NICHT mehr funktionieren
let meta_after = db.read_meta().expect("Read meta after");
let old_kek_again = derive_kek(password_v1, &meta_after.kdf_salt, &meta_after.kdf_params).unwrap();
assert!(
unwrap_dek(&old_kek_again, &meta_after.wrapped_dek, &meta_after.header_nonce, &meta_after.header_tag).is_err(),
"Altes Passwort darf nach Passwortaenderung nicht mehr funktionieren!"
);
// 4. Verifikation: Neues Passwort funktioniert und entschlüsselt alte Daten intakt
let new_kek = derive_kek(password_v2, &meta_after.kdf_salt, &meta_after.kdf_params).expect("Derive new KEK");
let active_dek = unwrap_dek(&new_kek, &meta_after.wrapped_dek, &meta_after.header_nonce, &meta_after.header_tag)
.expect("Unwrap with new password");
let fs_v2 = SanctumFs::new(db.clone(), active_dek);
let mut opts_read = OpenOptions::default();
opts_read.read = true;
let mut read_file = fs_v2.open(&file_path, opts_read).await.expect("Open read with new password");
let read_bytes = read_file.read_bytes(file_data.len()).await.expect("Read bytes");
assert_eq!(&read_bytes[..], file_data, "Daten muessen nach Passwortaenderung unveraendert lesbar sein!");
drop(read_file);
drop(fs_v2);
drop(db);
let _ = std::fs::remove_file(&container_path);
}