From 76758f095fe3368c9c66ee7780919c0e8e3c7f87 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Tue, 20 Apr 2021 17:21:03 +0200 Subject: [PATCH 01/19] Swap libsignal-protocol dependency with libsignal-client --- libsignal-service-actix/Cargo.toml | 3 ++- libsignal-service-hyper/Cargo.toml | 2 +- libsignal-service/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libsignal-service-actix/Cargo.toml b/libsignal-service-actix/Cargo.toml index eeb0193e1..a48b414f6 100644 --- a/libsignal-service-actix/Cargo.toml +++ b/libsignal-service-actix/Cargo.toml @@ -8,7 +8,8 @@ edition = "2018" [dependencies] libsignal-service = { path = "../libsignal-service" } -libsignal-protocol = { git = "https://github.com/Michael-F-Bryan/libsignal-protocol-rs" } +libsignal-protocol = { git = "https://github.com/signalapp/libsignal-client", version = "0.1.0", tag = "v0.4.0"} + awc = { version = "3.0.0-beta.5", features=["rustls"] } actix = "0.11.1" diff --git a/libsignal-service-hyper/Cargo.toml b/libsignal-service-hyper/Cargo.toml index 0c8ef4406..25da83999 100644 --- a/libsignal-service-hyper/Cargo.toml +++ b/libsignal-service-hyper/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" [dependencies] libsignal-service = { path = "../libsignal-service" } -libsignal-protocol = { git = "https://github.com/Michael-F-Bryan/libsignal-protocol-rs" } +libsignal-protocol = { git = "https://github.com/signalapp/libsignal-client", version = "0.1.0", tag = "v0.4.0"} async-trait = "0.1" base64 = "0.13" diff --git a/libsignal-service/Cargo.toml b/libsignal-service/Cargo.toml index d2a9d7542..a80b33e6b 100644 --- a/libsignal-service/Cargo.toml +++ b/libsignal-service/Cargo.toml @@ -7,7 +7,7 @@ license = "GPLv3" readme = "../README.md" [dependencies] -libsignal-protocol = { git = "https://github.com/Michael-F-Bryan/libsignal-protocol-rs" } +libsignal-protocol = { git = "https://github.com/signalapp/libsignal-client", version = "0.1.0", tag = "v0.4.0"} zkgroup = { git = "https://github.com/signalapp/zkgroup" } async-trait = "0.1.30" url = { version = "2.1.1", features = ["serde"] } From c4110d2fd0858ad744bd7e8cff06fb60e86fe6a2 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Tue, 20 Apr 2021 18:59:31 +0200 Subject: [PATCH 02/19] part 1 --- libsignal-service/src/account_manager.rs | 15 +- libsignal-service/src/cipher.rs | 12 +- libsignal-service/src/configuration.rs | 2 +- libsignal-service/src/content.rs | 2 +- libsignal-service/src/groups_v2/utils.rs | 14 +- libsignal-service/src/provisioning.rs | 440 ++++++++++++++++++ libsignal-service/src/push_service.rs | 6 +- .../src/sealed_session_cipher.rs | 6 +- libsignal-service/src/sender.rs | 12 +- libsignal-service/src/utils.rs | 7 +- 10 files changed, 471 insertions(+), 45 deletions(-) create mode 100644 libsignal-service/src/provisioning.rs diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 127054712..9f076ca67 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -14,13 +14,11 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::time::SystemTime; -use libsignal_protocol::keys::PublicKey; -use libsignal_protocol::{Context, StoreContext}; +use libsignal_protocol::PublicKey; use zkgroup::profiles::ProfileKey; pub struct AccountManager { - context: Context, service: Service, profile_key: Option<[u8; 32]>, } @@ -51,13 +49,8 @@ const PRE_KEY_MINIMUM: u32 = 10; const PRE_KEY_BATCH_SIZE: u32 = 100; impl AccountManager { - pub fn new( - context: Context, - service: Service, - profile_key: Option<[u8; 32]>, - ) -> Self { + pub fn new(service: Service, profile_key: Option<[u8; 32]>) -> Self { Self { - context, service, profile_key, } @@ -96,13 +89,11 @@ impl AccountManager { } let pre_keys = libsignal_protocol::generate_pre_keys( - &self.context, pre_keys_offset_id, PRE_KEY_BATCH_SIZE, )?; let identity_key_pair = store_context.identity_key_pair()?; let signed_pre_key = libsignal_protocol::generate_signed_pre_key( - &self.context, &identity_key_pair, next_signed_pre_key_id, SystemTime::now(), @@ -207,7 +198,7 @@ impl AccountManager { query.get("pub_key").ok_or(LinkError::InvalidPublicKey)?; let pub_key = base64::decode(&**pub_key) .map_err(|_e| LinkError::InvalidPublicKey)?; - let pub_key = PublicKey::decode_point(&self.context, &pub_key) + let pub_key = PublicKey::deserialize(&pub_key) .map_err(|_e| LinkError::InvalidPublicKey)?; let identity_key_pair = store_context.identity_key_pair()?; diff --git a/libsignal-service/src/cipher.rs b/libsignal-service/src/cipher.rs index dd4f87f20..0c81c6611 100644 --- a/libsignal-service/src/cipher.rs +++ b/libsignal-service/src/cipher.rs @@ -99,11 +99,11 @@ impl ServiceCipher { let sender = get_preferred_protocol_address( &self.store_context, envelope.source_address(), - envelope.source_device() as i32, + envelope.source_device(), )?; let metadata = Metadata { sender: envelope.source_address(), - sender_device: envelope.source_device() as i32, + sender_device: envelope.source_device(), timestamp: envelope.timestamp(), needs_receipt: false, }; @@ -130,11 +130,11 @@ impl ServiceCipher { let sender = get_preferred_protocol_address( &self.store_context, envelope.source_address(), - envelope.source_device() as i32, + envelope.source_device(), )?; let metadata = Metadata { sender: envelope.source_address(), - sender_device: envelope.source_device() as i32, + sender_device: envelope.source_device(), timestamp: envelope.timestamp(), needs_receipt: false, }; @@ -293,8 +293,8 @@ fn strip_padding( pub fn get_preferred_protocol_address( store_context: &StoreContext, address: ServiceAddress, - device_id: i32, -) -> Result { + device_id: u32, +) -> Result { if let Some(ref uuid) = address.uuid { let address = ProtocolAddress::new(uuid.to_string(), device_id as i32); if store_context.contains_session(&address)? { diff --git a/libsignal-service/src/configuration.rs b/libsignal-service/src/configuration.rs index 3c97a4bda..2a8ec4b6f 100644 --- a/libsignal-service/src/configuration.rs +++ b/libsignal-service/src/configuration.rs @@ -29,7 +29,7 @@ pub struct ServiceCredentials { pub phonenumber: phonenumber::PhoneNumber, pub password: Option, pub signaling_key: Option, - pub device_id: Option, + pub device_id: Option, } impl ServiceCredentials { diff --git a/libsignal-service/src/content.rs b/libsignal-service/src/content.rs index 850105563..87b083ea2 100644 --- a/libsignal-service/src/content.rs +++ b/libsignal-service/src/content.rs @@ -12,7 +12,7 @@ pub use crate::{ #[derive(Clone, Debug)] pub struct Metadata { pub sender: crate::ServiceAddress, - pub sender_device: i32, + pub sender_device: u32, pub timestamp: u64, pub needs_receipt: bool, } diff --git a/libsignal-service/src/groups_v2/utils.rs b/libsignal-service/src/groups_v2/utils.rs index bf874084d..98cc5889f 100644 --- a/libsignal-service/src/groups_v2/utils.rs +++ b/libsignal-service/src/groups_v2/utils.rs @@ -1,4 +1,4 @@ -use libsignal_protocol::{Context, Error}; +use libsignal_protocol::{error::SignalProtocolError, Context}; use zkgroup::groups::GroupMasterKey; use zkgroup::GROUP_MASTER_KEY_LEN; @@ -8,15 +8,11 @@ use zkgroup::GROUP_MASTER_KEY_LEN; pub fn derive_v2_migration_master_key( ctx: &Context, group_id: &[u8], -) -> Result { +) -> Result { assert_eq!(group_id.len(), 16, "Group ID must be exactly 16 bytes"); - let hkdf = libsignal_protocol::create_hkdf(ctx, 3)?; - let bytes = hkdf.derive_secrets( - GROUP_MASTER_KEY_LEN, - group_id, - &[], - b"GV2 Migration", - )?; + let hkdf = libsignal_protocol::HKDF::new(3)?; + let bytes = + hkdf.derive_secrets(group_id, b"GV2 Migration", GROUP_MASTER_KEY_LEN)?; let mut bytes_stack = [0u8; GROUP_MASTER_KEY_LEN]; bytes_stack.copy_from_slice(&bytes); Ok(GroupMasterKey::new(bytes_stack)) diff --git a/libsignal-service/src/provisioning.rs b/libsignal-service/src/provisioning.rs new file mode 100644 index 000000000..3eb42b147 --- /dev/null +++ b/libsignal-service/src/provisioning.rs @@ -0,0 +1,440 @@ +use aes::Aes256; +use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; +use bytes::{Bytes, BytesMut}; +use futures::{ + channel::mpsc::{self, Sender}, + prelude::*, + stream::FuturesUnordered, +}; +use hmac::{Hmac, Mac, NewMac}; +use pin_project::pin_project; +use prost::Message; +use rand::Rng; +use sha2::Sha256; +use url::Url; + +use libsignal_protocol::{ + keys::{KeyPair, PublicKey}, + Context, +}; + +pub use crate::proto::{ + ProvisionEnvelope, ProvisionMessage, ProvisioningVersion, +}; + +use crate::{ + envelope::{CIPHER_KEY_SIZE, IV_LENGTH, IV_OFFSET}, + messagepipe::{WebSocketService, WebSocketStreamItem}, + proto::{ + web_socket_message, ProvisioningUuid, WebSocketMessage, + WebSocketRequestMessage, WebSocketResponseMessage, + }, + push_service::ServiceError, +}; + +const VERSION: u8 = 1; + +#[derive(Debug)] +enum CipherMode { + Decrypt(KeyPair), + Encrypt(PublicKey), +} + +impl CipherMode { + fn public(&self) -> PublicKey { + match self { + CipherMode::Decrypt(pair) => pair.public(), + CipherMode::Encrypt(pub_key) => pub_key.clone(), + } + } +} + +#[derive(Debug)] +pub struct ProvisioningCipher { + ctx: Context, + key_material: CipherMode, +} + +#[derive(thiserror::Error, Debug)] +pub enum ProvisioningError { + #[error("Invalid provisioning data: {reason}")] + InvalidData { reason: String }, + #[error("Protobuf decoding error: {0}")] + DecodeError(#[from] prost::DecodeError), + #[error("Websocket error: {reason}")] + WsError { reason: String }, + #[error("Websocket closing: {reason}")] + WsClosing { reason: String }, + #[error("Service error: {0}")] + ServiceError(#[from] ServiceError), + #[error("libsignal-protocol error: {0}")] + ProtocolError(#[from] libsignal_protocol::error::SignalProtocolError), + #[error("ProvisioningCipher in encrypt-only mode")] + EncryptOnlyProvisioningCipher, +} + +impl ProvisioningCipher { + pub fn new(ctx: Context) -> Result { + let key_pair = libsignal_protocol::generate_key_pair(&ctx)?; + Ok(Self { + ctx, + key_material: CipherMode::Decrypt(key_pair), + }) + } + + pub fn from_public(ctx: Context, key: PublicKey) -> Self { + Self { + ctx, + key_material: CipherMode::Encrypt(key), + } + } + + pub fn from_key_pair(ctx: Context, key_pair: KeyPair) -> Self { + Self { + ctx, + key_material: CipherMode::Decrypt(key_pair), + } + } + + pub fn public_key(&self) -> PublicKey { + self.key_material.public() + } + + pub fn encrypt( + &self, + msg: ProvisionMessage, + ) -> Result { + let msg = { + let mut encoded = Vec::with_capacity(msg.encoded_len()); + msg.encode(&mut encoded).expect("infallible encoding"); + encoded + }; + + let mut rng = rand::thread_rng(); + let our_key_pair = libsignal_protocol::generate_key_pair(&self.ctx)?; + let agreement = self + .public_key() + .calculate_agreement(&our_key_pair.private())?; + let hkdf = libsignal_protocol::create_hkdf(&self.ctx, 3)?; + + let shared_secrets = hkdf.derive_secrets( + 64, + &agreement, + &[], + b"TextSecure Provisioning Message", + )?; + + let aes_key = &shared_secrets[0..32]; + let mac_key = &shared_secrets[32..]; + let iv: [u8; IV_LENGTH] = rng.gen(); + + let cipher = Cbc::::new_var(&aes_key, &iv) + .expect("initalization of CBC/AES/PKCS7"); + let ciphertext = cipher.encrypt_vec(&msg); + let mut mac = Hmac::::new_varkey(&mac_key) + .expect("HMAC can take any size key"); + mac.update(&[VERSION]); + mac.update(&iv); + mac.update(&ciphertext); + let mac = mac.finalize().into_bytes(); + + let body: Vec = std::iter::once(VERSION) + .chain(iv.iter().cloned()) + .chain(ciphertext) + .chain(mac) + .collect(); + + Ok(ProvisionEnvelope { + public_key: Some( + our_key_pair.public().to_bytes()?.as_slice().to_vec(), + ), + body: Some(body), + }) + } + + pub fn decrypt( + &self, + provision_envelope: ProvisionEnvelope, + ) -> Result { + let key_pair = match self.key_material { + CipherMode::Decrypt(ref key_pair) => key_pair, + CipherMode::Encrypt(_) => { + return Err(ProvisioningError::EncryptOnlyProvisioningCipher); + } + }; + let master_ephemeral = PublicKey::decode_point( + &self.ctx, + &provision_envelope.public_key.expect("no public key"), + )?; + let body = provision_envelope + .body + .expect("no body in ProvisionMessage"); + if body[0] != VERSION { + return Err(ProvisioningError::InvalidData { + reason: "Bad version number".into(), + }); + } + + let iv = &body[IV_OFFSET..(IV_LENGTH + IV_OFFSET)]; + let mac = &body[(body.len() - 32)..]; + let cipher_text = &body[16 + 1..(body.len() - CIPHER_KEY_SIZE)]; + let iv_and_cipher_text = &body[0..(body.len() - CIPHER_KEY_SIZE)]; + debug_assert_eq!(iv.len(), IV_LENGTH); + debug_assert_eq!(mac.len(), 32); + + let agreement = + master_ephemeral.calculate_agreement(&key_pair.private())?; + let hkdf = libsignal_protocol::create_hkdf(&self.ctx, 3)?; + + let shared_secrets = hkdf.derive_secrets( + 64, + &agreement, + &[], + b"TextSecure Provisioning Message", + )?; + + let parts1 = &shared_secrets[0..32]; + let parts2 = &shared_secrets[32..]; + + let mut verifier = Hmac::::new_varkey(&parts2) + .expect("HMAC can take any size key"); + verifier.update(&iv_and_cipher_text); + let our_mac = verifier.finalize().into_bytes(); + debug_assert_eq!(our_mac.len(), mac.len()); + if &our_mac[..32] != mac { + return Err(ProvisioningError::InvalidData { + reason: "wrong MAC".into(), + }); + } + + // libsignal-service-java uses Pkcs5, + // but that should not matter. + // https://crypto.stackexchange.com/questions/9043/what-is-the-difference-between-pkcs5-padding-and-pkcs7-padding + let cipher = Cbc::::new_var(&parts1, &iv) + .expect("initalization of CBC/AES/PKCS7"); + let input = cipher.decrypt_vec(cipher_text).map_err(|e| { + ProvisioningError::InvalidData { + reason: format!("CBC/Padding error: {:?}", e), + } + })?; + + Ok(prost::Message::decode(Bytes::from(input))?) + } +} + +#[pin_project] +pub struct ProvisioningPipe { + ws: WS, + #[pin] + stream: WS::Stream, + provisioning_cipher: ProvisioningCipher, +} + +#[derive(Debug)] +pub enum ProvisioningStep { + Url(Url), + Message(ProvisionMessage), +} + +impl ProvisioningPipe { + pub fn from_socket( + ws: WS, + stream: WS::Stream, + ctx: &Context, + ) -> Result { + Ok(ProvisioningPipe { + ws, + stream, + provisioning_cipher: ProvisioningCipher::new(ctx.clone())?, + }) + } + + async fn send_ok_response( + &mut self, + id: Option, + ) -> Result<(), ProvisioningError> { + self.send_response(WebSocketResponseMessage { + id, + status: Some(200), + message: Some("OK".into()), + body: None, + headers: vec![], + }) + .await + } + + async fn send_response( + &mut self, + r: WebSocketResponseMessage, + ) -> Result<(), ProvisioningError> { + let msg = WebSocketMessage { + r#type: Some(web_socket_message::Type::Response.into()), + response: Some(r), + ..Default::default() + }; + let mut buffer = BytesMut::with_capacity(msg.encoded_len()); + msg.encode(&mut buffer).unwrap(); + Ok(self.ws.send_message(buffer.into()).await?) + } + + /// Worker task that + async fn run( + mut self, + mut sink: Sender>, + ) -> Result<(), mpsc::SendError> { + use futures::future::LocalBoxFuture; + + // This is a runtime-agnostic, poor man's `::spawn(Future)`. + let mut background_work = FuturesUnordered::>::new(); + // a pending task is added, as to never end the background worker until + // it's dropped. + background_work.push(futures::future::pending().boxed_local()); + + loop { + futures::select! { + // WebsocketConnection::onMessage(ByteString) + frame = self.stream.next() => match frame { + Some(WebSocketStreamItem::Message(frame)) => { + let env = self.process_frame(frame).await.transpose(); + if let Some(env) = env { + sink.send(env).await?; + } + }, + // TODO: implement keep-alive? + Some(WebSocketStreamItem::KeepAliveRequest) => continue, + None => break, + }, + _ = background_work.next() => { + // no op + }, + complete => { + log::info!("select! complete"); + } + } + } + + Ok(()) + } + + async fn process_frame( + &mut self, + frame: Bytes, + ) -> Result, ProvisioningError> { + let msg = WebSocketMessage::decode(frame)?; + use web_socket_message::Type; + match (msg.r#type(), msg.request, msg.response) { + (Type::Request, Some(request), _) => { + match request { + // step 1: we get a ProvisioningUUID that we need to build a + // registration link + WebSocketRequestMessage { + id, + verb, + path, + body, + .. + } if verb == Some("PUT".into()) + && path == Some("/v1/address".into()) => + { + let uuid: ProvisioningUuid = + prost::Message::decode(Bytes::from(body.unwrap()))?; + let mut provisioning_url = Url::parse("tsdevice://") + .map_err(|e| ProvisioningError::WsError { + reason: e.to_string(), + })?; + provisioning_url + .query_pairs_mut() + .append_pair("uuid", &uuid.uuid.unwrap()) + .append_pair( + "pub_key", + &format!( + "{}", + self.provisioning_cipher.public_key() + ), + ); + + // acknowledge + self.send_ok_response(id).await?; + + Ok(Some(ProvisioningStep::Url(provisioning_url))) + } + // step 2: once the QR code is scanned by the (already + // validated) main device + // we get a ProvisionMessage, that contains a bunch of + // useful things + WebSocketRequestMessage { + id, + verb, + path, + body, + .. + } if verb == Some("PUT".into()) + && path == Some("/v1/message".into()) => + { + let provision_envelope: ProvisionEnvelope = + prost::Message::decode(Bytes::from(body.unwrap()))?; + let provision_message = self + .provisioning_cipher + .decrypt(provision_envelope)?; + + // acknowledge + self.send_ok_response(id).await?; + + Ok(Some(ProvisioningStep::Message(provision_message))) + } + _ => Err(ProvisioningError::WsError { + reason: "Incorrect request".into(), + }), + } + } + _ => Err(ProvisioningError::WsError { + reason: "Incorrect request".into(), + }), + } + } + + pub fn stream( + self, + ) -> impl Stream> { + let (sink, stream) = mpsc::channel(1); + + let stream = stream.map(Some); + let runner = self.run(sink).map(|_| { + log::info!("Sink closed, provisioning is done!"); + None + }); + + let combined = futures::stream::select(stream, runner.into_stream()); + combined.filter_map(|x| async { x }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn encrypt_provisioning_roundtrip() { + let ctx = Context::default(); + let cipher = ProvisioningCipher::new(ctx.clone()).unwrap(); + let encrypt_cipher = + ProvisioningCipher::from_public(ctx.clone(), cipher.public_key()); + + assert_eq!( + cipher.public_key(), + encrypt_cipher.public_key(), + "copy public key" + ); + + let msg = ProvisionMessage::default(); + let encrypted = encrypt_cipher.encrypt(msg.clone()).unwrap(); + + assert!(matches!( + encrypt_cipher.decrypt(encrypted.clone()), + Err(ProvisioningError::EncryptOnlyProvisioningCipher) + )); + + let decrypted = cipher.decrypt(encrypted).expect("decryptability"); + assert_eq!(msg, decrypted); + } +} diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index d5801e49f..89822793b 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -61,7 +61,7 @@ pub const STICKER_PATH: &str = "stickers/%s/full/%d"; **/ pub const KEEPALIVE_TIMEOUT_SECONDS: Duration = Duration::from_secs(55); -pub const DEFAULT_DEVICE_ID: i32 = 1; +pub const DEFAULT_DEVICE_ID: u32 = 1; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -244,7 +244,7 @@ pub enum ServiceError { MacError, #[error("Protocol error: {0}")] - SignalProtocolError(#[from] libsignal_protocol::Error), + SignalProtocolError(#[from] libsignal_protocol::error::SignalProtocolError), #[error("{0:?}")] MismatchedDevicesException(MismatchedDevices), @@ -534,7 +534,7 @@ pub trait PushService { &mut self, context: &Context, destination: &ServiceAddress, - device_id: i32, + device_id: u32, ) -> Result, ServiceError> { let path = match (device_id, destination.relay.as_ref()) { (1, None) => format!("/v2/keys/{}/*", destination.identifier()), diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index 628358d4c..06a5be4d1 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -102,7 +102,7 @@ pub struct UnidentifiedSenderMessageContent { pub struct SenderCertificate { signer: ServerCertificate, key: PublicKey, - sender_device_id: i32, + sender_device_id: u32, sender_uuid: Option, sender_e164: Option, expiration: u64, @@ -140,7 +140,7 @@ pub struct CertificateValidator { pub(crate) struct DecryptionResult { pub sender_uuid: Option, pub sender_e164: Option, - pub device_id: i32, + pub device_id: u32, pub padded_message: Vec, pub version: u32, } @@ -600,7 +600,7 @@ impl SenderCertificate { )?, sender_e164, sender_uuid, - sender_device_id: sender_device_id as i32, + sender_device_id, expiration: expires, certificate, signature, diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index cebdb7105..b3c105013 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -69,7 +69,7 @@ pub struct AttachmentSpec { pub struct MessageSender { service: Service, cipher: ServiceCipher, - device_id: i32, + device_id: u32, } #[derive(thiserror::Error, Debug)] @@ -86,7 +86,7 @@ pub enum MessageSenderError { #[error("{0}")] ServiceError(#[from] ServiceError), #[error("protocol error: {0}")] - ProtocolError(#[from] libsignal_protocol::Error), + ProtocolError(#[from] libsignal_protocol::error::SignalProtocolError), #[error("Failed to upload attachment {0}")] AttachmentUploadError(#[from] AttachmentUploadError), @@ -119,7 +119,7 @@ where pub fn new( service: Service, cipher: ServiceCipher, - device_id: i32, + device_id: u32, ) -> Self { MessageSender { service, @@ -680,7 +680,7 @@ where &mut self, recipient: &ServiceAddress, unidentified_access: Option<&UnidentifiedAccess>, - device_id: i32, + device_id: u32, content: &[u8], ) -> Result { let recipient_address = get_preferred_protocol_address( @@ -702,7 +702,7 @@ where .await?; for pre_key_bundle in pre_keys { if recipient.matches(&self.cipher.local_address) - && self.device_id == pre_key_bundle.device_id() + && self.device_id == pre_key_bundle.device_id()? { trace!("not establishing a session with myself!"); continue; @@ -711,7 +711,7 @@ where let pre_key_address = get_preferred_protocol_address( &self.cipher.store_context, recipient.clone(), - pre_key_bundle.device_id(), + pre_key_bundle.device_id()?, )?; let session_builder = SessionBuilder::new( &self.cipher.context, diff --git a/libsignal-service/src/utils.rs b/libsignal-service/src/utils.rs index 32f39c77b..d84a28898 100644 --- a/libsignal-service/src/utils.rs +++ b/libsignal-service/src/utils.rs @@ -57,7 +57,7 @@ pub mod serde_optional_base64 { } pub mod serde_public_key { - use libsignal_protocol::keys::PublicKey; + use libsignal_protocol::PublicKey; use serde::Serializer; pub fn serialize( @@ -67,8 +67,7 @@ pub mod serde_public_key { where S: Serializer, { - use serde::ser::Error; - serializer - .serialize_str(&public_key.to_base64().map_err(S::Error::custom)?) + let public_key = public_key.serialize(); + serializer.serialize_str(&base64::encode(&public_key)) } } From f4615ccc23abc24110fb5bba7b5fd5d30626cd26 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Tue, 20 Apr 2021 21:38:20 +0200 Subject: [PATCH 03/19] rand 0.7 because of compatibility --- libsignal-service/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsignal-service/Cargo.toml b/libsignal-service/Cargo.toml index a80b33e6b..e2573f430 100644 --- a/libsignal-service/Cargo.toml +++ b/libsignal-service/Cargo.toml @@ -30,7 +30,7 @@ aes = "0.6.0" aes-gcm = "0.8.0" aes-ctr = "0.6.0" block-modes = "0.7.0" -rand = "0.8.0" +rand = "0.7.0" uuid = "0.8" phonenumber = "0.3" From 9e61ecff564f2cd74a2bf62e02c3f95ced8f004d Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Tue, 20 Apr 2021 21:38:30 +0200 Subject: [PATCH 04/19] part 2 --- libsignal-service/src/account_manager.rs | 5 +- libsignal-service/src/provisioning.rs | 440 ------------------ libsignal-service/src/provisioning/cipher.rs | 79 ++-- libsignal-service/src/provisioning/mod.rs | 2 +- libsignal-service/src/provisioning/pipe.rs | 11 +- libsignal-service/src/push_service.rs | 81 ++-- .../src/sealed_session_cipher.rs | 6 +- 7 files changed, 87 insertions(+), 537 deletions(-) delete mode 100644 libsignal-service/src/provisioning.rs diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 9f076ca67..0c891d130 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -40,7 +40,7 @@ pub enum LinkError { #[error("TsUrl has an invalid pub_key field")] InvalidPublicKey, #[error("Protocol error {0}")] - ProtocolError(#[from] libsignal_protocol::Error), + ProtocolError(#[from] libsignal_protocol::error::SignalProtocolError), #[error(transparent)] ProvisioningError(#[from] ProvisioningError), } @@ -228,8 +228,7 @@ impl AccountManager { user_agent: None, }; - let cipher = - ProvisioningCipher::from_public(self.context.clone(), pub_key); + let cipher = ProvisioningCipher::from_public(pub_key); let encrypted = cipher.encrypt(msg)?; self.send_provisioning_message(ephemeral_id, encrypted) diff --git a/libsignal-service/src/provisioning.rs b/libsignal-service/src/provisioning.rs deleted file mode 100644 index 3eb42b147..000000000 --- a/libsignal-service/src/provisioning.rs +++ /dev/null @@ -1,440 +0,0 @@ -use aes::Aes256; -use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; -use bytes::{Bytes, BytesMut}; -use futures::{ - channel::mpsc::{self, Sender}, - prelude::*, - stream::FuturesUnordered, -}; -use hmac::{Hmac, Mac, NewMac}; -use pin_project::pin_project; -use prost::Message; -use rand::Rng; -use sha2::Sha256; -use url::Url; - -use libsignal_protocol::{ - keys::{KeyPair, PublicKey}, - Context, -}; - -pub use crate::proto::{ - ProvisionEnvelope, ProvisionMessage, ProvisioningVersion, -}; - -use crate::{ - envelope::{CIPHER_KEY_SIZE, IV_LENGTH, IV_OFFSET}, - messagepipe::{WebSocketService, WebSocketStreamItem}, - proto::{ - web_socket_message, ProvisioningUuid, WebSocketMessage, - WebSocketRequestMessage, WebSocketResponseMessage, - }, - push_service::ServiceError, -}; - -const VERSION: u8 = 1; - -#[derive(Debug)] -enum CipherMode { - Decrypt(KeyPair), - Encrypt(PublicKey), -} - -impl CipherMode { - fn public(&self) -> PublicKey { - match self { - CipherMode::Decrypt(pair) => pair.public(), - CipherMode::Encrypt(pub_key) => pub_key.clone(), - } - } -} - -#[derive(Debug)] -pub struct ProvisioningCipher { - ctx: Context, - key_material: CipherMode, -} - -#[derive(thiserror::Error, Debug)] -pub enum ProvisioningError { - #[error("Invalid provisioning data: {reason}")] - InvalidData { reason: String }, - #[error("Protobuf decoding error: {0}")] - DecodeError(#[from] prost::DecodeError), - #[error("Websocket error: {reason}")] - WsError { reason: String }, - #[error("Websocket closing: {reason}")] - WsClosing { reason: String }, - #[error("Service error: {0}")] - ServiceError(#[from] ServiceError), - #[error("libsignal-protocol error: {0}")] - ProtocolError(#[from] libsignal_protocol::error::SignalProtocolError), - #[error("ProvisioningCipher in encrypt-only mode")] - EncryptOnlyProvisioningCipher, -} - -impl ProvisioningCipher { - pub fn new(ctx: Context) -> Result { - let key_pair = libsignal_protocol::generate_key_pair(&ctx)?; - Ok(Self { - ctx, - key_material: CipherMode::Decrypt(key_pair), - }) - } - - pub fn from_public(ctx: Context, key: PublicKey) -> Self { - Self { - ctx, - key_material: CipherMode::Encrypt(key), - } - } - - pub fn from_key_pair(ctx: Context, key_pair: KeyPair) -> Self { - Self { - ctx, - key_material: CipherMode::Decrypt(key_pair), - } - } - - pub fn public_key(&self) -> PublicKey { - self.key_material.public() - } - - pub fn encrypt( - &self, - msg: ProvisionMessage, - ) -> Result { - let msg = { - let mut encoded = Vec::with_capacity(msg.encoded_len()); - msg.encode(&mut encoded).expect("infallible encoding"); - encoded - }; - - let mut rng = rand::thread_rng(); - let our_key_pair = libsignal_protocol::generate_key_pair(&self.ctx)?; - let agreement = self - .public_key() - .calculate_agreement(&our_key_pair.private())?; - let hkdf = libsignal_protocol::create_hkdf(&self.ctx, 3)?; - - let shared_secrets = hkdf.derive_secrets( - 64, - &agreement, - &[], - b"TextSecure Provisioning Message", - )?; - - let aes_key = &shared_secrets[0..32]; - let mac_key = &shared_secrets[32..]; - let iv: [u8; IV_LENGTH] = rng.gen(); - - let cipher = Cbc::::new_var(&aes_key, &iv) - .expect("initalization of CBC/AES/PKCS7"); - let ciphertext = cipher.encrypt_vec(&msg); - let mut mac = Hmac::::new_varkey(&mac_key) - .expect("HMAC can take any size key"); - mac.update(&[VERSION]); - mac.update(&iv); - mac.update(&ciphertext); - let mac = mac.finalize().into_bytes(); - - let body: Vec = std::iter::once(VERSION) - .chain(iv.iter().cloned()) - .chain(ciphertext) - .chain(mac) - .collect(); - - Ok(ProvisionEnvelope { - public_key: Some( - our_key_pair.public().to_bytes()?.as_slice().to_vec(), - ), - body: Some(body), - }) - } - - pub fn decrypt( - &self, - provision_envelope: ProvisionEnvelope, - ) -> Result { - let key_pair = match self.key_material { - CipherMode::Decrypt(ref key_pair) => key_pair, - CipherMode::Encrypt(_) => { - return Err(ProvisioningError::EncryptOnlyProvisioningCipher); - } - }; - let master_ephemeral = PublicKey::decode_point( - &self.ctx, - &provision_envelope.public_key.expect("no public key"), - )?; - let body = provision_envelope - .body - .expect("no body in ProvisionMessage"); - if body[0] != VERSION { - return Err(ProvisioningError::InvalidData { - reason: "Bad version number".into(), - }); - } - - let iv = &body[IV_OFFSET..(IV_LENGTH + IV_OFFSET)]; - let mac = &body[(body.len() - 32)..]; - let cipher_text = &body[16 + 1..(body.len() - CIPHER_KEY_SIZE)]; - let iv_and_cipher_text = &body[0..(body.len() - CIPHER_KEY_SIZE)]; - debug_assert_eq!(iv.len(), IV_LENGTH); - debug_assert_eq!(mac.len(), 32); - - let agreement = - master_ephemeral.calculate_agreement(&key_pair.private())?; - let hkdf = libsignal_protocol::create_hkdf(&self.ctx, 3)?; - - let shared_secrets = hkdf.derive_secrets( - 64, - &agreement, - &[], - b"TextSecure Provisioning Message", - )?; - - let parts1 = &shared_secrets[0..32]; - let parts2 = &shared_secrets[32..]; - - let mut verifier = Hmac::::new_varkey(&parts2) - .expect("HMAC can take any size key"); - verifier.update(&iv_and_cipher_text); - let our_mac = verifier.finalize().into_bytes(); - debug_assert_eq!(our_mac.len(), mac.len()); - if &our_mac[..32] != mac { - return Err(ProvisioningError::InvalidData { - reason: "wrong MAC".into(), - }); - } - - // libsignal-service-java uses Pkcs5, - // but that should not matter. - // https://crypto.stackexchange.com/questions/9043/what-is-the-difference-between-pkcs5-padding-and-pkcs7-padding - let cipher = Cbc::::new_var(&parts1, &iv) - .expect("initalization of CBC/AES/PKCS7"); - let input = cipher.decrypt_vec(cipher_text).map_err(|e| { - ProvisioningError::InvalidData { - reason: format!("CBC/Padding error: {:?}", e), - } - })?; - - Ok(prost::Message::decode(Bytes::from(input))?) - } -} - -#[pin_project] -pub struct ProvisioningPipe { - ws: WS, - #[pin] - stream: WS::Stream, - provisioning_cipher: ProvisioningCipher, -} - -#[derive(Debug)] -pub enum ProvisioningStep { - Url(Url), - Message(ProvisionMessage), -} - -impl ProvisioningPipe { - pub fn from_socket( - ws: WS, - stream: WS::Stream, - ctx: &Context, - ) -> Result { - Ok(ProvisioningPipe { - ws, - stream, - provisioning_cipher: ProvisioningCipher::new(ctx.clone())?, - }) - } - - async fn send_ok_response( - &mut self, - id: Option, - ) -> Result<(), ProvisioningError> { - self.send_response(WebSocketResponseMessage { - id, - status: Some(200), - message: Some("OK".into()), - body: None, - headers: vec![], - }) - .await - } - - async fn send_response( - &mut self, - r: WebSocketResponseMessage, - ) -> Result<(), ProvisioningError> { - let msg = WebSocketMessage { - r#type: Some(web_socket_message::Type::Response.into()), - response: Some(r), - ..Default::default() - }; - let mut buffer = BytesMut::with_capacity(msg.encoded_len()); - msg.encode(&mut buffer).unwrap(); - Ok(self.ws.send_message(buffer.into()).await?) - } - - /// Worker task that - async fn run( - mut self, - mut sink: Sender>, - ) -> Result<(), mpsc::SendError> { - use futures::future::LocalBoxFuture; - - // This is a runtime-agnostic, poor man's `::spawn(Future)`. - let mut background_work = FuturesUnordered::>::new(); - // a pending task is added, as to never end the background worker until - // it's dropped. - background_work.push(futures::future::pending().boxed_local()); - - loop { - futures::select! { - // WebsocketConnection::onMessage(ByteString) - frame = self.stream.next() => match frame { - Some(WebSocketStreamItem::Message(frame)) => { - let env = self.process_frame(frame).await.transpose(); - if let Some(env) = env { - sink.send(env).await?; - } - }, - // TODO: implement keep-alive? - Some(WebSocketStreamItem::KeepAliveRequest) => continue, - None => break, - }, - _ = background_work.next() => { - // no op - }, - complete => { - log::info!("select! complete"); - } - } - } - - Ok(()) - } - - async fn process_frame( - &mut self, - frame: Bytes, - ) -> Result, ProvisioningError> { - let msg = WebSocketMessage::decode(frame)?; - use web_socket_message::Type; - match (msg.r#type(), msg.request, msg.response) { - (Type::Request, Some(request), _) => { - match request { - // step 1: we get a ProvisioningUUID that we need to build a - // registration link - WebSocketRequestMessage { - id, - verb, - path, - body, - .. - } if verb == Some("PUT".into()) - && path == Some("/v1/address".into()) => - { - let uuid: ProvisioningUuid = - prost::Message::decode(Bytes::from(body.unwrap()))?; - let mut provisioning_url = Url::parse("tsdevice://") - .map_err(|e| ProvisioningError::WsError { - reason: e.to_string(), - })?; - provisioning_url - .query_pairs_mut() - .append_pair("uuid", &uuid.uuid.unwrap()) - .append_pair( - "pub_key", - &format!( - "{}", - self.provisioning_cipher.public_key() - ), - ); - - // acknowledge - self.send_ok_response(id).await?; - - Ok(Some(ProvisioningStep::Url(provisioning_url))) - } - // step 2: once the QR code is scanned by the (already - // validated) main device - // we get a ProvisionMessage, that contains a bunch of - // useful things - WebSocketRequestMessage { - id, - verb, - path, - body, - .. - } if verb == Some("PUT".into()) - && path == Some("/v1/message".into()) => - { - let provision_envelope: ProvisionEnvelope = - prost::Message::decode(Bytes::from(body.unwrap()))?; - let provision_message = self - .provisioning_cipher - .decrypt(provision_envelope)?; - - // acknowledge - self.send_ok_response(id).await?; - - Ok(Some(ProvisioningStep::Message(provision_message))) - } - _ => Err(ProvisioningError::WsError { - reason: "Incorrect request".into(), - }), - } - } - _ => Err(ProvisioningError::WsError { - reason: "Incorrect request".into(), - }), - } - } - - pub fn stream( - self, - ) -> impl Stream> { - let (sink, stream) = mpsc::channel(1); - - let stream = stream.map(Some); - let runner = self.run(sink).map(|_| { - log::info!("Sink closed, provisioning is done!"); - None - }); - - let combined = futures::stream::select(stream, runner.into_stream()); - combined.filter_map(|x| async { x }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn encrypt_provisioning_roundtrip() { - let ctx = Context::default(); - let cipher = ProvisioningCipher::new(ctx.clone()).unwrap(); - let encrypt_cipher = - ProvisioningCipher::from_public(ctx.clone(), cipher.public_key()); - - assert_eq!( - cipher.public_key(), - encrypt_cipher.public_key(), - "copy public key" - ); - - let msg = ProvisionMessage::default(); - let encrypted = encrypt_cipher.encrypt(msg.clone()).unwrap(); - - assert!(matches!( - encrypt_cipher.decrypt(encrypted.clone()), - Err(ProvisioningError::EncryptOnlyProvisioningCipher) - )); - - let decrypted = cipher.decrypt(encrypted).expect("decryptability"); - assert_eq!(msg, decrypted); - } -} diff --git a/libsignal-service/src/provisioning/cipher.rs b/libsignal-service/src/provisioning/cipher.rs index 5522e89b2..a43e1a81d 100644 --- a/libsignal-service/src/provisioning/cipher.rs +++ b/libsignal-service/src/provisioning/cipher.rs @@ -1,3 +1,5 @@ +use std::fmt::{self, Debug}; + use aes::Aes256; use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; use bytes::Bytes; @@ -6,10 +8,7 @@ use prost::Message; use rand::Rng; use sha2::Sha256; -use libsignal_protocol::{ - keys::{KeyPair, PublicKey}, - Context, -}; +use libsignal_protocol::{KeyPair, PublicKey}; pub use crate::proto::{ ProvisionEnvelope, ProvisionMessage, ProvisioningVersion, @@ -20,17 +19,31 @@ use crate::{ provisioning::ProvisioningError, }; -#[derive(Debug)] enum CipherMode { DecryptAndEncrypt(KeyPair), EncryptOnly(PublicKey), } +impl Debug for CipherMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + CipherMode::DecryptAndEncrypt(key_pair) => f + .debug_tuple("CipherMode::DecryptAndEncrypt") + .field(&key_pair.public_key) + .finish(), + CipherMode::EncryptOnly(public) => f + .debug_tuple("CipherMode::EncryptOnly") + .field(&public) + .finish(), + } + } +} + impl CipherMode { - fn public(&self) -> PublicKey { + fn public(&self) -> &PublicKey { match self { - CipherMode::DecryptAndEncrypt(pair) => pair.public(), - CipherMode::EncryptOnly(pub_key) => pub_key.clone(), + CipherMode::DecryptAndEncrypt(pair) => &pair.public_key, + CipherMode::EncryptOnly(pub_key) => &pub_key, } } } @@ -39,34 +52,34 @@ const VERSION: u8 = 1; #[derive(Debug)] pub struct ProvisioningCipher { - ctx: Context, key_material: CipherMode, } impl ProvisioningCipher { - pub fn new(ctx: Context) -> Result { - let key_pair = libsignal_protocol::generate_key_pair(&ctx)?; + /// Generate a random key pair + pub fn generate(rng: &mut R) -> Result + where + R: rand::Rng + rand::CryptoRng, + { + let key_pair = libsignal_protocol::KeyPair::generate(rng); Ok(Self { - ctx, key_material: CipherMode::DecryptAndEncrypt(key_pair), }) } - pub fn from_public(ctx: Context, key: PublicKey) -> Self { + pub fn from_public(key: PublicKey) -> Self { Self { - ctx, key_material: CipherMode::EncryptOnly(key), } } - pub fn from_key_pair(ctx: Context, key_pair: KeyPair) -> Self { + pub fn from_key_pair(key_pair: KeyPair) -> Self { Self { - ctx, key_material: CipherMode::DecryptAndEncrypt(key_pair), } } - pub fn public_key(&self) -> PublicKey { + pub fn public_key(&self) -> &PublicKey { self.key_material.public() } @@ -81,17 +94,14 @@ impl ProvisioningCipher { }; let mut rng = rand::thread_rng(); - let our_key_pair = libsignal_protocol::generate_key_pair(&self.ctx)?; - let agreement = self - .public_key() - .calculate_agreement(&our_key_pair.private())?; - let hkdf = libsignal_protocol::create_hkdf(&self.ctx, 3)?; + let our_key_pair = libsignal_protocol::KeyPair::generate(&mut rng); + let agreement = our_key_pair.calculate_agreement(self.public_key())?; + let hkdf = libsignal_protocol::HKDF::new(3)?; let shared_secrets = hkdf.derive_secrets( - 64, &agreement, - &[], b"TextSecure Provisioning Message", + 64, )?; let aes_key = &shared_secrets[0..32]; @@ -115,9 +125,7 @@ impl ProvisioningCipher { .collect(); Ok(ProvisionEnvelope { - public_key: Some( - our_key_pair.public().to_bytes()?.as_slice().to_vec(), - ), + public_key: Some(our_key_pair.public_key.serialize().into()), body: Some(body), }) } @@ -132,8 +140,7 @@ impl ProvisioningCipher { return Err(ProvisioningError::EncryptOnlyProvisioningCipher); } }; - let master_ephemeral = PublicKey::decode_point( - &self.ctx, + let master_ephemeral = PublicKey::deserialize( &provision_envelope.public_key.expect("no public key"), )?; let body = provision_envelope @@ -152,15 +159,13 @@ impl ProvisioningCipher { debug_assert_eq!(iv.len(), IV_LENGTH); debug_assert_eq!(mac.len(), 32); - let agreement = - master_ephemeral.calculate_agreement(&key_pair.private())?; - let hkdf = libsignal_protocol::create_hkdf(&self.ctx, 3)?; + let agreement = key_pair.calculate_agreement(&master_ephemeral)?; + let hkdf = libsignal_protocol::HKDF::new(3)?; let shared_secrets = hkdf.derive_secrets( - 64, &agreement, - &[], b"TextSecure Provisioning Message", + 64, )?; let parts1 = &shared_secrets[0..32]; @@ -198,10 +203,10 @@ mod tests { #[test] fn encrypt_provisioning_roundtrip() { - let ctx = Context::default(); - let cipher = ProvisioningCipher::new(ctx.clone()).unwrap(); + let mut rng = rand::thread_rng(); + let cipher = ProvisioningCipher::generate(&mut rng).unwrap(); let encrypt_cipher = - ProvisioningCipher::from_public(ctx.clone(), cipher.public_key()); + ProvisioningCipher::from_public(cipher.public_key()); assert_eq!( cipher.public_key(), diff --git a/libsignal-service/src/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index 4c768adb2..06e144172 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -26,7 +26,7 @@ pub enum ProvisioningError { #[error("Service error: {0}")] ServiceError(#[from] ServiceError), #[error("libsignal-protocol error: {0}")] - ProtocolError(#[from] libsignal_protocol::Error), + ProtocolError(#[from] libsignal_protocol::error::SignalProtocolError), #[error("ProvisioningCipher in encrypt-only mode")] EncryptOnlyProvisioningCipher, } diff --git a/libsignal-service/src/provisioning/pipe.rs b/libsignal-service/src/provisioning/pipe.rs index b2bcc4af3..6f16efcb0 100644 --- a/libsignal-service/src/provisioning/pipe.rs +++ b/libsignal-service/src/provisioning/pipe.rs @@ -48,7 +48,9 @@ impl ProvisioningPipe { Ok(ProvisioningPipe { ws, stream, - provisioning_cipher: ProvisioningCipher::new(ctx.clone())?, + provisioning_cipher: ProvisioningCipher::generate( + &mut rand::thread_rng(), + )?, }) } @@ -150,9 +152,10 @@ impl ProvisioningPipe { .append_pair("uuid", &uuid.uuid.unwrap()) .append_pair( "pub_key", - &format!( - "{}", - self.provisioning_cipher.public_key() + &base64::encode( + self.provisioning_cipher + .public_key() + .serialize(), ), ); diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index 89822793b..f1d5b80d5 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -20,7 +20,9 @@ use aes_gcm::{ use chrono::prelude::*; use prost::Message as ProtobufMessage; -use libsignal_protocol::{keys::PublicKey, Context, PreKeyBundle}; +use libsignal_protocol::{ + error::SignalProtocolError, Context, IdentityKey, PreKeyBundle, PublicKey, +}; use serde::{Deserialize, Serialize}; use zkgroup::profiles::{ProfileKeyCommitment, ProfileKeyVersion}; @@ -150,12 +152,34 @@ pub struct WhoAmIResponse { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PreKeyResponseItem { - pub device_id: i32, + pub device_id: u32, pub registration_id: u32, - pub signed_pre_key: Option, + pub signed_pre_key: SignedPreKeyEntity, pub pre_key: Option, } +impl PreKeyResponseItem { + fn into_bundle( + self, + identity: IdentityKey, + ) -> Result { + PreKeyBundle::new( + self.registration_id, + self.device_id, + self.pre_key + .map(|pk| -> Result<_, SignalProtocolError> { + Ok((pk.key_id, PublicKey::deserialize(&pk.public_key)?)) + }) + .transpose()?, + // pre_key: Option<(u32, PublicKey)>, + self.signed_pre_key.key_id, + PublicKey::deserialize(&self.signed_pre_key.public_key)?, + self.signed_pre_key.signature, + identity, + ) + } +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MismatchedDevices { @@ -244,7 +268,7 @@ pub enum ServiceError { MacError, #[error("Protocol error: {0}")] - SignalProtocolError(#[from] libsignal_protocol::error::SignalProtocolError), + SignalProtocolError(#[from] SignalProtocolError), #[error("{0:?}")] MismatchedDevicesException(MismatchedDevices), @@ -506,28 +530,9 @@ pub trait PushService { self.get_json(Endpoint::Service, &path, None).await?; assert!(!pre_key_response.devices.is_empty()); + let identity = IdentityKey::decode(&pre_key_response.identity_key)?; let device = pre_key_response.devices.remove(0); - let mut bundle = PreKeyBundle::builder() - .identity_key(&PublicKey::decode_point( - &context, - &pre_key_response.identity_key, - )?) - .device_id(device.device_id) - .registration_id(device.registration_id); - if let Some(signed_pre_key) = device.signed_pre_key { - bundle = bundle.signed_pre_key( - signed_pre_key.key_id, - &PublicKey::decode_point(&context, &signed_pre_key.public_key)?, - ); - bundle = bundle.signature(&signed_pre_key.signature); - } - if let Some(pre_key) = device.pre_key { - bundle = bundle.pre_key( - pre_key.key_id, - &PublicKey::decode_point(context, &pre_key.public_key)?, - ); - } - Ok(bundle.build()?) + Ok(device.into_bundle(identity)?) } async fn get_pre_keys( @@ -556,31 +561,9 @@ pub trait PushService { let pre_key_response: PreKeyResponse = self.get_json(Endpoint::Service, &path, None).await?; let mut pre_keys = vec![]; + let identity = IdentityKey::decode(&pre_key_response.identity_key)?; for device in pre_key_response.devices { - let mut bundle = PreKeyBundle::builder() - .identity_key(&PublicKey::decode_point( - &context, - &pre_key_response.identity_key, - )?) - .device_id(device.device_id) - .registration_id(device.registration_id); - if let Some(signed_pre_key) = device.signed_pre_key { - bundle = bundle.signed_pre_key( - signed_pre_key.key_id, - &PublicKey::decode_point( - &context, - &signed_pre_key.public_key, - )?, - ); - bundle = bundle.signature(&signed_pre_key.signature); - } - if let Some(pre_key) = device.pre_key { - bundle = bundle.pre_key( - pre_key.key_id, - &PublicKey::decode_point(context, &pre_key.public_key)?, - ); - } - pre_keys.push(bundle.build()?) + pre_keys.push(device.into_bundle(identity.clone())?); } Ok(pre_keys) } diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index 06a5be4d1..61b8f6650 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -8,10 +8,10 @@ use aes_ctr::{ use hmac::{Hmac, Mac, NewMac}; use libsignal_protocol::{ - keys::{PrivateKey, PublicKey}, + error::SignalProtocolError, messages::{CiphertextType, PreKeySignalMessage, SignalMessage}, Address as ProtocolAddress, Context, Deserializable, Serializable, - SessionCipher, StoreContext, + SessionCipher, StoreContext, {PrivateKey, PublicKey}, }; use log::error; use sha2::Sha256; @@ -42,7 +42,7 @@ pub enum SealedSessionError { EncodeError(#[from] prost::EncodeError), #[error("Protocol error {0}")] - ProtocolError(#[from] libsignal_protocol::Error), + ProtocolError(#[from] SignalProtocolError), #[error("recipient not trusted")] NoSessionWithRecipient, From 04788d8306a4bc615695f27254a0d0133f4698c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Mon, 26 Apr 2021 20:45:24 +0200 Subject: [PATCH 05/19] Progress towards using the new libsignal-protocol in Rust --- libsignal-service/Cargo.toml | 3 +- libsignal-service/src/account_manager.rs | 102 +++++--- libsignal-service/src/cipher.rs | 229 +++++++++++------ libsignal-service/src/configuration.rs | 6 +- libsignal-service/src/lib.rs | 9 + libsignal-service/src/pre_keys.rs | 29 ++- libsignal-service/src/provisioning/manager.rs | 21 +- libsignal-service/src/push_service.rs | 15 +- .../src/sealed_session_cipher.rs | 243 ++++++++++-------- libsignal-service/src/sender.rs | 216 +++++++++------- 10 files changed, 524 insertions(+), 349 deletions(-) diff --git a/libsignal-service/Cargo.toml b/libsignal-service/Cargo.toml index 18abd6ff5..72285f0c5 100644 --- a/libsignal-service/Cargo.toml +++ b/libsignal-service/Cargo.toml @@ -7,7 +7,8 @@ license = "GPLv3" readme = "../README.md" [dependencies] -libsignal-protocol = { git = "https://github.com/signalapp/libsignal-client", version = "0.1.0", tag = "v0.4.0"} +#libsignal-protocol = { git = "https://github.com/signalapp/libsignal-client", version = "0.1.0", tag = "v0.4.0"} +libsignal-protocol = { path = "../../libsignal-client/rust/protocol", version = "0.1.0", tag = "v0.4.0"} zkgroup = { git = "https://github.com/signalapp/zkgroup" } async-trait = "0.1.30" url = { version = "2.1.1", features = ["serde"] } diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 06f817029..dacea911a 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -12,14 +12,17 @@ use crate::{ }; use std::collections::HashMap; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::time::SystemTime; -use libsignal_protocol::PublicKey; - +use libsignal_protocol::{ + Context, IdentityKeyStore, KeyPair, PreKeyRecord, PreKeyStore, PublicKey, + SignedPreKeyRecord, SignedPreKeyStore, +}; use zkgroup::profiles::ProfileKey; pub struct AccountManager { + context: Context, service: Service, profile_key: Option<[u8; 32]>, } @@ -55,10 +58,16 @@ pub struct Profile { const PRE_KEY_MINIMUM: u32 = 10; const PRE_KEY_BATCH_SIZE: u32 = 100; +const PRE_KEY_MEDIUM_MAX_VALUE: u32 = 0xFFFFFF; impl AccountManager { - pub fn new(service: Service, profile_key: Option<[u8; 32]>) -> Self { + pub fn new( + context: Context, + service: Service, + profile_key: Option<[u8; 32]>, + ) -> Self { Self { + context, service, profile_key, } @@ -72,11 +81,19 @@ impl AccountManager { /// Equivalent to Java's RefreshPreKeysJob /// /// Returns the next pre-key offset and next signed pre-key offset as a tuple. - pub async fn update_pre_key_bundle( + pub async fn update_pre_key_bundle< + I: IdentityKeyStore, + P: PreKeyStore, + S: SignedPreKeyStore, + R: rand::Rng + rand::CryptoRng, + >( &mut self, - store_context: StoreContext, + identity_store: &I, + prekey_store: &mut P, + signed_prekey_store: &mut S, + csprng: &mut R, pre_keys_offset_id: u32, - next_signed_pre_key_id: u32, + signed_pre_key_id: u32, use_last_resort_key: bool, ) -> Result<(u32, u32), ServiceError> { let prekey_count = match self.service.get_pre_key_status().await { @@ -93,32 +110,54 @@ impl AccountManager { if prekey_count >= PRE_KEY_MINIMUM { log::info!("Available keys sufficient"); - return Ok((pre_keys_offset_id, next_signed_pre_key_id)); + return Ok((pre_keys_offset_id, signed_pre_key_id)); } - let pre_keys = libsignal_protocol::generate_pre_keys( - pre_keys_offset_id, - PRE_KEY_BATCH_SIZE, - )?; - let identity_key_pair = store_context.identity_key_pair()?; - let signed_pre_key = libsignal_protocol::generate_signed_pre_key( - &identity_key_pair, - next_signed_pre_key_id, - SystemTime::now(), - )?; - - store_context.store_signed_pre_key(&signed_pre_key)?; - let mut pre_key_entities = vec![]; - for pre_key in pre_keys { - store_context.store_pre_key(&pre_key)?; - pre_key_entities.push(PreKeyEntity::try_from(pre_key)?); + for i in 0..PRE_KEY_BATCH_SIZE { + let key_pair = KeyPair::generate(csprng); + let pre_key_id = + ((pre_keys_offset_id + i) % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) + 1; + let pre_key_record = PreKeyRecord::new(pre_key_id, &key_pair); + prekey_store + .save_pre_key(pre_key_id, &pre_key_record, None) + .await?; + + pre_key_entities.push(PreKeyEntity::try_from(pre_key_record)?); } + // Generate and store the next signed prekey + let identity_key_pair = + identity_store.get_identity_key_pair(None).await?; + let signed_pre_key_pair = KeyPair::generate(csprng); + let signed_pre_key_public = signed_pre_key_pair.public_key; + let signed_pre_key_signature = identity_key_pair + .private_key() + .calculate_signature(&signed_pre_key_public.serialize(), csprng)?; + + let unix_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("clock went backwards before UNIX EPOCH"); + + let signed_prekey_record = SignedPreKeyRecord::new( + signed_pre_key_id + 1, + unix_time.as_secs(), + &signed_pre_key_pair, + &signed_pre_key_signature, + ); + + signed_prekey_store + .save_signed_pre_key( + signed_pre_key_id + 1, + &signed_prekey_record, + None, + ) + .await?; + let pre_key_state = PreKeyState { pre_keys: pre_key_entities, - signed_pre_key: signed_pre_key.into(), - identity_key: identity_key_pair.public(), + signed_pre_key: signed_prekey_record.try_into()?, + identity_key: identity_key_pair.public_key().clone(), last_resort_key: if use_last_resort_key { Some(PreKeyEntity { key_id: 0x7fffffff, @@ -134,7 +173,7 @@ impl AccountManager { log::trace!("Successfully refreshed prekeys"); Ok(( pre_keys_offset_id + PRE_KEY_BATCH_SIZE, - next_signed_pre_key_id + 1, + signed_pre_key_id + 1, )) } @@ -201,7 +240,7 @@ impl AccountManager { pub async fn link_device( &mut self, url: url::Url, - store_context: StoreContext, + identity_store: &dyn IdentityKeyStore, credentials: ServiceCredentials, ) -> Result<(), LinkError> { let query: HashMap<_, _> = url.query_pairs().collect(); @@ -213,7 +252,8 @@ impl AccountManager { let pub_key = PublicKey::deserialize(&pub_key) .map_err(|_e| LinkError::InvalidPublicKey)?; - let identity_key_pair = store_context.identity_key_pair()?; + let identity_key_pair = + identity_store.get_identity_key_pair(None).await?; if credentials.uuid.is_none() { log::warn!("No local UUID set"); @@ -223,10 +263,10 @@ impl AccountManager { let msg = ProvisionMessage { identity_key_public: Some( - identity_key_pair.public().to_bytes()?.as_slice().to_vec(), + identity_key_pair.public_key().serialize().to_vec(), ), identity_key_private: Some( - identity_key_pair.private().to_bytes()?.as_slice().to_vec(), + identity_key_pair.private_key().serialize().to_vec(), ), number: Some(credentials.e164()), uuid: credentials.uuid.as_ref().map(|u| u.to_string()), diff --git a/libsignal-service/src/cipher.rs b/libsignal-service/src/cipher.rs index 0c81c6611..08bf32bc6 100644 --- a/libsignal-service/src/cipher.rs +++ b/libsignal-service/src/cipher.rs @@ -10,40 +10,66 @@ use crate::{ ServiceAddress, }; +use block_modes::block_padding::{Iso7816, Padding}; use libsignal_protocol::{ - messages::{CiphertextType, PreKeySignalMessage, SignalMessage}, - Address as ProtocolAddress, Context, Deserializable, Serializable, - SessionCipher, StoreContext, + message_decrypt_prekey, message_decrypt_signal, message_encrypt, + CiphertextMessage, CiphertextMessageType, Context, IdentityKeyStore, + PreKeySignalMessage, PreKeyStore, ProtocolAddress, SessionStore, + SignalMessage, SignalProtocolError, SignedPreKeyStore, }; - -use block_modes::block_padding::{Iso7816, Padding}; use prost::Message; +use std::convert::TryFrom; + /// Decrypts incoming messages and encrypts outgoing messages. /// /// Equivalent of SignalServiceCipher in Java. #[derive(Clone)] -pub struct ServiceCipher { - pub(crate) context: Context, - pub(crate) store_context: StoreContext, +pub struct ServiceCipher +where + S: SessionStore, + I: IdentityKeyStore, + SP: SignedPreKeyStore, + P: PreKeyStore, +{ + context: Context, + pub(crate) session_store: S, + pub(crate) identity_key_store: I, + pub(crate) signed_pre_key_store: SP, + pub(crate) pre_key_store: P, pub(crate) local_address: ServiceAddress, - sealed_session_cipher: SealedSessionCipher, + sealed_session_cipher: SealedSessionCipher, } -impl ServiceCipher { +impl ServiceCipher +where + S: SessionStore + Clone, + I: IdentityKeyStore + Clone, + SP: SignedPreKeyStore + Clone, + P: PreKeyStore + Clone, +{ pub fn from_context( context: Context, - store_context: StoreContext, + session_store: S, + identity_key_store: I, + signed_pre_key_store: SP, + pre_key_store: P, local_address: ServiceAddress, certificate_validator: CertificateValidator, ) -> Self { Self { context: context.clone(), - store_context: store_context.clone(), + session_store: session_store.clone(), + identity_key_store: identity_key_store.clone(), + signed_pre_key_store: signed_pre_key_store.clone(), + pre_key_store: pre_key_store.clone(), local_address: local_address.clone(), sealed_session_cipher: SealedSessionCipher::new( context, - store_context, + session_store, + identity_key_store, + signed_pre_key_store, + pre_key_store, local_address, certificate_validator, ), @@ -53,17 +79,17 @@ impl ServiceCipher { /// Opens ("decrypts") an envelope. /// /// Envelopes may be empty, in which case this method returns `Ok(None)` - pub fn open_envelope( + pub async fn open_envelope( &mut self, envelope: Envelope, ) -> Result, ServiceError> { if envelope.legacy_message.is_some() { - let plaintext = self.decrypt(&envelope)?; + let plaintext = self.decrypt(&envelope).await?; let message = crate::proto::DataMessage::decode(plaintext.data.as_slice())?; Ok(Some(Content::from_body(message, plaintext.metadata))) } else if envelope.content.is_some() { - let plaintext = self.decrypt(&envelope)?; + let plaintext = self.decrypt(&envelope).await?; let message = crate::proto::Content::decode(plaintext.data.as_slice())?; Ok(Content::from_proto(message, plaintext.metadata)) @@ -77,7 +103,7 @@ impl ServiceCipher { /// Triage of legacy messages happens inside this method, as opposed to the /// Java implementation, because it makes the borrow checker and the /// author happier. - fn decrypt( + async fn decrypt( &mut self, envelope: &Envelope, ) -> Result { @@ -97,61 +123,92 @@ impl ServiceCipher { let plaintext = match envelope.r#type() { Type::PrekeyBundle => { let sender = get_preferred_protocol_address( - &self.store_context, - envelope.source_address(), + None, + &self.session_store, + &envelope.source_address(), envelope.source_device(), - )?; + ) + .await?; let metadata = Metadata { sender: envelope.source_address(), sender_device: envelope.source_device(), timestamp: envelope.timestamp(), needs_receipt: false, }; - let cipher = SessionCipher::new( - &self.context, - &self.store_context, + + // FIXME: what + let mut csprng = rand::rngs::OsRng; + + let mut data = message_decrypt_prekey( + &PreKeySignalMessage::try_from(&ciphertext[..]).unwrap(), &sender, - )?; - let mut data = cipher - .decrypt_pre_key_message( - &PreKeySignalMessage::deserialize( - &self.context, - ciphertext, - )?, - )? - .as_slice() - .to_vec(); - let version = - self.store_context.load_session(&sender)?.state().version(); - strip_padding(version, &mut data)?; + &mut self.session_store, + &mut self.identity_key_store, + &mut self.pre_key_store, + &mut self.signed_pre_key_store, + &mut csprng, + None, + ) + .await? + .as_slice() + .to_vec(); + + let session_record = self + .session_store + .load_session(&sender, None) + .await? + .ok_or_else(|| { + SignalProtocolError::SessionNotFound(format!( + "{}", + sender + )) + })?; + + strip_padding(session_record.session_version()?, &mut data)?; Plaintext { metadata, data } } Type::Ciphertext => { let sender = get_preferred_protocol_address( - &self.store_context, - envelope.source_address(), + None, + &self.session_store, + &envelope.source_address(), envelope.source_device(), - )?; + ) + .await?; let metadata = Metadata { sender: envelope.source_address(), sender_device: envelope.source_device(), timestamp: envelope.timestamp(), needs_receipt: false, }; - let mut data = SessionCipher::new( - &self.context, - &self.store_context, + + // FIXME: what + let mut csprng = rand::rngs::OsRng; + + let mut data = message_decrypt_signal( + &SignalMessage::try_from(&ciphertext[..])?, &sender, - )? - .decrypt_message(&SignalMessage::deserialize( - &self.context, - ciphertext, - )?)? + &mut self.session_store, + &mut self.identity_key_store, + &mut csprng, + None, + ) + .await? .as_slice() .to_vec(); - let version = - self.store_context.load_session(&sender)?.state().version(); - strip_padding(version, &mut data)?; + + let session_record = self + .session_store + .load_session(&sender, None) + .await? + .ok_or_else(|| { + SignalProtocolError::SessionNotFound(format!( + "{}", + sender + )) + })?; + + strip_padding(session_record.session_version()?, &mut data)?; Plaintext { metadata, data } } Type::UnidentifiedSender => { @@ -163,7 +220,8 @@ impl ServiceCipher { version, } = self .sealed_session_cipher - .decrypt(ciphertext, envelope.timestamp())?; + .decrypt(ciphertext, envelope.timestamp()) + .await?; let sender = ServiceAddress { phonenumber: sender_e164, uuid: sender_uuid, @@ -191,8 +249,8 @@ impl ServiceCipher { Ok(plaintext) } - pub(crate) fn encrypt( - &self, + pub(crate) async fn encrypt( + &mut self, address: &ProtocolAddress, unindentified_access: Option<&UnidentifiedAccess>, content: &[u8], @@ -200,27 +258,35 @@ impl ServiceCipher { if unindentified_access.is_some() { unimplemented!("unidentified access is not implemented"); } else { - let session_cipher = SessionCipher::new( - &self.context, - &self.store_context, - address, - )?; + let session_record = self + .session_store + .load_session(&address, None) + .await? + .ok_or_else(|| { + SignalProtocolError::SessionNotFound(format!("{}", address)) + })?; let padded_content = - add_padding(session_cipher.get_session_version()?, content)?; - let message = session_cipher.encrypt(&padded_content)?; + add_padding(session_record.session_version()?, content)?; + + let message = message_encrypt( + &padded_content, + &address, + &mut self.session_store, + &mut self.identity_key_store, + None, + ) + .await?; let destination_registration_id = - session_cipher.get_remote_registration_id()?; - let body = base64::encode(message.serialize()?); + session_record.remote_registration_id()?; + + let body = base64::encode(message.serialize()); + use crate::proto::envelope::Type; - let message_type = match message.get_type().map_err(|_| { - ServiceError::InvalidFrameError { - reason: "unknown message type".into(), - } - })? { - CiphertextType::PreKey => Type::PrekeyBundle, - CiphertextType::Signal => Type::Ciphertext, + let message_type = match message.message_type() { + CiphertextMessageType::PreKey => Type::PrekeyBundle, + CiphertextMessageType::Whisper => Type::Ciphertext, t => panic!("Bad type: {:?}", t), } as u32; Ok(OutgoingPushMessage { @@ -290,20 +356,29 @@ fn strip_padding( } /// Equivalent of `SignalServiceCipher::getPreferredProtocolAddress` -pub fn get_preferred_protocol_address( - store_context: &StoreContext, - address: ServiceAddress, +pub async fn get_preferred_protocol_address( + context: Context, + session_store: &dyn SessionStore, + address: &ServiceAddress, device_id: u32, ) -> Result { if let Some(ref uuid) = address.uuid { - let address = ProtocolAddress::new(uuid.to_string(), device_id as i32); - if store_context.contains_session(&address)? { + let address = ProtocolAddress::new(uuid.to_string(), device_id); + if session_store + .load_session(&address, context) + .await? + .is_some() + { return Ok(address); } } if let Some(e164) = address.e164() { - let address = ProtocolAddress::new(e164, device_id as i32); - if store_context.contains_session(&address)? { + let address = ProtocolAddress::new(e164, device_id); + if session_store + .load_session(&address, context) + .await? + .is_some() + { return Ok(address); } if cfg!(feature = "prefer-e164") { @@ -318,5 +393,5 @@ pub fn get_preferred_protocol_address( ); } - Ok(ProtocolAddress::new(address.identifier(), device_id as i32)) + Ok(ProtocolAddress::new(address.identifier(), device_id)) } diff --git a/libsignal-service/src/configuration.rs b/libsignal-service/src/configuration.rs index 305725497..a2dd49de9 100644 --- a/libsignal-service/src/configuration.rs +++ b/libsignal-service/src/configuration.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, str::FromStr}; -use libsignal_protocol::{keys::PublicKey, Context}; +use libsignal_protocol::{Context, PublicKey}; use url::Url; use zkgroup::ServerPublicParams; @@ -155,10 +155,8 @@ impl From for ServiceConfiguration { impl ServiceConfiguration { pub fn credentials_validator( &self, - context: &Context, ) -> Result { - Ok(CertificateValidator::new(PublicKey::decode_point( - context, + Ok(CertificateValidator::new(PublicKey::deserialize( &base64::decode(&self.unidentified_sender_trust_root) .map_err(|_| SealedSessionError::InvalidCertificate)?, )?)) diff --git a/libsignal-service/src/lib.rs b/libsignal-service/src/lib.rs index 973803c4e..aa2c944ec 100644 --- a/libsignal-service/src/lib.rs +++ b/libsignal-service/src/lib.rs @@ -57,4 +57,13 @@ pub mod prelude { pub use prost::Message as ProtobufMessage; pub use uuid::{Error as UuidError, Uuid}; pub use zkgroup::groups::{GroupMasterKey, GroupSecretParams}; + + pub mod protocol { + pub use libsignal_protocol::{ + Context, Direction, IdentityKey, IdentityKeyPair, IdentityKeyStore, + KeyPair, PreKeyRecord, PreKeyStore, PrivateKey, ProtocolAddress, + PublicKey, SessionRecord, SessionStore, SignalProtocolError, + SignedPreKeyRecord, SignedPreKeyStore, + }; + } } diff --git a/libsignal-service/src/pre_keys.rs b/libsignal-service/src/pre_keys.rs index 59182a5b3..5ed0aed7a 100644 --- a/libsignal-service/src/pre_keys.rs +++ b/libsignal-service/src/pre_keys.rs @@ -2,8 +2,7 @@ use std::convert::TryFrom; use crate::utils::{serde_base64, serde_public_key}; use libsignal_protocol::{ - keys::{PreKey, PublicKey, SessionSignedPreKey}, - Error, + error::SignalProtocolError, PreKeyRecord, PublicKey, SignedPreKeyRecord, }; use serde::{Deserialize, Serialize}; @@ -16,13 +15,13 @@ pub struct PreKeyEntity { pub public_key: Vec, } -impl TryFrom for PreKeyEntity { - type Error = Error; +impl TryFrom for PreKeyEntity { + type Error = SignalProtocolError; - fn try_from(key: PreKey) -> Result { + fn try_from(key: PreKeyRecord) -> Result { Ok(PreKeyEntity { - key_id: key.id(), - public_key: key.key_pair().public().to_bytes()?.as_slice().to_vec(), + key_id: key.id()?, + public_key: key.key_pair()?.public_key.serialize().to_vec(), }) } } @@ -47,13 +46,15 @@ pub struct SignedPreKey { signature: Vec, } -impl From for SignedPreKey { - fn from(key: SessionSignedPreKey) -> SignedPreKey { - SignedPreKey { - key_id: key.id(), - public_key: key.key_pair().public(), - signature: key.signature().to_vec(), - } +impl TryFrom for SignedPreKey { + type Error = SignalProtocolError; + + fn try_from(key: SignedPreKeyRecord) -> Result { + Ok(SignedPreKey { + key_id: key.id()?, + public_key: key.key_pair()?.public_key, + signature: key.signature()?, + }) } } diff --git a/libsignal-service/src/provisioning/manager.rs b/libsignal-service/src/provisioning/manager.rs index c67944995..fbc5f6cef 100644 --- a/libsignal-service/src/provisioning/manager.rs +++ b/libsignal-service/src/provisioning/manager.rs @@ -1,4 +1,5 @@ use futures::{channel::mpsc::Sender, pin_mut, SinkExt, StreamExt}; +use libsignal_protocol::{Context, PrivateKey, PublicKey}; use phonenumber::PhoneNumber; use serde::{Deserialize, Serialize}; use url::Url; @@ -9,12 +10,6 @@ use super::{ ProvisioningError, }; -use libsignal_protocol::{ - generate_registration_id, - keys::{PrivateKey, PublicKey}, - Context, -}; - use crate::{ configuration::{Endpoint, ServiceConfiguration, SignalingKey}, messagepipe::ServiceCredentials, @@ -284,9 +279,10 @@ impl LinkingManager

