Files
relicario/crates/relicario-core/tests/org.rs
2026-06-19 23:44:15 -04:00

121 lines
4.0 KiB
Rust

use relicario_core::{
generate_org_key, wrap_org_key, unwrap_org_key,
encrypt_org_manifest, decrypt_org_manifest,
OrgManifest, OrgManifestEntry, OrgMember, OrgMembers, OrgRole,
MemberId, ItemId,
};
use relicario_core::item_types::ItemType;
use rand::rngs::OsRng;
use rand::RngCore;
use zeroize::Zeroizing;
fn make_member_keypair() -> (Zeroizing<[u8; 32]>, String) {
let mut seed = [0u8; 32];
OsRng.fill_bytes(&mut seed);
let signing_key = ed25519_dalek::SigningKey::from_bytes(&seed);
let pubkey_openssh = ssh_key::PrivateKey::from(
ssh_key::private::Ed25519Keypair::from(&signing_key),
)
.public_key()
.to_openssh()
.expect("openssh");
(Zeroizing::new(seed), pubkey_openssh)
}
#[test]
fn org_key_wrap_unwrap_round_trip() {
let (seed, pubkey) = make_member_keypair();
let org_key = generate_org_key();
let wrapped = wrap_org_key(&org_key, &pubkey).expect("wrap");
let unwrapped = unwrap_org_key(&wrapped, &seed).expect("unwrap");
assert_eq!(*org_key, *unwrapped);
}
#[test]
fn revoked_member_cannot_decrypt_after_rotation() {
// Alice and Bob both get access
let (alice_seed, alice_pubkey) = make_member_keypair();
let (_bob_seed, bob_pubkey) = make_member_keypair();
let org_key = generate_org_key();
let _alice_wrapped = wrap_org_key(&org_key, &alice_pubkey).expect("wrap alice");
let _bob_wrapped = wrap_org_key(&org_key, &bob_pubkey).expect("wrap bob");
// Rotate: new key, only Bob gets re-wrapped
let new_org_key = generate_org_key();
let new_bob_wrapped = wrap_org_key(&new_org_key, &bob_pubkey).expect("wrap bob new");
// Alice tries to use old org_key — she can still decrypt old items,
// but new_bob_wrapped was encrypted with new_org_key, not org_key.
// Verify: unwrapping new_bob_wrapped with Alice's seed fails.
let result = unwrap_org_key(&new_bob_wrapped, &alice_seed);
assert!(result.is_err(), "Alice should not be able to unwrap Bob's new key blob");
}
#[test]
fn org_manifest_filter_restricts_to_granted_collections() {
let mut manifest = OrgManifest::new();
for (title, collection) in &[("A", "prod"), ("B", "dev"), ("C", "prod")] {
manifest.entries.push(OrgManifestEntry {
id: ItemId::new(),
r#type: ItemType::SecureNote,
title: title.to_string(),
tags: vec![],
modified: 0,
trashed_at: None,
collection: collection.to_string(),
});
}
let member = OrgMember {
member_id: MemberId::new(),
display_name: "Alice".into(),
role: OrgRole::Member,
ed25519_pubkey: String::new(),
collections: vec!["prod".into()],
added_at: 0,
added_by: MemberId::new(),
};
let filtered = manifest.filter_for_member(&member);
assert_eq!(filtered.entries.len(), 2);
assert!(filtered.entries.iter().all(|e| e.collection == "prod"));
}
#[test]
fn org_manifest_encrypt_decrypt_round_trip() {
let key = generate_org_key();
let mut manifest = OrgManifest::new();
manifest.entries.push(OrgManifestEntry {
id: ItemId::new(),
r#type: ItemType::Login,
title: "GitHub".into(),
tags: vec!["work".into()],
modified: 1748000000,
trashed_at: None,
collection: "eng-tools".into(),
});
let encrypted = encrypt_org_manifest(&manifest, &key).expect("encrypt");
let decrypted = decrypt_org_manifest(&encrypted, &key).expect("decrypt");
assert_eq!(decrypted.entries.len(), 1);
assert_eq!(decrypted.entries[0].title, "GitHub");
assert_eq!(decrypted.entries[0].collection, "eng-tools");
}
#[test]
fn members_validation_rejects_invalid_id() {
let mut members = OrgMembers::new();
members.members.push(OrgMember {
member_id: MemberId("not-hex-lol!!".to_string()),
display_name: "Bad".into(),
role: OrgRole::Member,
ed25519_pubkey: String::new(),
collections: vec![],
added_at: 0,
added_by: MemberId::new(),
});
assert!(members.validate().is_err());
}