#[cfg(not(feature = "fips"))]
use citadel::Argon2Profile;
use citadel::{DatabaseBuilder, KdfAlgorithm};
#[test]
fn pbkdf2_create_open_reopen() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("pbkdf2.citadel");
let passphrase = b"pbkdf2-test-passphrase";
{
let db = DatabaseBuilder::new(&db_path)
.passphrase(passphrase)
.kdf_algorithm(KdfAlgorithm::Pbkdf2HmacSha256)
.pbkdf2_iterations(600_000)
.cache_size(64)
.create()
.unwrap();
let mut wtx = db.begin_write().unwrap();
wtx.insert(b"key1", b"value1").unwrap();
wtx.commit().unwrap();
let stats = db.stats();
assert_eq!(stats.entry_count, 1);
}
{
let db = DatabaseBuilder::new(&db_path)
.passphrase(passphrase)
.open()
.unwrap();
let mut rtx = db.begin_read();
let val = rtx.get(b"key1").unwrap();
assert_eq!(val, Some(b"value1".to_vec()));
}
{
let result = DatabaseBuilder::new(&db_path).passphrase(b"wrong").open();
assert!(result.is_err());
}
}
#[test]
fn pbkdf2_named_tables() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("pbkdf2_tables.citadel");
let db = DatabaseBuilder::new(&db_path)
.passphrase(b"test")
.kdf_algorithm(KdfAlgorithm::Pbkdf2HmacSha256)
.pbkdf2_iterations(600_000)
.cache_size(64)
.create()
.unwrap();
let mut wtx = db.begin_write().unwrap();
wtx.create_table(b"users").unwrap();
wtx.table_insert(b"users", b"alice", b"admin").unwrap();
wtx.create_table(b"logs").unwrap();
wtx.table_insert(b"logs", b"entry1", b"logged in").unwrap();
wtx.commit().unwrap();
let mut rtx = db.begin_read();
assert_eq!(
rtx.table_get(b"users", b"alice").unwrap(),
Some(b"admin".to_vec())
);
assert_eq!(
rtx.table_get(b"logs", b"entry1").unwrap(),
Some(b"logged in".to_vec())
);
}
#[test]
fn pbkdf2_in_memory() {
let db = DatabaseBuilder::new("unused_path")
.passphrase(b"inmem-test")
.kdf_algorithm(KdfAlgorithm::Pbkdf2HmacSha256)
.pbkdf2_iterations(600_000)
.cache_size(64)
.create_in_memory()
.unwrap();
let mut wtx = db.begin_write().unwrap();
for i in 0..100u32 {
wtx.insert(&i.to_be_bytes(), &(i * 2).to_be_bytes())
.unwrap();
}
wtx.commit().unwrap();
let mut rtx = db.begin_read();
for i in 0..100u32 {
let val = rtx.get(&i.to_be_bytes()).unwrap().unwrap();
assert_eq!(val, (i * 2).to_be_bytes());
}
assert_eq!(db.stats().entry_count, 100);
}
#[test]
fn pbkdf2_change_passphrase() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("pbkdf2_rekey.citadel");
let old_pass = b"old-password";
let new_pass = b"new-password";
{
let db = DatabaseBuilder::new(&db_path)
.passphrase(old_pass)
.kdf_algorithm(KdfAlgorithm::Pbkdf2HmacSha256)
.pbkdf2_iterations(600_000)
.cache_size(64)
.create()
.unwrap();
let mut wtx = db.begin_write().unwrap();
wtx.insert(b"secret", b"data").unwrap();
wtx.commit().unwrap();
db.change_passphrase(old_pass, new_pass).unwrap();
}
{
let result = DatabaseBuilder::new(&db_path).passphrase(old_pass).open();
assert!(result.is_err());
}
{
let db = DatabaseBuilder::new(&db_path)
.passphrase(new_pass)
.open()
.unwrap();
let mut rtx = db.begin_read();
assert_eq!(rtx.get(b"secret").unwrap(), Some(b"data".to_vec()));
}
}
#[test]
#[cfg(not(feature = "fips"))]
fn default_kdf_is_argon2id() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("default_kdf.citadel");
let db = DatabaseBuilder::new(&db_path)
.passphrase(b"test")
.argon2_profile(Argon2Profile::Iot)
.cache_size(64)
.create()
.unwrap();
let mut wtx = db.begin_write().unwrap();
wtx.insert(b"key", b"val").unwrap();
wtx.commit().unwrap();
drop(db);
let db = DatabaseBuilder::new(&db_path)
.passphrase(b"test")
.open()
.unwrap();
let mut rtx = db.begin_read();
assert_eq!(rtx.get(b"key").unwrap(), Some(b"val".to_vec()));
}
#[test]
#[cfg(not(feature = "fips"))]
fn pbkdf2_and_argon2_produce_different_databases() {
let dir = tempfile::tempdir().unwrap();
let passphrase = b"same-passphrase";
let db_argon2 = DatabaseBuilder::new(dir.path().join("argon2.citadel"))
.passphrase(passphrase)
.argon2_profile(Argon2Profile::Iot)
.cache_size(64)
.create()
.unwrap();
let db_pbkdf2 = DatabaseBuilder::new(dir.path().join("pbkdf2.citadel"))
.passphrase(passphrase)
.kdf_algorithm(KdfAlgorithm::Pbkdf2HmacSha256)
.pbkdf2_iterations(600_000)
.cache_size(64)
.create()
.unwrap();
{
let mut wtx = db_argon2.begin_write().unwrap();
wtx.insert(b"key", b"value").unwrap();
wtx.commit().unwrap();
}
{
let mut wtx = db_pbkdf2.begin_write().unwrap();
wtx.insert(b"key", b"value").unwrap();
wtx.commit().unwrap();
}
drop(db_argon2);
drop(db_pbkdf2);
let raw_argon2 = std::fs::read(dir.path().join("argon2.citadel")).unwrap();
let raw_pbkdf2 = std::fs::read(dir.path().join("pbkdf2.citadel")).unwrap();
if raw_argon2.len() > 512 && raw_pbkdf2.len() > 512 {
assert_ne!(
&raw_argon2[512..],
&raw_pbkdf2[512..],
"different KDFs should produce different encryption keys and ciphertext"
);
}
}
#[test]
fn pbkdf2_backup_and_integrity() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("pbkdf2_backup.citadel");
let backup_path = dir.path().join("pbkdf2_backup_copy.citadel");
let db = DatabaseBuilder::new(&db_path)
.passphrase(b"test")
.kdf_algorithm(KdfAlgorithm::Pbkdf2HmacSha256)
.pbkdf2_iterations(600_000)
.cache_size(64)
.create()
.unwrap();
let mut wtx = db.begin_write().unwrap();
for i in 0..50u32 {
wtx.insert(&i.to_be_bytes(), &format!("val-{i}").into_bytes())
.unwrap();
}
wtx.commit().unwrap();
let report = db.integrity_check().unwrap();
assert!(report.errors.is_empty());
db.backup(&backup_path).unwrap();
let backup_db = DatabaseBuilder::new(&backup_path)
.passphrase(b"test")
.open()
.unwrap();
let mut rtx = backup_db.begin_read();
for i in 0..50u32 {
let val = rtx.get(&i.to_be_bytes()).unwrap().unwrap();
assert_eq!(val, format!("val-{i}").into_bytes());
}
}
#[cfg(feature = "fips")]
mod fips_tests {
use citadel::core::types::CipherId;
use citadel::{DatabaseBuilder, KdfAlgorithm};
#[test]
fn fips_rejects_argon2id() {
let dir = tempfile::tempdir().unwrap();
let result = DatabaseBuilder::new(dir.path().join("fips.citadel"))
.passphrase(b"test")
.kdf_algorithm(KdfAlgorithm::Argon2id)
.cache_size(64)
.create();
let err = result.unwrap_err().to_string();
assert!(err.contains("FIPS"), "error should mention FIPS: {err}");
}
#[test]
fn fips_rejects_default_kdf() {
let dir = tempfile::tempdir().unwrap();
let result = DatabaseBuilder::new(dir.path().join("fips_default.citadel"))
.passphrase(b"test")
.cache_size(64)
.create();
assert!(result.is_err());
}
#[test]
fn fips_rejects_chacha20() {
let dir = tempfile::tempdir().unwrap();
let result = DatabaseBuilder::new(dir.path().join("fips_chacha.citadel"))
.passphrase(b"test")
.kdf_algorithm(KdfAlgorithm::Pbkdf2HmacSha256)
.pbkdf2_iterations(600_000)
.cipher(CipherId::ChaCha20)
.cache_size(64)
.create();
let err = result.unwrap_err().to_string();
assert!(err.contains("FIPS"), "error should mention FIPS: {err}");
}
#[test]
fn fips_accepts_pbkdf2_aes() {
let dir = tempfile::tempdir().unwrap();
let db = DatabaseBuilder::new(dir.path().join("fips_ok.citadel"))
.passphrase(b"test")
.kdf_algorithm(KdfAlgorithm::Pbkdf2HmacSha256)
.pbkdf2_iterations(600_000)
.cache_size(64)
.create()
.unwrap();
let mut wtx = db.begin_write().unwrap();
wtx.insert(b"key", b"value").unwrap();
wtx.commit().unwrap();
let mut rtx = db.begin_read();
assert_eq!(rtx.get(b"key").unwrap(), Some(b"value".to_vec()));
}
#[test]
fn fips_in_memory_accepts_pbkdf2() {
let db = DatabaseBuilder::new("unused")
.passphrase(b"test")
.kdf_algorithm(KdfAlgorithm::Pbkdf2HmacSha256)
.pbkdf2_iterations(600_000)
.cache_size(64)
.create_in_memory()
.unwrap();
let mut wtx = db.begin_write().unwrap();
wtx.insert(b"k", b"v").unwrap();
wtx.commit().unwrap();
}
#[test]
fn fips_in_memory_rejects_argon2id() {
let result = DatabaseBuilder::new("unused")
.passphrase(b"test")
.cache_size(64)
.create_in_memory();
assert!(result.is_err());
}
}