Skip to content

Commit

Permalink
Implement device name encryption/decryption
Browse files Browse the repository at this point in the history
  • Loading branch information
gferon committed Jan 18, 2022
1 parent 8523ba2 commit 295c67c
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 18 deletions.
1 change: 1 addition & 0 deletions libsignal-service-actix/src/push_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
5 changes: 5 additions & 0 deletions libsignal-service-hyper/src/push_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down Expand Up @@ -302,6 +305,8 @@ impl PushService for HyperPushService {
}
})?;

println!("{}", serde_json::to_string_pretty(&value).unwrap());

let mut response = self
.request(
Method::PUT,
Expand Down
9 changes: 6 additions & 3 deletions libsignal-service/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
[package]
name = "libsignal-service"
version = "0.1.0"
authors = ["Michael Bryan <[email protected]>", "Shady Khalifa <[email protected]>"]
authors = [
"Michael Bryan <[email protected]>",
"Shady Khalifa <[email protected]>",
]
edition = "2018"
license = "GPLv3"
readme = "../README.md"
Expand Down Expand Up @@ -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"

Expand All @@ -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"

Expand Down
10 changes: 10 additions & 0 deletions libsignal-service/protobuf/DeviceName.proto
Original file line number Diff line number Diff line change
@@ -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;
}
179 changes: 176 additions & 3 deletions libsignal-service/src/account_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -19,6 +25,7 @@ use crate::{
AccountAttributes, DeviceCapabilities, HttpAuthOverride, PushService,
ServiceError,
},
utils::{serde_base64, serde_public_key},
};

pub struct AccountManager<Service> {
Expand Down Expand Up @@ -410,6 +417,7 @@ impl<Service: PushService> AccountManager<Service> {
unrestricted_unidentified_access: bool,
discoverable_by_phone_number: bool,
capabilities: DeviceCapabilities,
name: String,
) -> Result<(), ServiceError> {
let attribs = AccountAttributes {
signaling_key,
Expand All @@ -422,10 +430,175 @@ impl<Service: PushService> AccountManager<Service> {
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<u8>,
}

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<u8>,
#[serde(with = "serde_base64")]
ciphertext: Vec<u8>,
}

impl DeviceName {
pub(crate) fn into_proto(
&self,
) -> Result<crate::proto::DeviceName, SignalProtocolError> {
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<Vec<u8>, ServiceError> {
let mut mac = Hmac::<Sha256>::new_from_slice(mac_key)
.map_err(|_| ServiceError::MacError)?;
mac.update(&ciphertext);
Ok(mac.finalize().into_bytes().to_vec())
}

pub fn encrypt_device_name<R: rand::Rng + rand::CryptoRng>(
csprng: &mut R,
device_name: &str,
identity_public: &PublicKey,
) -> Result<DeviceName, ServiceError> {
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<String, ServiceError> {
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(())
}
}
21 changes: 14 additions & 7 deletions libsignal-service/src/provisioning/manager.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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.
Expand All @@ -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<u8>,
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<u8>,
}

impl ConfirmCodeMessage {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -291,7 +298,7 @@ impl<P: PushService> LinkingManager<P> {
.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();
Expand Down Expand Up @@ -373,7 +380,7 @@ impl<P: PushService> LinkingManager<P> {
supports_sms: false,
fetches_messages: true,
registration_id,
name: device_name.to_string(),
name: vec![],
},
)
.await?;
Expand Down
7 changes: 4 additions & 3 deletions libsignal-service/src/provisioning/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<R: rand::Rng + rand::CryptoRng>(
Expand Down
8 changes: 6 additions & 2 deletions libsignal-service/src/push_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down

0 comments on commit 295c67c

Please sign in to comment.