#[test]
fn device_sync_authorization_changes_even_when_routing_peers_stay_the_same() {
let owner = Keys::generate();
let local = Keys::generate();
let sibling = Keys::generate();
let contact = Keys::generate();
let (mut core, _, _dir) = logged_in_test_core_with_updates("sync-authorization", &owner, &local);
configure_test_device_sync_profile(&mut core, &owner, &local, &sibling, None);
let owner_hex = owner.public_key().to_hex();
let mut foreign_roster = core.app_keys[&owner_hex].clone();
foreign_roster.owner_pubkey_hex = contact.public_key().to_hex();
core.app_keys.insert(contact.public_key().to_hex(), foreign_roster);
let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
let std::net::SocketAddr::V4(rendezvous) = socket.local_addr().unwrap() else {
unreachable!("IPv4 fixture");
};
drop(socket);
core.reconcile_device_sync_at_rendezvous_for_test(rendezvous);
assert!(core.device_sync_has_sibling_tcp_for_test());
core.app_keys.get_mut(&owner_hex).unwrap().devices.retain(|device| {
device.identity_pubkey_hex != sibling.public_key().to_hex()
});
core.reconcile_device_sync_at_rendezvous_for_test(rendezvous);
assert!(!core.device_sync_has_sibling_tcp_for_test(),
"revocation must retire the private service even when the device remains a routing contact");
assert!(core.device_sync_endpoint_for_test().is_some(), "shared routing remains available");
core.stop_device_sync();
}
#[test]
fn device_sync_locally_revoked_device_has_no_private_sync_peers() {
let owner = Keys::generate();
let local = Keys::generate();
let sibling = Keys::generate();
let (mut core, _, _dir) = logged_in_test_core_with_updates("sync-local-revocation", &owner, &local);
configure_test_device_sync_profile(&mut core, &owner, &local, &sibling, None);
assert!(core.device_sync_peer_is_authorized(&sibling.public_key().to_hex()));
core.app_keys.get_mut(&owner.public_key().to_hex()).unwrap().devices.retain(|device| {
device.identity_pubkey_hex != local.public_key().to_hex()
});
assert!(!core.device_sync_peer_is_authorized(&sibling.public_key().to_hex()));
let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
let std::net::SocketAddr::V4(rendezvous) = socket.local_addr().unwrap() else {
unreachable!("IPv4 fixture");
};
drop(socket);
core.reconcile_device_sync_at_rendezvous_for_test(rendezvous);
assert!(!core.device_sync_has_sibling_tcp_for_test(),
"a revoked local device must not start a private history service");
core.stop_device_sync();
}
#[test]
fn device_sync_stale_runtime_cannot_export_metadata_to_revoked_sibling() {
let owner = Keys::generate();
let local = Keys::generate();
let sibling = Keys::generate();
let (mut core, _, _dir) = logged_in_test_core_with_updates("sync-stale-runtime", &owner, &local);
configure_test_device_sync_profile(&mut core, &owner, &local, &sibling, None);
let endpoint = Arc::new(core.runtime.block_on(
fips_core::FipsEndpoint::builder().without_system_tun().bind()).unwrap());
let (sender, records) = DeviceSyncTcpSender::test_channel(64, 64 * 1024);
core.install_device_sync_sender_for_test(endpoint.clone(), sender, vec![test_fips_peer(&sibling)]);
core.broadcast_device_sync_snapshot();
assert!(records.try_recv().is_ok(), "the authorized positive control must export metadata");
while records.try_recv().is_ok() {}
core.app_keys.get_mut(&owner.public_key().to_hex()).unwrap().devices.retain(|device| {
device.identity_pubkey_hex != sibling.public_key().to_hex()
});
core.broadcast_device_sync_snapshot();
assert!(records.is_empty(), "current account authorization must gate every metadata export");
core.runtime.block_on(endpoint.shutdown()).unwrap();
}
#[test]
fn device_sync_private_negentropy_rejects_other_accounts_and_revoked_devices() {
let owner = Keys::generate();
let local = Keys::generate();
let sibling = Keys::generate();
let foreign_owner = Keys::generate();
let foreign_device = Keys::generate();
let unknown = Keys::generate();
let chat = Keys::generate().public_key().to_hex();
let (mut core, _, _dir) = logged_in_test_core_with_updates("sync-private-protocol", &owner, &local);
configure_test_device_sync_profile(&mut core, &owner, &local, &sibling, None);
let owner_hex = owner.public_key().to_hex();
let sibling_hex = sibling.public_key().to_hex();
let mut foreign_roster = core.app_keys[&owner_hex].clone();
foreign_roster.owner_pubkey_hex = foreign_owner.public_key().to_hex();
foreign_roster.devices[1].identity_pubkey_hex = foreign_device.public_key().to_hex();
core.app_keys.insert(foreign_owner.public_key().to_hex(), foreign_roster);
let original_roster = core.app_keys[&owner_hex].clone();
let endpoint = Arc::new(core.runtime.block_on(
fips_core::FipsEndpoint::builder().without_system_tun().bind()).unwrap());
let (sender, records) = DeviceSyncTcpSender::test_channel(64, 64 * 1024);
core.install_device_sync_sender_for_test(endpoint.clone(), sender, vec![test_fips_peer(&sibling)]);
let mut engine = nostr_pubsub_reconcile::Session::new(
[], nostr_pubsub_reconcile::Filter { since: 0, until: 0 }, Default::default()).unwrap();
let frame = engine.initiate().unwrap().iter().map(|byte| format!("{byte:02x}")).collect::<String>();
let session = "ab".repeat(16);
let request = serde_json::json!({"type":"request","v":1,"rosterAt":100,"recordReconcile":1});
let open = serde_json::json!({"type":"historyOpen","v":1,"scope":"state","session":session,"since":0,"until":0,"frame":frame});
for packet in [&request, &open] {
core.handle_device_sync_packet(&sibling_hex, DEVICE_SYNC_PORT, &serde_json::to_vec(packet).unwrap());
}
assert_eq!(core.device_history_session_count_for_test(), 1,
"an authorized sibling must open real negentropy reconciliation");
assert!(records.try_recv().is_ok());
while records.try_recv().is_ok() {}
let snapshot = serde_json::json!({
"type":"snapshot","v":1,"rosterAt":100,"chats":[{"id":chat,"updatedAt":200}],
"appKeys":[],"groups":[],"messages":[{"chatId":chat,"id":"private-sync-message", "body":"cHJpdmF0ZQ==","author":chat,"createdAt":200}]
});
let mut forged_roster = snapshot.clone();
forged_roster["appKeys"] = serde_json::json!([{
"ownerPubkey":owner_hex,"createdAt":300,"devices":[
{"identityPubkey":local.public_key().to_hex(),"createdAt":1},
{"identityPubkey":foreign_device.public_key().to_hex(),"createdAt":100}]
}]);
let attacks = vec![
request.clone(), open.clone(), forged_roster, snapshot.clone(),
serde_json::json!({"type":"resyncRequired","v":1}),
serde_json::json!({"type":"pageEnd","v":1,"rosterAt":100,"next":null,"recordReconcile":1}),
serde_json::json!({"type":"historyFrame","v":1,"session":session,"frame":frame}),
serde_json::json!({"type":"historyNeed","v":1,"session":session,"ids":["00".repeat(32)]}),
serde_json::json!({"type":"historyRecords","v":1,"session":session,"records":[{"type":"message","message":snapshot["messages"][0]}],"requested":["00".repeat(32)]}),
serde_json::json!({"type":"historyOverflow","v":1,"session":session}),
serde_json::json!({"type":"historyDone","v":1,"session":session}),
serde_json::json!({"type":"historyPolicy","v":1,"linkAt":100,"since":0,"linkId":"cd".repeat(32)}),
serde_json::json!({"type":"historyComplete","v":1,"linkAt":100,"linkId":"cd".repeat(32)}),
];
for source in [foreign_owner.public_key().to_hex(), foreign_device.public_key().to_hex(),
unknown.public_key().to_hex(), owner_hex.clone(), local.public_key().to_hex(),
local.public_key().to_hex().to_uppercase()] {
assert!(!core.device_sync_peer_is_authorized(&source));
for packet in &attacks {
core.handle_device_sync_packet(&source, DEVICE_SYNC_PORT, &serde_json::to_vec(packet).unwrap());
}
assert!(records.is_empty(), "unapproved source must never receive a private response");
assert!(!core.threads.contains_key(&chat), "unapproved source must never import a private record");
assert_eq!(core.app_keys[&owner_hex], original_roster, "an unapproved source cannot authorize itself");
assert_eq!(core.device_history_session_count_for_test(), 1, "another account cannot hijack or cancel a sibling's session");
}
core.handle_device_sync_packet(&sibling_hex, DEVICE_SYNC_PORT, &serde_json::to_vec(&snapshot).unwrap());
assert!(has_device_sync_message(&core, &chat, "private-sync-message"),
"the same private snapshot must import from the authorized sibling");
while records.try_recv().is_ok() {}
core.app_keys.get_mut(&owner_hex).unwrap().devices.retain(|device| {
device.identity_pubkey_hex != local.public_key().to_hex()
});
for packet in &attacks {
core.handle_device_sync_packet(&sibling_hex, DEVICE_SYNC_PORT, &serde_json::to_vec(packet).unwrap());
}
assert!(records.is_empty(), "local revocation closes both directions of private sync");
assert_eq!(core.device_history_session_count_for_test(), 0);
core.runtime.block_on(endpoint.shutdown()).unwrap();
}
#[test]
fn restored_authorization_projection_defers_transport_until_complete_roster() {
let owner = Keys::generate();
let local = Keys::generate();
let sibling = Keys::generate();
let contact_a = Keys::generate();
let device_a = Keys::generate();
let contact_b = Keys::generate();
let device_b = Keys::generate();
let (mut core, _, dir) =
logged_in_test_core_with_updates("restore-authorization-runtime", &owner, &local);
core.preferences.nostr_relay_urls.clear();
let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
let std::net::SocketAddr::V4(rendezvous) = socket.local_addr().unwrap() else {
unreachable!("IPv4 fixture");
};
drop(socket);
core.test_fips_rendezvous_addr = Some(rendezvous);
let created_at = unix_now().get() + 1;
let rosters = [
(&owner, vec![local.public_key(), sibling.public_key()]),
(&contact_a, vec![device_a.public_key()]),
(&contact_b, vec![device_b.public_key()]),
];
for (signer, devices) in &rosters {
let event = AppKeys::new(
devices
.iter()
.copied()
.map(|device| DeviceEntry::new(device, created_at))
.collect(),
)
.get_event_at(signer.public_key(), created_at)
.sign_with_keys(signer)
.unwrap();
core.capture_authorization_event(&event).unwrap();
}
core.state.busy.restoring_session = true;
core.restore_device_sync_record_projection();
for (signer, devices) in &rosters {
let restored = &core.app_keys[&signer.public_key().to_hex()];
for device in devices {
assert!(restored
.devices
.iter()
.any(|entry| entry.identity_pubkey_hex == device.to_hex()));
assert!(core
.protocol_engine
.as_ref()
.unwrap()
.has_device_roster_entry_for_owner(signer.public_key(), *device));
}
}
assert!(core.device_sync_peer_is_authorized(&sibling.public_key().to_hex()));
assert!(!core.device_sync_peer_is_authorized(&device_a.public_key().to_hex()));
assert!(
core.device_sync.is_none(),
"replay must not start FIPS per head"
);
assert!(core.attachment_service.is_none());
assert!(
!dir.path().join("attachments").exists(),
"intermediate authorization must not open the attachment LMDB environment"
);
core.start_session_inner(
owner.public_key(),
Some(owner.clone()),
local,
true,
true,
false,
)
.unwrap();
assert!(core.state.busy.restoring_session);
assert!(core.device_sync_has_sibling_tcp_for_test());
let runtime = core.device_sync.as_ref().unwrap();
let expected_peers = [
sibling.public_key(),
device_a.public_key(),
device_b.public_key(),
]
.into_iter()
.collect::<HashSet<_>>();
assert_eq!(
runtime
.peer_advert_authors
.iter()
.copied()
.collect::<HashSet<_>>(),
expected_peers
);
let endpoint = runtime.endpoint.clone();
let store_id = core.attachment_service.as_ref().unwrap().store.id;
core.state.busy.restoring_session = false;
core.reconcile_device_sync();
assert!(Arc::ptr_eq(
&endpoint,
&core.device_sync.as_ref().unwrap().endpoint
));
assert_eq!(core.attachment_service.as_ref().unwrap().store.id, store_id);
core.stop_device_sync();
core.stop_attachment_service();
}