Skip to content

Commit

Permalink
Implement device name encryption/decryption/update
Browse files Browse the repository at this point in the history
  • Loading branch information
gferon committed Aug 14, 2022
1 parent 1508fa8 commit 5109b7b
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 16 deletions.
2 changes: 1 addition & 1 deletion libsignal-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ prost-build = "0.10"

[dev-dependencies]
anyhow = "1.0"
tokio = { version = "1.0", features = ["rt", "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;
}
176 changes: 174 additions & 2 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};
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 @@ -18,6 +24,7 @@ use crate::{
push_service::{
AccountAttributes, HttpAuthOverride, PushService, ServiceError,
},
utils::{serde_base64, serde_public_key},
};

pub struct AccountManager<Service> {
Expand Down Expand Up @@ -401,4 +408,169 @@ impl<Service: PushService> AccountManager<Service> {
) -> Result<(), ServiceError> {
self.service.set_account_attributes(attributes).await
}

/// 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(())
}
}
1 change: 1 addition & 0 deletions libsignal-service/src/cipher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ where
P: PreKeyStore + Clone,
R: Rng + CryptoRng + Clone,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
session_store: S,
identity_key_store: I,
Expand Down
6 changes: 3 additions & 3 deletions libsignal-service/src/groups_v2/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl fmt::Debug for Member {
}
}

#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingMember {
pub uuid: Uuid,
pub role: Role,
Expand Down Expand Up @@ -185,12 +185,12 @@ impl fmt::Debug for GroupChange {
}
}

#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Timer {
pub duration: u32,
}

#[derive(Clone, PartialEq)]
#[derive(Clone, PartialEq, Eq)]
pub struct GroupJoinInfo {
pub title: String,
pub avatar: String,
Expand Down
3 changes: 2 additions & 1 deletion libsignal-service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod messagepipe;
pub mod models;
pub mod pre_keys;
pub mod profile_name;
#[allow(clippy::derive_partial_eq_without_eq)]
pub mod proto;
pub mod provisioning;
pub mod push_service;
Expand All @@ -25,7 +26,7 @@ mod session_store;
pub mod utils;

pub use crate::account_manager::{
AccountManager, Profile, ProfileManagerError,
decrypt_device_name, AccountManager, Profile, ProfileManagerError,
};
pub use crate::service_address::*;

Expand Down
18 changes: 13 additions & 5 deletions libsignal-service/src/provisioning/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,21 @@ use crate::{
/// 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,
// FIXME: the name goes back here when we send this via the websocket
//pub name: String,
#[serde(with = "serde_base64", skip_serializing_if = "Vec::is_empty")]
pub name: Vec<u8>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConfirmCodeResponse {
pub uuid: Uuid,
pub storage_capable: bool,
}

#[derive(Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -151,7 +158,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 @@ -233,7 +240,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 @@ -315,6 +322,7 @@ impl<P: PushService> LinkingManager<P> {
supports_sms: false,
fetches_messages: true,
registration_id,
name: vec![],
},
)
.await?;
Expand Down
5 changes: 3 additions & 2 deletions libsignal-service/src/provisioning/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ mod pipe;

pub use cipher::ProvisioningCipher;
pub use manager::{
LinkingManager, ProvisioningManager, SecondaryDeviceProvisioning,
VerificationCodeResponse, VerifyAccountResponse,
ConfirmCodeResponse, LinkingManager, ProvisioningManager,
SecondaryDeviceProvisioning, VerificationCodeResponse,
VerifyAccountResponse,
};

use crate::prelude::ServiceError;
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 5109b7b

Please sign in to comment.