Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Keys sync message #310

Merged
merged 4 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion libsignal-service/src/account_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use tracing_futures::Instrument;
use zkgroup::profiles::ProfileKey;

use crate::content::ContentBody;
use crate::master_key::MasterKey;
use crate::pre_keys::{
KyberPreKeyEntity, PreKeyEntity, PreKeysStore, SignedPreKeyEntity,
PRE_KEY_BATCH_SIZE, PRE_KEY_MINIMUM,
Expand Down Expand Up @@ -277,6 +278,7 @@ impl<Service: PushService> AccountManager<Service> {
aci_identity_store: &dyn IdentityKeyStore,
pni_identity_store: &dyn IdentityKeyStore,
credentials: ServiceCredentials,
master_key: Option<MasterKey>,
) -> Result<(), ProvisioningError> {
let query: HashMap<_, _> = url.query_pairs().collect();
let ephemeral_id =
Expand Down Expand Up @@ -328,7 +330,7 @@ impl<Service: PushService> AccountManager<Service> {
provisioning_code: Some(provisioning_code),
read_receipts: None,
user_agent: None,
master_key: None, // XXX
master_key: master_key.map(|x| x.into()),
};

let cipher = ProvisioningCipher::from_public(pub_key);
Expand Down
2 changes: 2 additions & 0 deletions libsignal-service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod content;
mod digeststream;
pub mod envelope;
pub mod groups_v2;
pub mod master_key;
pub mod messagepipe;
pub mod models;
pub mod pre_keys;
Expand Down Expand Up @@ -79,6 +80,7 @@ pub mod prelude {
AccessControl, Group, Member, PendingMember, RequestingMember,
Timer,
},
master_key::{MasterKey, MasterKeyStore, StorageServiceKey},
proto::{
attachment_pointer::AttachmentIdentifier, sync_message::Contacts,
AttachmentPointer,
Expand Down
111 changes: 111 additions & 0 deletions libsignal-service/src/master_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const MASTER_KEY_LEN: usize = 32;
const STORAGE_KEY_LEN: usize = 32;

#[derive(Debug, PartialEq)]
pub struct MasterKey {
pub inner: [u8; MASTER_KEY_LEN],
}

impl MasterKey {
pub fn generate() -> Self {
use rand::Rng;

// Create random bytes
let mut rng = rand::thread_rng();
let mut inner = [0_u8; MASTER_KEY_LEN];
rng.fill(&mut inner);
Self { inner }
}

pub fn from_slice(
slice: &[u8],
) -> Result<Self, std::array::TryFromSliceError> {
let inner = slice.try_into()?;
Ok(Self { inner })
}
}

impl From<MasterKey> for Vec<u8> {
fn from(val: MasterKey) -> Self {
val.inner.to_vec()
}
}

#[derive(Debug, PartialEq)]
pub struct StorageServiceKey {
pub inner: [u8; STORAGE_KEY_LEN],
}

impl StorageServiceKey {
pub fn from_master_key(master_key: &MasterKey) -> Self {
use hmac::{Hmac, Mac};
use sha2::Sha256;

type HmacSha256 = Hmac<Sha256>;
const KEY: &[u8] = b"Storage Service Encryption";

let mut mac = HmacSha256::new_from_slice(&master_key.inner).unwrap();
mac.update(KEY);
let result = mac.finalize();
let inner: [u8; STORAGE_KEY_LEN] = result.into_bytes().into();

Self { inner }
}

pub fn from_slice(
slice: &[u8],
) -> Result<Self, std::array::TryFromSliceError> {
let inner = slice.try_into()?;
Ok(Self { inner })
}
}

impl From<StorageServiceKey> for Vec<u8> {
fn from(val: StorageServiceKey) -> Self {
val.inner.to_vec()
}
}

/// Storage trait for handling MasterKey and StorageKey.
pub trait MasterKeyStore {
/// Fetch the master key from the store if it exists.
fn fetch_master_key(&self) -> Option<MasterKey>;

/// Fetch the storage service key from the store if it exists.
fn fetch_storage_service_key(&self) -> Option<StorageServiceKey>;

/// Save (or clear) the master key to the store.
fn store_master_key(&self, master_key: Option<&MasterKey>);

/// Save (or clear) the storage service key to the store.
fn store_storage_service_key(
&self,
storage_key: Option<&StorageServiceKey>,
);
}

mod tests {
#[test]
fn derive_storage_key_from_master_key() {
use super::{MasterKey, StorageServiceKey};
use base64::prelude::*;

// This test passed with actual 'masterKey' and 'storageKey' values taken
// from Signal Desktop v7.23.0 database at 2024-09-08 after linking it with Signal Andoid.

let master_key_bytes = BASE64_STANDARD
.decode("9hquLIIZmom8fHF7H8pbUAreawmPLEqli5ceJ94pFkU=")
.unwrap();
let storage_key_bytes = BASE64_STANDARD
.decode("QMgZ5RGTLFTr4u/J6nypaJX6DKDlSgMw8vmxU6gxnvI=")
.unwrap();
assert_eq!(master_key_bytes.len(), 32);
assert_eq!(storage_key_bytes.len(), 32);

let master_key = MasterKey::from_slice(&master_key_bytes).unwrap();
let storage_key = StorageServiceKey::from_master_key(&master_key);

assert_eq!(master_key.inner, master_key_bytes.as_slice());
assert_eq!(storage_key.inner, storage_key_bytes.as_slice());
}
}
50 changes: 50 additions & 0 deletions libsignal-service/src/sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,56 @@ where
Ok(())
}

/// Send `Keys` synchronization message
#[tracing::instrument(skip(self))]
pub async fn send_keys(
&mut self,
recipient: &ServiceAddress,
keys: sync_message::Keys,
) -> Result<(), MessageSenderError> {
let msg = SyncMessage {
keys: Some(keys),
..SyncMessage::with_padding()
};

let ts = Utc::now().timestamp_millis() as u64;
self.send_message(recipient, None, msg, ts, false, false)
.await?;

Ok(())
}

/// Send a `Keys` request message
#[tracing::instrument(skip(self))]
pub async fn send_sync_message_request(
&mut self,
recipient: &ServiceAddress,
request_type: sync_message::request::Type,
) -> Result<(), MessageSenderError> {
if self.device_id == DEFAULT_DEVICE_ID.into() {
let reason = format!(
"Primary device can't send sync requests, ignoring {:?}",
request_type
);
return Err(MessageSenderError::ServiceError(
ServiceError::SendError { reason },
));
}

let msg = SyncMessage {
request: Some(sync_message::Request {
r#type: Some(request_type.into()),
}),
..SyncMessage::with_padding()
};

let ts = Utc::now().timestamp_millis() as u64;
self.send_message(recipient, None, msg, ts, false, false)
.await?;

Ok(())
}

#[tracing::instrument(level = "trace", skip(self))]
fn create_pni_signature(
&mut self,
Expand Down
Loading