1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
//! iOS Mobile Installation Agent (misagent) Client
//!
//! Provides functionality for interacting with the misagent service on iOS devices,
//! which manages provisioning profiles and certificates.
use tracing::warn;
use crate::{Idevice, IdeviceError, IdeviceService, RsdService, obf};
/// Client for interacting with the iOS misagent service
///
/// The misagent service handles:
/// - Installation of provisioning profiles
/// - Removal of provisioning profiles
/// - Querying installed profiles
#[derive(Debug)]
pub struct MisagentClient {
/// The underlying device connection with established misagent service
pub idevice: Idevice,
}
impl RsdService for MisagentClient {
fn rsd_service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.misagent.shim.remote")
}
async fn from_stream(stream: Box<dyn crate::ReadWrite>) -> Result<Self, IdeviceError> {
let mut stream = Idevice::new(stream, "");
stream.rsd_checkin().await?;
Ok(Self::new(stream))
}
}
impl IdeviceService for MisagentClient {
/// Returns the misagent service name as registered with lockdownd
fn service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.misagent")
}
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}
impl MisagentClient {
/// Creates a new misagent client from an existing device connection
///
/// # Arguments
/// * `idevice` - Pre-established device connection
pub fn new(idevice: Idevice) -> Self {
Self { idevice }
}
/// Installs a provisioning profile on the device
///
/// # Arguments
/// * `profile` - The provisioning profile data to install
///
/// # Returns
/// `Ok(())` on successful installation
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - The profile is invalid
/// - Installation is not permitted
///
/// # Example
/// ```rust
/// let profile_data = std::fs::read("profile.mobileprovision")?;
/// client.install(profile_data).await?;
/// ```
pub async fn install(&mut self, profile: Vec<u8>) -> Result<(), IdeviceError> {
let req = crate::plist!({
"MessageType": "Install",
"Profile": profile,
"ProfileType": "Provisioning"
});
self.idevice.send_plist(req).await?;
let mut res = self.idevice.read_plist().await?;
match res.remove("Status") {
Some(plist::Value::Integer(status)) => {
if let Some(status) = status.as_unsigned() {
if status == 0 {
Ok(())
} else {
Err(IdeviceError::MisagentFailure)
}
} else {
warn!("Misagent return status wasn't unsigned");
Err(IdeviceError::UnexpectedResponse(
"install Status is not an unsigned integer".into(),
))
}
}
_ => {
warn!("Did not get integer status response");
Err(IdeviceError::UnexpectedResponse(
"missing integer Status in install response".into(),
))
}
}
}
/// Removes a provisioning profile from the device
///
/// # Arguments
/// * `id` - The UUID of the profile to remove
///
/// # Returns
/// `Ok(())` on successful removal
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - The profile doesn't exist
/// - Removal is not permitted
///
/// # Example
/// ```rust
/// client.remove("asdf").await?;
/// ```
pub async fn remove(&mut self, id: &str) -> Result<(), IdeviceError> {
let req = crate::plist!({
"MessageType": "Remove",
"ProfileID": id,
"ProfileType": "Provisioning"
});
self.idevice.send_plist(req).await?;
let mut res = self.idevice.read_plist().await?;
match res.remove("Status") {
Some(plist::Value::Integer(status)) => {
if let Some(status) = status.as_unsigned() {
if status == 0 {
Ok(())
} else {
Err(IdeviceError::MisagentFailure)
}
} else {
warn!("Misagent return status wasn't unsigned");
Err(IdeviceError::UnexpectedResponse(
"remove Status is not an unsigned integer".into(),
))
}
}
_ => {
warn!("Did not get integer status response");
Err(IdeviceError::UnexpectedResponse(
"missing integer Status in remove response".into(),
))
}
}
}
/// Retrieves all provisioning profiles from the device
///
/// # Returns
/// A vector containing raw profile data for each installed profile
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - The response is malformed
///
/// # Example
/// ```rust
/// let profiles = client.copy_all().await?;
/// for profile in profiles {
/// println!("Profile size: {} bytes", profile.len());
/// }
/// ```
pub async fn copy_all(&mut self) -> Result<Vec<Vec<u8>>, IdeviceError> {
let req = crate::plist!({
"MessageType": "CopyAll",
"ProfileType": "Provisioning"
});
self.idevice.send_plist(req).await?;
let mut res = self.idevice.read_plist().await?;
match res.remove("Payload") {
Some(plist::Value::Array(a)) => {
let mut res = Vec::new();
for profile in a {
if let Some(profile) = profile.as_data() {
res.push(profile.to_vec());
} else {
warn!("Misagent CopyAll did not return data plists");
return Err(IdeviceError::UnexpectedResponse(
"CopyAll Payload array contains non-data entry".into(),
));
}
}
Ok(res)
}
_ => {
warn!("Did not get a payload of provisioning profiles as an array");
Err(IdeviceError::UnexpectedResponse(
"missing Payload array in CopyAll response".into(),
))
}
}
}
}