217 lines
7.0 KiB
Rust
217 lines
7.0 KiB
Rust
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);
|
|
}
|