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 +. 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 diff --git a/libsignal-service-actix/Cargo.toml b/libsignal-service-actix/Cargo.toml index eeb0193e1..cd9ff12e2 100644 --- a/libsignal-service-actix/Cargo.toml +++ b/libsignal-service-actix/Cargo.toml @@ -4,11 +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/Michael-F-Bryan/libsignal-protocol-rs" } awc = { version = "3.0.0-beta.5", features=["rustls"] } actix = "0.11.1" @@ -22,7 +19,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" @@ -36,6 +33,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..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")] @@ -36,8 +32,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 +42,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-hyper/Cargo.toml b/libsignal-service-hyper/Cargo.toml index 0c8ef4406..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/Michael-F-Bryan/libsignal-protocol-rs" } async-trait = "0.1" base64 = "0.13" diff --git a/libsignal-service/Cargo.toml b/libsignal-service/Cargo.toml index 6594f26f2..aa3d4acd7 100644 --- a/libsignal-service/Cargo.toml +++ b/libsignal-service/Cargo.toml @@ -7,8 +7,8 @@ license = "GPLv3" readme = "../README.md" [dependencies] -libsignal-protocol = { git = "https://github.com/Michael-F-Bryan/libsignal-protocol-rs" } -zkgroup = { git = "https://github.com/signalapp/zkgroup" } +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"] } base64 = "0.13" @@ -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" uuid = { version = "0.8", features = [ "serde" ] } phonenumber = "0.3" @@ -40,6 +40,7 @@ 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 ba98f7529..8db3a25fe 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,7 @@ use crate::{ }, }; -use std::collections::HashMap; -use std::convert::TryFrom; -use std::time::SystemTime; - -use libsignal_protocol::keys::PublicKey; -use libsignal_protocol::{Context, StoreContext}; -use zkgroup::profiles::ProfileKey; - pub struct AccountManager { - context: Context, service: Service, profile_key: Option<[u8; 32]>, } @@ -42,7 +43,7 @@ pub enum LinkError { #[error("TsUrl has an invalid pub_key field")] InvalidPublicKey, #[error("Protocol error {0}")] - ProtocolError(#[from] libsignal_protocol::Error), + ProtocolError(#[from] SignalProtocolError), #[error(transparent)] ProvisioningError(#[from] ProvisioningError), } @@ -56,15 +57,11 @@ 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( - 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, } @@ -78,9 +75,13 @@ 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( + #[allow(clippy::clippy::too_many_arguments)] + pub async fn update_pre_key_bundle( &mut self, - store_context: StoreContext, + 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, next_signed_pre_key_id: u32, use_last_resort_key: bool, @@ -102,31 +103,51 @@ impl AccountManager { return Ok((pre_keys_offset_id, next_signed_pre_key_id)); } - 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(), - )?; - - 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); + pre_key_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) + .unwrap(); + + let signed_prekey_record = SignedPreKeyRecord::new( + 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( + next_signed_pre_key_id, + &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(), last_resort_key: if use_last_resort_key { Some(PreKeyEntity { key_id: 0x7fffffff, @@ -209,7 +230,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(); @@ -218,10 +239,11 @@ 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()?; + let identity_key_pair = + identity_store.get_identity_key_pair(None).await?; if credentials.uuid.is_none() { log::warn!("No local UUID set"); @@ -231,10 +253,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().into_vec(), ), identity_key_private: Some( - identity_key_pair.private().to_bytes()?.as_slice().to_vec(), + identity_key_pair.private_key().serialize(), ), number: Some(credentials.e164()), uuid: credentials.uuid.as_ref().map(|u| u.to_string()), @@ -248,8 +270,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/cipher.rs b/libsignal-service/src/cipher.rs index dd4f87f20..9c862c2d8 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,41 +22,47 @@ use crate::{ ServiceAddress, }; -use libsignal_protocol::{ - messages::{CiphertextType, PreKeySignalMessage, SignalMessage}, - Address as ProtocolAddress, Context, Deserializable, Serializable, - SessionCipher, StoreContext, -}; - -use block_modes::block_padding::{Iso7816, Padding}; -use prost::Message; - /// 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(crate) local_address: ServiceAddress, - sealed_session_cipher: SealedSessionCipher, +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, } -impl ServiceCipher { - pub fn from_context( - context: Context, - store_context: StoreContext, - local_address: ServiceAddress, +impl ServiceCipher +where + S: SessionStore + Clone, + I: IdentityKeyStore + Clone, + SP: SignedPreKeyStore + Clone, + P: PreKeyStore + Clone, + R: Rng + CryptoRng + Clone, +{ + pub fn new( + session_store: S, + identity_key_store: I, + signed_pre_key_store: SP, + pre_key_store: P, + csprng: R, certificate_validator: CertificateValidator, ) -> Self { Self { - context: context.clone(), - store_context: store_context.clone(), - local_address: local_address.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( - context, - store_context, - local_address, + session_store, + identity_key_store, + signed_pre_key_store, + pre_key_store, + csprng, certificate_validator, ), } @@ -53,17 +71,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 +95,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 +115,84 @@ impl ServiceCipher { let plaintext = match envelope.r#type() { Type::PrekeyBundle => { let sender = get_preferred_protocol_address( - &self.store_context, - envelope.source_address(), - envelope.source_device() as i32, - )?; + &self.session_store, + &envelope.source_address(), + envelope.source_device(), + ) + .await?; 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, }; - let cipher = SessionCipher::new( - &self.context, - &self.store_context, + + 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 self.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(), - envelope.source_device() as i32, - )?; + &self.session_store, + &envelope.source_address(), + envelope.source_device(), + ) + .await?; 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, }; - let mut data = SessionCipher::new( - &self.context, - &self.store_context, + + 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 self.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 +204,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 +233,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 +242,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 +340,20 @@ fn strip_padding( } /// Equivalent of `SignalServiceCipher::getPreferredProtocolAddress` -pub fn get_preferred_protocol_address( - store_context: &StoreContext, - address: ServiceAddress, - device_id: i32, -) -> Result { +pub async fn get_preferred_protocol_address( + 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, None).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, None).await?.is_some() { return Ok(address); } if cfg!(feature = "prefer-e164") { @@ -318,5 +368,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 8a4ebd99b..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::{keys::PublicKey, Context}; +use libsignal_protocol::PublicKey; use url::Url; use zkgroup::ServerPublicParams; @@ -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 { @@ -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/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..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::{Context, Error}; +use libsignal_protocol::error::SignalProtocolError; use zkgroup::groups::GroupMasterKey; use zkgroup::GROUP_MASTER_KEY_LEN; @@ -6,17 +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 { +) -> 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/lib.rs b/libsignal-service/src/lib.rs index 3e695e790..29c2d1bee 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; +mod session_store; pub mod utils; pub use crate::account_manager::{ @@ -59,4 +60,14 @@ 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 crate::session_store::SessionStoreExt; + 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/cipher.rs b/libsignal-service/src/provisioning/cipher.rs index d44c757eb..26bced3be 100644 --- a/libsignal-service/src/provisioning/cipher.rs +++ b/libsignal-service/src/provisioning/cipher.rs @@ -1,16 +1,14 @@ +use std::fmt::{self, Debug}; + 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::{ - keys::{KeyPair, PublicKey}, - Context, -}; - pub use crate::proto::{ ProvisionEnvelope, ProvisionMessage, ProvisioningVersion, }; @@ -20,17 +18,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 +51,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 +93,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 +124,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 +139,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 +158,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]; @@ -197,11 +201,11 @@ mod tests { use super::*; #[test] - fn encrypt_provisioning_roundtrip() { - let ctx = Context::default(); - let cipher = ProvisioningCipher::new(ctx.clone()).unwrap(); + fn encrypt_provisioning_roundtrip() -> anyhow::Result<()> { + let mut rng = rand::thread_rng(); + let cipher = ProvisioningCipher::generate(&mut rng)?; let encrypt_cipher = - ProvisioningCipher::from_public(ctx, cipher.public_key()); + ProvisioningCipher::from_public(cipher.public_key().clone()); assert_eq!( cipher.public_key(), @@ -210,14 +214,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 c67944995..a5da6eac4 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::{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,9 @@ impl LinkingManager

{ } } - pub async fn provision_secondary_device( + pub async fn provision_secondary_device( &mut self, - ctx: &Context, + csprng: &mut R, signaling_key: SignalingKey, device_name: &str, mut tx: Sender, @@ -297,10 +292,10 @@ 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)?; + 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 { @@ -324,8 +319,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 +327,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/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index 5d2ee23e9..b20fbb017 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -27,7 +27,13 @@ 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, } + +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 b2bcc4af3..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,12 +41,13 @@ impl ProvisioningPipe { pub fn from_socket( ws: WS, stream: WS::Stream, - ctx: &Context, ) -> Result { Ok(ProvisioningPipe { ws, stream, - provisioning_cipher: ProvisioningCipher::new(ctx.clone())?, + provisioning_cipher: ProvisioningCipher::generate( + &mut rand::thread_rng(), + )?, }) } @@ -150,9 +149,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 feb583b5e..a3fde4c54 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -12,13 +12,14 @@ use crate::{ ServiceAddress, }; -use libsignal_protocol::{keys::PublicKey, Context, PreKeyBundle}; - use aes_gcm::{ aead::{generic_array::GenericArray, Aead}, Aes256Gcm, NewAead, }; use chrono::prelude::*; +use libsignal_protocol::{ + error::SignalProtocolError, IdentityKey, PreKeyBundle, PublicKey, +}; use prost::Message as ProtobufMessage; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -61,12 +62,12 @@ 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")] pub struct DeviceId { - pub device_id: i32, + pub device_id: u32, } #[derive(Debug, Serialize, Deserialize)] @@ -163,23 +164,45 @@ 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 { - pub missing_devices: Vec, - pub extra_devices: Vec, + pub missing_devices: Vec, + pub extra_devices: Vec, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct StaleDevices { - pub stale_devices: Vec, + pub stale_devices: Vec, } #[derive(Debug, Deserialize)] @@ -268,7 +291,7 @@ pub enum ServiceError { MacError, #[error("Protocol error: {0}")] - SignalProtocolError(#[from] libsignal_protocol::Error), + SignalProtocolError(#[from] SignalProtocolError), #[error("{0:?}")] MismatchedDevicesException(MismatchedDevices), @@ -554,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!( @@ -574,35 +596,15 @@ pub trait PushService { .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( &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()), @@ -625,31 +627,9 @@ pub trait PushService { .get_json(Endpoint::Service, &path, HttpAuthOverride::NoOverride) .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)?); } Ok(pre_keys) } 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 385300618..08d994a0b 100644 --- a/libsignal-service/src/sealed_session_cipher.rs +++ b/libsignal-service/src/sealed_session_cipher.rs @@ -1,20 +1,21 @@ -use phonenumber::PhoneNumber; -use uuid::Uuid; +use std::convert::TryFrom; use aes_ctr::{ cipher::stream::{NewStreamCipher, StreamCipher}, Aes256Ctr, }; - use hmac::{Hmac, Mac, NewMac}; use libsignal_protocol::{ - keys::{PrivateKey, PublicKey}, - messages::{CiphertextType, PreKeySignalMessage, SignalMessage}, - Address as ProtocolAddress, Context, Deserializable, Serializable, - SessionCipher, StoreContext, + error::SignalProtocolError, message_decrypt_prekey, message_decrypt_signal, + message_encrypt, CiphertextMessageType, IdentityKeyStore, KeyPair, + PreKeySignalMessage, PreKeyStore, PrivateKey, ProtocolAddress, PublicKey, + SessionStore, SignalMessage, SignedPreKeyStore, HKDF, }; use log::error; +use phonenumber::PhoneNumber; +use rand::{CryptoRng, Rng}; use sha2::Sha256; +use uuid::Uuid; use crate::{push_service::ProfileKey, ServiceAddress}; @@ -42,7 +43,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, @@ -65,10 +66,12 @@ pub enum MacError { } #[derive(Clone)] -pub(crate) struct SealedSessionCipher { - context: Context, - store_context: StoreContext, - local_address: ServiceAddress, +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, } @@ -93,7 +96,7 @@ struct UnidentifiedSenderMessage { #[derive(Debug, Clone)] pub struct UnidentifiedSenderMessageContent { - r#type: CiphertextType, + r#type: CiphertextMessageType, sender_certificate: SenderCertificate, content: Vec, } @@ -102,7 +105,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 +143,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, } @@ -160,10 +163,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 +184,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 +199,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,17 +208,28 @@ impl UnidentifiedSenderMessage { } } -impl SealedSessionCipher { +impl SealedSessionCipher +where + S: SessionStore, + I: IdentityKeyStore, + SP: SignedPreKeyStore, + P: PreKeyStore, + R: Rng + CryptoRng, +{ pub(crate) fn new( - context: Context, - store_context: StoreContext, - local_address: ServiceAddress, + session_store: S, + identity_key_store: I, + signed_pre_key_store: SP, + pre_key_store: P, + csprng: R, certificate_validator: CertificateValidator, ) -> Self { Self { - context, - store_context, - local_address, + session_store, + identity_key_store, + signed_pre_key_store, + pre_key_store, + csprng, certificate_validator, } } @@ -231,43 +237,47 @@ 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)?; + let ephemeral = KeyPair::generate(&mut self.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 +287,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 +305,33 @@ 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 +341,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 +357,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 +371,13 @@ 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_salted_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 +392,13 @@ 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_salted_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,51 @@ impl SealedSessionCipher { sender_certificate, } = message; let sender = crate::cipher::get_preferred_protocol_address( - &self.store_context, - sender_certificate.address(), + &self.session_store, + &sender_certificate.address(), sender_certificate.sender_device_id, - )?; - let session_cipher = - SessionCipher::new(&self.context, &self.store_context, &sender)?; + ) + .await?; + 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 self.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 self.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 +523,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 +533,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 +548,6 @@ impl UnidentifiedSenderMessageContent { } }, sender_certificate: SenderCertificate::try_from( - &context, sender_certificate, )?, content, @@ -532,8 +566,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 +590,6 @@ impl UnidentifiedSenderMessageContent { impl SenderCertificate { fn try_from( - context: &Context, wrapper: crate::proto::SenderCertificate, ) -> Result { use crate::proto::sender_certificate::Certificate; @@ -591,16 +624,11 @@ 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: sender_device_id as i32, + sender_device_id, expiration: expires, certificate, signature, @@ -624,7 +652,6 @@ impl SenderCertificate { impl ServerCertificate { fn try_from( - context: &Context, wrapper: crate::proto::ServerCertificate, ) -> Result { use crate::proto::server_certificate; @@ -637,7 +664,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, }), @@ -660,26 +687,28 @@ impl CertificateValidator { validation_time: u64, ) -> Result<(), SealedSessionError> { let server_certificate = &certificate.signer; - self.trust_root - .verify_signature( - &server_certificate.certificate, - &server_certificate.signature, - ) - .map_err(|e| { - error!("failed to verify server certificate: {}", e); - SealedSessionError::InvalidCertificate - })?; - 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 - })?; + { + Err(_) | Ok(false) => { + return Err(SealedSessionError::InvalidCertificate) + } + _ => (), + } if validation_time > certificate.expiration { - error!("certificate is expired"); return Err(SealedSessionError::ExpiredCertificate); } @@ -689,21 +718,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, SignedPreKeyRecord, + SignedPreKeyStore, }; - use crate::ServiceAddress; + use crate::{provisioning::generate_registration_id, ServiceAddress}; use super::{ CertificateValidator, SealedSessionCipher, SealedSessionError, @@ -720,51 +745,62 @@ 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_pre_key_store: InMemSignedPreKeyStore, + pre_key_store: InMemPreKeyStore, } - #[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)?; + #[tokio::test] + async fn test_encrypt_decrypt() -> 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_pre_key_store, + alice_stores.pre_key_store, + csprng, 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, - 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_pre_key_store, + bob_stores.pre_key_store, + csprng, 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), @@ -777,49 +813,59 @@ 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_pre_key_store, + alice_stores.pre_key_store, + csprng, certificate_validator, ); - let ciphertext = alice_cipher.encrypt( - &ProtocolAddress::new("+14152222222", 1), - sender_certificate, - "и вот я".as_bytes(), - )?; - - let bob_cipher = SealedSessionCipher::new( - ctx, - bob_store_context, - bob_address(), + let ciphertext = alice_cipher + .encrypt( + &ProtocolAddress::new("+14152222222".into(), 1), + sender_certificate, + "и вот я".as_bytes(), + ) + .await?; + + let mut bob_cipher = SealedSessionCipher::new( + bob_stores.session_store, + bob_stores.identity_key_store, + bob_stores.signed_pre_key_store, + bob_stores.pre_key_store, + csprng, false_certificate_validator, ); - let plaintext = bob_cipher.decrypt(&ciphertext, 31335); + let plaintext = bob_cipher.decrypt(&ciphertext, 31335).await; match plaintext { Err(SealedSessionError::InvalidCertificate) => Ok(()), @@ -827,132 +873,150 @@ 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_pre_key_store, + alice_stores.pre_key_store, + csprng, 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, - 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_pre_key_store, + bob_stores.pre_key_store, + csprng, 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_pre_key_store, + alice_stores.pre_key_store, + csprng, 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, - 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_pre_key_store, + bob_stores.pre_key_store, + csprng, 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_pre_key_store: InMemSignedPreKeyStore::new(), + pre_key_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_pre_key_store: InMemSignedPreKeyStore::new(), + pre_key_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(); @@ -960,22 +1024,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![]; @@ -983,62 +1042,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)?; - SenderCertificate::try_from( - &context, + Ok(SenderCertificate::try_from( 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, - 2, - UNIX_EPOCH, - )?; + 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_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), + let bob_signed_pre_key_record = SignedPreKeyRecord::new( + 2, + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64, + &signed_pre_key_pair, + &signed_pre_key_signature, ); - alice_session_builder.process_pre_key_bundle(&bob_bundle)?; - bob_store_context.store_signed_pre_key(&bob_signed_pre_key)?; - bob_store_context.store_pre_key(&PreKey::new(1, &bob_pre_key)?)?; + let bob_bundle = PreKeyBundle::new( + 1, + 1, + Some((1, bob_pre_key.public_key()?)), + 2, + signed_pre_key_pair.public_key, + signed_pre_key_signature, + *bob_identity_key_pair.identity_key(), + )?; + + 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_stores + .signed_pre_key_store + .save_signed_pre_key(2, &bob_signed_pre_key_record, None) + .await?; + bob_stores + .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 cebdb7105..b293b1218 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -1,19 +1,25 @@ 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 chrono::prelude::*; -use libsignal_protocol::SessionBuilder; +use libsignal_protocol::{ + process_prekey_bundle, IdentityKeyStore, PreKeyStore, ProtocolAddress, + SessionStore, SignalProtocolError, SignedPreKeyStore, +}; 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}; @@ -22,7 +28,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,10 +72,14 @@ pub struct AttachmentSpec { /// Equivalent of Java's `SignalServiceMessageSender`. #[derive(Clone)] -pub struct MessageSender { +pub struct MessageSender { service: Service, - cipher: ServiceCipher, - device_id: i32, + cipher: ServiceCipher, + csprng: R, + session_store: S, + identity_key_store: I, + local_address: ServiceAddress, + device_id: u32, } #[derive(thiserror::Error, Debug)] @@ -86,7 +96,7 @@ pub enum MessageSenderError { #[error("{0}")] ServiceError(#[from] ServiceError), #[error("protocol error: {0}")] - ProtocolError(#[from] libsignal_protocol::Error), + ProtocolError(#[from] SignalProtocolError), #[error("Failed to upload attachment {0}")] AttachmentUploadError(#[from] AttachmentUploadError), @@ -112,18 +122,31 @@ 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, - device_id: i32, + cipher: ServiceCipher, + csprng: R, + session_store: S, + identity_key_store: I, + local_address: ServiceAddress, + device_id: u32, ) -> Self { MessageSender { service, cipher, + csprng, + session_store, + identity_key_store, + local_address, device_id, } } @@ -326,7 +349,7 @@ where timestamp, ); self.try_send_message( - (&self.cipher.local_address).clone(), + (&self.local_address).clone(), None, &sync_message, timestamp, @@ -340,12 +363,12 @@ 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())?; + self.session_store + .delete_all_sessions(&uuid.to_string()) + .await?; } if let Some(e164) = recipient.e164() { - self.cipher.store_context.delete_all_sessions(&e164)?; + self.session_store.delete_all_sessions(&e164).await?; } } @@ -400,7 +423,7 @@ where ); self.try_send_message( - self.cipher.local_address.clone(), + self.local_address.clone(), unidentified_access, &sync_message, timestamp, @@ -461,21 +484,21 @@ 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( + if let Some(uuid) = recipient.uuid { + self.session_store + .delete_session(&ProtocolAddress::new( uuid.to_string(), *extra_device_id, - ), - )?; + )) + .await?; } if let Some(e164) = recipient.e164() { - self.cipher.store_context.delete_session( - &libsignal_protocol::Address::new( - &e164, + self.session_store + .delete_session(&ProtocolAddress::new( + e164, *extra_device_id, - ), - )?; + )) + .await?; } } @@ -484,23 +507,24 @@ 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, - ), + + process_prekey_bundle( + &remote_address, + &mut self.session_store, + &mut self.identity_key_store, + &pre_key, + &mut self.csprng, + None, ) - .process_pre_key_bundle(&pre_key) + .await .map_err(|e| { log::error!("failed to create session: {}", e); MessageSenderError::UntrustedIdentity { @@ -517,20 +541,20 @@ where extra_device_id ); if let Some(ref uuid) = recipient.uuid { - self.cipher.store_context.delete_session( - &libsignal_protocol::Address::new( + self.session_store + .delete_session(&ProtocolAddress::new( uuid.to_string(), *extra_device_id, - ), - )?; + )) + .await?; } if let Some(e164) = recipient.e164() { - self.cipher.store_context.delete_session( - &libsignal_protocol::Address::new( + self.session_store + .delete_session(&ProtocolAddress::new( e164, *extra_device_id, - ), - )?; + )) + .await?; } } } @@ -620,7 +644,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( @@ -634,30 +658,17 @@ where ); } - // XXX maybe refactor this in a method, this is probably something we need on every call to - // get_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)?, - ); - } - - 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.cipher.store_context, - recipient.clone(), + &self.session_store, + recipient, device_id, - )?; - if self.cipher.store_context.contains_session(&ppa)? { + ) + .await?; + if self.session_store.load_session(&ppa, None).await?.is_some() { messages.push( self.create_encrypted_message( recipient, @@ -680,53 +691,57 @@ 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( - &self.cipher.store_context, - recipient.clone(), + &self.session_store, + recipient, device_id, - )?; + ) + .await?; log::trace!("encrypting message for {:?}", recipient_address); - if !self - .cipher - .store_context - .contains_session(&recipient_address)? + if self + .session_store + .load_session(&recipient_address, None) + .await? + .is_none() { 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() + if recipient.matches(&self.local_address) + && self.device_id == pre_key_bundle.device_id()? { trace!("not establishing a session with myself!"); continue; } let pre_key_address = get_preferred_protocol_address( - &self.cipher.store_context, - recipient.clone(), - pre_key_bundle.device_id(), - )?; - let session_builder = SessionBuilder::new( - &self.cipher.context, - &self.cipher.store_context, + &self.session_store, + recipient, + pre_key_bundle.device_id()?, + ) + .await?; + + process_prekey_bundle( &pre_key_address, - ); - session_builder.process_pre_key_bundle(&pre_key_bundle)?; + &mut self.session_store, + &mut self.identity_key_store, + &pre_key_bundle, + &mut self.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) } 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 new file mode 100644 index 000000000..942a1cff8 --- /dev/null +++ b/libsignal-service/src/session_store.rs @@ -0,0 +1,29 @@ +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 { + /// Get the IDs of all known devices with active sessions for a recipient. + async fn get_sub_device_sessions( + &self, + name: &str, + ) -> Result, SignalProtocolError>; + + /// Remove a session record for a recipient ID + device ID tuple. + async 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. + async fn delete_all_sessions( + &self, + address: &str, + ) -> Result; +} 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)) } }