diff --git a/services/commtest/src/identity/device.rs b/services/commtest/src/identity/device.rs index 885f13abb..d00a97c90 100644 --- a/services/commtest/src/identity/device.rs +++ b/services/commtest/src/identity/device.rs @@ -1,235 +1,234 @@ use comm_opaque2::client::{Login, Registration}; use grpc_clients::identity::{ get_auth_client, get_unauthenticated_client, PlatformMetadata, }; use rand::{distributions::Alphanumeric, Rng}; use std::borrow::Cow; -use crate::identity::olm_account_infos::generate_random_olm_key; -use crate::identity::olm_account_infos::ClientPublicKeys; +use crate::identity::olm_account::{generate_random_olm_key, ClientPublicKeys}; use crate::service_addr; use grpc_clients::identity::protos::unauth::{ DeviceKeyUpload, DeviceType, Empty, IdentityKeyInfo, OpaqueLoginFinishRequest, OpaqueLoginStartRequest, Prekey, RegistrationFinishRequest, RegistrationStartRequest, VerifyUserAccessTokenRequest, }; pub const PLACEHOLDER_CODE_VERSION: u64 = 0; pub const DEVICE_TYPE: &str = "service"; const PASSWORD: &str = "pass"; pub struct DeviceInfo { pub username: String, pub user_id: String, pub device_id: String, pub access_token: String, } impl From<&DeviceInfo> for VerifyUserAccessTokenRequest { fn from(value: &DeviceInfo) -> Self { Self { user_id: value.user_id.to_string(), device_id: value.device_id.to_string(), access_token: value.access_token.to_string(), } } } /// Register a new user with a device. /// - Gives random username (returned by function). /// - Device type defaults to keyserver. /// - Device ID taken from `keys` (ed25519), see [`DEFAULT_CLIENT_KEYS`] pub async fn register_user_device( keys: Option<&ClientPublicKeys>, device_type: Option, ) -> DeviceInfo { register_user_device_with_device_list(keys, device_type, None).await } /// Same as [`register_user_device`] but with third param being a /// stringified signed device list JSON pub async fn register_user_device_with_device_list( keys: Option<&ClientPublicKeys>, device_type: Option, initial_device_list: Option, ) -> DeviceInfo { let username: String = rand::thread_rng() .sample_iter(&Alphanumeric) .take(7) .map(char::from) .collect(); let device_keys = keys.map(Cow::Borrowed).unwrap_or_default(); let example_payload = serde_json::to_string(&device_keys) .expect("Failed to serialize example payload"); // The ed25519 value from the olm payload let device_id = device_keys.device_id(); let device_type = device_type.unwrap_or(DeviceType::Keyserver); let mut client_registration = Registration::new(); let opaque_registration_request = client_registration.start(PASSWORD).unwrap(); let registration_start_request = RegistrationStartRequest { opaque_registration_request, username: username.to_string(), device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: example_payload.to_string(), payload_signature: "foo".to_string(), }), content_upload: Some(Prekey { prekey: generate_random_olm_key(), prekey_signature: "content_prekey_sig".to_string(), }), notif_upload: Some(Prekey { prekey: generate_random_olm_key(), prekey_signature: "notif_prekey_sig".to_string(), }), one_time_content_prekeys: Vec::new(), one_time_notif_prekeys: Vec::new(), device_type: device_type.into(), }), farcaster_id: None, initial_device_list: initial_device_list.unwrap_or_default(), }; let mut identity_client = get_unauthenticated_client( &service_addr::IDENTITY_GRPC.to_string(), PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let registration_start_response = identity_client .register_password_user_start(registration_start_request) .await .unwrap() .into_inner(); let opaque_registration_upload = client_registration .finish( PASSWORD, ®istration_start_response.opaque_registration_response, ) .unwrap(); let registration_finish_request = RegistrationFinishRequest { session_id: registration_start_response.session_id, opaque_registration_upload, }; let registration_finish_response = identity_client .register_password_user_finish(registration_finish_request) .await .unwrap() .into_inner(); DeviceInfo { username: username.to_string(), device_id: device_id.to_string(), user_id: registration_finish_response.user_id, access_token: registration_finish_response.access_token, } } /// Log in existing user with a device. /// - Tries to log in with given username (it has to be already registered) /// - Device type defaults to keyserver. /// - Device ID taken from `keys` (ed25519), see [`DEFAULT_CLIENT_KEYS`] pub async fn login_user_device( username: &str, keys: Option<&ClientPublicKeys>, device_type: Option, force: bool, ) -> DeviceInfo { let device_keys = keys.cloned().unwrap_or_default(); let example_payload = serde_json::to_string(&device_keys) .expect("Failed to serialize example payload"); // The ed25519 value from the olm payload let device_id = device_keys.device_id(); let device_type = device_type.unwrap_or(DeviceType::Keyserver); let mut client_login = Login::new(); let opaque_login_request = client_login.start(PASSWORD).unwrap(); let login_start_request = OpaqueLoginStartRequest { opaque_login_request, username: username.to_string(), device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: example_payload.to_string(), payload_signature: "foo".to_string(), }), content_upload: Some(Prekey { prekey: generate_random_olm_key(), prekey_signature: "content_prekey_sig".to_string(), }), notif_upload: Some(Prekey { prekey: generate_random_olm_key(), prekey_signature: "notif_prekey_sig".to_string(), }), one_time_content_prekeys: Vec::new(), one_time_notif_prekeys: Vec::new(), device_type: device_type.into(), }), force: Some(force), }; let mut identity_client = get_unauthenticated_client( &service_addr::IDENTITY_GRPC.to_string(), PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let login_start_response = identity_client .log_in_password_user_start(login_start_request) .await .unwrap() .into_inner(); let opaque_login_upload = client_login .finish(&login_start_response.opaque_login_response) .unwrap(); let login_finish_request = OpaqueLoginFinishRequest { session_id: login_start_response.session_id, opaque_login_upload, }; let login_finish_response = identity_client .log_in_password_user_finish(login_finish_request) .await .unwrap() .into_inner(); DeviceInfo { username: username.to_string(), device_id: device_id.to_string(), user_id: login_finish_response.user_id, access_token: login_finish_response.access_token, } } pub async fn logout_user_device(device_info: DeviceInfo) { let DeviceInfo { user_id, device_id, access_token, .. } = device_info; let mut client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), user_id, device_id, access_token, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldnt connect to auth identity service"); client .log_out_user(Empty {}) .await .expect("Failed to logout user"); } diff --git a/services/commtest/src/identity/mod.rs b/services/commtest/src/identity/mod.rs index f47ac03f8..96814f841 100644 --- a/services/commtest/src/identity/mod.rs +++ b/services/commtest/src/identity/mod.rs @@ -1,43 +1,2 @@ -use base64::Engine; -use ed25519_dalek::{ed25519::signature::Signer, Keypair, Signature}; -use rand::rngs::OsRng; - -use self::olm_account_infos::ClientPublicKeys; - pub mod device; -pub mod olm_account_infos; - -pub struct MockOlmAccount { - // primary account ed25519 keypair - signing_key: Keypair, -} - -impl MockOlmAccount { - pub fn new() -> Self { - let mut rng = OsRng {}; - let signing_key = Keypair::generate(&mut rng); - Self { signing_key } - } - - /// returns device public keys, required for device key upload - pub fn public_keys(&self) -> ClientPublicKeys { - let signing_public_key = self.signing_key.public.to_bytes(); - let ed25519 = base64::engine::general_purpose::STANDARD_NO_PAD - .encode(signing_public_key); - - ClientPublicKeys::new(ed25519) - } - - /// signs message, returns signature - pub fn sign_message(&self, message: &str) -> String { - let signature: Signature = self.signing_key.sign(message.as_bytes()); - base64::engine::general_purpose::STANDARD_NO_PAD - .encode(signature.to_bytes()) - } -} - -impl Default for MockOlmAccount { - fn default() -> Self { - Self::new() - } -} +pub mod olm_account; diff --git a/services/commtest/src/identity/olm_account_infos.rs b/services/commtest/src/identity/olm_account.rs similarity index 51% rename from services/commtest/src/identity/olm_account_infos.rs rename to services/commtest/src/identity/olm_account.rs index 1207b17ea..547bdcfdd 100644 --- a/services/commtest/src/identity/olm_account_infos.rs +++ b/services/commtest/src/identity/olm_account.rs @@ -1,50 +1,91 @@ -use rand::{distributions::Alphanumeric, Rng}; +use base64::Engine; +use ed25519_dalek::{ed25519::signature::Signer, Keypair, Signature}; +use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct IdentityPublicKeys { pub ed25519: String, pub curve25519: String, } +/// Represents device's identity key info #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct ClientPublicKeys { pub primary_identity_public_keys: IdentityPublicKeys, pub notification_identity_public_keys: IdentityPublicKeys, } impl ClientPublicKeys { /// Generates random keys with given `ed25519` primary account /// signing public key. Use [`ClientPublicKeys::default`] for random key. pub fn new(primary_signing_public_key: impl Into) -> Self { Self { primary_identity_public_keys: IdentityPublicKeys { ed25519: primary_signing_public_key.into(), curve25519: generate_random_olm_key(), }, notification_identity_public_keys: IdentityPublicKeys { ed25519: generate_random_olm_key(), curve25519: generate_random_olm_key(), }, } } pub fn device_id(&self) -> &str { &self.primary_identity_public_keys.ed25519 } } impl Default for ClientPublicKeys { fn default() -> Self { Self::new(generate_random_olm_key()) } } +/// Struct that simulates Olm account +pub struct MockOlmAccount { + // primary account ed25519 keypair + signing_key: Keypair, +} + +impl MockOlmAccount { + pub fn new() -> Self { + let mut rng = OsRng {}; + let signing_key = Keypair::generate(&mut rng); + Self { signing_key } + } + + /// returns device public keys, required for device key upload + pub fn public_keys(&self) -> ClientPublicKeys { + let signing_public_key = self.signing_key.public.to_bytes(); + let ed25519 = base64::engine::general_purpose::STANDARD_NO_PAD + .encode(signing_public_key); + + ClientPublicKeys::new(ed25519) + } + + /// signs message, returns signature + pub fn sign_message(&self, message: &str) -> String { + let signature: Signature = self.signing_key.sign(message.as_bytes()); + base64::engine::general_purpose::STANDARD_NO_PAD + .encode(signature.to_bytes()) + } +} + +impl Default for MockOlmAccount { + fn default() -> Self { + Self::new() + } +} + +/// Generates random 43-character ahlpanumeric string. +/// It simulates 32-byte (256bit) long base64-encoded data. pub fn generate_random_olm_key() -> String { rand::thread_rng() .sample_iter(&Alphanumeric) .take(43) .map(char::from) .collect() } diff --git a/services/commtest/tests/identity_access_tokens_tests.rs b/services/commtest/tests/identity_access_tokens_tests.rs index 1ec850bc9..7a0be3546 100644 --- a/services/commtest/tests/identity_access_tokens_tests.rs +++ b/services/commtest/tests/identity_access_tokens_tests.rs @@ -1,88 +1,88 @@ use commtest::identity::device::{ register_user_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; -use commtest::identity::MockOlmAccount; +use commtest::identity::olm_account::MockOlmAccount; use commtest::service_addr; use grpc_clients::identity::protos::unauth::{ Empty, ExistingDeviceLoginRequest, }; use grpc_clients::identity::PlatformMetadata; use grpc_clients::identity::{ get_unauthenticated_client, protos::unauth::VerifyUserAccessTokenRequest, }; #[tokio::test] async fn verify_access_token() { let identity_grpc_endpoint = service_addr::IDENTITY_GRPC.to_string(); let device_info = register_user_device(None, None).await; let mut identity_client = get_unauthenticated_client( &identity_grpc_endpoint, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let verify_request = VerifyUserAccessTokenRequest::from(&device_info); let response = identity_client .verify_user_access_token(verify_request) .await .unwrap(); assert!(response.into_inner().token_valid); } #[tokio::test] async fn refresh_token_test() { let identity_grpc_endpoint = service_addr::IDENTITY_GRPC.to_string(); let mut client = get_unauthenticated_client( &identity_grpc_endpoint, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let account = MockOlmAccount::new(); let client_keys = account.public_keys(); let user = register_user_device(Some(&client_keys), None).await; // refresh session let nonce = client .generate_nonce(Empty {}) .await .expect("failed to generate nonce") .into_inner() .nonce; let nonce_signature = account.sign_message(&nonce); let new_credentials = client .log_in_existing_device(ExistingDeviceLoginRequest { user_id: user.user_id.clone(), device_id: user.device_id.clone(), nonce, nonce_signature, }) .await .expect("LogInExistingDevice call failed") .into_inner(); // old token should now be invalid let old_token_result = client .verify_user_access_token(VerifyUserAccessTokenRequest::from(&user)) .await .expect("failed to verify token") .into_inner(); assert!(!old_token_result.token_valid); // new token should be valid let new_token_result = client .verify_user_access_token(VerifyUserAccessTokenRequest { user_id: new_credentials.user_id, access_token: new_credentials.access_token, device_id: user.device_id, }) .await .expect("failed to verify token") .into_inner(); assert!(new_token_result.token_valid); } diff --git a/services/commtest/tests/identity_device_list_tests.rs b/services/commtest/tests/identity_device_list_tests.rs index 1fbf117c0..d81ffe05b 100644 --- a/services/commtest/tests/identity_device_list_tests.rs +++ b/services/commtest/tests/identity_device_list_tests.rs @@ -1,558 +1,557 @@ use std::collections::{HashMap, HashSet}; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; use commtest::identity::device::{ login_user_device, logout_user_device, register_user_device, register_user_device_with_device_list, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; -use commtest::identity::olm_account_infos::ClientPublicKeys; -use commtest::identity::MockOlmAccount; +use commtest::identity::olm_account::{ClientPublicKeys, MockOlmAccount}; use commtest::service_addr; use grpc_clients::identity::authenticated::ChainedInterceptedAuthClient; use grpc_clients::identity::protos::auth::{ PeersDeviceListsRequest, UpdateDeviceListRequest, }; use grpc_clients::identity::protos::authenticated::GetDeviceListRequest; use grpc_clients::identity::DeviceType; use grpc_clients::identity::{get_auth_client, PlatformMetadata}; use serde::{Deserialize, Serialize}; // 1. register user with android device // 2. register a web device // 3. remove android device // 4. register ios device // 5. get device list - should have 4 updates: // - [android] // - [android, web] // - [web] // - [ios, web] - mobile should be first #[tokio::test] async fn test_device_list_rotation() { let device_keys_web = ClientPublicKeys::default(); let device_keys_ios = ClientPublicKeys::default(); let device_keys_android = ClientPublicKeys::default(); // Create viewer (user that doesn't change devices) let viewer = register_user_device(None, None).await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), viewer.user_id.clone(), viewer.device_id, viewer.access_token, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let android_device_id = device_keys_android.device_id(); let web_device_id = device_keys_web.device_id(); let ios_device_id = device_keys_ios.device_id(); // 1. Register user with primary Android device let android = register_user_device(Some(&device_keys_android), Some(DeviceType::Android)) .await; let user_id = android.user_id.clone(); let username = android.username.clone(); // 2. Log in a web device let _web = login_user_device( &username, Some(&device_keys_web), Some(DeviceType::Web), false, ) .await; // 3. Remove android device logout_user_device(android).await; // 4. Log in an iOS device let _ios = login_user_device( &username, Some(&device_keys_ios), Some(DeviceType::Ios), false, ) .await; // Get device list updates for the user let device_lists_response: Vec> = get_raw_device_list_history(&mut auth_client, &user_id) .await .into_iter() .map(|device_list| device_list.devices) .collect(); let expected_device_list: Vec> = vec![ vec![android_device_id.into()], vec![android_device_id.into(), web_device_id.into()], vec![web_device_id.into()], vec![ios_device_id.into(), web_device_id.into()], ]; assert_eq!(device_lists_response, expected_device_list); } #[tokio::test] async fn test_update_device_list_rpc() { // Register user with primary device let primary_device = register_user_device(None, None).await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), primary_device.user_id.clone(), primary_device.device_id, primary_device.access_token, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); // Initial device list check let initial_device_list = get_raw_device_list_history(&mut auth_client, &primary_device.user_id) .await .into_iter() .map(|device_list| device_list.devices) .next() .expect("Expected to get single device list update"); assert!(initial_device_list.len() == 1, "Expected single device"); let primary_device_id = initial_device_list[0].clone(); // perform update by adding a new device let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); let devices_payload = vec![primary_device_id, "device2".to_string()]; let update_payload = SignedDeviceList::from_raw_unsigned(&RawDeviceList { devices: devices_payload.clone(), timestamp: now.as_millis() as i64, }); let update_request = UpdateDeviceListRequest::from(&update_payload); auth_client .update_device_list(update_request) .await .expect("Update device list RPC failed"); // get device list again let last_device_list = get_raw_device_list_history(&mut auth_client, &primary_device.user_id) .await; let last_device_list = last_device_list .last() .expect("Failed to get last device list update"); // check that the device list is properly updated assert_eq!(last_device_list.devices, devices_payload); assert_eq!(last_device_list.timestamp, now.as_millis() as i64); } #[tokio::test] async fn test_device_list_signatures() { // device list history as list of tuples: (signature, devices) type DeviceListHistoryItem = (Option, Vec); // Register user with primary device let mut primary_account = MockOlmAccount::new(); let primary_device_keys = primary_account.public_keys(); let primary_device_id = primary_device_keys.device_id(); let user = register_user_device(Some(&primary_device_keys), Some(DeviceType::Ios)) .await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), user.user_id.clone(), user.device_id, user.access_token, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); // Perform unsigned update (add a new device) let first_update: DeviceListHistoryItem = { let update_payload = SignedDeviceList::from_raw_unsigned(&RawDeviceList::new(vec![ primary_device_id.to_string(), "device2".to_string(), ])); let update_request = UpdateDeviceListRequest::from(&update_payload); auth_client .update_device_list(update_request) .await .expect("Unsigned device list update failed"); ( update_payload.cur_primary_signature.clone(), update_payload.into_raw().devices, ) }; // now perform a update (remove a device), but sign the device list let second_update: DeviceListHistoryItem = { let update_payload = SignedDeviceList::create_signed( &RawDeviceList::new(vec![primary_device_id.to_string()]), &mut primary_account, None, ); let update_request = UpdateDeviceListRequest::from(&update_payload); auth_client .update_device_list(update_request) .await .expect("Signed device list update failed"); ( update_payload.cur_primary_signature.clone(), update_payload.into_raw().devices, ) }; // now perform a signed update (add a device), but with invalid signature { let mut update_payload = SignedDeviceList::create_signed( &RawDeviceList::new(vec![ primary_device_id.to_string(), "device3".to_string(), ]), &mut primary_account, None, ); // malfolm signature by replacing first characters update_payload .cur_primary_signature .as_mut() .expect("signature should be present") .replace_range(0..3, "foo"); let update_request = UpdateDeviceListRequest::from(&update_payload); auth_client .update_device_list(update_request) .await .expect_err("RPC should fail for invalid signature"); } // check the history to make sure our updates are correct let device_list_history = get_device_list_history(&mut auth_client, &user.user_id).await; let expected_devices_lists: Vec = vec![ (None, vec![primary_device_id.to_string()]), // auto-generated during registration first_update, second_update, ]; let actual_device_lists: Vec = device_list_history .into_iter() .map(|list| (list.cur_primary_signature.clone(), list.into_raw().devices)) .collect(); assert_eq!(actual_device_lists, expected_devices_lists); } #[tokio::test] async fn test_keyserver_force_login() { let device_keys_android = ClientPublicKeys::default(); let device_keys_keyserver_1 = ClientPublicKeys::default(); let device_keys_keyserver_2 = ClientPublicKeys::default(); // Create viewer (user that doesn't change devices) let viewer = register_user_device(None, None).await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), viewer.user_id.clone(), viewer.device_id, viewer.access_token, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let android_device_id = device_keys_android.device_id(); let keyserver_1_device_id = device_keys_keyserver_1.device_id(); let keyserver_2_device_id = device_keys_keyserver_2.device_id(); // 1. Register user with primary Android device let android = register_user_device(Some(&device_keys_android), Some(DeviceType::Android)) .await; let user_id = android.user_id.clone(); let username = android.username.clone(); // 2. Log in on keyserver 1 let _keyserver_1 = login_user_device( &username, Some(&device_keys_keyserver_1), Some(DeviceType::Keyserver), false, ) .await; // 3. Log in on keyserver 2 with force = true let _keyserver_2 = login_user_device( &username, Some(&device_keys_keyserver_2), Some(DeviceType::Keyserver), true, ) .await; // Get device list updates for the user let device_lists_response: Vec> = get_raw_device_list_history(&mut auth_client, &user_id) .await .into_iter() .map(|device_list| device_list.devices) .collect(); let expected_device_list: Vec> = vec![ vec![android_device_id.into()], vec![android_device_id.into(), keyserver_1_device_id.into()], vec![android_device_id.into()], vec![android_device_id.into(), keyserver_2_device_id.into()], ]; assert_eq!(device_lists_response, expected_device_list); } #[tokio::test] async fn test_device_list_multifetch() { // Create viewer (user that only auths request) let viewer = register_user_device(None, None).await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), viewer.user_id.clone(), viewer.device_id, viewer.access_token, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); // Register users and prepare expected device lists let mut expected_device_lists = HashMap::new(); for _ in 0..5 { let user = register_user_device(None, None).await; expected_device_lists.insert(user.user_id, vec![user.device_id]); } // Fetch device lists from server let user_ids: Vec<_> = expected_device_lists.keys().cloned().collect(); let request = PeersDeviceListsRequest { user_ids }; let response_device_lists = auth_client .get_device_lists_for_users(request) .await .expect("GetDeviceListsForUser RPC failed") .into_inner() .users_device_lists; // verify if response has the same user IDs as request let expected_user_ids: HashSet = expected_device_lists.keys().cloned().collect(); let response_user_ids: HashSet = response_device_lists.keys().cloned().collect(); let difference: HashSet<_> = expected_user_ids .symmetric_difference(&response_user_ids) .collect(); assert!(difference.is_empty(), "User IDs differ: {:?}", difference); // verify device list for each user for (user_id, expected_devices) in expected_device_lists { let response_payload = response_device_lists.get(&user_id).unwrap(); let returned_devices = SignedDeviceList::from_str(response_payload) .expect("failed to deserialize signed device list") .into_raw() .devices; assert_eq!( returned_devices, expected_devices, "Device list differs for user: {}, Expected {:?}, but got {:?}", user_id, expected_devices, returned_devices ); } } #[tokio::test] async fn test_initial_device_list() { // create signing account let mut primary_account = MockOlmAccount::new(); let primary_device_keys = primary_account.public_keys(); let primary_device_id = primary_device_keys.device_id().to_string(); // create initial device list let raw_device_list = RawDeviceList::new(vec![primary_device_id]); let signed_list = SignedDeviceList::create_signed( &raw_device_list, &mut primary_account, None, ); // register user with initial list let user = register_user_device_with_device_list( Some(&primary_device_keys), Some(DeviceType::Ios), Some(signed_list.as_json_string()), ) .await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), user.user_id.clone(), user.device_id, user.access_token, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let mut history = get_device_list_history(&mut auth_client, &user.user_id).await; let received_list = history.pop().expect("Received empty device list history"); assert!( history.is_empty(), "Device list history should have no more updates" ); assert_eq!( received_list.cur_primary_signature, signed_list.cur_primary_signature, "Signature mismatch" ); assert!(received_list.last_primary_signature.is_none()); assert_eq!(received_list.into_raw(), raw_device_list); } // See GetDeviceListResponse in identity_authenticated.proto // for details on the response format. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] #[allow(unused)] struct RawDeviceList { devices: Vec, timestamp: i64, } #[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct SignedDeviceList { raw_device_list: String, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] cur_primary_signature: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] last_primary_signature: Option, } impl RawDeviceList { fn new(devices: Vec) -> Self { let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); RawDeviceList { devices, timestamp: now.as_millis() as i64, } } fn as_json_string(&self) -> String { serde_json::to_string(self).expect("Failed to serialize RawDeviceList") } } impl SignedDeviceList { fn from_raw_unsigned(raw: &RawDeviceList) -> Self { Self { raw_device_list: raw.as_json_string(), cur_primary_signature: None, last_primary_signature: None, } } fn create_signed( raw: &RawDeviceList, cur_primary_account: &mut MockOlmAccount, last_primary_account: Option<&mut MockOlmAccount>, ) -> Self { let raw_device_list = raw.as_json_string(); let cur_primary_signature = cur_primary_account.sign_message(&raw_device_list); let last_primary_signature = last_primary_account .map(|account| account.sign_message(&raw_device_list)); Self { raw_device_list, cur_primary_signature: Some(cur_primary_signature), last_primary_signature, } } fn into_raw(self) -> RawDeviceList { self .raw_device_list .parse() .expect("Failed to parse raw device list") } fn as_json_string(&self) -> String { serde_json::to_string(self).expect("Failed to serialize SignedDeviceList") } } impl FromStr for SignedDeviceList { type Err = serde_json::Error; fn from_str(s: &str) -> Result { serde_json::from_str(s) } } impl FromStr for RawDeviceList { type Err = serde_json::Error; fn from_str(s: &str) -> Result { // The device list payload is sent as an escaped JSON payload. // Escaped double quotes need to be trimmed before attempting to deserialize serde_json::from_str(&s.replace(r#"\""#, r#"""#)) } } impl From<&SignedDeviceList> for UpdateDeviceListRequest { fn from(value: &SignedDeviceList) -> Self { Self { new_device_list: value.as_json_string(), } } } async fn get_device_list_history( client: &mut ChainedInterceptedAuthClient, user_id: &str, ) -> Vec { let request = GetDeviceListRequest { user_id: user_id.to_string(), since_timestamp: None, }; let response = client .get_device_list_for_user(request) .await .expect("Get device list request failed") .into_inner(); response .device_list_updates .into_iter() .map(|update| { SignedDeviceList::from_str(&update) .expect("Failed to parse device list update") }) .collect() } async fn get_raw_device_list_history( client: &mut ChainedInterceptedAuthClient, user_id: &str, ) -> Vec { get_device_list_history(client, user_id) .await .into_iter() .map(|signed| signed.into_raw()) .collect() } diff --git a/services/commtest/tests/identity_keyserver_tests.rs b/services/commtest/tests/identity_keyserver_tests.rs index 3d21404a6..944d8a288 100644 --- a/services/commtest/tests/identity_keyserver_tests.rs +++ b/services/commtest/tests/identity_keyserver_tests.rs @@ -1,76 +1,76 @@ use commtest::identity::device::{ register_user_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; -use commtest::identity::olm_account_infos::generate_random_olm_key; +use commtest::identity::olm_account::generate_random_olm_key; use commtest::service_addr; use grpc_clients::identity::PlatformMetadata; use grpc_clients::identity::{ get_auth_client, protos::authenticated::{ OutboundKeysForUserRequest, UploadOneTimeKeysRequest, }, }; #[tokio::test] async fn set_prekey() { let identity_grpc_endpoint = service_addr::IDENTITY_GRPC.to_string(); let device_info = register_user_device(None, None).await; let mut client = get_auth_client( &identity_grpc_endpoint, device_info.user_id.clone(), device_info.device_id, device_info.access_token, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let content_one_time_prekey = generate_random_olm_key(); let notif_one_time_prekey = generate_random_olm_key(); let upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: vec![content_one_time_prekey.clone()], notif_one_time_prekeys: vec![notif_one_time_prekey.clone()], }; client .upload_one_time_keys(upload_request) .await .expect("Failed to upload keys"); // Currently allowed to request your own outbound keys let keyserver_request = OutboundKeysForUserRequest { user_id: device_info.user_id.clone(), }; println!("Getting keyserver info for user, {}", device_info.user_id); let first_reponse = client .get_keyserver_keys(keyserver_request.clone()) .await .expect("First keyserver keys request failed") .into_inner() .keyserver_info .unwrap(); assert_eq!( first_reponse.one_time_content_prekey, Some(content_one_time_prekey) ); assert_eq!( first_reponse.one_time_notif_prekey, Some(notif_one_time_prekey) ); let second_reponse = client .get_keyserver_keys(keyserver_request) .await .expect("Second keyserver keys request failed") .into_inner() .keyserver_info .unwrap(); // The one time keys should be exhausted assert_eq!(second_reponse.one_time_content_prekey, None); assert_eq!(second_reponse.one_time_notif_prekey, None); } diff --git a/services/commtest/tests/identity_one_time_key_tests.rs b/services/commtest/tests/identity_one_time_key_tests.rs index 1ccd580af..323e7fcca 100644 --- a/services/commtest/tests/identity_one_time_key_tests.rs +++ b/services/commtest/tests/identity_one_time_key_tests.rs @@ -1,187 +1,187 @@ use commtest::identity::device::{ register_user_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; -use commtest::identity::olm_account_infos::generate_random_olm_key; +use commtest::identity::olm_account::generate_random_olm_key; use commtest::service_addr; use grpc_clients::identity::PlatformMetadata; use grpc_clients::identity::{ get_auth_client, protos::authenticated::OutboundKeysForUserRequest, protos::authenticated::UploadOneTimeKeysRequest, }; #[tokio::test] async fn upload_one_time_keys() { let device_info = register_user_device(None, None).await; let mut identity_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), device_info.user_id, device_info.device_id, device_info.access_token, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: vec![ generate_random_olm_key(), generate_random_olm_key(), ], notif_one_time_prekeys: vec![ generate_random_olm_key(), generate_random_olm_key(), ], }; identity_client .upload_one_time_keys(upload_request) .await .unwrap(); } #[tokio::test] async fn max_hundred_keys_in_ddb() { let device_info = register_user_device(None, None).await; let mut identity_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), device_info.user_id.clone(), device_info.device_id, device_info.access_token, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); // We expect these keys to be removed by the identity service before we // retrieve any OTKs let first_upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: vec![generate_random_olm_key()], notif_one_time_prekeys: vec![generate_random_olm_key()], }; identity_client .upload_one_time_keys(first_upload_request) .await .unwrap(); let mut expected_first_retrieved_content_key = None; let mut expected_first_retrieved_notif_key = None; let mut expected_second_retrieved_content_key = None; let mut expected_second_retrieved_notif_key = None; // Upload 100 content and notif one-time keys in batches of 20 keys for request_num in 0..5 { let content_keys: Vec<_> = (0..20).map(|_| generate_random_olm_key()).collect(); let notif_keys: Vec<_> = (0..20).map(|_| generate_random_olm_key()).collect(); if request_num == 0 { expected_first_retrieved_content_key = content_keys.first().cloned(); expected_first_retrieved_notif_key = notif_keys.first().cloned(); expected_second_retrieved_content_key = content_keys.get(5).cloned(); expected_second_retrieved_notif_key = notif_keys.get(5).cloned(); } let upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: content_keys, notif_one_time_prekeys: notif_keys, }; identity_client .upload_one_time_keys(upload_request) .await .unwrap(); } let keyserver_request = OutboundKeysForUserRequest { user_id: device_info.user_id, }; let first_keyserver_response = identity_client .get_keyserver_keys(keyserver_request.clone()) .await .unwrap() .into_inner() .keyserver_info .unwrap(); assert!(first_keyserver_response.one_time_content_prekey.is_some()); assert!(first_keyserver_response.one_time_notif_prekey.is_some()); assert_eq!( expected_first_retrieved_content_key, first_keyserver_response.one_time_content_prekey ); assert_eq!( expected_first_retrieved_notif_key, first_keyserver_response.one_time_notif_prekey ); // Upload 5 more keys for each account let content_keys: Vec<_> = (0..5).map(|_| generate_random_olm_key()).collect(); let notif_keys: Vec<_> = (0..5).map(|_| generate_random_olm_key()).collect(); let final_upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: content_keys, notif_one_time_prekeys: notif_keys, }; identity_client .upload_one_time_keys(final_upload_request) .await .unwrap(); let second_keyserver_response = identity_client .get_keyserver_keys(keyserver_request) .await .unwrap() .into_inner() .keyserver_info .unwrap(); assert!(second_keyserver_response.one_time_content_prekey.is_some()); assert!(second_keyserver_response.one_time_notif_prekey.is_some()); assert_eq!( expected_second_retrieved_content_key, second_keyserver_response.one_time_content_prekey ); assert_eq!( expected_second_retrieved_notif_key, second_keyserver_response.one_time_notif_prekey ); } #[tokio::test] async fn max_24_keys_per_account_per_upload() { let device_info = register_user_device(None, None).await; let mut identity_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), device_info.user_id, device_info.device_id, device_info.access_token, PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); // The limit is 24 keys per account per upload, so this should fail let content_keys = (0..26).map(|_| generate_random_olm_key()).collect(); let notif_keys = (0..20).map(|_| generate_random_olm_key()).collect(); let upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: content_keys, notif_one_time_prekeys: notif_keys, }; assert!(identity_client .upload_one_time_keys(upload_request) .await .is_err()); } diff --git a/services/commtest/tests/identity_tunnelbroker_tests.rs b/services/commtest/tests/identity_tunnelbroker_tests.rs index 4ac9be785..d1160b6d5 100644 --- a/services/commtest/tests/identity_tunnelbroker_tests.rs +++ b/services/commtest/tests/identity_tunnelbroker_tests.rs @@ -1,87 +1,87 @@ use commtest::identity::device::{ register_user_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; -use commtest::identity::olm_account_infos::generate_random_olm_key; +use commtest::identity::olm_account::generate_random_olm_key; use commtest::service_addr; use commtest::tunnelbroker::socket::{create_socket, receive_message}; use futures_util::StreamExt; use grpc_clients::identity::protos::authenticated::{ OutboundKeysForUserRequest, UploadOneTimeKeysRequest, }; use grpc_clients::identity::{get_auth_client, DeviceType, PlatformMetadata}; use tunnelbroker_messages::RefreshKeyRequest; #[tokio::test] async fn test_tunnelbroker_invalid_auth() { let mut device_info = register_user_device(None, None).await; device_info.access_token = "".to_string(); let socket = create_socket(&device_info).await; assert!(matches!(socket, Result::Err(_))) } #[tokio::test] async fn test_tunnelbroker_valid_auth() { let device_info = register_user_device(None, None).await; let mut socket = create_socket(&device_info).await.unwrap(); socket .next() .await .expect("Failed to receive response") .expect("Failed to read the response"); } #[tokio::test] async fn test_refresh_keys_request_upon_depletion() { // This function registers a device without uploading OTKs let keyserver = register_user_device(None, Some(DeviceType::Keyserver)).await; let mut client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), keyserver.user_id.clone(), keyserver.device_id.clone(), keyserver.access_token.clone(), PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let content_one_time_prekeys = vec![generate_random_olm_key()]; let notif_one_time_prekeys = vec![generate_random_olm_key()]; let upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys, notif_one_time_prekeys, }; client.upload_one_time_keys(upload_request).await.unwrap(); // Request outbound keys, which should trigger identity service to ask for more keys let keyserver_request = OutboundKeysForUserRequest { user_id: keyserver.user_id.clone(), }; println!("Getting keyserver info for user, {}", keyserver.user_id); let _first_reponse = client .get_keyserver_keys(keyserver_request.clone()) .await .expect("keyserver keys request failed") .into_inner() .keyserver_info .unwrap(); // The current threshold is 5, but we only uploaded only two. // Should receive request from Tunnelbroker to refresh key. // Create Tunnelbroker session as a keyserver let mut socket = create_socket(&keyserver).await.unwrap(); let response = receive_message(&mut socket).await.unwrap(); let serialized_response: RefreshKeyRequest = serde_json::from_str(&response).unwrap(); let expected_response = RefreshKeyRequest { device_id: keyserver.device_id.to_string(), number_of_keys: 5, }; assert_eq!(serialized_response, expected_response); }