Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
335 changes: 318 additions & 17 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ incremental = true # Improves re-compile times
[workspace.dependencies]
tokio = { version = "1.43", features = ["full"] }
# Application
idevice = { git = "https://github.com/PlumeImpactor/plume-idevice", rev = "5e350a", default-features = false, features = [
idevice = { git = "https://github.com/PlumeImpactor/plume-idevice", rev = "9bc774a69cd259d8d6b9a01bbadfd9668bb1ec5a", default-features = false, features = [
"full",
"ring",
] }
Expand Down
38 changes: 33 additions & 5 deletions apps/plumeimpactor/src/screen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,16 @@ impl Impactor {
.cloned();

if let ImpactorScreen::Utilities(_) = self.current_screen {
let rppairing_enabled = match &self.current_screen {
ImpactorScreen::Utilities(screen) => screen.rppairing_enabled,
_ => false,
};
self.current_screen = ImpactorScreen::Utilities(
utilties::UtilitiesScreen::new(self.selected_device.clone()),
);
return Task::done(Message::UtilitiesScreen(utilties::Message::RefreshApps));
return Task::done(Message::UtilitiesScreen(utilties::Message::RefreshApps(
rppairing_enabled,
)));
}

Task::none()
Expand All @@ -176,10 +182,16 @@ impl Impactor {
}

if let ImpactorScreen::Utilities(_) = self.current_screen {
let rppairing_enabled = match &self.current_screen {
ImpactorScreen::Utilities(screen) => screen.rppairing_enabled,
_ => false,
};
self.current_screen = ImpactorScreen::Utilities(
utilties::UtilitiesScreen::new(self.selected_device.clone()),
);
return Task::done(Message::UtilitiesScreen(utilties::Message::RefreshApps));
return Task::done(Message::UtilitiesScreen(utilties::Message::RefreshApps(
rppairing_enabled,
)));
}

Task::none()
Expand All @@ -204,10 +216,16 @@ impl Impactor {
}

if let ImpactorScreen::Utilities(_) = self.current_screen {
let rppairing_enabled = match &self.current_screen {
ImpactorScreen::Utilities(screen) => screen.rppairing_enabled,
_ => false,
};
self.current_screen = ImpactorScreen::Utilities(
utilties::UtilitiesScreen::new(self.selected_device.clone()),
);
return Task::done(Message::UtilitiesScreen(utilties::Message::RefreshApps));
return Task::done(Message::UtilitiesScreen(utilties::Message::RefreshApps(
rppairing_enabled,
)));
}

Task::none()
Expand All @@ -225,7 +243,13 @@ impl Impactor {
self.navigate_to_screen(screen_type.clone());

if screen_type == ImpactorScreenType::Utilities {
return Task::done(Message::UtilitiesScreen(utilties::Message::RefreshApps));
let rppairing_enabled = match &self.current_screen {
ImpactorScreen::Utilities(screen) => screen.rppairing_enabled,
_ => false,
};
return Task::done(Message::UtilitiesScreen(utilties::Message::RefreshApps(
rppairing_enabled,
)));
}

Task::none()
Expand Down Expand Up @@ -384,11 +408,15 @@ impl Impactor {
package::PackageScreen::new(Some(package), options),
);
} else if let general::Message::NavigateToUtilities = msg {
let rppairing_enabled = match &self.current_screen {
ImpactorScreen::Utilities(screen) => screen.rppairing_enabled,
_ => false,
};
self.current_screen = ImpactorScreen::Utilities(
utilties::UtilitiesScreen::new(self.selected_device.clone()),
);
return Task::done(Message::UtilitiesScreen(
utilties::Message::RefreshApps,
utilties::Message::RefreshApps(rppairing_enabled),
));
}

Expand Down
47 changes: 38 additions & 9 deletions apps/plumeimpactor/src/screen/utilties.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use iced::widget::{button, column, container, row, rule, scrollable, text};
use iced::widget::{button, column, container, row, rule, scrollable, text, toggler};
use iced::{Center, Color, Element, Task};

use crate::appearance;
use crate::defaults::get_data_path;
use plume_utils::{Device, SignerAppReal};
use std::collections::HashMap;

Expand Down Expand Up @@ -37,12 +38,13 @@ impl StatusMessage {

#[derive(Debug, Clone)]
pub enum Message {
RefreshApps,
RefreshApps(bool),
AppsLoaded(Result<Vec<SignerAppReal>, String>),
InstallPairingFile(SignerAppReal),
Trust,
PairResult(Result<(), String>),
InstallPairingResult(String, Result<(), String>),
ToggleRPPairing(bool),
}

#[derive(Debug, Clone)]
Expand All @@ -53,6 +55,7 @@ pub struct UtilitiesScreen {
app_statuses: HashMap<String, StatusMessage>,
loading: bool,
trust_loading: bool,
pub rppairing_enabled: bool,
}

impl UtilitiesScreen {
Expand All @@ -64,6 +67,7 @@ impl UtilitiesScreen {
app_statuses: HashMap::new(),
loading: false,
trust_loading: false,
rppairing_enabled: false,
};

if screen.device.as_ref().map(|d| d.is_mac).unwrap_or(false) {
Expand All @@ -75,7 +79,7 @@ impl UtilitiesScreen {

pub fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::RefreshApps => {
Message::RefreshApps(supports_remote_pairing) => {
self.loading = true;
self.status_message = None;
self.app_statuses.clear();
Expand All @@ -91,7 +95,7 @@ impl UtilitiesScreen {
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(async move {
device
.installed_apps()
.installed_apps(supports_remote_pairing)
.await
.map_err(|e| format!("Failed to load apps: {}", e))
});
Expand Down Expand Up @@ -135,13 +139,28 @@ impl UtilitiesScreen {
let app_key = Self::app_key(&app);
let (tx, rx) = std::sync::mpsc::sync_channel(1);

let rppairing_enabled = self.rppairing_enabled;

std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(async move {
device
.install_pairing_record(&bundle_id, &pairing_path)
.await
.map_err(|e| format!("Failed to install pairing record: {}", e))
if rppairing_enabled {
device
.install_remote_pairing_record(
&bundle_id,
&pairing_path,
get_data_path(),
)
.await
.map_err(|e| {
format!("Failed to install remote pairing record: {}", e)
})
} else {
device
.install_pairing_record(&bundle_id, &pairing_path)
.await
.map_err(|e| format!("Failed to install pairing record: {}", e))
}
});
let _ = tx.send(result);
});
Expand Down Expand Up @@ -215,6 +234,10 @@ impl UtilitiesScreen {
self.app_statuses.insert(app_key, status);
Task::none()
}
Message::ToggleRPPairing(enabled) => {
self.rppairing_enabled = enabled;
Task::perform(async move { Message::RefreshApps(enabled) }, |msg| msg)
}
}
}

Expand Down Expand Up @@ -265,7 +288,7 @@ impl UtilitiesScreen {
.on_press_maybe(if self.loading {
None
} else {
Some(Message::RefreshApps)
Some(Message::RefreshApps(self.rppairing_enabled))
})
.style(appearance::s_button)
.width(iced::Length::Fill),
Expand All @@ -274,6 +297,12 @@ impl UtilitiesScreen {
);
}

let toggle = toggler(self.rppairing_enabled)
.label("Use Remote Pairing")
.on_toggle(Message::ToggleRPPairing);

content = content.push(toggle);

if !self.installed_apps.is_empty() {
content = content
.push(container(rule::horizontal(1)).padding([appearance::THEME_PADDING, 0.0]));
Expand Down
4 changes: 2 additions & 2 deletions crates/plume_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ serde.workspace = true
serde_json.workspace = true
rustls.workspace = true
# Core dependencies
reqwest = { version = "0.11.14", default-features = false, features = [
reqwest = { version = "0.13.0", default-features = false, features = [
"blocking",
"json",
"rustls-tls",
"rustls",
] }
regex = "1.11.2"
base64 = "0.22"
Expand Down
121 changes: 117 additions & 4 deletions crates/plume_utils/src/device.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use std::fmt;
use std::path::{Component, Path, PathBuf};

use idevice::IdeviceService;
use idevice::core_device_proxy::CoreDeviceProxy;
use idevice::installation_proxy::InstallationProxyClient;
use idevice::lockdown::LockdownClient;
use idevice::misagent::MisagentClient;
use idevice::provider::UsbmuxdProvider;
use idevice::remote_pairing::{RemotePairingClient, RpPairingFile};
use idevice::rsd::RsdHandshake;
use idevice::usbmuxd::{Connection, UsbmuxdAddr, UsbmuxdDevice};
use idevice::utils::installation;
use idevice::{IdeviceService, RemoteXpcClient};
use plume_core::MobileProvision;

use crate::Error;
Expand Down Expand Up @@ -65,7 +69,10 @@ impl Device {
Ok(get_dict_string!(values, "DeviceName"))
}

pub async fn installed_apps(&self) -> Result<Vec<SignerAppReal>, Error> {
pub async fn installed_apps(
&self,
supports_remote_pairing: bool,
) -> Result<Vec<SignerAppReal>, Error> {
let device = match &self.usbmuxd_device {
Some(dev) => dev,
None => return Err(Error::Other("Device is not connected via USB".to_string())),
Expand All @@ -88,7 +95,17 @@ impl Device {
app_name.as_deref(),
);

if signer_app.app.supports_pairing_file_alt() {
let should_include = if supports_remote_pairing {
signer_app.app.supports_rsd()
} else {
signer_app.app.supports_pairing_file_alt()
};

if should_include
&& !found_apps
.iter()
.any(|a: &SignerAppReal| a.bundle_id == signer_app.bundle_id)
{
found_apps.push(signer_app);
}
}
Expand Down Expand Up @@ -144,7 +161,7 @@ impl Device {
let mut lc = LockdownClient::connect(&provider).await?;
let id = uuid::Uuid::new_v4().to_string().to_uppercase();
let buid = usbmuxd.get_buid().await?;
let mut pairing_file = lc.pair(id, buid).await?;
let mut pairing_file = lc.pair(id, buid, None).await?;
pairing_file.udid = Some(self.udid.clone());
let pairing_file = pairing_file.serialize()?;

Expand Down Expand Up @@ -210,6 +227,102 @@ impl Device {
Ok(())
}

pub async fn install_remote_pairing_record(
&self,
identifier: &String,
path: &str,
path_to_store: PathBuf,
) -> Result<(), Error> {
if self.usbmuxd_device.is_none() {
return Err(Error::Other("Device is not connected via USB".to_string()));
}

let provider = self
.usbmuxd_device
.clone()
.unwrap()
.to_provider(UsbmuxdAddr::default(), HOUSE_ARREST_LABEL);

let pairing_file = self.get_rsd_pairing_file(&provider, path_to_store).await?;

let hc = HouseArrestClient::connect(&provider).await?;
let mut ac = hc.vend_documents(identifier.clone()).await?;
if let Some(parent) = Path::new(path).parent() {
let mut current = String::new();
let has_root = parent.has_root();

for component in parent.components() {
if let Component::Normal(dir) = component {
if has_root && current.is_empty() {
current.push('/');
} else if !current.is_empty() && !current.ends_with('/') {
current.push('/');
}

current.push_str(&dir.to_string_lossy());
ac.mk_dir(&current).await?;
}
}
}

let mut f = ac.open(path, AfcFopenMode::Wr).await?;
f.write_entire(&pairing_file.to_bytes()).await?;

Ok(())
}

async fn get_rsd_pairing_file(
&self,
provider: &UsbmuxdProvider,
path: PathBuf,
) -> Result<RpPairingFile, Error> {
let pairing_file_path = path.join(format!("plume_{}.plist", self.udid));

if pairing_file_path.exists() {
return Ok(RpPairingFile::read_from_file(pairing_file_path).await?);
} else {
let cdp = CoreDeviceProxy::connect(provider).await?;
let cdp_port = cdp.tunnel_info().server_rsd_port;
let cdp_adapter = cdp.create_software_tunnel()?;
let mut cdp_adapter = cdp_adapter.to_async_handle();

let cdp_stream = cdp_adapter.connect(cdp_port).await?;
let cdp_handshake = RsdHandshake::new(cdp_stream).await?;

let tunnel_service = cdp_handshake
.services
.get("com.apple.internal.dt.coredevice.untrusted.tunnelservice")
.ok_or_else(|| Error::Other("Tunnel service not found".to_string()))?;

let tunnel_service_stream = cdp_adapter.connect(tunnel_service.port).await?;
let mut remote_xpc = RemoteXpcClient::new(tunnel_service_stream).await?;
remote_xpc.do_handshake().await?;
let _ = remote_xpc.recv_root().await;

let suffix: String = uuid::Uuid::new_v4()
.simple()
.to_string()
.chars()
.take(6)
.collect();

let hostname = format!("plume-{}", suffix);

let mut pairing_file = RpPairingFile::generate(&hostname);
let mut pairing_client =
RemotePairingClient::new(remote_xpc, &hostname, &mut pairing_file);
pairing_client
.connect(async |_| "000000".to_string(), ())
.await?;

let pairing_file_bytes = pairing_file.to_bytes();

tokio::fs::write(&pairing_file_path, &pairing_file_bytes).await?;

Ok(pairing_file)
}
}

pub async fn install_app<F, Fut>(
&self,
app_path: &PathBuf,
Expand Down
Loading