use std::path::PathBuf; use sanctum::crypto::{ derive_kek, generate_dek, generate_salt, wrap_dek, KdfParams, MIN_MEMORY_COST_KIB, MIN_TIME_COST, }; use sanctum::storage::Database; #[test] fn test_reject_arbitrary_sqlite_database() { let temp_dir = std::env::temp_dir(); let db_path: PathBuf = temp_dir.join(format!("test_foreign_sqlite_{}.db", std::process::id())); if db_path.exists() { let _ = std::fs::remove_file(&db_path); } // Erstelle eine fremde SQLite-Datenbank mit beliebigen Daten { let conn = rusqlite::Connection::open(&db_path).unwrap(); conn.execute( "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)", [], ) .unwrap(); conn.execute("INSERT INTO users (name) VALUES ('Alice')", []).unwrap(); } // Sanctum Database::open muss die Datei sofort fail-closed ablehnen let res = Database::open(&db_path); match res { Err(err) => { let msg = err.to_string(); assert!( msg.contains("kein gültiger Sanctum-Container"), "Fehler muss ungültigen Container abweisen: {msg}" ); } Ok(_) => panic!("Fremde SQLite-DB ohne meta-Tabelle muss abgelehnt werden"), } let _ = std::fs::remove_file(&db_path); } #[test] fn test_reject_sqlite_database_with_invalid_magic() { let temp_dir = std::env::temp_dir(); let db_path: PathBuf = temp_dir.join(format!("test_invalid_magic_{}.db", std::process::id())); if db_path.exists() { let _ = std::fs::remove_file(&db_path); } // Erstelle SQLite DB mit 'meta' Tabelle, aber falscher Magic { let conn = rusqlite::Connection::open(&db_path).unwrap(); conn.execute( "CREATE TABLE meta ( slot_id INTEGER PRIMARY KEY, magic BLOB NOT NULL, version INTEGER NOT NULL, kdf_salt BLOB NOT NULL, kdf_params TEXT NOT NULL, wrapped_dek BLOB NOT NULL, header_nonce BLOB NOT NULL, header_tag BLOB NOT NULL );", [], ) .unwrap(); conn.execute( "INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag) VALUES (0, ?1, 2, ?2, '{}', ?3, ?4, ?5)", rusqlite::params![ b"EVIL_MAGIC", &[0u8; 16][..], &[0u8; 40][..], &[0u8; 12][..], &[0u8; 16][..] ], ) .unwrap(); } let res = Database::open(&db_path); match res { Err(err) => { let msg = err.to_string(); assert!( msg.contains("kein gültiger Sanctum-Container"), "Fehler muss ungültigen Magic-Header monieren: {msg}" ); } Ok(_) => panic!("Container mit ungültigen Magic Bytes muss abgelehnt werden"), } let _ = std::fs::remove_file(&db_path); } #[test] fn test_reject_out_of_bounds_kdf_params() { let temp_dir = std::env::temp_dir(); let db_path: PathBuf = temp_dir.join(format!("test_oob_kdf_{}.sanctum", std::process::id())); if db_path.exists() { let _ = std::fs::remove_file(&db_path); } // 1. Initialisiere gültigen Container let salt = generate_salt(); let kdf_params = KdfParams { memory_cost: MIN_MEMORY_COST_KIB, time_cost: MIN_TIME_COST, parallelism: 1, }; let kek = derive_kek("ValidPassword2026!", &salt, &kdf_params).unwrap(); let dek = generate_dek(); let (wrapped_dek, nonce, tag) = wrap_dek(&kek, &dek).unwrap(); let db = Database::open(&db_path).unwrap(); db.init_schema(&salt, &kdf_params, &wrapped_dek, &nonce, &tag).unwrap(); db.checkpoint().unwrap(); drop(db); // 2. Manipuliere KDF-Parameter in der meta-Tabelle auf gefährliche Werte (DoS: 16 GiB Memory) { let conn = rusqlite::Connection::open(&db_path).unwrap(); conn.execute( "UPDATE meta SET kdf_params = '{\"memory_cost\": 16777216, \"time_cost\": 3, \"parallelism\": 4}' WHERE slot_id = 0", [], ) .unwrap(); } // 3. Öffnen und read_slots() aufrufen: muss mit Kdf-Param-Fehler abbrechen let db_reopened = Database::open(&db_path).unwrap(); let slots_res = db_reopened.read_slots(); match slots_res { Err(err) => { let msg = err.to_string(); assert!( msg.contains("KDF-Parameter"), "Fehler muss KDF-Parameter monieren: {msg}" ); } Ok(_) => panic!("Überhöhte KDF-Parameter müssen fail-closed abgewehrt werden"), } // 4. Manipuliere KDF-Parameter auf zu schwache Werte (< MIN_MEMORY_COST_KIB) { let conn = rusqlite::Connection::open(&db_path).unwrap(); conn.execute( "UPDATE meta SET kdf_params = '{\"memory_cost\": 1024, \"time_cost\": 3, \"parallelism\": 4}' WHERE slot_id = 0", [], ) .unwrap(); } let slots_res2 = db_reopened.read_slots(); match slots_res2 { Err(err) => { let msg = err.to_string(); assert!( msg.contains("KDF-Parameter"), "Fehler muss KDF-Parameter monieren: {msg}" ); } Ok(_) => panic!("Zu schwache KDF-Parameter müssen fail-closed abgewehrt werden"), } let _ = std::fs::remove_file(&db_path); } #[test] fn test_reject_corrupted_slot_lengths() { let temp_dir = std::env::temp_dir(); let db_path: PathBuf = temp_dir.join(format!("test_slot_lengths_{}.sanctum", std::process::id())); if db_path.exists() { let _ = std::fs::remove_file(&db_path); } let salt = generate_salt(); let kdf_params = KdfParams { memory_cost: MIN_MEMORY_COST_KIB, time_cost: MIN_TIME_COST, parallelism: 1, }; let kek = derive_kek("ValidPassword2026!", &salt, &kdf_params).unwrap(); let dek = generate_dek(); let (wrapped_dek, nonce, tag) = wrap_dek(&kek, &dek).unwrap(); let db = Database::open(&db_path).unwrap(); db.init_schema(&salt, &kdf_params, &wrapped_dek, &nonce, &tag).unwrap(); db.checkpoint().unwrap(); drop(db); // Manipuliere Salt-Länge auf 8 Byte statt 16 Byte { let conn = rusqlite::Connection::open(&db_path).unwrap(); conn.execute( "UPDATE meta SET kdf_salt = ?1 WHERE slot_id = 0", [&[0u8; 8][..]], ) .unwrap(); } let db_reopened = Database::open(&db_path).unwrap(); let res = db_reopened.read_slots(); match res { Err(err) => { let msg = err.to_string(); assert!( msg.contains("Salt-Länge"), "Fehler muss fehlerhafte Salt-Länge monieren: {msg}" ); } Ok(_) => panic!("Ungültige Salt-Länge muss abgewiesen werden"), } let _ = std::fs::remove_file(&db_path); }