From 295c67c281e737d9855b5b6544ff26a96efd080c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Tue, 18 Jan 2022 23:55:54 +0100 Subject: [PATCH] Implement device name encryption/decryption --- libsignal-service-actix/src/push_service.rs | 1 + libsignal-service-hyper/src/push_service.rs | 5 + libsignal-service/Cargo.toml | 9 +- libsignal-service/protobuf/DeviceName.proto | 10 + libsignal-service/src/account_manager.rs | 179 +++++++++++++++++- libsignal-service/src/provisioning/manager.rs | 21 +- libsignal-service/src/provisioning/mod.rs | 7 +- libsignal-service/src/push_service.rs | 8 +- 8 files changed, 222 insertions(+), 18 deletions(-) create mode 100644 libsignal-service/protobuf/DeviceName.proto diff --git a/libsignal-service-actix/src/push_service.rs b/libsignal-service-actix/src/push_service.rs index 221612625..b45befc0d 100644 --- a/libsignal-service-actix/src/push_service.rs +++ b/libsignal-service-actix/src/push_service.rs @@ -84,6 +84,7 @@ impl AwcPushService { Err(ServiceError::RateLimitExceeded) } StatusCode::CONFLICT => { + dbg!(response.text()); let mismatched_devices = response.json().await.map_err(|e| { log::error!( diff --git a/libsignal-service-hyper/src/push_service.rs b/libsignal-service-hyper/src/push_service.rs index bf09fe8b5..e81f242c6 100644 --- a/libsignal-service-hyper/src/push_service.rs +++ b/libsignal-service-hyper/src/push_service.rs @@ -142,6 +142,9 @@ impl HyperPushService { Err(ServiceError::RateLimitExceeded) } StatusCode::CONFLICT => { + let v: serde_json::Value = Self::json(&mut response).await?; + println!("{:#?}", v); + let mismatched_devices = Self::json(&mut response).await.map_err(|e| { log::error!( @@ -302,6 +305,8 @@ impl PushService for HyperPushService { } })?; + println!("{}", serde_json::to_string_pretty(&value).unwrap()); + let mut response = self .request( Method::PUT, diff --git a/libsignal-service/Cargo.toml b/libsignal-service/Cargo.toml index 5baea3b3b..55753fea1 100644 --- a/libsignal-service/Cargo.toml +++ b/libsignal-service/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "libsignal-service" version = "0.1.0" -authors = ["Michael Bryan ", "Shady Khalifa "] +authors = [ + "Michael Bryan ", + "Shady Khalifa ", +] edition = "2018" license = "GPLv3" readme = "../README.md" @@ -31,7 +34,7 @@ aes-gcm = "0.9" block-modes = "0.8" rand = "0.7" -uuid = { version = "0.8", features = [ "serde" ] } +uuid = { version = "0.8", features = ["serde"] } phonenumber = "0.3" hkdf = "0.11" @@ -40,7 +43,7 @@ prost-build = "0.9" [dev-dependencies] anyhow = "1.0" -tokio = { version = "1.0", features = [ "macros" ] } +tokio = { version = "1.0", features = ["macros", "rt"] } rustls = "0.20" diff --git a/libsignal-service/protobuf/DeviceName.proto b/libsignal-service/protobuf/DeviceName.proto new file mode 100644 index 000000000..512d76505 --- /dev/null +++ b/libsignal-service/protobuf/DeviceName.proto @@ -0,0 +1,10 @@ +// Copyright 2018-2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +package signalservice; + +message DeviceName { + optional bytes ephemeralPublic = 1; + optional bytes syntheticIv = 2; + optional bytes ciphertext = 3; +} diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index e4489b78a..95bbbc08d 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -2,10 +2,16 @@ use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::time::SystemTime; +use aes::cipher::generic_array::GenericArray; +use aes::cipher::{NewCipher, StreamCipher}; +use aes::Aes256Ctr; +use hmac::{Hmac, Mac, NewMac}; use libsignal_protocol::{ - IdentityKeyStore, KeyPair, PreKeyRecord, PreKeyStore, PublicKey, - SignalProtocolError, SignedPreKeyRecord, SignedPreKeyStore, + IdentityKeyStore, KeyPair, PreKeyRecord, PreKeyStore, PrivateKey, + PublicKey, SignalProtocolError, SignedPreKeyRecord, SignedPreKeyStore, }; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; use zkgroup::profiles::ProfileKey; use crate::{ @@ -19,6 +25,7 @@ use crate::{ AccountAttributes, DeviceCapabilities, HttpAuthOverride, PushService, ServiceError, }, + utils::{serde_base64, serde_public_key}, }; pub struct AccountManager { @@ -410,6 +417,7 @@ impl AccountManager { unrestricted_unidentified_access: bool, discoverable_by_phone_number: bool, capabilities: DeviceCapabilities, + name: String, ) -> Result<(), ServiceError> { let attribs = AccountAttributes { signaling_key, @@ -422,10 +430,175 @@ impl AccountManager { unidentified_access_key, unrestricted_unidentified_access, discoverable_by_phone_number, - capabilities, + name, }; self.service.set_account_attributes(attribs).await?; Ok(()) } + + /// Update (encrypted) device name + pub async fn update_device_name( + &mut self, + device_name: &str, + public_key: &PublicKey, + ) -> Result<(), ServiceError> { + let encrypted_device_name: DeviceName = encrypt_device_name( + &mut rand::thread_rng(), + device_name, + public_key, + )?; + + let encrypted_device_name_proto: crate::proto::DeviceName = + encrypted_device_name.clone().into_proto()?; + + #[derive(Serialize)] + #[serde(rename_all = "camelCase")] + struct Data { + #[serde(with = "serde_base64")] + device_name: Vec, + } + + self.service + .put_json( + Endpoint::Service, + "/v1/accounts/name", + HttpAuthOverride::NoOverride, + Data { + device_name: prost::Message::encode_to_vec( + &encrypted_device_name_proto, + ), + }, + ) + .await?; + + Ok(()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeviceName { + #[serde(with = "serde_public_key")] + ephemeral_public: PublicKey, + #[serde(with = "serde_base64")] + synthetic_iv: Vec, + #[serde(with = "serde_base64")] + ciphertext: Vec, +} + +impl DeviceName { + pub(crate) fn into_proto( + &self, + ) -> Result { + Ok(crate::proto::DeviceName { + ephemeral_public: Some( + self.ephemeral_public.public_key_bytes()?.to_vec(), + ), + synthetic_iv: Some(self.synthetic_iv.to_vec()), + ciphertext: Some(self.ciphertext.clone()), + }) + } +} + +fn calculate_hmac256( + mac_key: &[u8], + ciphertext: &[u8], +) -> Result, ServiceError> { + let mut mac = Hmac::::new_from_slice(mac_key) + .map_err(|_| ServiceError::MacError)?; + mac.update(&ciphertext); + Ok(mac.finalize().into_bytes().to_vec()) +} + +pub fn encrypt_device_name( + csprng: &mut R, + device_name: &str, + identity_public: &PublicKey, +) -> Result { + let plaintext = device_name.as_bytes().to_vec(); + let ephemeral_key_pair = KeyPair::generate(csprng); + + let master_secret = ephemeral_key_pair + .private_key + .calculate_agreement(identity_public)?; + + let key1 = calculate_hmac256(&master_secret, b"auth")?; + let mut synthetic_iv = calculate_hmac256(&key1, &plaintext)?; + synthetic_iv.truncate(16); + + let key2 = calculate_hmac256(&master_secret, b"cipher")?; + let cipher_key = calculate_hmac256(&key2, &synthetic_iv)?; + + let mut ciphertext = plaintext; + let mut cipher = Aes256Ctr::new( + GenericArray::from_slice(&cipher_key), + GenericArray::from_slice(&[0u8; 16]), + ); + cipher.apply_keystream(&mut ciphertext); + + Ok(DeviceName { + ephemeral_public: ephemeral_key_pair.public_key, + synthetic_iv, + ciphertext, + }) +} + +pub fn decrypt_device_name( + private_key: &PrivateKey, + device_name: &DeviceName, +) -> Result { + let DeviceName { + ephemeral_public, + synthetic_iv, + ciphertext, + } = device_name; + + let master_secret = private_key.calculate_agreement(ephemeral_public)?; + let key2 = calculate_hmac256(&master_secret, b"cipher")?; + let cipher_key = calculate_hmac256(&key2, synthetic_iv)?; + + let mut plaintext = ciphertext.to_vec(); + let mut cipher = Aes256Ctr::new( + GenericArray::from_slice(&cipher_key), + GenericArray::from_slice(&[0u8; 16]), + ); + cipher.apply_keystream(&mut plaintext); + + let key1 = calculate_hmac256(&master_secret, b"auth")?; + let mut our_synthetic_iv = calculate_hmac256(&key1, &plaintext)?; + our_synthetic_iv.truncate(16); + + if synthetic_iv != &our_synthetic_iv { + Err(ServiceError::MacError) + } else { + Ok(String::from_utf8_lossy(&plaintext).to_string()) + } +} + +#[cfg(test)] +mod tests { + use bytes::Bytes; + use libsignal_protocol::{KeyPair, PrivateKey, PublicKey}; + + use super::{decrypt_device_name, encrypt_device_name, DeviceName}; + + #[test] + fn encrypt_decrypt_device_name() -> anyhow::Result<()> { + let input_device_name = "Nokia 3310 Millenial Edition"; + let mut csprng = rand::thread_rng(); + let identity = KeyPair::generate(&mut csprng); + + let device_name = encrypt_device_name( + &mut csprng, + &input_device_name, + &identity.public_key, + )?; + + let decrypted_device_name = + decrypt_device_name(&identity.private_key, &device_name)?; + + assert_eq!(input_device_name, decrypted_device_name); + + Ok(()) + } } diff --git a/libsignal-service/src/provisioning/manager.rs b/libsignal-service/src/provisioning/manager.rs index 95ab29019..6f6e14f88 100644 --- a/libsignal-service/src/provisioning/manager.rs +++ b/libsignal-service/src/provisioning/manager.rs @@ -1,7 +1,13 @@ +use aes::{ + cipher::{generic_array::GenericArray, NewCipher, StreamCipher}, + Aes256Ctr, +}; use futures::{channel::mpsc::Sender, pin_mut, SinkExt, StreamExt}; -use libsignal_protocol::{PrivateKey, PublicKey}; +use hmac::{Hmac, Mac, NewMac}; +use libsignal_protocol::{KeyPair, PrivateKey, PublicKey, SignalProtocolError}; use phonenumber::PhoneNumber; use serde::{Deserialize, Serialize}; +use sha2::Sha256; use url::Url; use uuid::Uuid; @@ -16,7 +22,7 @@ use crate::{ DeviceCapabilities, DeviceId, HttpAuthOverride, PushService, ServiceError, }, - utils::{serde_base64, serde_optional_base64}, + utils::{serde_base64, serde_optional_base64, serde_public_key}, }; /// Message received after confirming the SMS/voice code on registration. @@ -40,13 +46,14 @@ pub struct ConfirmCodeMessage { /// Message received when linking a new secondary device. #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ConfirmDeviceMessage { +pub(crate) struct ConfirmDeviceMessage { #[serde(with = "serde_base64")] pub signaling_key: Vec, pub supports_sms: bool, pub fetches_messages: bool, pub registration_id: u32, - pub name: String, + #[serde(with = "serde_base64", skip_serializing_if = "Vec::is_empty")] + pub name: Vec, } impl ConfirmCodeMessage { @@ -208,7 +215,7 @@ impl<'a, P: PushService + 'a> ProvisioningManager<'a, P> { .await } - pub async fn confirm_device( + pub(crate) async fn confirm_device( &mut self, confirm_code: u32, confirm_code_message: ConfirmDeviceMessage, @@ -291,7 +298,7 @@ impl LinkingManager

{ .await?; // see libsignal-protocol-c / signal_protocol_key_helper_generate_registration_id - let registration_id = csprng.gen_range(1, 16380); + let registration_id = csprng.gen_range(1, 256); let provisioning_pipe = ProvisioningPipe::from_socket(ws, stream)?; let provision_stream = provisioning_pipe.stream(); @@ -373,7 +380,7 @@ impl LinkingManager

{ supports_sms: false, fetches_messages: true, registration_id, - name: device_name.to_string(), + name: vec![], }, ) .await?; diff --git a/libsignal-service/src/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index 02e2d63ec..058308834 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -4,9 +4,8 @@ mod pipe; pub use cipher::ProvisioningCipher; pub use manager::{ - ConfirmCodeMessage, ConfirmCodeResponse, ConfirmDeviceMessage, - LinkingManager, ProvisioningManager, SecondaryDeviceProvisioning, - VerificationCodeResponse, + ConfirmCodeMessage, ConfirmCodeResponse, LinkingManager, + ProvisioningManager, SecondaryDeviceProvisioning, VerificationCodeResponse, }; use crate::prelude::ServiceError; @@ -31,6 +30,8 @@ pub enum ProvisioningError { ProtocolError(#[from] libsignal_protocol::error::SignalProtocolError), #[error("ProvisioningCipher in encrypt-only mode")] EncryptOnlyProvisioningCipher, + #[error(transparent)] + MacError(#[from] crate::sealed_session_cipher::MacError), } pub fn generate_registration_id( diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index 6d9c2de0c..65a8aaf4a 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -97,17 +97,21 @@ pub struct AccountAttributes { pub unrestricted_unidentified_access: bool, pub discoverable_by_phone_number: bool, pub capabilities: DeviceCapabilities, + pub name: String, } #[derive(Debug, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct DeviceCapabilities { - pub uuid: bool, + // pub uuid: bool, + // pub storage: bool, + pub announcement_group: bool, #[serde(rename = "gv2-3")] pub gv2: bool, - pub storage: bool, #[serde(rename = "gv1-migration")] pub gv1_migration: bool, + pub sender_key: bool, + pub change_number: bool, } #[derive(Clone)]