mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
Compare commits
3 Commits
| 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",
|
"builder",
|
||||||
"pem",
|
"pem",
|
||||||
], default-features = false }
|
], 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 }
|
obfstr = { version = "0.4", optional = true }
|
||||||
|
|
||||||
@@ -98,6 +102,14 @@ pcapd = []
|
|||||||
preboard_service = []
|
preboard_service = []
|
||||||
obfuscate = ["dep:obfstr"]
|
obfuscate = ["dep:obfstr"]
|
||||||
restore_service = []
|
restore_service = []
|
||||||
|
remote_pairing = [
|
||||||
|
"dep:json",
|
||||||
|
"dep:x25519-dalek",
|
||||||
|
"dep:ed25519-dalek",
|
||||||
|
"dep:hkdf",
|
||||||
|
"dep:chacha20poly1305",
|
||||||
|
"dep:uuid",
|
||||||
|
]
|
||||||
rsd = ["xpc"]
|
rsd = ["xpc"]
|
||||||
screenshotr = []
|
screenshotr = []
|
||||||
syslog_relay = ["dep:bytes"]
|
syslog_relay = ["dep:bytes"]
|
||||||
@@ -136,6 +148,10 @@ full = [
|
|||||||
"pcapd",
|
"pcapd",
|
||||||
"preboard_service",
|
"preboard_service",
|
||||||
"restore_service",
|
"restore_service",
|
||||||
|
"usbmuxd",
|
||||||
|
"xpc",
|
||||||
|
"location_simulation",
|
||||||
|
"remote_pairing",
|
||||||
"rsd",
|
"rsd",
|
||||||
"screenshotr",
|
"screenshotr",
|
||||||
"springboardservices",
|
"springboardservices",
|
||||||
|
|||||||
@@ -723,6 +723,25 @@ pub enum IdeviceError {
|
|||||||
|
|
||||||
#[error("Developer mode is not enabled")]
|
#[error("Developer mode is not enabled")]
|
||||||
DeveloperModeNotEnabled = -68,
|
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 {
|
impl IdeviceError {
|
||||||
@@ -887,6 +906,15 @@ impl IdeviceError {
|
|||||||
#[cfg(feature = "installation_proxy")]
|
#[cfg(feature = "installation_proxy")]
|
||||||
IdeviceError::MalformedPackageArchive(_) => -67,
|
IdeviceError::MalformedPackageArchive(_) => -67,
|
||||||
IdeviceError::DeveloperModeNotEnabled => -68,
|
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;
|
pub mod pcapd;
|
||||||
#[cfg(feature = "preboard_service")]
|
#[cfg(feature = "preboard_service")]
|
||||||
pub mod preboard_service;
|
pub mod preboard_service;
|
||||||
|
#[cfg(feature = "remote_pairing")]
|
||||||
|
pub mod remote_pairing;
|
||||||
#[cfg(feature = "restore_service")]
|
#[cfg(feature = "restore_service")]
|
||||||
pub mod restore_service;
|
pub mod restore_service;
|
||||||
#[cfg(feature = "rsd")]
|
#[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"
|
name = "restore_service"
|
||||||
path = "src/restore_service.rs"
|
path = "src/restore_service.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "remote_pairing"
|
||||||
|
path = "src/remote_pairing.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "companion_proxy"
|
name = "companion_proxy"
|
||||||
path = "src/companion_proxy.rs"
|
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