diff --git a/Cargo.toml b/Cargo.toml index 21f74d6..32212cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" + diff --git a/src/carrier.rs b/src/carrier.rs index b5ac0c7..5d48aed 100644 --- a/src/carrier.rs +++ b/src/carrier.rs @@ -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); diff --git a/src/crypto.rs b/src/crypto.rs index 1de72ed..8f45c7a 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -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)); diff --git a/src/mount.rs b/src/mount.rs index 86c3499..f8503c3 100644 --- a/src/mount.rs +++ b/src/mount.rs @@ -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")); } } diff --git a/src/storage.rs b/src/storage.rs index ca54f28..d7fd078 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -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); } } - diff --git a/src/sync.rs b/src/sync.rs index ad9e2a2..2c5e07d 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -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; diff --git a/src/upgrade.rs b/src/upgrade.rs index f315f13..4e2c71b 100644 --- a/src/upgrade.rs +++ b/src/upgrade.rs @@ -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 { - 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)) } diff --git a/src/verify.rs b/src/verify.rs index 449cc0e..38908f5 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -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( diff --git a/src/vfs.rs b/src/vfs.rs index c593804..df96d3b 100644 --- a/src/vfs.rs +++ b/src/vfs.rs @@ -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 diff --git a/src/windows.rs b/src/windows.rs index 52cadf3..49f69eb 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -855,7 +855,9 @@ pub fn get_available_disk_space>(path: P) -> Option = dir.as_os_str().encode_wide().collect(); wide.push(0); diff --git a/tests/carrier_model_a_test.rs b/tests/carrier_model_a_test.rs index 060cb09..d58e3bc 100644 --- a/tests/carrier_model_a_test.rs +++ b/tests/carrier_model_a_test.rs @@ -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)> { - let mut stmt = conn - .prepare( - "SELECT type, name, sql FROM sqlite_master + let get_schema_elements = + |conn: &rusqlite::Connection| -> Vec<(String, String, Option)> { + 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>(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>(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, 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>(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, 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>(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); } - diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 2e7d082..60a5d4d 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -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" diff --git a/tests/mount_security_test.rs b/tests/mount_security_test.rs index 496b845..9a002fe 100644 --- a/tests/mount_security_test.rs +++ b/tests/mount_security_test.rs @@ -309,6 +309,3 @@ async fn test_z04_webdav_quota_report() { drop(db); let _ = std::fs::remove_file(&container_path); } - - - diff --git a/tests/sync_test.rs b/tests/sync_test.rs index 4e17152..936b2e7 100644 --- a/tests/sync_test.rs +++ b/tests/sync_test.rs @@ -1187,5 +1187,3 @@ fn test_s09_pull_backup_and_update_conflict_safety() { let _ = fs::remove_dir_all(&temp_root); } - - diff --git a/tests/untrusted_container_test.rs b/tests/untrusted_container_test.rs index cb95302..58ecf1d 100644 --- a/tests/untrusted_container_test.rs +++ b/tests/untrusted_container_test.rs @@ -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); } - - diff --git a/tests/upgrade_security_test.rs b/tests/upgrade_security_test.rs index 5f14e71..b057905 100644 --- a/tests/upgrade_security_test.rs +++ b/tests/upgrade_security_test.rs @@ -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" ); } - -