mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
Compare commits
3 Commits
rppairing-
...
rppairing
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aefda7e6f1 | ||
|
|
b4caa3b271 | ||
|
|
81a644170e |
682
Cargo.lock
generated
682
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,10 @@ x509-cert = { version = "0.2", optional = true, features = [
|
||||
"builder",
|
||||
"pem",
|
||||
], default-features = false }
|
||||
x25519-dalek = { version = "2", optional = true }
|
||||
ed25519-dalek = { version = "2", features = ["rand_core"], optional = true }
|
||||
hkdf = { version = "0.12", optional = true }
|
||||
chacha20poly1305 = { version = "0.10", optional = true }
|
||||
|
||||
obfstr = { version = "0.4", optional = true }
|
||||
|
||||
@@ -98,6 +102,14 @@ pcapd = []
|
||||
preboard_service = []
|
||||
obfuscate = ["dep:obfstr"]
|
||||
restore_service = []
|
||||
remote_pairing = [
|
||||
"dep:json",
|
||||
"dep:x25519-dalek",
|
||||
"dep:ed25519-dalek",
|
||||
"dep:hkdf",
|
||||
"dep:chacha20poly1305",
|
||||
"dep:uuid",
|
||||
]
|
||||
rsd = ["xpc"]
|
||||
screenshotr = []
|
||||
syslog_relay = ["dep:bytes"]
|
||||
@@ -136,6 +148,10 @@ full = [
|
||||
"pcapd",
|
||||
"preboard_service",
|
||||
"restore_service",
|
||||
"usbmuxd",
|
||||
"xpc",
|
||||
"location_simulation",
|
||||
"remote_pairing",
|
||||
"rsd",
|
||||
"screenshotr",
|
||||
"springboardservices",
|
||||
|
||||
@@ -723,6 +723,25 @@ pub enum IdeviceError {
|
||||
|
||||
#[error("Developer mode is not enabled")]
|
||||
DeveloperModeNotEnabled = -68,
|
||||
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
#[error("could not parse as JSON")]
|
||||
JsonParseFailed(#[from] json::Error) = -69,
|
||||
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
#[error("unknown TLV type: {0}")]
|
||||
UnknownTlv(u8) = -70,
|
||||
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
#[error("malformed TLV")]
|
||||
MalformedTlv = -71,
|
||||
|
||||
#[error("failed to decode base64 string")]
|
||||
Base64Decode(#[from] base64::DecodeError) = -72,
|
||||
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
#[error("pair verify failed")]
|
||||
PairVerifyFailed = -73,
|
||||
}
|
||||
|
||||
impl IdeviceError {
|
||||
@@ -887,6 +906,15 @@ impl IdeviceError {
|
||||
#[cfg(feature = "installation_proxy")]
|
||||
IdeviceError::MalformedPackageArchive(_) => -67,
|
||||
IdeviceError::DeveloperModeNotEnabled => -68,
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
IdeviceError::JsonParseFailed(_) => -69,
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
IdeviceError::UnknownTlv(_) => -70,
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
IdeviceError::MalformedTlv => -71,
|
||||
IdeviceError::Base64Decode(_) => -72,
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
IdeviceError::PairVerifyFailed => -73,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ pub mod os_trace_relay;
|
||||
pub mod pcapd;
|
||||
#[cfg(feature = "preboard_service")]
|
||||
pub mod preboard_service;
|
||||
#[cfg(feature = "remote_pairing")]
|
||||
pub mod remote_pairing;
|
||||
#[cfg(feature = "restore_service")]
|
||||
pub mod restore_service;
|
||||
#[cfg(feature = "rsd")]
|
||||
|
||||
319
idevice/src/services/remote_pairing/mod.rs
Normal file
319
idevice/src/services/remote_pairing/mod.rs
Normal file
@@ -0,0 +1,319 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
|
||||
use chacha20poly1305::{
|
||||
aead::{Aead, Payload},
|
||||
ChaCha20Poly1305, KeyInit as _, Nonce,
|
||||
};
|
||||
use ed25519_dalek::{Signature, SigningKey};
|
||||
use hkdf::Hkdf;
|
||||
use json::{object, JsonValue};
|
||||
use log::{debug, warn};
|
||||
use rp_pairing_file::RpPairingFile;
|
||||
use rsa::signature::SignerMut;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
use crate::{IdeviceError, ReadWrite};
|
||||
|
||||
pub mod rp_pairing_file;
|
||||
mod tlv;
|
||||
|
||||
const RP_MAGIC: &str = "RPPairing";
|
||||
|
||||
pub struct RPPairingClient<R: ReadWrite> {
|
||||
socket: R,
|
||||
sequence_number: usize,
|
||||
}
|
||||
|
||||
impl<R: ReadWrite> RPPairingClient<R> {
|
||||
pub fn new(socket: R) -> Self {
|
||||
Self {
|
||||
socket,
|
||||
sequence_number: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handshake(&mut self) -> Result<(), IdeviceError> {
|
||||
let req = object! {
|
||||
"request": {
|
||||
"_0": {
|
||||
"handshake": {
|
||||
"_0": {
|
||||
"hostOptions": {
|
||||
"attemptPairVerify": true
|
||||
},
|
||||
"wireProtocolVersion": 24
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
self.send_plain(req).await?;
|
||||
let res = self.read_json().await?;
|
||||
debug!("Handshake response: {res:#}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn pair(&mut self) -> Result<RpPairingFile, IdeviceError> {
|
||||
let pairing = RpPairingFile::generate();
|
||||
|
||||
// M1 for a NEW pairing
|
||||
let t = vec![
|
||||
tlv::TLV8Entry {
|
||||
tlv_type: tlv::PairingDataComponentType::Method,
|
||||
data: vec![0x00],
|
||||
},
|
||||
tlv::TLV8Entry {
|
||||
tlv_type: tlv::PairingDataComponentType::State,
|
||||
data: vec![0x01],
|
||||
},
|
||||
];
|
||||
let t = B64.encode(tlv::serialize_tlv8(&t));
|
||||
|
||||
self.send_pairing_data(object! {
|
||||
"data": t,
|
||||
"kind": "setupManualPairing",
|
||||
"sendingHost": "Mac",
|
||||
"startNewSession": true,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let res = self.read_event_data().await?;
|
||||
debug!("Pair (M1) res: {res:#?}");
|
||||
|
||||
// M2: Now you handle the SRP steps...
|
||||
todo!("Implement SRP steps using the device's public key and salt from the response");
|
||||
}
|
||||
|
||||
pub async fn validate_pairing(&mut self, pairing: RpPairingFile) -> Result<(), IdeviceError> {
|
||||
let pairing_data = tlv::serialize_tlv8(&[
|
||||
tlv::TLV8Entry {
|
||||
tlv_type: tlv::PairingDataComponentType::State,
|
||||
data: vec![0x01],
|
||||
},
|
||||
tlv::TLV8Entry {
|
||||
tlv_type: tlv::PairingDataComponentType::PublicKey,
|
||||
data: pairing.x_public_key.to_bytes().to_vec(),
|
||||
},
|
||||
]);
|
||||
let pairing_data = B64.encode(pairing_data);
|
||||
|
||||
let req = object! {
|
||||
"event": {
|
||||
"_0": {
|
||||
"pairingData": {
|
||||
"_0": {
|
||||
"data": pairing_data,
|
||||
"kind": "verifyManualPairing",
|
||||
"startNewSession": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
self.send_plain(req).await?;
|
||||
let res = self.read_json().await?;
|
||||
debug!("Public key response: {res:#}");
|
||||
let data =
|
||||
&res["message"]["plain"]["_0"]["event"]["_0"]["pairingData"]["_0"]["data"].as_str();
|
||||
let data = match data {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
warn!("RPPairing validate pair message didn't contain pairingData -> _0 -> data");
|
||||
return Err(IdeviceError::UnexpectedResponse);
|
||||
}
|
||||
};
|
||||
let data = B64.decode(data)?;
|
||||
let data = tlv::deserialize_tlv8(&data)?;
|
||||
println!("{data:#?}");
|
||||
|
||||
let device_public_key = match data
|
||||
.iter()
|
||||
.find(|x| x.tlv_type == tlv::PairingDataComponentType::PublicKey)
|
||||
{
|
||||
Some(d) => d,
|
||||
None => {
|
||||
warn!("No public key in TLV data");
|
||||
return Err(IdeviceError::UnexpectedResponse);
|
||||
}
|
||||
};
|
||||
let peer_pub_bytes: [u8; 32] = match device_public_key.data.as_slice().try_into() {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
warn!("Device public key isn't the expected size");
|
||||
return Err(IdeviceError::NotEnoughBytes(
|
||||
32,
|
||||
device_public_key.data.len(),
|
||||
));
|
||||
}
|
||||
};
|
||||
let device_public_key = x25519_dalek::PublicKey::from(peer_pub_bytes);
|
||||
let shared_secret = pairing.x_private_key.diffie_hellman(&device_public_key);
|
||||
|
||||
// Derive encryption key with HKDF-SHA512
|
||||
let hk =
|
||||
Hkdf::<sha2::Sha512>::new(Some(b"Pair-Verify-Encrypt-Salt"), shared_secret.as_bytes());
|
||||
|
||||
let mut okm = [0u8; 32];
|
||||
hk.expand(b"Pair-Verify-Encrypt-Info", &mut okm).unwrap();
|
||||
|
||||
// ChaCha20Poly1305 AEAD cipher
|
||||
let cipher = ChaCha20Poly1305::new(chacha20poly1305::Key::from_slice(&okm));
|
||||
|
||||
let mut ed25519_signing_key = pairing.e_private_key;
|
||||
|
||||
let mut signbuf = Vec::with_capacity(32 + pairing.identifier.len() + 32);
|
||||
signbuf.extend_from_slice(pairing.x_public_key.as_bytes()); // 32 bytes
|
||||
signbuf.extend_from_slice(pairing.identifier.as_bytes()); // variable
|
||||
signbuf.extend_from_slice(device_public_key.as_bytes()); // 32 bytes
|
||||
|
||||
let signature: Signature = ed25519_signing_key.sign(&signbuf);
|
||||
|
||||
let plaintext = vec![
|
||||
tlv::TLV8Entry {
|
||||
tlv_type: tlv::PairingDataComponentType::Identifier,
|
||||
data: pairing.identifier.as_bytes().to_vec(),
|
||||
},
|
||||
tlv::TLV8Entry {
|
||||
tlv_type: tlv::PairingDataComponentType::Signature,
|
||||
data: signature.to_vec(),
|
||||
},
|
||||
];
|
||||
let plaintext = tlv::serialize_tlv8(&plaintext);
|
||||
let nonce = Nonce::from_slice(b"\x00\x00\x00\x00PV-Msg03"); // 12-byte nonce
|
||||
let ciphertext = cipher
|
||||
.encrypt(
|
||||
nonce,
|
||||
Payload {
|
||||
msg: &plaintext,
|
||||
aad: &[],
|
||||
},
|
||||
)
|
||||
.expect("encryption should not fail");
|
||||
|
||||
let msg = vec![
|
||||
tlv::TLV8Entry {
|
||||
tlv_type: tlv::PairingDataComponentType::State,
|
||||
data: [0x03].to_vec(),
|
||||
},
|
||||
tlv::TLV8Entry {
|
||||
tlv_type: tlv::PairingDataComponentType::EncryptedData,
|
||||
data: ciphertext,
|
||||
},
|
||||
];
|
||||
|
||||
let msg = object! {"event": {"_0": {"pairingData": {"_0": {
|
||||
"data": B64.encode(tlv::serialize_tlv8(&msg)),
|
||||
"kind": "verifyManualPairing",
|
||||
"startNewSession": false}}}}};
|
||||
|
||||
self.send_plain(msg).await?;
|
||||
|
||||
let res = self.read_json().await?;
|
||||
debug!("Verify response: {res:#}");
|
||||
|
||||
let data =
|
||||
&res["message"]["plain"]["_0"]["event"]["_0"]["pairingData"]["_0"]["data"].as_str();
|
||||
let data = match data {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
warn!("RPPairing validate pair message didn't contain pairingData -> _0 -> data");
|
||||
return Err(IdeviceError::UnexpectedResponse);
|
||||
}
|
||||
};
|
||||
let data = B64.decode(data)?;
|
||||
let data = tlv::deserialize_tlv8(&data)?;
|
||||
println!("{data:#?}");
|
||||
|
||||
// Check if the device responded with an error (which is expected for a new pairing)
|
||||
if data
|
||||
.iter()
|
||||
.any(|x| x.tlv_type == tlv::PairingDataComponentType::ErrorResponse)
|
||||
{
|
||||
debug!("Verification failed, device reported an error. This is expected for a new pairing.");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
// Tell the device we are aborting the verification attempt.
|
||||
let msg = object! {"event": {"_0": {"pairVerifyFailed": {}}}};
|
||||
self.send_plain(msg).await?;
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
|
||||
self.pair().await?;
|
||||
// Return a specific error to the caller.
|
||||
return Err(IdeviceError::PairVerifyFailed);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_pairing_data(&mut self, data: JsonValue) -> Result<(), IdeviceError> {
|
||||
self.send_event(object! {
|
||||
"pairingData": {
|
||||
"_0": data
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn send_event(&mut self, data: JsonValue) -> Result<(), IdeviceError> {
|
||||
let req = object! {
|
||||
"event": {
|
||||
"_0": data
|
||||
}
|
||||
};
|
||||
self.send_plain(req).await
|
||||
}
|
||||
|
||||
async fn read_event_data(&mut self) -> Result<Vec<tlv::TLV8Entry>, IdeviceError> {
|
||||
let res = self.read_json().await?;
|
||||
match &res["message"]["plain"]["_0"]["event"]["_0"]["pairingData"]["_0"]["data"].as_str() {
|
||||
Some(r) => Ok(tlv::deserialize_tlv8(&B64.decode(r)?)?),
|
||||
None => Err(IdeviceError::UnexpectedResponse),
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_plain(&mut self, data: JsonValue) -> Result<(), IdeviceError> {
|
||||
let req = object! {
|
||||
sequenceNumber: self.sequence_number,
|
||||
originatedBy: "host",
|
||||
message: {
|
||||
plain: {
|
||||
_0: data
|
||||
}
|
||||
}
|
||||
};
|
||||
debug!("Sending {req:#}");
|
||||
|
||||
self.sequence_number += 1;
|
||||
self.send_json(req).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_json(&mut self, data: JsonValue) -> Result<(), IdeviceError> {
|
||||
// Send the magic
|
||||
self.socket.write_all(RP_MAGIC.as_bytes()).await?;
|
||||
|
||||
// Packet length
|
||||
let data = data.to_string().into_bytes();
|
||||
self.socket.write_u16(data.len() as u16).await?; // big endian
|
||||
|
||||
self.socket.write_all(&data).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read_json(&mut self) -> Result<JsonValue, IdeviceError> {
|
||||
// Read the magic
|
||||
let mut magic_buf = [0u8; RP_MAGIC.len()];
|
||||
self.socket.read_exact(&mut magic_buf).await?;
|
||||
|
||||
// Read JSON length
|
||||
let len = self.socket.read_u16().await?;
|
||||
|
||||
let mut buf = vec![0u8; len as usize];
|
||||
self.socket.read_exact(&mut buf).await?;
|
||||
|
||||
let data = String::from_utf8_lossy(&buf);
|
||||
Ok(json::parse(&data)?)
|
||||
}
|
||||
}
|
||||
35
idevice/src/services/remote_pairing/rp_pairing_file.rs
Normal file
35
idevice/src/services/remote_pairing/rp_pairing_file.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||
use rsa::rand_core::OsRng;
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey as X25519PublicKey};
|
||||
|
||||
pub struct RpPairingFile {
|
||||
pub(crate) x_private_key: EphemeralSecret,
|
||||
pub(crate) x_public_key: X25519PublicKey,
|
||||
pub(crate) e_private_key: SigningKey,
|
||||
pub(crate) e_public_key: VerifyingKey,
|
||||
pub(crate) identifier: String,
|
||||
}
|
||||
|
||||
impl RpPairingFile {
|
||||
pub fn generate() -> Self {
|
||||
// X25519 private key (ephemeral)
|
||||
let x25519_private_key = EphemeralSecret::random_from_rng(OsRng);
|
||||
let x25519_public_key = X25519PublicKey::from(&x25519_private_key);
|
||||
|
||||
// Ed25519 private key (persistent signing key)
|
||||
let ed25519_private_key = SigningKey::generate(&mut OsRng);
|
||||
let ed25519_public_key = VerifyingKey::from(&ed25519_private_key);
|
||||
|
||||
let identifier = uuid::Uuid::new_v4().to_string().to_uppercase();
|
||||
|
||||
Self {
|
||||
x_private_key: x25519_private_key,
|
||||
x_public_key: x25519_public_key,
|
||||
e_private_key: ed25519_private_key,
|
||||
e_public_key: ed25519_public_key,
|
||||
identifier,
|
||||
}
|
||||
}
|
||||
}
|
||||
133
idevice/src/services/remote_pairing/tlv.rs
Normal file
133
idevice/src/services/remote_pairing/tlv.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use crate::IdeviceError;
|
||||
|
||||
// from pym3
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum PairingDataComponentType {
|
||||
Method = 0x00,
|
||||
Identifier = 0x01,
|
||||
Salt = 0x02,
|
||||
PublicKey = 0x03,
|
||||
Proof = 0x04,
|
||||
EncryptedData = 0x05,
|
||||
State = 0x06,
|
||||
ErrorResponse = 0x07,
|
||||
RetryDelay = 0x08,
|
||||
Certificate = 0x09,
|
||||
Signature = 0x0a,
|
||||
Permissions = 0x0b,
|
||||
FragmentData = 0x0c,
|
||||
FragmentLast = 0x0d,
|
||||
SessionId = 0x0e,
|
||||
Ttl = 0x0f,
|
||||
ExtraData = 0x10,
|
||||
Info = 0x11,
|
||||
Acl = 0x12,
|
||||
Flags = 0x13,
|
||||
ValidationData = 0x14,
|
||||
MfiAuthToken = 0x15,
|
||||
MfiProductType = 0x16,
|
||||
SerialNumber = 0x17,
|
||||
MfiAuthTokenUuid = 0x18,
|
||||
AppFlags = 0x19,
|
||||
OwnershipProof = 0x1a,
|
||||
SetupCodeType = 0x1b,
|
||||
ProductionData = 0x1c,
|
||||
AppInfo = 0x1d,
|
||||
Separator = 0xff,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TLV8Entry {
|
||||
pub tlv_type: PairingDataComponentType,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TLV8Entry {
|
||||
/// SRP stage
|
||||
pub fn m(stage: u8) -> Self {
|
||||
Self {
|
||||
tlv_type: PairingDataComponentType::State,
|
||||
data: [stage].to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_tlv8(entries: &[TLV8Entry]) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
for entry in entries {
|
||||
out.push(entry.tlv_type as u8);
|
||||
out.push(entry.data.len() as u8);
|
||||
out.extend(&entry.data);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn deserialize_tlv8(input: &[u8]) -> Result<Vec<TLV8Entry>, IdeviceError> {
|
||||
let mut index = 0;
|
||||
let mut result = Vec::new();
|
||||
|
||||
while index + 2 <= input.len() {
|
||||
let type_byte = input[index];
|
||||
let length = input[index + 1] as usize;
|
||||
index += 2;
|
||||
|
||||
if index + length > input.len() {
|
||||
return Err(IdeviceError::MalformedTlv);
|
||||
}
|
||||
|
||||
let data = input[index..index + length].to_vec();
|
||||
index += length;
|
||||
|
||||
let tlv_type = PairingDataComponentType::try_from(type_byte)
|
||||
.map_err(|_| IdeviceError::UnknownTlv(type_byte))?;
|
||||
|
||||
result.push(TLV8Entry { tlv_type, data });
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for PairingDataComponentType {
|
||||
type Error = u8;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
use PairingDataComponentType::*;
|
||||
Ok(match value {
|
||||
0x00 => Method,
|
||||
0x01 => Identifier,
|
||||
0x02 => Salt,
|
||||
0x03 => PublicKey,
|
||||
0x04 => Proof,
|
||||
0x05 => EncryptedData,
|
||||
0x06 => State,
|
||||
0x07 => ErrorResponse,
|
||||
0x08 => RetryDelay,
|
||||
0x09 => Certificate,
|
||||
0x0a => Signature,
|
||||
0x0b => Permissions,
|
||||
0x0c => FragmentData,
|
||||
0x0d => FragmentLast,
|
||||
0x0e => SessionId,
|
||||
0x0f => Ttl,
|
||||
0x10 => ExtraData,
|
||||
0x11 => Info,
|
||||
0x12 => Acl,
|
||||
0x13 => Flags,
|
||||
0x14 => ValidationData,
|
||||
0x15 => MfiAuthToken,
|
||||
0x16 => MfiProductType,
|
||||
0x17 => SerialNumber,
|
||||
0x18 => MfiAuthTokenUuid,
|
||||
0x19 => AppFlags,
|
||||
0x1a => OwnershipProof,
|
||||
0x1b => SetupCodeType,
|
||||
0x1c => ProductionData,
|
||||
0x1d => AppInfo,
|
||||
0xff => Separator,
|
||||
other => return Err(other),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,10 @@ path = "src/lockdown.rs"
|
||||
name = "restore_service"
|
||||
path = "src/restore_service.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "remote_pairing"
|
||||
path = "src/remote_pairing.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "companion_proxy"
|
||||
path = "src/companion_proxy.rs"
|
||||
|
||||
25
tools/src/remote_pairing.rs
Normal file
25
tools/src/remote_pairing.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use idevice::{
|
||||
remote_pairing::{rp_pairing_file::RpPairingFile, RPPairingClient},
|
||||
IdeviceError,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), IdeviceError> {
|
||||
env_logger::init();
|
||||
let conn = tokio::net::TcpStream::connect("192.168.50.247:49152")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut client = RPPairingClient::new(conn);
|
||||
client.handshake().await?;
|
||||
let pairing = RpPairingFile::generate();
|
||||
client
|
||||
.validate_pairing(pairing)
|
||||
.await
|
||||
.expect("No validate?");
|
||||
client.pair().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user