diff --git a/services/identity/src/grpc_services/authenticated.rs b/services/identity/src/grpc_services/authenticated.rs index 6f4979d45..ba159c270 100644 --- a/services/identity/src/grpc_services/authenticated.rs +++ b/services/identity/src/grpc_services/authenticated.rs @@ -1,152 +1,174 @@ use crate::{client_service::handle_db_error, database::DatabaseClient}; use tonic::{Request, Response, Status}; // This must be named client, because generated code from the authenticated // protobuf file references message structs from the client protobuf file // with the client:: namespace pub mod client { tonic::include_proto!("identity.client"); } pub mod auth_proto { tonic::include_proto!("identity.authenticated"); } use auth_proto::{ identity_client_service_server::IdentityClientService, KeyserverKeysResponse, OutboundKeyInfo, OutboundKeysForUserRequest, RefreshUserPreKeysRequest, + UploadOneTimeKeysRequest, }; use client::{Empty, IdentityKeyInfo}; use tracing::debug; #[derive(derive_more::Constructor)] pub struct AuthenticatedService { db_client: DatabaseClient, } fn get_value(req: &Request, key: &str) -> Option { let raw_value = req.metadata().get(key)?; raw_value.to_str().ok().map(|s| s.to_string()) } fn get_auth_info(req: &Request<()>) -> Option<(String, String, String)> { debug!("Retrieving auth info for request: {:?}", req); let user_id = get_value(req, "user_id")?; let device_id = get_value(req, "device_id")?; let access_token = get_value(req, "access_token")?; Some((user_id, device_id, access_token)) } pub fn auth_intercept( req: Request<()>, db_client: &DatabaseClient, ) -> Result, Status> { println!("Intercepting request: {:?}", 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, "user_id") .ok_or_else(|| Status::unauthenticated("Missing user_id field"))?; let device_id = get_value(request, "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( &self, 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 .ok_or_else(|| Status::invalid_argument("Missing content keys"))?; let notif_keys = message .new_notif_pre_keys .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, ) .await .map_err(handle_db_error)?; let response = Response::new(Empty {}); Ok(response) } 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(client::PreKey { pre_key: db_keys.content_prekey.prekey, pre_key_signature: db_keys.content_prekey.prekey_signature, }), notif_prekey: Some(client::PreKey { pre_key: db_keys.notif_prekey.prekey, pre_key_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, + ) + .await + .map_err(handle_db_error)?; + + Ok(tonic::Response::new(Empty {})) + } } diff --git a/shared/protos/identity_authenticated.proto b/shared/protos/identity_authenticated.proto index 6e60d6e96..ef2ec00c2 100644 --- a/shared/protos/identity_authenticated.proto +++ b/shared/protos/identity_authenticated.proto @@ -1,54 +1,66 @@ 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 { + // Replenish one-time preKeys + rpc UploadOneTimeKeys(UploadOneTimeKeysRequest) + returns (identity.client.Empty) {} // Rotate a devices preKey and preKey signature // Rotated for deniability of older messages rpc RefreshUserPreKeys(RefreshUserPreKeysRequest) 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) {} } // Helper types +// UploadOneTimeKeys + +// As OPKs get exhausted, they need to be refreshed +message UploadOneTimeKeysRequest { + repeated string contentOneTimePreKeys = 1; + repeated string notifOneTimePreKeys = 2; +} + + // RefreshUserPreKeys message RefreshUserPreKeysRequest { identity.client.PreKey newContentPreKeys = 1; identity.client.PreKey newNotifPreKeys = 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; } message KeyserverKeysResponse { optional OutboundKeyInfo keyserverInfo = 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; }