mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
Allow rppairing with underlying RemoteXPC connection
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -2190,9 +2190,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plist-macro"
|
name = "plist-macro"
|
||||||
version = "0.1.4"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68ef85ea642f18d31023a2b2c4a3fd823a985b0ab344d1a8fb70d5e122c28544"
|
checksum = "82c39e020f2d8d361e91a434cfe26bc7c3e21ae54414379816d0110f3f7ca121"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"plist",
|
"plist",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ tokio-openssl = { version = "0.6", optional = true }
|
|||||||
openssl = { version = "0.10", optional = true }
|
openssl = { version = "0.10", optional = true }
|
||||||
|
|
||||||
plist = { version = "1.8" }
|
plist = { version = "1.8" }
|
||||||
plist-macro = { version = "0.1.3" }
|
plist-macro = { version = "0.1.6" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
ns-keyed-archive = { version = "0.1.4", optional = true }
|
ns-keyed-archive = { version = "0.1.4", optional = true }
|
||||||
crossfire = { version = "2.1", optional = true }
|
crossfire = { version = "2.1", optional = true }
|
||||||
@@ -102,7 +102,12 @@ misagent = []
|
|||||||
mobile_image_mounter = ["dep:sha2"]
|
mobile_image_mounter = ["dep:sha2"]
|
||||||
mobileactivationd = ["dep:reqwest"]
|
mobileactivationd = ["dep:reqwest"]
|
||||||
mobilebackup2 = []
|
mobilebackup2 = []
|
||||||
notification_proxy = ["tokio/macros", "tokio/time", "dep:async-stream", "dep:futures"]
|
notification_proxy = [
|
||||||
|
"tokio/macros",
|
||||||
|
"tokio/time",
|
||||||
|
"dep:async-stream",
|
||||||
|
"dep:futures",
|
||||||
|
]
|
||||||
location_simulation = []
|
location_simulation = []
|
||||||
pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"]
|
pair = ["chrono/default", "tokio/time", "dep:sha2", "dep:rsa", "dep:x509-cert"]
|
||||||
pcapd = []
|
pcapd = []
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
//! Remote Pairing
|
//! Remote Pairing
|
||||||
|
|
||||||
use crate::{IdeviceError, ReadWrite};
|
use crate::IdeviceError;
|
||||||
|
|
||||||
use base64::{Engine as _, engine::general_purpose::STANDARD as B64};
|
|
||||||
use chacha20poly1305::{
|
use chacha20poly1305::{
|
||||||
ChaCha20Poly1305, Key, KeyInit, Nonce,
|
ChaCha20Poly1305, Key, KeyInit, Nonce,
|
||||||
aead::{Aead, Payload},
|
aead::{Aead, Payload},
|
||||||
@@ -10,26 +9,28 @@ use chacha20poly1305::{
|
|||||||
use ed25519_dalek::Signature;
|
use ed25519_dalek::Signature;
|
||||||
use hkdf::Hkdf;
|
use hkdf::Hkdf;
|
||||||
use idevice_srp::{client::SrpClient, groups::G_3072};
|
use idevice_srp::{client::SrpClient, groups::G_3072};
|
||||||
|
use plist_macro::plist;
|
||||||
|
use plist_macro::{PlistConvertible, PlistExt};
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
use rsa::{rand_core::OsRng, signature::SignerMut};
|
use rsa::{rand_core::OsRng, signature::SignerMut};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::json;
|
|
||||||
use sha2::Sha512;
|
use sha2::Sha512;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey as X25519PublicKey};
|
use x25519_dalek::{EphemeralSecret, PublicKey as X25519PublicKey};
|
||||||
|
|
||||||
mod opack;
|
mod opack;
|
||||||
mod rp_pairing_file;
|
mod rp_pairing_file;
|
||||||
|
mod socket;
|
||||||
mod tlv;
|
mod tlv;
|
||||||
|
|
||||||
// export
|
// export
|
||||||
pub use rp_pairing_file::RpPairingFile;
|
pub use rp_pairing_file::RpPairingFile;
|
||||||
|
pub use socket::{RpPairingSocket, RpPairingSocketProvider};
|
||||||
|
|
||||||
const RPPAIRING_MAGIC: &[u8] = b"RPPairing";
|
const RPPAIRING_MAGIC: &[u8] = b"RPPairing";
|
||||||
const WIRE_PROTOCOL_VERSION: u8 = 19;
|
const WIRE_PROTOCOL_VERSION: u8 = 19;
|
||||||
|
|
||||||
pub struct RemotePairingClient<'a, R: ReadWrite> {
|
pub struct RemotePairingClient<'a, R: RpPairingSocketProvider> {
|
||||||
inner: R,
|
inner: R,
|
||||||
sequence_number: usize,
|
sequence_number: usize,
|
||||||
pairing_file: &'a mut RpPairingFile,
|
pairing_file: &'a mut RpPairingFile,
|
||||||
@@ -39,7 +40,7 @@ pub struct RemotePairingClient<'a, R: ReadWrite> {
|
|||||||
server_cipher: ChaCha20Poly1305,
|
server_cipher: ChaCha20Poly1305,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, R: ReadWrite> RemotePairingClient<'a, R> {
|
impl<'a, R: RpPairingSocketProvider> RemotePairingClient<'a, R> {
|
||||||
pub fn new(inner: R, sending_host: &str, pairing_file: &'a mut RpPairingFile) -> Self {
|
pub fn new(inner: R, sending_host: &str, pairing_file: &'a mut RpPairingFile) -> Self {
|
||||||
let hk = Hkdf::<sha2::Sha512>::new(None, pairing_file.e_private_key.as_bytes());
|
let hk = Hkdf::<sha2::Sha512>::new(None, pairing_file.e_private_key.as_bytes());
|
||||||
let mut okm = [0u8; 32];
|
let mut okm = [0u8; 32];
|
||||||
@@ -92,22 +93,24 @@ impl<'a, R: ReadWrite> RemotePairingClient<'a, R> {
|
|||||||
data: x_public_key.to_bytes().to_vec(),
|
data: x_public_key.to_bytes().to_vec(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
let pairing_data = B64.encode(pairing_data);
|
let pairing_data = R::serialize_bytes(&pairing_data);
|
||||||
self.send_pairing_data(json! {{
|
self.send_pairing_data(plist!({
|
||||||
"data": pairing_data,
|
"data": pairing_data,
|
||||||
"kind": "verifyManualPairing",
|
"kind": "verifyManualPairing",
|
||||||
"startNewSession": true
|
"startNewSession": true
|
||||||
}})
|
}))
|
||||||
.await?;
|
.await?;
|
||||||
debug!("Waiting for response from verifyManualPairing");
|
debug!("Waiting for response from verifyManualPairing");
|
||||||
|
|
||||||
let pairing_data = self.receive_pairing_data().await?;
|
let pairing_data = self.receive_pairing_data().await?;
|
||||||
let pairing_data = match pairing_data.as_str() {
|
|
||||||
Some(p) => p,
|
let data = match R::deserialize_bytes(pairing_data) {
|
||||||
None => return Err(IdeviceError::UnexpectedResponse),
|
Some(d) => d,
|
||||||
|
None => {
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let data = B64.decode(pairing_data)?;
|
|
||||||
let data = tlv::deserialize_tlv8(&data)?;
|
let data = tlv::deserialize_tlv8(&data)?;
|
||||||
|
|
||||||
if data
|
if data
|
||||||
@@ -194,23 +197,18 @@ impl<'a, R: ReadWrite> RemotePairingClient<'a, R> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
debug!("Waiting for signbuf response");
|
debug!("Waiting for signbuf response");
|
||||||
self.send_pairing_data(json! {{
|
self.send_pairing_data(plist! ({
|
||||||
"data": B64.encode(tlv::serialize_tlv8(&msg)),
|
"data": R::serialize_bytes(&tlv::serialize_tlv8(&msg)),
|
||||||
"kind": "verifyManualPairing",
|
"kind": "verifyManualPairing",
|
||||||
"startNewSession": false
|
"startNewSession": false
|
||||||
}})
|
}))
|
||||||
.await?;
|
.await?;
|
||||||
let res = self.receive_pairing_data().await?;
|
let res = self.receive_pairing_data().await?;
|
||||||
let res = match res.as_str() {
|
|
||||||
Some(r) => r,
|
|
||||||
None => {
|
|
||||||
warn!("Pairing data response was not a string");
|
|
||||||
return Err(IdeviceError::UnexpectedResponse);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
debug!("Verify response: {res:#}");
|
|
||||||
|
|
||||||
let data = B64.decode(res)?;
|
let data = match R::deserialize_bytes(res) {
|
||||||
|
Some(d) => d,
|
||||||
|
None => return Err(IdeviceError::UnexpectedResponse),
|
||||||
|
};
|
||||||
let data = tlv::deserialize_tlv8(&data)?;
|
let data = tlv::deserialize_tlv8(&data)?;
|
||||||
debug!("Verify TLV: {data:#?}");
|
debug!("Verify TLV: {data:#?}");
|
||||||
|
|
||||||
@@ -231,34 +229,56 @@ impl<'a, R: ReadWrite> RemotePairingClient<'a, R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_pair_verified_failed(&mut self) -> Result<(), IdeviceError> {
|
pub async fn send_pair_verified_failed(&mut self) -> Result<(), IdeviceError> {
|
||||||
self.send_plain_request(json! {{"event": {"_0": {"pairVerifyFailed": {}}}}})
|
self.inner
|
||||||
.await
|
.send_plain(
|
||||||
|
plist!({
|
||||||
|
"event": {
|
||||||
|
"_0": {
|
||||||
|
"pairVerifyFailed": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
self.sequence_number,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
self.sequence_number += 1;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn attempt_pair_verify(&mut self) -> Result<serde_json::Value, IdeviceError> {
|
pub async fn attempt_pair_verify(&mut self) -> Result<plist::Value, IdeviceError> {
|
||||||
debug!("Sending attemptPairVerify");
|
debug!("Sending attemptPairVerify");
|
||||||
self.send_plain_request(json! {
|
self.inner
|
||||||
{
|
.send_plain(
|
||||||
|
plist!({
|
||||||
"request": {
|
"request": {
|
||||||
"_0": {
|
"_0": {
|
||||||
"handshake": {
|
"handshake": {
|
||||||
"_0": {
|
"_0": {
|
||||||
"hostOptions": {"attemptPairVerify": true},
|
"hostOptions": {
|
||||||
"wireProtocolVersion": WIRE_PROTOCOL_VERSION,
|
"attemptPairVerify": true
|
||||||
|
},
|
||||||
|
"wireProtocolVersion": plist::Value::Integer(WIRE_PROTOCOL_VERSION.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}),
|
||||||
})
|
self.sequence_number,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
self.sequence_number += 1;
|
||||||
|
|
||||||
debug!("Waiting for attemptPairVerify response");
|
debug!("Waiting for attemptPairVerify response");
|
||||||
let response = self.receive_plain_request().await?;
|
let response = self.inner.recv_plain().await?;
|
||||||
|
|
||||||
let response = response
|
let response = response
|
||||||
.get("response")
|
.as_dictionary()
|
||||||
|
.and_then(|x| x.get("response"))
|
||||||
|
.and_then(|x| x.as_dictionary())
|
||||||
.and_then(|x| x.get("_1"))
|
.and_then(|x| x.get("_1"))
|
||||||
|
.and_then(|x| x.as_dictionary())
|
||||||
.and_then(|x| x.get("handshake"))
|
.and_then(|x| x.get("handshake"))
|
||||||
|
.and_then(|x| x.as_dictionary())
|
||||||
.and_then(|x| x.get("_0"));
|
.and_then(|x| x.get("_0"));
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
@@ -301,38 +321,47 @@ impl<'a, R: ReadWrite> RemotePairingClient<'a, R> {
|
|||||||
data: vec![0x01],
|
data: vec![0x01],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
let tlv = B64.encode(tlv);
|
let tlv = R::serialize_bytes(&tlv);
|
||||||
self.send_pairing_data(json! {{
|
self.send_pairing_data(plist!({
|
||||||
"data": tlv,
|
"data": tlv,
|
||||||
"kind": "setupManualPairing",
|
"kind": "setupManualPairing",
|
||||||
"sendingHost": self.sending_host,
|
"sendingHost": &self.sending_host,
|
||||||
"startNewSession": true
|
"startNewSession": true
|
||||||
}})
|
}))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let response = self.receive_plain_request().await?;
|
let response = self.inner.recv_plain().await?;
|
||||||
let response = &response["event"]["_0"];
|
let response = match response
|
||||||
|
.get_by("event")
|
||||||
|
.and_then(|x| x.get_by("_0"))
|
||||||
|
.and_then(|x| x.as_dictionary())
|
||||||
|
{
|
||||||
|
Some(r) => r,
|
||||||
|
None => {
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut pin = None;
|
let mut pin = None;
|
||||||
|
|
||||||
let pairing_data = match if let Some(err) = response.get("pairingRejectedWithError") {
|
let pairing_data = match if let Some(err) = response.get("pairingRejectedWithError") {
|
||||||
let context = err
|
let context = err
|
||||||
.get("wrappedError")
|
.get_by("wrappedError")
|
||||||
.and_then(|x| x.get("userInfo"))
|
.and_then(|x| x.get_by("userInfo"))
|
||||||
.and_then(|x| x.get("NSLocalizedDescription"))
|
.and_then(|x| x.get_by("NSLocalizedDescription"))
|
||||||
.and_then(|x| x.as_str())
|
.and_then(|x| x.as_string())
|
||||||
.map(|x| x.to_string());
|
.map(|x| x.to_string());
|
||||||
return Err(IdeviceError::PairingRejected(context.unwrap_or_default()));
|
return Err(IdeviceError::PairingRejected(context.unwrap_or_default()));
|
||||||
} else if response.get("awaitingUserConsent").is_some() {
|
} else if response.get("awaitingUserConsent").is_some() {
|
||||||
pin = Some("000000".to_string());
|
pin = Some("000000".to_string());
|
||||||
self.receive_pairing_data()
|
Some(self.receive_pairing_data().await?)
|
||||||
.await?
|
|
||||||
.as_str()
|
|
||||||
.map(|x| x.to_string())
|
|
||||||
} else {
|
} else {
|
||||||
// On Apple TV, we can get the pin now
|
// On Apple TV, we can get the pin now
|
||||||
response["pairingData"]["_0"]["data"]
|
response
|
||||||
.as_str()
|
.get_by("pairingData")
|
||||||
.map(|x| x.to_string())
|
.and_then(|x| x.get_by("_0"))
|
||||||
|
.and_then(|x| x.get_by("data"))
|
||||||
|
.map(|x| x.to_owned())
|
||||||
} {
|
} {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
None => {
|
None => {
|
||||||
@@ -340,7 +369,10 @@ impl<'a, R: ReadWrite> RemotePairingClient<'a, R> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let tlv = tlv::deserialize_tlv8(&B64.decode(pairing_data)?)?;
|
let tlv = tlv::deserialize_tlv8(&match R::deserialize_bytes(pairing_data) {
|
||||||
|
Some(t) => t,
|
||||||
|
None => return Err(IdeviceError::UnexpectedResponse),
|
||||||
|
})?;
|
||||||
debug!("Received pairingData response: {tlv:#?}");
|
debug!("Received pairingData response: {tlv:#?}");
|
||||||
|
|
||||||
let mut salt = Vec::new();
|
let mut salt = Vec::new();
|
||||||
@@ -427,25 +459,22 @@ impl<'a, R: ReadWrite> RemotePairingClient<'a, R> {
|
|||||||
data: client_proof.to_vec(),
|
data: client_proof.to_vec(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
let tlv = B64.encode(tlv);
|
let tlv = R::serialize_bytes(&tlv);
|
||||||
|
|
||||||
self.send_pairing_data(json! {{
|
self.send_pairing_data(plist!({
|
||||||
"data": tlv,
|
"data": tlv,
|
||||||
"kind": "setupManualPairing",
|
"kind": "setupManualPairing",
|
||||||
"sendingHost": self.sending_host,
|
"sendingHost": &self.sending_host,
|
||||||
"startNewSession": false,
|
"startNewSession": false,
|
||||||
|
|
||||||
}})
|
}))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let response = self.receive_pairing_data().await?;
|
let response = self.receive_pairing_data().await?;
|
||||||
let response = match response.as_str() {
|
let response = tlv::deserialize_tlv8(&match R::deserialize_bytes(response.to_owned()) {
|
||||||
Some(r) => tlv::deserialize_tlv8(&B64.decode(r)?)?,
|
Some(r) => r,
|
||||||
None => {
|
None => return Err(IdeviceError::UnexpectedResponse),
|
||||||
warn!("Pairing data proof response was not a string");
|
})?;
|
||||||
return Err(IdeviceError::UnexpectedResponse);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("Proof response: {response:#?}");
|
debug!("Proof response: {response:#?}");
|
||||||
|
|
||||||
@@ -576,22 +605,22 @@ impl<'a, R: ReadWrite> RemotePairingClient<'a, R> {
|
|||||||
data: vec![0x05],
|
data: vec![0x05],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
let tlv = B64.encode(&tlv);
|
let tlv = R::serialize_bytes(&tlv);
|
||||||
|
|
||||||
debug!("Sending encrypted data");
|
debug!("Sending encrypted data");
|
||||||
self.send_pairing_data(json! {{
|
self.send_pairing_data(plist!({
|
||||||
"data": tlv,
|
"data": tlv,
|
||||||
"kind": "setupManualPairing",
|
"kind": "setupManualPairing",
|
||||||
"sendingHost": self.sending_host,
|
"sendingHost": &self.sending_host,
|
||||||
"startNewSession": false,
|
"startNewSession": false,
|
||||||
}})
|
}))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
debug!("Waiting for encrypted data");
|
debug!("Waiting for encrypted data");
|
||||||
let response = match self.receive_pairing_data().await?.as_str() {
|
let response = match R::deserialize_bytes(self.receive_pairing_data().await?) {
|
||||||
Some(r) => B64.decode(r)?,
|
Some(r) => r,
|
||||||
None => {
|
None => {
|
||||||
warn!("Pairing data response was not base64");
|
warn!("Pairing data response was not deserializable");
|
||||||
return Err(IdeviceError::UnexpectedResponse);
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -630,89 +659,56 @@ impl<'a, R: ReadWrite> RemotePairingClient<'a, R> {
|
|||||||
|
|
||||||
async fn send_pairing_data(
|
async fn send_pairing_data(
|
||||||
&mut self,
|
&mut self,
|
||||||
pairing_data: impl Serialize,
|
pairing_data: impl Serialize + PlistConvertible,
|
||||||
) -> Result<(), IdeviceError> {
|
) -> Result<(), IdeviceError> {
|
||||||
self.send_plain_request(json! {{"event": {"_0": {"pairingData": {"_0": pairing_data}}}}})
|
self.inner
|
||||||
.await
|
.send_plain(
|
||||||
}
|
plist!({
|
||||||
|
"event": {
|
||||||
async fn receive_pairing_data(&mut self) -> Result<serde_json::Value, IdeviceError> {
|
"_0": {
|
||||||
let response = self.receive_plain_request().await?;
|
"pairingData": {
|
||||||
|
"_0": pairing_data
|
||||||
let response = match response.get("event").and_then(|x| x.get("_0")) {
|
|
||||||
Some(r) => r,
|
|
||||||
None => return Err(IdeviceError::UnexpectedResponse),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(data) = response
|
|
||||||
.get("pairingData")
|
|
||||||
.and_then(|x| x.get("_0"))
|
|
||||||
.and_then(|x| x.get("data"))
|
|
||||||
{
|
|
||||||
Ok(data.to_owned())
|
|
||||||
} else if let Some(err) = response.get("pairingRejectedWithError") {
|
|
||||||
let context = err
|
|
||||||
.get("wrappedError")
|
|
||||||
.and_then(|x| x.get("userInfo"))
|
|
||||||
.and_then(|x| x.get("NSLocalizedDescription"))
|
|
||||||
.and_then(|x| x.as_str())
|
|
||||||
.map(|x| x.to_string());
|
|
||||||
Err(IdeviceError::PairingRejected(context.unwrap_or_default()))
|
|
||||||
} else {
|
|
||||||
Err(IdeviceError::UnexpectedResponse)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
async fn send_plain_request(&mut self, value: impl Serialize) -> Result<(), IdeviceError> {
|
}),
|
||||||
self.send_rppairing(json!({
|
self.sequence_number,
|
||||||
"message": {"plain": {"_0": value}},
|
)
|
||||||
"originatedBy": "host",
|
|
||||||
"sequenceNumber": self.sequence_number
|
|
||||||
}))
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.sequence_number += 1;
|
self.sequence_number += 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive_plain_request(&mut self) -> Result<serde_json::Value, IdeviceError> {
|
async fn receive_pairing_data(&mut self) -> Result<plist::Value, IdeviceError> {
|
||||||
self.inner
|
let response = self.inner.recv_plain().await?;
|
||||||
.read_exact(&mut vec![0u8; RPPAIRING_MAGIC.len()])
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut packet_len_bytes = [0u8; 2];
|
let response = match response.get_by("event").and_then(|x| x.get_by("_0")) {
|
||||||
self.inner.read_exact(&mut packet_len_bytes).await?;
|
Some(r) => r,
|
||||||
let packet_len = u16::from_be_bytes(packet_len_bytes);
|
None => return Err(IdeviceError::UnexpectedResponse),
|
||||||
|
};
|
||||||
|
|
||||||
let mut value = vec![0u8; packet_len as usize];
|
if let Some(data) = response
|
||||||
self.inner.read_exact(&mut value).await?;
|
.get_by("pairingData")
|
||||||
|
.and_then(|x| x.get_by("_0"))
|
||||||
let value: serde_json::Value = serde_json::from_slice(&value)?;
|
.and_then(|x| x.get_by("data"))
|
||||||
let value = value
|
{
|
||||||
.get("message")
|
Ok(data.to_owned())
|
||||||
.and_then(|x| x.get("plain"))
|
} else if let Some(err) = response.get_by("pairingRejectedWithError") {
|
||||||
.and_then(|x| x.get("_0"));
|
let context = err
|
||||||
|
.get_by("wrappedError")
|
||||||
match value {
|
.and_then(|x| x.get_by("userInfo"))
|
||||||
Some(v) => Ok(v.to_owned()),
|
.and_then(|x| x.get_by("NSLocalizedDescription"))
|
||||||
None => Err(IdeviceError::UnexpectedResponse),
|
.and_then(|x| x.as_string())
|
||||||
|
.map(|x| x.to_string());
|
||||||
|
Err(IdeviceError::PairingRejected(context.unwrap_or_default()))
|
||||||
|
} else {
|
||||||
|
Err(IdeviceError::UnexpectedResponse)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_rppairing(&mut self, value: impl Serialize) -> Result<(), IdeviceError> {
|
impl<R: RpPairingSocketProvider> std::fmt::Debug for RemotePairingClient<'_, R> {
|
||||||
let value = serde_json::to_string(&value)?;
|
|
||||||
let x = value.as_bytes();
|
|
||||||
|
|
||||||
self.inner.write_all(RPPAIRING_MAGIC).await?;
|
|
||||||
self.inner
|
|
||||||
.write_all(&(x.len() as u16).to_be_bytes())
|
|
||||||
.await?;
|
|
||||||
self.inner.write_all(x).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: ReadWrite> std::fmt::Debug for RemotePairingClient<'_, R> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("RemotePairingClient")
|
f.debug_struct("RemotePairingClient")
|
||||||
.field("inner", &self.inner)
|
.field("inner", &self.inner)
|
||||||
|
|||||||
173
idevice/src/remote_pairing/socket.rs
Normal file
173
idevice/src/remote_pairing/socket.rs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use base64::{Engine as _, engine::general_purpose::STANDARD as B64};
|
||||||
|
use plist_macro::{plist, pretty_print_plist};
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::json;
|
||||||
|
use std::{fmt::Debug, pin::Pin};
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
IdeviceError, ReadWrite, RemoteXpcClient, remote_pairing::RPPAIRING_MAGIC, xpc::XPCObject,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait RpPairingSocketProvider: Debug {
|
||||||
|
fn send_plain(
|
||||||
|
&mut self,
|
||||||
|
value: impl Serialize,
|
||||||
|
seq: usize,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<(), IdeviceError>> + Send + '_>>;
|
||||||
|
|
||||||
|
fn recv_plain<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<plist::Value, IdeviceError>> + Send + 'a>>;
|
||||||
|
|
||||||
|
/// rppairing uses b64, while RemoteXPC uses raw bytes just fine
|
||||||
|
fn serialize_bytes(b: &[u8]) -> plist::Value;
|
||||||
|
fn deserialize_bytes(v: plist::Value) -> Option<Vec<u8>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RpPairingSocket<R: ReadWrite> {
|
||||||
|
pub inner: R,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: ReadWrite> RpPairingSocket<R> {
|
||||||
|
pub fn new(socket: R) -> Self {
|
||||||
|
Self { inner: socket }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_rppairing(&mut self, value: impl Serialize) -> Result<(), IdeviceError> {
|
||||||
|
let value = serde_json::to_string(&value)?;
|
||||||
|
let x = value.as_bytes();
|
||||||
|
|
||||||
|
self.inner.write_all(RPPAIRING_MAGIC).await?;
|
||||||
|
self.inner
|
||||||
|
.write_all(&(x.len() as u16).to_be_bytes())
|
||||||
|
.await?;
|
||||||
|
self.inner.write_all(x).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: ReadWrite> RpPairingSocketProvider for RpPairingSocket<R> {
|
||||||
|
fn send_plain(
|
||||||
|
&mut self,
|
||||||
|
value: impl Serialize,
|
||||||
|
seq: usize,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<(), IdeviceError>> + Send + '_>> {
|
||||||
|
let v = json!({
|
||||||
|
"message": {"plain": {"_0": value}},
|
||||||
|
"originatedBy": "host",
|
||||||
|
"sequenceNumber": seq
|
||||||
|
});
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
self.send_rppairing(v).await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recv_plain<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<plist::Value, IdeviceError>> + Send + 'a>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.inner
|
||||||
|
.read_exact(&mut vec![0u8; RPPAIRING_MAGIC.len()])
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut packet_len_bytes = [0u8; 2];
|
||||||
|
self.inner.read_exact(&mut packet_len_bytes).await?;
|
||||||
|
let packet_len = u16::from_be_bytes(packet_len_bytes);
|
||||||
|
|
||||||
|
let mut value = vec![0u8; packet_len as usize];
|
||||||
|
self.inner.read_exact(&mut value).await?;
|
||||||
|
|
||||||
|
let value: serde_json::Value = serde_json::from_slice(&value)?;
|
||||||
|
let value = value
|
||||||
|
.get("message")
|
||||||
|
.and_then(|x| x.get("plain"))
|
||||||
|
.and_then(|x| x.get("_0"));
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Some(v) => Ok(plist::to_value(v).unwrap()),
|
||||||
|
None => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_bytes(b: &[u8]) -> plist::Value {
|
||||||
|
plist!(B64.encode(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_bytes(v: plist::Value) -> Option<Vec<u8>> {
|
||||||
|
if let plist::Value::String(v) = v {
|
||||||
|
B64.decode(v).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: ReadWrite> RpPairingSocketProvider for RemoteXpcClient<R> {
|
||||||
|
fn send_plain(
|
||||||
|
&mut self,
|
||||||
|
value: impl Serialize,
|
||||||
|
seq: usize,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<(), IdeviceError>> + Send + '_>> {
|
||||||
|
let value: plist::Value = plist::to_value(&value).expect("plist assert failed");
|
||||||
|
let value: XPCObject = value.into();
|
||||||
|
|
||||||
|
let v = crate::xpc!({
|
||||||
|
"mangledTypeName": "RemotePairing.ControlChannelMessageEnvelope",
|
||||||
|
"value": {
|
||||||
|
"message": {"plain": {"_0": value}},
|
||||||
|
"originatedBy": "host",
|
||||||
|
"sequenceNumber": seq as u64
|
||||||
|
}
|
||||||
|
});
|
||||||
|
debug!("Sending XPC: {v:#?}");
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
self.send_object(v, true).await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recv_plain<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<plist::Value, IdeviceError>> + Send + 'a>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let msg = self.recv_root().await.unwrap();
|
||||||
|
debug!("Received RemoteXPC {}", pretty_print_plist(&msg));
|
||||||
|
let value = msg
|
||||||
|
.into_dictionary()
|
||||||
|
.and_then(|mut x| x.remove("value"))
|
||||||
|
.and_then(|x| x.into_dictionary())
|
||||||
|
.and_then(|mut x| x.remove("message"))
|
||||||
|
.and_then(|x| x.into_dictionary())
|
||||||
|
.and_then(|mut x| x.remove("plain"))
|
||||||
|
.and_then(|x| x.into_dictionary())
|
||||||
|
.and_then(|mut x| x.remove("_0"));
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Some(v) => Ok(v),
|
||||||
|
None => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_bytes(b: &[u8]) -> plist::Value {
|
||||||
|
plist::Value::Data(b.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_bytes(v: plist::Value) -> Option<Vec<u8>> {
|
||||||
|
if let plist::Value::Data(v) = v {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
warn!("Non-data passed to rppairingsocket::deserialize_bytes for RemoteXPC provider");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ impl<R: ReadWrite> RemoteXpcClient<R> {
|
|||||||
pub async fn new(socket: R) -> Result<Self, IdeviceError> {
|
pub async fn new(socket: R) -> Result<Self, IdeviceError> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
h2_client: http2::Http2Client::new(socket).await?,
|
h2_client: http2::Http2Client::new(socket).await?,
|
||||||
root_id: 0,
|
root_id: 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
use std::{io::Write, net::IpAddr, str::FromStr};
|
use std::{io::Write, net::IpAddr, str::FromStr};
|
||||||
|
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
use idevice::remote_pairing::{RemotePairingClient, RpPairingFile};
|
use idevice::remote_pairing::{RemotePairingClient, RpPairingFile, RpPairingSocket};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -49,6 +49,7 @@ async fn main() {
|
|||||||
tokio::net::TcpStream::connect((IpAddr::from_str(ip).expect("failed to parse IP"), port))
|
tokio::net::TcpStream::connect((IpAddr::from_str(ip).expect("failed to parse IP"), port))
|
||||||
.await
|
.await
|
||||||
.expect("Failed to connect");
|
.expect("Failed to connect");
|
||||||
|
let conn = RpPairingSocket::new(conn);
|
||||||
|
|
||||||
let host = "idevice-rs-jkcoxson";
|
let host = "idevice-rs-jkcoxson";
|
||||||
let mut rpf = RpPairingFile::generate(host);
|
let mut rpf = RpPairingFile::generate(host);
|
||||||
@@ -74,6 +75,8 @@ async fn main() {
|
|||||||
tokio::net::TcpStream::connect((IpAddr::from_str(ip).expect("failed to parse IP"), port))
|
tokio::net::TcpStream::connect((IpAddr::from_str(ip).expect("failed to parse IP"), port))
|
||||||
.await
|
.await
|
||||||
.expect("Failed to connect");
|
.expect("Failed to connect");
|
||||||
|
let conn = RpPairingSocket::new(conn);
|
||||||
|
|
||||||
let mut rpc = RemotePairingClient::new(conn, host, &mut rpf);
|
let mut rpc = RemotePairingClient::new(conn, host, &mut rpf);
|
||||||
rpc.connect(
|
rpc.connect(
|
||||||
async |_| {
|
async |_| {
|
||||||
|
|||||||
@@ -7,7 +7,12 @@
|
|||||||
use std::{any::Any, sync::Arc, time::Duration};
|
use std::{any::Any, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
use idevice::{RemoteXpcClient, rsd::RsdHandshake, xpc};
|
use idevice::{
|
||||||
|
RemoteXpcClient,
|
||||||
|
remote_pairing::{RemotePairingClient, RpPairingFile},
|
||||||
|
rsd::RsdHandshake,
|
||||||
|
};
|
||||||
|
use tokio::net::TcpStream;
|
||||||
use zeroconf::{
|
use zeroconf::{
|
||||||
BrowserEvent, MdnsBrowser, ServiceType,
|
BrowserEvent, MdnsBrowser, ServiceType,
|
||||||
prelude::{TEventLoop, TMdnsBrowser},
|
prelude::{TEventLoop, TMdnsBrowser},
|
||||||
@@ -57,7 +62,55 @@ fn on_service_discovered(
|
|||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
println!("Found iOS device to pair with!! - {result:?}");
|
println!("Found iOS device to pair with!! - {result:?}");
|
||||||
|
|
||||||
let looked_up = tokio::net::lookup_host(format!("{}:{}", result.host_name(), 58783))
|
let stream = match lookup_host_and_connect(result.host_name(), 58783).await {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
println!("Couldn't open TCP port on device");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let handshake = RsdHandshake::new(stream).await.expect("no rsd");
|
||||||
|
|
||||||
|
println!("handshake: {handshake:#?}");
|
||||||
|
|
||||||
|
let ts = handshake
|
||||||
|
.services
|
||||||
|
.get("com.apple.internal.dt.coredevice.untrusted.tunnelservice")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("connecting to tunnel service");
|
||||||
|
let stream = lookup_host_and_connect(result.host_name(), ts.port)
|
||||||
|
.await
|
||||||
|
.expect("failed to connect to tunnselservice");
|
||||||
|
let mut conn = RemoteXpcClient::new(stream).await.unwrap();
|
||||||
|
|
||||||
|
println!("doing tunnel service handshake");
|
||||||
|
conn.do_handshake().await.unwrap();
|
||||||
|
|
||||||
|
let msg = conn.recv_root().await.unwrap();
|
||||||
|
println!("{msg:#?}");
|
||||||
|
|
||||||
|
let host = "idevice-rs-jkcoxson";
|
||||||
|
let mut rpf = RpPairingFile::generate(host);
|
||||||
|
let mut rpc = RemotePairingClient::new(conn, host, &mut rpf);
|
||||||
|
rpc.connect(
|
||||||
|
async |_| "000000".to_string(),
|
||||||
|
0u8, // we need no state, so pass a single byte that will hopefully get optimized out
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("no pair");
|
||||||
|
|
||||||
|
rpf.write_to_file("ios_pairing_file.plist").await.unwrap();
|
||||||
|
println!(
|
||||||
|
"congrats you're paired now, the rppairing record has been saved. Have a nice day."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn lookup_host_and_connect(host: &str, port: u16) -> Option<TcpStream> {
|
||||||
|
let looked_up = tokio::net::lookup_host(format!("{}:{}", host, port))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -78,17 +131,6 @@ fn on_service_discovered(
|
|||||||
Err(e) => println!("failed to connect: {e:?}"),
|
Err(e) => println!("failed to connect: {e:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let stream = match stream {
|
|
||||||
Some(s) => s,
|
|
||||||
None => {
|
|
||||||
println!("Couldn't open TCP port on device");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let handshake = RsdHandshake::new(stream).await.expect("no rsd");
|
stream
|
||||||
|
|
||||||
println!("handshake: {handshake:?}");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user