Skip to content

Commit

Permalink
Registration fixes for 2023 (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
rubdos authored Aug 7, 2023
1 parent 81eb911 commit 8789920
Show file tree
Hide file tree
Showing 7 changed files with 661 additions and 181 deletions.
165 changes: 150 additions & 15 deletions libsignal-service-actix/examples/registering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,167 @@
//! ```

use anyhow::Error;
use libsignal_service::{
configuration::*, provisioning::ProvisioningManager, USER_AGENT,
use libsignal_service::configuration::SignalServers;
use libsignal_service::prelude::{ProfileKey, ServiceCredentials};
use libsignal_service::provisioning::generate_registration_id;
use libsignal_service::push_service::{
AccountAttributes, DeviceCapabilities, PushService, RegistrationMethod,
VerificationTransport,
};
use libsignal_service::USER_AGENT;
use libsignal_service_actix::prelude::AwcPushService;
use rand::RngCore;
use structopt::StructOpt;

#[actix_rt::main]
async fn main() -> Result<(), Error> {
env_logger::init();
let client = "libsignal-service-hyper-example";
let use_voice = false;

let args = Args::from_args();
let Args {
servers,
phonenumber,
password,
captcha,
} = Args::from_args();

let push_token = None;
// Mobile country code and mobile network code can in theory be extracted from the phone
// number, but it's not necessary for the API to function correctly.
// XXX: We could internalize this if statement to create_verification_session
let (mcc, mnc) = if let Some(carrier) = phonenumber.carrier() {
(Some(&carrier[0..3]), Some(&carrier[3..]))
} else {
(None, None)
};

// Only used with MessageSender and MessageReceiver
// let password = args.get_password()?;

let mut push_service =
AwcPushService::new(args.servers, None, USER_AGENT.into());
let mut provision_manager: ProvisioningManager<AwcPushService> =
ProvisioningManager::new(
&mut push_service,
args.username,
args.password.unwrap(),
let mut push_service = AwcPushService::new(
servers,
Some(ServiceCredentials {
uuid: None,
phonenumber: phonenumber.clone(),
password,
signaling_key: None,
device_id: None,
}),
USER_AGENT.into(),
);

let mut session = push_service
.create_verification_session(
&phonenumber.to_string(),
push_token,
mcc,
mnc,
)
.await
.expect("create a registration verification session");
println!("Sending registration request...");

if session.captcha_required() {
session = push_service
.patch_verification_session(
&session.id,
None,
None,
None,
captcha.as_deref(),
None,
)
.await
.expect("submit captcha");
}

if session.push_challenge_required() {
anyhow::bail!("Push challenge required, but not implemented.");
}

if !session.allowed_to_request_code {
anyhow::bail!(
"Not allowed to request verification code, reason unknown: {session:?}",
);
}

session = push_service
.request_verification_code(
&session.id,
client,
if use_voice {
VerificationTransport::Voice
} else {
VerificationTransport::Sms
},
)
.await
.expect("request verification code");

let confirmation_code = let_user_enter_confirmation_code();

println!("Submitting confirmation code...");

session = push_service
.submit_verification_code(&session.id, confirmation_code)
.await
.expect("Sending confirmation code failed.");

provision_manager
// You probably want to generate a reCAPTCHA though!
.request_sms_verification_code(None, None)
.await?;
if !session.verified {
anyhow::bail!("Session is not verified");
}

let registration_id = generate_registration_id(&mut rand::thread_rng());
let pni_registration_id = generate_registration_id(&mut rand::thread_rng());
let signaling_key = generate_signaling_key();
let mut profile_key = [0u8; 32];
rand::thread_rng().fill_bytes(&mut profile_key);
let profile_key = ProfileKey::create(profile_key);
let skip_device_transfer = false;
let _registration_data = push_service
.submit_registration_request(
RegistrationMethod::SessionId(&session.id),
AccountAttributes {
signaling_key: Some(signaling_key.to_vec()),
registration_id,
pni_registration_id,
voice: false,
video: false,
fetches_messages: true,
pin: None,
registration_lock: None,
unidentified_access_key: Some(
profile_key.derive_access_key().to_vec(),
),
unrestricted_unidentified_access: false, // TODO: make this configurable?
discoverable_by_phone_number: true,
name: Some("libsignal-service-hyper test".into()),
capabilities: DeviceCapabilities::default(),
},
skip_device_transfer,
)
.await;

// You would want to store the registration data

println!("Registration completed!");

Ok(())
}

fn let_user_enter_confirmation_code() -> &'static str {
"12345"
}

fn generate_signaling_key() -> [u8; 52] {
// Signaling key that decrypts the incoming Signal messages
let mut rng = rand::thread_rng();
let mut signaling_key = [0u8; 52];
rng.fill_bytes(&mut signaling_key);
signaling_key
}

#[derive(Debug, Clone, PartialEq, Eq, StructOpt)]
pub struct Args {
#[structopt(
Expand All @@ -65,11 +194,17 @@ pub struct Args {
help = "Your username or other identifier",
default_value = "+14151231234"
)]
pub username: phonenumber::PhoneNumber,
pub phonenumber: phonenumber::PhoneNumber,
#[structopt(
short = "p",
long = "password",
help = "The password to use. Read from stdin if not provided"
)]
pub password: Option<String>,
#[structopt(
short = "c",
long = "captcha",
help = "Captcha for registration"
)]
pub captcha: Option<String>,
}
92 changes: 92 additions & 0 deletions libsignal-service-actix/src/push_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,98 @@ impl PushService for AwcPushService {
Self::json(&text)
}

