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()); }