{ } } - pub async fn provision_secondary_device( + pub async fn provision_secondary_device( &mut self, - ctx: &Context, + ctx: Context, + csprng: &mut R, signaling_key: SignalingKey, device_name: &str, mut tx: Sender, @@ -297,7 +293,8 @@ impl LinkingManager

{ .ws("/v1/websocket/provisioning/", None) .await?; - let registration_id = generate_registration_id(&ctx, 0)?; + // see libsignal-protocol-c / signal_protocol_key_helper_generate_registration_id + let registration_id = csprng.gen_range(1, 16380); let provisioning_pipe = ProvisioningPipe::from_socket(ws, stream, &ctx)?; @@ -324,8 +321,7 @@ impl LinkingManager

{ }) })?; - let public_key = PublicKey::decode_point( - &ctx, + let public_key = PublicKey::deserialize( &message.identity_key_public.ok_or( ProvisioningError::InvalidData { reason: "missing public key".into(), @@ -333,8 +329,7 @@ impl LinkingManager

{ )?, )?; - let private_key = PrivateKey::decode_point( - &ctx, + let private_key = PrivateKey::deserialize( &message.identity_key_private.ok_or( ProvisioningError::InvalidData { reason: "missing public key".into(), diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index 5f8396e0f..95c4c4bf7 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -12,18 +12,15 @@ use crate::{ ServiceAddress, }; -use libsignal_protocol::{keys::PublicKey, Context, PreKeyBundle}; - use aes_gcm::{ aead::{generic_array::GenericArray, Aead}, Aes256Gcm, NewAead, }; use chrono::prelude::*; -use prost::Message as ProtobufMessage; - use libsignal_protocol::{ error::SignalProtocolError, Context, IdentityKey, PreKeyBundle, PublicKey, }; +use prost::Message as ProtobufMessage; use serde::{Deserialize, Serialize}; use uuid::Uuid; use zkgroup::profiles::{ProfileKeyCommitment, ProfileKeyVersion}; @@ -70,7 +67,7 @@ pub const DEFAULT_DEVICE_ID: u32 = 1; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DeviceId { - pub device_id: i32, + pub device_id: u32, } #[derive(Debug, Serialize, Deserialize)] @@ -198,8 +195,8 @@ impl PreKeyResponseItem { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MismatchedDevices { - pub missing_devices: Vec, - pub extra_devices: Vec, + pub missing_devices: Vec, + pub extra_devices: Vec, } #[derive(Debug, Deserialize)] @@ -580,9 +577,8 @@ pub trait PushService { async fn get_pre_key( &mut self, - context: &Context, destination: &ServiceAddress, - device_id: i32, + device_id: u32, ) -> Result { let path = if let Some(ref relay) = destination.relay { format!( @@ -607,7 +603,6 @@ pub trait PushService { async fn get_pre_keys( &mut self, - context: &Context, destination: &ServiceAddress, device_id: u32, ) -> Result, ServiceError> { diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index 61b8f6650..7fe719576 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -1,22 +1,22 @@ -use phonenumber::PhoneNumber; -use uuid::Uuid; +use crate::{push_service::ProfileKey, ServiceAddress}; use aes_ctr::{ cipher::stream::{NewStreamCipher, StreamCipher}, Aes256Ctr, }; - use hmac::{Hmac, Mac, NewMac}; use libsignal_protocol::{ - error::SignalProtocolError, - messages::{CiphertextType, PreKeySignalMessage, SignalMessage}, - Address as ProtocolAddress, Context, Deserializable, Serializable, - SessionCipher, StoreContext, {PrivateKey, PublicKey}, + error::SignalProtocolError, message_decrypt_prekey, message_decrypt_signal, + message_encrypt, CiphertextMessageType, Context, IdentityKeyStore, KeyPair, + PreKeySignalMessage, PreKeyStore, PrivateKey, ProtocolAddress, PublicKey, + SessionStore, SignalMessage, SignedPreKeyStore, HKDF, }; use log::error; +use phonenumber::PhoneNumber; use sha2::Sha256; +use uuid::Uuid; -use crate::{push_service::ProfileKey, ServiceAddress}; +use std::convert::TryFrom; #[derive(Debug, thiserror::Error)] pub enum SealedSessionError { @@ -65,9 +65,18 @@ pub enum MacError { } #[derive(Clone)] -pub(crate) struct SealedSessionCipher { +pub(crate) struct SealedSessionCipher +where + S: SessionStore, + I: IdentityKeyStore, + SP: SignedPreKeyStore, + P: PreKeyStore, +{ context: Context, - store_context: StoreContext, + session_store: S, + identity_key_store: I, + signed_pre_key_store: SP, + pre_key_store: P, local_address: ServiceAddress, certificate_validator: CertificateValidator, } @@ -93,7 +102,7 @@ struct UnidentifiedSenderMessage { #[derive(Debug, Clone)] pub struct UnidentifiedSenderMessageContent { - r#type: CiphertextType, + r#type: CiphertextMessageType, sender_certificate: SenderCertificate, content: Vec, } @@ -160,10 +169,7 @@ impl UnidentifiedAccess { impl UnidentifiedSenderMessage { const CIPHERTEXT_VERSION: u8 = 1; - fn from_bytes( - context: &Context, - serialized: &[u8], - ) -> Result { + fn from_bytes(serialized: &[u8]) -> Result { let version = serialized[0] >> 4; if version > Self::CIPHERTEXT_VERSION { return Err(SealedSessionError::InvalidMetadataVersionError( @@ -184,10 +190,7 @@ impl UnidentifiedSenderMessage { Some(encrypted_static), Some(encrypted_message), ) => Ok(Self { - ephemeral: PublicKey::decode_point( - &context, - &ephemeral_public, - )?, + ephemeral: PublicKey::deserialize(&ephemeral_public)?, encrypted_static, encrypted_message, }), @@ -202,9 +205,7 @@ impl UnidentifiedSenderMessage { let mut buf = vec![Self::CIPHERTEXT_VERSION << 4 | Self::CIPHERTEXT_VERSION]; crate::proto::UnidentifiedSenderMessage { - ephemeral_public: Some( - self.ephemeral.to_bytes()?.as_slice().to_vec(), - ), + ephemeral_public: Some(self.ephemeral.serialize().to_vec()), encrypted_static: Some(self.encrypted_static), encrypted_message: Some(self.encrypted_message), } @@ -213,16 +214,28 @@ impl UnidentifiedSenderMessage { } } -impl SealedSessionCipher { +impl SealedSessionCipher +where + S: SessionStore, + I: IdentityKeyStore, + SP: SignedPreKeyStore, + P: PreKeyStore, +{ pub(crate) fn new( context: Context, - store_context: StoreContext, + session_store: S, + identity_key_store: I, + signed_pre_key_store: SP, + pre_key_store: P, local_address: ServiceAddress, certificate_validator: CertificateValidator, ) -> Self { Self { context, - store_context, + session_store, + identity_key_store, + signed_pre_key_store, + pre_key_store, local_address, certificate_validator, } @@ -230,44 +243,50 @@ impl SealedSessionCipher { /// unused until we make progress on https://github.com/Michael-F-Bryan/libsignal-service-rs/issues/25 /// messages from unidentified senders can only be sent via a unidentifiedPipe - #[allow(dead_code)] - pub fn encrypt( - &self, + pub async fn encrypt( + &mut self, destination: &ProtocolAddress, sender_certificate: SenderCertificate, padded_plaintext: &[u8], ) -> Result, SealedSessionError> { - let message = SessionCipher::new( - &self.context, - &self.store_context, - &destination, - )? - .encrypt(padded_plaintext)?; - - let our_identity = &self.store_context.identity_key_pair()?; + let message = message_encrypt( + padded_plaintext, + destination, + &mut self.session_store, + &mut self.identity_key_store, + None, + ) + .await?; + + let our_identity = + &self.identity_key_store.get_identity_key_pair(None).await?; let their_identity = self - .store_context - .get_identity(destination.clone())? + .identity_key_store + .get_identity(destination, None) + .await? .ok_or(SealedSessionError::NoSessionWithRecipient)?; - let ephemeral = libsignal_protocol::generate_key_pair(&self.context)?; + // FIXME: what + let mut csprng = rand::rngs::OsRng; + + let ephemeral = KeyPair::generate(&mut csprng); let ephemeral_salt = [ b"UnidentifiedDelivery", - their_identity.to_bytes()?.as_slice(), - ephemeral.public().to_bytes()?.as_slice(), + their_identity.serialize().as_ref(), + ephemeral.public_key.serialize().as_ref(), ] .concat(); let ephemeral_keys = self.calculate_ephemeral_keys( - &their_identity, - &ephemeral.private(), + &their_identity.public_key(), + &ephemeral.private_key, &ephemeral_salt, )?; let static_key_ciphertext = self.encrypt_bytes( &ephemeral_keys.cipher_key, &ephemeral_keys.mac_key, - our_identity.public().to_bytes()?.as_slice(), + &our_identity.public_key().serialize(), )?; let static_salt = [ @@ -277,15 +296,15 @@ impl SealedSessionCipher { .concat(); let static_keys = self.calculate_static_keys( - &their_identity, - &our_identity.private(), + &their_identity.public_key(), + &our_identity.private_key(), &static_salt, )?; let content = UnidentifiedSenderMessageContent { - r#type: message.get_type()?, + r#type: message.message_type(), sender_certificate, - content: message.serialize()?.as_slice().to_vec(), + content: message.serialize().to_vec(), }; let message_bytes = self.encrypt_bytes( @@ -295,32 +314,32 @@ impl SealedSessionCipher { )?; UnidentifiedSenderMessage { - ephemeral: ephemeral.public(), + ephemeral: ephemeral.public_key, encrypted_static: static_key_ciphertext, encrypted_message: message_bytes, } .into_bytes() } - pub fn decrypt( - &self, + pub async fn decrypt( + &mut self, ciphertext: &[u8], timestamp: u64, ) -> Result { - let our_identity = self.store_context.identity_key_pair()?; - let wrapper = - UnidentifiedSenderMessage::from_bytes(&self.context, ciphertext)?; + let our_identity = + self.identity_key_store.get_identity_key_pair(None).await?; + let wrapper = UnidentifiedSenderMessage::from_bytes(ciphertext)?; let ephemeral_salt = [ b"UnidentifiedDelivery", - our_identity.public().to_bytes()?.as_slice(), - wrapper.ephemeral.to_bytes()?.as_slice(), + our_identity.public_key().serialize().as_ref(), + wrapper.ephemeral.serialize().as_ref(), ] .concat(); let ephemeral_keys = self.calculate_ephemeral_keys( &wrapper.ephemeral, - &our_identity.private(), + &our_identity.private_key(), &ephemeral_salt, )?; @@ -330,13 +349,12 @@ impl SealedSessionCipher { &wrapper.encrypted_static, )?; - let static_key = - PublicKey::decode_point(&self.context, &static_key_bytes)?; + let static_key = PublicKey::deserialize(&static_key_bytes)?; let static_salt = [ephemeral_keys.chain_key, wrapper.encrypted_static].concat(); let static_keys = self.calculate_static_keys( &static_key, - &our_identity.private(), + &our_identity.private_key(), &static_salt, )?; @@ -347,13 +365,12 @@ impl SealedSessionCipher { )?; let content = UnidentifiedSenderMessageContent::try_from( - &self.context, message_bytes.as_slice(), )?; self.certificate_validator .validate(&content.sender_certificate, timestamp)?; - self.decrypt_message_content(content) + self.decrypt_message_content(content).await } fn calculate_ephemeral_keys( @@ -362,12 +379,9 @@ impl SealedSessionCipher { private_key: &PrivateKey, salt: &[u8], ) -> Result { - let ephemeral_secret = public_key.calculate_agreement(private_key)?; - let ephemeral_derived = libsignal_protocol::create_hkdf( - &self.context, - 3, - )? - .derive_secrets(96, &ephemeral_secret, salt, &[])?; + let ephemeral_secret = private_key.calculate_agreement(public_key)?; + let ephemeral_derived = + HKDF::new(3)?.derive_secrets(&ephemeral_secret, salt, 96)?; let ephemeral_keys = EphemeralKeys { chain_key: ephemeral_derived[0..32].into(), cipher_key: ephemeral_derived[32..64].into(), @@ -382,9 +396,9 @@ impl SealedSessionCipher { private_key: &PrivateKey, salt: &[u8], ) -> Result { - let static_secret = public_key.calculate_agreement(private_key)?; - let static_derived = libsignal_protocol::create_hkdf(&self.context, 3)? - .derive_secrets(96, &static_secret, salt, &[])?; + let static_secret = private_key.calculate_agreement(public_key)?; + let static_derived = + HKDF::new(3)?.derive_secrets(&static_secret, salt, 96)?; Ok(StaticKeys { cipher_key: static_derived[32..64].into(), mac_key: static_derived[64..96].into(), @@ -443,8 +457,8 @@ impl SealedSessionCipher { Ok(decrypted) } - fn decrypt_message_content( - &self, + async fn decrypt_message_content( + &mut self, message: UnidentifiedSenderMessageContent, ) -> Result { let UnidentifiedSenderMessageContent { @@ -453,29 +467,55 @@ impl SealedSessionCipher { sender_certificate, } = message; let sender = crate::cipher::get_preferred_protocol_address( - &self.store_context, - sender_certificate.address(), + None, + &self.session_store, + &sender_certificate.address(), sender_certificate.sender_device_id, - )?; - let session_cipher = - SessionCipher::new(&self.context, &self.store_context, &sender)?; + ) + .await?; + + // FIXME: what + let mut csprng = rand::rngs::OsRng; + let msg = match r#type { - CiphertextType::Signal => { - let msg = session_cipher.decrypt_message( - &SignalMessage::deserialize(&self.context, &content)?, - )?; + CiphertextMessageType::Whisper => { + let msg = message_decrypt_signal( + &SignalMessage::try_from(&content[..])?, + &sender, + &mut self.session_store, + &mut self.identity_key_store, + &mut csprng, + None, + ) + .await?; msg.as_slice().to_vec() } - CiphertextType::PreKey => { - let msg = session_cipher.decrypt_pre_key_message( - &PreKeySignalMessage::deserialize(&self.context, &content)?, - )?; + CiphertextMessageType::PreKey => { + let msg = message_decrypt_prekey( + &PreKeySignalMessage::try_from(&content[..])?, + &sender, + &mut self.session_store, + &mut self.identity_key_store, + &mut self.pre_key_store, + &mut self.signed_pre_key_store, + &mut csprng, + None, + ) + .await?; msg.as_slice().to_vec() } _ => unreachable!("unknown message from unidentified sender type"), }; - let version = session_cipher.get_session_version()?; + let version = self + .session_store + .load_session(&sender, None) + .await? + .ok_or_else(|| { + SignalProtocolError::SessionNotFound(format!("{}", sender)) + })? + .session_version()?; + Ok(DecryptionResult { padded_message: msg, version, @@ -487,10 +527,7 @@ impl SealedSessionCipher { } impl UnidentifiedSenderMessageContent { - fn try_from( - context: &Context, - serialized: &[u8], - ) -> Result { + fn try_from(serialized: &[u8]) -> Result { use crate::proto::unidentified_sender_message::{self, message}; let message: unidentified_sender_message::Message = @@ -500,9 +537,11 @@ impl UnidentifiedSenderMessageContent { (Some(message_type), Some(sender_certificate), Some(content)) => { Ok(Self { r#type: match message::Type::from_i32(message_type) { - Some(message::Type::Message) => CiphertextType::Signal, + Some(message::Type::Message) => { + CiphertextMessageType::Whisper + } Some(message::Type::PrekeyMessage) => { - CiphertextType::PreKey + CiphertextMessageType::PreKey } t => { return Err( @@ -513,7 +552,6 @@ impl UnidentifiedSenderMessageContent { } }, sender_certificate: SenderCertificate::try_from( - &context, sender_certificate, )?, content, @@ -532,8 +570,8 @@ impl UnidentifiedSenderMessageContent { unidentified_sender_message::Message { r#type: Some(match self.r#type { - CiphertextType::PreKey => message::Type::PrekeyMessage, - CiphertextType::Signal => message::Type::Message, + CiphertextMessageType::PreKey => message::Type::PrekeyMessage, + CiphertextMessageType::Whisper => message::Type::Message, _ => { return Err( SealedSessionError::InvalidMetadataMessageError( @@ -556,7 +594,6 @@ impl UnidentifiedSenderMessageContent { impl SenderCertificate { fn try_from( - context: &Context, wrapper: crate::proto::SenderCertificate, ) -> Result { use crate::proto::sender_certificate::Certificate; @@ -591,13 +628,8 @@ impl SenderCertificate { .transpose()?; Ok(Self { - signer: ServerCertificate::try_from( - &context, signer, - )?, - key: PublicKey::decode_point( - &context, - &identity_key, - )?, + signer: ServerCertificate::try_from(signer)?, + key: PublicKey::deserialize(&identity_key)?, sender_e164, sender_uuid, sender_device_id, @@ -624,7 +656,6 @@ impl SenderCertificate { impl ServerCertificate { fn try_from( - context: &Context, wrapper: crate::proto::ServerCertificate, ) -> Result { use crate::proto::server_certificate; @@ -637,7 +668,7 @@ impl ServerCertificate { match (server_certificate.id, server_certificate.key) { (Some(id), Some(key)) => Ok(Self { key_id: id, - key: PublicKey::decode_point(context, &key)?, + key: PublicKey::deserialize(&key)?, certificate, signature, }), diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index b3c105013..7c6c8c581 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -8,7 +8,11 @@ use crate::proto::{ }; use chrono::prelude::*; -use libsignal_protocol::SessionBuilder; +use libsignal_protocol::{ + process_prekey_bundle, Context, IdentityKeyStore, PreKeyStore, + ProtocolAddress, SessionRecord, SessionStore, SignalProtocolError, + SignedPreKeyStore, +}; use log::{info, trace}; use crate::{ @@ -22,7 +26,7 @@ pub use crate::proto::{ContactDetails, GroupDetails}; #[serde(rename_all = "camelCase")] pub struct OutgoingPushMessage { pub r#type: u32, - pub destination_device_id: i32, + pub destination_device_id: u32, pub destination_registration_id: u32, pub content: String, } @@ -66,9 +70,16 @@ pub struct AttachmentSpec { /// Equivalent of Java's `SignalServiceMessageSender`. #[derive(Clone)] -pub struct MessageSender { +pub struct MessageSender +where + S: SessionStore + Clone, + I: IdentityKeyStore + Clone, + SP: SignedPreKeyStore + Clone, + P: PreKeyStore + Clone, +{ service: Service, - cipher: ServiceCipher, + context: Context, + cipher: ServiceCipher, device_id: u32, } @@ -86,7 +97,7 @@ pub enum MessageSenderError { #[error("{0}")] ServiceError(#[from] ServiceError), #[error("protocol error: {0}")] - ProtocolError(#[from] libsignal_protocol::error::SignalProtocolError), + ProtocolError(#[from] SignalProtocolError), #[error("Failed to upload attachment {0}")] AttachmentUploadError(#[from] AttachmentUploadError), @@ -112,17 +123,23 @@ pub enum MessageSenderError { IdentityFailure { recipient: ServiceAddress }, } -impl MessageSender +impl MessageSender where Service: PushService + Clone, + S: SessionStore + Clone, + I: IdentityKeyStore + Clone, + SP: SignedPreKeyStore + Clone, + P: PreKeyStore + Clone, { pub fn new( service: Service, - cipher: ServiceCipher, + context: Context, + cipher: ServiceCipher, device_id: u32, ) -> Self { MessageSender { service, + context, cipher, device_id, } @@ -338,15 +355,16 @@ where } if end_session { - log::debug!("ending session with {}", recipient); - if let Some(ref uuid) = recipient.uuid { - self.cipher - .store_context - .delete_all_sessions(&uuid.to_string())?; - } - if let Some(e164) = recipient.e164() { - self.cipher.store_context.delete_all_sessions(&e164)?; - } + // log::debug!("ending session with {}", recipient); + // if let Some(ref uuid) = recipient.uuid { + // self.cipher + // .store_context + // .delete_all_sessions(&uuid.to_string())?; + // } + // if let Some(e164) = recipient.e164() { + // self.cipher.store_context.delete_all_sessions(&e164)?; + // } + log::warn!("deleting all sessions: unimplemented following the switch to the Rust version of libsignal-protocol"); } result @@ -461,21 +479,11 @@ where "dropping session with device {}", extra_device_id ); - if let Some(ref uuid) = recipient.uuid { - self.cipher.store_context.delete_session( - &libsignal_protocol::Address::new( - uuid.to_string(), - *extra_device_id, - ), - )?; + if let Some(_uuid) = recipient.uuid { + unimplemented!("deleting session: unimplemented following the switch to the Rust version of libsignal-protocol"); } - if let Some(e164) = recipient.e164() { - self.cipher.store_context.delete_session( - &libsignal_protocol::Address::new( - &e164, - *extra_device_id, - ), - )?; + if let Some(_e164) = recipient.e164() { + unimplemented!("deleting session: unimplemented following the switch to the Rust version of libsignal-protocol"); } } @@ -484,23 +492,27 @@ where "creating session with missing device {}", missing_device_id ); + let remote_address = ProtocolAddress::new( + recipient.identifier(), + *missing_device_id, + ); let pre_key = self .service - .get_pre_key( - &self.cipher.context, - &recipient, - *missing_device_id, - ) + .get_pre_key(&recipient, *missing_device_id) .await?; - SessionBuilder::new( - &self.cipher.context, - &self.cipher.store_context, - &libsignal_protocol::Address::new( - &recipient.identifier(), - *missing_device_id, - ), + + // FIXME: what + let mut csprng = rand::rngs::OsRng; + + process_prekey_bundle( + &remote_address, + &mut self.cipher.session_store, + &mut self.cipher.identity_key_store, + &pre_key, + &mut csprng, + None, ) - .process_pre_key_bundle(&pre_key) + .await .map_err(|e| { log::error!("failed to create session: {}", e); MessageSenderError::UntrustedIdentity { @@ -517,20 +529,19 @@ where extra_device_id ); if let Some(ref uuid) = recipient.uuid { - self.cipher.store_context.delete_session( - &libsignal_protocol::Address::new( - uuid.to_string(), - *extra_device_id, - ), - )?; + log::warn!("deleting session: unimplemented following the switch to the Rust version of libsignal-protocol"); + // self.cipher.store_context.delete_session( + // &ProtocolAddress::new( + // uuid.to_string(), + // *extra_device_id, + // ), + // )?; } if let Some(e164) = recipient.e164() { - self.cipher.store_context.delete_session( - &libsignal_protocol::Address::new( - e164, - *extra_device_id, - ), - )?; + log::warn!("deleting session: unimplemented following the switch to the Rust version of libsignal-protocol"); + // self.cipher.store_context.delete_session( + // &ProtocolAddress::new(e164, *extra_device_id), + // )?; } } } @@ -636,28 +647,37 @@ where // XXX maybe refactor this in a method, this is probably something we need on every call to // get_sub_device_sessions. + // FIXME: re-implement sub device sessions let mut sub_device_sessions = Vec::new(); - if let Some(uuid) = &recipient.uuid { - sub_device_sessions.extend( - self.cipher - .store_context - .get_sub_device_sessions(&uuid.to_string())?, - ); - } - if let Some(e164) = &recipient.e164() { - sub_device_sessions.extend( - self.cipher.store_context.get_sub_device_sessions(&e164)?, - ); - } + // if let Some(uuid) = &recipient.uuid { + // sub_device_sessions.extend( + // self.cipher + // .session_store + // .get_sub_device_sessions(&uuid.to_string())?, + // ); + // } + // if let Some(e164) = &recipient.e164() { + // sub_device_sessions.extend( + // self.cipher.store_context.get_sub_device_sessions(&e164)?, + // ); + // } for device_id in sub_device_sessions { trace!("sending message to device {}", device_id); let ppa = get_preferred_protocol_address( - &self.cipher.store_context, - recipient.clone(), + None, + &self.cipher.session_store, + recipient, device_id, - )?; - if self.cipher.store_context.contains_session(&ppa)? { + ) + .await?; + if self + .cipher + .session_store + .load_session(&ppa, None) + .await? + .is_some() + { messages.push( self.create_encrypted_message( recipient, @@ -684,22 +704,24 @@ where content: &[u8], ) -> Result { let recipient_address = get_preferred_protocol_address( - &self.cipher.store_context, - recipient.clone(), + None, + &self.cipher.session_store, + recipient, device_id, - )?; + ) + .await?; log::trace!("encrypting message for {:?}", recipient_address); if !self .cipher - .store_context - .contains_session(&recipient_address)? + .session_store + .load_session(&recipient_address, None) + .await? + .is_some() { info!("establishing new session with {:?}", recipient_address); - let pre_keys = self - .service - .get_pre_keys(&self.cipher.context, recipient, device_id) - .await?; + let pre_keys = + self.service.get_pre_keys(&recipient, device_id).await?; for pre_key_bundle in pre_keys { if recipient.matches(&self.cipher.local_address) && self.device_id == pre_key_bundle.device_id()? @@ -709,24 +731,32 @@ where } let pre_key_address = get_preferred_protocol_address( - &self.cipher.store_context, - recipient.clone(), + None, + &self.cipher.session_store, + recipient, pre_key_bundle.device_id()?, - )?; - let session_builder = SessionBuilder::new( - &self.cipher.context, - &self.cipher.store_context, + ) + .await?; + + // FIXME: what + let mut csprng = rand::rngs::OsRng; + + process_prekey_bundle( &pre_key_address, - ); - session_builder.process_pre_key_bundle(&pre_key_bundle)?; + &mut self.cipher.session_store, + &mut self.cipher.identity_key_store, + &pre_key_bundle, + &mut csprng, + None, + ) + .await?; } } - let message = self.cipher.encrypt( - &recipient_address, - unidentified_access, - content, - )?; + let message = self + .cipher + .encrypt(&recipient_address, unidentified_access, content) + .await?; Ok(message) } From 5a3531e0557ebfe52b1830425c910502415b7aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Tue, 27 Apr 2021 10:29:34 +0200 Subject: [PATCH 06/19] Fix HKDF usage --- libsignal-service/src/groups_v2/utils.rs | 7 +++++-- libsignal-service/src/sealed_session_cipher.rs | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/libsignal-service/src/groups_v2/utils.rs b/libsignal-service/src/groups_v2/utils.rs index 98cc5889f..ff4a12414 100644 --- a/libsignal-service/src/groups_v2/utils.rs +++ b/libsignal-service/src/groups_v2/utils.rs @@ -11,8 +11,11 @@ pub fn derive_v2_migration_master_key( ) -> Result { assert_eq!(group_id.len(), 16, "Group ID must be exactly 16 bytes"); let hkdf = libsignal_protocol::HKDF::new(3)?; - let bytes = - hkdf.derive_secrets(group_id, b"GV2 Migration", GROUP_MASTER_KEY_LEN)?; + let bytes = hkdf.derive_salted_secrets( + group_id, + b"GV2 Migration", + GROUP_MASTER_KEY_LEN, + )?; let mut bytes_stack = [0u8; GROUP_MASTER_KEY_LEN]; bytes_stack.copy_from_slice(&bytes); Ok(GroupMasterKey::new(bytes_stack)) diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index 7fe719576..d5569050c 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -328,6 +328,7 @@ where ) -> Result { let our_identity = self.identity_key_store.get_identity_key_pair(None).await?; + let wrapper = UnidentifiedSenderMessage::from_bytes(ciphertext)?; let ephemeral_salt = [ @@ -380,8 +381,12 @@ where salt: &[u8], ) -> Result { let ephemeral_secret = private_key.calculate_agreement(public_key)?; - let ephemeral_derived = - HKDF::new(3)?.derive_secrets(&ephemeral_secret, salt, 96)?; + let ephemeral_derived = HKDF::new(3)?.derive_salted_secrets( + &ephemeral_secret, + salt, + &[], + 96, + )?; let ephemeral_keys = EphemeralKeys { chain_key: ephemeral_derived[0..32].into(), cipher_key: ephemeral_derived[32..64].into(), @@ -397,8 +402,12 @@ where salt: &[u8], ) -> Result { let static_secret = private_key.calculate_agreement(public_key)?; - let static_derived = - HKDF::new(3)?.derive_secrets(&static_secret, salt, 96)?; + let static_derived = HKDF::new(3)?.derive_salted_secrets( + &static_secret, + salt, + &[], + 96, + )?; Ok(StaticKeys { cipher_key: static_derived[32..64].into(), mac_key: static_derived[64..96].into(), From 839baaabd2250205b73460f3a2376c9b4cf30b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Tue, 27 Apr 2021 13:13:24 +0200 Subject: [PATCH 07/19] Use dynamic dispatch and fix tests --- libsignal-service-actix/Cargo.toml | 4 +- libsignal-service-actix/examples/link.rs | 9 +- libsignal-service/Cargo.toml | 4 +- libsignal-service/src/account_manager.rs | 10 +- libsignal-service/src/cipher.rs | 79 ++- libsignal-service/src/configuration.rs | 2 +- libsignal-service/src/groups_v2/utils.rs | 10 +- libsignal-service/src/lib.rs | 1 + libsignal-service/src/provisioning/cipher.rs | 12 +- libsignal-service/src/provisioning/manager.rs | 6 +- libsignal-service/src/provisioning/mod.rs | 6 + libsignal-service/src/provisioning/pipe.rs | 3 - libsignal-service/src/push_service.rs | 4 +- .../src/sealed_session_cipher.rs | 474 ++++++++++-------- libsignal-service/src/sender.rs | 132 +++-- libsignal-service/src/session_store.rs | 30 ++ 16 files changed, 401 insertions(+), 385 deletions(-) create mode 100644 libsignal-service/src/session_store.rs diff --git a/libsignal-service-actix/Cargo.toml b/libsignal-service-actix/Cargo.toml index a48b414f6..419721fde 100644 --- a/libsignal-service-actix/Cargo.toml +++ b/libsignal-service-actix/Cargo.toml @@ -23,7 +23,7 @@ rustls = "0.19" url = "2.1" serde = "1.0" log = "0.4" -rand = "0.8" +rand = "0.7" failure = "0.1.5" thiserror = "1.0" @@ -37,6 +37,6 @@ env_logger = "0.8" image = { version = "0.23", default-features = false, features = ["png"] } opener = "0.4" qrcode = "0.12" -rand = "0.8" +rand = "0.7" structopt = "0.3" tokio = { version = "1", features=["macros"] } diff --git a/libsignal-service-actix/examples/link.rs b/libsignal-service-actix/examples/link.rs index 0dd53f579..f7e0abebd 100644 --- a/libsignal-service-actix/examples/link.rs +++ b/libsignal-service-actix/examples/link.rs @@ -36,8 +36,7 @@ async fn main() -> Result<(), Error> { // generate a random 16 bytes password let mut rng = rand::rngs::OsRng::default(); - let password: Vec = rng.sample_iter(&Alphanumeric).take(24).collect(); - let password = String::from_utf8(password)?; + let password: String = rng.sample_iter(&Alphanumeric).take(24).collect(); // generate a 52 bytes signaling key let mut signaling_key = [0u8; 52]; @@ -47,16 +46,16 @@ async fn main() -> Result<(), Error> { base64::encode(&signaling_key.to_vec()) ); - let signal_context = Context::default(); - let mut provision_manager: LinkingManager = LinkingManager::new(args.servers, USER_AGENT.into(), password); let (tx, mut rx) = channel(1); + let mut csprng = rand::thread_rng(); + let (fut1, fut2) = future::join( provision_manager.provision_secondary_device( - &signal_context, + &mut csprng, signaling_key, &args.device_name, tx, diff --git a/libsignal-service/Cargo.toml b/libsignal-service/Cargo.toml index 72285f0c5..dc6dbec06 100644 --- a/libsignal-service/Cargo.toml +++ b/libsignal-service/Cargo.toml @@ -31,11 +31,13 @@ aes = "0.6.0" aes-gcm = "0.8.0" aes-ctr = "0.6.0" block-modes = "0.7.0" -rand = "0.7.0" +rand = "0.7" uuid = { version = "0.8", features = [ "serde" ] } phonenumber = "0.3" +tokio = { version = "1.0", features = [ "macros" ] } + [build-dependencies] prost-build = "0.7" diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index dacea911a..a7227d8ff 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -16,13 +16,12 @@ use std::convert::{TryFrom, TryInto}; use std::time::SystemTime; use libsignal_protocol::{ - Context, IdentityKeyStore, KeyPair, PreKeyRecord, PreKeyStore, PublicKey, + IdentityKeyStore, KeyPair, PreKeyRecord, PreKeyStore, PublicKey, SignedPreKeyRecord, SignedPreKeyStore, }; use zkgroup::profiles::ProfileKey; pub struct AccountManager { - context: Context, service: Service, profile_key: Option<[u8; 32]>, } @@ -61,13 +60,8 @@ const PRE_KEY_BATCH_SIZE: u32 = 100; const PRE_KEY_MEDIUM_MAX_VALUE: u32 = 0xFFFFFF; impl AccountManager { - pub fn new( - context: Context, - service: Service, - profile_key: Option<[u8; 32]>, - ) -> Self { + pub fn new(service: Service, profile_key: Option<[u8; 32]>) -> Self { Self { - context, service, profile_key, } diff --git a/libsignal-service/src/cipher.rs b/libsignal-service/src/cipher.rs index 08bf32bc6..49b6a4658 100644 --- a/libsignal-service/src/cipher.rs +++ b/libsignal-service/src/cipher.rs @@ -13,9 +13,9 @@ use crate::{ use block_modes::block_padding::{Iso7816, Padding}; use libsignal_protocol::{ message_decrypt_prekey, message_decrypt_signal, message_encrypt, - CiphertextMessage, CiphertextMessageType, Context, IdentityKeyStore, - PreKeySignalMessage, PreKeyStore, ProtocolAddress, SessionStore, - SignalMessage, SignalProtocolError, SignedPreKeyStore, + CiphertextMessageType, Context, IdentityKeyStore, PreKeySignalMessage, + PreKeyStore, ProtocolAddress, SessionStore, SignalMessage, + SignalProtocolError, SignedPreKeyStore, }; use prost::Message; @@ -24,53 +24,32 @@ use std::convert::TryFrom; /// Decrypts incoming messages and encrypts outgoing messages. /// /// Equivalent of SignalServiceCipher in Java. -#[derive(Clone)] -pub struct ServiceCipher -where - S: SessionStore, - I: IdentityKeyStore, - SP: SignedPreKeyStore, - P: PreKeyStore, -{ - context: Context, - pub(crate) session_store: S, - pub(crate) identity_key_store: I, - pub(crate) signed_pre_key_store: SP, - pub(crate) pre_key_store: P, - pub(crate) local_address: ServiceAddress, - sealed_session_cipher: SealedSessionCipher, +pub struct ServiceCipher { + session_store: Box, + identity_key_store: Box, + signed_pre_key_store: Box, + pre_key_store: Box, + sealed_session_cipher: SealedSessionCipher, } -impl ServiceCipher -where - S: SessionStore + Clone, - I: IdentityKeyStore + Clone, - SP: SignedPreKeyStore + Clone, - P: PreKeyStore + Clone, -{ - pub fn from_context( - context: Context, - session_store: S, - identity_key_store: I, - signed_pre_key_store: SP, - pre_key_store: P, - local_address: ServiceAddress, +impl ServiceCipher { + pub fn new( + session_store: impl SessionStore + Clone + 'static, + identity_key_store: impl IdentityKeyStore + Clone + 'static, + signed_pre_key_store: impl SignedPreKeyStore + Clone + 'static, + pre_key_store: impl PreKeyStore + Clone + 'static, certificate_validator: CertificateValidator, ) -> Self { Self { - context: context.clone(), - session_store: session_store.clone(), - identity_key_store: identity_key_store.clone(), - signed_pre_key_store: signed_pre_key_store.clone(), - pre_key_store: pre_key_store.clone(), - local_address: local_address.clone(), + session_store: Box::new(session_store.clone()), + identity_key_store: Box::new(identity_key_store.clone()), + signed_pre_key_store: Box::new(signed_pre_key_store.clone()), + pre_key_store: Box::new(pre_key_store.clone()), sealed_session_cipher: SealedSessionCipher::new( - context, session_store, identity_key_store, signed_pre_key_store, pre_key_store, - local_address, certificate_validator, ), } @@ -124,7 +103,7 @@ where Type::PrekeyBundle => { let sender = get_preferred_protocol_address( None, - &self.session_store, + self.session_store.as_ref(), &envelope.source_address(), envelope.source_device(), ) @@ -142,10 +121,10 @@ where let mut data = message_decrypt_prekey( &PreKeySignalMessage::try_from(&ciphertext[..]).unwrap(), &sender, - &mut self.session_store, - &mut self.identity_key_store, - &mut self.pre_key_store, - &mut self.signed_pre_key_store, + self.session_store.as_mut(), + self.identity_key_store.as_mut(), + self.pre_key_store.as_mut(), + self.signed_pre_key_store.as_mut(), &mut csprng, None, ) @@ -170,7 +149,7 @@ where Type::Ciphertext => { let sender = get_preferred_protocol_address( None, - &self.session_store, + self.session_store.as_ref(), &envelope.source_address(), envelope.source_device(), ) @@ -188,8 +167,8 @@ where let mut data = message_decrypt_signal( &SignalMessage::try_from(&ciphertext[..])?, &sender, - &mut self.session_store, - &mut self.identity_key_store, + self.session_store.as_mut(), + self.identity_key_store.as_mut(), &mut csprng, None, ) @@ -272,8 +251,8 @@ where let message = message_encrypt( &padded_content, &address, - &mut self.session_store, - &mut self.identity_key_store, + self.session_store.as_mut(), + self.identity_key_store.as_mut(), None, ) .await?; diff --git a/libsignal-service/src/configuration.rs b/libsignal-service/src/configuration.rs index a2dd49de9..8adcaff5e 100644 --- a/libsignal-service/src/configuration.rs +++ b/libsignal-service/src/configuration.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, str::FromStr}; -use libsignal_protocol::{Context, PublicKey}; +use libsignal_protocol::PublicKey; use url::Url; use zkgroup::ServerPublicParams; diff --git a/libsignal-service/src/groups_v2/utils.rs b/libsignal-service/src/groups_v2/utils.rs index ff4a12414..4864f17b4 100644 --- a/libsignal-service/src/groups_v2/utils.rs +++ b/libsignal-service/src/groups_v2/utils.rs @@ -1,4 +1,4 @@ -use libsignal_protocol::{error::SignalProtocolError, Context}; +use libsignal_protocol::error::SignalProtocolError; use zkgroup::groups::GroupMasterKey; use zkgroup::GROUP_MASTER_KEY_LEN; @@ -6,16 +6,12 @@ use zkgroup::GROUP_MASTER_KEY_LEN; /// /// Panics if the group_id is not 16 bytes long. pub fn derive_v2_migration_master_key( - ctx: &Context, group_id: &[u8], ) -> Result { assert_eq!(group_id.len(), 16, "Group ID must be exactly 16 bytes"); let hkdf = libsignal_protocol::HKDF::new(3)?; - let bytes = hkdf.derive_salted_secrets( - group_id, - b"GV2 Migration", - GROUP_MASTER_KEY_LEN, - )?; + let bytes = + hkdf.derive_secrets(group_id, b"GV2 Migration", GROUP_MASTER_KEY_LEN)?; let mut bytes_stack = [0u8; GROUP_MASTER_KEY_LEN]; bytes_stack.copy_from_slice(&bytes); Ok(GroupMasterKey::new(bytes_stack)) diff --git a/libsignal-service/src/lib.rs b/libsignal-service/src/lib.rs index aa2c944ec..501ddcd39 100644 --- a/libsignal-service/src/lib.rs +++ b/libsignal-service/src/lib.rs @@ -21,6 +21,7 @@ pub mod push_service; pub mod receiver; pub mod sender; pub mod service_address; +pub mod session_store; pub mod utils; pub use crate::account_manager::{AccountManager, Profile}; diff --git a/libsignal-service/src/provisioning/cipher.rs b/libsignal-service/src/provisioning/cipher.rs index a43e1a81d..0d55ef35d 100644 --- a/libsignal-service/src/provisioning/cipher.rs +++ b/libsignal-service/src/provisioning/cipher.rs @@ -202,11 +202,11 @@ mod tests { use super::*; #[test] - fn encrypt_provisioning_roundtrip() { + fn encrypt_provisioning_roundtrip() -> anyhow::Result<()> { let mut rng = rand::thread_rng(); - let cipher = ProvisioningCipher::generate(&mut rng).unwrap(); + let cipher = ProvisioningCipher::generate(&mut rng)?; let encrypt_cipher = - ProvisioningCipher::from_public(cipher.public_key()); + ProvisioningCipher::from_public(cipher.public_key().clone()); assert_eq!( cipher.public_key(), @@ -215,14 +215,16 @@ mod tests { ); let msg = ProvisionMessage::default(); - let encrypted = encrypt_cipher.encrypt(msg.clone()).unwrap(); + let encrypted = encrypt_cipher.encrypt(msg.clone())?; assert!(matches!( encrypt_cipher.decrypt(encrypted.clone()), Err(ProvisioningError::EncryptOnlyProvisioningCipher) )); - let decrypted = cipher.decrypt(encrypted).expect("decryptability"); + let decrypted = cipher.decrypt(encrypted)?; assert_eq!(msg, decrypted); + + Ok(()) } } diff --git a/libsignal-service/src/provisioning/manager.rs b/libsignal-service/src/provisioning/manager.rs index fbc5f6cef..a5da6eac4 100644 --- a/libsignal-service/src/provisioning/manager.rs +++ b/libsignal-service/src/provisioning/manager.rs @@ -1,5 +1,5 @@ use futures::{channel::mpsc::Sender, pin_mut, SinkExt, StreamExt}; -use libsignal_protocol::{Context, PrivateKey, PublicKey}; +use libsignal_protocol::{PrivateKey, PublicKey}; use phonenumber::PhoneNumber; use serde::{Deserialize, Serialize}; use url::Url; @@ -281,7 +281,6 @@ impl LinkingManager

{ pub async fn provision_secondary_device( &mut self, - ctx: Context, csprng: &mut R, signaling_key: SignalingKey, device_name: &str, @@ -296,8 +295,7 @@ impl LinkingManager

{ // see libsignal-protocol-c / signal_protocol_key_helper_generate_registration_id let registration_id = csprng.gen_range(1, 16380); - let provisioning_pipe = - ProvisioningPipe::from_socket(ws, stream, &ctx)?; + let provisioning_pipe = ProvisioningPipe::from_socket(ws, stream)?; let provision_stream = provisioning_pipe.stream(); pin_mut!(provision_stream); while let Some(step) = provision_stream.next().await { diff --git a/libsignal-service/src/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index 771e0cde7..b20fbb017 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -31,3 +31,9 @@ pub enum ProvisioningError { #[error("ProvisioningCipher in encrypt-only mode")] EncryptOnlyProvisioningCipher, } + +pub fn generate_registration_id( + csprng: &mut R, +) -> u32 { + csprng.gen_range(1, 16380) +} diff --git a/libsignal-service/src/provisioning/pipe.rs b/libsignal-service/src/provisioning/pipe.rs index 6f16efcb0..ccb5f2ce5 100644 --- a/libsignal-service/src/provisioning/pipe.rs +++ b/libsignal-service/src/provisioning/pipe.rs @@ -8,8 +8,6 @@ use pin_project::pin_project; use prost::Message; use url::Url; -use libsignal_protocol::Context; - pub use crate::proto::{ ProvisionEnvelope, ProvisionMessage, ProvisioningVersion, }; @@ -43,7 +41,6 @@ impl ProvisioningPipe { pub fn from_socket( ws: WS, stream: WS::Stream, - ctx: &Context, ) -> Result { Ok(ProvisioningPipe { ws, diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index 95c4c4bf7..401b2a55d 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -18,7 +18,7 @@ use aes_gcm::{ }; use chrono::prelude::*; use libsignal_protocol::{ - error::SignalProtocolError, Context, IdentityKey, PreKeyBundle, PublicKey, + error::SignalProtocolError, IdentityKey, PreKeyBundle, PublicKey, }; use prost::Message as ProtobufMessage; use serde::{Deserialize, Serialize}; @@ -202,7 +202,7 @@ pub struct MismatchedDevices { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct StaleDevices { - pub stale_devices: Vec, + pub stale_devices: Vec, } #[derive(Debug, Deserialize)] diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index d5569050c..dac083a10 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -7,7 +7,7 @@ use aes_ctr::{ use hmac::{Hmac, Mac, NewMac}; use libsignal_protocol::{ error::SignalProtocolError, message_decrypt_prekey, message_decrypt_signal, - message_encrypt, CiphertextMessageType, Context, IdentityKeyStore, KeyPair, + message_encrypt, CiphertextMessageType, IdentityKeyStore, KeyPair, PreKeySignalMessage, PreKeyStore, PrivateKey, ProtocolAddress, PublicKey, SessionStore, SignalMessage, SignedPreKeyStore, HKDF, }; @@ -64,20 +64,11 @@ pub enum MacError { BadMac, } -#[derive(Clone)] -pub(crate) struct SealedSessionCipher -where - S: SessionStore, - I: IdentityKeyStore, - SP: SignedPreKeyStore, - P: PreKeyStore, -{ - context: Context, - session_store: S, - identity_key_store: I, - signed_pre_key_store: SP, - pre_key_store: P, - local_address: ServiceAddress, +pub(crate) struct SealedSessionCipher { + session_store: Box, + identity_key_store: Box, + signed_pre_key_store: Box, + pre_key_store: Box, certificate_validator: CertificateValidator, } @@ -214,29 +205,19 @@ impl UnidentifiedSenderMessage { } } -impl SealedSessionCipher -where - S: SessionStore, - I: IdentityKeyStore, - SP: SignedPreKeyStore, - P: PreKeyStore, -{ +impl SealedSessionCipher { pub(crate) fn new( - context: Context, - session_store: S, - identity_key_store: I, - signed_pre_key_store: SP, - pre_key_store: P, - local_address: ServiceAddress, + session_store: impl SessionStore + 'static, + identity_key_store: impl IdentityKeyStore + 'static, + signed_pre_key_store: impl SignedPreKeyStore + 'static, + pre_key_store: impl PreKeyStore + 'static, certificate_validator: CertificateValidator, ) -> Self { Self { - context, - session_store, - identity_key_store, - signed_pre_key_store, - pre_key_store, - local_address, + session_store: Box::new(session_store), + identity_key_store: Box::new(identity_key_store), + signed_pre_key_store: Box::new(signed_pre_key_store), + pre_key_store: Box::new(pre_key_store), certificate_validator, } } @@ -252,8 +233,8 @@ where let message = message_encrypt( padded_plaintext, destination, - &mut self.session_store, - &mut self.identity_key_store, + self.session_store.as_mut(), + self.identity_key_store.as_mut(), None, ) .await?; @@ -477,7 +458,7 @@ where } = message; let sender = crate::cipher::get_preferred_protocol_address( None, - &self.session_store, + self.session_store.as_ref(), &sender_certificate.address(), sender_certificate.sender_device_id, ) @@ -491,8 +472,8 @@ where let msg = message_decrypt_signal( &SignalMessage::try_from(&content[..])?, &sender, - &mut self.session_store, - &mut self.identity_key_store, + self.session_store.as_mut(), + self.identity_key_store.as_mut(), &mut csprng, None, ) @@ -503,10 +484,10 @@ where let msg = message_decrypt_prekey( &PreKeySignalMessage::try_from(&content[..])?, &sender, - &mut self.session_store, - &mut self.identity_key_store, - &mut self.pre_key_store, - &mut self.signed_pre_key_store, + self.session_store.as_mut(), + self.identity_key_store.as_mut(), + self.pre_key_store.as_mut(), + self.signed_pre_key_store.as_mut(), &mut csprng, None, ) @@ -729,21 +710,17 @@ impl CertificateValidator { #[cfg(test)] mod tests { - use std::time::UNIX_EPOCH; + use std::time::{SystemTime, UNIX_EPOCH}; use libsignal_protocol::{ - self as sig, - crypto::DefaultCrypto, - keys::PreKey, - keys::{KeyPair, PublicKey}, - stores::InMemoryPreKeyStore, - stores::InMemorySessionStore, - stores::{InMemoryIdentityKeyStore, InMemorySignedPreKeyStore}, - Address as ProtocolAddress, Context, PreKeyBundle, Serializable, - SessionBuilder, StoreContext, + process_prekey_bundle, IdentityKeyPair, IdentityKeyStore, + InMemIdentityKeyStore, InMemPreKeyStore, InMemSessionStore, + InMemSignedPreKeyStore, KeyPair, PreKeyBundle, PreKeyRecord, + PreKeyStore, ProtocolAddress, PublicKey, SessionStore, + SignedPreKeyRecord, SignedPreKeyStore, }; - use crate::ServiceAddress; + use crate::{provisioning::generate_registration_id, ServiceAddress}; use super::{ CertificateValidator, SealedSessionCipher, SealedSessionError, @@ -768,43 +745,60 @@ mod tests { .unwrap() } - #[test] - fn test_encrypt_decrypt() -> anyhow::Result<()> { - let (ctx, alice_store_context, bob_store_context) = create_contexts()?; - initialize_session(&ctx, &bob_store_context, &alice_store_context)?; + struct Stores { + identity_key_store: InMemIdentityKeyStore, + session_store: InMemSessionStore, + signed_prekey_store: InMemSignedPreKeyStore, + prekey_store: InMemPreKeyStore, + } + + #[tokio::test] + async fn test_encrypt_decrypt() -> anyhow::Result<()> { + let mut csprng = rand::thread_rng(); - let trust_root = libsignal_protocol::generate_key_pair(&ctx)?; + let (alice_stores, bob_stores) = create_stores(&mut csprng).await?; + + let trust_root = KeyPair::generate(&mut csprng); let certificate_validator = - CertificateValidator::new(trust_root.public()); + CertificateValidator::new(trust_root.public_key); let sender_certificate = create_certificate_for( - &ctx, &trust_root, alice_address(), 1, - alice_store_context.identity_key_pair()?.public(), + *alice_stores + .identity_key_store + .get_identity_key_pair(None) + .await? + .public_key(), 31337, + &mut csprng, )?; - let alice_cipher = SealedSessionCipher::new( - ctx.clone(), - alice_store_context, - alice_address(), + let mut alice_cipher = SealedSessionCipher::new( + alice_stores.session_store, + alice_stores.identity_key_store, + alice_stores.signed_prekey_store, + alice_stores.prekey_store, certificate_validator.clone(), ); - let ciphertext = alice_cipher.encrypt( - &ProtocolAddress::new("+14152222222", 1), - sender_certificate, - "smert za smert".as_bytes(), - )?; - let bob_cipher = SealedSessionCipher::new( - ctx.clone(), - bob_store_context, - bob_address(), + let ciphertext = alice_cipher + .encrypt( + &ProtocolAddress::new("+14152222222".into(), 1), + sender_certificate, + "smert za smert".as_bytes(), + ) + .await?; + + let mut bob_cipher = SealedSessionCipher::new( + bob_stores.session_store, + bob_stores.identity_key_store, + bob_stores.signed_prekey_store, + bob_stores.prekey_store, certificate_validator, ); - let plaintext = bob_cipher.decrypt(&ciphertext, 31335)?; + let plaintext = bob_cipher.decrypt(&ciphertext, 31335).await?; assert_eq!( String::from_utf8_lossy(&plaintext.padded_message), @@ -817,49 +811,57 @@ mod tests { Ok(()) } - #[test] - fn test_encrypt_decrypt_untrusted() -> anyhow::Result<()> { - let (ctx, alice_store_context, bob_store_context) = create_contexts()?; - initialize_session(&ctx, &bob_store_context, &alice_store_context)?; + #[tokio::test] + async fn test_encrypt_decrypt_untrusted() -> anyhow::Result<()> { + let mut csprng = rand::thread_rng(); + let (alice_stores, bob_stores) = create_stores(&mut csprng).await?; - let trust_root = libsignal_protocol::generate_key_pair(&ctx)?; + let trust_root = KeyPair::generate(&mut csprng); let certificate_validator = - CertificateValidator::new(trust_root.public()); + CertificateValidator::new(trust_root.public_key); - let false_trust_root = libsignal_protocol::generate_key_pair(&ctx)?; + let false_trust_root = KeyPair::generate(&mut csprng); let false_certificate_validator = - CertificateValidator::new(false_trust_root.public()); + CertificateValidator::new(false_trust_root.public_key); let sender_certificate = create_certificate_for( - &ctx, &trust_root, alice_address(), 1, - alice_store_context.identity_key_pair()?.public(), + *alice_stores + .identity_key_store + .get_identity_key_pair(None) + .await? + .public_key(), 31337, + &mut csprng, )?; - let alice_cipher = SealedSessionCipher::new( - ctx.clone(), - alice_store_context, - alice_address(), + let mut alice_cipher = SealedSessionCipher::new( + alice_stores.session_store, + alice_stores.identity_key_store, + alice_stores.signed_prekey_store, + alice_stores.prekey_store, certificate_validator, ); - let ciphertext = alice_cipher.encrypt( - &ProtocolAddress::new("+14152222222", 1), - sender_certificate, - "и вот я".as_bytes(), - )?; + let ciphertext = alice_cipher + .encrypt( + &ProtocolAddress::new("+14152222222".into(), 1), + sender_certificate, + "и вот я".as_bytes(), + ) + .await?; - let bob_cipher = SealedSessionCipher::new( - ctx, - bob_store_context, - bob_address(), + let mut bob_cipher = SealedSessionCipher::new( + bob_stores.session_store, + bob_stores.identity_key_store, + bob_stores.signed_prekey_store, + bob_stores.prekey_store, false_certificate_validator, ); - let plaintext = bob_cipher.decrypt(&ciphertext, 31335); + let plaintext = bob_cipher.decrypt(&ciphertext, 31335).await; match plaintext { Err(SealedSessionError::InvalidCertificate) => Ok(()), @@ -867,132 +869,146 @@ mod tests { } } - #[test] - fn test_encrypt_decrypt_expired() -> anyhow::Result<()> { - let (ctx, alice_store_context, bob_store_context) = create_contexts()?; - initialize_session(&ctx, &bob_store_context, &alice_store_context)?; + #[tokio::test] + async fn test_encrypt_decrypt_expired() -> anyhow::Result<()> { + let mut csprng = rand::thread_rng(); + let (alice_stores, bob_stores) = create_stores(&mut csprng).await?; - let trust_root = libsignal_protocol::generate_key_pair(&ctx)?; + let trust_root = KeyPair::generate(&mut csprng); let certificate_validator = - CertificateValidator::new(trust_root.public()); + CertificateValidator::new(trust_root.public_key); let sender_certificate = create_certificate_for( - &ctx, &trust_root, alice_address(), 1, - alice_store_context.identity_key_pair()?.public(), + *alice_stores + .identity_key_store + .get_identity_key_pair(None) + .await? + .public_key(), 31337, + &mut csprng, )?; - let alice_cipher = SealedSessionCipher::new( - ctx.clone(), - alice_store_context, - alice_address(), + let mut alice_cipher = SealedSessionCipher::new( + alice_stores.session_store, + alice_stores.identity_key_store, + alice_stores.signed_prekey_store, + alice_stores.prekey_store, certificate_validator.clone(), ); - let ciphertext = alice_cipher.encrypt( - &ProtocolAddress::new("+14152222222", 1), - sender_certificate, - "smert za smert".as_bytes(), - )?; + let ciphertext = alice_cipher + .encrypt( + &ProtocolAddress::new("+14152222222".into(), 1), + sender_certificate, + "smert za smert".as_bytes(), + ) + .await?; - let bob_cipher = SealedSessionCipher::new( - ctx.clone(), - bob_store_context, - bob_address(), + let mut bob_cipher = SealedSessionCipher::new( + bob_stores.session_store, + bob_stores.identity_key_store, + bob_stores.signed_prekey_store, + bob_stores.prekey_store, certificate_validator, ); - match bob_cipher.decrypt(&ciphertext, 31338) { + match bob_cipher.decrypt(&ciphertext, 31338).await { Err(SealedSessionError::ExpiredCertificate) => Ok(()), _ => panic!("certificate is expired, we should not get decrypted data here!11!") } } - #[test] - fn test_encrypt_from_wrong_identity() -> anyhow::Result<()> { - let (ctx, alice_store_context, bob_store_context) = create_contexts()?; - initialize_session(&ctx, &bob_store_context, &alice_store_context)?; + #[tokio::test] + async fn test_encrypt_from_wrong_identity() -> anyhow::Result<()> { + let mut csprng = rand::thread_rng(); + let (alice_stores, bob_stores) = create_stores(&mut csprng).await?; - let trust_root = libsignal_protocol::generate_key_pair(&ctx)?; - let random_key_pair = libsignal_protocol::generate_key_pair(&ctx)?; + let trust_root = KeyPair::generate(&mut csprng); + let random_key_pair = KeyPair::generate(&mut csprng); let certificate_validator = - CertificateValidator::new(trust_root.public()); + CertificateValidator::new(trust_root.public_key); let sender_certificate = create_certificate_for( - &ctx, &random_key_pair, alice_address(), 1, - alice_store_context.identity_key_pair()?.public(), + *alice_stores + .identity_key_store + .get_identity_key_pair(None) + .await? + .public_key(), 31337, + &mut csprng, )?; - let alice_cipher = SealedSessionCipher::new( - ctx.clone(), - alice_store_context, - alice_address(), + let mut alice_cipher = SealedSessionCipher::new( + alice_stores.session_store, + alice_stores.identity_key_store, + alice_stores.signed_prekey_store, + alice_stores.prekey_store, certificate_validator.clone(), ); - let ciphertext = alice_cipher.encrypt( - &ProtocolAddress::new("+14152222222", 1), - sender_certificate, - "smert za smert".as_bytes(), - )?; - let bob_cipher = SealedSessionCipher::new( - ctx.clone(), - bob_store_context, - bob_address(), + let ciphertext = alice_cipher + .encrypt( + &ProtocolAddress::new("+14152222222".into(), 1), + sender_certificate, + "smert za smert".as_bytes(), + ) + .await?; + + let mut bob_cipher = SealedSessionCipher::new( + bob_stores.session_store, + bob_stores.identity_key_store, + bob_stores.signed_prekey_store, + bob_stores.prekey_store, certificate_validator, ); - match bob_cipher.decrypt(&ciphertext, 31335) { + match bob_cipher.decrypt(&ciphertext, 31335).await { Err(SealedSessionError::InvalidCertificate) => Ok(()), _ => panic!("the certificate is invalid here!11"), } } - fn create_contexts( - ) -> Result<(Context, StoreContext, StoreContext), SealedSessionError> { - let ctx = Context::new(DefaultCrypto::default())?; - - let alice_identity = sig::generate_identity_key_pair(&ctx).unwrap(); - let alice_store = sig::store_context( - &ctx, - InMemoryPreKeyStore::default(), - InMemorySignedPreKeyStore::default(), - InMemorySessionStore::default(), - InMemoryIdentityKeyStore::new( - sig::generate_registration_id(&ctx, 0).unwrap(), - &alice_identity, + async fn create_stores( + csprng: &mut R, + ) -> anyhow::Result<(Stores, Stores)> { + let mut alice_stores = Stores { + identity_key_store: InMemIdentityKeyStore::new( + IdentityKeyPair::generate(csprng), + generate_registration_id(csprng), ), - )?; + session_store: InMemSessionStore::new(), + signed_prekey_store: InMemSignedPreKeyStore::new(), + prekey_store: InMemPreKeyStore::new(), + }; - let bob_identity = sig::generate_identity_key_pair(&ctx).unwrap(); - let bob_store = sig::store_context( - &ctx, - InMemoryPreKeyStore::default(), - InMemorySignedPreKeyStore::default(), - InMemorySessionStore::default(), - InMemoryIdentityKeyStore::new( - sig::generate_registration_id(&ctx, 0).unwrap(), - &bob_identity, + let mut bob_stores = Stores { + identity_key_store: InMemIdentityKeyStore::new( + IdentityKeyPair::generate(csprng), + generate_registration_id(csprng), ), - )?; + session_store: InMemSessionStore::new(), + signed_prekey_store: InMemSignedPreKeyStore::new(), + prekey_store: InMemPreKeyStore::new(), + }; - Ok((ctx, alice_store, bob_store)) + initialize_session(&mut alice_stores, &mut bob_stores, csprng).await?; + + Ok((alice_stores, bob_stores)) } - fn create_certificate_for( - context: &Context, + fn create_certificate_for( trust_root: &KeyPair, addr: ServiceAddress, device_id: u32, identity_key: PublicKey, expires: u64, + csprng: &mut R, ) -> Result { - let server_key = libsignal_protocol::generate_key_pair(&context)?; + let server_key = KeyPair::generate(csprng); let uuid = addr.uuid.as_ref().map(uuid::Uuid::to_string); let e164 = addr.e164(); @@ -1000,22 +1016,17 @@ mod tests { let mut server_certificate_bytes = vec![]; crate::proto::server_certificate::Certificate { id: Some(1), - key: Some(server_key.public().serialize()?.as_slice().to_vec()), + key: Some(server_key.public_key.serialize().into_vec()), } .encode(&mut server_certificate_bytes)?; - let server_certificate_signature = - libsignal_protocol::calculate_signature( - &context, - &trust_root.private(), - &server_certificate_bytes, - )? - .as_slice() - .to_vec(); + let server_certificate_signature = trust_root + .private_key + .calculate_signature(&server_certificate_bytes, csprng)?; let server_certificate = crate::proto::ServerCertificate { certificate: Some(server_certificate_bytes), - signature: Some(server_certificate_signature), + signature: Some(server_certificate_signature.into_vec()), }; let mut sender_certificate_bytes = vec![]; @@ -1023,62 +1034,83 @@ mod tests { sender_uuid: uuid, sender_e164: e164, sender_device: Some(device_id), - identity_key: Some(identity_key.serialize()?.as_slice().to_vec()), + identity_key: Some(identity_key.serialize().into_vec()), expires: Some(expires), signer: Some(server_certificate), } .encode(&mut sender_certificate_bytes)?; - let sender_certificate_signature = - libsignal_protocol::calculate_signature( - &context, - &server_key.private(), - &sender_certificate_bytes, - )? - .as_slice() - .to_vec(); + let sender_certificate_signature = server_key + .private_key + .calculate_signature(&sender_certificate_bytes, csprng)?; Ok(SenderCertificate::try_from( - &context, crate::proto::SenderCertificate { certificate: Some(sender_certificate_bytes), - signature: Some(sender_certificate_signature), + signature: Some(sender_certificate_signature.into_vec()), }, )?) } - fn initialize_session( - context: &Context, - bob_store_context: &StoreContext, - alice_store_context: &StoreContext, + async fn initialize_session( + alice_stores: &mut Stores, + bob_stores: &mut Stores, + csprng: &mut R, ) -> Result<(), SealedSessionError> { - let bob_pre_key = libsignal_protocol::generate_key_pair(&context)?; - let bob_identity_key = bob_store_context.identity_key_pair()?; - let bob_signed_pre_key = libsignal_protocol::generate_signed_pre_key( - &context, - &bob_identity_key, + let bob_pre_key = PreKeyRecord::new(1, &KeyPair::generate(csprng)); + let bob_identity_key_pair = bob_stores + .identity_key_store + .get_identity_key_pair(None) + .await?; + + // TODO: check + let signed_pre_key_pair = KeyPair::generate(csprng); + let signed_pre_key_signature = bob_identity_key_pair + .private_key() + .calculate_signature( + &signed_pre_key_pair.public_key.serialize(), + csprng, + )? + .into_vec(); + + let bob_signed_pre_key_record = SignedPreKeyRecord::new( + 2, + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + &signed_pre_key_pair, + &signed_pre_key_signature, + ); + + let bob_bundle = PreKeyBundle::new( + 1, + 1, + Some((1, bob_pre_key.public_key()?)), 2, - UNIX_EPOCH, + signed_pre_key_pair.public_key, + signed_pre_key_signature, + *bob_identity_key_pair.identity_key(), )?; - let bob_bundle = PreKeyBundle::builder() - .registration_id(1) - .device_id(1) - .pre_key(1, &bob_pre_key.public()) - .signed_pre_key(2, &bob_signed_pre_key.key_pair().public()) - .signature(&bob_signed_pre_key.signature()) - .identity_key(&bob_identity_key.public()) - .build()?; - - let alice_session_builder = SessionBuilder::new( - &context, - &alice_store_context, - &ProtocolAddress::new("+14152222222", 1), - ); - alice_session_builder.process_pre_key_bundle(&bob_bundle)?; + process_prekey_bundle( + &ProtocolAddress::new("+14152222222".into(), 1), + &mut alice_stores.session_store, + &mut alice_stores.identity_key_store, + &bob_bundle, + csprng, + None, + ) + .await?; - bob_store_context.store_signed_pre_key(&bob_signed_pre_key)?; - bob_store_context.store_pre_key(&PreKey::new(1, &bob_pre_key)?)?; + bob_stores + .signed_prekey_store + .save_signed_pre_key(2, &bob_signed_pre_key_record, None) + .await?; + bob_stores + .prekey_store + .save_pre_key(1, &bob_pre_key, None) + .await?; Ok(()) } } diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index 7c6c8c581..499c2df54 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -1,17 +1,18 @@ use std::time::SystemTime; -use crate::cipher::get_preferred_protocol_address; use crate::proto::{ attachment_pointer::AttachmentIdentifier, attachment_pointer::Flags as AttachmentPointerFlags, sync_message, AttachmentPointer, SyncMessage, }; +use crate::{ + cipher::get_preferred_protocol_address, session_store::SessionStoreExt, +}; use chrono::prelude::*; use libsignal_protocol::{ - process_prekey_bundle, Context, IdentityKeyStore, PreKeyStore, - ProtocolAddress, SessionRecord, SessionStore, SignalProtocolError, - SignedPreKeyStore, + process_prekey_bundle, IdentityKeyStore, ProtocolAddress, + SignalProtocolError, }; use log::{info, trace}; @@ -69,17 +70,12 @@ pub struct AttachmentSpec { } /// Equivalent of Java's `SignalServiceMessageSender`. -#[derive(Clone)] -pub struct MessageSender -where - S: SessionStore + Clone, - I: IdentityKeyStore + Clone, - SP: SignedPreKeyStore + Clone, - P: PreKeyStore + Clone, -{ +pub struct MessageSender { service: Service, - context: Context, - cipher: ServiceCipher, + cipher: ServiceCipher, + session_store: Box, + identity_key_store: Box, + local_address: ServiceAddress, device_id: u32, } @@ -123,24 +119,24 @@ pub enum MessageSenderError { IdentityFailure { recipient: ServiceAddress }, } -impl MessageSender +impl MessageSender where Service: PushService + Clone, - S: SessionStore + Clone, - I: IdentityKeyStore + Clone, - SP: SignedPreKeyStore + Clone, - P: PreKeyStore + Clone, { pub fn new( service: Service, - context: Context, - cipher: ServiceCipher, + cipher: ServiceCipher, + session_store: impl SessionStoreExt + 'static, + identity_key_store: impl IdentityKeyStore + 'static, + local_address: ServiceAddress, device_id: u32, ) -> Self { MessageSender { service, - context, cipher, + session_store: Box::new(session_store), + identity_key_store: Box::new(identity_key_store), + local_address, device_id, } } @@ -343,7 +339,7 @@ where timestamp, ); self.try_send_message( - (&self.cipher.local_address).clone(), + (&self.local_address).clone(), None, &sync_message, timestamp, @@ -355,15 +351,13 @@ where } if end_session { - // log::debug!("ending session with {}", recipient); - // if let Some(ref uuid) = recipient.uuid { - // self.cipher - // .store_context - // .delete_all_sessions(&uuid.to_string())?; - // } - // if let Some(e164) = recipient.e164() { - // self.cipher.store_context.delete_all_sessions(&e164)?; - // } + log::debug!("ending session with {}", recipient); + if let Some(ref uuid) = recipient.uuid { + self.session_store.delete_all_sessions(&uuid.to_string())?; + } + if let Some(e164) = recipient.e164() { + self.session_store.delete_all_sessions(&e164)?; + } log::warn!("deleting all sessions: unimplemented following the switch to the Rust version of libsignal-protocol"); } @@ -418,7 +412,7 @@ where ); self.try_send_message( - self.cipher.local_address.clone(), + self.local_address.clone(), unidentified_access, &sync_message, timestamp, @@ -506,8 +500,8 @@ where process_prekey_bundle( &remote_address, - &mut self.cipher.session_store, - &mut self.cipher.identity_key_store, + self.session_store.as_mut_session_store(), + self.identity_key_store.as_mut(), &pre_key, &mut csprng, None, @@ -529,19 +523,17 @@ where extra_device_id ); if let Some(ref uuid) = recipient.uuid { - log::warn!("deleting session: unimplemented following the switch to the Rust version of libsignal-protocol"); - // self.cipher.store_context.delete_session( - // &ProtocolAddress::new( - // uuid.to_string(), - // *extra_device_id, - // ), - // )?; + self.session_store.delete_session( + &ProtocolAddress::new( + uuid.to_string(), + *extra_device_id, + ), + )?; } if let Some(e164) = recipient.e164() { - log::warn!("deleting session: unimplemented following the switch to the Rust version of libsignal-protocol"); - // self.cipher.store_context.delete_session( - // &ProtocolAddress::new(e164, *extra_device_id), - // )?; + self.session_store.delete_session( + &ProtocolAddress::new(e164, *extra_device_id), + )?; } } } @@ -631,7 +623,7 @@ where ) -> Result, MessageSenderError> { let mut messages = vec![]; - let myself = recipient.matches(&self.cipher.local_address); + let myself = recipient.matches(&self.local_address); if !myself || unidentified_access.is_some() { trace!("sending message to default device"); messages.push( @@ -645,39 +637,28 @@ where ); } - // XXX maybe refactor this in a method, this is probably something we need on every call to - // get_sub_device_sessions. - // FIXME: re-implement sub device sessions let mut sub_device_sessions = Vec::new(); - // if let Some(uuid) = &recipient.uuid { - // sub_device_sessions.extend( - // self.cipher - // .session_store - // .get_sub_device_sessions(&uuid.to_string())?, - // ); - // } - // if let Some(e164) = &recipient.e164() { - // sub_device_sessions.extend( - // self.cipher.store_context.get_sub_device_sessions(&e164)?, - // ); - // } + if let Some(uuid) = &recipient.uuid { + sub_device_sessions.extend( + self.session_store + .get_sub_device_sessions(&uuid.to_string())?, + ); + } + if let Some(e164) = &recipient.e164() { + sub_device_sessions + .extend(self.session_store.get_sub_device_sessions(&e164)?); + } for device_id in sub_device_sessions { trace!("sending message to device {}", device_id); let ppa = get_preferred_protocol_address( None, - &self.cipher.session_store, + self.session_store.as_mut_session_store(), recipient, device_id, ) .await?; - if self - .cipher - .session_store - .load_session(&ppa, None) - .await? - .is_some() - { + if self.session_store.load_session(&ppa, None).await?.is_some() { messages.push( self.create_encrypted_message( recipient, @@ -705,7 +686,7 @@ where ) -> Result { let recipient_address = get_preferred_protocol_address( None, - &self.cipher.session_store, + self.session_store.as_mut_session_store(), recipient, device_id, ) @@ -713,7 +694,6 @@ where log::trace!("encrypting message for {:?}", recipient_address); if !self - .cipher .session_store .load_session(&recipient_address, None) .await? @@ -723,7 +703,7 @@ where let pre_keys = self.service.get_pre_keys(&recipient, device_id).await?; for pre_key_bundle in pre_keys { - if recipient.matches(&self.cipher.local_address) + if recipient.matches(&self.local_address) && self.device_id == pre_key_bundle.device_id()? { trace!("not establishing a session with myself!"); @@ -732,7 +712,7 @@ where let pre_key_address = get_preferred_protocol_address( None, - &self.cipher.session_store, + self.session_store.as_mut_session_store(), recipient, pre_key_bundle.device_id()?, ) @@ -743,8 +723,8 @@ where process_prekey_bundle( &pre_key_address, - &mut self.cipher.session_store, - &mut self.cipher.identity_key_store, + self.session_store.as_mut_session_store(), + self.identity_key_store.as_mut(), &pre_key_bundle, &mut csprng, None, diff --git a/libsignal-service/src/session_store.rs b/libsignal-service/src/session_store.rs new file mode 100644 index 000000000..33b149b8d --- /dev/null +++ b/libsignal-service/src/session_store.rs @@ -0,0 +1,30 @@ +use libsignal_protocol::{ProtocolAddress, SessionStore, SignalProtocolError}; + +/// This is additional functions required to handle +/// session deletion. It might be a candidate for inclusion into +/// the bigger `SessionStore` trait. +pub trait SessionStoreExt: SessionStore { + /// Use this to downcast as a regular `SessionStore` + fn as_mut_session_store(&mut self) -> &mut dyn SessionStore; + + /// Get the IDs of all known devices with active sessions for a recipient. + fn get_sub_device_sessions( + &self, + name: &str, + ) -> Result, SignalProtocolError>; + + /// Remove a session record for a recipient ID + device ID tuple. + fn delete_session( + &self, + address: &ProtocolAddress, + ) -> Result<(), SignalProtocolError>; + + /// Remove the session records corresponding to all devices of a recipient + /// ID. + /// + /// Returns the number of deleted sessions. + fn delete_all_sessions( + &self, + address: &str, + ) -> Result; +} From 3419aa90179923825f6e603294e0da108cbf6629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Tue, 27 Apr 2021 13:23:43 +0200 Subject: [PATCH 08/19] Adjust usage of verify_signature --- libsignal-service/src/cipher.rs | 15 ++------------- libsignal-service/src/sealed_session_cipher.rs | 16 +++++++++++----- libsignal-service/src/sender.rs | 3 --- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/libsignal-service/src/cipher.rs b/libsignal-service/src/cipher.rs index 49b6a4658..9c04dccde 100644 --- a/libsignal-service/src/cipher.rs +++ b/libsignal-service/src/cipher.rs @@ -102,7 +102,6 @@ impl ServiceCipher { let plaintext = match envelope.r#type() { Type::PrekeyBundle => { let sender = get_preferred_protocol_address( - None, self.session_store.as_ref(), &envelope.source_address(), envelope.source_device(), @@ -148,7 +147,6 @@ impl ServiceCipher { } Type::Ciphertext => { let sender = get_preferred_protocol_address( - None, self.session_store.as_ref(), &envelope.source_address(), envelope.source_device(), @@ -336,28 +334,19 @@ fn strip_padding( /// Equivalent of `SignalServiceCipher::getPreferredProtocolAddress` pub async fn get_preferred_protocol_address( - context: Context, session_store: &dyn SessionStore, address: &ServiceAddress, device_id: u32, ) -> Result { if let Some(ref uuid) = address.uuid { let address = ProtocolAddress::new(uuid.to_string(), device_id); - if session_store - .load_session(&address, context) - .await? - .is_some() - { + if session_store.load_session(&address, None).await?.is_some() { return Ok(address); } } if let Some(e164) = address.e164() { let address = ProtocolAddress::new(e164, device_id); - if session_store - .load_session(&address, context) - .await? - .is_some() - { + if session_store.load_session(&address, None).await?.is_some() { return Ok(address); } if cfg!(feature = "prefer-e164") { diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index dac083a10..41ac62360 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -457,7 +457,6 @@ impl SealedSessionCipher { sender_certificate, } = message; let sender = crate::cipher::get_preferred_protocol_address( - None, self.session_store.as_ref(), &sender_certificate.address(), sender_certificate.sender_device_id, @@ -681,7 +680,8 @@ impl CertificateValidator { validation_time: u64, ) -> Result<(), SealedSessionError> { let server_certificate = &certificate.signer; - self.trust_root + if !self + .trust_root .verify_signature( &server_certificate.certificate, &server_certificate.signature, @@ -689,15 +689,21 @@ impl CertificateValidator { .map_err(|e| { error!("failed to verify server certificate: {}", e); SealedSessionError::InvalidCertificate - })?; + })? + { + return Err(SealedSessionError::InvalidCertificate); + } - server_certificate + if !server_certificate .key .verify_signature(&certificate.certificate, &certificate.signature) .map_err(|e| { error!("failed to verify certificate: {}", e); SealedSessionError::InvalidCertificate - })?; + })? + { + return Err(SealedSessionError::InvalidCertificate); + } if validation_time > certificate.expiration { error!("certificate is expired"); diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index 499c2df54..855d2e50d 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -652,7 +652,6 @@ where for device_id in sub_device_sessions { trace!("sending message to device {}", device_id); let ppa = get_preferred_protocol_address( - None, self.session_store.as_mut_session_store(), recipient, device_id, @@ -685,7 +684,6 @@ where content: &[u8], ) -> Result { let recipient_address = get_preferred_protocol_address( - None, self.session_store.as_mut_session_store(), recipient, device_id, @@ -711,7 +709,6 @@ where } let pre_key_address = get_preferred_protocol_address( - None, self.session_store.as_mut_session_store(), recipient, pre_key_bundle.device_id()?, From 66b89696003d2925a854388e45e0ea037927c98f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Tue, 27 Apr 2021 18:13:33 +0200 Subject: [PATCH 09/19] Remove FIXME --- libsignal-service-actix/Cargo.toml | 4 - libsignal-service-actix/examples/link.rs | 14 ++- libsignal-service-hyper/Cargo.toml | 3 - libsignal-service/Cargo.toml | 8 +- libsignal-service/src/account_manager.rs | 45 +++++----- libsignal-service/src/cipher.rs | 43 +++++---- libsignal-service/src/provisioning/cipher.rs | 3 +- .../src/sealed_session_cipher.rs | 89 +++++++++---------- libsignal-service/src/sender.rs | 23 +++-- 9 files changed, 105 insertions(+), 127 deletions(-) diff --git a/libsignal-service-actix/Cargo.toml b/libsignal-service-actix/Cargo.toml index 419721fde..cd9ff12e2 100644 --- a/libsignal-service-actix/Cargo.toml +++ b/libsignal-service-actix/Cargo.toml @@ -4,12 +4,8 @@ version = "0.1.0" authors = ["Ruben De Smet "] edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] libsignal-service = { path = "../libsignal-service" } -libsignal-protocol = { git = "https://github.com/signalapp/libsignal-client", version = "0.1.0", tag = "v0.4.0"} - awc = { version = "3.0.0-beta.5", features=["rustls"] } actix = "0.11.1" diff --git a/libsignal-service-actix/examples/link.rs b/libsignal-service-actix/examples/link.rs index f7e0abebd..7f4650e53 100644 --- a/libsignal-service-actix/examples/link.rs +++ b/libsignal-service-actix/examples/link.rs @@ -1,20 +1,16 @@ use failure::Error; use futures::{channel::mpsc::channel, future, StreamExt}; use image::Luma; -use libsignal_service::configuration::SignalServers; +use libsignal_service::{ + configuration::SignalServers, provisioning::LinkingManager, + provisioning::SecondaryDeviceProvisioning, USER_AGENT, +}; +use libsignal_service_actix::prelude::AwcPushService; use log::LevelFilter; use qrcode::QrCode; use rand::{distributions::Alphanumeric, Rng, RngCore}; use structopt::StructOpt; -use libsignal_protocol::Context; - -use libsignal_service::{ - provisioning::LinkingManager, provisioning::SecondaryDeviceProvisioning, - USER_AGENT, -}; -use libsignal_service_actix::prelude::AwcPushService; - #[derive(Debug, StructOpt)] struct Args { #[structopt(long = "servers", short = "s", default_value = "staging")] diff --git a/libsignal-service-hyper/Cargo.toml b/libsignal-service-hyper/Cargo.toml index 25da83999..b22389d56 100644 --- a/libsignal-service-hyper/Cargo.toml +++ b/libsignal-service-hyper/Cargo.toml @@ -4,11 +4,8 @@ version = "0.1.0" authors = ["Gabriel Féron "] edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] libsignal-service = { path = "../libsignal-service" } -libsignal-protocol = { git = "https://github.com/signalapp/libsignal-client", version = "0.1.0", tag = "v0.4.0"} async-trait = "0.1" base64 = "0.13" diff --git a/libsignal-service/Cargo.toml b/libsignal-service/Cargo.toml index dc6dbec06..1b496aa19 100644 --- a/libsignal-service/Cargo.toml +++ b/libsignal-service/Cargo.toml @@ -7,9 +7,8 @@ license = "GPLv3" readme = "../README.md" [dependencies] -#libsignal-protocol = { git = "https://github.com/signalapp/libsignal-client", version = "0.1.0", tag = "v0.4.0"} -libsignal-protocol = { path = "../../libsignal-client/rust/protocol", version = "0.1.0", tag = "v0.4.0"} -zkgroup = { git = "https://github.com/signalapp/zkgroup" } +libsignal-protocol = { git = "https://github.com/whisperfish/libsignal-client", branch = "make-error-sync" } +zkgroup = { git = "https://github.com/signalapp/zkgroup", tag = "v0.7.2" } async-trait = "0.1.30" url = { version = "2.1.1", features = ["serde"] } base64 = "0.13" @@ -36,13 +35,12 @@ rand = "0.7" uuid = { version = "0.8", features = [ "serde" ] } phonenumber = "0.3" -tokio = { version = "1.0", features = [ "macros" ] } - [build-dependencies] prost-build = "0.7" [dev-dependencies] anyhow = "1.0" +tokio = { version = "1.0", features = [ "macros" ] } [features] prefer-e164 = [] diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index a7227d8ff..e95816340 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -1,3 +1,13 @@ +use std::collections::HashMap; +use std::convert::{TryFrom, TryInto}; +use std::time::SystemTime; + +use libsignal_protocol::{ + IdentityKeyStore, KeyPair, PreKeyRecord, PreKeyStore, PublicKey, + SignalProtocolError, SignedPreKeyRecord, SignedPreKeyStore, +}; +use zkgroup::profiles::ProfileKey; + use crate::{ configuration::{Endpoint, ServiceCredentials}, pre_keys::{PreKeyEntity, PreKeyState}, @@ -11,16 +21,6 @@ use crate::{ }, }; -use std::collections::HashMap; -use std::convert::{TryFrom, TryInto}; -use std::time::SystemTime; - -use libsignal_protocol::{ - IdentityKeyStore, KeyPair, PreKeyRecord, PreKeyStore, PublicKey, - SignedPreKeyRecord, SignedPreKeyStore, -}; -use zkgroup::profiles::ProfileKey; - pub struct AccountManager { service: Service, profile_key: Option<[u8; 32]>, @@ -43,7 +43,7 @@ pub enum LinkError { #[error("TsUrl has an invalid pub_key field")] InvalidPublicKey, #[error("Protocol error {0}")] - ProtocolError(#[from] libsignal_protocol::error::SignalProtocolError), + ProtocolError(#[from] SignalProtocolError), #[error(transparent)] ProvisioningError(#[from] ProvisioningError), } @@ -75,16 +75,11 @@ impl AccountManager { /// Equivalent to Java's RefreshPreKeysJob /// /// Returns the next pre-key offset and next signed pre-key offset as a tuple. - pub async fn update_pre_key_bundle< - I: IdentityKeyStore, - P: PreKeyStore, - S: SignedPreKeyStore, - R: rand::Rng + rand::CryptoRng, - >( + pub async fn update_pre_key_bundle( &mut self, - identity_store: &I, - prekey_store: &mut P, - signed_prekey_store: &mut S, + identity_store: &dyn IdentityKeyStore, + pre_key_store: &mut dyn PreKeyStore, + signed_pre_key_store: &mut dyn SignedPreKeyStore, csprng: &mut R, pre_keys_offset_id: u32, signed_pre_key_id: u32, @@ -113,7 +108,7 @@ impl AccountManager { let pre_key_id = ((pre_keys_offset_id + i) % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) + 1; let pre_key_record = PreKeyRecord::new(pre_key_id, &key_pair); - prekey_store + pre_key_store .save_pre_key(pre_key_id, &pre_key_record, None) .await?; @@ -131,7 +126,7 @@ impl AccountManager { let unix_time = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) - .expect("clock went backwards before UNIX EPOCH"); + .unwrap(); let signed_prekey_record = SignedPreKeyRecord::new( signed_pre_key_id + 1, @@ -140,7 +135,7 @@ impl AccountManager { &signed_pre_key_signature, ); - signed_prekey_store + signed_pre_key_store .save_signed_pre_key( signed_pre_key_id + 1, &signed_prekey_record, @@ -257,10 +252,10 @@ impl AccountManager { let msg = ProvisionMessage { identity_key_public: Some( - identity_key_pair.public_key().serialize().to_vec(), + identity_key_pair.public_key().serialize().into_vec(), ), identity_key_private: Some( - identity_key_pair.private_key().serialize().to_vec(), + identity_key_pair.private_key().serialize(), ), number: Some(credentials.e164()), uuid: credentials.uuid.as_ref().map(|u| u.to_string()), diff --git a/libsignal-service/src/cipher.rs b/libsignal-service/src/cipher.rs index 9c04dccde..ee8a68ff4 100644 --- a/libsignal-service/src/cipher.rs +++ b/libsignal-service/src/cipher.rs @@ -1,3 +1,15 @@ +use std::convert::TryFrom; + +use block_modes::block_padding::{Iso7816, Padding}; +use libsignal_protocol::{ + message_decrypt_prekey, message_decrypt_signal, message_encrypt, + CiphertextMessageType, IdentityKeyStore, PreKeySignalMessage, PreKeyStore, + ProtocolAddress, SessionStore, SignalMessage, SignalProtocolError, + SignedPreKeyStore, +}; +use prost::Message; +use rand::{CryptoRng, Rng}; + use crate::{ content::{Content, Metadata}, envelope::Envelope, @@ -10,47 +22,40 @@ use crate::{ ServiceAddress, }; -use block_modes::block_padding::{Iso7816, Padding}; -use libsignal_protocol::{ - message_decrypt_prekey, message_decrypt_signal, message_encrypt, - CiphertextMessageType, Context, IdentityKeyStore, PreKeySignalMessage, - PreKeyStore, ProtocolAddress, SessionStore, SignalMessage, - SignalProtocolError, SignedPreKeyStore, -}; -use prost::Message; - -use std::convert::TryFrom; - /// Decrypts incoming messages and encrypts outgoing messages. /// /// Equivalent of SignalServiceCipher in Java. -pub struct ServiceCipher { +pub struct ServiceCipher { session_store: Box, identity_key_store: Box, signed_pre_key_store: Box, pre_key_store: Box, - sealed_session_cipher: SealedSessionCipher, + csprng: R, + sealed_session_cipher: SealedSessionCipher, } -impl ServiceCipher { +impl ServiceCipher { pub fn new( session_store: impl SessionStore + Clone + 'static, identity_key_store: impl IdentityKeyStore + Clone + 'static, signed_pre_key_store: impl SignedPreKeyStore + Clone + 'static, pre_key_store: impl PreKeyStore + Clone + 'static, certificate_validator: CertificateValidator, + csprng: R, ) -> Self { Self { session_store: Box::new(session_store.clone()), identity_key_store: Box::new(identity_key_store.clone()), signed_pre_key_store: Box::new(signed_pre_key_store.clone()), pre_key_store: Box::new(pre_key_store.clone()), + csprng: csprng.clone(), sealed_session_cipher: SealedSessionCipher::new( session_store, identity_key_store, signed_pre_key_store, pre_key_store, certificate_validator, + csprng, ), } } @@ -114,9 +119,6 @@ impl ServiceCipher { needs_receipt: false, }; - // FIXME: what - let mut csprng = rand::rngs::OsRng; - let mut data = message_decrypt_prekey( &PreKeySignalMessage::try_from(&ciphertext[..]).unwrap(), &sender, @@ -124,7 +126,7 @@ impl ServiceCipher { self.identity_key_store.as_mut(), self.pre_key_store.as_mut(), self.signed_pre_key_store.as_mut(), - &mut csprng, + &mut self.csprng, None, ) .await? @@ -159,15 +161,12 @@ impl ServiceCipher { needs_receipt: false, }; - // FIXME: what - let mut csprng = rand::rngs::OsRng; - let mut data = message_decrypt_signal( &SignalMessage::try_from(&ciphertext[..])?, &sender, self.session_store.as_mut(), self.identity_key_store.as_mut(), - &mut csprng, + &mut self.csprng, None, ) .await? diff --git a/libsignal-service/src/provisioning/cipher.rs b/libsignal-service/src/provisioning/cipher.rs index 0d55ef35d..26bced3be 100644 --- a/libsignal-service/src/provisioning/cipher.rs +++ b/libsignal-service/src/provisioning/cipher.rs @@ -4,12 +4,11 @@ use aes::Aes256; use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; use bytes::Bytes; use hmac::{Hmac, Mac, NewMac}; +use libsignal_protocol::{KeyPair, PublicKey}; use prost::Message; use rand::Rng; use sha2::Sha256; -use libsignal_protocol::{KeyPair, PublicKey}; - pub use crate::proto::{ ProvisionEnvelope, ProvisionMessage, ProvisioningVersion, }; diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index 41ac62360..62b188eab 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -13,6 +13,7 @@ use libsignal_protocol::{ }; use log::error; use phonenumber::PhoneNumber; +use rand::{CryptoRng, Rng}; use sha2::Sha256; use uuid::Uuid; @@ -64,12 +65,13 @@ pub enum MacError { BadMac, } -pub(crate) struct SealedSessionCipher { +pub(crate) struct SealedSessionCipher { session_store: Box, identity_key_store: Box, signed_pre_key_store: Box, pre_key_store: Box, certificate_validator: CertificateValidator, + csprng: R, } #[derive(Clone)] @@ -205,13 +207,14 @@ impl UnidentifiedSenderMessage { } } -impl SealedSessionCipher { +impl SealedSessionCipher { pub(crate) fn new( session_store: impl SessionStore + 'static, identity_key_store: impl IdentityKeyStore + 'static, signed_pre_key_store: impl SignedPreKeyStore + 'static, pre_key_store: impl PreKeyStore + 'static, certificate_validator: CertificateValidator, + csprng: R, ) -> Self { Self { session_store: Box::new(session_store), @@ -219,11 +222,13 @@ impl SealedSessionCipher { signed_pre_key_store: Box::new(signed_pre_key_store), pre_key_store: Box::new(pre_key_store), certificate_validator, + csprng, } } /// unused until we make progress on https://github.com/Michael-F-Bryan/libsignal-service-rs/issues/25 /// messages from unidentified senders can only be sent via a unidentifiedPipe + #[allow(dead_code)] pub async fn encrypt( &mut self, destination: &ProtocolAddress, @@ -247,10 +252,7 @@ impl SealedSessionCipher { .await? .ok_or(SealedSessionError::NoSessionWithRecipient)?; - // FIXME: what - let mut csprng = rand::rngs::OsRng; - - let ephemeral = KeyPair::generate(&mut csprng); + let ephemeral = KeyPair::generate(&mut self.csprng); let ephemeral_salt = [ b"UnidentifiedDelivery", their_identity.serialize().as_ref(), @@ -463,9 +465,6 @@ impl SealedSessionCipher { ) .await?; - // FIXME: what - let mut csprng = rand::rngs::OsRng; - let msg = match r#type { CiphertextMessageType::Whisper => { let msg = message_decrypt_signal( @@ -473,7 +472,7 @@ impl SealedSessionCipher { &sender, self.session_store.as_mut(), self.identity_key_store.as_mut(), - &mut csprng, + &mut self.csprng, None, ) .await?; @@ -487,7 +486,7 @@ impl SealedSessionCipher { self.identity_key_store.as_mut(), self.pre_key_store.as_mut(), self.signed_pre_key_store.as_mut(), - &mut csprng, + &mut self.csprng, None, ) .await?; @@ -722,8 +721,8 @@ mod tests { process_prekey_bundle, IdentityKeyPair, IdentityKeyStore, InMemIdentityKeyStore, InMemPreKeyStore, InMemSessionStore, InMemSignedPreKeyStore, KeyPair, PreKeyBundle, PreKeyRecord, - PreKeyStore, ProtocolAddress, PublicKey, SessionStore, - SignedPreKeyRecord, SignedPreKeyStore, + PreKeyStore, ProtocolAddress, PublicKey, SignedPreKeyRecord, + SignedPreKeyStore, }; use crate::{provisioning::generate_registration_id, ServiceAddress}; @@ -743,19 +742,11 @@ mod tests { .unwrap() } - fn bob_address() -> ServiceAddress { - ServiceAddress::parse( - Some("+14152222222"), - Some("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), - ) - .unwrap() - } - struct Stores { identity_key_store: InMemIdentityKeyStore, session_store: InMemSessionStore, - signed_prekey_store: InMemSignedPreKeyStore, - prekey_store: InMemPreKeyStore, + signed_pre_key_store: InMemSignedPreKeyStore, + pre_key_store: InMemPreKeyStore, } #[tokio::test] @@ -783,9 +774,10 @@ mod tests { let mut alice_cipher = SealedSessionCipher::new( alice_stores.session_store, alice_stores.identity_key_store, - alice_stores.signed_prekey_store, - alice_stores.prekey_store, + alice_stores.signed_pre_key_store, + alice_stores.pre_key_store, certificate_validator.clone(), + csprng, ); let ciphertext = alice_cipher @@ -799,9 +791,10 @@ mod tests { let mut bob_cipher = SealedSessionCipher::new( bob_stores.session_store, bob_stores.identity_key_store, - bob_stores.signed_prekey_store, - bob_stores.prekey_store, + bob_stores.signed_pre_key_store, + bob_stores.pre_key_store, certificate_validator, + csprng, ); let plaintext = bob_cipher.decrypt(&ciphertext, 31335).await?; @@ -846,9 +839,10 @@ mod tests { let mut alice_cipher = SealedSessionCipher::new( alice_stores.session_store, alice_stores.identity_key_store, - alice_stores.signed_prekey_store, - alice_stores.prekey_store, + alice_stores.signed_pre_key_store, + alice_stores.pre_key_store, certificate_validator, + csprng, ); let ciphertext = alice_cipher @@ -862,9 +856,10 @@ mod tests { let mut bob_cipher = SealedSessionCipher::new( bob_stores.session_store, bob_stores.identity_key_store, - bob_stores.signed_prekey_store, - bob_stores.prekey_store, + bob_stores.signed_pre_key_store, + bob_stores.pre_key_store, false_certificate_validator, + csprng, ); let plaintext = bob_cipher.decrypt(&ciphertext, 31335).await; @@ -899,9 +894,10 @@ mod tests { let mut alice_cipher = SealedSessionCipher::new( alice_stores.session_store, alice_stores.identity_key_store, - alice_stores.signed_prekey_store, - alice_stores.prekey_store, + alice_stores.signed_pre_key_store, + alice_stores.pre_key_store, certificate_validator.clone(), + csprng, ); let ciphertext = alice_cipher @@ -915,9 +911,10 @@ mod tests { let mut bob_cipher = SealedSessionCipher::new( bob_stores.session_store, bob_stores.identity_key_store, - bob_stores.signed_prekey_store, - bob_stores.prekey_store, + bob_stores.signed_pre_key_store, + bob_stores.pre_key_store, certificate_validator, + csprng, ); match bob_cipher.decrypt(&ciphertext, 31338).await { @@ -951,9 +948,10 @@ mod tests { let mut alice_cipher = SealedSessionCipher::new( alice_stores.session_store, alice_stores.identity_key_store, - alice_stores.signed_prekey_store, - alice_stores.prekey_store, + alice_stores.signed_pre_key_store, + alice_stores.pre_key_store, certificate_validator.clone(), + csprng, ); let ciphertext = alice_cipher @@ -967,9 +965,10 @@ mod tests { let mut bob_cipher = SealedSessionCipher::new( bob_stores.session_store, bob_stores.identity_key_store, - bob_stores.signed_prekey_store, - bob_stores.prekey_store, + bob_stores.signed_pre_key_store, + bob_stores.pre_key_store, certificate_validator, + csprng, ); match bob_cipher.decrypt(&ciphertext, 31335).await { @@ -987,8 +986,8 @@ mod tests { generate_registration_id(csprng), ), session_store: InMemSessionStore::new(), - signed_prekey_store: InMemSignedPreKeyStore::new(), - prekey_store: InMemPreKeyStore::new(), + signed_pre_key_store: InMemSignedPreKeyStore::new(), + pre_key_store: InMemPreKeyStore::new(), }; let mut bob_stores = Stores { @@ -997,8 +996,8 @@ mod tests { generate_registration_id(csprng), ), session_store: InMemSessionStore::new(), - signed_prekey_store: InMemSignedPreKeyStore::new(), - prekey_store: InMemPreKeyStore::new(), + signed_pre_key_store: InMemSignedPreKeyStore::new(), + pre_key_store: InMemPreKeyStore::new(), }; initialize_session(&mut alice_stores, &mut bob_stores, csprng).await?; @@ -1110,11 +1109,11 @@ mod tests { .await?; bob_stores - .signed_prekey_store + .signed_pre_key_store .save_signed_pre_key(2, &bob_signed_pre_key_record, None) .await?; bob_stores - .prekey_store + .pre_key_store .save_pre_key(1, &bob_pre_key, None) .await?; Ok(()) diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index 855d2e50d..4766c6064 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -15,6 +15,7 @@ use libsignal_protocol::{ SignalProtocolError, }; use log::{info, trace}; +use rand::{CryptoRng, Rng}; use crate::{ cipher::ServiceCipher, content::ContentBody, push_service::*, @@ -70,9 +71,10 @@ pub struct AttachmentSpec { } /// Equivalent of Java's `SignalServiceMessageSender`. -pub struct MessageSender { +pub struct MessageSender { service: Service, - cipher: ServiceCipher, + cipher: ServiceCipher, + csprng: R, session_store: Box, identity_key_store: Box, local_address: ServiceAddress, @@ -119,13 +121,15 @@ pub enum MessageSenderError { IdentityFailure { recipient: ServiceAddress }, } -impl MessageSender +impl MessageSender where Service: PushService + Clone, + R: Rng + CryptoRng + Clone, { pub fn new( service: Service, - cipher: ServiceCipher, + cipher: ServiceCipher, + csprng: R, session_store: impl SessionStoreExt + 'static, identity_key_store: impl IdentityKeyStore + 'static, local_address: ServiceAddress, @@ -134,6 +138,7 @@ where MessageSender { service, cipher, + csprng, session_store: Box::new(session_store), identity_key_store: Box::new(identity_key_store), local_address, @@ -495,15 +500,12 @@ where .get_pre_key(&recipient, *missing_device_id) .await?; - // FIXME: what - let mut csprng = rand::rngs::OsRng; - process_prekey_bundle( &remote_address, self.session_store.as_mut_session_store(), self.identity_key_store.as_mut(), &pre_key, - &mut csprng, + &mut self.csprng, None, ) .await @@ -715,15 +717,12 @@ where ) .await?; - // FIXME: what - let mut csprng = rand::rngs::OsRng; - process_prekey_bundle( &pre_key_address, self.session_store.as_mut_session_store(), self.identity_key_store.as_mut(), &pre_key_bundle, - &mut csprng, + &mut self.csprng, None, ) .await?; From 926d062ec885be61f70d51916a814fb22d804da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Tue, 27 Apr 2021 18:17:33 +0200 Subject: [PATCH 10/19] Last changes before shipping\! --- .../src/sealed_session_cipher.rs | 4 ++-- libsignal-service/src/sender.rs | 23 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index 62b188eab..4397caecf 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -1,4 +1,4 @@ -use crate::{push_service::ProfileKey, ServiceAddress}; +use std::convert::TryFrom; use aes_ctr::{ cipher::stream::{NewStreamCipher, StreamCipher}, @@ -17,7 +17,7 @@ use rand::{CryptoRng, Rng}; use sha2::Sha256; use uuid::Uuid; -use std::convert::TryFrom; +use crate::{push_service::ProfileKey, ServiceAddress}; #[derive(Debug, thiserror::Error)] pub enum SealedSessionError { diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index 4766c6064..c556cffae 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -1,14 +1,5 @@ use std::time::SystemTime; -use crate::proto::{ - attachment_pointer::AttachmentIdentifier, - attachment_pointer::Flags as AttachmentPointerFlags, sync_message, - AttachmentPointer, SyncMessage, -}; -use crate::{ - cipher::get_preferred_protocol_address, session_store::SessionStoreExt, -}; - use chrono::prelude::*; use libsignal_protocol::{ process_prekey_bundle, IdentityKeyStore, ProtocolAddress, @@ -18,8 +9,17 @@ use log::{info, trace}; use rand::{CryptoRng, Rng}; use crate::{ - cipher::ServiceCipher, content::ContentBody, push_service::*, - sealed_session_cipher::UnidentifiedAccess, ServiceAddress, + cipher::{get_preferred_protocol_address, ServiceCipher}, + content::ContentBody, + proto::{ + attachment_pointer::AttachmentIdentifier, + attachment_pointer::Flags as AttachmentPointerFlags, sync_message, + AttachmentPointer, SyncMessage, + }, + push_service::*, + sealed_session_cipher::UnidentifiedAccess, + session_store::SessionStoreExt, + ServiceAddress, }; pub use crate::proto::{ContactDetails, GroupDetails}; @@ -363,7 +363,6 @@ where if let Some(e164) = recipient.e164() { self.session_store.delete_all_sessions(&e164)?; } - log::warn!("deleting all sessions: unimplemented following the switch to the Rust version of libsignal-protocol"); } result From 53dddee54accae3f43f79b5bc3bee97e79416d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Tue, 27 Apr 2021 18:17:33 +0200 Subject: [PATCH 11/19] Last changes before shipping! --- .../src/sealed_session_cipher.rs | 4 ++-- libsignal-service/src/sender.rs | 23 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index 62b188eab..4397caecf 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -1,4 +1,4 @@ -use crate::{push_service::ProfileKey, ServiceAddress}; +use std::convert::TryFrom; use aes_ctr::{ cipher::stream::{NewStreamCipher, StreamCipher}, @@ -17,7 +17,7 @@ use rand::{CryptoRng, Rng}; use sha2::Sha256; use uuid::Uuid; -use std::convert::TryFrom; +use crate::{push_service::ProfileKey, ServiceAddress}; #[derive(Debug, thiserror::Error)] pub enum SealedSessionError { diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index 4766c6064..c556cffae 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -1,14 +1,5 @@ use std::time::SystemTime; -use crate::proto::{ - attachment_pointer::AttachmentIdentifier, - attachment_pointer::Flags as AttachmentPointerFlags, sync_message, - AttachmentPointer, SyncMessage, -}; -use crate::{ - cipher::get_preferred_protocol_address, session_store::SessionStoreExt, -}; - use chrono::prelude::*; use libsignal_protocol::{ process_prekey_bundle, IdentityKeyStore, ProtocolAddress, @@ -18,8 +9,17 @@ use log::{info, trace}; use rand::{CryptoRng, Rng}; use crate::{ - cipher::ServiceCipher, content::ContentBody, push_service::*, - sealed_session_cipher::UnidentifiedAccess, ServiceAddress, + cipher::{get_preferred_protocol_address, ServiceCipher}, + content::ContentBody, + proto::{ + attachment_pointer::AttachmentIdentifier, + attachment_pointer::Flags as AttachmentPointerFlags, sync_message, + AttachmentPointer, SyncMessage, + }, + push_service::*, + sealed_session_cipher::UnidentifiedAccess, + session_store::SessionStoreExt, + ServiceAddress, }; pub use crate::proto::{ContactDetails, GroupDetails}; @@ -363,7 +363,6 @@ where if let Some(e164) = recipient.e164() { self.session_store.delete_all_sessions(&e164)?; } - log::warn!("deleting all sessions: unimplemented following the switch to the Rust version of libsignal-protocol"); } result From f1ae70110ed195557aab6e81b7d70cca4095bab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Tue, 27 Apr 2021 21:47:42 +0200 Subject: [PATCH 12/19] Use upstream libsignal-client --- libsignal-service/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsignal-service/Cargo.toml b/libsignal-service/Cargo.toml index 1b496aa19..aa3d4acd7 100644 --- a/libsignal-service/Cargo.toml +++ b/libsignal-service/Cargo.toml @@ -7,7 +7,7 @@ license = "GPLv3" readme = "../README.md" [dependencies] -libsignal-protocol = { git = "https://github.com/whisperfish/libsignal-client", branch = "make-error-sync" } +libsignal-protocol = { git = "https://github.com/signalapp/libsignal-client" } zkgroup = { git = "https://github.com/signalapp/zkgroup", tag = "v0.7.2" } async-trait = "0.1.30" url = { version = "2.1.1", features = ["serde"] } From 1829246fa5c53199abd943bded5f7dc95847eacb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Tue, 27 Apr 2021 22:05:43 +0200 Subject: [PATCH 13/19] Address review comments --- libsignal-service/src/account_manager.rs | 12 ++++++------ libsignal-service/src/sealed_session_cipher.rs | 2 +- libsignal-service/src/sender.rs | 15 +++++++++++---- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index e95816340..fbdcaa2a6 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -82,7 +82,7 @@ impl AccountManager { signed_pre_key_store: &mut dyn SignedPreKeyStore, csprng: &mut R, pre_keys_offset_id: u32, - signed_pre_key_id: u32, + next_signed_pre_key_id: u32, use_last_resort_key: bool, ) -> Result<(u32, u32), ServiceError> { let prekey_count = match self.service.get_pre_key_status().await { @@ -99,7 +99,7 @@ impl AccountManager { if prekey_count >= PRE_KEY_MINIMUM { log::info!("Available keys sufficient"); - return Ok((pre_keys_offset_id, signed_pre_key_id)); + return Ok((pre_keys_offset_id, next_signed_pre_key_id)); } let mut pre_key_entities = vec![]; @@ -129,15 +129,15 @@ impl AccountManager { .unwrap(); let signed_prekey_record = SignedPreKeyRecord::new( - signed_pre_key_id + 1, - unix_time.as_secs(), + next_signed_pre_key_id, + unix_time.as_millis() as u64, &signed_pre_key_pair, &signed_pre_key_signature, ); signed_pre_key_store .save_signed_pre_key( - signed_pre_key_id + 1, + next_signed_pre_key_id, &signed_prekey_record, None, ) @@ -162,7 +162,7 @@ impl AccountManager { log::trace!("Successfully refreshed prekeys"); Ok(( pre_keys_offset_id + PRE_KEY_BATCH_SIZE, - signed_pre_key_id + 1, + next_signed_pre_key_id + 1, )) } diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index 4397caecf..1be5b0d47 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -1083,7 +1083,7 @@ mod tests { SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() - .as_secs(), + .as_millis() as u64, &signed_pre_key_pair, &signed_pre_key_signature, ); diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index c556cffae..890f27125 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -477,11 +477,18 @@ where "dropping session with device {}", extra_device_id ); - if let Some(_uuid) = recipient.uuid { - unimplemented!("deleting session: unimplemented following the switch to the Rust version of libsignal-protocol"); + if let Some(uuid) = recipient.uuid { + self.session_store.delete_session( + &ProtocolAddress::new( + uuid.to_string(), + *extra_device_id, + ), + )?; } - if let Some(_e164) = recipient.e164() { - unimplemented!("deleting session: unimplemented following the switch to the Rust version of libsignal-protocol"); + if let Some(e164) = recipient.e164() { + self.session_store.delete_session( + &ProtocolAddress::new(e164, *extra_device_id), + )?; } } From 13657820238f0bc9607ca7c95d35943c7814bd22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Thu, 29 Apr 2021 13:29:20 +0200 Subject: [PATCH 14/19] Use static dispatch --- libsignal-service/src/cipher.rs | 61 ++++++++------- .../src/sealed_session_cipher.rs | 75 ++++++++++--------- libsignal-service/src/sender.rs | 38 +++++----- 3 files changed, 96 insertions(+), 78 deletions(-) diff --git a/libsignal-service/src/cipher.rs b/libsignal-service/src/cipher.rs index ee8a68ff4..716752a8e 100644 --- a/libsignal-service/src/cipher.rs +++ b/libsignal-service/src/cipher.rs @@ -25,37 +25,44 @@ use crate::{ /// Decrypts incoming messages and encrypts outgoing messages. /// /// Equivalent of SignalServiceCipher in Java. -pub struct ServiceCipher { - session_store: Box, - identity_key_store: Box, - signed_pre_key_store: Box, - pre_key_store: Box, +pub struct ServiceCipher { + session_store: S, + identity_key_store: I, + signed_pre_key_store: SP, + pre_key_store: P, csprng: R, - sealed_session_cipher: SealedSessionCipher, + sealed_session_cipher: SealedSessionCipher, } -impl ServiceCipher { +impl ServiceCipher +where + S: SessionStore + Clone, + I: IdentityKeyStore + Clone, + SP: SignedPreKeyStore + Clone, + P: PreKeyStore + Clone, + R: Rng + CryptoRng + Clone, +{ pub fn new( - session_store: impl SessionStore + Clone + 'static, - identity_key_store: impl IdentityKeyStore + Clone + 'static, - signed_pre_key_store: impl SignedPreKeyStore + Clone + 'static, - pre_key_store: impl PreKeyStore + Clone + 'static, - certificate_validator: CertificateValidator, + session_store: S, + identity_key_store: I, + signed_pre_key_store: SP, + pre_key_store: P, csprng: R, + certificate_validator: CertificateValidator, ) -> Self { Self { - session_store: Box::new(session_store.clone()), - identity_key_store: Box::new(identity_key_store.clone()), - signed_pre_key_store: Box::new(signed_pre_key_store.clone()), - pre_key_store: Box::new(pre_key_store.clone()), + session_store: session_store.clone(), + identity_key_store: identity_key_store.clone(), + signed_pre_key_store: signed_pre_key_store.clone(), + pre_key_store: pre_key_store.clone(), csprng: csprng.clone(), sealed_session_cipher: SealedSessionCipher::new( session_store, identity_key_store, signed_pre_key_store, pre_key_store, - certificate_validator, csprng, + certificate_validator.clone(), ), } } @@ -107,7 +114,7 @@ impl ServiceCipher { let plaintext = match envelope.r#type() { Type::PrekeyBundle => { let sender = get_preferred_protocol_address( - self.session_store.as_ref(), + &self.session_store, &envelope.source_address(), envelope.source_device(), ) @@ -122,10 +129,10 @@ impl ServiceCipher { let mut data = message_decrypt_prekey( &PreKeySignalMessage::try_from(&ciphertext[..]).unwrap(), &sender, - self.session_store.as_mut(), - self.identity_key_store.as_mut(), - self.pre_key_store.as_mut(), - self.signed_pre_key_store.as_mut(), + &mut self.session_store, + &mut self.identity_key_store, + &mut self.pre_key_store, + &mut self.signed_pre_key_store, &mut self.csprng, None, ) @@ -149,7 +156,7 @@ impl ServiceCipher { } Type::Ciphertext => { let sender = get_preferred_protocol_address( - self.session_store.as_ref(), + &self.session_store, &envelope.source_address(), envelope.source_device(), ) @@ -164,8 +171,8 @@ impl ServiceCipher { let mut data = message_decrypt_signal( &SignalMessage::try_from(&ciphertext[..])?, &sender, - self.session_store.as_mut(), - self.identity_key_store.as_mut(), + &mut self.session_store, + &mut self.identity_key_store, &mut self.csprng, None, ) @@ -248,8 +255,8 @@ impl ServiceCipher { let message = message_encrypt( &padded_content, &address, - self.session_store.as_mut(), - self.identity_key_store.as_mut(), + &mut self.session_store, + &mut self.identity_key_store, None, ) .await?; diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index 1be5b0d47..db1ba1e57 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -65,13 +65,13 @@ pub enum MacError { BadMac, } -pub(crate) struct SealedSessionCipher { - session_store: Box, - identity_key_store: Box, - signed_pre_key_store: Box, - pre_key_store: Box, - certificate_validator: CertificateValidator, +pub(crate) struct SealedSessionCipher { + session_store: S, + identity_key_store: I, + signed_pre_key_store: SP, + pre_key_store: P, csprng: R, + certificate_validator: CertificateValidator, } #[derive(Clone)] @@ -207,22 +207,29 @@ impl UnidentifiedSenderMessage { } } -impl SealedSessionCipher { +impl SealedSessionCipher +where + S: SessionStore, + I: IdentityKeyStore, + SP: SignedPreKeyStore, + P: PreKeyStore, + R: Rng + CryptoRng, +{ pub(crate) fn new( - session_store: impl SessionStore + 'static, - identity_key_store: impl IdentityKeyStore + 'static, - signed_pre_key_store: impl SignedPreKeyStore + 'static, - pre_key_store: impl PreKeyStore + 'static, - certificate_validator: CertificateValidator, + session_store: S, + identity_key_store: I, + signed_pre_key_store: SP, + pre_key_store: P, csprng: R, + certificate_validator: CertificateValidator, ) -> Self { Self { - session_store: Box::new(session_store), - identity_key_store: Box::new(identity_key_store), - signed_pre_key_store: Box::new(signed_pre_key_store), - pre_key_store: Box::new(pre_key_store), - certificate_validator, + session_store, + identity_key_store, + signed_pre_key_store, + pre_key_store, csprng, + certificate_validator, } } @@ -238,8 +245,8 @@ impl SealedSessionCipher { let message = message_encrypt( padded_plaintext, destination, - self.session_store.as_mut(), - self.identity_key_store.as_mut(), + &mut self.session_store, + &mut self.identity_key_store, None, ) .await?; @@ -459,7 +466,7 @@ impl SealedSessionCipher { sender_certificate, } = message; let sender = crate::cipher::get_preferred_protocol_address( - self.session_store.as_ref(), + &self.session_store, &sender_certificate.address(), sender_certificate.sender_device_id, ) @@ -470,8 +477,8 @@ impl SealedSessionCipher { let msg = message_decrypt_signal( &SignalMessage::try_from(&content[..])?, &sender, - self.session_store.as_mut(), - self.identity_key_store.as_mut(), + &mut self.session_store, + &mut self.identity_key_store, &mut self.csprng, None, ) @@ -482,10 +489,10 @@ impl SealedSessionCipher { let msg = message_decrypt_prekey( &PreKeySignalMessage::try_from(&content[..])?, &sender, - self.session_store.as_mut(), - self.identity_key_store.as_mut(), - self.pre_key_store.as_mut(), - self.signed_pre_key_store.as_mut(), + &mut self.session_store, + &mut self.identity_key_store, + &mut self.pre_key_store, + &mut self.signed_pre_key_store, &mut self.csprng, None, ) @@ -776,8 +783,8 @@ mod tests { alice_stores.identity_key_store, alice_stores.signed_pre_key_store, alice_stores.pre_key_store, - certificate_validator.clone(), csprng, + certificate_validator.clone(), ); let ciphertext = alice_cipher @@ -793,8 +800,8 @@ mod tests { bob_stores.identity_key_store, bob_stores.signed_pre_key_store, bob_stores.pre_key_store, - certificate_validator, csprng, + certificate_validator, ); let plaintext = bob_cipher.decrypt(&ciphertext, 31335).await?; @@ -841,8 +848,8 @@ mod tests { alice_stores.identity_key_store, alice_stores.signed_pre_key_store, alice_stores.pre_key_store, - certificate_validator, csprng, + certificate_validator, ); let ciphertext = alice_cipher @@ -858,8 +865,8 @@ mod tests { bob_stores.identity_key_store, bob_stores.signed_pre_key_store, bob_stores.pre_key_store, - false_certificate_validator, csprng, + false_certificate_validator, ); let plaintext = bob_cipher.decrypt(&ciphertext, 31335).await; @@ -896,8 +903,8 @@ mod tests { alice_stores.identity_key_store, alice_stores.signed_pre_key_store, alice_stores.pre_key_store, - certificate_validator.clone(), csprng, + certificate_validator.clone(), ); let ciphertext = alice_cipher @@ -913,8 +920,8 @@ mod tests { bob_stores.identity_key_store, bob_stores.signed_pre_key_store, bob_stores.pre_key_store, - certificate_validator, csprng, + certificate_validator, ); match bob_cipher.decrypt(&ciphertext, 31338).await { @@ -950,8 +957,8 @@ mod tests { alice_stores.identity_key_store, alice_stores.signed_pre_key_store, alice_stores.pre_key_store, - certificate_validator.clone(), csprng, + certificate_validator.clone(), ); let ciphertext = alice_cipher @@ -967,8 +974,8 @@ mod tests { bob_stores.identity_key_store, bob_stores.signed_pre_key_store, bob_stores.pre_key_store, - certificate_validator, csprng, + certificate_validator, ); match bob_cipher.decrypt(&ciphertext, 31335).await { diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index 890f27125..d9ac82f8d 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -2,8 +2,8 @@ use std::time::SystemTime; use chrono::prelude::*; use libsignal_protocol::{ - process_prekey_bundle, IdentityKeyStore, ProtocolAddress, - SignalProtocolError, + process_prekey_bundle, IdentityKeyStore, PreKeyStore, ProtocolAddress, + SessionStore, SignalProtocolError, SignedPreKeyStore, }; use log::{info, trace}; use rand::{CryptoRng, Rng}; @@ -71,12 +71,12 @@ pub struct AttachmentSpec { } /// Equivalent of Java's `SignalServiceMessageSender`. -pub struct MessageSender { +pub struct MessageSender { service: Service, - cipher: ServiceCipher, + cipher: ServiceCipher, csprng: R, - session_store: Box, - identity_key_store: Box, + session_store: S, + identity_key_store: I, local_address: ServiceAddress, device_id: u32, } @@ -121,17 +121,21 @@ pub enum MessageSenderError { IdentityFailure { recipient: ServiceAddress }, } -impl MessageSender +impl MessageSender where Service: PushService + Clone, + S: SessionStore + SessionStoreExt + Clone, + I: IdentityKeyStore + Clone, + SP: SignedPreKeyStore + Clone, + P: PreKeyStore + Clone, R: Rng + CryptoRng + Clone, { pub fn new( service: Service, - cipher: ServiceCipher, + cipher: ServiceCipher, csprng: R, - session_store: impl SessionStoreExt + 'static, - identity_key_store: impl IdentityKeyStore + 'static, + session_store: S, + identity_key_store: I, local_address: ServiceAddress, device_id: u32, ) -> Self { @@ -139,8 +143,8 @@ where service, cipher, csprng, - session_store: Box::new(session_store), - identity_key_store: Box::new(identity_key_store), + session_store, + identity_key_store, local_address, device_id, } @@ -509,7 +513,7 @@ where process_prekey_bundle( &remote_address, self.session_store.as_mut_session_store(), - self.identity_key_store.as_mut(), + &mut self.identity_key_store, &pre_key, &mut self.csprng, None, @@ -692,7 +696,7 @@ where content: &[u8], ) -> Result { let recipient_address = get_preferred_protocol_address( - self.session_store.as_mut_session_store(), + &mut self.session_store, recipient, device_id, ) @@ -717,7 +721,7 @@ where } let pre_key_address = get_preferred_protocol_address( - self.session_store.as_mut_session_store(), + &mut self.session_store, recipient, pre_key_bundle.device_id()?, ) @@ -725,8 +729,8 @@ where process_prekey_bundle( &pre_key_address, - self.session_store.as_mut_session_store(), - self.identity_key_store.as_mut(), + &mut self.session_store, + &mut self.identity_key_store, &pre_key_bundle, &mut self.csprng, None, From f6c4df7205d7b01582b98e4a4250a01ec6433fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Fri, 30 Apr 2021 11:45:08 +0200 Subject: [PATCH 15/19] Derive clone on ciphers and sender/receiver --- libsignal-service/src/cipher.rs | 1 + libsignal-service/src/lib.rs | 1 + libsignal-service/src/receiver.rs | 1 + libsignal-service/src/sealed_session_cipher.rs | 1 + libsignal-service/src/sender.rs | 1 + 5 files changed, 5 insertions(+) diff --git a/libsignal-service/src/cipher.rs b/libsignal-service/src/cipher.rs index 716752a8e..0e26aee6d 100644 --- a/libsignal-service/src/cipher.rs +++ b/libsignal-service/src/cipher.rs @@ -25,6 +25,7 @@ use crate::{ /// Decrypts incoming messages and encrypts outgoing messages. /// /// Equivalent of SignalServiceCipher in Java. +#[derive(Clone)] pub struct ServiceCipher { session_store: S, identity_key_store: I, diff --git a/libsignal-service/src/lib.rs b/libsignal-service/src/lib.rs index b90e05a6b..4c6b42c80 100644 --- a/libsignal-service/src/lib.rs +++ b/libsignal-service/src/lib.rs @@ -62,6 +62,7 @@ pub mod prelude { pub use zkgroup::groups::{GroupMasterKey, GroupSecretParams}; pub mod protocol { + pub use crate::session_store::SessionStoreExt; pub use libsignal_protocol::{ Context, Direction, IdentityKey, IdentityKeyPair, IdentityKeyStore, KeyPair, PreKeyRecord, PreKeyStore, PrivateKey, ProtocolAddress, diff --git a/libsignal-service/src/receiver.rs b/libsignal-service/src/receiver.rs index 2507f92ee..cb50a703d 100644 --- a/libsignal-service/src/receiver.rs +++ b/libsignal-service/src/receiver.rs @@ -10,6 +10,7 @@ use crate::{ }; /// Equivalent of Java's `SignalServiceMessageReceiver`. +#[derive(Clone)] pub struct MessageReceiver { service: Service, } diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index db1ba1e57..4d5ae124b 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -65,6 +65,7 @@ pub enum MacError { BadMac, } +#[derive(Clone)] pub(crate) struct SealedSessionCipher { session_store: S, identity_key_store: I, diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index d9ac82f8d..69d48dfc4 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -71,6 +71,7 @@ pub struct AttachmentSpec { } /// Equivalent of Java's `SignalServiceMessageSender`. +#[derive(Clone)] pub struct MessageSender { service: Service, cipher: ServiceCipher, From 797dead1011916e24ee3779cfd462f72218f6811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Fri, 30 Apr 2021 12:04:27 +0200 Subject: [PATCH 16/19] Fix clippy warnings --- libsignal-service/src/account_manager.rs | 3 +- libsignal-service/src/cipher.rs | 2 +- libsignal-service/src/push_service.rs | 2 +- .../src/sealed_session_cipher.rs | 35 ++++++++----------- libsignal-service/src/sender.rs | 8 ++--- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index fbdcaa2a6..8db3a25fe 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -75,6 +75,7 @@ impl AccountManager { /// Equivalent to Java's RefreshPreKeysJob /// /// Returns the next pre-key offset and next signed pre-key offset as a tuple. + #[allow(clippy::clippy::too_many_arguments)] pub async fn update_pre_key_bundle( &mut self, identity_store: &dyn IdentityKeyStore, @@ -146,7 +147,7 @@ impl AccountManager { let pre_key_state = PreKeyState { pre_keys: pre_key_entities, signed_pre_key: signed_prekey_record.try_into()?, - identity_key: identity_key_pair.public_key().clone(), + identity_key: *identity_key_pair.public_key(), last_resort_key: if use_last_resort_key { Some(PreKeyEntity { key_id: 0x7fffffff, diff --git a/libsignal-service/src/cipher.rs b/libsignal-service/src/cipher.rs index 0e26aee6d..9c862c2d8 100644 --- a/libsignal-service/src/cipher.rs +++ b/libsignal-service/src/cipher.rs @@ -63,7 +63,7 @@ where signed_pre_key_store, pre_key_store, csprng, - certificate_validator.clone(), + certificate_validator, ), } } diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index 401b2a55d..a3fde4c54 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -629,7 +629,7 @@ pub trait PushService { let mut pre_keys = vec![]; let identity = IdentityKey::decode(&pre_key_response.identity_key)?; for device in pre_key_response.devices { - pre_keys.push(device.into_bundle(identity.clone())?); + pre_keys.push(device.into_bundle(identity)?); } Ok(pre_keys) } diff --git a/libsignal-service/src/sealed_session_cipher.rs b/libsignal-service/src/sealed_session_cipher.rs index 4d5ae124b..08d994a0b 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -687,33 +687,28 @@ impl CertificateValidator { validation_time: u64, ) -> Result<(), SealedSessionError> { let server_certificate = &certificate.signer; - if !self - .trust_root - .verify_signature( - &server_certificate.certificate, - &server_certificate.signature, - ) - .map_err(|e| { - error!("failed to verify server certificate: {}", e); - SealedSessionError::InvalidCertificate - })? - { - return Err(SealedSessionError::InvalidCertificate); - } - if !server_certificate + match self.trust_root.verify_signature( + &server_certificate.certificate, + &server_certificate.signature, + ) { + Err(_) | Ok(false) => { + return Err(SealedSessionError::InvalidCertificate) + } + _ => (), + }; + + match server_certificate .key .verify_signature(&certificate.certificate, &certificate.signature) - .map_err(|e| { - error!("failed to verify certificate: {}", e); - SealedSessionError::InvalidCertificate - })? { - return Err(SealedSessionError::InvalidCertificate); + Err(_) | Ok(false) => { + return Err(SealedSessionError::InvalidCertificate) + } + _ => (), } if validation_time > certificate.expiration { - error!("certificate is expired"); return Err(SealedSessionError::ExpiredCertificate); } diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index 69d48dfc4..d895ee343 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -697,18 +697,18 @@ where content: &[u8], ) -> Result { let recipient_address = get_preferred_protocol_address( - &mut self.session_store, + &self.session_store, recipient, device_id, ) .await?; log::trace!("encrypting message for {:?}", recipient_address); - if !self + if self .session_store .load_session(&recipient_address, None) .await? - .is_some() + .is_none() { info!("establishing new session with {:?}", recipient_address); let pre_keys = @@ -722,7 +722,7 @@ where } let pre_key_address = get_preferred_protocol_address( - &mut self.session_store, + &self.session_store, recipient, pre_key_bundle.device_id()?, ) From a3be3d58d691f5ed8ad0d8ac9824c3a56054a912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Fri, 30 Apr 2021 12:23:41 +0200 Subject: [PATCH 17/19] Add get_sub_device_sessions method to ServiceAddress --- libsignal-service/src/lib.rs | 2 +- libsignal-service/src/sender.rs | 60 ++++++++++++------------ libsignal-service/src/service_address.rs | 21 +++++++++ libsignal-service/src/session_store.rs | 11 ++--- 4 files changed, 56 insertions(+), 38 deletions(-) diff --git a/libsignal-service/src/lib.rs b/libsignal-service/src/lib.rs index 4c6b42c80..29c2d1bee 100644 --- a/libsignal-service/src/lib.rs +++ b/libsignal-service/src/lib.rs @@ -21,7 +21,7 @@ pub mod push_service; pub mod receiver; pub mod sender; pub mod service_address; -pub mod session_store; +mod session_store; pub mod utils; pub use crate::account_manager::{ diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index d895ee343..b293b1218 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -363,10 +363,12 @@ where if end_session { log::debug!("ending session with {}", recipient); if let Some(ref uuid) = recipient.uuid { - self.session_store.delete_all_sessions(&uuid.to_string())?; + self.session_store + .delete_all_sessions(&uuid.to_string()) + .await?; } if let Some(e164) = recipient.e164() { - self.session_store.delete_all_sessions(&e164)?; + self.session_store.delete_all_sessions(&e164).await?; } } @@ -483,17 +485,20 @@ where extra_device_id ); if let Some(uuid) = recipient.uuid { - self.session_store.delete_session( - &ProtocolAddress::new( + self.session_store + .delete_session(&ProtocolAddress::new( uuid.to_string(), *extra_device_id, - ), - )?; + )) + .await?; } if let Some(e164) = recipient.e164() { - self.session_store.delete_session( - &ProtocolAddress::new(e164, *extra_device_id), - )?; + self.session_store + .delete_session(&ProtocolAddress::new( + e164, + *extra_device_id, + )) + .await?; } } @@ -513,7 +518,7 @@ where process_prekey_bundle( &remote_address, - self.session_store.as_mut_session_store(), + &mut self.session_store, &mut self.identity_key_store, &pre_key, &mut self.csprng, @@ -536,17 +541,20 @@ where extra_device_id ); if let Some(ref uuid) = recipient.uuid { - self.session_store.delete_session( - &ProtocolAddress::new( + self.session_store + .delete_session(&ProtocolAddress::new( uuid.to_string(), *extra_device_id, - ), - )?; + )) + .await?; } if let Some(e164) = recipient.e164() { - self.session_store.delete_session( - &ProtocolAddress::new(e164, *extra_device_id), - )?; + self.session_store + .delete_session(&ProtocolAddress::new( + e164, + *extra_device_id, + )) + .await?; } } } @@ -650,22 +658,12 @@ where ); } - let mut sub_device_sessions = Vec::new(); - if let Some(uuid) = &recipient.uuid { - sub_device_sessions.extend( - self.session_store - .get_sub_device_sessions(&uuid.to_string())?, - ); - } - if let Some(e164) = &recipient.e164() { - sub_device_sessions - .extend(self.session_store.get_sub_device_sessions(&e164)?); - } - - for device_id in sub_device_sessions { + for device_id in + recipient.sub_device_sessions(&self.session_store).await? + { trace!("sending message to device {}", device_id); let ppa = get_preferred_protocol_address( - self.session_store.as_mut_session_store(), + &self.session_store, recipient, device_id, ) diff --git a/libsignal-service/src/service_address.rs b/libsignal-service/src/service_address.rs index 74acdc54f..958edede7 100644 --- a/libsignal-service/src/service_address.rs +++ b/libsignal-service/src/service_address.rs @@ -1,6 +1,8 @@ use phonenumber::*; use uuid::Uuid; +use crate::{push_service::ServiceError, session_store::SessionStoreExt}; + #[derive(thiserror::Error, Debug)] pub enum ParseServiceAddressError { #[error("Supplied phone number could not be parsed in E164 format")] @@ -27,6 +29,25 @@ impl ServiceAddress { .as_ref() .map(|pn| pn.format().mode(phonenumber::Mode::E164).to_string()) } + + pub async fn sub_device_sessions( + &self, + session_store: &dyn SessionStoreExt, + ) -> Result, ServiceError> { + let mut sub_device_sessions = Vec::new(); + if let Some(uuid) = &self.uuid { + sub_device_sessions.extend( + session_store + .get_sub_device_sessions(&uuid.to_string()) + .await?, + ); + } + if let Some(e164) = &self.e164() { + sub_device_sessions + .extend(session_store.get_sub_device_sessions(&e164).await?); + } + Ok(sub_device_sessions) + } } impl std::fmt::Display for ServiceAddress { diff --git a/libsignal-service/src/session_store.rs b/libsignal-service/src/session_store.rs index 33b149b8d..942a1cff8 100644 --- a/libsignal-service/src/session_store.rs +++ b/libsignal-service/src/session_store.rs @@ -1,20 +1,19 @@ +use async_trait::async_trait; use libsignal_protocol::{ProtocolAddress, SessionStore, SignalProtocolError}; /// This is additional functions required to handle /// session deletion. It might be a candidate for inclusion into /// the bigger `SessionStore` trait. +#[async_trait(?Send)] pub trait SessionStoreExt: SessionStore { - /// Use this to downcast as a regular `SessionStore` - fn as_mut_session_store(&mut self) -> &mut dyn SessionStore; - /// Get the IDs of all known devices with active sessions for a recipient. - fn get_sub_device_sessions( + async fn get_sub_device_sessions( &self, name: &str, ) -> Result, SignalProtocolError>; /// Remove a session record for a recipient ID + device ID tuple. - fn delete_session( + async fn delete_session( &self, address: &ProtocolAddress, ) -> Result<(), SignalProtocolError>; @@ -23,7 +22,7 @@ pub trait SessionStoreExt: SessionStore { /// ID. /// /// Returns the number of deleted sessions. - fn delete_all_sessions( + async fn delete_all_sessions( &self, address: &str, ) -> Result; From 4a5cfa29e065ec88c3d20916c784744e4659799e Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Fri, 30 Apr 2021 12:30:56 +0200 Subject: [PATCH 18/19] Change license to AGPLv3 --- LICENSE.md | 158 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 99 insertions(+), 59 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 94a045322..be3f7b28e 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,23 +1,21 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble - The GNU General Public License is a free, copyleft license for -software and other kinds of works. + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to +our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. +software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you @@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. The precise terms and conditions for copying, distribution and modification follow. @@ -72,7 +60,7 @@ modification follow. 0. Definitions. - "This License" refers to version 3 of the GNU General Public License. + "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. @@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. - 13. Use with the GNU Affero General Public License. + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single +under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General +Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published +GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's +versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. @@ -619,3 +617,45 @@ Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. From ead122e3466d147eedf936a9e970006542dccfa0 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Fri, 30 Apr 2021 12:31:08 +0200 Subject: [PATCH 19/19] Reflect license and copyright changes in README --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a37fde6b7..6c35ef2be 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,14 @@ If you're looking to contribute or want to ask a question, you're more than welc ## License -Copyright 2015-2019 Open Whisper Systems - -Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html +Copyright 2015-2019 Open Whisper Systems +Copyright 2020-2021 Signal Messenger, LLC +Copyright 2019-2021 Ruben De Smet +Copyright 2019-2021 Michael F Bryan +Copyright 2019-2021 Gabriel Féron +Copyright 2019-2021 Whisperfish contributors + +Licensed under the AGPLv3: http://www.gnu.org/licenses/agpl-3.0.html Additional Permissions For Submission to Apple App Store: Provided that you are otherwise in compliance with the GPLv3 for each covered work you convey