diff --git a/native/native_rust_library/src/argon2_tools.rs b/native/native_rust_library/src/argon2_tools.rs new file mode 100644 index 000000000..e57cda458 --- /dev/null +++ b/native/native_rust_library/src/argon2_tools.rs @@ -0,0 +1,65 @@ +use argon2::{Algorithm, Argon2, Params, Version}; + +pub const BACKUP_KEY_LENGTH: usize = 32; //256-bit digest + +pub fn compute_backup_key( + password: &str, + backup_id: &str, +) -> Result<[u8; BACKUP_KEY_LENGTH], argon2::Error> { + let mut backup_key = [0u8; BACKUP_KEY_LENGTH]; + let argon_params = Params::new( + Params::DEFAULT_M_COST, + Params::DEFAULT_T_COST, + Params::DEFAULT_P_COST, + Some(BACKUP_KEY_LENGTH), + )?; + + Argon2::new(Algorithm::Argon2i, Version::V0x13, argon_params) + .hash_password_into( + password.as_bytes(), + backup_id.as_bytes(), + &mut backup_key, + )?; + + Ok(backup_key) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_compute_backup_key() { + let password = "password123"; + let backup_id = "backup_123"; + + let result = compute_backup_key(password, backup_id); + assert!(result.is_ok()); + + let key = result.unwrap(); + assert_eq!(key.len(), BACKUP_KEY_LENGTH); + } + + #[test] + fn test_compute_backup_key_error() { + let password = "password123"; + let backup_id = ""; + + let result = compute_backup_key(password, backup_id); + assert!(result.is_err()); + } + + #[test] + fn test_compute_backup_key_accuracy() { + let password = "password123"; + let backup_id = "backup_123"; + + let result1 = compute_backup_key(password, backup_id); + let result2 = compute_backup_key(password, backup_id); + assert!(result1.is_ok()); + assert!(result2.is_ok()); + let key1 = result1.unwrap(); + let key2 = result2.unwrap(); + assert_eq!(key1, key2); + } +} diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs index 8fab5f323..a8c46d6b2 100644 --- a/native/native_rust_library/src/lib.rs +++ b/native/native_rust_library/src/lib.rs @@ -1,458 +1,464 @@ use crate::ffi::string_callback; use crate::identity::Empty; use comm_opaque2::client::{Login, Registration}; use comm_opaque2::grpc::opaque_error_to_grpc_status as handle_error; use lazy_static::lazy_static; use serde::Serialize; use std::sync::Arc; use tokio::runtime::{Builder, Runtime}; use tonic::{transport::Channel, Status}; use tracing::instrument; +mod argon2_tools; mod crypto_tools; mod identity_client; + mod identity { tonic::include_proto!("identity.client"); } +use argon2_tools::compute_backup_key; use crypto_tools::generate_device_id; use identity::identity_client_service_client::IdentityClientServiceClient; use identity::{ DeviceKeyUpload, DeviceType, IdentityKeyInfo, OpaqueLoginFinishRequest, OpaqueLoginStartRequest, PreKey, RegistrationFinishRequest, RegistrationStartRequest, WalletLoginRequest, }; #[cfg(not(feature = "android"))] pub const DEVICE_TYPE: DeviceType = DeviceType::Ios; #[cfg(feature = "android")] pub const DEVICE_TYPE: DeviceType = DeviceType::Android; lazy_static! { pub static ref RUNTIME: Arc = Arc::new( Builder::new_multi_thread() .worker_threads(1) .max_blocking_threads(1) .enable_all() .build() .unwrap() ); } #[cxx::bridge] mod ffi { enum DeviceType { KEYSERVER, WEB, MOBILE, } extern "Rust" { // Identity Service Client type IdentityClient; #[cxx_name = "identityInitializeClient"] fn initialize_identity_client(addr: String) -> Box; #[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 = "identityGenerateNonce"] fn generate_nonce(promise_id: u32); // Crypto Tools fn generate_device_id(device_type: DeviceType) -> Result; + + // Argon2 + fn compute_backup_key(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); } } 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 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 = IdentityClientServiceClient::connect("http://127.0.0.1:50054").await?; let nonce = identity_client .generate_nonce(Empty {}) .await? .into_inner() .nonce; Ok(nonce) } #[derive(Debug)] pub struct IdentityClient { identity_client: IdentityClientServiceClient, } fn initialize_identity_client(addr: String) -> Box { Box::new(IdentityClient { identity_client: RUNTIME .block_on(IdentityClientServiceClient::connect(addr)) .unwrap(), }) } #[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)] struct UserIDAndDeviceAccessToken { 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, }), notif_upload: Some(PreKey { pre_key: password_user_info.notif_prekey, pre_key_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 = IdentityClientServiceClient::connect("http://127.0.0.1:50054").await?; let registration_start_response = identity_client .register_password_user_start(registration_start_request) .await? .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 registration_finish_response = identity_client .register_password_user_finish(registration_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, }), notif_upload: Some(PreKey { pre_key: password_user_info.notif_prekey, pre_key_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 = IdentityClientServiceClient::connect("http://127.0.0.1:50054").await?; let login_start_response = identity_client .login_password_user_start(login_start_request) .await? .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 login_finish_response = identity_client .login_password_user_finish(login_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, }), notif_upload: Some(PreKey { pre_key: wallet_user_info.notif_prekey, pre_key_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 = IdentityClientServiceClient::connect("http://127.0.0.1:50054").await?; let login_response = identity_client .login_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)?) } #[derive( Debug, derive_more::Display, derive_more::From, derive_more::Error, )] pub enum Error { #[display(...)] TonicGRPC(Status), #[display(...)] TonicTransport(tonic::transport::Error), #[display(...)] SerdeJson(serde_json::Error), }