diff --git a/libsignal-service-actix/examples/link.rs b/libsignal-service-actix/examples/link.rs deleted file mode 100644 index 131724983..000000000 --- a/libsignal-service-actix/examples/link.rs +++ /dev/null @@ -1,98 +0,0 @@ -use anyhow::Error; -use futures::{channel::mpsc::channel, future, StreamExt}; -use image::Luma; -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; - -#[derive(Debug, StructOpt)] -struct Args { - #[structopt(long = "servers", short = "s", default_value = "staging")] - servers: SignalServers, -} - -#[actix_rt::main] -async fn main() -> Result<(), Error> { - env_logger::builder() - .format_timestamp(None) - .filter_level(LevelFilter::Info) - .init(); - let args = Args::from_args(); - - // generate a random 16 bytes password - let password: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(24) - .map(char::from) - .collect(); - - // generate a 52 bytes signaling key - let mut signaling_key = [0u8; 52]; - rand::thread_rng().fill_bytes(&mut signaling_key); - log::info!("generated signaling key: {}", base64::encode(signaling_key)); - - let push_service = - AwcPushService::new(args.servers, None, USER_AGENT.into()); - - let mut provision_manager: LinkingManager = - LinkingManager::new(push_service, password); - - let (tx, mut rx) = channel(1); - - let (fut1, fut2) = future::join( - provision_manager.provision_secondary_device( - &mut rand::thread_rng(), - signaling_key, - tx, - ), - async move { - while let Some(provisioning_step) = rx.next().await { - match provisioning_step { - SecondaryDeviceProvisioning::Url(url) => { - log::info!( - "generating qrcode from provisioning link: {}", - &url - ); - let code = QrCode::new(url.as_str()) - .expect("failed to generate qrcode"); - let image = code.render::>().build(); - let path = std::env::temp_dir().join("device-link.png"); - image.save(&path)?; - opener::open(path)?; - }, - SecondaryDeviceProvisioning::NewDeviceRegistration { - phone_number: _, - device_id: _, - registration_id: _, - service_ids, - profile_key: _, - aci_private_key: _, - aci_public_key: _, - pni_private_key: _, - pni_public_key: _, - pni_registration_id: _, - } => { - log::info!( - "successfully registered device {}", - &service_ids - ); - // here you would store all of this data somehow to use it later! - }, - } - } - Result::Ok::<(), Error>(()) - }, - ) - .await; - - fut1?; - fut2?; - - Ok(()) -} diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 59355eaeb..35dc382dc 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -11,11 +11,13 @@ use libsignal_protocol::{ PreKeyRecord, PrivateKey, ProtocolStore, PublicKey, SignalProtocolError, SignedPreKeyRecord, }; +use prost::Message; use serde::{Deserialize, Serialize}; use sha2::Sha256; use zkgroup::profiles::ProfileKey; use crate::pre_keys::KyberPreKeyEntity; +use crate::proto::DeviceName; use crate::push_service::{AvatarWrite, RecaptchaAttributes, ServiceIdType}; use crate::ServiceAddress; use crate::{ @@ -28,7 +30,7 @@ use crate::{ push_service::{ AccountAttributes, HttpAuthOverride, PushService, ServiceError, }, - utils::{serde_base64, serde_public_key}, + utils::serde_base64, }; pub struct AccountManager { @@ -259,8 +261,6 @@ impl AccountManager { destination: &str, env: ProvisionEnvelope, ) -> Result<(), ServiceError> { - use prost::Message; - #[derive(serde::Serialize)] struct ProvisioningMessage { body: String, @@ -465,15 +465,12 @@ impl AccountManager { device_name: &str, public_key: &PublicKey, ) -> Result<(), ServiceError> { - let encrypted_device_name: DeviceName = encrypt_device_name( + let encrypted_device_name = 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 { @@ -488,9 +485,7 @@ impl AccountManager { &[], HttpAuthOverride::NoOverride, Data { - device_name: prost::Message::encode_to_vec( - &encrypted_device_name_proto, - ), + device_name: encrypted_device_name.encode_to_vec(), }, ) .await?; @@ -526,30 +521,6 @@ impl AccountManager { } } -#[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], @@ -586,11 +557,15 @@ pub fn encrypt_device_name( ); cipher.apply_keystream(&mut ciphertext); - Ok(DeviceName { - ephemeral_public: ephemeral_key_pair.public_key, - synthetic_iv, - ciphertext, - }) + let device_name = DeviceName { + ephemeral_public: Some( + ephemeral_key_pair.public_key.serialize().to_vec(), + ), + synthetic_iv: Some(synthetic_iv.to_vec()), + ciphertext: Some(ciphertext), + }; + + Ok(device_name) } pub fn decrypt_device_name( @@ -598,12 +573,17 @@ pub fn decrypt_device_name( device_name: &DeviceName, ) -> Result { let DeviceName { - ephemeral_public, - synthetic_iv, - ciphertext, - } = device_name; + ephemeral_public: Some(ephemeral_public), + synthetic_iv: Some(synthetic_iv), + ciphertext: Some(ciphertext), + } = device_name + else { + return Err(ServiceError::InvalidDeviceName); + }; + + let ephemeral_public = PublicKey::deserialize(ephemeral_public)?; - let master_secret = private_key.calculate_agreement(ephemeral_public)?; + 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)?; @@ -661,11 +641,11 @@ mod tests { )?)?; let device_name = DeviceName { - ephemeral_public: ephemeral_public_key, - synthetic_iv: base64::decode("86gekHGmltnnZ9QARhiFcg==")?, - ciphertext: base64::decode( + ephemeral_public: Some(ephemeral_public_key.serialize().to_vec()), + synthetic_iv: Some(base64::decode("86gekHGmltnnZ9QARhiFcg==")?), + ciphertext: Some(base64::decode( "MtJ9/9KBWLBVAxfZJD4pLKzP4q+iodRJeCc+/A==", - )?, + )?), }; let decrypted_device_name = diff --git a/libsignal-service/src/pre_keys.rs b/libsignal-service/src/pre_keys.rs index 4dc86e13b..c1c45ca6d 100644 --- a/libsignal-service/src/pre_keys.rs +++ b/libsignal-service/src/pre_keys.rs @@ -1,13 +1,48 @@ -use std::convert::TryFrom; +use std::{convert::TryFrom, time::SystemTime}; use crate::utils::{serde_base64, serde_public_key}; use libsignal_protocol::{ - error::SignalProtocolError, GenericSignedPreKey, KyberPreKeyRecord, - PreKeyRecord, PublicKey, SignedPreKeyRecord, + error::SignalProtocolError, kem, GenericSignedPreKey, KeyPair, + KyberPreKeyRecord, KyberPreKeyStore, PreKeyRecord, PreKeyStore, PublicKey, + SignedPreKeyRecord, SignedPreKeyStore, }; use serde::{Deserialize, Serialize}; +/// Stores the ID of keys published ahead of time +/// +/// +pub trait PreKeysStore: + PreKeyStore + SignedPreKeyStore + KyberPreKeyStore +{ + /// ID of the next pre key + fn pre_keys_offset_id(&self) -> Result; + + /// ID of the next signed pre key + fn next_signed_pre_key_id(&self) -> Result; + + /// ID of the next PQ pre key + fn next_pq_pre_key_id(&self) -> Result; + + /// set the ID of the next pre key + fn set_pre_keys_offset_id( + &mut self, + id: u32, + ) -> Result<(), SignalProtocolError>; + + /// set the ID of the next signed pre key + fn set_next_signed_pre_key_id( + &mut self, + id: u32, + ) -> Result<(), SignalProtocolError>; + + /// set the ID of the next PQ pre key + fn set_next_pq_pre_key_id( + &mut self, + id: u32, + ) -> Result<(), SignalProtocolError>; +} + #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PreKeyEntity { @@ -92,3 +127,51 @@ pub struct PreKeyState { pub pq_last_resort_key: Option, pub pq_pre_keys: Vec, } + +pub(crate) async fn generate_last_resort_kyber_key( + store: &mut S, + identity_key: &KeyPair, +) -> Result { + let id = store.next_pq_pre_key_id()?; + let id = id.max(1); // TODO: Hack, keys start with 1 + + let record = KyberPreKeyRecord::generate( + kem::KeyType::Kyber1024, + id.into(), + &identity_key.private_key, + )?; + + store.save_kyber_pre_key(id.into(), &record).await?; + store.set_next_pq_pre_key_id(id + 1)?; + + Ok(record) +} + +pub(crate) async fn generate_signed_pre_key< + S: PreKeysStore, + R: rand::Rng + rand::CryptoRng, +>( + store: &mut S, + csprng: &mut R, + identity_key: &KeyPair, +) -> Result { + let id = store.next_signed_pre_key_id()?; + let id = id.max(1); // TODO: Hack, keys start with 1 + + let key_pair = KeyPair::generate(csprng); + let signature = identity_key + .private_key + .calculate_signature(&key_pair.public_key.serialize(), csprng)?; + + let unix_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + let record = + SignedPreKeyRecord::new(id.into(), unix_time, &key_pair, &signature); + + store.save_signed_pre_key(id.into(), &record).await?; + store.set_next_signed_pre_key_id(id + 1)?; + + Ok(record) +} diff --git a/libsignal-service/src/provisioning/cipher.rs b/libsignal-service/src/provisioning/cipher.rs index 68677a228..0e9e054d7 100644 --- a/libsignal-service/src/provisioning/cipher.rs +++ b/libsignal-service/src/provisioning/cipher.rs @@ -180,7 +180,7 @@ impl ProvisioningCipher { .expect("initalization of CBC/AES/PKCS7"); let input = cipher.decrypt_vec(cipher_text).map_err(|e| { ProvisioningError::InvalidData { - reason: format!("CBC/Padding error: {:?}", e), + reason: format!("CBC/Padding error: {:?}", e).into(), } })?; diff --git a/libsignal-service/src/provisioning/manager.rs b/libsignal-service/src/provisioning/manager.rs deleted file mode 100644 index d18ce351e..000000000 --- a/libsignal-service/src/provisioning/manager.rs +++ /dev/null @@ -1,292 +0,0 @@ -use derivative::Derivative; -use futures::{channel::mpsc::Sender, pin_mut, SinkExt, StreamExt}; -use libsignal_protocol::{PrivateKey, PublicKey}; -use phonenumber::PhoneNumber; -use serde::{Deserialize, Serialize}; -use url::Url; -use uuid::Uuid; - -use super::{ - pipe::{ProvisioningPipe, ProvisioningStep}, - ProvisioningError, -}; - -use crate::{ - configuration::{Endpoint, ServiceCredentials, SignalingKey}, - push_service::{ - DeviceId, HttpAuthOverride, PushService, ServiceError, ServiceIds, - }, - utils::serde_base64, -}; - -/// Message received when linking a new secondary device. -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -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 pni_registration_id: u32, - #[serde(with = "serde_base64", skip_serializing_if = "Vec::is_empty")] - pub name: Vec, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ConfirmCodeResponse { - pub uuid: Uuid, - pub storage_capable: bool, -} - -pub struct ProvisioningManager<'a, P: PushService + 'a> { - push_service: &'a mut P, - phone_number: PhoneNumber, - password: String, -} - -#[derive(Derivative)] -#[derivative(Debug)] -pub enum SecondaryDeviceProvisioning { - Url(Url), - NewDeviceRegistration { - phone_number: phonenumber::PhoneNumber, - device_id: DeviceId, - registration_id: u32, - pni_registration_id: u32, - service_ids: ServiceIds, - #[derivative(Debug = "ignore")] - aci_private_key: PrivateKey, - aci_public_key: PublicKey, - #[derivative(Debug = "ignore")] - pni_private_key: PrivateKey, - pni_public_key: PublicKey, - #[derivative(Debug = "ignore")] - profile_key: Vec, - }, -} - -impl<'a, P: PushService + 'a> ProvisioningManager<'a, P> { - pub fn new( - push_service: &'a mut P, - phone_number: PhoneNumber, - password: String, - ) -> Self { - Self { - push_service, - phone_number, - password, - } - } - - pub(crate) async fn confirm_device( - &mut self, - confirm_code: &str, - confirm_code_message: ConfirmDeviceMessage, - ) -> Result { - self.push_service - .put_json( - Endpoint::Service, - &format!("/v1/devices/{}", confirm_code), - &[], - self.auth_override(), - confirm_code_message, - ) - .await - } - - fn auth_override(&self) -> HttpAuthOverride { - let credentials = ServiceCredentials { - uuid: None, - phonenumber: self.phone_number.clone(), - password: Some(self.password.clone()), - signaling_key: None, - device_id: None, - }; - if let Some(auth) = credentials.authorization() { - HttpAuthOverride::Identified(auth) - } else { - HttpAuthOverride::NoOverride - } - } -} - -#[derive(Clone)] -pub struct LinkingManager { - push_service: P, - // forwarded to the `ProvisioningManager` - password: String, -} - -impl LinkingManager