async fn patch_json<D, S>(
&mut self,
endpoint: Endpoint,
path: &str,
credentials_override: HttpAuthOverride,
value: S,
) -> Result<D, ServiceError>
where
for<'de> D: Deserialize<'de>,
S: Serialize,
{
let mut response = self
.request(Method::PATCH, endpoint, path, credentials_override)?
.send_json(&value)
.await
.map_err(|e| ServiceError::SendError {
reason: e.to_string(),
})?;

log::debug!("AwcPushService::patch response: {:?}", response);

Self::from_response(&mut response).await?;

// In order to catch the zero-length output, we have to collect
// the whole response. The actix-web api is meant to used as a
// streaming deserializer, so we have this little awkward match.
//
// This is also the reason we depend directly on serde_json, however
// actix already imports that anyway.
let text = match response.body().await {
Ok(text) => {
log::debug!(
"PATCH response: {:?}",
String::from_utf8_lossy(&text)
);
text
},
Err(e) => {
return Err(ServiceError::ResponseError {
reason: e.to_string(),
})
},
};
Self::json(&text)
}

async fn post_json<D, S>(
&mut self,
endpoint: Endpoint,
path: &str,
credentials_override: HttpAuthOverride,
value: S,
) -> Result<D, ServiceError>
where
for<'de> D: Deserialize<'de>,
S: Serialize,
{
let mut response = self
.request(Method::POST, endpoint, path, credentials_override)?
.send_json(&value)
.await
.map_err(|e| ServiceError::SendError {
reason: e.to_string(),
})?;

log::debug!("AwcPushService::post response: {:?}", response);

Self::from_response(&mut response).await?;

// In order to catch the zero-length output, we have to collect
// the whole response. The actix-web api is meant to used as a
// streaming deserializer, so we have this little awkward match.
//
// This is also the reason we depend directly on serde_json, however
// actix already imports that anyway.
let text = match response.body().await {
Ok(text) => {
log::debug!(
"GET response: {:?}",
String::from_utf8_lossy(&text)
);
text
},
Err(e) => {
return Err(ServiceError::ResponseError {
reason: e.to_string(),
})
},
};
Self::json(&text)
}

async fn get_protobuf<T>(
&mut self,
endpoint: Endpoint,
Expand Down
Loading

0 comments on commit 8789920

Please sign in to comment.