use chisel::Key;
use chisel::{Chisel, ChiselError, Options};
use tempfile::TempDir;
use zeroize::Zeroizing;
fn raw(b: u8) -> Key {
Key::Raw(Zeroizing::new(vec![b; 32]))
}
#[test]
fn add_key_lets_either_credential_open() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let h = {
let mut db = Chisel::open(&path, Options::default().encryption_key(raw(1))).unwrap();
db.begin().unwrap();
let h = db.allocate(b"secret").unwrap();
db.commit().unwrap();
db.add_key(&raw(1), &raw(2)).unwrap();
db.close().unwrap();
h
};
let db1 = Chisel::open(&path, Options::default().encryption_key(raw(1))).unwrap();
assert_eq!(db1.read(h).unwrap(), b"secret");
db1.close().unwrap();
let db2 = Chisel::open(&path, Options::default().encryption_key(raw(2))).unwrap();
assert_eq!(db2.read(h).unwrap(), b"secret");
db2.close().unwrap();
}
#[test]
fn add_key_wrong_existing_is_invalid_encryption_key() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let mut db = Chisel::open(&path, Options::default().encryption_key(raw(1))).unwrap();
let err = db.add_key(&raw(9), &raw(2)).unwrap_err();
assert!(
matches!(err, ChiselError::InvalidEncryptionKey),
"expected InvalidEncryptionKey, got {err:?}"
);
assert!(!db.is_poisoned());
}
#[test]
fn add_key_full_table_is_no_free_key_slot() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let mut db = Chisel::open(&path, Options::default().encryption_key(raw(1))).unwrap();
for k in 2u8..=8 {
db.add_key(&raw(1), &raw(k)).unwrap();
}
let err = db.add_key(&raw(1), &raw(99)).unwrap_err();
assert!(
matches!(err, ChiselError::NoFreeKeySlot),
"expected NoFreeKeySlot, got {err:?}"
);
assert!(!db.is_poisoned());
}
#[test]
fn add_key_plaintext_db_returns_encryption_not_supported() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let mut db = Chisel::open(&path, Options::default()).unwrap();
let err = db.add_key(&raw(1), &raw(2)).unwrap_err();
assert!(
matches!(err, ChiselError::EncryptionNotSupported),
"expected EncryptionNotSupported, got {err:?}"
);
assert!(!db.is_poisoned());
}
#[test]
fn rotate_key_revokes_old_and_admits_new() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let h = {
let mut db = Chisel::open(&path, Options::default().encryption_key(raw(1))).unwrap();
db.begin().unwrap();
let h = db.allocate(b"data").unwrap();
db.commit().unwrap();
db.rotate_key(&raw(1), &raw(2)).unwrap();
db.close().unwrap();
h
};
let err = Chisel::open(&path, Options::default().encryption_key(raw(1)))
.err()
.expect("old key must be rejected after rotate");
assert!(
matches!(err, ChiselError::InvalidEncryptionKey),
"expected InvalidEncryptionKey, got {err:?}"
);
let db = Chisel::open(&path, Options::default().encryption_key(raw(2))).unwrap();
assert!(!db.is_poisoned());
assert_eq!(db.read(h).unwrap(), b"data");
db.close().unwrap();
}
#[test]
fn rotate_key_plaintext_db_returns_encryption_not_supported() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let mut db = Chisel::open(&path, Options::default()).unwrap();
let err = db.rotate_key(&raw(1), &raw(2)).unwrap_err();
assert!(
matches!(err, ChiselError::EncryptionNotSupported),
"expected EncryptionNotSupported, got {err:?}"
);
assert!(!db.is_poisoned());
}
#[test]
fn rotate_key_wrong_old_is_invalid_encryption_key() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let mut db = Chisel::open(&path, Options::default().encryption_key(raw(1))).unwrap();
let err = db.rotate_key(&raw(9), &raw(2)).unwrap_err();
assert!(
matches!(err, ChiselError::InvalidEncryptionKey),
"expected InvalidEncryptionKey, got {err:?}"
);
assert!(!db.is_poisoned());
}
#[test]
fn rotate_key_full_table_is_no_free_key_slot() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let mut db = Chisel::open(&path, Options::default().encryption_key(raw(1))).unwrap();
for k in 2u8..=8 {
db.add_key(&raw(1), &raw(k)).unwrap();
}
let err = db.rotate_key(&raw(1), &raw(99)).unwrap_err();
assert!(
matches!(err, ChiselError::NoFreeKeySlot),
"expected NoFreeKeySlot, got {err:?}"
);
assert!(!db.is_poisoned());
}
#[test]
fn remove_key_leaves_others_working() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let h = {
let mut db = Chisel::open(&path, Options::default().encryption_key(raw(1))).unwrap();
db.begin().unwrap();
let h = db.allocate(b"v").unwrap();
db.commit().unwrap();
db.add_key(&raw(1), &raw(2)).unwrap();
db.remove_key(&raw(1)).unwrap(); db.close().unwrap();
h
};
let err = Chisel::open(&path, Options::default().encryption_key(raw(1)))
.err()
.expect("old key must be rejected after remove");
assert!(
matches!(err, ChiselError::InvalidEncryptionKey),
"expected InvalidEncryptionKey, got {err:?}"
);
let db = Chisel::open(&path, Options::default().encryption_key(raw(2))).unwrap();
assert_eq!(db.read(h).unwrap(), b"v");
db.close().unwrap();
}
#[test]
fn remove_last_key_is_rejected() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let mut db = Chisel::open(&path, Options::default().encryption_key(raw(1))).unwrap();
let err = db.remove_key(&raw(1)).unwrap_err();
assert!(
matches!(err, ChiselError::LastKeySlot),
"expected LastKeySlot, got {err:?}"
);
assert!(!db.is_poisoned());
drop(db);
let db2 = Chisel::open(&path, Options::default().encryption_key(raw(1))).unwrap();
assert!(!db2.is_poisoned());
db2.close().unwrap();
}
#[test]
fn remove_unknown_key_is_invalid_encryption_key() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let mut db = Chisel::open(&path, Options::default().encryption_key(raw(1))).unwrap();
db.add_key(&raw(1), &raw(2)).unwrap();
let err = db.remove_key(&raw(9)).unwrap_err();
assert!(
matches!(err, ChiselError::InvalidEncryptionKey),
"expected InvalidEncryptionKey, got {err:?}"
);
assert!(!db.is_poisoned());
}
#[test]
fn remove_key_plaintext_db_returns_encryption_not_supported() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db");
let mut db = Chisel::open(&path, Options::default()).unwrap();
let err = db.remove_key(&raw(1)).unwrap_err();
assert!(
matches!(err, ChiselError::EncryptionNotSupported),
"expected EncryptionNotSupported, got {err:?}"
);
assert!(!db.is_poisoned());
}