{ - pub fn new(push_service: P, password: String) -> Self { - Self { - push_service, - password, - } - } - - pub async fn provision_secondary_device( - &mut self, - csprng: &mut R, - signaling_key: SignalingKey, - mut tx: Sender, - ) -> Result<(), ProvisioningError> { - // open a websocket without authentication, to receive a tsurl:// - let ws = self - .push_service - .ws( - "/v1/websocket/provisioning/", - "/v1/keepalive/provisioning", - &[], - None, - ) - .await?; - - let registration_id = csprng.gen_range(1..256); - let pni_registration_id = csprng.gen_range(1..256); - - let provisioning_pipe = ProvisioningPipe::from_socket(ws)?; - let provision_stream = provisioning_pipe.stream(); - pin_mut!(provision_stream); - while let Some(step) = provision_stream.next().await { - match step { - Ok(ProvisioningStep::Url(url)) => { - tx.send(SecondaryDeviceProvisioning::Url(url)) - .await - .expect("failed to send provisioning Url in channel"); - }, - Ok(ProvisioningStep::Message(message)) => { - let aci_uuid = message - .aci - .ok_or(ProvisioningError::InvalidData { - reason: "missing client UUID".into(), - }) - .and_then(|ref s| { - Uuid::parse_str(s).map_err(|e| { - ProvisioningError::InvalidData { - reason: format!("invalid UUID: {}", e), - } - }) - })?; - - let pni_uuid = message - .pni - .ok_or(ProvisioningError::InvalidData { - reason: "missing client UUID".into(), - }) - .and_then(|ref s| { - Uuid::parse_str(s).map_err(|e| { - ProvisioningError::InvalidData { - reason: format!("invalid UUID: {}", e), - } - }) - })?; - - let aci_public_key = PublicKey::deserialize( - &message.aci_identity_key_public.ok_or( - ProvisioningError::InvalidData { - reason: "missing public key".into(), - }, - )?, - )?; - - let aci_private_key = PrivateKey::deserialize( - &message.aci_identity_key_private.ok_or( - ProvisioningError::InvalidData { - reason: "missing public key".into(), - }, - )?, - )?; - - let pni_public_key = PublicKey::deserialize( - &message.pni_identity_key_public.ok_or( - ProvisioningError::InvalidData { - reason: "missing public key".into(), - }, - )?, - )?; - - let pni_private_key = PrivateKey::deserialize( - &message.pni_identity_key_private.ok_or( - ProvisioningError::InvalidData { - reason: "missing public key".into(), - }, - )?, - )?; - - let profile_key = message.profile_key.ok_or( - ProvisioningError::InvalidData { - reason: "missing profile key".into(), - }, - )?; - - let phone_number = message.number.ok_or( - ProvisioningError::InvalidData { - reason: "missing phone number".into(), - }, - )?; - - let phone_number = phonenumber::parse(None, phone_number) - .map_err(|e| { - ProvisioningError::InvalidData { - reason: format!("invalid phone number ({})", e), - } - })?; - - let mut provisioning_manager = ProvisioningManager::new( - &mut self.push_service, - phone_number.clone(), - self.password.clone(), - ); - - let provisioning_code = message.provisioning_code.ok_or( - ProvisioningError::InvalidData { - reason: "no provisioning confirmation code".into(), - }, - )?; - - let device_id = provisioning_manager - .confirm_device( - &provisioning_code, - ConfirmDeviceMessage { - signaling_key: signaling_key.to_vec(), - supports_sms: false, - fetches_messages: true, - registration_id, - pni_registration_id, - name: vec![], - }, - ) - .await?; - - tx.send( - SecondaryDeviceProvisioning::NewDeviceRegistration { - phone_number, - device_id, - registration_id, - pni_registration_id, - service_ids: ServiceIds { - aci: aci_uuid, - pni: pni_uuid, - }, - aci_private_key, - aci_public_key, - pni_private_key, - pni_public_key, - profile_key, - }, - ) - .await - .expect( - "failed to send provisioning message in rx channel", - ); - }, - Err(e) => return Err(e), - } - } - - Ok(()) - } -} diff --git a/libsignal-service/src/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index 7508c2c85..3fe28684e 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -1,14 +1,35 @@ -pub(crate) mod cipher; -pub(crate) mod manager; -pub(crate) mod pipe; +mod cipher; +mod pipe; + +use std::convert::TryInto; +use std::{array::TryFromSliceError, borrow::Cow}; pub use cipher::ProvisioningCipher; -pub use manager::{ - ConfirmCodeResponse, LinkingManager, ProvisioningManager, - SecondaryDeviceProvisioning, -}; + +use derivative::Derivative; +use futures::StreamExt; +use futures::{channel::mpsc::Sender, pin_mut, SinkExt}; +use libsignal_protocol::{DeviceId, KeyPair, PrivateKey, PublicKey}; +use prost::Message; +use serde::{Deserialize, Serialize}; +use url::Url; +use uuid::Uuid; +use zkgroup::profiles::ProfileKey; + +use pipe::{ProvisioningPipe, ProvisioningStep}; use crate::prelude::ServiceError; +use crate::{ + account_manager::encrypt_device_name, + pre_keys::{ + generate_last_resort_kyber_key, generate_signed_pre_key, PreKeysStore, + }, + push_service::{ + HttpAuth, LinkAccountAttributes, LinkCapabilities, LinkRequest, + LinkResponse, PushService, ServiceIds, + }, + utils::serde_base64, +}; pub use crate::proto::{ ProvisionEnvelope, ProvisionMessage, ProvisioningVersion, @@ -17,7 +38,7 @@ pub use crate::proto::{ #[derive(thiserror::Error, Debug)] pub enum ProvisioningError { #[error("Invalid provisioning data: {reason}")] - InvalidData { reason: String }, + InvalidData { reason: Cow<'static, str> }, #[error("Protobuf decoding error: {0}")] DecodeError(#[from] prost::DecodeError), #[error("Websocket error: {reason}")] @@ -30,6 +51,8 @@ pub enum ProvisioningError { ProtocolError(#[from] libsignal_protocol::error::SignalProtocolError), #[error("ProvisioningCipher in encrypt-only mode")] EncryptOnlyProvisioningCipher, + #[error("invalid profile key bytes")] + InvalidProfileKey(TryFromSliceError), } pub fn generate_registration_id( @@ -37,3 +60,231 @@ pub fn generate_registration_id( ) -> u32 { csprng.gen_range(1..16380) } + +/// Message received when linking a new secondary device. +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +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 pni_registration_id: u32, + #[serde(with = "serde_base64", skip_serializing_if = "Vec::is_empty")] + pub name: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ConfirmCodeResponse { + pub uuid: Uuid, + pub storage_capable: bool, +} + +#[derive(Debug)] +pub enum SecondaryDeviceProvisioning { + Url(Url), + NewDeviceRegistration(NewDeviceRegistration), +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct NewDeviceRegistration { + pub phone_number: phonenumber::PhoneNumber, + pub device_id: DeviceId, + pub registration_id: u32, + pub pni_registration_id: u32, + pub service_ids: ServiceIds, + #[derivative(Debug = "ignore")] + pub aci_private_key: PrivateKey, + pub aci_public_key: PublicKey, + #[derivative(Debug = "ignore")] + pub pni_private_key: PrivateKey, + pub pni_public_key: PublicKey, + #[derivative(Debug = "ignore")] + pub profile_key: ProfileKey, +} + +pub async fn link_device< + R: rand::Rng + rand::CryptoRng, + S: PreKeysStore, + P: PushService, +>( + aci_store: &mut S, + pni_store: &mut S, + csprng: &mut R, + mut push_service: P, + password: &str, + device_name: &str, + mut tx: Sender, +) -> Result<(), ProvisioningError> { + // open a websocket without authentication, to receive a tsurl:// + let ws = push_service + .ws( + "/v1/websocket/provisioning/", + "/v1/keepalive/provisioning", + &[], + None, + ) + .await?; + + let registration_id = csprng.gen_range(1..256); + let pni_registration_id = csprng.gen_range(1..256); + + let provisioning_pipe = ProvisioningPipe::from_socket(ws)?; + let provision_stream = provisioning_pipe.stream(); + pin_mut!(provision_stream); + + if let ProvisioningStep::Url(url) = provision_stream.next().await.ok_or( + ProvisioningError::InvalidData { + reason: "no provisioning URL received".into(), + }, + )?? { + tx.send(SecondaryDeviceProvisioning::Url(url)) + .await + .expect("failed to send provisioning Url in channel"); + } else { + return Err(ProvisioningError::InvalidData { + reason: "wrong provisioning step received".into(), + }); + } + + if let ProvisioningStep::Message(message) = + provision_stream.next().await.ok_or( + ProvisioningError::InvalidData { + reason: "no provisioning message received".into(), + }, + )?? + { + let aci_public_key = + PublicKey::deserialize(&message.aci_identity_key_public.ok_or( + ProvisioningError::InvalidData { + reason: "missing public key".into(), + }, + )?)?; + + let aci_private_key = + PrivateKey::deserialize(&message.aci_identity_key_private.ok_or( + ProvisioningError::InvalidData { + reason: "missing public key".into(), + }, + )?)?; + + let pni_public_key = + PublicKey::deserialize(&message.pni_identity_key_public.ok_or( + ProvisioningError::InvalidData { + reason: "missing public key".into(), + }, + )?)?; + + let pni_private_key = + PrivateKey::deserialize(&message.pni_identity_key_private.ok_or( + ProvisioningError::InvalidData { + reason: "missing public key".into(), + }, + )?)?; + + let profile_key = + message.profile_key.ok_or(ProvisioningError::InvalidData { + reason: "missing profile key".into(), + })?; + + let phone_number = + message.number.ok_or(ProvisioningError::InvalidData { + reason: "missing phone number".into(), + })?; + + let phone_number = + phonenumber::parse(None, phone_number).map_err(|e| { + ProvisioningError::InvalidData { + reason: format!("invalid phone number ({})", e).into(), + } + })?; + + let provisioning_code = message.provisioning_code.ok_or( + ProvisioningError::InvalidData { + reason: "no provisioning confirmation code".into(), + }, + )?; + + let aci_key_pair = KeyPair::new(aci_public_key, aci_private_key); + let pni_key_pair = KeyPair::new(pni_public_key, pni_private_key); + + let aci_pq_last_resort_pre_key = + generate_last_resort_kyber_key(aci_store, &aci_key_pair).await?; + let pni_pq_last_resort_pre_key = + generate_last_resort_kyber_key(pni_store, &pni_key_pair).await?; + + let aci_signed_pre_key = + generate_signed_pre_key(aci_store, csprng, &aci_key_pair).await?; + let pni_signed_pre_key = + generate_signed_pre_key(pni_store, csprng, &pni_key_pair).await?; + + let encrypted_device_name = base64::encode( + encrypt_device_name(csprng, device_name, &aci_public_key)? + .encode_to_vec(), + ); + + let profile_key = ProfileKey::create( + profile_key + .as_slice() + .try_into() + .map_err(ProvisioningError::InvalidProfileKey)?, + ); + + let request = LinkRequest { + verification_code: provisioning_code, + account_attributes: LinkAccountAttributes { + registration_id, + pni_registration_id, + fetches_messages: true, + capabilities: LinkCapabilities { pni: true }, + name: encrypted_device_name, + }, + aci_signed_pre_key: aci_signed_pre_key.try_into()?, + pni_signed_pre_key: pni_signed_pre_key.try_into()?, + aci_pq_last_resort_pre_key: aci_pq_last_resort_pre_key + .try_into()?, + pni_pq_last_resort_pre_key: pni_pq_last_resort_pre_key + .try_into()?, + }; + + let LinkResponse { + aci, + pni, + device_id, + } = push_service + .link_device( + &request, + HttpAuth { + username: phone_number.to_string(), + password: password.to_owned(), + }, + ) + .await?; + + tx.send(SecondaryDeviceProvisioning::NewDeviceRegistration( + NewDeviceRegistration { + phone_number, + service_ids: ServiceIds { aci, pni }, + device_id: device_id.into(), + registration_id, + pni_registration_id, + aci_private_key, + aci_public_key, + pni_private_key, + pni_public_key, + profile_key, + }, + )) + .await + .expect("failed to send provisioning message in rx channel"); + } else { + return Err(ProvisioningError::InvalidData { + reason: "wrong provisioning step received".into(), + }); + } + + Ok(()) +} diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index d5c2cb8d4..41cd0fe83 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -5,7 +5,8 @@ use crate::{ envelope::*, groups_v2::GroupDecodingError, pre_keys::{ - KyberPreKeyEntity, PreKeyEntity, PreKeyState, SignedPreKeyEntity, + KyberPreKeyEntity, PreKeyEntity, PreKeyState, SignedPreKey, + SignedPreKeyEntity, }, profile_cipher::ProfileCipherError, proto::{attachment_pointer::AttachmentIdentifier, AttachmentPointer}, @@ -381,6 +382,42 @@ pub struct StaleDevices { pub stale_devices: Vec, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LinkRequest { + pub verification_code: String, + pub account_attributes: LinkAccountAttributes, + pub aci_signed_pre_key: SignedPreKey, + pub pni_signed_pre_key: SignedPreKey, + pub aci_pq_last_resort_pre_key: KyberPreKeyEntity, + pub pni_pq_last_resort_pre_key: KyberPreKeyEntity, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LinkAccountAttributes { + pub fetches_messages: bool, + pub name: String, + pub registration_id: u32, + pub pni_registration_id: u32, + pub capabilities: LinkCapabilities, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LinkCapabilities { + pub pni: bool, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LinkResponse { + #[serde(rename = "uuid")] + pub aci: Uuid, + pub pni: Uuid, + pub device_id: u32, +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SignalServiceProfile { @@ -522,6 +559,9 @@ pub enum ServiceError { #[error("Not found.")] NotFoundError, + + #[error("invalid device name")] + InvalidDeviceName, } #[cfg_attr(feature = "unsend-futures", async_trait::async_trait(?Send))] @@ -951,6 +991,21 @@ pub trait PushService: MaybeSend { Ok(SenderCertificate::deserialize(&cert.certificate)?) } + async fn link_device( + &mut self, + link_request: &LinkRequest, + http_auth: HttpAuth, + ) -> Result { + self.put_json( + Endpoint::Service, + "/v1/devices/link", + &[], + HttpAuthOverride::Identified(http_auth), + &link_request, + ) + .await + } + async fn set_account_attributes( &mut self, attributes: AccountAttributes,