From 0d7674eb3ac9a80222c472fcadc562d04c9623be Mon Sep 17 00:00:00 2001 From: Dakota Brink <779390+codabrink@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:29:21 -0500 Subject: [PATCH] Provide HMAC Keys (#1394) * hkdf wip * calculate the kdf * hash the message, provide the hmac * missing files * lint * provide on the ffi * needless bit flipping * test fix * lint * lint * bump version * test it * move down * typo * update hmac algo * lint * move the salt const --- Cargo.lock | 3 + Cargo.toml | 1 + bindings_ffi/src/mls.rs | 34 ++++++ xmtp_mls/Cargo.toml | 3 + .../down.sql | 2 + .../up.sql | 2 + xmtp_mls/src/api/mls.rs | 27 ++--- xmtp_mls/src/client.rs | 6 +- xmtp_mls/src/configuration.rs | 1 + xmtp_mls/src/groups/device_sync.rs | 3 +- .../src/groups/device_sync/consent_sync.rs | 2 +- .../device_sync/preference_sync.rs} | 0 xmtp_mls/src/groups/mls_sync.rs | 103 ++++++++++++++++-- xmtp_mls/src/groups/mod.rs | 13 ++- xmtp_mls/src/lib.rs | 1 - .../src/storage/encrypted_store/schema.rs | 2 +- .../encrypted_store/user_preferences.rs | 45 +++++--- xmtp_mls/src/subscriptions.rs | 6 +- xmtp_mls/src/utils/mod.rs | 22 +++- xmtp_v2/Cargo.toml | 6 +- 20 files changed, 226 insertions(+), 56 deletions(-) create mode 100644 xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/down.sql create mode 100644 xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/up.sql rename xmtp_mls/src/{preferences.rs => groups/device_sync/preference_sync.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index 513ee426e..17a96844d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7384,6 +7384,8 @@ dependencies = [ "getrandom", "gloo-timers 0.3.0", "hex", + "hkdf", + "hmac 0.12.1", "indicatif", "libsqlite3-sys", "mockall", @@ -7401,6 +7403,7 @@ dependencies = [ "reqwest 0.12.9", "serde", "serde_json", + "sha2 0.10.8", "tempfile", "thiserror 2.0.6", "tls_codec", diff --git a/Cargo.toml b/Cargo.toml index 2408f730c..0226f5a35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ futures = "0.3.30" futures-core = "0.3.30" getrandom = { version = "0.2", default-features = false } hex = "0.4.3" +hkdf = "0.12.3" openmls = { git = "https://github.com/xmtp/openmls", rev = "043b347cb18d528647df36f500725ab57c41c7db", default-features = false } openmls_basic_credential = { git = "https://github.com/xmtp/openmls", rev = "043b347cb18d528647df36f500725ab57c41c7db" } openmls_rust_crypto = { git = "https://github.com/xmtp/openmls", rev = "043b347cb18d528647df36f500725ab57c41c7db" } diff --git a/bindings_ffi/src/mls.rs b/bindings_ffi/src/mls.rs index f5014e982..6a8955bec 100644 --- a/bindings_ffi/src/mls.rs +++ b/bindings_ffi/src/mls.rs @@ -16,6 +16,7 @@ use xmtp_id::{ InboxId, }; use xmtp_mls::groups::scoped_client::LocalScopedGroupClient; +use xmtp_mls::groups::HmacKey; use xmtp_mls::storage::group::ConversationType; use xmtp_mls::storage::group_message::MsgQueryArgs; use xmtp_mls::storage::group_message::SortDirection; @@ -527,6 +528,33 @@ impl FfiXmtpClient { scw_verifier: self.inner_client.scw_verifier().clone().clone(), })) } + + pub fn get_hmac_keys(&self) -> Result, GenericError> { + let inner = self.inner_client.as_ref(); + let conversations = inner.find_groups(GroupQueryArgs::default())?; + + let mut keys = vec![]; + for conversation in conversations { + let mut k = conversation + .hmac_keys(-1..=1)? + .into_iter() + .map(Into::into) + .collect::>(); + + keys.append(&mut k); + } + + Ok(keys) + } +} + +impl From for FfiHmacKey { + fn from(value: HmacKey) -> Self { + Self { + epoch: value.epoch, + key: value.key.to_vec(), + } + } } #[derive(uniffi::Record)] @@ -537,6 +565,12 @@ pub struct FfiInboxState { pub account_addresses: Vec, } +#[derive(uniffi::Record)] +pub struct FfiHmacKey { + key: Vec, + epoch: i64, +} + #[derive(uniffi::Record)] pub struct FfiInstallation { pub id: Vec, diff --git a/xmtp_mls/Cargo.toml b/xmtp_mls/Cargo.toml index 1d10929c6..c1f8b4c72 100644 --- a/xmtp_mls/Cargo.toml +++ b/xmtp_mls/Cargo.toml @@ -51,6 +51,7 @@ bincode.workspace = true diesel_migrations.workspace = true futures.workspace = true hex.workspace = true +hkdf.workspace = true openmls_rust_crypto = { workspace = true } openmls_traits = { workspace = true } parking_lot.workspace = true @@ -59,6 +60,7 @@ rand = { workspace = true } reqwest = { version = "0.12.4", features = ["stream"] } serde = { workspace = true } serde_json.workspace = true +sha2.workspace = true thiserror = { workspace = true } tls_codec = { workspace = true } tokio-stream = { version = "0.1", default-features = false, features = [ @@ -90,6 +92,7 @@ criterion = { version = "0.5", features = [ "html_reports", "async_tokio", ], optional = true } +hmac = "0.12.1" indicatif = { version = "0.17", optional = true } mockall = { version = "0.13.1", optional = true } once_cell = { version = "1.19", optional = true } diff --git a/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/down.sql b/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/down.sql new file mode 100644 index 000000000..409db041b --- /dev/null +++ b/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE user_preferences DROP COLUMN hmac_key; +ALTER TABLE user_preferences ADD COLUMN hmac_key BLOB; diff --git a/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/up.sql b/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/up.sql new file mode 100644 index 000000000..584d0f375 --- /dev/null +++ b/xmtp_mls/migrations/2024-12-06-212729_make_hmac_required/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE user_preferences DROP COLUMN hmac_key; +ALTER TABLE user_preferences ADD COLUMN hmac_key BLOB NOT NULL; diff --git a/xmtp_mls/src/api/mls.rs b/xmtp_mls/src/api/mls.rs index 33a42a577..a319f7737 100644 --- a/xmtp_mls/src/api/mls.rs +++ b/xmtp_mls/src/api/mls.rs @@ -4,13 +4,12 @@ use super::ApiClientWrapper; use crate::{retry_async, XmtpApi}; use xmtp_proto::api_client::XmtpMlsStreams; use xmtp_proto::xmtp::mls::api::v1::{ - group_message_input::{Version as GroupMessageInputVersion, V1 as GroupMessageInputV1}, subscribe_group_messages_request::Filter as GroupFilterProto, - subscribe_welcome_messages_request::Filter as WelcomeFilterProto, - FetchKeyPackagesRequest, GroupMessage, GroupMessageInput, KeyPackageUpload, PagingInfo, - QueryGroupMessagesRequest, QueryWelcomeMessagesRequest, SendGroupMessagesRequest, - SendWelcomeMessagesRequest, SortDirection, SubscribeGroupMessagesRequest, - SubscribeWelcomeMessagesRequest, UploadKeyPackageRequest, WelcomeMessage, WelcomeMessageInput, + subscribe_welcome_messages_request::Filter as WelcomeFilterProto, FetchKeyPackagesRequest, + GroupMessage, GroupMessageInput, KeyPackageUpload, PagingInfo, QueryGroupMessagesRequest, + QueryWelcomeMessagesRequest, SendGroupMessagesRequest, SendWelcomeMessagesRequest, + SortDirection, SubscribeGroupMessagesRequest, SubscribeWelcomeMessagesRequest, + UploadKeyPackageRequest, WelcomeMessage, WelcomeMessageInput, }; use xmtp_proto::{Error as ApiError, ErrorKind}; @@ -250,28 +249,22 @@ where } #[tracing::instrument(level = "trace", skip_all)] - pub async fn send_group_messages(&self, group_messages: Vec<&[u8]>) -> Result<(), ApiError> { + pub async fn send_group_messages( + &self, + group_messages: Vec, + ) -> Result<(), ApiError> { tracing::debug!( inbox_id = self.inbox_id, "sending [{}] group messages", group_messages.len() ); - let to_send: Vec = group_messages - .iter() - .map(|msg| GroupMessageInput { - version: Some(GroupMessageInputVersion::V1(GroupMessageInputV1 { - data: msg.to_vec(), - sender_hmac: vec![], - })), - }) - .collect(); retry_async!( self.retry_strategy, (async { self.api_client .send_group_messages(SendGroupMessagesRequest { - messages: to_send.clone(), + messages: group_messages.clone(), }) .await }) diff --git a/xmtp_mls/src/client.rs b/xmtp_mls/src/client.rs index 81751d5a8..819356dc4 100644 --- a/xmtp_mls/src/client.rs +++ b/xmtp_mls/src/client.rs @@ -32,12 +32,14 @@ use xmtp_proto::xmtp::mls::api::v1::{ use crate::{ api::ApiClientWrapper, - groups::{group_permissions::PolicySet, GroupError, GroupMetadataOptions, MlsGroup}, + groups::{ + device_sync::preference_sync::UserPreferenceUpdate, group_permissions::PolicySet, + GroupError, GroupMetadataOptions, MlsGroup, + }, identity::{parse_credential, Identity, IdentityError}, identity_updates::{load_identity_updates, IdentityUpdateError}, intents::ProcessIntentError, mutex_registry::MutexRegistry, - preferences::UserPreferenceUpdate, retry::Retry, retry_async, retryable, storage::{ diff --git a/xmtp_mls/src/configuration.rs b/xmtp_mls/src/configuration.rs index 9b36d7045..d8fa3ea65 100644 --- a/xmtp_mls/src/configuration.rs +++ b/xmtp_mls/src/configuration.rs @@ -57,3 +57,4 @@ pub const DEFAULT_GROUP_PINNED_FRAME_URL: &str = ""; // If a metadata field name starts with this character, // and it does not have a policy set, it is a super admin only field pub const SUPER_ADMIN_METADATA_PREFIX: &str = "_"; +pub(crate) const HMAC_SALT: &[u8] = b"libXMTP HKDF salt!"; diff --git a/xmtp_mls/src/groups/device_sync.rs b/xmtp_mls/src/groups/device_sync.rs index c0af9ac84..3f9536371 100644 --- a/xmtp_mls/src/groups/device_sync.rs +++ b/xmtp_mls/src/groups/device_sync.rs @@ -1,6 +1,5 @@ use super::{GroupError, MlsGroup}; use crate::configuration::NS_IN_HOUR; -use crate::preferences::UserPreferenceUpdate; use crate::retry::{Retry, RetryableError}; use crate::storage::group::{ConversationType, GroupQueryArgs}; use crate::storage::group_message::MsgQueryArgs; @@ -25,6 +24,7 @@ use aes_gcm::{ Aes256Gcm, }; use futures::{Stream, StreamExt}; +use preference_sync::UserPreferenceUpdate; use rand::{ distributions::{Alphanumeric, DistString}, Rng, RngCore, @@ -50,6 +50,7 @@ use xmtp_proto::xmtp::mls::message_contents::{ pub mod consent_sync; pub mod message_sync; +pub mod preference_sync; pub const ENC_KEY_SIZE: usize = 32; // 256-bit key pub const NONCE_SIZE: usize = 12; // 96-bit nonce diff --git a/xmtp_mls/src/groups/device_sync/consent_sync.rs b/xmtp_mls/src/groups/device_sync/consent_sync.rs index 4003322b8..d327923fe 100644 --- a/xmtp_mls/src/groups/device_sync/consent_sync.rs +++ b/xmtp_mls/src/groups/device_sync/consent_sync.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{preferences::UserPreferenceUpdate, Client, XmtpApi}; +use crate::{Client, XmtpApi}; use xmtp_id::scw_verifier::SmartContractSignatureVerifier; use xmtp_proto::xmtp::mls::message_contents::UserPreferenceUpdate as UserPreferenceUpdateProto; diff --git a/xmtp_mls/src/preferences.rs b/xmtp_mls/src/groups/device_sync/preference_sync.rs similarity index 100% rename from xmtp_mls/src/preferences.rs rename to xmtp_mls/src/groups/device_sync/preference_sync.rs diff --git a/xmtp_mls/src/groups/mls_sync.rs b/xmtp_mls/src/groups/mls_sync.rs index 91426988d..d1131d40c 100644 --- a/xmtp_mls/src/groups/mls_sync.rs +++ b/xmtp_mls/src/groups/mls_sync.rs @@ -6,12 +6,12 @@ use super::{ UpdateAdminListIntentData, UpdateGroupMembershipIntentData, UpdatePermissionIntentData, }, validated_commit::{extract_group_membership, CommitValidationError}, - GroupError, IntentError, MlsGroup, ScopedGroupClient, + GroupError, HmacKey, IntentError, MlsGroup, ScopedGroupClient, }; use crate::{ codecs::{group_updated::GroupUpdatedCodec, ContentCodec}, configuration::{ - GRPC_DATA_LIMIT, MAX_GROUP_SIZE, MAX_INTENT_PUBLISH_ATTEMPTS, MAX_PAST_EPOCHS, + GRPC_DATA_LIMIT, HMAC_SALT, MAX_GROUP_SIZE, MAX_INTENT_PUBLISH_ATTEMPTS, MAX_PAST_EPOCHS, SYNC_UPDATE_INSTALLATIONS_INTERVAL_NS, }, groups::{intents::UpdateMetadataIntentData, validated_commit::ValidatedCommit}, @@ -28,14 +28,18 @@ use crate::{ refresh_state::EntityKind, serialization::{db_deserialize, db_serialize}, sql_key_store, + user_preferences::StoredUserPreferences, + StorageError, }, subscriptions::LocalEvents, - utils::{hash::sha256, id::calculate_message_id}, + utils::{hash::sha256, id::calculate_message_id, time::hmac_epoch}, xmtp_openmls_provider::XmtpOpenMlsProvider, Delete, Fetch, StoreOrIgnore, }; use crate::{groups::device_sync::DeviceSyncContent, subscriptions::SyncMessage}; use futures::future::try_join_all; +use hkdf::Hkdf; +use hmac::{Hmac, Mac}; use openmls::{ credentials::BasicCredential, extensions::Extensions, @@ -53,19 +57,22 @@ use openmls::{framing::WireFormat, prelude::BasicCredentialError}; use openmls_traits::{signatures::Signer, OpenMlsProvider}; use prost::bytes::Bytes; use prost::Message; +use sha2::Sha256; use std::{ collections::{HashMap, HashSet}, mem::{discriminant, Discriminant}, + ops::RangeInclusive, }; use thiserror::Error; use xmtp_id::{InboxId, InboxIdRef}; use xmtp_proto::xmtp::mls::{ api::v1::{ group_message::{Version as GroupMessageVersion, V1 as GroupMessageV1}, + group_message_input::{Version as GroupMessageInputVersion, V1 as GroupMessageInputV1}, welcome_message_input::{ Version as WelcomeMessageInputVersion, V1 as WelcomeMessageInputV1, }, - GroupMessage, WelcomeMessageInput, + GroupMessage, GroupMessageInput, WelcomeMessageInput, }, message_contents::{ plaintext_envelope::{v2::MessageType, Content, V1, V2}, @@ -1021,10 +1028,8 @@ where intent.id ); - self.client - .api() - .send_group_messages(vec![payload_slice]) - .await?; + let messages = self.prepare_group_messages(vec![payload_slice])?; + self.client.api().send_group_messages(messages).await?; tracing::info!( intent.id, @@ -1387,6 +1392,62 @@ where try_join_all(futures).await?; Ok(()) } + + /// Provides hmac keys for a range of epochs around current epoch + /// `group.hmac_keys(-1..=1)`` will provide 3 keys consisting of last epoch, current epoch, and next epoch + /// `group.hmac_keys(0..=0) will provide 1 key, consisting of only the current epoch + #[tracing::instrument(level = "trace", skip_all)] + pub fn hmac_keys( + &self, + epoch_delta_range: RangeInclusive, + ) -> Result, StorageError> { + let conn = self.client.store().conn()?; + let mut ikm = StoredUserPreferences::load(&conn)?.hmac_key; + ikm.extend(&self.group_id); + let hkdf = Hkdf::::new(Some(HMAC_SALT), &ikm[..]); + + let mut result = vec![]; + let current_epoch = hmac_epoch(); + for delta in epoch_delta_range { + let mut key = [0; 42]; + let epoch = current_epoch + delta; + hkdf.expand(&epoch.to_le_bytes(), &mut key) + .expect("Length is correct"); + + result.push(HmacKey { key, epoch }); + } + + Ok(result) + } + + #[tracing::instrument(level = "trace", skip_all)] + pub(super) fn prepare_group_messages( + &self, + payloads: Vec<&[u8]>, + ) -> Result, GroupError> { + let hmac_key = self + .hmac_keys(0..=0)? + .pop() + .expect("Range of count 1 was provided."); + let sender_hmac = + Hmac::::new_from_slice(&hmac_key.key).expect("HMAC can take key of any size"); + + let mut result = vec![]; + for payload in payloads { + let mut sender_hmac = sender_hmac.clone(); + sender_hmac.update(payload); + let sender_hmac = sender_hmac.finalize(); + + result.push(GroupMessageInput { + version: Some(GroupMessageInputVersion::V1(GroupMessageInputV1 { + data: payload.to_vec(), + sender_hmac: sender_hmac.into_bytes().to_vec(), + })), + }); + } + + Ok(result) + } } // Extracts the message sender, but does not do any validation to ensure that the @@ -1569,4 +1630,30 @@ pub(crate) mod tests { } future::join_all(futures).await; } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + async fn hmac_keys_work_as_expected() { + let wallet = generate_local_wallet(); + let amal = Arc::new(ClientBuilder::new_test_client(&wallet).await); + let amal_group: Arc> = + Arc::new(amal.create_group(None, Default::default()).unwrap()); + + let hmac_keys = amal_group.hmac_keys(-1..=1).unwrap(); + let current_hmac_key = amal_group.hmac_keys(0..=0).unwrap().pop().unwrap(); + assert_eq!(hmac_keys.len(), 3); + assert_eq!(hmac_keys[1].key, current_hmac_key.key); + assert_eq!(hmac_keys[1].epoch, current_hmac_key.epoch); + + // Make sure the keys are different + assert_ne!(hmac_keys[0].key, hmac_keys[1].key); + assert_ne!(hmac_keys[0].key, hmac_keys[2].key); + assert_ne!(hmac_keys[1].key, hmac_keys[2].key); + + // Make sure the epochs align + let current_epoch = hmac_epoch(); + assert_eq!(hmac_keys[0].epoch, current_epoch - 1); + assert_eq!(hmac_keys[1].epoch, current_epoch); + assert_eq!(hmac_keys[2].epoch, current_epoch + 1); + } } diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index f23bb8b6b..360245fd1 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -11,6 +11,7 @@ pub(super) mod mls_sync; pub(super) mod subscriptions; pub mod validated_commit; +use device_sync::preference_sync::UserPreferenceUpdate; use intents::SendMessageIntentData; use mls_sync::GroupMessageProcessingError; use openmls::{ @@ -83,7 +84,6 @@ use crate::{ identity::{parse_credential, IdentityError}, identity_updates::{load_identity_updates, InstallationDiffError}, intents::ProcessIntentError, - preferences::UserPreferenceUpdate, retry::RetryableError, storage::{ consent_record::{ConsentState, ConsentType, StoredConsentRecord}, @@ -285,6 +285,12 @@ impl Clone for MlsGroup { } } +pub struct HmacKey { + pub key: [u8; 42], + // # of 30 day periods since unix epoch + pub epoch: i64, +} + #[derive(Debug, Clone, PartialEq)] pub enum UpdateAdminListType { Add, @@ -1686,9 +1692,12 @@ pub(crate) mod tests { }], serialized_welcome, ); + let messages = sender_group + .prepare_group_messages(vec![serialized_commit.as_slice()]) + .unwrap(); sender_client .api_client - .send_group_messages(vec![serialized_commit.as_slice()]) + .send_group_messages(messages) .await .unwrap(); sender_group diff --git a/xmtp_mls/src/lib.rs b/xmtp_mls/src/lib.rs index cf0a08702..fb21ba10b 100644 --- a/xmtp_mls/src/lib.rs +++ b/xmtp_mls/src/lib.rs @@ -12,7 +12,6 @@ pub mod identity; pub mod identity_updates; mod intents; mod mutex_registry; -mod preferences; pub mod retry; pub mod storage; mod stream_handles; diff --git a/xmtp_mls/src/storage/encrypted_store/schema.rs b/xmtp_mls/src/storage/encrypted_store/schema.rs index 2c1f722fa..14ede47e5 100644 --- a/xmtp_mls/src/storage/encrypted_store/schema.rs +++ b/xmtp_mls/src/storage/encrypted_store/schema.rs @@ -110,7 +110,7 @@ diesel::table! { diesel::table! { user_preferences (id) { id -> Nullable, - hmac_key -> Nullable, + hmac_key -> Binary, } } diff --git a/xmtp_mls/src/storage/encrypted_store/user_preferences.rs b/xmtp_mls/src/storage/encrypted_store/user_preferences.rs index 0810a415b..9d59bc299 100644 --- a/xmtp_mls/src/storage/encrypted_store/user_preferences.rs +++ b/xmtp_mls/src/storage/encrypted_store/user_preferences.rs @@ -5,16 +5,27 @@ use super::{ DbConnection, }; use diesel::prelude::*; +use rand::{rngs::OsRng, RngCore}; -#[derive(Identifiable, Insertable, Queryable, Debug, Clone, PartialEq, Eq, Default)] +#[derive(Identifiable, Insertable, Queryable, Debug, Clone, PartialEq, Eq)] #[diesel(table_name = user_preferences)] #[diesel(primary_key(id))] pub struct StoredUserPreferences { /// Primary key - latest key is the "current" preference pub id: Option, /// Randomly generated hmac key root - pub hmac_key: Option>, + pub hmac_key: Vec, } + +impl Default for StoredUserPreferences { + fn default() -> Self { + let mut hmac_key = vec![0; 32]; + OsRng.fill_bytes(&mut hmac_key); + + Self { id: None, hmac_key } + } +} + impl_store!(StoredUserPreferences, user_preferences); impl StoredUserPreferences { @@ -22,13 +33,25 @@ impl StoredUserPreferences { let query = dsl::user_preferences.order(dsl::id.desc()).limit(1); let mut result = conn.raw_query(|conn| query.load::(conn))?; - Ok(result.pop().unwrap_or_default()) + let result = match result.pop() { + Some(result) => result, + None => { + // Create a default and store it. + let result = Self::default(); + // TODO: emit an hmac key update event here. + result.store(conn)?; + result + } + }; + + Ok(result) } pub fn set_hmac_key(conn: &DbConnection, hmac_key: Vec) -> Result<(), StorageError> { let mut preferences = Self::load(conn)?; + // Have the id increment preferences.id = None; - preferences.hmac_key = Some(hmac_key); + preferences.hmac_key = hmac_key; preferences.store(conn)?; @@ -38,7 +61,7 @@ impl StoredUserPreferences { #[cfg(test)] mod tests { - use crate::{storage::encrypted_store::tests::with_connection, Store}; + use crate::storage::encrypted_store::tests::with_connection; #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); @@ -46,14 +69,10 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - async fn test_insert_and_upate_preferences() { + async fn test_insert_and_update_preferences() { with_connection(|conn| { - // loads a default - let pref = StoredUserPreferences::load(conn).unwrap(); - assert_eq!(pref, StoredUserPreferences::default()); - assert_eq!(pref.id, None); - // save that default - pref.store(conn).unwrap(); + // loads and stores a default + let _pref = StoredUserPreferences::load(conn).unwrap(); // set an hmac key let hmac_key = vec![1, 2, 1, 2, 1, 2, 1, 2, 1, 2]; @@ -61,7 +80,7 @@ mod tests { // load preferences from db let pref = StoredUserPreferences::load(conn).unwrap(); - assert_eq!(pref.hmac_key, Some(hmac_key)); + assert_eq!(pref.hmac_key, hmac_key); assert_eq!(pref.id, Some(2)); // check that there are two preferences stored diff --git a/xmtp_mls/src/subscriptions.rs b/xmtp_mls/src/subscriptions.rs index b14a9291c..d90bab05f 100644 --- a/xmtp_mls/src/subscriptions.rs +++ b/xmtp_mls/src/subscriptions.rs @@ -13,10 +13,10 @@ use xmtp_proto::{api_client::XmtpMlsStreams, xmtp::mls::api::v1::WelcomeMessage} use crate::{ client::{extract_welcome_message, ClientError}, groups::{ - group_metadata::GroupMetadata, mls_sync::GroupMessageProcessingError, - scoped_client::ScopedGroupClient as _, subscriptions, GroupError, MlsGroup, + device_sync::preference_sync::UserPreferenceUpdate, group_metadata::GroupMetadata, + mls_sync::GroupMessageProcessingError, scoped_client::ScopedGroupClient as _, + subscriptions, GroupError, MlsGroup, }, - preferences::UserPreferenceUpdate, retry::{Retry, RetryableError}, retry_async, retryable, storage::{ diff --git a/xmtp_mls/src/utils/mod.rs b/xmtp_mls/src/utils/mod.rs index 19872e390..a81bba777 100644 --- a/xmtp_mls/src/utils/mod.rs +++ b/xmtp_mls/src/utils/mod.rs @@ -8,16 +8,30 @@ pub mod hash { } pub mod time { + use std::time::Duration; + use wasm_timer::{SystemTime, UNIX_EPOCH}; pub const NS_IN_SEC: i64 = 1_000_000_000; + const SECS_IN_30_DAYS: i64 = 60 * 60 * 24 * 30; + + fn duration_since_epoch() -> Duration { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + } pub fn now_ns() -> i64 { - let now = SystemTime::now(); + duration_since_epoch().as_nanos() as i64 + } - now.duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_nanos() as i64 + pub fn now_secs() -> i64 { + duration_since_epoch().as_secs() as i64 + } + + /// Current hmac epoch. HMAC keys change every 30 days + pub fn hmac_epoch() -> i64 { + now_secs() / SECS_IN_30_DAYS } } diff --git a/xmtp_v2/Cargo.toml b/xmtp_v2/Cargo.toml index b1fcf7e7a..5917affcd 100644 --- a/xmtp_v2/Cargo.toml +++ b/xmtp_v2/Cargo.toml @@ -1,19 +1,19 @@ [package] edition = "2021" +license.workspace = true name = "xmtp_v2" rust-version = "1.64" version.workspace = true -license.workspace = true [dependencies] aes-gcm = "0.10.1" ecdsa = "0.15.1" generic-array = "0.14.6" hex = { workspace = true } -hkdf = "0.12.3" +hkdf.workspace = true k256 = { version = "0.12.0", features = ["ecdh"] } rand = { workspace = true } -sha2 = "0.10.6" +sha2.workspace = true sha3 = "0.10.6" [target.'cfg(target_arch = "wasm32")'.dependencies]