diff --git a/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs new file mode 100644 --- /dev/null +++ b/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs @@ -0,0 +1,137 @@ +use super::*; + +#[napi] +#[instrument(skip_all)] +async fn login_user_pake( + user_id: String, + signing_public_key: String, + password: String, + session_initialization_info: HashMap, +) -> Result { + let channel = Channel::from_static(&IDENTITY_SERVICE_SOCKET_ADDR) + .connect() + .await + .map_err(|_| { + Error::new( + Status::GenericFailure, + "Unable to connect to identity service".to_string(), + ) + })?; + let token: MetadataValue<_> = AUTH_TOKEN + .parse() + .map_err(|_| Error::from_status(Status::GenericFailure))?; + let mut identity_client = + IdentityServiceClient::with_interceptor(channel, |mut req: Request<()>| { + req.metadata_mut().insert("authorization", token.clone()); + Ok(req) + }); + + // Create a LoginRequest 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 = Request::new(stream); + + // `response` is the Stream for inbound messages + let mut response = identity_client + .login_user(request) + .await + .map_err(|_| Error::from_status(Status::GenericFailure))? + .into_inner(); + + // Start PAKE login on client and send initial login request to Identity + // service + let mut client_rng = OsRng; + let client_login_start_result = pake_login_start(&mut client_rng, &password)?; + let pake_credential_request = + client_login_start_result.message.serialize().map_err(|e| { + error!("Could not serialize credential request: {}", e); + Error::new(Status::GenericFailure, e.to_string()) + })?; + let login_request = LoginRequest { + data: Some(PakeLoginRequest(PakeLoginRequestStruct { + data: Some(PakeCredentialRequestAndUserId( + PakeCredentialRequestAndUserIdStruct { + user_id, + signing_public_key, + pake_credential_request, + session_initialization_info: Some(SessionInitializationInfo { + info: session_initialization_info, + }), + }, + )), + })), + }; + + send_to_mpsc(tx.clone(), login_request).await?; + + // Handle responses from Identity service sequentially, making sure we get + // messages in the correct order + + // Finish PAKE login; send final login request to Identity service + let message = response.message().await.map_err(|e| { + error!("Received an error from inbound message stream: {}", e); + Error::from_status(Status::GenericFailure) + })?; + handle_login_credential_response( + message, + client_login_start_result.state, + tx, + ) + .await?; + + // Return access token + let message = response.message().await.map_err(|e| { + error!("Received an error from inbound message stream: {}", e); + Error::from_status(Status::GenericFailure) + })?; + handle_login_token_response(message) +} + +async fn handle_login_credential_response( + message: Option, + client_login: ClientLogin, + tx: mpsc::Sender, +) -> Result<(), Status> { + if let Some(LoginResponse { + data: + Some(LoginPakeLoginResponse(PakeLoginResponseStruct { + data: Some(PakeCredentialResponse(credential_response_bytes)), + })), + }) = message + { + let credential_finalization_bytes = + pake_login_finish(&credential_response_bytes, client_login)? + .serialize() + .map_err(|e| { + error!("Could not serialize credential request: {}", e); + Error::from_status(Status::GenericFailure) + })?; + let login_request = LoginRequest { + data: Some(PakeLoginRequest(PakeLoginRequestStruct { + data: Some(LoginPakeCredentialFinalization( + credential_finalization_bytes, + )), + })), + }; + send_to_mpsc(tx, login_request).await + } else { + Err(handle_unexpected_response(message)) + } +} + +fn handle_login_token_response( + message: Option, +) -> Result { + if let Some(LoginResponse { + data: + Some(LoginPakeLoginResponse(PakeLoginResponseStruct { + data: Some(AccessToken(access_token)), + })), + }) = message + { + Ok(access_token) + } else { + Err(handle_unexpected_response(message)) + } +} 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 @@ -1,4 +1,5 @@ pub mod delete_user; +pub mod login_user; pub mod register_user; pub mod identity { tonic::include_proto!("identity"); @@ -7,6 +8,10 @@ use comm_opaque::Cipher; use identity::identity_service_client::IdentityServiceClient; use identity::{ + login_request::Data::PakeLoginRequest, + login_response::Data::PakeLoginResponse as LoginPakeLoginResponse, + pake_login_request::Data::PakeCredentialFinalization as LoginPakeCredentialFinalization, + pake_login_request::Data::PakeCredentialRequestAndUserId, pake_login_response::Data::AccessToken, pake_login_response::Data::PakeCredentialResponse, registration_request::Data::PakeCredentialFinalization as RegistrationPakeCredentialFinalization, @@ -14,6 +19,9 @@ registration_request::Data::PakeRegistrationUploadAndCredentialRequest, registration_response::Data::PakeLoginResponse as RegistrationPakeLoginResponse, registration_response::Data::PakeRegistrationResponse, DeleteUserRequest, + LoginRequest, LoginResponse, + PakeCredentialRequestAndUserId as PakeCredentialRequestAndUserIdStruct, + PakeLoginRequest as PakeLoginRequestStruct, PakeLoginResponse as PakeLoginResponseStruct, PakeRegistrationRequestAndUserId as PakeRegistrationRequestAndUserIdStruct, PakeRegistrationUploadAndCredentialRequest as PakeRegistrationUploadAndCredentialRequestStruct, diff --git a/keyserver/src/responders/user-responders.js b/keyserver/src/responders/user-responders.js --- a/keyserver/src/responders/user-responders.js +++ b/keyserver/src/responders/user-responders.js @@ -1,6 +1,7 @@ // @flow import invariant from 'invariant'; +import { getRustAPI } from 'rust-node-addon'; import { ErrorTypes, SiweMessage } from 'siwe'; import t from 'tcomb'; import bcrypt from 'twin-bcrypt'; @@ -64,6 +65,7 @@ normalizeCalendarQuery, verifyCalendarQueryThreadIDs, } from './entry-responders.js'; +import { handleAsyncPromise } from './handlers.js'; import { createAccount, processSIWEAccountCreation, @@ -362,11 +364,10 @@ await validateInput(viewer, logInRequestInputValidator, input); const request: LogInRequest = input; + let identityKeys: ?IdentityKeysBlob; const { signedIdentityKeysBlob } = request; if (signedIdentityKeysBlob) { - const identityKeys: IdentityKeysBlob = JSON.parse( - signedIdentityKeysBlob.payload, - ); + identityKeys = JSON.parse(signedIdentityKeysBlob.payload); const olmUtil: OLMUtility = getOLMUtility(); try { @@ -402,8 +403,10 @@ WHERE LCASE(username) = LCASE(${username}) `; promises.userQuery = dbQuery(userQuery); + promises.rustAPI = getRustAPI(); const { userQuery: [userResult], + rustAPI, } = await promiseAll(promises); if (userResult.length === 0) { @@ -421,6 +424,18 @@ } const id = userRow.id.toString(); + + if (identityKeys && signedIdentityKeysBlob) { + handleAsyncPromise( + rustAPI.loginUserPake( + id, + identityKeys.primaryIdentityPublicKeys.ed25519, + request.password, + signedIdentityKeysBlob, + ), + ); + } + return await processSuccessfulLogin({ viewer, input, 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 @@ -1,5 +1,7 @@ // @flow +import type { SignedIdentityKeysBlob } from './crypto-types.js'; + type tunnelbrokerOnReceiveCallback = ( err: Error | null, payload: string, @@ -21,6 +23,12 @@ password: string, userPublicKey: string, ) => Promise, + +loginUserPake: ( + userId: string, + signingPublicKey: string, + password: string, + sessionInitializationInfo: SignedIdentityKeysBlob, + ) => Promise, +deleteUser: (userId: string) => Promise, +TunnelbrokerClient: Class, };