327 lines
9.9 KiB
Rust
327 lines
9.9 KiB
Rust
use sanctum::sync::validate_node_name;
|
|
|
|
#[test]
|
|
fn test_validate_node_name_rejections() {
|
|
// 1. Leere Namen
|
|
assert!(validate_node_name("").is_err());
|
|
|
|
// 2. Relative Verzeichnisreferenzen
|
|
assert!(validate_node_name(".").is_err());
|
|
assert!(validate_node_name("..").is_err());
|
|
|
|
// 3. Pfadtrenner
|
|
assert!(validate_node_name("foo/bar").is_err());
|
|
assert!(validate_node_name("foo\\bar").is_err());
|
|
assert!(validate_node_name("../evil.exe").is_err());
|
|
assert!(validate_node_name("..\\evil.exe").is_err());
|
|
assert!(validate_node_name("/root_file").is_err());
|
|
|
|
// 4. Null-Bytes
|
|
assert!(validate_node_name("test\0file.txt").is_err());
|
|
|
|
// 5. Steuerzeichen (< 0x20)
|
|
assert!(validate_node_name("line\nbreak.txt").is_err());
|
|
assert!(validate_node_name("carriage\rreturn.txt").is_err());
|
|
assert!(validate_node_name("tab\tseparated.txt").is_err());
|
|
assert!(validate_node_name("control\x01char.txt").is_err());
|
|
|
|
// 6. Windows reservierte Gerätenamen
|
|
let reserved_names = [
|
|
"CON",
|
|
"con",
|
|
"prn",
|
|
"PRN",
|
|
"aux",
|
|
"AUX",
|
|
"nul",
|
|
"NUL",
|
|
"COM1",
|
|
"com2",
|
|
"COM9",
|
|
"lpt1",
|
|
"LPT2",
|
|
"LPT9",
|
|
"con.txt",
|
|
"aux.dat",
|
|
"NUL.tar.gz",
|
|
"com1.log",
|
|
"prn.pdf",
|
|
];
|
|
for res in reserved_names {
|
|
assert!(
|
|
validate_node_name(res).is_err(),
|
|
"Reservierter Name '{}' muss abgewiesen werden",
|
|
res
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_node_name_accepted() {
|
|
let valid_names = [
|
|
"document.pdf",
|
|
"my_notes_2026.txt",
|
|
"archive.tar.gz",
|
|
"spaces are allowed.docx",
|
|
"unicode_äöü_ß_test.rs",
|
|
"hyphens-and_underscores.json",
|
|
"file (1).jpg",
|
|
"normal_file",
|
|
];
|
|
for valid in valid_names {
|
|
assert!(
|
|
validate_node_name(valid).is_ok(),
|
|
"Gültiger Name '{}' sollte akzeptiert werden",
|
|
valid
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_storage_create_and_rename_reject_invalid_names() {
|
|
let mut path = std::env::temp_dir();
|
|
path.push(format!(
|
|
"sanctum_test_pathutil_{}.sanctum",
|
|
std::process::id()
|
|
));
|
|
if path.exists() {
|
|
let _ = std::fs::remove_file(&path);
|
|
}
|
|
|
|
let salt = sanctum::crypto::generate_salt();
|
|
let kdf_params = sanctum::crypto::KdfParams {
|
|
memory_cost: sanctum::crypto::MIN_MEMORY_COST_KIB,
|
|
time_cost: sanctum::crypto::MIN_TIME_COST,
|
|
parallelism: 1,
|
|
};
|
|
let kek = sanctum::crypto::derive_kek("Pass1234!", &salt, &kdf_params).unwrap();
|
|
let dek = sanctum::crypto::generate_dek();
|
|
let (wrapped, nonce, tag) = sanctum::crypto::wrap_dek(&kek, &dek).unwrap();
|
|
|
|
let db = sanctum::storage::Database::open(&path).unwrap();
|
|
db.init_schema(&salt, &kdf_params, &wrapped, &nonce, &tag)
|
|
.unwrap();
|
|
|
|
// create_node_in_vault mit ungültigem Namen muss scheitern
|
|
assert!(db.create_node_in_vault(0, 1, "..", false, &dek).is_err());
|
|
assert!(db.create_node_in_vault(0, 1, "CON", false, &dek).is_err());
|
|
assert!(db
|
|
.create_node_in_vault(0, 1, "sub/dir", false, &dek)
|
|
.is_err());
|
|
|
|
// Gültige Datei erstellen
|
|
let valid_node = db
|
|
.create_node_in_vault(0, 1, "valid.txt", false, &dek)
|
|
.unwrap();
|
|
|
|
// rename_node_in_vault mit ungültigem Namen muss scheitern
|
|
assert!(db
|
|
.rename_node_in_vault(valid_node.id, 1, "..", 0, &dek)
|
|
.is_err());
|
|
assert!(db
|
|
.rename_node_in_vault(valid_node.id, 1, "AUX", 0, &dek)
|
|
.is_err());
|
|
assert!(db
|
|
.rename_node_in_vault(valid_node.id, 1, "bad\\name", 0, &dek)
|
|
.is_err());
|
|
|
|
let _ = std::fs::remove_file(&path);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_carrier_fs_rejects_invalid_names() {
|
|
use dav_server::davpath::DavPath;
|
|
use dav_server::fs::{DavFileSystem, FsError, OpenOptions};
|
|
use rand::rngs::OsRng;
|
|
use rand::RngCore;
|
|
use sanctum::carrier::CarrierFs;
|
|
use sanctum::crypto::{
|
|
derive_kek, generate_dek, generate_salt, wrap_slot0_payload, wrap_slot1_payload, KdfParams,
|
|
MIN_MEMORY_COST_KIB, MIN_TIME_COST,
|
|
};
|
|
use sanctum::storage::Database;
|
|
use std::sync::Arc;
|
|
|
|
let mut path = std::env::temp_dir();
|
|
let id: u64 = OsRng.next_u64();
|
|
path.push(format!("sanctum_test_carrier_pathval_{}.sanctum", id));
|
|
if path.exists() {
|
|
let _ = std::fs::remove_file(&path);
|
|
}
|
|
|
|
let carrier_size_bytes = 10 * 1024 * 1024;
|
|
let carrier_name = "system_backup.dat";
|
|
let pass_decoy = "DecoyPassword2026!";
|
|
let pass_hidden = "SuperSecretHiddenPassword2026!";
|
|
|
|
let kdf_params = KdfParams {
|
|
memory_cost: MIN_MEMORY_COST_KIB,
|
|
time_cost: MIN_TIME_COST,
|
|
parallelism: 1,
|
|
};
|
|
|
|
let salt_0 = generate_salt();
|
|
let kek_0 = derive_kek(pass_decoy, &salt_0, &kdf_params).unwrap();
|
|
let dek_0 = generate_dek();
|
|
|
|
let salt_1 = generate_salt();
|
|
let kek_1 = derive_kek(pass_hidden, &salt_1, &kdf_params).unwrap();
|
|
let dek_1 = generate_dek();
|
|
|
|
let carrier_node_id = 3i64;
|
|
let (wrapped_0, nonce_0, tag_0) = wrap_slot0_payload(&kek_0, &dek_0, carrier_node_id).unwrap();
|
|
let (wrapped_1, nonce_1, tag_1) =
|
|
wrap_slot1_payload(&kek_1, &dek_1, &dek_0, carrier_node_id).unwrap();
|
|
|
|
let db = Database::open(&path).expect("Open database");
|
|
|
|
db.init_schema_with_carrier(
|
|
&salt_0,
|
|
&kdf_params,
|
|
&wrapped_0,
|
|
&nonce_0,
|
|
&tag_0,
|
|
Some((
|
|
carrier_name,
|
|
carrier_size_bytes,
|
|
&salt_1,
|
|
&kdf_params,
|
|
&wrapped_1,
|
|
&nonce_1,
|
|
&tag_1,
|
|
&dek_0,
|
|
&dek_1,
|
|
)),
|
|
)
|
|
.expect("Init carrier schema");
|
|
|
|
let cfs = CarrierFs::load(
|
|
db.clone(),
|
|
carrier_node_id,
|
|
Arc::new(dek_0),
|
|
Arc::new(dek_1),
|
|
3,
|
|
false,
|
|
)
|
|
.expect("Load CarrierFs");
|
|
|
|
// 1. create_dir mit ungültigem Namen (Windows-Reservierung CON, PRN)
|
|
let p_con = DavPath::new("/CON").unwrap();
|
|
assert_eq!(cfs.create_dir(&p_con).await, Err(FsError::Forbidden));
|
|
|
|
let p_aux = DavPath::new("/aux.txt").unwrap();
|
|
assert_eq!(cfs.create_dir(&p_aux).await, Err(FsError::Forbidden));
|
|
|
|
// 2. open (create) mit ungültigem Namen
|
|
let p_nul = DavPath::new("/NUL").unwrap();
|
|
let mut opt_create = OpenOptions::default();
|
|
opt_create.create = true;
|
|
opt_create.write = true;
|
|
assert_eq!(
|
|
cfs.open(&p_nul, opt_create.clone()).await.err(),
|
|
Some(FsError::Forbidden)
|
|
);
|
|
|
|
let p_ctrl = DavPath::new("/prn.pdf").unwrap();
|
|
assert_eq!(
|
|
cfs.open(&p_ctrl, opt_create.clone()).await.err(),
|
|
Some(FsError::Forbidden)
|
|
);
|
|
|
|
// 3. Gültige Datei erstellen
|
|
let p_valid = DavPath::new("/valid.txt").unwrap();
|
|
let file = cfs.open(&p_valid, opt_create).await;
|
|
assert!(file.is_ok(), "Valid file should be created");
|
|
drop(file);
|
|
|
|
// 4. rename mit ungültigem Zielnamen
|
|
let p_com1 = DavPath::new("/COM1.txt").unwrap();
|
|
assert_eq!(cfs.rename(&p_valid, &p_com1).await, Err(FsError::Forbidden));
|
|
|
|
// 5. copy mit ungültigem Zielnamen
|
|
let p_lpt1 = DavPath::new("/LPT1").unwrap();
|
|
assert_eq!(cfs.copy(&p_valid, &p_lpt1).await, Err(FsError::Forbidden));
|
|
|
|
let _ = std::fs::remove_file(&path);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_vfs_rejects_invalid_names() {
|
|
use dav_server::davpath::DavPath;
|
|
use dav_server::fs::{DavFileSystem, FsError, OpenOptions};
|
|
use rand::rngs::OsRng;
|
|
use rand::RngCore;
|
|
use sanctum::crypto::{
|
|
derive_kek, generate_dek, generate_salt, wrap_dek, KdfParams, FORMAT_VERSION,
|
|
MIN_MEMORY_COST_KIB, MIN_TIME_COST,
|
|
};
|
|
use sanctum::storage::Database;
|
|
use sanctum::vfs::SanctumFs;
|
|
|
|
let mut path = std::env::temp_dir();
|
|
let id: u64 = OsRng.next_u64();
|
|
path.push(format!("sanctum_test_vfs_pathval_{}.sanctum", id));
|
|
if path.exists() {
|
|
let _ = std::fs::remove_file(&path);
|
|
}
|
|
|
|
let password = "TestPassword1234!";
|
|
let kdf_params = KdfParams {
|
|
memory_cost: MIN_MEMORY_COST_KIB,
|
|
time_cost: MIN_TIME_COST,
|
|
parallelism: 1,
|
|
};
|
|
let salt = generate_salt();
|
|
let kek = derive_kek(password, &salt, &kdf_params).unwrap();
|
|
let dek = generate_dek();
|
|
let (wrapped, nonce, tag) = wrap_dek(&kek, &dek).unwrap();
|
|
|
|
let db = Database::open(&path).unwrap();
|
|
db.init_schema(&salt, &kdf_params, &wrapped, &nonce, &tag)
|
|
.unwrap();
|
|
db.set_active_slot_and_dek(0, dek.clone());
|
|
|
|
let fs = SanctumFs::new(db.clone(), dek, FORMAT_VERSION);
|
|
|
|
// 1. create_dir mit ungültigem Namen
|
|
let p_con = DavPath::new("/CON").unwrap();
|
|
assert_eq!(fs.create_dir(&p_con).await, Err(FsError::Forbidden));
|
|
|
|
let p_aux = DavPath::new("/aux.txt").unwrap();
|
|
assert_eq!(fs.create_dir(&p_aux).await, Err(FsError::Forbidden));
|
|
|
|
// 2. open (create) mit ungültigem Namen
|
|
let p_nul = DavPath::new("/NUL").unwrap();
|
|
let mut opt_create = OpenOptions::default();
|
|
opt_create.create = true;
|
|
opt_create.write = true;
|
|
assert_eq!(
|
|
fs.open(&p_nul, opt_create.clone()).await.err(),
|
|
Some(FsError::Forbidden)
|
|
);
|
|
|
|
let p_prn = DavPath::new("/prn.pdf").unwrap();
|
|
assert_eq!(
|
|
fs.open(&p_prn, opt_create.clone()).await.err(),
|
|
Some(FsError::Forbidden)
|
|
);
|
|
|
|
// 3. Gültige Datei erstellen
|
|
let p_valid = DavPath::new("/valid.txt").unwrap();
|
|
let file = fs.open(&p_valid, opt_create).await;
|
|
assert!(file.is_ok(), "Valid file should be created");
|
|
drop(file);
|
|
|
|
// 4. rename mit ungültigem Zielnamen
|
|
let p_com1 = DavPath::new("/COM1.txt").unwrap();
|
|
assert_eq!(fs.rename(&p_valid, &p_com1).await, Err(FsError::Forbidden));
|
|
|
|
// 5. copy mit ungültigem Zielnamen
|
|
let p_lpt1 = DavPath::new("/LPT1").unwrap();
|
|
assert_eq!(fs.copy(&p_valid, &p_lpt1).await, Err(FsError::Forbidden));
|
|
|
|
drop(fs);
|
|
drop(db);
|
|
let _ = std::fs::remove_file(&path);
|
|
}
|