Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
121 lines
4.0 KiB
Rust
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());
|
|
}
|