diff --git a/services/identity/src/client_service.rs b/services/identity/src/client_service.rs --- a/services/identity/src/client_service.rs +++ b/services/identity/src/client_service.rs @@ -22,10 +22,10 @@ RegistrationFinishRequest, RegistrationFinishResponse, RegistrationStartRequest, RegistrationStartResponse, RemoveReservedUsernameRequest, ReservedRegistrationStartRequest, - UpdateUserPasswordFinishRequest, UpdateUserPasswordStartRequest, - UpdateUserPasswordStartResponse, UploadOneTimeKeysRequest, - VerifyUserAccessTokenRequest, VerifyUserAccessTokenResponse, - WalletLoginRequest, WalletLoginResponse, + ReservedWalletLoginRequest, UpdateUserPasswordFinishRequest, + UpdateUserPasswordStartRequest, UpdateUserPasswordStartResponse, + UploadOneTimeKeysRequest, VerifyUserAccessTokenRequest, + VerifyUserAccessTokenResponse, WalletLoginRequest, WalletLoginResponse, }; use crate::config::CONFIG; use crate::database::{ @@ -122,7 +122,11 @@ return Err(tonic::Status::invalid_argument("username reserved")); } - let registration_state = construct_user_registration_info(&message, None)?; + let registration_state = construct_user_registration_info( + &message, + None, + message.username.clone(), + )?; let server_registration = comm_opaque2::server::Registration::new(); let server_message = server_registration .start( @@ -170,8 +174,11 @@ &message.keyserver_signature, )?; - let registration_state = - construct_user_registration_info(&message, Some(user_id))?; + let registration_state = construct_user_registration_info( + &message, + Some(user_id), + message.username.clone(), + )?; let server_registration = comm_opaque2::server::Registration::new(); let server_message = server_registration .start( @@ -353,74 +360,28 @@ return Err(tonic::Status::not_found("user not found")); }; - if let client_proto::OpaqueLoginStartRequest { - opaque_login_request: login_message, - username, - device_key_upload: - Some(client_proto::DeviceKeyUpload { - device_key_info: - Some(client_proto::IdentityKeyInfo { - payload, - payload_signature, - social_proof: _social_proof, - }), - content_upload: - Some(client_proto::PreKey { - pre_key: content_prekey, - pre_key_signature: content_prekey_signature, - }), - notif_upload: - Some(client_proto::PreKey { - pre_key: notif_prekey, - pre_key_signature: notif_prekey_signature, - }), - one_time_content_prekeys, - one_time_notif_prekeys, - device_type, - }), - } = message - { - let mut server_login = comm_opaque2::server::Login::new(); - let server_response = server_login - .start( - &CONFIG.server_setup, - &password_file_bytes, - &login_message, - username.as_bytes(), - ) - .map_err(protocol_error_to_grpc_status)?; + let mut server_login = comm_opaque2::server::Login::new(); + let server_response = server_login + .start( + &CONFIG.server_setup, + &password_file_bytes, + &message.opaque_login_request, + message.username.as_bytes(), + ) + .map_err(protocol_error_to_grpc_status)?; - let key_info = KeyPayload::from_str(&payload) - .map_err(|_| tonic::Status::invalid_argument("malformed payload"))?; - let login_state = UserLoginInfo { - user_id, - opaque_server_login: server_login, - flattened_device_key_upload: FlattenedDeviceKeyUpload { - device_id_key: key_info.primary_identity_public_keys.ed25519, - key_payload: payload, - key_payload_signature: payload_signature, - content_prekey, - content_prekey_signature, - content_one_time_keys: one_time_content_prekeys, - notif_prekey, - notif_prekey_signature, - notif_one_time_keys: one_time_notif_prekeys, - device_type: DeviceType::try_from(DBDeviceTypeInt(device_type)) - .map_err(handle_db_error)?, - }, - }; - let session_id = self - .insert_into_cache(WorkflowInProgress::Login(Box::new(login_state))) - .await; - - let response = Response::new(OpaqueLoginStartResponse { - session_id, - opaque_login_response: server_response, - }); - Ok(response) - } else { - Err(tonic::Status::invalid_argument("unexpected message data")) - } + let login_state = + construct_user_login_info(&message, user_id, server_login)?; + + let session_id = self + .insert_into_cache(WorkflowInProgress::Login(Box::new(login_state))) + .await; + + let response = Response::new(OpaqueLoginStartResponse { + session_id, + opaque_login_response: server_response, + }); + Ok(response) } async fn login_password_user_finish( @@ -501,55 +462,28 @@ let wallet_address = eip55(&parsed_message.address); - let (flattened_device_key_upload, social_proof) = - if let client_proto::WalletLoginRequest { - siwe_message: _, - siwe_signature: _, - device_key_upload: - Some(client_proto::DeviceKeyUpload { - device_key_info: - Some(client_proto::IdentityKeyInfo { - payload, - payload_signature, - social_proof: Some(social_proof), - }), - content_upload: - Some(client_proto::PreKey { - pre_key: content_prekey, - pre_key_signature: content_prekey_signature, - }), - notif_upload: - Some(client_proto::PreKey { - pre_key: notif_prekey, - pre_key_signature: notif_prekey_signature, - }), - one_time_content_prekeys, - one_time_notif_prekeys, - device_type, - }), - } = message - { - let key_info = KeyPayload::from_str(&payload) - .map_err(|_| tonic::Status::invalid_argument("malformed payload"))?; - ( - FlattenedDeviceKeyUpload { - device_id_key: key_info.primary_identity_public_keys.ed25519, - key_payload: payload, - key_payload_signature: payload_signature, - content_prekey, - content_prekey_signature, - content_one_time_keys: one_time_content_prekeys, - notif_prekey, - notif_prekey_signature, - notif_one_time_keys: one_time_notif_prekeys, - device_type: DeviceType::try_from(DBDeviceTypeInt(device_type)) - .map_err(handle_db_error)?, - }, - social_proof, - ) - } else { - return Err(tonic::Status::invalid_argument("unexpected message data")); - }; + let key_info = KeyPayload::from_str(&message.payload()?) + .map_err(|_| tonic::Status::invalid_argument("malformed payload"))?; + + let flattened_device_key_upload = FlattenedDeviceKeyUpload { + device_id_key: key_info.primary_identity_public_keys.ed25519, + key_payload: message.payload()?, + key_payload_signature: message.payload_signature()?, + content_prekey: message.content_prekey()?, + content_prekey_signature: message.content_prekey_signature()?, + content_one_time_keys: message.one_time_content_prekeys()?, + notif_prekey: message.notif_prekey()?, + notif_prekey_signature: message.notif_prekey_signature()?, + notif_one_time_keys: message.one_time_notif_prekeys()?, + device_type: DeviceType::try_from(DBDeviceTypeInt( + message.device_type()?, + )) + .map_err(handle_db_error)?, + }; + + let social_proof = message + .social_proof()? + .ok_or_else(|| tonic::Status::invalid_argument("malformed payload"))?; let user_id = match self .client @@ -578,6 +512,7 @@ flattened_device_key_upload.clone(), wallet_address, social_proof, + None, ) .await .map_err(handle_db_error)? @@ -607,6 +542,109 @@ Ok(Response::new(response)) } + async fn login_reserved_wallet_user( + &self, + request: tonic::Request, + ) -> Result, tonic::Status> { + let message = request.into_inner(); + + let parsed_message = parse_and_verify_siwe_message( + &message.siwe_message, + &message.siwe_signature, + )?; + + match self + .client + .get_nonce_from_nonces_table(&parsed_message.nonce) + .await + .map_err(handle_db_error)? + { + None => return Err(tonic::Status::invalid_argument("invalid nonce")), + Some(_) => self + .client + .remove_nonce_from_nonces_table(&parsed_message.nonce) + .await + .map_err(handle_db_error)?, + }; + + let wallet_address = eip55(&parsed_message.address); + + self.check_wallet_address_taken(&wallet_address).await?; + + let wallet_address_in_reserved_usernames_table = self + .client + .username_in_reserved_usernames_table(&wallet_address) + .await + .map_err(handle_db_error)?; + if !wallet_address_in_reserved_usernames_table { + return Err(tonic::Status::permission_denied( + "wallet address not reserved", + )); + } + + let user_id = validate_account_ownership_message_and_get_user_id( + &wallet_address, + &message.keyserver_message, + &message.keyserver_signature, + )?; + + let key_info = KeyPayload::from_str(&message.payload()?) + .map_err(|_| tonic::Status::invalid_argument("malformed payload"))?; + + let flattened_device_key_upload = FlattenedDeviceKeyUpload { + device_id_key: key_info.primary_identity_public_keys.ed25519, + key_payload: message.payload()?, + key_payload_signature: message.payload_signature()?, + content_prekey: message.content_prekey()?, + content_prekey_signature: message.content_prekey_signature()?, + content_one_time_keys: message.one_time_content_prekeys()?, + notif_prekey: message.notif_prekey()?, + notif_prekey_signature: message.notif_prekey_signature()?, + notif_one_time_keys: message.one_time_notif_prekeys()?, + device_type: DeviceType::try_from(DBDeviceTypeInt( + message.device_type()?, + )) + .map_err(handle_db_error)?, + }; + + let social_proof = message + .social_proof()? + .ok_or_else(|| tonic::Status::invalid_argument("malformed payload"))?; + + self + .client + .add_wallet_user_to_users_table( + flattened_device_key_upload.clone(), + wallet_address, + social_proof, + Some(user_id.clone()), + ) + .await + .map_err(handle_db_error)?; + + let token = AccessTokenData::new( + user_id.clone(), + flattened_device_key_upload.device_id_key, + crate::token::AuthType::Password, + &mut OsRng, + ); + + let access_token = token.access_token.clone(); + + self + .client + .put_access_token_data(token) + .await + .map_err(handle_db_error)?; + + let response = WalletLoginResponse { + user_id, + access_token, + }; + + Ok(Response::new(response)) + } + async fn log_out_user( &self, request: tonic::Request, @@ -929,6 +967,23 @@ Ok(()) } + async fn check_wallet_address_taken( + &self, + wallet_address: &str, + ) -> Result<(), tonic::Status> { + let wallet_address_taken = self + .client + .wallet_address_taken(wallet_address.to_string()) + .await + .map_err(handle_db_error)?; + if wallet_address_taken { + return Err(tonic::Status::already_exists( + "wallet address already exists", + )); + } + Ok(()) + } + async fn insert_into_cache(&self, workflow: WorkflowInProgress) -> String { let session_id = generate_uuid(); self.cache.insert(session_id.clone(), workflow).await; @@ -955,12 +1010,13 @@ fn construct_user_registration_info( message: &impl DeviceKeyUploadActions, user_id: Option, + username: String, ) -> Result { let key_info = KeyPayload::from_str(&message.payload()?) .map_err(|_| tonic::Status::invalid_argument("malformed payload"))?; Ok(UserRegistrationInfo { - username: message.username(), + username, flattened_device_key_upload: FlattenedDeviceKeyUpload { device_id_key: key_info.primary_identity_public_keys.ed25519, key_payload: message.payload()?, @@ -979,3 +1035,32 @@ user_id, }) } + +fn construct_user_login_info( + message: &impl DeviceKeyUploadActions, + user_id: String, + opaque_server_login: comm_opaque2::server::Login, +) -> Result { + let key_info = KeyPayload::from_str(&message.payload()?) + .map_err(|_| tonic::Status::invalid_argument("malformed payload"))?; + + Ok(UserLoginInfo { + user_id, + flattened_device_key_upload: FlattenedDeviceKeyUpload { + device_id_key: key_info.primary_identity_public_keys.ed25519, + key_payload: message.payload()?, + key_payload_signature: message.payload_signature()?, + content_prekey: message.content_prekey()?, + content_prekey_signature: message.content_prekey_signature()?, + content_one_time_keys: message.one_time_content_prekeys()?, + notif_prekey: message.notif_prekey()?, + notif_prekey_signature: message.notif_prekey_signature()?, + notif_one_time_keys: message.one_time_notif_prekeys()?, + device_type: DeviceType::try_from(DBDeviceTypeInt( + message.device_type()?, + )) + .map_err(handle_db_error)?, + }, + opaque_server_login, + }) +} diff --git a/services/identity/src/database.rs b/services/identity/src/database.rs --- a/services/identity/src/database.rs +++ b/services/identity/src/database.rs @@ -154,6 +154,7 @@ flattened_device_key_upload: FlattenedDeviceKeyUpload, wallet_address: String, social_proof: String, + user_id: Option, ) -> Result { self .add_user_to_users_table( @@ -161,7 +162,7 @@ None, Some(wallet_address), Some(social_proof), - None, + user_id, ) .await } @@ -843,6 +844,17 @@ Ok(()) } + pub async fn wallet_address_taken( + &self, + wallet_address: String, + ) -> Result { + let result = self + .get_user_id_from_user_info(wallet_address, &AuthType::Wallet) + .await?; + + Ok(result.is_some()) + } + pub async fn username_taken(&self, username: String) -> Result { let result = self .get_user_id_from_user_info(username, &AuthType::Password) diff --git a/services/identity/src/grpc_utils.rs b/services/identity/src/grpc_utils.rs --- a/services/identity/src/grpc_utils.rs +++ b/services/identity/src/grpc_utils.rs @@ -5,8 +5,10 @@ use crate::{ client_service::client_proto::{ - DeviceKeyUpload, IdentityKeyInfo, InboundKeyInfo, OutboundKeyInfo, PreKey, - RegistrationStartRequest, ReservedRegistrationStartRequest, + DeviceKeyUpload, IdentityKeyInfo, InboundKeyInfo, OpaqueLoginStartRequest, + OutboundKeyInfo, PreKey, RegistrationStartRequest, + ReservedRegistrationStartRequest, ReservedWalletLoginRequest, + WalletLoginRequest, }, constants::{ CONTENT_ONE_TIME_KEY, NOTIF_ONE_TIME_KEY, @@ -131,29 +133,39 @@ pub trait DeviceKeyUploadData { fn device_key_upload(&self) -> Option<&DeviceKeyUpload>; - fn username(&self) -> &str; } impl DeviceKeyUploadData for RegistrationStartRequest { fn device_key_upload(&self) -> Option<&DeviceKeyUpload> { self.device_key_upload.as_ref() } - fn username(&self) -> &str { - &self.username - } } impl DeviceKeyUploadData for ReservedRegistrationStartRequest { fn device_key_upload(&self) -> Option<&DeviceKeyUpload> { self.device_key_upload.as_ref() } - fn username(&self) -> &str { - &self.username +} + +impl DeviceKeyUploadData for OpaqueLoginStartRequest { + fn device_key_upload(&self) -> Option<&DeviceKeyUpload> { + self.device_key_upload.as_ref() + } +} + +impl DeviceKeyUploadData for WalletLoginRequest { + fn device_key_upload(&self) -> Option<&DeviceKeyUpload> { + self.device_key_upload.as_ref() + } +} + +impl DeviceKeyUploadData for ReservedWalletLoginRequest { + fn device_key_upload(&self) -> Option<&DeviceKeyUpload> { + self.device_key_upload.as_ref() } } pub trait DeviceKeyUploadActions { - fn username(&self) -> String; fn payload(&self) -> Result; fn payload_signature(&self) -> Result; fn content_prekey(&self) -> Result; @@ -163,13 +175,10 @@ fn one_time_content_prekeys(&self) -> Result, Status>; fn one_time_notif_prekeys(&self) -> Result, Status>; fn device_type(&self) -> Result; + fn social_proof(&self) -> Result, Status>; } impl DeviceKeyUploadActions for T { - fn username(&self) -> String { - self.username().to_string() - } - fn payload(&self) -> Result { self .device_key_upload() @@ -237,4 +246,11 @@ .map(|upload| upload.device_type) .ok_or_else(|| Status::invalid_argument("unexpected message data")) } + fn social_proof(&self) -> Result, Status> { + self + .device_key_upload() + .and_then(|upload| upload.device_key_info.as_ref()) + .map(|info| info.social_proof.clone()) + .ok_or_else(|| Status::invalid_argument("unexpected message data")) + } } diff --git a/services/identity/src/reserved_users.rs b/services/identity/src/reserved_users.rs --- a/services/identity/src/reserved_users.rs +++ b/services/identity/src/reserved_users.rs @@ -84,6 +84,7 @@ keyserver_message: &str, keyserver_signature: &str, ) -> Result { + // Note that username in this context includes wallet addresses, too. const EXPECTED_STATEMENT: &[u8; 60] = b"This user is the owner of the following username and user ID"; diff --git a/shared/protos/identity_client.proto b/shared/protos/identity_client.proto --- a/shared/protos/identity_client.proto +++ b/shared/protos/identity_client.proto @@ -28,6 +28,8 @@ rpc LoginPasswordUserFinish(OpaqueLoginFinishRequest) returns (OpaqueLoginFinishResponse) {} rpc LoginWalletUser(WalletLoginRequest) returns (WalletLoginResponse) {} + rpc LoginReservedWalletUser(ReservedWalletLoginRequest) returns + (WalletLoginResponse) {} // Called by user to log out (clears device's keys and access token) rpc LogOutUser(LogoutRequest) returns (Empty) {} // Called by a user to delete their own account @@ -245,6 +247,19 @@ DeviceKeyUpload deviceKeyUpload = 3; } +message ReservedWalletLoginRequest { + string siweMessage = 1; + string siweSignature = 2; + // Information specific to a user's device needed to open a new channel of + // communication with this user + DeviceKeyUpload deviceKeyUpload = 3; + // Message from Ashoat's keyserver attesting that a given user has ownership + // of a given wallet address + string keyserverMessage = 4; + // Above message signed with Ashoat's keyserver's signing ed25519 key + string keyserverSignature = 5; +} + message WalletLoginResponse { string userID = 1; string accessToken = 2;