diff --git a/services/identity/Cargo.lock b/services/identity/Cargo.lock --- a/services/identity/Cargo.lock +++ b/services/identity/Cargo.lock @@ -74,7 +74,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.28", ] [[package]] @@ -852,7 +852,7 @@ "proc-macro2", "quote", "scratch", - "syn 2.0.13", + "syn 2.0.28", ] [[package]] @@ -869,7 +869,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.28", ] [[package]] @@ -1147,7 +1147,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.28", ] [[package]] @@ -1468,6 +1468,7 @@ "tonic-web", "tracing", "tracing-subscriber", + "tunnelbroker_messages", "uuid", ] @@ -1949,9 +1950,9 @@ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -2039,9 +2040,9 @@ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -2355,29 +2356,29 @@ [[package]] name = "serde" -version = "1.0.159" +version = "1.0.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "bdb30a74471f5b7a1fa299f40b4bf1be93af61116df95465b2b5fc419331e430" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "6f4c2c6ea4bc09b5c419012eafcdb0fcef1d9119d626c8f3a0708a5b92d38a70" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.28", ] [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -2526,9 +2527,9 @@ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -2592,7 +2593,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.28", ] [[package]] @@ -2677,7 +2678,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.28", ] [[package]] @@ -2902,6 +2903,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "tunnelbroker_messages" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "typenum" version = "1.16.0" @@ -3326,5 +3335,5 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.28", ] diff --git a/services/identity/Cargo.toml b/services/identity/Cargo.toml --- a/services/identity/Cargo.toml +++ b/services/identity/Cargo.toml @@ -25,6 +25,7 @@ tonic-web = "0.9.1" serde = { version = "1.0.159", features = [ "derive" ] } serde_json = "1.0.95" +tunnelbroker_messages = { path = "../../shared/tunnelbroker_messages" } moka = { version = "0.10", features = ["future"] } uuid = { version = "1.3", features = [ "v4" ] } base64 = "0.21.2" diff --git a/services/identity/build.rs b/services/identity/build.rs --- a/services/identity/build.rs +++ b/services/identity/build.rs @@ -9,5 +9,12 @@ ], &["../../shared/protos/"], )?; + tonic_build::configure() + .build_server(false) + .build_client(true) + .compile( + &["../../shared/protos/tunnelbroker.proto"], + &["../../shared/protos/"], + )?; Ok(()) } diff --git a/services/identity/src/constants.rs b/services/identity/src/constants.rs --- a/services/identity/src/constants.rs +++ b/services/identity/src/constants.rs @@ -130,3 +130,10 @@ // Tunnelbroker pub const TUNNELBROKER_GRPC_ENDPOINT: &str = "TUNNELBROKER_GRPC_ENDPOINT"; pub const DEFAULT_TUNNELBROKER_ENDPOINT: &str = "http://localhost:50051"; + +// X3DH key management + +// Threshold for requesting more onetime keys +pub const ONETIME_KEY_MINIMUM_THRESHOLD: usize = 5; +// Number of keys to be refreshed when below the threshold +pub const ONETIME_KEY_REFRESH_NUMBER: u32 = 5; diff --git a/services/identity/src/database.rs b/services/identity/src/database.rs --- a/services/identity/src/database.rs +++ b/services/identity/src/database.rs @@ -7,7 +7,7 @@ use crate::ddb_utils::{ create_one_time_key_partition_key, into_one_time_put_requests, OlmAccountType, }; -use crate::error::{DBItemAttributeError, DBItemError, Error}; +use crate::error::{consume_error, DBItemAttributeError, DBItemError, Error}; use aws_config::SdkConfig; use aws_sdk_dynamodb::model::{AttributeValue, PutRequest, WriteRequest}; use aws_sdk_dynamodb::output::{ @@ -379,22 +379,32 @@ account_type: OlmAccountType, ) -> Result, Error> { use crate::constants::one_time_keys_table as otk_table; + use crate::constants::ONETIME_KEY_MINIMUM_THRESHOLD; let query_result = self.get_onetime_keys(device_id, account_type).await?; let items = query_result.items(); // If no onetime keys exists, return none early - if items.is_none() { + let Some(item_vec) = items else { debug!("Unable to find {:?} onetime-key", account_type); return Ok(None); + }; + + if item_vec.len() < ONETIME_KEY_MINIMUM_THRESHOLD { + // Avoid device_id being moved out-of-scope by "move" + let device_id = device_id.to_string(); + tokio::spawn(async move { + debug!("Attempting to request more keys for device: {}", &device_id); + let result = + crate::tunnelbroker::send_refresh_keys_request(&device_id).await; + consume_error(result); + }); } let mut result = None; - - // "items" was checked to be None above, will be safe to unwrap here. // Attempt to delete the onetime keys individually, a successful delete // mints the onetime key to the requester - for item in items.unwrap() { + for item in item_vec { let pk = item.get_string(otk_table::PARTITION_KEY)?; let otk = item.get_string(otk_table::SORT_KEY)?; diff --git a/services/identity/src/error.rs b/services/identity/src/error.rs --- a/services/identity/src/error.rs +++ b/services/identity/src/error.rs @@ -1,6 +1,7 @@ use aws_sdk_dynamodb::{model::AttributeValue, Error as DynamoDBError}; use std::collections::hash_map::HashMap; use std::fmt::{Display, Formatter, Result as FmtResult}; +use tracing::error; #[derive( Debug, derive_more::Display, derive_more::From, derive_more::Error, @@ -11,6 +12,10 @@ #[display(...)] Attribute(DBItemError), #[display(...)] + Transport(tonic::transport::Error), + #[display(...)] + Status(tonic::Status), + #[display(...)] MissingItem, } @@ -141,3 +146,12 @@ .to_vec(key) } } + +pub fn consume_error(result: Result) { + match result { + Ok(_) => return, + Err(e) => { + error!("{}", e); + } + } +} diff --git a/services/identity/src/main.rs b/services/identity/src/main.rs --- a/services/identity/src/main.rs +++ b/services/identity/src/main.rs @@ -18,6 +18,7 @@ mod reserved_users; mod siwe; mod token; +mod tunnelbroker; use config::load_config; use constants::{IDENTITY_SERVICE_SOCKET_ADDR, SECRETS_DIRECTORY}; diff --git a/services/identity/src/tunnelbroker.rs b/services/identity/src/tunnelbroker.rs new file mode 100644 --- /dev/null +++ b/services/identity/src/tunnelbroker.rs @@ -0,0 +1,48 @@ +mod proto { + tonic::include_proto!("tunnelbroker"); +} +use crate::config::CONFIG; +use proto::tunnelbroker_service_client::TunnelbrokerServiceClient; +use proto::{Empty, MessageToDevice}; +use tonic::transport::Channel; +use tonic::Response; +use tracing::error; +use tunnelbroker_messages as messages; + +use crate::error::Error; + +pub async fn create_tunnelbroker_client( +) -> Result, Error> { + TunnelbrokerServiceClient::connect(CONFIG.tunnelbroker_endpoint.to_string()) + .await + .map_err(|e| { + error!("Unable able to connect to tunnelbroker: {:?}", e); + e.into() + }) +} + +pub async fn send_refresh_keys_request( + device_id: &str, +) -> Result, Error> { + use crate::constants::ONETIME_KEY_REFRESH_NUMBER; + + let mut tunnelbroker_client = create_tunnelbroker_client().await?; + + let refresh_request = messages::RefreshKeyRequest { + device_id: device_id.to_string(), + number_of_keys: ONETIME_KEY_REFRESH_NUMBER, + }; + + let payload = serde_json::to_string(&refresh_request).unwrap(); + let request = MessageToDevice { + device_id: device_id.to_string(), + payload, + }; + + let grpc_message = tonic::Request::new(request); + Ok( + tunnelbroker_client + .send_message_to_device(grpc_message) + .await?, + ) +}