diff --git a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs --- a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs @@ -4,6 +4,7 @@ pub mod identity { tonic::include_proto!("identity"); } +pub mod update_user; use comm_opaque::Cipher; use identity::identity_service_client::IdentityServiceClient; diff --git a/keyserver/addons/rust-node-addon/src/identity_client/update_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/update_user.rs new file mode 100644 --- /dev/null +++ b/keyserver/addons/rust-node-addon/src/identity_client/update_user.rs @@ -0,0 +1,312 @@ +use crate::identity_client::identity as proto; +use crate::identity_client::identity::identity_service_client::IdentityServiceClient; +use crate::identity_client::identity::pake_login_response::Data::AccessToken; +use crate::identity_client::identity::{ + update_user_request, update_user_response, UpdateUserRequest, + UpdateUserResponse, +}; +use crate::identity_client::{AUTH_TOKEN, IDENTITY_SERVICE_SOCKET_ADDR}; +use comm_opaque::Cipher; +use napi::bindgen_prelude::*; +use opaque_ke::{ + ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, + ClientLoginStartResult, ClientRegistration, + ClientRegistrationFinishParameters, CredentialFinalization, + CredentialResponse, RegistrationUpload, +}; +use rand::{rngs::OsRng, CryptoRng, Rng}; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; +use tonic; +use tonic::{metadata::MetadataValue, transport::Channel}; +use tracing::{error, instrument}; + +#[napi] +#[instrument(skip_all)] +pub async fn update_user(user_id: String, password: String) -> Result { + let channel = Channel::from_static(&IDENTITY_SERVICE_SOCKET_ADDR) + .connect() + .await + .map_err(|_| Error::from_status(Status::GenericFailure))?; + let token: MetadataValue<_> = AUTH_TOKEN + .parse() + .map_err(|_| Error::from_status(Status::GenericFailure))?; + let mut identity_client = IdentityServiceClient::with_interceptor( + channel, + |mut req: tonic::Request<()>| { + req.metadata_mut().insert("authorization", token.clone()); + Ok(req) + }, + ); + + // Create a RegistrationRequest channel and use ReceiverStream to turn the + // MPSC receiver into a Stream for outbound messages + let (tx, rx) = mpsc::channel(1); + let stream = ReceiverStream::new(rx); + let request = tonic::Request::new(stream); + + // `response` is the Stream for inbound messages + let mut response = identity_client + .update_user(request) + .await + .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))? + .into_inner(); + + // Start PAKE registration on client and send initial registration request + // to Identity service + let mut client_rng = OsRng; + let (registration_request, client_registration) = + pake_registration_start(&mut client_rng, user_id, &password)?; + send_to_mpsc(&tx, registration_request).await?; + + // Handle responses from Identity service sequentially, making sure we get + // messages in the correct order + + // Finish PAKE registration and begin PAKE login; send the final + // registration request and initial login request together to reduce the + // number of trips + let message = response + .message() + .await + .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; + let registration_response = get_registration_response(message)?; + let client_login = handle_registration_response( + ®istration_response, + &mut client_rng, + client_registration, + &password, + &tx, + ) + .await?; + + // Finish PAKE login; send final login request to Identity service + let message = response + .message() + .await + .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; + let credential_response = get_login_credential_response(message)?; + handle_login_credential_response(&credential_response, client_login, &tx) + .await + .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; + + // Return access token + let message = response + .message() + .await + .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; + get_login_token_response(message) +} + +fn handle_unexpected_response(message: Option) -> Error { + error!("Received an unexpected message: {:?}", message); + Error::from_status(Status::GenericFailure) +} + +async fn send_to_mpsc(tx: &mpsc::Sender, request: T) -> Result<()> { + if let Err(e) = tx.send(request).await { + error!("Response was dropped: {}", e); + return Err(Error::from_status(Status::GenericFailure)); + } + Ok(()) +} + +fn pake_login_start( + rng: &mut (impl Rng + CryptoRng), + password: &str, +) -> Result> { + ClientLogin::::start( + rng, + password.as_bytes(), + ClientLoginStartParameters::default(), + ) + .map_err(|e| { + error!("Failed to start PAKE login: {}", e); + Error::from_status(Status::GenericFailure) + }) +} + +fn pake_login_finish( + credential_response_bytes: &[u8], + client_login: ClientLogin, +) -> Result> { + client_login + .finish( + CredentialResponse::deserialize(credential_response_bytes).map_err( + |e| { + error!("Could not deserialize credential response bytes: {}", e); + Error::from_status(Status::GenericFailure) + }, + )?, + ClientLoginFinishParameters::default(), + ) + .map_err(|e| { + error!("Failed to finish PAKE login: {}", e); + Error::from_status(Status::GenericFailure) + }) + .map(|res| res.message) +} + +fn pake_registration_start( + rng: &mut (impl Rng + CryptoRng), + user_id: String, + password: &str, +) -> Result<(UpdateUserRequest, ClientRegistration)> { + let client_registration_start_result = + ClientRegistration::::start(rng, password.as_bytes()).map_err( + |e| { + error!("Failed to start PAKE registration: {}", e); + Error::from_status(Status::GenericFailure) + }, + )?; + let pake_registration_request = + client_registration_start_result.message.serialize(); + Ok(( + UpdateUserRequest { + data: Some(update_user_request::Data::Request( + crate::identity_client::identity::PakeRegistrationRequestAndUserId { + user_id, + pake_registration_request, + username: "placeholder-username".to_string(), + signing_public_key: "placeholder-signing-public-key".to_string(), + session_initialization_info: None, + }, + )), + }, + client_registration_start_result.state, + )) +} + +async fn handle_registration_response( + registration_reponse_payload: &[u8], + client_rng: &mut (impl Rng + CryptoRng), + client_registration: ClientRegistration, + password: &str, + tx: &mpsc::Sender, +) -> Result> { + let pake_registration_upload = pake_registration_finish( + client_rng, + ®istration_reponse_payload, + client_registration, + )? + .serialize(); + let client_login_start_result = pake_login_start(client_rng, password)?; + let pake_login_request = + client_login_start_result.message.serialize().map_err(|e| { + error!("Could not serialize credential request: {}", e); + Error::from_status(Status::GenericFailure) + })?; + + // `registration_request` is a gRPC message containing serialized bytes to + // complete PAKE registration and begin PAKE login + let inner_message: update_user_request::Data = + update_user_request::Data::PakeRegistrationUploadAndCredentialRequest( + crate::identity_client::identity::PakeRegistrationUploadAndCredentialRequest { + pake_registration_upload, + pake_credential_request: pake_login_request, + }, + ); + let registration_request = UpdateUserRequest { + data: Some(inner_message), + }; + send_to_mpsc(tx, registration_request).await?; + Ok(client_login_start_result.state) +} + +fn get_registration_response( + message: Option, +) -> Result> { + match message { + Some(UpdateUserResponse { + data: + Some(update_user_response::Data::PakeRegistrationResponse( + registration_response_bytes, + )), + .. + }) => Ok(registration_response_bytes), + _ => { + error!("Received an unexpected message: {:?}", message); + Err(Error::from_status(Status::GenericFailure)) + } + } +} + +async fn handle_login_credential_response( + registration_response_payload: &[u8], + client_login: ClientLogin, + tx: &mpsc::Sender, +) -> Result<()> { + let pake_login_finish_result = + pake_login_finish(®istration_response_payload, client_login)?; + let login_finish_message = + pake_login_finish_result.serialize().map_err(|e| { + error!("Could not serialize credential request: {}", e); + Error::from_status(Status::GenericFailure) + })?; + let registration_request = UpdateUserRequest { + data: Some( + proto::update_user_request::Data::PakeLoginFinalizationMessage( + login_finish_message, + ), + ), + }; + send_to_mpsc(tx, registration_request).await +} + +fn get_login_credential_response( + message: Option, +) -> Result> { + match message { + Some(UpdateUserResponse { + data: + Some(update_user_response::Data::PakeLoginResponse( + proto::PakeLoginResponse { + data: + Some(proto::pake_login_response::Data::PakeCredentialResponse( + bytes, + )), + }, + )), + }) => Ok(bytes), + _ => Err(handle_unexpected_response(message)), + } +} + +fn get_login_token_response( + message: Option, +) -> Result { + match message { + Some(UpdateUserResponse { + data: + Some(update_user_response::Data::PakeLoginResponse( + proto::PakeLoginResponse { + data: Some(AccessToken(access_token)), + }, + )), + }) => Ok(access_token), + _ => Err(handle_unexpected_response(message)), + } +} + +fn pake_registration_finish( + rng: &mut (impl Rng + CryptoRng), + registration_response_bytes: &[u8], + client_registration: ClientRegistration, +) -> Result> { + let register_payload = + opaque_ke::RegistrationResponse::deserialize(registration_response_bytes) + .map_err(|e| { + error!("Could not deserialize registration response bytes: {}", e); + Error::from_status(Status::GenericFailure) + })?; + client_registration + .finish( + rng, + register_payload, + ClientRegistrationFinishParameters::default(), + ) + .map_err(|e| { + error!("Failed to finish PAKE registration: {}", e); + Error::from_status(Status::GenericFailure) + }) + .map(|res| res.message) +} diff --git a/keyserver/src/updaters/account-updaters.js b/keyserver/src/updaters/account-updaters.js --- a/keyserver/src/updaters/account-updaters.js +++ b/keyserver/src/updaters/account-updaters.js @@ -1,5 +1,6 @@ // @flow +import { getRustAPI } from 'rust-node-addon'; import bcrypt from 'twin-bcrypt'; import type { @@ -14,6 +15,7 @@ import { createUpdates } from '../creators/update-creator.js'; import { dbQuery, SQL } from '../database/database.js'; +import { handleAsyncPromise } from '../responders/handlers.js'; import type { Viewer } from '../session/viewer.js'; async function accountUpdater( @@ -48,6 +50,12 @@ UPDATE users SET ${changedFields} WHERE id = ${viewer.userID} `; await dbQuery(saveQuery); + handleAsyncPromise( + (async () => { + const rustApi = await getRustAPI(); + await rustApi.updateUser(viewer.userID, newPassword); + })(), + ); const updateDatas = [ { diff --git a/lib/types/rust-binding-types.js b/lib/types/rust-binding-types.js --- a/lib/types/rust-binding-types.js +++ b/lib/types/rust-binding-types.js @@ -38,6 +38,7 @@ socialProof: string, ) => Promise, +deleteUser: (userId: string) => Promise, + +updateUser: (userId: string, password: string) => Promise, +TunnelbrokerClient: Class, };