diff --git a/keyserver/addons/rust-node-addon/rust-binding-types.js b/keyserver/addons/rust-node-addon/rust-binding-types.js index f5b0649c2..57ae32f86 100644 --- a/keyserver/addons/rust-node-addon/rust-binding-types.js +++ b/keyserver/addons/rust-node-addon/rust-binding-types.js @@ -1,62 +1,62 @@ // @flow import type { SignedIdentityKeysBlob } from 'lib/types/crypto-types.js'; import type { InboundKeyInfoResponse, UserLoginResponse, } from 'lib/types/identity-service-types.js'; type RustNativeBindingAPI = { +loginUser: ( username: string, password: string, signedIdentityKeysBlob: SignedIdentityKeysBlob, contentPrekey: string, contentPrekeySignature: string, notifPrekey: string, notifPrekeySignature: string, contentOneTimeKeys: $ReadOnlyArray, notifOneTimeKeys: $ReadOnlyArray, ) => Promise, +registerUser: ( username: string, password: string, signedIdentityKeysBlob: SignedIdentityKeysBlob, contentPrekey: string, contentPrekeySignature: string, notifPrekey: string, notifPrekeySignature: string, contentOneTimeKeys: $ReadOnlyArray, notifOneTimeKeys: $ReadOnlyArray, ) => Promise, +addReservedUsernames: (message: string, signature: string) => Promise, +removeReservedUsername: ( message: string, signature: string, ) => Promise, +publishPrekeys: ( userId: string, deviceId: string, accessToken: string, contentPrekey: string, contentPrekeySignature: string, notifPrekey: string, notifPrekeySignature: string, ) => Promise, +uploadOneTimeKeys: ( userId: string, deviceId: string, accessToken: string, - contentOneTimePreKeys: $ReadOnlyArray, - notifOneTimePreKeys: $ReadOnlyArray, + contentOneTimePrekeys: $ReadOnlyArray, + notifOneTimePrekeys: $ReadOnlyArray, ) => Promise, +getInboundKeysForUserDevice: ( authUserId: string, authDeviceId: string, authAccessToken: string, userId: string, deviceId: string, ) => Promise, }; export type { RustNativeBindingAPI }; diff --git a/keyserver/addons/rust-node-addon/src/identity_client/login.rs b/keyserver/addons/rust-node-addon/src/identity_client/login.rs index 9b49fa10c..36451b12f 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/login.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/login.rs @@ -1,103 +1,103 @@ use super::*; use comm_opaque2::client::Login; use grpc_clients::identity::protos::unauthenticated::{ OpaqueLoginFinishRequest, OpaqueLoginStartRequest, }; use tracing::debug; #[napi] #[instrument(skip_all)] pub async fn login_user( username: String, password: String, signed_identity_keys_blob: SignedIdentityKeysBlob, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, ) -> Result { debug!("Attempting to login user: {}", username); // Set up the gRPC client that will be used to talk to the Identity service let mut identity_client = get_identity_client().await?; // Start OPAQUE registration and send initial registration request let mut client_login = Login::new(); let opaque_login_request = client_login .start(&password) .map_err(|_| Error::from_reason("Failed to create opaque login request"))?; let login_start_request = OpaqueLoginStartRequest { opaque_login_request, username, device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: signed_identity_keys_blob.payload, payload_signature: signed_identity_keys_blob.signature, social_proof: None, }), - content_upload: Some(PreKey { - pre_key: content_prekey, - pre_key_signature: content_prekey_signature, + content_upload: Some(Prekey { + prekey: content_prekey, + prekey_signature: content_prekey_signature, }), - notif_upload: Some(PreKey { - pre_key: notif_prekey, - pre_key_signature: notif_prekey_signature, + notif_upload: Some(Prekey { + prekey: notif_prekey, + prekey_signature: notif_prekey_signature, }), one_time_content_prekeys: content_one_time_keys, one_time_notif_prekeys: notif_one_time_keys, device_type: DeviceType::Keyserver.into(), }), }; debug!("Starting login to identity service"); let response = identity_client - .login_password_user_start(login_start_request) + .log_in_password_user_start(login_start_request) .await .map_err(handle_grpc_error)?; debug!("Received login response from identity service"); // We need to get the load balancer cookie from from the response and send it // in the subsequent request to ensure it is routed to the same identity // service instance as the first request let cookie = response .metadata() .get(RESPONSE_METADATA_COOKIE_KEY) .cloned(); let login_start_response = response.into_inner(); let opaque_login_upload = client_login .finish(&login_start_response.opaque_login_response) .map_err(|_| Error::from_reason("Failed to finish opaque login request"))?; let mut login_finish_request = Request::new(OpaqueLoginFinishRequest { session_id: login_start_response.session_id, opaque_login_upload, }); // Cookie won't be available in local dev environments if let Some(cookie_metadata) = cookie { login_finish_request .metadata_mut() .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata); } debug!("Attempting to finalize opaque login exchange with identity service"); let login_finish_response = identity_client - .login_password_user_finish(login_finish_request) + .log_in_password_user_finish(login_finish_request) .await .map_err(handle_grpc_error)? .into_inner(); debug!("Finished login with identity service"); let user_info = UserLoginInfo { user_id: login_finish_response.user_id, access_token: login_finish_response.access_token, }; Ok(user_info) } diff --git a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs index c39a2e67e..80cc9845a 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs @@ -1,211 +1,211 @@ pub mod add_reserved_usernames; pub mod config; pub mod get_inbound_keys_for_user; pub mod login; pub mod prekey; pub mod register_user; pub mod remove_reserved_usernames; pub mod upload_one_time_keys; use client_proto::identity_client_service_client::IdentityClientServiceClient; use client_proto::{ AddReservedUsernamesRequest, DeviceKeyUpload, DeviceType, IdentityKeyInfo, - PreKey, RegistrationFinishRequest, RegistrationStartRequest, + Prekey, RegistrationFinishRequest, RegistrationStartRequest, RemoveReservedUsernameRequest, }; use config::get_identity_service_config; use generated::CODE_VERSION; use grpc_clients::identity::authenticated::ChainedInterceptedAuthClient; use grpc_clients::identity::protos::authenticated::{ InboundKeyInfo, UploadOneTimeKeysRequest, }; use grpc_clients::identity::protos::unauthenticated as client_proto; use grpc_clients::identity::shared::CodeVersionLayer; use grpc_clients::identity::{ REQUEST_METADATA_COOKIE_KEY, RESPONSE_METADATA_COOKIE_KEY, }; use lazy_static::lazy_static; use napi::bindgen_prelude::*; use serde::{Deserialize, Serialize}; use std::env::var; use tonic::codegen::InterceptedService; use tonic::{transport::Channel, Request}; use tracing::{self, info, instrument, warn, Level}; use tracing_subscriber::EnvFilter; mod generated { // We get the CODE_VERSION from this generated file include!(concat!(env!("OUT_DIR"), "/version.rs")); } const DEVICE_TYPE: &str = "keyserver"; const ENV_NODE_ENV: &str = "NODE_ENV"; const ENV_DEVELOPMENT: &str = "development"; lazy_static! { static ref IDENTITY_SERVICE_CONFIG: IdentityServiceConfig = { let filter = EnvFilter::builder() .with_default_directive(Level::INFO.into()) .with_env_var(EnvFilter::DEFAULT_ENV) .from_env_lossy(); let subscriber = tracing_subscriber::fmt().with_env_filter(filter).finish(); tracing::subscriber::set_global_default(subscriber) .expect("Unable to configure tracing"); get_identity_service_config() .unwrap_or_else(|_| IdentityServiceConfig::default()) }; } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct IdentityServiceConfig { identity_socket_addr: String, } impl Default for IdentityServiceConfig { fn default() -> Self { info!("Using default identity configuration based on NODE_ENV"); const DEV_SOCKET_ADDR: &str = "https://identity.staging.commtechnologies.org:50054"; const PROD_SOCKET_ADDR: &str = "https://identity.commtechnologies.org:50054"; let default_socket_addr = match var(ENV_NODE_ENV) { Ok(val) if val == ENV_DEVELOPMENT => DEV_SOCKET_ADDR, _ => PROD_SOCKET_ADDR, } .to_string(); Self { identity_socket_addr: default_socket_addr, } } } async fn get_identity_client() -> Result< IdentityClientServiceClient>, > { info!("Connecting to identity service"); grpc_clients::identity::get_unauthenticated_client( &IDENTITY_SERVICE_CONFIG.identity_socket_addr, CODE_VERSION, DEVICE_TYPE.to_string(), ) .await .map_err(|_| { Error::new( Status::GenericFailure, "Unable to connect to identity service".to_string(), ) }) } async fn get_authenticated_identity_client( user_id: String, device_id: String, access_token: String, ) -> Result { info!("Connecting to identity service"); grpc_clients::identity::get_auth_client( &IDENTITY_SERVICE_CONFIG.identity_socket_addr, user_id, device_id, access_token, CODE_VERSION, DEVICE_TYPE.to_string(), ) .await .map_err(|_| { Error::new( Status::GenericFailure, "Unable to connect to identity service".to_string(), ) }) } #[napi(object)] pub struct SignedIdentityKeysBlob { pub payload: String, pub signature: String, } #[napi(object)] pub struct UserLoginInfo { pub user_id: String, pub access_token: String, } #[napi(object)] pub struct InboundKeyInfoResponse { pub payload: String, pub payload_signature: String, pub social_proof: Option, pub content_prekey: String, pub content_prekey_signature: String, pub notif_prekey: String, pub notif_prekey_signature: String, } impl TryFrom for InboundKeyInfoResponse { type Error = Error; fn try_from(key_info: InboundKeyInfo) -> Result { let identity_info = key_info .identity_info .ok_or(Error::from_status(Status::GenericFailure))?; let IdentityKeyInfo { payload, payload_signature, social_proof, } = identity_info; let content_prekey = key_info .content_prekey .ok_or(Error::from_status(Status::GenericFailure))?; - let PreKey { - pre_key: content_prekey_value, - pre_key_signature: content_prekey_signature, + let Prekey { + prekey: content_prekey_value, + prekey_signature: content_prekey_signature, } = content_prekey; let notif_prekey = key_info .notif_prekey .ok_or(Error::from_status(Status::GenericFailure))?; - let PreKey { - pre_key: notif_prekey_value, - pre_key_signature: notif_prekey_signature, + let Prekey { + prekey: notif_prekey_value, + prekey_signature: notif_prekey_signature, } = notif_prekey; Ok(Self { payload, payload_signature, social_proof, content_prekey: content_prekey_value, content_prekey_signature, notif_prekey: notif_prekey_value, notif_prekey_signature, }) } } pub fn handle_grpc_error(error: tonic::Status) -> napi::Error { warn!("Received error: {}", error.message()); Error::new(Status::GenericFailure, error.message()) } #[cfg(test)] mod tests { use super::CODE_VERSION; #[test] fn test_code_version_exists() { assert!(CODE_VERSION > 0); } } diff --git a/keyserver/addons/rust-node-addon/src/identity_client/prekey.rs b/keyserver/addons/rust-node-addon/src/identity_client/prekey.rs index 341acc1b2..cdeb65332 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/prekey.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/prekey.rs @@ -1,44 +1,44 @@ use super::get_authenticated_identity_client; use super::{Error, Status}; use grpc_clients::identity::protos::{ - authenticated::RefreshUserPreKeysRequest, unauthenticated::PreKey, + authenticated::RefreshUserPrekeysRequest, unauthenticated::Prekey, }; use napi::Result; use tracing::warn; #[napi] pub async fn publish_prekeys( user_id: String, device_id: String, access_token: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, ) -> Result { // Once this rust addon can do getCommConfig, remove explicit passing of user // credentials within this scope let mut client = get_authenticated_identity_client(user_id, device_id, access_token).await?; - let message = RefreshUserPreKeysRequest { - new_content_pre_keys: Some(PreKey { - pre_key: content_prekey, - pre_key_signature: content_prekey_signature, + let message = RefreshUserPrekeysRequest { + new_content_prekeys: Some(Prekey { + prekey: content_prekey, + prekey_signature: content_prekey_signature, }), - new_notif_pre_keys: Some(PreKey { - pre_key: notif_prekey, - pre_key_signature: notif_prekey_signature, + new_notif_prekeys: Some(Prekey { + prekey: notif_prekey, + prekey_signature: notif_prekey_signature, }), }; - client.refresh_user_pre_keys(message).await.map_err(|e| { + client.refresh_user_prekeys(message).await.map_err(|e| { warn!( "Failed to upload new prekeys to identity service: {:?}", e.message() ); Error::from_status(Status::GenericFailure) })?; Ok(true) } diff --git a/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs index ed7f3d70c..6b11f8326 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs @@ -1,101 +1,101 @@ use super::*; use tracing::{debug, warn}; #[napi] #[instrument(skip_all)] pub async fn register_user( username: String, password: String, signed_identity_keys_blob: SignedIdentityKeysBlob, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, ) -> Result { debug!("Attempting to register user: {}", username); // Set up the gRPC client that will be used to talk to the Identity service let mut identity_client = get_identity_client().await?; // Start OPAQUE registration and send initial registration request let mut opaque_registration = comm_opaque2::client::Registration::new(); let opaque_registration_request = opaque_registration .start(&password) .map_err(|_| Error::from_status(Status::GenericFailure))?; let device_key_upload = DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: signed_identity_keys_blob.payload, payload_signature: signed_identity_keys_blob.signature, social_proof: None, }), - content_upload: Some(PreKey { - pre_key: content_prekey, - pre_key_signature: content_prekey_signature, + content_upload: Some(Prekey { + prekey: content_prekey, + prekey_signature: content_prekey_signature, }), - notif_upload: Some(PreKey { - pre_key: notif_prekey, - pre_key_signature: notif_prekey_signature, + notif_upload: Some(Prekey { + prekey: notif_prekey, + prekey_signature: notif_prekey_signature, }), one_time_content_prekeys: content_one_time_keys, one_time_notif_prekeys: notif_one_time_keys, device_type: DeviceType::Keyserver.into(), }; let registration_start_request = Request::new(RegistrationStartRequest { opaque_registration_request, username, device_key_upload: Some(device_key_upload), }); // Finish OPAQUE registration and send final registration request let response = identity_client .register_password_user_start(registration_start_request) .await .map_err(handle_grpc_error)?; debug!("Received registration start response"); // We need to get the load balancer cookie from from the response and send it // in the subsequent request to ensure it is routed to the same identity // service instance as the first request let cookie = response .metadata() .get(RESPONSE_METADATA_COOKIE_KEY) .cloned(); let registration_start_response = response.into_inner(); let opaque_registration_upload = opaque_registration .finish( &password, ®istration_start_response.opaque_registration_response, ) .map_err(|_| Error::from_status(Status::GenericFailure))?; let mut registration_finish_request = Request::new(RegistrationFinishRequest { session_id: registration_start_response.session_id, opaque_registration_upload, }); // Cookie won't be available in local dev environments if let Some(cookie_metadata) = cookie { registration_finish_request .metadata_mut() .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata); } let registration_response = identity_client .register_password_user_finish(registration_finish_request) .await .map_err(handle_grpc_error)? .into_inner(); let user_info = UserLoginInfo { user_id: registration_response.user_id, access_token: registration_response.access_token, }; Ok(user_info) } diff --git a/keyserver/addons/rust-node-addon/src/identity_client/upload_one_time_keys.rs b/keyserver/addons/rust-node-addon/src/identity_client/upload_one_time_keys.rs index b812c1d9f..1b490a19e 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/upload_one_time_keys.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/upload_one_time_keys.rs @@ -1,30 +1,30 @@ use super::*; use tracing::debug; #[napi] #[instrument(skip_all)] pub async fn upload_one_time_keys( user_id: String, device_id: String, access_token: String, - content_one_time_pre_keys: Vec, - notif_one_time_pre_keys: Vec, + content_one_time_prekeys: Vec, + notif_one_time_prekeys: Vec, ) -> Result { // Set up the gRPC client that will be used to talk to the Identity service let mut identity_client = get_authenticated_identity_client(user_id, device_id, access_token).await?; let upload_request = UploadOneTimeKeysRequest { - content_one_time_pre_keys, - notif_one_time_pre_keys, + content_one_time_prekeys, + notif_one_time_prekeys, }; debug!("Sending one time keys to Identity service"); identity_client .upload_one_time_keys(upload_request) .await .map_err(handle_grpc_error)?; Ok(true) } diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs index e0204a6d6..86e0fd8f0 100644 --- a/native/native_rust_library/src/lib.rs +++ b/native/native_rust_library/src/lib.rs @@ -1,1095 +1,1095 @@ use backup::ffi::*; use comm_opaque2::client::{Login, Registration}; use comm_opaque2::grpc::opaque_error_to_grpc_status as handle_error; use ffi::{bool_callback, string_callback, void_callback}; use grpc_clients::identity::protos::authenticated::{ InboundKeyInfo, InboundKeysForUserRequest, OutboundKeyInfo, OutboundKeysForUserRequest, UpdateUserPasswordFinishRequest, UpdateUserPasswordStartRequest, UploadOneTimeKeysRequest, }; use grpc_clients::identity::protos::client::{ DeviceKeyUpload, DeviceType, Empty, IdentityKeyInfo, - OpaqueLoginFinishRequest, OpaqueLoginStartRequest, PreKey, + OpaqueLoginFinishRequest, OpaqueLoginStartRequest, Prekey, RegistrationFinishRequest, RegistrationStartRequest, WalletLoginRequest, }; use grpc_clients::identity::{ get_auth_client, get_unauthenticated_client, REQUEST_METADATA_COOKIE_KEY, RESPONSE_METADATA_COOKIE_KEY, }; use lazy_static::lazy_static; use serde::Serialize; use std::sync::Arc; use tokio::runtime::{Builder, Runtime}; use tonic::{Request, Status}; use tracing::instrument; mod argon2_tools; mod backup; mod constants; use argon2_tools::compute_backup_key_str; mod generated { // We get the CODE_VERSION from this generated file include!(concat!(env!("OUT_DIR"), "/version.rs")); // We get the IDENTITY_SOCKET_ADDR from this generated file include!(concat!(env!("OUT_DIR"), "/socket_config.rs")); } pub use generated::CODE_VERSION; pub use generated::{BACKUP_SOCKET_ADDR, IDENTITY_SOCKET_ADDR}; #[cfg(not(target_os = "android"))] pub const DEVICE_TYPE: DeviceType = DeviceType::Ios; #[cfg(target_os = "android")] pub const DEVICE_TYPE: DeviceType = DeviceType::Android; lazy_static! { static ref RUNTIME: Arc = Arc::new(Builder::new_multi_thread().enable_all().build().unwrap()); } #[cxx::bridge] mod ffi { extern "Rust" { #[cxx_name = "identityRegisterUser"] fn register_user( username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ); #[cxx_name = "identityLoginPasswordUser"] fn login_password_user( username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ); #[cxx_name = "identityLoginWalletUser"] fn login_wallet_user( siwe_message: String, siwe_signature: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, social_proof: String, promise_id: u32, ); #[cxx_name = "identityUpdateUserPassword"] fn update_user_password( user_id: String, device_id: String, access_token: String, password: String, promise_id: u32, ); #[cxx_name = "identityDeleteUser"] fn delete_user( user_id: String, device_id: String, access_token: String, promise_id: u32, ); #[cxx_name = "identityGetOutboundKeysForUser"] fn get_outbound_keys_for_user( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_id: String, promise_id: u32, ); #[cxx_name = "identityGetInboundKeysForUser"] fn get_inbound_keys_for_user( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_id: String, promise_id: u32, ); #[cxx_name = "identityGenerateNonce"] fn generate_nonce(promise_id: u32); #[cxx_name = "identityVersionSupported"] fn version_supported(promise_id: u32); #[cxx_name = "identityUploadOneTimeKeys"] fn upload_one_time_keys( auth_user_id: String, auth_device_id: String, auth_access_token: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ); // Argon2 #[cxx_name = "compute_backup_key"] fn compute_backup_key_str( password: &str, backup_id: &str, ) -> Result<[u8; 32]>; } unsafe extern "C++" { include!("RustCallback.h"); #[namespace = "comm"] #[cxx_name = "stringCallback"] fn string_callback(error: String, promise_id: u32, ret: String); #[namespace = "comm"] #[cxx_name = "voidCallback"] fn void_callback(error: String, promise_id: u32); #[namespace = "comm"] #[cxx_name = "boolCallback"] fn bool_callback(error: String, promise_id: u32, ret: bool); } // AES cryptography #[namespace = "comm"] unsafe extern "C++" { include!("RustAESCrypto.h"); #[allow(unused)] #[cxx_name = "aesGenerateKey"] fn generate_key(buffer: &mut [u8]) -> Result<()>; /// The first two argument aren't mutated but creation of Java ByteBuffer /// requires the underlying bytes to be mutable. #[allow(unused)] #[cxx_name = "aesEncrypt"] fn encrypt( key: &mut [u8], plaintext: &mut [u8], sealed_data: &mut [u8], ) -> Result<()>; /// The first two argument aren't mutated but creation of Java ByteBuffer /// requires the underlying bytes to be mutable. #[allow(unused)] #[cxx_name = "aesDecrypt"] fn decrypt( key: &mut [u8], sealed_data: &mut [u8], plaintext: &mut [u8], ) -> Result<()>; } // Comm Services Auth Metadata Emission #[namespace = "comm"] unsafe extern "C++" { include!("RustCSAMetadataEmitter.h"); #[allow(unused)] #[cxx_name = "sendAuthMetadataToJS"] fn send_auth_metadata_to_js( access_token: String, user_id: String, ) -> Result<()>; } // Backup extern "Rust" { #[cxx_name = "createBackup"] fn create_backup_sync( backup_id: String, backup_secret: String, pickle_key: String, pickled_account: String, user_data: String, promise_id: u32, ); #[cxx_name = "restoreBackup"] fn restore_backup_sync(backup_secret: String, promise_id: u32); } // Secure store #[namespace = "comm"] unsafe extern "C++" { include!("RustSecureStore.h"); #[allow(unused)] #[cxx_name = "secureStoreSet"] fn secure_store_set(key: &str, value: String) -> Result<()>; #[cxx_name = "secureStoreGet"] fn secure_store_get(key: &str) -> Result; } } fn handle_string_result_as_callback( result: Result, promise_id: u32, ) where E: std::fmt::Display, { match result { Err(e) => string_callback(e.to_string(), promise_id, "".to_string()), Ok(r) => string_callback("".to_string(), promise_id, r), } } fn handle_void_result_as_callback(result: Result<(), E>, promise_id: u32) where E: std::fmt::Display, { match result { Err(e) => void_callback(e.to_string(), promise_id), Ok(_) => void_callback("".to_string(), promise_id), } } fn handle_bool_result_as_callback(result: Result, promise_id: u32) where E: std::fmt::Display, { match result { Err(e) => bool_callback(e.to_string(), promise_id, false), Ok(r) => bool_callback("".to_string(), promise_id, r), } } fn generate_nonce(promise_id: u32) { RUNTIME.spawn(async move { let result = fetch_nonce().await; handle_string_result_as_callback(result, promise_id); }); } async fn fetch_nonce() -> Result { let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let nonce = identity_client .generate_nonce(Empty {}) .await? .into_inner() .nonce; Ok(nonce) } fn version_supported(promise_id: u32) { RUNTIME.spawn(async move { let result = version_supported_helper().await; handle_bool_result_as_callback(result, promise_id); }); } async fn version_supported_helper() -> Result { let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client.ping(Empty {}).await; match response { Ok(_) => Ok(true), Err(e) => { if grpc_clients::error::is_version_unsupported(&e) { Ok(false) } else { Err(e.into()) } } } } struct AuthInfo { user_id: String, device_id: String, access_token: String, } #[instrument] fn register_user( username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ) { RUNTIME.spawn(async move { let password_user_info = PasswordUserInfo { username, password, key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys, notif_one_time_keys, }; let result = register_user_helper(password_user_info).await; handle_string_result_as_callback(result, promise_id); }); } struct PasswordUserInfo { username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct UserIDAndDeviceAccessToken { #[serde(rename = "userID")] user_id: String, access_token: String, } async fn register_user_helper( password_user_info: PasswordUserInfo, ) -> Result { let mut client_registration = Registration::new(); let opaque_registration_request = client_registration .start(&password_user_info.password) .map_err(handle_error)?; let registration_start_request = RegistrationStartRequest { opaque_registration_request, username: password_user_info.username, device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: password_user_info.key_payload, payload_signature: password_user_info.key_payload_signature, social_proof: None, }), - content_upload: Some(PreKey { - pre_key: password_user_info.content_prekey, - pre_key_signature: password_user_info.content_prekey_signature, + content_upload: Some(Prekey { + prekey: password_user_info.content_prekey, + prekey_signature: password_user_info.content_prekey_signature, }), - notif_upload: Some(PreKey { - pre_key: password_user_info.notif_prekey, - pre_key_signature: password_user_info.notif_prekey_signature, + notif_upload: Some(Prekey { + prekey: password_user_info.notif_prekey, + prekey_signature: password_user_info.notif_prekey_signature, }), one_time_content_prekeys: password_user_info.content_one_time_keys, one_time_notif_prekeys: password_user_info.notif_one_time_keys, device_type: DEVICE_TYPE.into(), }), }; let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client .register_password_user_start(registration_start_request) .await?; // We need to get the load balancer cookie from from the response and send it // in the subsequent request to ensure it is routed to the same identity // service instance as the first request let cookie = response .metadata() .get(RESPONSE_METADATA_COOKIE_KEY) .cloned(); let registration_start_response = response.into_inner(); let opaque_registration_upload = client_registration .finish( &password_user_info.password, ®istration_start_response.opaque_registration_response, ) .map_err(handle_error)?; let registration_finish_request = RegistrationFinishRequest { session_id: registration_start_response.session_id, opaque_registration_upload, }; let mut finish_request = Request::new(registration_finish_request); // Cookie won't be available in local dev environments if let Some(cookie_metadata) = cookie { finish_request .metadata_mut() .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata); } let registration_finish_response = identity_client .register_password_user_finish(finish_request) .await? .into_inner(); let user_id_and_access_token = UserIDAndDeviceAccessToken { user_id: registration_finish_response.user_id, access_token: registration_finish_response.access_token, }; Ok(serde_json::to_string(&user_id_and_access_token)?) } #[instrument] fn login_password_user( username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ) { RUNTIME.spawn(async move { let password_user_info = PasswordUserInfo { username, password, key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys, notif_one_time_keys, }; let result = login_password_user_helper(password_user_info).await; handle_string_result_as_callback(result, promise_id); }); } async fn login_password_user_helper( password_user_info: PasswordUserInfo, ) -> Result { let mut client_login = Login::new(); let opaque_login_request = client_login .start(&password_user_info.password) .map_err(handle_error)?; let login_start_request = OpaqueLoginStartRequest { opaque_login_request, username: password_user_info.username, device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: password_user_info.key_payload, payload_signature: password_user_info.key_payload_signature, social_proof: None, }), - content_upload: Some(PreKey { - pre_key: password_user_info.content_prekey, - pre_key_signature: password_user_info.content_prekey_signature, + content_upload: Some(Prekey { + prekey: password_user_info.content_prekey, + prekey_signature: password_user_info.content_prekey_signature, }), - notif_upload: Some(PreKey { - pre_key: password_user_info.notif_prekey, - pre_key_signature: password_user_info.notif_prekey_signature, + notif_upload: Some(Prekey { + prekey: password_user_info.notif_prekey, + prekey_signature: password_user_info.notif_prekey_signature, }), one_time_content_prekeys: password_user_info.content_one_time_keys, one_time_notif_prekeys: password_user_info.notif_one_time_keys, device_type: DEVICE_TYPE.into(), }), }; let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client - .login_password_user_start(login_start_request) + .log_in_password_user_start(login_start_request) .await?; // We need to get the load balancer cookie from from the response and send it // in the subsequent request to ensure it is routed to the same identity // service instance as the first request let cookie = response .metadata() .get(RESPONSE_METADATA_COOKIE_KEY) .cloned(); let login_start_response = response.into_inner(); let opaque_login_upload = client_login .finish(&login_start_response.opaque_login_response) .map_err(handle_error)?; let login_finish_request = OpaqueLoginFinishRequest { session_id: login_start_response.session_id, opaque_login_upload, }; let mut finish_request = Request::new(login_finish_request); // Cookie won't be available in local dev environments if let Some(cookie_metadata) = cookie { finish_request .metadata_mut() .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata); } let login_finish_response = identity_client - .login_password_user_finish(finish_request) + .log_in_password_user_finish(finish_request) .await? .into_inner(); let user_id_and_access_token = UserIDAndDeviceAccessToken { user_id: login_finish_response.user_id, access_token: login_finish_response.access_token, }; Ok(serde_json::to_string(&user_id_and_access_token)?) } struct WalletUserInfo { siwe_message: String, siwe_signature: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, social_proof: String, } #[instrument] fn login_wallet_user( siwe_message: String, siwe_signature: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, social_proof: String, promise_id: u32, ) { RUNTIME.spawn(async move { let wallet_user_info = WalletUserInfo { siwe_message, siwe_signature, key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys, notif_one_time_keys, social_proof, }; let result = login_wallet_user_helper(wallet_user_info).await; handle_string_result_as_callback(result, promise_id); }); } async fn login_wallet_user_helper( wallet_user_info: WalletUserInfo, ) -> Result { let login_request = WalletLoginRequest { siwe_message: wallet_user_info.siwe_message, siwe_signature: wallet_user_info.siwe_signature, device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: wallet_user_info.key_payload, payload_signature: wallet_user_info.key_payload_signature, social_proof: Some(wallet_user_info.social_proof), }), - content_upload: Some(PreKey { - pre_key: wallet_user_info.content_prekey, - pre_key_signature: wallet_user_info.content_prekey_signature, + content_upload: Some(Prekey { + prekey: wallet_user_info.content_prekey, + prekey_signature: wallet_user_info.content_prekey_signature, }), - notif_upload: Some(PreKey { - pre_key: wallet_user_info.notif_prekey, - pre_key_signature: wallet_user_info.notif_prekey_signature, + notif_upload: Some(Prekey { + prekey: wallet_user_info.notif_prekey, + prekey_signature: wallet_user_info.notif_prekey_signature, }), one_time_content_prekeys: wallet_user_info.content_one_time_keys, one_time_notif_prekeys: wallet_user_info.notif_one_time_keys, device_type: DEVICE_TYPE.into(), }), }; let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let login_response = identity_client - .login_wallet_user(login_request) + .log_in_wallet_user(login_request) .await? .into_inner(); let user_id_and_access_token = UserIDAndDeviceAccessToken { user_id: login_response.user_id, access_token: login_response.access_token, }; Ok(serde_json::to_string(&user_id_and_access_token)?) } struct UpdatePasswordInfo { user_id: String, device_id: String, access_token: String, password: String, } fn update_user_password( user_id: String, device_id: String, access_token: String, password: String, promise_id: u32, ) { RUNTIME.spawn(async move { let update_password_info = UpdatePasswordInfo { access_token, user_id, device_id, password, }; let result = update_user_password_helper(update_password_info).await; handle_void_result_as_callback(result, promise_id); }); } async fn update_user_password_helper( update_password_info: UpdatePasswordInfo, ) -> Result<(), Error> { let mut client_registration = Registration::new(); let opaque_registration_request = client_registration .start(&update_password_info.password) .map_err(handle_error)?; let update_password_start_request = UpdateUserPasswordStartRequest { opaque_registration_request, }; let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, update_password_info.user_id, update_password_info.device_id, update_password_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client .update_user_password_start(update_password_start_request) .await?; // We need to get the load balancer cookie from from the response and send it // in the subsequent request to ensure it is routed to the same identity // service instance as the first request let cookie = response .metadata() .get(RESPONSE_METADATA_COOKIE_KEY) .cloned(); let update_password_start_response = response.into_inner(); let opaque_registration_upload = client_registration .finish( &update_password_info.password, &update_password_start_response.opaque_registration_response, ) .map_err(handle_error)?; let update_password_finish_request = UpdateUserPasswordFinishRequest { session_id: update_password_start_response.session_id, opaque_registration_upload, }; let mut finish_request = Request::new(update_password_finish_request); // Cookie won't be available in local dev environments if let Some(cookie_metadata) = cookie { finish_request .metadata_mut() .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata); } identity_client .update_user_password_finish(finish_request) .await?; Ok(()) } fn delete_user( user_id: String, device_id: String, access_token: String, promise_id: u32, ) { RUNTIME.spawn(async move { let auth_info = AuthInfo { access_token, user_id, device_id, }; let result = delete_user_helper(auth_info).await; handle_void_result_as_callback(result, promise_id); }); } async fn delete_user_helper(auth_info: AuthInfo) -> Result<(), Error> { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, auth_info.user_id, auth_info.device_id, auth_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; identity_client.delete_user(Empty {}).await?; Ok(()) } struct GetOutboundKeysRequestInfo { user_id: String, } struct GetInboundKeysRequestInfo { user_id: String, } // This struct should not be altered without also updating // OutboundKeyInfoResponse in lib/types/identity-service-types.js #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct OutboundKeyInfoResponse { pub payload: String, pub payload_signature: String, pub social_proof: Option, pub content_prekey: String, pub content_prekey_signature: String, pub notif_prekey: String, pub notif_prekey_signature: String, pub one_time_content_prekey: Option, pub one_time_notif_prekey: Option, } // This struct should not be altered without also updating // InboundKeyInfoResponse in lib/types/identity-service-types.js #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct InboundKeyInfoResponse { pub payload: String, pub payload_signature: String, pub social_proof: Option, pub content_prekey: String, pub content_prekey_signature: String, pub notif_prekey: String, pub notif_prekey_signature: String, } impl TryFrom for OutboundKeyInfoResponse { type Error = Error; fn try_from(key_info: OutboundKeyInfo) -> Result { let identity_info = key_info.identity_info.ok_or(Error::MissingResponseData)?; let IdentityKeyInfo { payload, payload_signature, social_proof, } = identity_info; let content_prekey = key_info.content_prekey.ok_or(Error::MissingResponseData)?; - let PreKey { - pre_key: content_prekey_value, - pre_key_signature: content_prekey_signature, + let Prekey { + prekey: content_prekey_value, + prekey_signature: content_prekey_signature, } = content_prekey; let notif_prekey = key_info.notif_prekey.ok_or(Error::MissingResponseData)?; - let PreKey { - pre_key: notif_prekey_value, - pre_key_signature: notif_prekey_signature, + let Prekey { + prekey: notif_prekey_value, + prekey_signature: notif_prekey_signature, } = notif_prekey; let one_time_content_prekey = key_info.one_time_content_prekey; let one_time_notif_prekey = key_info.one_time_notif_prekey; Ok(Self { payload, payload_signature, social_proof, content_prekey: content_prekey_value, content_prekey_signature, notif_prekey: notif_prekey_value, notif_prekey_signature, one_time_content_prekey, one_time_notif_prekey, }) } } fn get_outbound_keys_for_user( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_id: String, promise_id: u32, ) { RUNTIME.spawn(async move { let get_outbound_keys_request_info = GetOutboundKeysRequestInfo { user_id }; let auth_info = AuthInfo { access_token: auth_access_token, user_id: auth_user_id, device_id: auth_device_id, }; let result = get_outbound_keys_for_user_helper( get_outbound_keys_request_info, auth_info, ) .await; handle_string_result_as_callback(result, promise_id); }); } async fn get_outbound_keys_for_user_helper( get_outbound_keys_request_info: GetOutboundKeysRequestInfo, auth_info: AuthInfo, ) -> Result { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, auth_info.user_id, auth_info.device_id, auth_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client .get_outbound_keys_for_user(OutboundKeysForUserRequest { user_id: get_outbound_keys_request_info.user_id, }) .await? .into_inner(); let outbound_key_info: Vec = response .devices .into_values() .map(OutboundKeyInfoResponse::try_from) .collect::, _>>()?; Ok(serde_json::to_string(&outbound_key_info)?) } impl TryFrom for InboundKeyInfoResponse { type Error = Error; fn try_from(key_info: InboundKeyInfo) -> Result { let identity_info = key_info.identity_info.ok_or(Error::MissingResponseData)?; let IdentityKeyInfo { payload, payload_signature, social_proof, } = identity_info; let content_prekey = key_info.content_prekey.ok_or(Error::MissingResponseData)?; - let PreKey { - pre_key: content_prekey_value, - pre_key_signature: content_prekey_signature, + let Prekey { + prekey: content_prekey_value, + prekey_signature: content_prekey_signature, } = content_prekey; let notif_prekey = key_info.notif_prekey.ok_or(Error::MissingResponseData)?; - let PreKey { - pre_key: notif_prekey_value, - pre_key_signature: notif_prekey_signature, + let Prekey { + prekey: notif_prekey_value, + prekey_signature: notif_prekey_signature, } = notif_prekey; Ok(Self { payload, payload_signature, social_proof, content_prekey: content_prekey_value, content_prekey_signature, notif_prekey: notif_prekey_value, notif_prekey_signature, }) } } fn get_inbound_keys_for_user( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_id: String, promise_id: u32, ) { RUNTIME.spawn(async move { let get_inbound_keys_request_info = GetInboundKeysRequestInfo { user_id }; let auth_info = AuthInfo { access_token: auth_access_token, user_id: auth_user_id, device_id: auth_device_id, }; let result = get_inbound_keys_for_user_helper( get_inbound_keys_request_info, auth_info, ) .await; handle_string_result_as_callback(result, promise_id); }); } async fn get_inbound_keys_for_user_helper( get_inbound_keys_request_info: GetInboundKeysRequestInfo, auth_info: AuthInfo, ) -> Result { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, auth_info.user_id, auth_info.device_id, auth_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client .get_inbound_keys_for_user(InboundKeysForUserRequest { user_id: get_inbound_keys_request_info.user_id, }) .await? .into_inner(); let inbound_key_info: Vec = response .devices .into_values() .map(InboundKeyInfoResponse::try_from) .collect::, _>>()?; Ok(serde_json::to_string(&inbound_key_info)?) } #[instrument] fn upload_one_time_keys( auth_user_id: String, auth_device_id: String, auth_access_token: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ) { RUNTIME.spawn(async move { let upload_request = UploadOneTimeKeysRequest { - content_one_time_pre_keys: content_one_time_keys, - notif_one_time_pre_keys: notif_one_time_keys, + content_one_time_prekeys: content_one_time_keys, + notif_one_time_prekeys: notif_one_time_keys, }; let auth_info = AuthInfo { access_token: auth_access_token, user_id: auth_user_id, device_id: auth_device_id, }; let result = upload_one_time_keys_helper(auth_info, upload_request).await; handle_void_result_as_callback(result, promise_id); }); } async fn upload_one_time_keys_helper( auth_info: AuthInfo, upload_request: UploadOneTimeKeysRequest, ) -> Result<(), Error> { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, auth_info.user_id, auth_info.device_id, auth_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; identity_client.upload_one_time_keys(upload_request).await?; Ok(()) } #[derive( Debug, derive_more::Display, derive_more::From, derive_more::Error, )] pub enum Error { #[display(...)] TonicGRPC(Status), #[display(...)] SerdeJson(serde_json::Error), #[display(...)] MissingResponseData, GRPClient(grpc_clients::error::Error), } #[cfg(test)] mod tests { use super::{BACKUP_SOCKET_ADDR, CODE_VERSION, IDENTITY_SOCKET_ADDR}; #[test] fn test_code_version_exists() { assert!(CODE_VERSION > 0); } #[test] fn test_identity_socket_addr_exists() { assert!(IDENTITY_SOCKET_ADDR.len() > 0); assert!(BACKUP_SOCKET_ADDR.len() > 0); } } diff --git a/services/commtest/src/identity/device.rs b/services/commtest/src/identity/device.rs index 4fc747f99..8fdb15094 100644 --- a/services/commtest/src/identity/device.rs +++ b/services/commtest/src/identity/device.rs @@ -1,103 +1,103 @@ use comm_opaque2::client::Registration; use grpc_clients::identity::get_unauthenticated_client; use rand::{distributions::Alphanumeric, Rng}; use crate::identity::olm_account_infos::{ ClientPublicKeys, DEFAULT_CLIENT_KEYS, }; use crate::service_addr; use grpc_clients::identity::protos::client::{ - DeviceKeyUpload, DeviceType, IdentityKeyInfo, PreKey, + DeviceKeyUpload, DeviceType, IdentityKeyInfo, Prekey, RegistrationFinishRequest, RegistrationStartRequest, }; pub const PLACEHOLDER_CODE_VERSION: u64 = 0; pub const DEVICE_TYPE: &str = "service"; pub struct DeviceInfo { pub username: String, pub user_id: String, pub device_id: String, pub access_token: String, } pub async fn create_device(keys: Option<&ClientPublicKeys>) -> DeviceInfo { let password = "pass"; let username: String = rand::thread_rng() .sample_iter(&Alphanumeric) .take(7) .map(char::from) .collect(); // TODO: Generate dynamic valid olm account info let keys = keys.unwrap_or_else(|| &DEFAULT_CLIENT_KEYS); let example_payload = serde_json::to_string(&keys).expect("Failed to serialize example payload"); // The ed25519 value from the olm payload let device_id = &keys.primary_identity_public_keys.ed25519; 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(), social_proof: None, }), - content_upload: Some(PreKey { - pre_key: "content_prekey".to_string(), - pre_key_signature: "content_prekey_sig".to_string(), + content_upload: Some(Prekey { + prekey: "content_prekey".to_string(), + prekey_signature: "content_prekey_sig".to_string(), }), - notif_upload: Some(PreKey { - pre_key: "notif_prekey".to_string(), - pre_key_signature: "notif_prekey_sig".to_string(), + notif_upload: Some(Prekey { + prekey: "notif_prekey".to_string(), + prekey_signature: "notif_prekey_sig".to_string(), }), one_time_content_prekeys: Vec::new(), one_time_notif_prekeys: Vec::new(), device_type: DeviceType::Keyserver.into(), }), }; let mut identity_client = get_unauthenticated_client( &service_addr::IDENTITY_GRPC.to_string(), PLACEHOLDER_CODE_VERSION, DEVICE_TYPE.to_string(), ) .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, } } diff --git a/services/commtest/tests/identity_access_tokens_tests.rs b/services/commtest/tests/identity_access_tokens_tests.rs index e09644ef0..d0717bcf2 100644 --- a/services/commtest/tests/identity_access_tokens_tests.rs +++ b/services/commtest/tests/identity_access_tokens_tests.rs @@ -1,34 +1,34 @@ use commtest::identity::device::{ create_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::service_addr; use grpc_clients::identity::{ get_unauthenticated_client, protos::client::VerifyUserAccessTokenRequest, }; #[tokio::test] async fn verify_access_token() { let identity_grpc_endpoint = service_addr::IDENTITY_GRPC.to_string(); let device_info = create_device(None).await; let mut identity_client = get_unauthenticated_client( &identity_grpc_endpoint, PLACEHOLDER_CODE_VERSION, DEVICE_TYPE.to_string(), ) .await .expect("Couldn't connect to identity service"); let verify_request = VerifyUserAccessTokenRequest { user_id: device_info.user_id, - signing_public_key: device_info.device_id, + device_id: device_info.device_id, access_token: device_info.access_token, }; let response = identity_client .verify_user_access_token(verify_request) .await .unwrap(); assert!(response.into_inner().token_valid); } diff --git a/services/commtest/tests/identity_keyserver_tests.rs b/services/commtest/tests/identity_keyserver_tests.rs index ec093066d..885558d07 100644 --- a/services/commtest/tests/identity_keyserver_tests.rs +++ b/services/commtest/tests/identity_keyserver_tests.rs @@ -1,72 +1,72 @@ use commtest::identity::device::{ create_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::service_addr; 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 = create_device(None).await; let mut client = get_auth_client( &identity_grpc_endpoint, device_info.user_id.clone(), device_info.device_id, device_info.access_token, PLACEHOLDER_CODE_VERSION, DEVICE_TYPE.to_string(), ) .await .expect("Couldn't connect to identity service"); let upload_request = UploadOneTimeKeysRequest { - content_one_time_pre_keys: vec!["content1".to_string()], - notif_one_time_pre_keys: vec!["notif1".to_string()], + content_one_time_prekeys: vec!["content1".to_string()], + notif_one_time_prekeys: vec!["notif1".to_string()], }; 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("Second keyserver keys request failed") .into_inner() .keyserver_info .unwrap(); assert_eq!( first_reponse.one_time_content_prekey, Some("content1".to_string()) ); assert_eq!( first_reponse.one_time_notif_prekey, Some("notif1".to_string()) ); 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 f58cb2d71..bb0c973eb 100644 --- a/services/commtest/tests/identity_one_time_key_tests.rs +++ b/services/commtest/tests/identity_one_time_key_tests.rs @@ -1,36 +1,36 @@ use commtest::identity::device::{ create_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::service_addr; use grpc_clients::identity::{ get_auth_client, protos::authenticated::UploadOneTimeKeysRequest, }; #[tokio::test] async fn upload_one_time_keys() { let device_info = create_device(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, PLACEHOLDER_CODE_VERSION, DEVICE_TYPE.to_string(), ) .await .expect("Couldn't connect to identity service"); let upload_request = UploadOneTimeKeysRequest { - content_one_time_pre_keys: vec![ + content_one_time_prekeys: vec![ "content1".to_string(), "content2".to_string(), ], - notif_one_time_pre_keys: vec!["notif1".to_string(), "notif2".to_string()], + notif_one_time_prekeys: vec!["notif1".to_string(), "notif2".to_string()], }; identity_client .upload_one_time_keys(upload_request) .await .unwrap(); } diff --git a/services/commtest/tests/identity_prekey_tests.rs b/services/commtest/tests/identity_prekey_tests.rs index d04283051..72a481ee0 100644 --- a/services/commtest/tests/identity_prekey_tests.rs +++ b/services/commtest/tests/identity_prekey_tests.rs @@ -1,41 +1,41 @@ use commtest::identity::device::{ create_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::service_addr; use grpc_clients::identity::{ get_auth_client, - protos::{authenticated::RefreshUserPreKeysRequest, client::PreKey}, + protos::{authenticated::RefreshUserPrekeysRequest, client::Prekey}, }; #[tokio::test] async fn set_prekey() { let device_info = create_device(None).await; let mut client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), device_info.user_id, device_info.device_id, device_info.access_token, PLACEHOLDER_CODE_VERSION, DEVICE_TYPE.to_string(), ) .await .expect("Couldn't connect to identity service"); - let upload_request = RefreshUserPreKeysRequest { - new_content_pre_keys: Some(PreKey { - pre_key: "content_prekey".to_string(), - pre_key_signature: "content_prekey_signature".to_string(), + let upload_request = RefreshUserPrekeysRequest { + new_content_prekeys: Some(Prekey { + prekey: "content_prekey".to_string(), + prekey_signature: "content_prekey_signature".to_string(), }), - new_notif_pre_keys: Some(PreKey { - pre_key: "content_prekey".to_string(), - pre_key_signature: "content_prekey_signature".to_string(), + new_notif_prekeys: Some(Prekey { + prekey: "content_prekey".to_string(), + prekey_signature: "content_prekey_signature".to_string(), }), }; // This send will fail if the one-time keys weren't successfully added println!( "Error: {:?}", - client.refresh_user_pre_keys(upload_request).await + client.refresh_user_prekeys(upload_request).await ); } diff --git a/services/commtest/tests/identity_tunnelbroker_tests.rs b/services/commtest/tests/identity_tunnelbroker_tests.rs index b15ce0ad7..6f78ddc07 100644 --- a/services/commtest/tests/identity_tunnelbroker_tests.rs +++ b/services/commtest/tests/identity_tunnelbroker_tests.rs @@ -1,88 +1,88 @@ use commtest::identity::device::{ create_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::service_addr; use commtest::tunnelbroker::socket::{create_socket, receive_message}; use futures_util::StreamExt; use grpc_clients::identity::get_auth_client; use grpc_clients::identity::protos::authenticated::{ OutboundKeysForUserRequest, UploadOneTimeKeysRequest, }; use tunnelbroker_messages::RefreshKeyRequest; #[tokio::test] async fn test_tunnelbroker_invalid_auth() { let mut device_info = create_device(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 = create_device(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() { let identity_grpc_endpoint = service_addr::IDENTITY_GRPC.to_string(); let device_info = create_device(None).await; // Request outbound keys, which should trigger identity service to ask for more keys let mut client = get_auth_client( &identity_grpc_endpoint, device_info.user_id.clone(), device_info.device_id, device_info.access_token, PLACEHOLDER_CODE_VERSION, DEVICE_TYPE.to_string(), ) .await .expect("Couldn't connect to identity service"); let upload_request = UploadOneTimeKeysRequest { - content_one_time_pre_keys: vec!["content1".to_string()], - notif_one_time_pre_keys: vec!["notif1".to_string()], + content_one_time_prekeys: vec!["content1".to_string()], + notif_one_time_prekeys: vec!["notif1".to_string()], }; client.upload_one_time_keys(upload_request).await.unwrap(); 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("Second keyserver keys request failed") .into_inner() .keyserver_info .unwrap(); // The current threshold is 5, but we only upload two. Should receive request // from Tunnelbroker to refresh keys // Create session as a keyserver let device_info = create_device(None).await; let mut socket = create_socket(&device_info).await.unwrap(); for _ in 0..2 { let response = receive_message(&mut socket).await.unwrap(); let serialized_response: RefreshKeyRequest = serde_json::from_str(&response).unwrap(); let expected_response = RefreshKeyRequest { device_id: device_info.device_id.to_string(), number_of_keys: 5, }; assert_eq!(serialized_response, expected_response); } } diff --git a/services/identity/src/client_service.rs b/services/identity/src/client_service.rs index a6b79bc7b..8b5d571b5 100644 --- a/services/identity/src/client_service.rs +++ b/services/identity/src/client_service.rs @@ -1,770 +1,770 @@ // Standard library imports use std::str::FromStr; // External crate imports use comm_lib::aws::DynamoDBError; use comm_opaque2::grpc::protocol_error_to_grpc_status; use moka::future::Cache; use rand::rngs::OsRng; use siwe::eip55; use tonic::Response; use tracing::{debug, error}; // Workspace crate imports use crate::config::CONFIG; use crate::database::{ DBDeviceTypeInt, DatabaseClient, DeviceType, KeyPayload, }; use crate::error::Error as DBError; use crate::grpc_services::protos::unauth::{ AddReservedUsernamesRequest, Empty, GenerateNonceResponse, OpaqueLoginFinishRequest, OpaqueLoginFinishResponse, OpaqueLoginStartRequest, OpaqueLoginStartResponse, RegistrationFinishRequest, RegistrationFinishResponse, RegistrationStartRequest, RegistrationStartResponse, RemoveReservedUsernameRequest, ReservedRegistrationStartRequest, ReservedWalletLoginRequest, VerifyUserAccessTokenRequest, VerifyUserAccessTokenResponse, WalletLoginRequest, WalletLoginResponse, }; use crate::grpc_utils::DeviceKeyUploadActions; use crate::id::generate_uuid; use crate::nonce::generate_nonce_data; use crate::reserved_users::{ validate_account_ownership_message_and_get_user_id, validate_add_reserved_usernames_message, validate_remove_reserved_username_message, }; use crate::siwe::{is_valid_ethereum_address, parse_and_verify_siwe_message}; use crate::token::{AccessTokenData, AuthType}; pub use crate::grpc_services::protos::client::identity_client_service_server::{ IdentityClientService, IdentityClientServiceServer, }; #[derive(Clone)] pub enum WorkflowInProgress { Registration(Box), Login(Box), Update(UpdateState), } #[derive(Clone)] pub struct UserRegistrationInfo { pub username: String, pub flattened_device_key_upload: FlattenedDeviceKeyUpload, pub user_id: Option, } #[derive(Clone)] pub struct UserLoginInfo { pub user_id: String, pub flattened_device_key_upload: FlattenedDeviceKeyUpload, pub opaque_server_login: comm_opaque2::server::Login, } #[derive(Clone)] pub struct UpdateState { pub user_id: String, } #[derive(Clone)] pub struct FlattenedDeviceKeyUpload { pub device_id_key: String, pub key_payload: String, pub key_payload_signature: String, pub content_prekey: String, pub content_prekey_signature: String, pub content_one_time_keys: Vec, pub notif_prekey: String, pub notif_prekey_signature: String, pub notif_one_time_keys: Vec, pub device_type: DeviceType, } #[derive(derive_more::Constructor)] pub struct ClientService { client: DatabaseClient, cache: Cache, } #[tonic::async_trait] impl IdentityClientService for ClientService { async fn register_password_user_start( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); debug!("Received registration request for: {}", message.username); self.check_username_taken(&message.username).await?; let username_in_reserved_usernames_table = self .client .username_in_reserved_usernames_table(&message.username) .await .map_err(handle_db_error)?; if username_in_reserved_usernames_table { return Err(tonic::Status::already_exists("username already exists")); } if CONFIG.reserved_usernames.contains(&message.username) || is_valid_ethereum_address(&message.username) { return Err(tonic::Status::invalid_argument("username reserved")); } 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( &CONFIG.server_setup, &message.opaque_registration_request, message.username.as_bytes(), ) .map_err(protocol_error_to_grpc_status)?; let session_id = self .cache .insert_with_uuid_key(WorkflowInProgress::Registration(Box::new( registration_state, ))) .await; let response = RegistrationStartResponse { session_id, opaque_registration_response: server_message, }; Ok(Response::new(response)) } async fn register_reserved_password_user_start( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); self.check_username_taken(&message.username).await?; if CONFIG.reserved_usernames.contains(&message.username) { return Err(tonic::Status::invalid_argument("username reserved")); } let username_in_reserved_usernames_table = self .client .username_in_reserved_usernames_table(&message.username) .await .map_err(handle_db_error)?; if !username_in_reserved_usernames_table { return Err(tonic::Status::permission_denied("username not reserved")); } let user_id = validate_account_ownership_message_and_get_user_id( &message.username, &message.keyserver_message, &message.keyserver_signature, )?; 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( &CONFIG.server_setup, &message.opaque_registration_request, message.username.as_bytes(), ) .map_err(protocol_error_to_grpc_status)?; let session_id = self .cache .insert_with_uuid_key(WorkflowInProgress::Registration(Box::new( registration_state, ))) .await; let response = RegistrationStartResponse { session_id, opaque_registration_response: server_message, }; Ok(Response::new(response)) } async fn register_password_user_finish( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); if let Some(WorkflowInProgress::Registration(state)) = self.cache.get(&message.session_id) { self.cache.invalidate(&message.session_id).await; let server_registration = comm_opaque2::server::Registration::new(); let password_file = server_registration .finish(&message.opaque_registration_upload) .map_err(protocol_error_to_grpc_status)?; let device_id = state.flattened_device_key_upload.device_id_key.clone(); let user_id = self .client .add_password_user_to_users_table(*state, password_file) .await .map_err(handle_db_error)?; // Create access token let token = AccessTokenData::new( user_id.clone(), device_id, 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 = RegistrationFinishResponse { user_id, access_token, }; Ok(Response::new(response)) } else { Err(tonic::Status::not_found("session not found")) } } - async fn login_password_user_start( + async fn log_in_password_user_start( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); debug!("Attempting to login user: {:?}", &message.username); let user_id_and_password_file = self .client .get_user_id_and_password_file_from_username(&message.username) .await .map_err(handle_db_error)?; let (user_id, password_file_bytes) = if let Some(data) = user_id_and_password_file { data } else { // It's possible that the user attempting login is already registered // on Ashoat's keyserver. If they are, we should send back a gRPC status // code instructing them to get a signed message from Ashoat's keyserver // in order to claim their username and register with the Identity // service. let username_in_reserved_usernames_table = self .client .username_in_reserved_usernames_table(&message.username) .await .map_err(handle_db_error)?; if username_in_reserved_usernames_table { return Err(tonic::Status::failed_precondition( "need keyserver message to claim username", )); } return Err(tonic::Status::not_found("user not found")); }; 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 login_state = construct_user_login_info(&message, user_id, server_login)?; let session_id = self .cache .insert_with_uuid_key(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( + async fn log_in_password_user_finish( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); if let Some(WorkflowInProgress::Login(state)) = self.cache.get(&message.session_id) { self.cache.invalidate(&message.session_id).await; let mut server_login = state.opaque_server_login.clone(); server_login .finish(&message.opaque_login_upload) .map_err(protocol_error_to_grpc_status)?; self .client .add_password_user_device_to_users_table( state.user_id.clone(), state.flattened_device_key_upload.clone(), ) .await .map_err(handle_db_error)?; // Create access token let token = AccessTokenData::new( state.user_id.clone(), state.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 = OpaqueLoginFinishResponse { user_id: state.user_id, access_token, }; Ok(Response::new(response)) } else { Err(tonic::Status::not_found("session not found")) } } - async fn login_wallet_user( + async fn log_in_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); let flattened_device_key_upload = construct_flattened_device_key_upload(&message)?; let social_proof = message .social_proof()? .ok_or_else(|| tonic::Status::invalid_argument("malformed payload"))?; let user_id = match self .client .get_user_id_from_user_info(wallet_address.clone(), &AuthType::Wallet) .await .map_err(handle_db_error)? { Some(id) => { // User already exists, so we should update the DDB item self .client .add_wallet_user_device_to_users_table( id.clone(), flattened_device_key_upload.clone(), social_proof, ) .await .map_err(handle_db_error)?; id } None => { // It's possible that the user attempting login is already registered // on Ashoat's keyserver. If they are, we should send back a gRPC status // code instructing them to get a signed message from Ashoat's keyserver // in order to claim their wallet address and register with the Identity // service. let username_in_reserved_usernames_table = self .client .username_in_reserved_usernames_table(&wallet_address) .await .map_err(handle_db_error)?; if username_in_reserved_usernames_table { return Err(tonic::Status::failed_precondition( "need keyserver message to claim username", )); } // User doesn't exist yet and wallet address isn't reserved, so we // should add a new user in DDB self .client .add_wallet_user_to_users_table( flattened_device_key_upload.clone(), wallet_address, social_proof, None, ) .await .map_err(handle_db_error)? } }; // Create access token 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 login_reserved_wallet_user( + async fn log_in_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 flattened_device_key_upload = construct_flattened_device_key_upload(&message)?; 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 generate_nonce( &self, _request: tonic::Request, ) -> Result, tonic::Status> { let nonce_data = generate_nonce_data(&mut OsRng); match self .client .add_nonce_to_nonces_table(nonce_data.clone()) .await { Ok(_) => Ok(Response::new(GenerateNonceResponse { nonce: nonce_data.nonce, })), Err(e) => Err(handle_db_error(e)), } } async fn verify_user_access_token( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); - debug!("Verifying device: {}", &message.signing_public_key); + debug!("Verifying device: {}", &message.device_id); let token_valid = self .client .verify_access_token( message.user_id, - message.signing_public_key.clone(), + message.device_id.clone(), message.access_token, ) .await .map_err(handle_db_error)?; let response = Response::new(VerifyUserAccessTokenResponse { token_valid }); debug!( "device {} was verified: {}", - &message.signing_public_key, token_valid + &message.device_id, token_valid ); Ok(response) } async fn add_reserved_usernames( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); let usernames = validate_add_reserved_usernames_message( &message.message, &message.signature, )?; let filtered_usernames = self .client .filter_out_taken_usernames(usernames) .await .map_err(handle_db_error)?; self .client .add_usernames_to_reserved_usernames_table(filtered_usernames) .await .map_err(handle_db_error)?; let response = Response::new(Empty {}); Ok(response) } async fn remove_reserved_username( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); let username = validate_remove_reserved_username_message( &message.message, &message.signature, )?; self .client .delete_username_from_reserved_usernames_table(username) .await .map_err(handle_db_error)?; let response = Response::new(Empty {}); Ok(response) } async fn ping( &self, _request: tonic::Request, ) -> Result, tonic::Status> { let response = Response::new(Empty {}); Ok(response) } } impl ClientService { async fn check_username_taken( &self, username: &str, ) -> Result<(), tonic::Status> { let username_taken = self .client .username_taken(username.to_string()) .await .map_err(handle_db_error)?; if username_taken { return Err(tonic::Status::already_exists("username already exists")); } 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(()) } } #[tonic::async_trait] pub trait CacheExt { async fn insert_with_uuid_key(&self, value: T) -> String; } #[tonic::async_trait] impl CacheExt for Cache where T: Clone + Send + Sync + 'static, { async fn insert_with_uuid_key(&self, value: T) -> String { let session_id = generate_uuid(); self.insert(session_id.clone(), value).await; session_id } } pub fn handle_db_error(db_error: DBError) -> tonic::Status { match db_error { DBError::AwsSdk(DynamoDBError::InternalServerError(_)) | DBError::AwsSdk(DynamoDBError::ProvisionedThroughputExceededException( _, )) | DBError::AwsSdk(DynamoDBError::RequestLimitExceeded(_)) => { tonic::Status::unavailable("please retry") } e => { error!("Encountered an unexpected error: {}", e); tonic::Status::failed_precondition("unexpected error") } } } fn construct_user_registration_info( message: &impl DeviceKeyUploadActions, user_id: Option, username: String, ) -> Result { Ok(UserRegistrationInfo { username, flattened_device_key_upload: construct_flattened_device_key_upload( message, )?, user_id, }) } fn construct_user_login_info( message: &impl DeviceKeyUploadActions, user_id: String, opaque_server_login: comm_opaque2::server::Login, ) -> Result { Ok(UserLoginInfo { user_id, flattened_device_key_upload: construct_flattened_device_key_upload( message, )?, opaque_server_login, }) } fn construct_flattened_device_key_upload( message: &impl DeviceKeyUploadActions, ) -> Result { 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)?, }; Ok(flattened_device_key_upload) } diff --git a/services/identity/src/grpc_services/authenticated.rs b/services/identity/src/grpc_services/authenticated.rs index b827f77cf..395433428 100644 --- a/services/identity/src/grpc_services/authenticated.rs +++ b/services/identity/src/grpc_services/authenticated.rs @@ -1,508 +1,508 @@ use std::collections::HashMap; use crate::config::CONFIG; use crate::database::DeviceListRow; use crate::grpc_utils::DeviceInfoWithAuth; use crate::{ client_service::{ handle_db_error, CacheExt, UpdateState, WorkflowInProgress, }, constants::request_metadata, database::DatabaseClient, ddb_utils::DateTimeExt, grpc_services::shared::get_value, token::AuthType, }; use chrono::{DateTime, Utc}; use comm_opaque2::grpc::protocol_error_to_grpc_status; use moka::future::Cache; use tonic::{Request, Response, Status}; use tracing::{debug, error}; use super::protos::auth::{ find_user_id_request, identity_client_service_server::IdentityClientService, FindUserIdRequest, FindUserIdResponse, GetDeviceListRequest, GetDeviceListResponse, InboundKeyInfo, InboundKeysForUserRequest, InboundKeysForUserResponse, KeyserverKeysResponse, OutboundKeyInfo, OutboundKeysForUserRequest, OutboundKeysForUserResponse, - RefreshUserPreKeysRequest, UpdateUserPasswordFinishRequest, + RefreshUserPrekeysRequest, UpdateUserPasswordFinishRequest, UpdateUserPasswordStartRequest, UpdateUserPasswordStartResponse, UploadOneTimeKeysRequest, }; -use super::protos::client::{Empty, IdentityKeyInfo, PreKey}; +use super::protos::client::{Empty, IdentityKeyInfo, Prekey}; #[derive(derive_more::Constructor)] pub struct AuthenticatedService { db_client: DatabaseClient, cache: Cache, } fn get_auth_info(req: &Request<()>) -> Option<(String, String, String)> { debug!("Retrieving auth info for request: {:?}", req); let user_id = get_value(req, request_metadata::USER_ID)?; let device_id = get_value(req, request_metadata::DEVICE_ID)?; let access_token = get_value(req, request_metadata::ACCESS_TOKEN)?; Some((user_id, device_id, access_token)) } pub fn auth_interceptor( req: Request<()>, db_client: &DatabaseClient, ) -> Result, Status> { debug!("Intercepting request to check auth info: {:?}", req); let (user_id, device_id, access_token) = get_auth_info(&req) .ok_or_else(|| Status::unauthenticated("Missing credentials"))?; let handle = tokio::runtime::Handle::current(); let new_db_client = db_client.clone(); // This function cannot be `async`, yet must call the async db call // Force tokio to resolve future in current thread without an explicit .await let valid_token = tokio::task::block_in_place(move || { handle .block_on(new_db_client.verify_access_token( user_id, device_id, access_token, )) .map_err(handle_db_error) })?; if !valid_token { return Err(Status::aborted("Bad Credentials")); } Ok(req) } pub fn get_user_and_device_id( request: &Request, ) -> Result<(String, String), Status> { let user_id = get_value(request, request_metadata::USER_ID) .ok_or_else(|| Status::unauthenticated("Missing user_id field"))?; let device_id = get_value(request, request_metadata::DEVICE_ID) .ok_or_else(|| Status::unauthenticated("Missing device_id field"))?; Ok((user_id, device_id)) } #[tonic::async_trait] impl IdentityClientService for AuthenticatedService { - async fn refresh_user_pre_keys( + async fn refresh_user_prekeys( &self, - request: Request, + request: Request, ) -> Result, Status> { let (user_id, device_id) = get_user_and_device_id(&request)?; let message = request.into_inner(); debug!("Refreshing prekeys for user: {}", user_id); let content_keys = message - .new_content_pre_keys + .new_content_prekeys .ok_or_else(|| Status::invalid_argument("Missing content keys"))?; let notif_keys = message - .new_notif_pre_keys + .new_notif_prekeys .ok_or_else(|| Status::invalid_argument("Missing notification keys"))?; self .db_client .set_prekey( user_id, device_id, - content_keys.pre_key, - content_keys.pre_key_signature, - notif_keys.pre_key, - notif_keys.pre_key_signature, + content_keys.prekey, + content_keys.prekey_signature, + notif_keys.prekey, + notif_keys.prekey_signature, ) .await .map_err(handle_db_error)?; let response = Response::new(Empty {}); Ok(response) } async fn get_outbound_keys_for_user( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); let devices_map = self .db_client .get_keys_for_user_id(&message.user_id, true) .await .map_err(handle_db_error)? .ok_or_else(|| tonic::Status::not_found("user not found"))?; let transformed_devices = devices_map .into_iter() .filter_map(|(key, device_info)| { let device_info_with_auth = DeviceInfoWithAuth { device_info, auth_type: None, }; match OutboundKeyInfo::try_from(device_info_with_auth) { Ok(key_info) => Some((key, key_info)), Err(_) => { error!("Failed to transform device info for key {}", key); None } } }) .collect::>(); Ok(tonic::Response::new(OutboundKeysForUserResponse { devices: transformed_devices, })) } async fn get_inbound_keys_for_user( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); let devices_map = self .db_client .get_keys_for_user_id(&message.user_id, false) .await .map_err(handle_db_error)? .ok_or_else(|| tonic::Status::not_found("user not found"))?; let transformed_devices = devices_map .into_iter() .filter_map(|(key, device_info)| { let device_info_with_auth = DeviceInfoWithAuth { device_info, auth_type: None, }; match InboundKeyInfo::try_from(device_info_with_auth) { Ok(key_info) => Some((key, key_info)), Err(_) => { error!("Failed to transform device info for key {}", key); None } } }) .collect::>(); Ok(tonic::Response::new(InboundKeysForUserResponse { devices: transformed_devices, })) } async fn get_keyserver_keys( &self, request: Request, ) -> Result, Status> { let message = request.into_inner(); let inner_response = self .db_client .get_keyserver_keys_for_user(&message.user_id) .await .map_err(handle_db_error)? .map(|db_keys| OutboundKeyInfo { identity_info: Some(IdentityKeyInfo { payload: db_keys.key_payload, payload_signature: db_keys.key_payload_signature, social_proof: db_keys.social_proof, }), - content_prekey: Some(PreKey { - pre_key: db_keys.content_prekey.prekey, - pre_key_signature: db_keys.content_prekey.prekey_signature, + content_prekey: Some(Prekey { + prekey: db_keys.content_prekey.prekey, + prekey_signature: db_keys.content_prekey.prekey_signature, }), - notif_prekey: Some(PreKey { - pre_key: db_keys.notif_prekey.prekey, - pre_key_signature: db_keys.notif_prekey.prekey_signature, + notif_prekey: Some(Prekey { + prekey: db_keys.notif_prekey.prekey, + prekey_signature: db_keys.notif_prekey.prekey_signature, }), one_time_content_prekey: db_keys.content_one_time_key, one_time_notif_prekey: db_keys.notif_one_time_key, }); let response = Response::new(KeyserverKeysResponse { keyserver_info: inner_response, }); return Ok(response); } async fn upload_one_time_keys( &self, request: tonic::Request, ) -> Result, tonic::Status> { let (user_id, device_id) = get_user_and_device_id(&request)?; let message = request.into_inner(); debug!("Attempting to update one time keys for user: {}", user_id); self .db_client .append_one_time_prekeys( device_id, - message.content_one_time_pre_keys, - message.notif_one_time_pre_keys, + message.content_one_time_prekeys, + message.notif_one_time_prekeys, ) .await .map_err(handle_db_error)?; Ok(tonic::Response::new(Empty {})) } async fn find_user_id( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); use find_user_id_request::Identifier; let (user_ident, auth_type) = match message.identifier { None => { return Err(tonic::Status::invalid_argument("no identifier provided")) } Some(Identifier::Username(username)) => (username, AuthType::Password), Some(Identifier::WalletAddress(address)) => (address, AuthType::Wallet), }; let (is_reserved_result, user_id_result) = tokio::join!( self .db_client .username_in_reserved_usernames_table(&user_ident), self .db_client .get_user_id_from_user_info(user_ident.clone(), &auth_type), ); let is_reserved = is_reserved_result.map_err(handle_db_error)?; let user_id = user_id_result.map_err(handle_db_error)?; Ok(Response::new(FindUserIdResponse { user_id, is_reserved, })) } async fn update_user_password_start( &self, request: tonic::Request, ) -> Result, tonic::Status> { let (user_id, _) = get_user_and_device_id(&request)?; let message = request.into_inner(); let server_registration = comm_opaque2::server::Registration::new(); let server_message = server_registration .start( &CONFIG.server_setup, &message.opaque_registration_request, user_id.as_bytes(), ) .map_err(protocol_error_to_grpc_status)?; let update_state = UpdateState { user_id }; let session_id = self .cache .insert_with_uuid_key(WorkflowInProgress::Update(update_state)) .await; let response = UpdateUserPasswordStartResponse { session_id, opaque_registration_response: server_message, }; Ok(Response::new(response)) } async fn update_user_password_finish( &self, request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); let Some(WorkflowInProgress::Update(state)) = self.cache.get(&message.session_id) else { return Err(tonic::Status::not_found("session not found")); }; self.cache.invalidate(&message.session_id).await; let server_registration = comm_opaque2::server::Registration::new(); let password_file = server_registration .finish(&message.opaque_registration_upload) .map_err(protocol_error_to_grpc_status)?; self .db_client .update_user_password(state.user_id, password_file) .await .map_err(handle_db_error)?; let response = Empty {}; Ok(Response::new(response)) } async fn log_out_user( &self, request: tonic::Request, ) -> Result, tonic::Status> { let (user_id, device_id) = get_user_and_device_id(&request)?; self .db_client .remove_device_from_users_table(user_id.clone(), device_id.clone()) .await .map_err(handle_db_error)?; self .db_client .delete_access_token_data(user_id, device_id) .await .map_err(handle_db_error)?; let response = Empty {}; Ok(Response::new(response)) } async fn delete_user( &self, request: tonic::Request, ) -> Result, tonic::Status> { let (user_id, _) = get_user_and_device_id(&request)?; self .db_client .delete_user(user_id) .await .map_err(handle_db_error)?; let response = Empty {}; Ok(Response::new(response)) } async fn get_device_list_for_user( &self, request: tonic::Request, ) -> Result, tonic::Status> { let GetDeviceListRequest { user_id, since_timestamp, } = request.into_inner(); let since = since_timestamp .map(|timestamp| { DateTime::::from_utc_timestamp_millis(timestamp) .ok_or_else(|| tonic::Status::invalid_argument("Invalid timestamp")) }) .transpose()?; let mut db_result = self .db_client .get_device_list_history(user_id, since) .await .map_err(handle_db_error)?; // these should be sorted already, but just in case db_result.sort_by_key(|list| list.timestamp); let device_list_updates: Vec = db_result .into_iter() .map(RawDeviceList::from) .map(SignedDeviceList::try_from_raw) .collect::, _>>()?; let stringified_updates = device_list_updates .iter() .map(serde_json::to_string) .collect::, _>>() .map_err(|err| { error!("Failed to serialize device list updates: {}", err); tonic::Status::failed_precondition("unexpected error") })?; Ok(Response::new(GetDeviceListResponse { device_list_updates: stringified_updates, })) } } // raw device list that can be serialized to JSON (and then signed in the future) #[derive(serde::Serialize)] struct RawDeviceList { devices: Vec, timestamp: i64, } impl From for RawDeviceList { fn from(row: DeviceListRow) -> Self { Self { devices: row.device_ids, timestamp: row.timestamp.timestamp_millis(), } } } #[derive(serde::Serialize)] #[serde(rename_all = "camelCase")] struct SignedDeviceList { /// JSON-stringified [`RawDeviceList`] raw_device_list: String, } impl SignedDeviceList { /// Serialize (and sign in the future) a [`RawDeviceList`] fn try_from_raw(raw: RawDeviceList) -> Result { let stringified_list = serde_json::to_string(&raw).map_err(|err| { error!("Failed to serialize raw device list: {}", err); tonic::Status::failed_precondition("unexpected error") })?; Ok(Self { raw_device_list: stringified_list, }) } } #[cfg(test)] mod tests { use super::*; #[test] fn serialize_device_list_updates() { let raw_updates = vec![ RawDeviceList { devices: vec!["device1".into()], timestamp: 111111111, }, RawDeviceList { devices: vec!["device1".into(), "device2".into()], timestamp: 222222222, }, ]; let expected_raw_list1 = r#"{"devices":["device1"],"timestamp":111111111}"#; let expected_raw_list2 = r#"{"devices":["device1","device2"],"timestamp":222222222}"#; let signed_updates = raw_updates .into_iter() .map(SignedDeviceList::try_from_raw) .collect::, _>>() .expect("signing device list updates failed"); assert_eq!(signed_updates[0].raw_device_list, expected_raw_list1); assert_eq!(signed_updates[1].raw_device_list, expected_raw_list2); let stringified_updates = signed_updates .iter() .map(serde_json::to_string) .collect::, _>>() .expect("serialize signed device lists failed"); let expected_stringified_list1 = r#"{"rawDeviceList":"{\"devices\":[\"device1\"],\"timestamp\":111111111}"}"#; let expected_stringified_list2 = r#"{"rawDeviceList":"{\"devices\":[\"device1\",\"device2\"],\"timestamp\":222222222}"}"#; assert_eq!(stringified_updates[0], expected_stringified_list1); assert_eq!(stringified_updates[1], expected_stringified_list2); } } diff --git a/services/identity/src/grpc_utils.rs b/services/identity/src/grpc_utils.rs index 16ff3befa..4c8fe5da2 100644 --- a/services/identity/src/grpc_utils.rs +++ b/services/identity/src/grpc_utils.rs @@ -1,258 +1,258 @@ use std::collections::HashMap; use tonic::Status; use tracing::error; use crate::{ constants::{ CONTENT_ONE_TIME_KEY, NOTIF_ONE_TIME_KEY, USERS_TABLE_DEVICES_MAP_CONTENT_PREKEY_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_CONTENT_PREKEY_SIGNATURE_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_KEY_PAYLOAD_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_KEY_PAYLOAD_SIGNATURE_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_SIGNATURE_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_SOCIAL_PROOF_ATTRIBUTE_NAME, }, database::DeviceKeys, grpc_services::protos::{ auth::{InboundKeyInfo, OutboundKeyInfo}, unauth::{ - DeviceKeyUpload, IdentityKeyInfo, OpaqueLoginStartRequest, PreKey, + DeviceKeyUpload, IdentityKeyInfo, OpaqueLoginStartRequest, Prekey, RegistrationStartRequest, ReservedRegistrationStartRequest, ReservedWalletLoginRequest, WalletLoginRequest, }, }, token::AuthType, }; pub struct DeviceInfoWithAuth<'a> { pub device_info: HashMap, pub auth_type: Option<&'a AuthType>, } impl TryFrom> for InboundKeyInfo { type Error = Status; fn try_from(data: DeviceInfoWithAuth) -> Result { let mut device_info = data.device_info; let identity_info = extract_identity_info(&mut device_info, data.auth_type)?; Ok(InboundKeyInfo { identity_info: Some(identity_info), content_prekey: Some(create_prekey( &mut device_info, USERS_TABLE_DEVICES_MAP_CONTENT_PREKEY_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_CONTENT_PREKEY_SIGNATURE_ATTRIBUTE_NAME, )?), notif_prekey: Some(create_prekey( &mut device_info, USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_SIGNATURE_ATTRIBUTE_NAME, )?), }) } } impl TryFrom> for OutboundKeyInfo { type Error = Status; fn try_from(data: DeviceInfoWithAuth) -> Result { let mut device_info = data.device_info; let identity_info = extract_identity_info(&mut device_info, data.auth_type)?; let content_one_time_key = device_info.remove(CONTENT_ONE_TIME_KEY); let notif_one_time_key = device_info.remove(NOTIF_ONE_TIME_KEY); Ok(OutboundKeyInfo { identity_info: Some(identity_info), content_prekey: Some(create_prekey( &mut device_info, USERS_TABLE_DEVICES_MAP_CONTENT_PREKEY_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_CONTENT_PREKEY_SIGNATURE_ATTRIBUTE_NAME, )?), notif_prekey: Some(create_prekey( &mut device_info, USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_SIGNATURE_ATTRIBUTE_NAME, )?), one_time_content_prekey: content_one_time_key, one_time_notif_prekey: notif_one_time_key, }) } } fn extract_key( device_info: &mut DeviceKeys, key: &str, ) -> Result { device_info.remove(key).ok_or_else(|| { error!("{} missing from device info", key); Status::failed_precondition("Database item malformed") }) } fn extract_identity_info( device_info: &mut HashMap, auth_type: Option<&AuthType>, ) -> Result { let payload = extract_key( device_info, USERS_TABLE_DEVICES_MAP_KEY_PAYLOAD_ATTRIBUTE_NAME, )?; let payload_signature = extract_key( device_info, USERS_TABLE_DEVICES_MAP_KEY_PAYLOAD_SIGNATURE_ATTRIBUTE_NAME, )?; let social_proof = device_info.remove(USERS_TABLE_DEVICES_MAP_SOCIAL_PROOF_ATTRIBUTE_NAME); if social_proof.is_none() && auth_type == Some(&AuthType::Wallet) { error!("Social proof missing for wallet user"); return Err(Status::failed_precondition("Database item malformed")); } Ok(IdentityKeyInfo { payload, payload_signature, social_proof, }) } fn create_prekey( device_info: &mut HashMap, key_attr: &str, signature_attr: &str, -) -> Result { - Ok(PreKey { - pre_key: extract_key(device_info, key_attr)?, - pre_key_signature: extract_key(device_info, signature_attr)?, +) -> Result { + Ok(Prekey { + prekey: extract_key(device_info, key_attr)?, + prekey_signature: extract_key(device_info, signature_attr)?, }) } pub trait DeviceKeyUploadData { fn device_key_upload(&self) -> Option<&DeviceKeyUpload>; } impl DeviceKeyUploadData for RegistrationStartRequest { fn device_key_upload(&self) -> Option<&DeviceKeyUpload> { self.device_key_upload.as_ref() } } impl DeviceKeyUploadData for ReservedRegistrationStartRequest { fn device_key_upload(&self) -> Option<&DeviceKeyUpload> { self.device_key_upload.as_ref() } } 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 payload(&self) -> Result; fn payload_signature(&self) -> Result; fn content_prekey(&self) -> Result; fn content_prekey_signature(&self) -> Result; fn notif_prekey(&self) -> Result; fn notif_prekey_signature(&self) -> Result; 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 payload(&self) -> Result { self .device_key_upload() .and_then(|upload| upload.device_key_info.as_ref()) .map(|info| info.payload.clone()) .ok_or_else(|| Status::invalid_argument("unexpected message data")) } fn payload_signature(&self) -> Result { self .device_key_upload() .and_then(|upload| upload.device_key_info.as_ref()) .map(|info| info.payload_signature.clone()) .ok_or_else(|| Status::invalid_argument("unexpected message data")) } fn content_prekey(&self) -> Result { self .device_key_upload() .and_then(|upload| upload.content_upload.as_ref()) - .map(|prekey| prekey.pre_key.clone()) + .map(|prekey| prekey.prekey.clone()) .ok_or_else(|| Status::invalid_argument("unexpected message data")) } fn content_prekey_signature(&self) -> Result { self .device_key_upload() .and_then(|upload| upload.content_upload.as_ref()) - .map(|prekey| prekey.pre_key_signature.clone()) + .map(|prekey| prekey.prekey_signature.clone()) .ok_or_else(|| Status::invalid_argument("unexpected message data")) } fn notif_prekey(&self) -> Result { self .device_key_upload() .and_then(|upload| upload.notif_upload.as_ref()) - .map(|prekey| prekey.pre_key.clone()) + .map(|prekey| prekey.prekey.clone()) .ok_or_else(|| Status::invalid_argument("unexpected message data")) } fn notif_prekey_signature(&self) -> Result { self .device_key_upload() .and_then(|upload| upload.notif_upload.as_ref()) - .map(|prekey| prekey.pre_key_signature.clone()) + .map(|prekey| prekey.prekey_signature.clone()) .ok_or_else(|| Status::invalid_argument("unexpected message data")) } fn one_time_content_prekeys(&self) -> Result, Status> { self .device_key_upload() .map(|upload| upload.one_time_content_prekeys.clone()) .ok_or_else(|| Status::invalid_argument("unexpected message data")) } fn one_time_notif_prekeys(&self) -> Result, Status> { self .device_key_upload() .map(|upload| upload.one_time_notif_prekeys.clone()) .ok_or_else(|| Status::invalid_argument("unexpected message data")) } fn device_type(&self) -> Result { self .device_key_upload() .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/tunnelbroker/src/identity/mod.rs b/services/tunnelbroker/src/identity/mod.rs index 3f3220184..cde5d0adc 100644 --- a/services/tunnelbroker/src/identity/mod.rs +++ b/services/tunnelbroker/src/identity/mod.rs @@ -1,37 +1,37 @@ use client_proto::VerifyUserAccessTokenRequest; use grpc_clients::identity; use grpc_clients::tonic::Request; use identity::get_unauthenticated_client; use identity::protos::unauthenticated as client_proto; use crate::config::CONFIG; use crate::error::Error; // Identity service gRPC clients require a code version and device type. // We can supply some placeholder values for services for the time being, since // this metadata is only relevant for devices. const PLACEHOLDER_CODE_VERSION: u64 = 0; const DEVICE_TYPE: &str = "service"; /// Returns true if access token is valid pub async fn verify_user_access_token( user_id: &str, device_id: &str, access_token: &str, ) -> Result { let mut grpc_client = get_unauthenticated_client( &CONFIG.identity_endpoint, PLACEHOLDER_CODE_VERSION, DEVICE_TYPE.to_string(), ) .await?; let message = VerifyUserAccessTokenRequest { user_id: user_id.to_string(), - signing_public_key: device_id.to_string(), + device_id: device_id.to_string(), access_token: access_token.to_string(), }; let request = Request::new(message); let response = grpc_client.verify_user_access_token(request).await?; Ok(response.into_inner().token_valid) } diff --git a/shared/grpc_clients/src/identity/unauthenticated/client.rs b/shared/grpc_clients/src/identity/unauthenticated/client.rs index 9b9f098e7..437cce030 100644 --- a/shared/grpc_clients/src/identity/unauthenticated/client.rs +++ b/shared/grpc_clients/src/identity/unauthenticated/client.rs @@ -1,44 +1,44 @@ /// This file is meant to contain commonly used RPCs use crate::error::Error; use super::get_unauthenticated_client; use crate::identity::protos::unauthenticated::{ Empty, VerifyUserAccessTokenRequest, }; use tonic::Request; /// Returns true if access token is valid pub async fn verify_user_access_token( identity_url: &str, user_id: &str, device_id: &str, access_token: &str, code_version: u64, device_type: String, ) -> Result { let mut grpc_client = get_unauthenticated_client(identity_url, code_version, device_type).await?; let message = VerifyUserAccessTokenRequest { user_id: user_id.to_string(), - signing_public_key: device_id.to_string(), + device_id: device_id.to_string(), access_token: access_token.to_string(), }; let request = Request::new(message); let response = grpc_client.verify_user_access_token(request).await?; Ok(response.into_inner().token_valid) } pub async fn ping( identity_url: &str, code_version: u64, device_type: String, ) -> Result<(), Error> { let mut grpc_client = get_unauthenticated_client(identity_url, code_version, device_type).await?; let request = Request::new(Empty {}); grpc_client.ping(request).await?; Ok(()) } diff --git a/shared/protos/identity_authenticated.proto b/shared/protos/identity_authenticated.proto index 1f12755bd..ea2522381 100644 --- a/shared/protos/identity_authenticated.proto +++ b/shared/protos/identity_authenticated.proto @@ -1,184 +1,184 @@ syntax = "proto3"; import "identity_client.proto"; package identity.authenticated; // RPCs from a client (iOS, Android, or web) to identity service // // This service will assert authenticity of a device by verifying the access // token through an interceptor, thus avoiding the need to explicitly pass // the credentials on every request service IdentityClientService { // X3DH actions // Replenish one-time preKeys rpc UploadOneTimeKeys(UploadOneTimeKeysRequest) returns (identity.client.Empty) {} - // Rotate a device's preKey and preKey signature + // Rotate a device's prekey and prekey signature // Rotated for deniability of older messages - rpc RefreshUserPreKeys(RefreshUserPreKeysRequest) + rpc RefreshUserPrekeys(RefreshUserPrekeysRequest) returns (identity.client.Empty) {} // Called by clients to get all device keys associated with a user in order // to open a new channel of communication on any of their devices. // Specially, this will return the following per device: // - Identity keys (both Content and Notif Keys) - // - PreKey (including preKey signature) - // - One-time PreKey + // - Prekey (including prekey signature) + // - One-time Prekey rpc GetOutboundKeysForUser(OutboundKeysForUserRequest) returns (OutboundKeysForUserResponse) {} // Called by receivers of a communication request. The reponse will only // return identity keys (both content and notif keys) and related prekeys per // device, but will not contain one-time keys. rpc GetInboundKeysForUser(InboundKeysForUserRequest) returns (InboundKeysForUserResponse) {} // Called by user to update password and receive new access token rpc UpdateUserPasswordStart(UpdateUserPasswordStartRequest) returns (UpdateUserPasswordStartResponse) {} rpc UpdateUserPasswordFinish(UpdateUserPasswordFinishRequest) returns (identity.client.Empty) {} // Called by user to log out (clears device's keys and access token) rpc LogOutUser(identity.client.Empty) returns (identity.client.Empty) {} // Called by a user to delete their own account rpc DeleteUser(identity.client.Empty) returns (identity.client.Empty) {} // Called by clients to get required keys for opening a connection // to a user's keyserver rpc GetKeyserverKeys(OutboundKeysForUserRequest) returns (KeyserverKeysResponse) {} // Returns userID for given username or wallet address rpc FindUserID(FindUserIDRequest) returns (FindUserIDResponse) {} // Returns device list history rpc GetDeviceListForUser(GetDeviceListRequest) returns (GetDeviceListResponse) {} } // Helper types // UploadOneTimeKeys // As OPKs get exhausted, they need to be refreshed message UploadOneTimeKeysRequest { - repeated string contentOneTimePreKeys = 1; - repeated string notifOneTimePreKeys = 2; + repeated string content_one_time_prekeys = 1; + repeated string notif_one_time_prekeys = 2; } // RefreshUserPreKeys -message RefreshUserPreKeysRequest { - identity.client.PreKey newContentPreKeys = 1; - identity.client.PreKey newNotifPreKeys = 2; +message RefreshUserPrekeysRequest { + identity.client.Prekey new_content_prekeys = 1; + identity.client.Prekey new_notif_prekeys = 2; } // Information needed when establishing communication to someone else's device message OutboundKeyInfo { - identity.client.IdentityKeyInfo identityInfo = 1; - identity.client.PreKey contentPrekey = 2; - identity.client.PreKey notifPrekey = 3; - optional string oneTimeContentPrekey = 4; - optional string oneTimeNotifPrekey = 5; + identity.client.IdentityKeyInfo identity_info = 1; + identity.client.Prekey content_prekey = 2; + identity.client.Prekey notif_prekey = 3; + optional string one_time_content_prekey = 4; + optional string one_time_notif_prekey = 5; } message KeyserverKeysResponse { - optional OutboundKeyInfo keyserverInfo = 1; + optional OutboundKeyInfo keyserver_info = 1; } // GetOutboundKeysForUser message OutboundKeysForUserResponse { // Map is keyed on devices' public ed25519 key used for signing map devices = 1; } // Information needed by a device to establish communcation when responding // to a request. // The device receiving a request only needs the content key and prekey. message OutboundKeysForUserRequest { - string userID = 1; + string user_id = 1; } // GetInboundKeysForUser message InboundKeyInfo { - identity.client.IdentityKeyInfo identityInfo = 1; - identity.client.PreKey contentPrekey = 2; - identity.client.PreKey notifPrekey = 3; + identity.client.IdentityKeyInfo identity_info = 1; + identity.client.Prekey content_prekey = 2; + identity.client.Prekey notif_prekey = 3; } message InboundKeysForUserResponse { // Map is keyed on devices' public ed25519 key used for signing map devices = 1; } message InboundKeysForUserRequest { - string userID = 1; + string user_id = 1; } // FindUserID message FindUserIDRequest { oneof identifier { string username = 1; - string walletAddress = 2; + string wallet_address = 2; } } message FindUserIDResponse { // userID if the user is registered with Identity Service, null otherwise - optional string userID = 1; + optional string user_id = 1; // true if the identifier (username or wallet address) exists in the // reserved usernames list, false otherwise. It doesn't take into account // whether the user is registered with Identity Service (userID != null). bool is_reserved = 2; } // UpdateUserPassword // Request for updating a user, similar to registration but need a // access token to validate user before updating password message UpdateUserPasswordStartRequest { // Message sent to initiate PAKE registration (step 1) - bytes opaqueRegistrationRequest = 1; + bytes opaque_registration_request = 1; } // Do a user registration, but overwrite the existing credentials // after validation of user message UpdateUserPasswordFinishRequest { // Identifier used to correlate start and finish request - string sessionID = 1; + string session_id = 1; // Opaque client registration upload (step 3) - bytes opaqueRegistrationUpload = 2; + bytes opaque_registration_upload = 2; } message UpdateUserPasswordStartResponse { // Identifier used to correlate start request with finish request - string sessionID = 1; - bytes opaqueRegistrationResponse = 2; + string session_id = 1; + bytes opaque_registration_response = 2; } // GetDeviceListForUser message GetDeviceListRequest { // User whose device lists we want to retrieve string user_id = 1; // UTC timestamp in milliseconds // If none, whole device list history will be retrieved optional int64 since_timestamp = 2; } message GetDeviceListResponse { // A list of stringified JSON objects of the following format: // { // "rawDeviceList": JSON.stringify({ // "devices": [, ...] // "timestamp": , // }) // } repeated string device_list_updates = 1; } diff --git a/shared/protos/identity_client.proto b/shared/protos/identity_client.proto index a0dbafbb1..35e447700 100644 --- a/shared/protos/identity_client.proto +++ b/shared/protos/identity_client.proto @@ -1,246 +1,246 @@ syntax = "proto3"; package identity.client; // RPCs from a client (iOS, Android, or web) to identity service service IdentityClientService { // Account actions // Called by user to register with the Identity Service (PAKE only) // Due to limitations of grpc-web, the Opaque challenge+response // needs to be split up over two unary requests // Start/Finish is used here to align with opaque protocol rpc RegisterPasswordUserStart(RegistrationStartRequest) returns ( RegistrationStartResponse) {} rpc RegisterReservedPasswordUserStart(ReservedRegistrationStartRequest) returns (RegistrationStartResponse) {} rpc RegisterPasswordUserFinish(RegistrationFinishRequest) returns ( RegistrationFinishResponse) {} // Called by user to register device and get an access token - rpc LoginPasswordUserStart(OpaqueLoginStartRequest) returns + rpc LogInPasswordUserStart(OpaqueLoginStartRequest) returns (OpaqueLoginStartResponse) {} - rpc LoginPasswordUserFinish(OpaqueLoginFinishRequest) returns + rpc LogInPasswordUserFinish(OpaqueLoginFinishRequest) returns (OpaqueLoginFinishResponse) {} - rpc LoginWalletUser(WalletLoginRequest) returns (WalletLoginResponse) {} - rpc LoginReservedWalletUser(ReservedWalletLoginRequest) returns + rpc LogInWalletUser(WalletLoginRequest) returns (WalletLoginResponse) {} + rpc LogInReservedWalletUser(ReservedWalletLoginRequest) returns (WalletLoginResponse) {} // Sign-In with Ethereum actions // Called by clients to get a nonce for a Sign-In with Ethereum message rpc GenerateNonce(Empty) returns (GenerateNonceResponse) {} // Service actions // Called by other services to verify a user's access token rpc VerifyUserAccessToken(VerifyUserAccessTokenRequest) returns (VerifyUserAccessTokenResponse) {} // Ashoat's keyserver actions // Called by Ashoat's keyserver to add usernames to the Identity service's // reserved list rpc AddReservedUsernames(AddReservedUsernamesRequest) returns (Empty) {} // Called by Ashoat's keyserver to remove usernames from the Identity // service's reserved list rpc RemoveReservedUsername(RemoveReservedUsernameRequest) returns (Empty) {} // Miscellaneous actions // Called by users periodically to check if their code version is supported rpc Ping(Empty) returns (Empty) {} } // Helper types message Empty {} -message PreKey { - string preKey = 1; - string preKeySignature = 2; +message Prekey { + string prekey = 1; + string prekey_signature = 2; } // Key information needed for starting a X3DH session message IdentityKeyInfo { // JSON payload containing Olm keys // Sessions for users will contain both ContentKeys and NotifKeys // For keyservers, this will only contain ContentKeys string payload = 1; // Payload signed with the signing ed25519 key - string payloadSignature = 2; + string payload_signature = 2; // Signed message used for SIWE // This correlates a given wallet with a device's content key - optional string socialProof = 3; + optional string social_proof = 3; } // RegisterUser // Ephemeral information provided so others can create initial message // to this device // // Prekeys are generally rotated periodically // One-time Prekeys are "consumed" after first use, so many need to // be provide to avoid exhausting them. enum DeviceType { - Keyserver = 0; - Web = 1; + KEYSERVER = 0; + WEB = 1; // iOS doesn't leave a good option for title to camel case renaming - Ios = 2; - Android = 3; - Windows = 4; - MacOS = 5; + IOS = 2; + ANDROID = 3; + WINDOWS = 4; + MAC_OS = 5; } // Bundle of information needed for creating an initial message using X3DH message DeviceKeyUpload { - IdentityKeyInfo deviceKeyInfo = 1; - PreKey contentUpload = 2; - PreKey notifUpload = 3; - repeated string oneTimeContentPrekeys = 4; - repeated string oneTimeNotifPrekeys = 5; - DeviceType deviceType = 6; + IdentityKeyInfo device_key_info = 1; + Prekey content_upload = 2; + Prekey notif_upload = 3; + repeated string one_time_content_prekeys = 4; + repeated string one_time_notif_prekeys = 5; + DeviceType device_type = 6; } // Request for registering a new user message RegistrationStartRequest { // Message sent to initiate PAKE registration (step 1) - bytes opaqueRegistrationRequest = 1; + bytes opaque_registration_request = 1; string username = 2; // Information needed to open a new channel to current user's device - DeviceKeyUpload deviceKeyUpload = 3; + DeviceKeyUpload device_key_upload = 3; } message ReservedRegistrationStartRequest { // Message sent to initiate PAKE registration (step 1) - bytes opaqueRegistrationRequest = 1; + bytes opaque_registration_request = 1; string username = 2; // Information needed to open a new channel to current user's device - DeviceKeyUpload deviceKeyUpload = 3; + DeviceKeyUpload device_key_upload = 3; // Message from Ashoat's keyserver attesting that a given user has ownership // of a given username - string keyserverMessage = 4; + string keyserver_message = 4; // Above message signed with Ashoat's keyserver's signing ed25519 key - string keyserverSignature = 5; + string keyserver_signature = 5; } // Messages sent from a client to Identity Service message RegistrationFinishRequest { // Identifier to correlate RegisterStart session - string sessionID = 1; + string session_id = 1; // Final message in PAKE registration - bytes opaqueRegistrationUpload = 2; + bytes opaque_registration_upload = 2; } // Messages sent from Identity Service to client message RegistrationStartResponse { // Identifier used to correlate start request with finish request - string sessionID = 1; + string session_id = 1; // sent to the user upon reception of the PAKE registration attempt // (step 2) - bytes opaqueRegistrationResponse = 2; + bytes opaque_registration_response = 2; } message RegistrationFinishResponse { // Unique identifier for newly registered user - string userID = 1; + string user_id = 1; // After successful unpacking of user credentials, return token - string accessToken = 2; + string access_token = 2; } // LoginUser message OpaqueLoginStartRequest { string username = 1; // Message sent to initiate PAKE login (step 1) - bytes opaqueLoginRequest = 2; + bytes opaque_login_request = 2; // Information specific to a user's device needed to open a new channel of // communication with this user - DeviceKeyUpload deviceKeyUpload = 3; + DeviceKeyUpload device_key_upload = 3; } message OpaqueLoginFinishRequest { // Identifier used to correlate start request with finish request - string sessionID = 1; + string session_id = 1; // Message containing client's reponse to server challenge. // Used to verify that client holds password secret (Step 3) - bytes opaqueLoginUpload = 2; + bytes opaque_login_upload = 2; } message OpaqueLoginStartResponse { // Identifier used to correlate start request with finish request - string sessionID = 1; + string session_id = 1; // Opaque challenge sent from server to client attempting to login (Step 2) - bytes opaqueLoginResponse = 2; + bytes opaque_login_response = 2; } message OpaqueLoginFinishResponse { - string userID = 1; + string user_id = 1; // Mint and return a new access token upon successful login - string accessToken = 2; + string access_token = 2; } message WalletLoginRequest { - string siweMessage = 1; - string siweSignature = 2; + string siwe_message = 1; + string siwe_signature = 2; // Information specific to a user's device needed to open a new channel of // communication with this user - DeviceKeyUpload deviceKeyUpload = 3; + DeviceKeyUpload device_key_upload = 3; } message ReservedWalletLoginRequest { - string siweMessage = 1; - string siweSignature = 2; + string siwe_message = 1; + string siwe_signature = 2; // Information specific to a user's device needed to open a new channel of // communication with this user - DeviceKeyUpload deviceKeyUpload = 3; + DeviceKeyUpload device_key_upload = 3; // Message from Ashoat's keyserver attesting that a given user has ownership // of a given wallet address - string keyserverMessage = 4; + string keyserver_message = 4; // Above message signed with Ashoat's keyserver's signing ed25519 key - string keyserverSignature = 5; + string keyserver_signature = 5; } message WalletLoginResponse { - string userID = 1; - string accessToken = 2; + string user_id = 1; + string access_token = 2; } // GenerateNonce message GenerateNonceResponse{ string nonce = 1; } // VerifyUserAccessToken message VerifyUserAccessTokenRequest { - string userID = 1; + string user_id = 1; // signing ed25519 key for the given user's device - string signingPublicKey = 2; - string accessToken = 3; + string device_id = 2; + string access_token = 3; } message VerifyUserAccessTokenResponse { - bool tokenValid = 1; + bool token_valid = 1; } // AddReservedUsernames message AddReservedUsernamesRequest { // Message from Ashoat's keyserver containing the username to be added string message = 1; // Above message signed with Ashoat's keyserver's signing ed25519 key string signature = 2; } // RemoveReservedUsername message RemoveReservedUsernameRequest { // Message from Ashoat's keyserver containing the username to be removed string message = 1; // Above message signed with Ashoat's keyserver's signing ed25519 key string signature = 2; } diff --git a/shared/protos/tunnelbroker.proto b/shared/protos/tunnelbroker.proto index fd96153bb..b8b2bd625 100644 --- a/shared/protos/tunnelbroker.proto +++ b/shared/protos/tunnelbroker.proto @@ -1,25 +1,25 @@ syntax = "proto3"; package tunnelbroker; // gRPC service for Comm services (client) to issue requests to // tunnelbroker (server). // // Authentication between services are expected to be validated outside of the // RPC protocol. service TunnelbrokerService { // Sends a stringified JSON payload to device // // Tunnelbroker will enqueue the message, and send it next time the device // connects to tunnelbroker and flushes the queue. rpc SendMessageToDevice(MessageToDevice) returns (Empty) {} } message Empty {} message MessageToDevice { // The primary identity key of a device - string deviceID = 1; + string device_id = 1; // JSON encoded message. See shared/tunnelbroker_messages for valid payloads string payload = 2; }