CI / Format and clippy (push) Successful in 1m57s
CI / Dependency audit (push) Successful in 13s
CI / Check desktop app (Linux) (push) Successful in 3m54s
CI / Tests (Linux) (push) Failing after 7m25s
Release / Create release (push) Successful in 6s
Release / CLI (linux-aarch64) (push) Successful in 1m48s
Release / CLI (linux-x86_64) (push) Successful in 2m39s
Release / Desktop app (linux-aarch64) (push) Successful in 10m9s
Release / Desktop app (linux-x86_64) (push) Successful in 12m20s
Release / Desktop app (windows) (push) Successful in 7m32s
Release / Build latest.json (push) Successful in 5s
Completes the English-only standardization for code, comments, scripts and CI templates (UI/CLI-facing strings stay bilingual pt-BR/en, untouched). Covers the remaining crates/, apps/gui/ui/ and apps/gui/src-tauri/ comments, .gitea/ workflow and template comments, .githooks/ hooks, and the scripts/ source comments for the files already renamed in a prior commit (check.sh, generate-logo.py, install-hooks.sh). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
114 lines
4.3 KiB
Rust
114 lines
4.3 KiB
Rust
//! BitLocker: real test images from the `cryptsetup` test suite (with known passwords/recovery keys).
|
|
//! Download with `scripts/fetch-testdata.sh`; tests are skipped if images are absent.
|
|
|
|
use guara_core::crypt::{bitlocker, Secret, UnlockError};
|
|
use guara_core::partition::detect_fs_in;
|
|
use guara_core::source::{read_vec, FileSource, Source};
|
|
use std::collections::BTreeMap;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
fn dir() -> Option<PathBuf> {
|
|
let d = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../target/testdata/bitlk-images");
|
|
d.join("images.conf").exists().then_some(d)
|
|
}
|
|
|
|
fn conf(d: &std::path::Path) -> BTreeMap<String, BTreeMap<String, String>> {
|
|
let mut m: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
|
|
let mut cur = String::new();
|
|
for l in std::fs::read_to_string(d.join("images.conf"))
|
|
.unwrap()
|
|
.lines()
|
|
{
|
|
let l = l.trim();
|
|
if let Some(n) = l.strip_prefix('[').and_then(|x| x.strip_suffix(']')) {
|
|
cur = n.to_string();
|
|
} else if let Some((k, v)) = l.split_once('=') {
|
|
m.entry(cur.clone()).or_default().insert(k.into(), v.into());
|
|
}
|
|
}
|
|
m
|
|
}
|
|
|
|
fn check_fs(src: &dyn Source, what: &str) {
|
|
// Used-space-only ("encrypt-on-write") images in `images.conf` have an empty filesystem UUID.
|
|
if what.contains("-eow ") {
|
|
return;
|
|
}
|
|
let head = read_vec(src, 0, 4096).unwrap();
|
|
let fs = detect_fs_in(&head);
|
|
assert!(
|
|
fs.is_some(),
|
|
"{what}: setor 0 aberto não é um sistema de arquivos: {:02x?}",
|
|
&head[..32]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn opens_all_reference_images_with_password_and_recovery_key() {
|
|
let Some(d) = dir() else {
|
|
eprintln!("SKIP: execute scripts/fetch-testdata.sh");
|
|
return;
|
|
};
|
|
let mut opened = 0;
|
|
for (name, c) in conf(&d) {
|
|
let path = d.join(format!("{name}.img"));
|
|
if !path.exists() {
|
|
continue;
|
|
}
|
|
let src: Arc<dyn Source> = Arc::new(FileSource::open(&path).unwrap());
|
|
let pw = c.get("PW").cloned().unwrap_or_default();
|
|
let rp = c.get("RP").cloned().unwrap_or_default();
|
|
// Suspended protection (clear key present on disk): unlocks without any user secret.
|
|
if let Ok(u) = bitlocker::unlock(src.clone(), &[]) {
|
|
check_fs(u.source.as_ref(), &format!("{name} (chave limpa)"));
|
|
eprintln!("ok {name}: chave limpa");
|
|
opened += 1;
|
|
continue;
|
|
}
|
|
match bitlocker::unlock(src.clone(), &[Secret::Password("errada".into())]) {
|
|
Err(UnlockError::WrongSecret) | Err(UnlockError::Unsupported(_)) => {}
|
|
Err(e) => panic!("{name}: {e}"),
|
|
Ok(_) => panic!("{name}: abriu com senha errada"),
|
|
}
|
|
let mut views = vec![];
|
|
if !pw.is_empty() {
|
|
let u = bitlocker::unlock(src.clone(), &[Secret::Password(pw.clone())])
|
|
.unwrap_or_else(|e| panic!("{name} senha: {e}"));
|
|
check_fs(u.source.as_ref(), &format!("{name} (senha)"));
|
|
views.push(u);
|
|
}
|
|
if !rp.is_empty() {
|
|
let u = bitlocker::unlock(src.clone(), &[Secret::RecoveryKey(rp.clone())])
|
|
.unwrap_or_else(|e| panic!("{name} recuperação: {e}"));
|
|
check_fs(u.source.as_ref(), &format!("{name} (recuperação)"));
|
|
views.push(u);
|
|
}
|
|
if let Some(g) = c.get("SK_VMK_GUID") {
|
|
let f = d.join(format!("{}.BEK", g.to_uppercase()));
|
|
if f.exists() {
|
|
let u =
|
|
bitlocker::unlock(src.clone(), &[Secret::KeyFile(std::fs::read(f).unwrap())])
|
|
.unwrap_or_else(|e| panic!("{name} BEK: {e}"));
|
|
check_fs(u.source.as_ref(), &format!("{name} (BEK)"));
|
|
views.push(u);
|
|
}
|
|
}
|
|
assert!(!views.is_empty(), "{name}: nenhum protetor testável");
|
|
let first = read_vec(views[0].source.as_ref(), 0, 1 << 20).unwrap();
|
|
for v in &views[1..] {
|
|
assert!(
|
|
read_vec(v.source.as_ref(), 0, 1 << 20).unwrap() == first,
|
|
"{name}: protetores divergem"
|
|
);
|
|
}
|
|
opened += 1;
|
|
eprintln!(
|
|
"ok {name}: {} ({} protetores)",
|
|
views[0].description,
|
|
views.len()
|
|
);
|
|
}
|
|
assert!(opened > 0);
|
|
}
|