Files
guara/crates/guara-core/tests/e2e_crypt.rs
T
Lucas RayanandClaude Opus 5.5 beb83fef92
Release / Cria a release (push) Canceled after 0s
Release / CLI (linux-aarch64) (push) Canceled after 0s
Release / CLI (linux-x86_64) (push) Canceled after 0s
Release / CLI (macos-aarch64) (push) Canceled after 0s
Release / CLI (macos-x86_64) (push) Canceled after 0s
Release / CLI (windows-x86_64) (push) Canceled after 0s
Release / App desktop (linux-x86_64) (push) Canceled after 0s
Release / App desktop (macos-aarch64) (push) Canceled after 0s
Release / App desktop (macos-x86_64) (push) Canceled after 0s
Release / App desktop (windows-x86_64) (push) Canceled after 0s
Release / Publica a release (push) Canceled after 0s
Formatação (cargo fmt) e ajustes do clippy para o CI da 1.0.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:41:58 -03:00

207 lines
5.5 KiB
Rust

//! Ponta a ponta: volume LUKS2 com ext4 dentro, arquivo apagado, senha informada ao Guará.
mod common;
use common::*;
use guara_core::assemble::{assemble_source, Assembly, SecretInput};
use guara_core::scan::{self, Outcome};
use guara_core::source::{FileSource, Source};
use guara_core::*;
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::Arc;
fn have(t: &str) -> bool {
Command::new("sh")
.args(["-c", &format!("command -v {t}")])
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn run_stdin(c: &mut Command, input: &str) -> bool {
use std::io::Write;
let mut ch = c
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap();
ch.stdin
.take()
.unwrap()
.write_all(input.as_bytes())
.unwrap();
ch.wait().unwrap().success()
}
fn data(seed: u64, n: usize) -> Vec<u8> {
let mut x = seed | 1;
(0..n)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
(x >> 24) as u8
})
.collect()
}
#[test]
fn deleted_file_inside_luks2_is_recovered_with_password() {
if !(have("cryptsetup") && have("mke2fs") && have("debugfs") && have("python3")) {
eprintln!("SKIP: ferramentas ausentes");
return;
}
init();
let dir = tempfile::tempdir().unwrap();
let root = dir.path().join("root");
std::fs::create_dir_all(root.join("docs")).unwrap();
let secret_doc = data(11, 120_000);
std::fs::write(root.join("docs/segredo.dat"), &secret_doc).unwrap();
std::fs::write(root.join("docs/fica.dat"), data(12, 5000)).unwrap();
let plain = dir.path().join("plain.img");
std::fs::File::create(&plain)
.unwrap()
.set_len(8 << 20)
.unwrap();
assert!(Command::new("mke2fs")
.args(["-q", "-F", "-t", "ext4", "-d"])
.arg(&root)
.arg(&plain)
.status()
.unwrap()
.success());
let dbg = |c: &str| {
Command::new("debugfs")
.arg("-w")
.arg("-R")
.arg(c)
.arg(&plain)
.output()
.unwrap()
};
dbg("rm /docs/segredo.dat");
let img = dir.path().join("luks.img");
std::fs::File::create(&img)
.unwrap()
.set_len(32 << 20)
.unwrap();
let pw = "correct horse";
let mut fmt = Command::new("cryptsetup");
fmt.args([
"luksFormat",
"--batch-mode",
"--type",
"luks2",
"--pbkdf",
"pbkdf2",
"--pbkdf-force-iterations",
"1000",
"--sector-size",
"512",
"--key-file",
"-",
])
.arg(&img);
assert!(run_stdin(&mut fmt, pw));
let vkf = dir.path().join("vk");
let mut d = Command::new("cryptsetup");
d.args([
"luksDump",
"--dump-volume-key",
"--batch-mode",
"--key-file",
"-",
"--volume-key-file",
])
.arg(&vkf)
.arg(&img);
assert!(run_stdin(&mut d, pw));
let out = Command::new("cryptsetup")
.arg("luksDump")
.arg(&img)
.output()
.unwrap();
let t = String::from_utf8_lossy(&out.stdout).to_string();
let off: u64 = t
.lines()
.skip_while(|l| !l.starts_with("Data segments"))
.find(|l| l.contains("offset:"))
.unwrap()
.split(':')
.nth(1)
.unwrap()
.split_whitespace()
.next()
.unwrap()
.parse()
.unwrap();
assert!(Command::new("python3")
.arg(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/support/luks_encrypt.py"))
.arg(&img)
.arg(&plain)
.arg(&vkf)
.arg(off.to_string())
.arg("aes-xts-plain64")
.arg("512")
.status()
.unwrap()
.success());
let base: Arc<dyn Source> = Arc::new(FileSource::open(&img).unwrap());
// Sem senha: o volume segue fechado e a varredura não inventa arquivos.
let locked = assemble_source(base.clone(), &Assembly::default()).unwrap();
assert_eq!(locked.unlocked.len(), 0);
assert_eq!(locked.locked.len(), 1);
let asm = Assembly {
secrets: vec![SecretInput::Password { value: pw.into() }],
..Default::default()
};
let open = assemble_source(base, &asm).unwrap();
assert_eq!(open.unlocked.len(), 1);
let mut sink = Collect::default();
let outc = scan::run(
open.source.as_ref(),
&ScanOptions {
mode: ScanMode::Deep,
..Default::default()
},
None,
&[],
&|| false,
&mut sink,
);
assert!(matches!(outc, Outcome::Completed));
let f = sink
.found
.iter()
.find(|f| f.name == "segredo.dat")
.unwrap_or_else(|| {
panic!(
"não achou: {:?}",
sink.found.iter().map(|f| &f.name).collect::<Vec<_>>()
)
});
assert_eq!(f.size, 120_000);
let dest = tempfile::tempdir().unwrap();
let refs = vec![f];
let rep = recover::recover(
open.source.as_ref(),
&refs,
dest.path(),
&|| false,
&mut |_, _, _| {},
)
.unwrap();
assert!(rep.failed.is_empty());
let p = &rep.recovered[0].path;
let p = if Path::new(p).is_absolute() {
Path::new(p).to_path_buf()
} else {
dest.path().join(p)
};
assert!(std::fs::read(p).unwrap() == secret_doc);
}