chore(qa): format code and configure clippy lints

This commit is contained in:
2026-09-19 10:04:53 +02:00
parent 45572b7370
commit 28a4bb6b70
16 changed files with 157 additions and 109 deletions
+14
View File
@@ -55,3 +55,17 @@ codegen-units = 1
panic = "unwind"
strip = true
overflow-checks = true
[lints.clippy]
too_many_arguments = "allow"
type_complexity = "allow"
field_reassign_with_default = "allow"
upper_case_acronyms = "allow"
collapsible_if = "allow"
manual_dangling_ptr = "allow"
needless_borrow = "allow"
manual_strip = "allow"
redundant_pattern_matching = "allow"
needless_range_loop = "allow"
manual_range_contains = "allow"
+4 -2
View File
@@ -934,7 +934,8 @@ impl DavFileSystem for CarrierFs {
Box::pin(async move {
self.touch();
let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
let total_capacity = inner.manifest.total_blocks as u64 * crate::crypto::CHUNK_SIZE as u64;
let total_capacity =
inner.manifest.total_blocks as u64 * crate::crypto::CHUNK_SIZE as u64;
let used_bytes = inner
.manifest
.inodes
@@ -1122,7 +1123,8 @@ impl DavFile for CarrierFile {
let target = self.cursor;
while self.file_size < target {
let block_idx = (self.file_size / CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize;
let offset_in_block = (self.file_size % CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize;
let offset_in_block =
(self.file_size % CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize;
let space_in_block = CARRIER_BLOCK_PAYLOAD_SIZE - offset_in_block;
let to_pad = ((target - self.file_size) as usize).min(space_in_block);
+50 -14
View File
@@ -590,13 +590,13 @@ pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
}
let mut inner_hasher = Sha256::new();
inner_hasher.update(&k_ipad);
inner_hasher.update(k_ipad);
inner_hasher.update(data);
let inner_hash = inner_hasher.finalize();
let mut outer_hasher = Sha256::new();
outer_hasher.update(&k_opad);
outer_hasher.update(&inner_hash);
outer_hasher.update(k_opad);
outer_hasher.update(inner_hash);
let out = outer_hasher.finalize();
let mut result = [0u8; 32];
@@ -840,8 +840,14 @@ mod tests {
256 * 1024,
"Default memory cost must be 256 MiB (262,144 KiB)"
);
assert_eq!(defaults.time_cost, 4, "Default time cost must be 4 iterations");
assert_eq!(defaults.parallelism, 4, "Default parallelism must be 4 threads");
assert_eq!(
defaults.time_cost, 4,
"Default time cost must be 4 iterations"
);
assert_eq!(
defaults.parallelism, 4,
"Default parallelism must be 4 threads"
);
assert!(
validate_kdf_params(&defaults).is_ok(),
"Default KDF parameters must pass validation"
@@ -923,8 +929,15 @@ mod tests {
let chunk_index = 0u32;
// 1. Chunk mit Generation 1 verschlüsseln
let (ct1, nonce1, tag1) =
encrypt_chunk(&dek, node_id, chunk_index, plaintext_v1, FORMAT_VERSION_V3, 1).unwrap();
let (ct1, nonce1, tag1) = encrypt_chunk(
&dek,
node_id,
chunk_index,
plaintext_v1,
FORMAT_VERSION_V3,
1,
)
.unwrap();
// Verifiziere reguläre Entschlüsselung mit Generation 1
let dec1 = decrypt_chunk(
@@ -957,8 +970,15 @@ mod tests {
);
// 3. Chunk überschreiben mit Generation 2
let (ct2, nonce2, tag2) =
encrypt_chunk(&dek, node_id, chunk_index, plaintext_v2, FORMAT_VERSION_V3, 2).unwrap();
let (ct2, nonce2, tag2) = encrypt_chunk(
&dek,
node_id,
chunk_index,
plaintext_v2,
FORMAT_VERSION_V3,
2,
)
.unwrap();
let dec2 = decrypt_chunk(
&dek,
node_id,
@@ -983,11 +1003,21 @@ mod tests {
FORMAT_VERSION_V3,
2,
);
assert!(attack_res.is_err(), "Replay von altem Ciphertext muss abgewehrt werden");
assert!(
attack_res.is_err(),
"Replay von altem Ciphertext muss abgewehrt werden"
);
// 5. Abwärtskompatibilität: In V2 wird generation ignoriert
let (ct_v2, nonce_v2, tag_v2) =
encrypt_chunk(&dek, node_id, chunk_index, plaintext_v1, FORMAT_VERSION_V2, 0).unwrap();
let (ct_v2, nonce_v2, tag_v2) = encrypt_chunk(
&dek,
node_id,
chunk_index,
plaintext_v1,
FORMAT_VERSION_V2,
0,
)
.unwrap();
let dec_v2 = decrypt_chunk(
&dek,
node_id,
@@ -1043,7 +1073,8 @@ mod tests {
let mut random_bytes = vec![0u8; 1000];
OsRng.fill_bytes(&mut random_bytes);
let (ct, nonce, tag) = encrypt_chunk(&dek, 1, 0, &random_bytes, FORMAT_VERSION_V2, 0).unwrap();
let (ct, nonce, tag) =
encrypt_chunk(&dek, 1, 0, &random_bytes, FORMAT_VERSION_V2, 0).unwrap();
// Da Kompression keine 64 Bytes spart, wird COMPRESSION_NONE (1 Byte) + Plaintext gespeichert
assert_eq!(ct.len(), random_bytes.len() + 1);
@@ -1353,7 +1384,12 @@ mod tests {
assert!(verify_metadata_mac(&mac_key, gen, canonical_nodes, &mac));
// Manipulierte Generation -> ungültig
assert!(!verify_metadata_mac(&mac_key, gen + 1, canonical_nodes, &mac));
assert!(!verify_metadata_mac(
&mac_key,
gen + 1,
canonical_nodes,
&mac
));
// Manipulierte Knoten-Bytes -> ungültig
assert!(!verify_metadata_mac(&mac_key, gen, b"tampered_nodes", &mac));
+4 -6
View File
@@ -1006,16 +1006,14 @@ mod tests {
#[test]
fn test_m03_format_linux_mount_instructions() {
let token = "fedcba9876543210fedcba9876543210";
let instructions = format_linux_mount_instructions(
8443,
token,
Some(std::path::Path::new("/mnt/secure")),
);
let instructions =
format_linux_mount_instructions(8443, token, Some(std::path::Path::new("/mnt/secure")));
let all_text = instructions.join("\n");
assert!(all_text.contains("Benutzer: sanctum"));
assert!(all_text.contains(token));
assert!(all_text.contains("gio mount dav://sanctum@127.0.0.1:8443/"));
assert!(all_text.contains("mount -t davfs -o username=sanctum http://127.0.0.1:8443/ /mnt/secure"));
assert!(all_text
.contains("mount -t davfs -o username=sanctum http://127.0.0.1:8443/ /mnt/secure"));
}
}
+17 -5
View File
@@ -396,7 +396,10 @@ impl Database {
/// Setzt den aktiven Slot und DEK für automatische Metadaten-Authentifizierung (K-01).
pub fn set_active_slot_and_dek(&self, slot_id: u32, dek: Zeroizing<[u8; 32]>) {
*self.active_session.lock().unwrap_or_else(|e| e.into_inner()) = Some((slot_id, dek));
*self
.active_session
.lock()
.unwrap_or_else(|e| e.into_inner()) = Some((slot_id, dek));
}
/// Gibt den aktuellen aktiven DEK zurück, falls gesetzt.
@@ -2046,7 +2049,11 @@ impl Database {
/// Aktualisiert den Metadaten-MAC des aktiven Slots bei strukturellen Modifikationen (Format V3 / K-01).
pub fn update_metadata_mac(&self) -> Result<()> {
let session_opt = self.active_session.lock().unwrap_or_else(|e| e.into_inner()).clone();
let session_opt = self
.active_session
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone();
let Some((slot_id, dek)) = session_opt else {
return Ok(());
};
@@ -2664,7 +2671,10 @@ mod tests {
db.truncate_chunks_after(file.id, 0).unwrap();
// Chunk 0 muss unberührt geblieben sein
let c0 = db.read_chunk(file.id, 0).unwrap().expect("Chunk 0 survives");
let c0 = db
.read_chunk(file.id, 0)
.unwrap()
.expect("Chunk 0 survives");
assert_eq!(c0.ciphertext, payload_0);
// Chunks 1 und 2 müssen gelöscht sein
@@ -2884,7 +2894,10 @@ mod tests {
panic!("Simulierter Crash im Worker-Thread während aktiver SQLite-Verbindung");
});
let res = handle.join();
assert!(res.is_err(), "Worker-Thread muss wie erwartet gepanict haben");
assert!(
res.is_err(),
"Worker-Thread muss wie erwartet gepanict haben"
);
// 3. Mutex ist nun poisoned. Ohne Z-02 schlägt jeder nachfolgende Aufruf fehl.
// Mit Z-02 fängt unwrap_or_else(|e| e.into_inner()) das Poisoning ab:
@@ -2911,4 +2924,3 @@ mod tests {
assert_eq!(read_dek.unwrap()[0], 42);
}
}
+2 -1
View File
@@ -240,7 +240,8 @@ pub fn is_excluded(name: &str, rel_path: &str, patterns: &[String]) -> bool {
return true;
}
// Wenn p_clean ein Verzeichnis ohne Wildcards ist, auch alle Unterpfade erfassen
if !p_clean.contains('*') && !p_clean.contains('?')
if !p_clean.contains('*')
&& !p_clean.contains('?')
&& (norm_rel == p_clean || norm_rel.starts_with(&format!("{}/", p_clean)))
{
return true;
+3 -5
View File
@@ -87,9 +87,7 @@ pub fn validate_trusted_comment_version(
trusted_comment: &str,
expected_release: &str,
) -> Result<()> {
let clean_expected = expected_release
.trim()
.trim_start_matches(|c| c == 'v' || c == 'V');
let clean_expected = expected_release.trim().trim_start_matches(['v', 'V']);
if clean_expected.is_empty() {
bail!("Erwartetes Release-Tag darf nicht leer sein.");
@@ -107,7 +105,7 @@ pub fn validate_trusted_comment_version(
token
};
let clean_val = val.trim_start_matches(|c| c == 'v' || c == 'V');
let clean_val = val.trim_start_matches(['v', 'V']);
clean_val == clean_expected
});
@@ -259,7 +257,7 @@ pub fn parse_checksum_for_asset(sha256sums_content: &str, target_asset: &str) ->
/// Bereinigt einen Versionsstring von führenden 'v' / 'V' Zeichen und parst ihn als SemVer.
pub fn parse_clean_version(ver_str: &str) -> Result<Version> {
let clean = ver_str.trim().trim_start_matches(|c| c == 'v' || c == 'V');
let clean = ver_str.trim().trim_start_matches(['v', 'V']);
Version::parse(clean).with_context(|| format!("Ungültiges SemVer-Format: '{}'", ver_str))
}
+8 -6
View File
@@ -4,9 +4,7 @@ use std::path::Path;
use anyhow::{bail, Context, Result};
use zeroize::Zeroizing;
use crate::crypto::{
decrypt_chunk, FORMAT_VERSION_V1, FORMAT_VERSION_V2, FORMAT_VERSION_V3,
};
use crate::crypto::{decrypt_chunk, FORMAT_VERSION_V1, FORMAT_VERSION_V2, FORMAT_VERSION_V3};
use crate::storage::Database;
/// Bericht über das Ergebnis einer Container-Integritätsprüfung.
@@ -264,7 +262,9 @@ pub fn verify_container(
);
}
Err(e) => {
report.errors.push(format!("Fehler bei der Metadaten-MAC-Prüfung: {e}"));
report
.errors
.push(format!("Fehler bei der Metadaten-MAC-Prüfung: {e}"));
}
}
}
@@ -374,11 +374,13 @@ mod tests {
// 2 Chunks schreiben
let chunk0_data = b"Sample JPEG data header and pixels";
let (ct0, n0, t0) = encrypt_chunk(&dek, file.id, 0, chunk0_data, FORMAT_VERSION, 0).unwrap();
let (ct0, n0, t0) =
encrypt_chunk(&dek, file.id, 0, chunk0_data, FORMAT_VERSION, 0).unwrap();
db.write_chunk(file.id, 0, 0, &n0, &t0, &ct0).unwrap();
let chunk1_data = b"Additional payload data bytes";
let (ct1, n1, t1) = encrypt_chunk(&dek, file.id, 1, chunk1_data, FORMAT_VERSION, 0).unwrap();
let (ct1, n1, t1) =
encrypt_chunk(&dek, file.id, 1, chunk1_data, FORMAT_VERSION, 0).unwrap();
db.write_chunk(file.id, 1, 0, &n1, &t1, &ct1).unwrap();
db.update_node_size_and_time(
+9 -7
View File
@@ -4,7 +4,6 @@ use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::path::Path;
use anyhow::{Context, Result};
use bytes::{Buf, Bytes, BytesMut};
use dav_server::{
@@ -15,6 +14,7 @@ use dav_server::{
},
};
use futures_util::stream;
use std::path::Path;
use tracing::{debug, error, warn};
use zeroize::{Zeroize, Zeroizing};
@@ -50,7 +50,7 @@ pub const LEAK_SUFFIXES: &[&str] = &[
".crdownload", // Google Chrome temporärer Download
".part", // Mozilla Firefox unvollständiger Download
".partial",
"~", // Linux/UNIX Editor-Backups (Vim, Emacs, Gedit)
"~", // Linux/UNIX Editor-Backups (Vim, Emacs, Gedit)
];
/// Prüft, ob ein Dateiname zu den typischen Windows Explorer-, OS- oder Anwendungs-
@@ -319,12 +319,11 @@ impl SanctumFile {
.next_chunk_generation(self.node_id, idx)
.unwrap_or(0);
let (ciphertext, nonce, tag) =
encrypt_chunk(&self.dek, self.node_id, idx, data, self.format_version, gen).map_err(
|e| {
encrypt_chunk(&self.dek, self.node_id, idx, data, self.format_version, gen)
.map_err(|e| {
error!("Verschlüsselungsfehler beim Chunk-Flush: {e}");
FsError::GeneralFailure
},
)?;
})?;
if let Err(e) = self.db.write_chunk_and_update_size(
self.node_id,
@@ -1404,7 +1403,10 @@ mod tests {
assert!(is_leak_file_with_custom("audit.secret_log", &custom_rules));
assert!(is_leak_file_with_custom("debug_dump.txt", &custom_rules));
assert!(is_leak_file_with_custom("app_temp_cache.dat", &custom_rules));
assert!(is_leak_file_with_custom(
"app_temp_cache.dat",
&custom_rules
));
assert!(is_leak_file_with_custom("custom_exact.bin", &custom_rules));
// Normale Datei wird nicht blockiert
+3 -1
View File
@@ -855,7 +855,9 @@ pub fn get_available_disk_space<P: AsRef<std::path::Path>>(path: P) -> Option<u6
let dir = if p.is_dir() {
p.to_path_buf()
} else {
p.parent().map(|parent| parent.to_path_buf()).unwrap_or_else(|| p.to_path_buf())
p.parent()
.map(|parent| parent.to_path_buf())
.unwrap_or_else(|| p.to_path_buf())
};
let mut wide: Vec<u16> = dir.as_os_str().encode_wide().collect();
wide.push(0);
+38 -41
View File
@@ -792,13 +792,7 @@ fn test_m05_deniability_schema_equality_standard_vs_hidden() {
let db_std = Database::open(&path_std).expect("Open std db");
db_std
.init_schema(
&salt_std,
&kdf_params,
&wrapped_std,
&nonce_std,
&tag_std,
)
.init_schema(&salt_std, &kdf_params, &wrapped_std, &nonce_std, &tag_std)
.expect("Init std schema");
db_std.checkpoint().unwrap();
@@ -842,25 +836,26 @@ fn test_m05_deniability_schema_equality_standard_vs_hidden() {
let conn_std = rusqlite::Connection::open(&path_std).unwrap();
let conn_hidden = rusqlite::Connection::open(&path_hidden).unwrap();
let get_schema_elements = |conn: &rusqlite::Connection| -> Vec<(String, String, Option<String>)> {
let mut stmt = conn
.prepare(
"SELECT type, name, sql FROM sqlite_master
let get_schema_elements =
|conn: &rusqlite::Connection| -> Vec<(String, String, Option<String>)> {
let mut stmt = conn
.prepare(
"SELECT type, name, sql FROM sqlite_master
WHERE type IN ('table', 'index') AND name NOT LIKE 'sqlite_%'
ORDER BY type, name",
)
.unwrap();
let rows = stmt
.query_map([], |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, Option<String>>(2)?,
))
})
.unwrap();
rows.map(|r| r.unwrap()).collect()
};
)
.unwrap();
let rows = stmt
.query_map([], |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, Option<String>>(2)?,
))
})
.unwrap();
rows.map(|r| r.unwrap()).collect()
};
let schema_std = get_schema_elements(&conn_std);
let schema_hidden = get_schema_elements(&conn_hidden);
@@ -872,22 +867,25 @@ fn test_m05_deniability_schema_equality_standard_vs_hidden() {
// Spalten- und Typinformationen vergleichen
for table in &["meta", "nodes", "chunks"] {
let get_table_info = |conn: &rusqlite::Connection| -> Vec<(i64, String, String, i64, Option<String>, i64)> {
let mut stmt = conn.prepare(&format!("PRAGMA table_info({});", table)).unwrap();
let rows = stmt
.query_map([], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, i64>(3)?,
row.get::<_, Option<String>>(4)?,
row.get::<_, i64>(5)?,
))
})
.unwrap();
rows.map(|r| r.unwrap()).collect()
};
let get_table_info =
|conn: &rusqlite::Connection| -> Vec<(i64, String, String, i64, Option<String>, i64)> {
let mut stmt = conn
.prepare(&format!("PRAGMA table_info({});", table))
.unwrap();
let rows = stmt
.query_map([], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, i64>(3)?,
row.get::<_, Option<String>>(4)?,
row.get::<_, i64>(5)?,
))
})
.unwrap();
rows.map(|r| r.unwrap()).collect()
};
let cols_std = get_table_info(&conn_std);
let cols_hidden = get_table_info(&conn_hidden);
@@ -925,4 +923,3 @@ fn test_m05_deniability_schema_equality_standard_vs_hidden() {
let _ = std::fs::remove_file(&path_std);
let _ = std::fs::remove_file(&path_hidden);
}
+1 -4
View File
@@ -1798,10 +1798,7 @@ fn test_m02_release_profile_enables_overflow_checks() {
.split("[profile.release]")
.nth(1)
.expect("Must have [profile.release] section");
let release_block = release_section
.split('[')
.next()
.unwrap_or(release_section);
let release_block = release_section.split('[').next().unwrap_or(release_section);
assert!(
release_block.contains("overflow-checks = true"),
"M-02: [profile.release] must explicitly set overflow-checks = true to prevent integer overflow vulnerabilities"
-3
View File
@@ -309,6 +309,3 @@ async fn test_z04_webdav_quota_report() {
drop(db);
let _ = std::fs::remove_file(&container_path);
}
-2
View File
@@ -1187,5 +1187,3 @@ fn test_s09_pull_backup_and_update_conflict_safety() {
let _ = fs::remove_dir_all(&temp_root);
}
+3 -6
View File
@@ -704,9 +704,7 @@ fn test_k01_metadata_tampering_detected_by_verify() {
.unwrap();
// Knoten anlegen
let node = db
.create_node(1, "secret_financials.xlsx", false)
.unwrap();
let node = db.create_node(1, "secret_financials.xlsx", false).unwrap();
db.update_node_size_and_time(node.id, 50000, 1000).unwrap();
db.checkpoint().unwrap();
@@ -773,7 +771,8 @@ fn test_k01_metadata_tampering_detected_by_verify() {
#[test]
fn test_k01_upgrade_format_v2_to_v3() {
let temp_dir = std::env::temp_dir();
let db_path: PathBuf = temp_dir.join(format!("test_k01_upgrade_{}.sanctum", std::process::id()));
let db_path: PathBuf =
temp_dir.join(format!("test_k01_upgrade_{}.sanctum", std::process::id()));
if db_path.exists() {
let _ = std::fs::remove_file(&db_path);
}
@@ -996,5 +995,3 @@ async fn test_k02_chunk_replay_detected_by_vfs_and_crypto() {
let _ = std::fs::remove_file(&db_path);
}
+1 -6
View File
@@ -98,10 +98,7 @@ trusted comment: timestamp:1789750897\tfile:test_payload.txt\thashed\n\
assert!(sig_line.starts_with("RU"));
sig_line.replace_range(0..2, "RW");
let legacy_sig_text = format!(
"{}\n{}\n{}\n{}\n",
lines[0], sig_line, lines[2], lines[3]
);
let legacy_sig_text = format!("{}\n{}\n{}\n{}\n", lines[0], sig_line, lines[2], lines[3]);
let res = verify_minisign_signature(payload, &legacy_sig_text, SANCTUM_RELEASE_PUBKEY);
assert!(
@@ -194,5 +191,3 @@ zjZAS4uy1PH+0S9KQ0EU435lpBczU3lULyS5SkWf093iCEBc1ULiscwE5gWkEkex3ecPdRugopT+fKaZ
"Weder Primär- noch Backup-Schlüssel dürfen manipulierte Nutzdaten akzeptieren"
);
}