1use std::collections::HashMap;
9
10use plist::Value;
11use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
12
13pub const SERVICE_NAME: &str = "com.apple.mobile.mobile_image_mounter";
14
15service_error!(
16 ImageMounterError,
17 #[error("device error: {0}")]
18 DeviceError(String),
19 #[error("TSS error: {0}")]
20 Tss(String),
21 #[error("download error: {0}")]
22 Download(String),
23);
24
25pub struct ImageMounterClient<S> {
27 stream: S,
28}
29
30impl<S: AsyncRead + AsyncWrite + Unpin + Send> ImageMounterClient<S> {
31 pub fn new(stream: S) -> Self {
32 Self { stream }
33 }
34
35 pub async fn copy_devices(&mut self) -> Result<Vec<plist::Dictionary>, ImageMounterError> {
37 let req = plist::Dictionary::from_iter([(
38 "Command".to_string(),
39 Value::String("CopyDevices".into()),
40 )]);
41 send_plist(&mut self.stream, &Value::Dictionary(req)).await?;
42 let resp = recv_plist(&mut self.stream).await?;
43 check_error(&resp)?;
44
45 match resp.get("EntryList") {
46 Some(Value::Array(items)) => items
47 .iter()
48 .map(|value| {
49 value.as_dictionary().cloned().ok_or_else(|| {
50 ImageMounterError::Protocol("CopyDevices entry was not a dictionary".into())
51 })
52 })
53 .collect(),
54 None => Ok(Vec::new()),
55 Some(_) => Err(ImageMounterError::Protocol(
56 "CopyDevices EntryList had unexpected type".into(),
57 )),
58 }
59 }
60
61 pub async fn is_image_mounted(&mut self) -> Result<bool, ImageMounterError> {
63 Ok(!self.lookup_image_signatures("Developer").await?.is_empty()
64 || !self
65 .lookup_image_signatures("Personalized")
66 .await?
67 .is_empty())
68 }
69
70 pub async fn lookup_image_signatures(
72 &mut self,
73 image_type: &str,
74 ) -> Result<Vec<Vec<u8>>, ImageMounterError> {
75 let req = plist::Dictionary::from_iter([
76 ("Command".to_string(), Value::String("LookupImage".into())),
77 ("ImageType".to_string(), Value::String(image_type.into())),
78 ]);
79 send_plist(&mut self.stream, &Value::Dictionary(req)).await?;
80 let resp = recv_plist(&mut self.stream).await?;
81 check_error(&resp)?;
82
83 match resp.get("ImageSignature") {
84 Some(Value::Array(items)) => items
85 .iter()
86 .map(|value| {
87 value.as_data().map(|bytes| bytes.to_vec()).ok_or_else(|| {
88 ImageMounterError::Protocol(
89 "LookupImage ImageSignature entry was not data".into(),
90 )
91 })
92 })
93 .collect(),
94 Some(Value::Data(bytes)) => Ok(vec![bytes.clone()]),
95 None => Ok(Vec::new()),
96 Some(_) => Err(ImageMounterError::Protocol(
97 "LookupImage ImageSignature had unexpected type".into(),
98 )),
99 }
100 }
101
102 pub async fn mount_standard(
107 &mut self,
108 image_bytes: &[u8],
109 signature: &[u8],
110 ) -> Result<(), ImageMounterError> {
111 self.upload_image(image_bytes, signature).await?;
113
114 let mount_req = plist::Dictionary::from_iter([
116 ("Command".to_string(), Value::String("MountImage".into())),
117 ("ImageType".to_string(), Value::String("Developer".into())),
118 (
119 "ImagePath".to_string(),
120 Value::String("/private/var/mobile/Media/PublicStaging/staging.dimage".into()),
121 ),
122 (
123 "ImageSignature".to_string(),
124 Value::Data(signature.to_vec()),
125 ),
126 ]);
127 send_plist(&mut self.stream, &Value::Dictionary(mount_req)).await?;
128 let resp = recv_plist(&mut self.stream).await?;
129 check_error(&resp)?;
130 Ok(())
131 }
132
133 pub async fn mount_personalized(
140 &mut self,
141 trustcache: &[u8],
142 build_manifest: &[u8],
143 image_bytes: &[u8],
144 ticket: &[u8],
145 ) -> Result<(), ImageMounterError> {
146 let ids = self.query_personalization_identifiers().await?;
148 tracing::debug!(
149 "personalization identifiers: {:?}",
150 ids.keys().collect::<Vec<_>>()
151 );
152
153 let nonce = self.query_nonce().await?;
155 tracing::debug!("personalization nonce: {} bytes", nonce.len());
156
157 self.upload_personalized_image(image_bytes, trustcache, build_manifest)
159 .await?;
160
161 let mount_req = plist::Dictionary::from_iter([
163 ("Command".to_string(), Value::String("MountImage".into())),
164 (
165 "ImageType".to_string(),
166 Value::String("Personalized".into()),
167 ),
168 (
169 "ImagePath".to_string(),
170 Value::String("/private/var/mobile/Media/PublicStaging/staging.dimage".into()),
171 ),
172 ("ImageSignature".to_string(), Value::Data(ticket.to_vec())),
173 ]);
174 send_plist(&mut self.stream, &Value::Dictionary(mount_req)).await?;
175 let resp = recv_plist(&mut self.stream).await?;
176 check_error(&resp)?;
177 Ok(())
178 }
179
180 pub async fn query_personalization_identifiers(
182 &mut self,
183 ) -> Result<HashMap<String, Value>, ImageMounterError> {
184 self.query_personalization_identifiers_with_type("DeveloperDiskImage")
185 .await
186 }
187
188 pub async fn query_personalization_identifiers_with_type(
190 &mut self,
191 personalized_image_type: &str,
192 ) -> Result<HashMap<String, Value>, ImageMounterError> {
193 let req = plist::Dictionary::from_iter([
194 (
195 "Command".to_string(),
196 Value::String("QueryPersonalizationIdentifiers".into()),
197 ),
198 (
199 "PersonalizedImageType".to_string(),
200 Value::String(personalized_image_type.into()),
201 ),
202 ]);
203 send_plist(&mut self.stream, &Value::Dictionary(req)).await?;
204 let resp = recv_plist(&mut self.stream).await?;
205 check_error(&resp)?;
206
207 let ids = resp
208 .get("PersonalizationIdentifiers")
209 .and_then(|v| v.as_dictionary())
210 .ok_or_else(|| {
211 ImageMounterError::Protocol("missing PersonalizationIdentifiers".into())
212 })?;
213
214 Ok(ids.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
215 }
216
217 pub async fn query_personalization_manifest(
219 &mut self,
220 personalized_image_type: &str,
221 image_signature: &[u8],
222 ) -> Result<Vec<u8>, ImageMounterError> {
223 let req = plist::Dictionary::from_iter([
224 (
225 "Command".to_string(),
226 Value::String("QueryPersonalizationManifest".into()),
227 ),
228 (
229 "PersonalizedImageType".to_string(),
230 Value::String(personalized_image_type.into()),
231 ),
232 (
233 "ImageType".to_string(),
234 Value::String(personalized_image_type.into()),
235 ),
236 (
237 "ImageSignature".to_string(),
238 Value::Data(image_signature.to_vec()),
239 ),
240 ]);
241 send_plist(&mut self.stream, &Value::Dictionary(req)).await?;
242 let resp = recv_plist(&mut self.stream).await?;
243 check_error(&resp)?;
244
245 let manifest = resp
246 .get("ImageSignature")
247 .and_then(|v| v.as_data())
248 .ok_or_else(|| ImageMounterError::Protocol("missing ImageSignature".into()))?;
249
250 Ok(manifest.to_vec())
251 }
252
253 pub async fn query_nonce(&mut self) -> Result<Vec<u8>, ImageMounterError> {
255 self.query_nonce_with_type("DeveloperDiskImage").await
256 }
257
258 pub async fn query_nonce_with_type(
260 &mut self,
261 personalized_image_type: &str,
262 ) -> Result<Vec<u8>, ImageMounterError> {
263 let req = plist::Dictionary::from_iter([
264 ("Command".to_string(), Value::String("QueryNonce".into())),
265 (
266 "PersonalizedImageType".to_string(),
267 Value::String(personalized_image_type.into()),
268 ),
269 ]);
270 send_plist(&mut self.stream, &Value::Dictionary(req)).await?;
271 let resp = recv_plist(&mut self.stream).await?;
272 check_error(&resp)?;
273
274 let nonce = resp
275 .get("PersonalizationNonce")
276 .and_then(|v| v.as_data())
277 .ok_or_else(|| ImageMounterError::Protocol("missing PersonalizationNonce".into()))?;
278
279 Ok(nonce.to_vec())
280 }
281
282 pub async fn query_developer_mode_status(&mut self) -> Result<bool, ImageMounterError> {
284 let req = plist::Dictionary::from_iter([(
285 "Command".to_string(),
286 Value::String("QueryDeveloperModeStatus".into()),
287 )]);
288 send_plist(&mut self.stream, &Value::Dictionary(req)).await?;
289 let resp = recv_plist(&mut self.stream).await?;
290 check_error(&resp)?;
291
292 Ok(resp
293 .get("DeveloperModeStatus")
294 .and_then(|v| v.as_boolean())
295 .unwrap_or(false))
296 }
297
298 pub async fn unmount_image(&mut self, mount_path: &str) -> Result<(), ImageMounterError> {
300 let req = plist::Dictionary::from_iter([
301 ("Command".to_string(), Value::String("UnmountImage".into())),
302 ("MountPath".to_string(), Value::String(mount_path.into())),
303 ]);
304 send_plist(&mut self.stream, &Value::Dictionary(req)).await?;
305 let resp = recv_plist(&mut self.stream).await?;
306 check_error(&resp)?;
307 Ok(())
308 }
309
310 async fn upload_image(
311 &mut self,
312 image_bytes: &[u8],
313 signature: &[u8],
314 ) -> Result<(), ImageMounterError> {
315 let req = plist::Dictionary::from_iter([
316 ("Command".to_string(), Value::String("ReceiveBytes".into())),
317 ("ImageType".to_string(), Value::String("Developer".into())),
318 (
319 "ImageSize".to_string(),
320 Value::Integer((image_bytes.len() as i64).into()),
321 ),
322 (
323 "ImageSignature".to_string(),
324 Value::Data(signature.to_vec()),
325 ),
326 ]);
327 send_plist(&mut self.stream, &Value::Dictionary(req)).await?;
328 let resp = recv_plist(&mut self.stream).await?;
329
330 let status = resp.get("Status").and_then(|v| v.as_string()).unwrap_or("");
331
332 if status == "ReceiveBytesAck" {
333 self.stream.write_all(image_bytes).await?;
335 self.stream.flush().await?;
336 let resp2 = recv_plist(&mut self.stream).await?;
337 check_error(&resp2)?;
338 } else {
339 check_error(&resp)?;
340 }
341 Ok(())
342 }
343
344 async fn upload_personalized_image(
345 &mut self,
346 image_bytes: &[u8],
347 trustcache: &[u8],
348 build_manifest: &[u8],
349 ) -> Result<(), ImageMounterError> {
350 let req = plist::Dictionary::from_iter([
351 ("Command".to_string(), Value::String("ReceiveBytes".into())),
352 (
353 "ImageType".to_string(),
354 Value::String("Personalized".into()),
355 ),
356 (
357 "ImageSize".to_string(),
358 Value::Integer((image_bytes.len() as i64).into()),
359 ),
360 (
361 "ImageTrustCache".to_string(),
362 Value::Data(trustcache.to_vec()),
363 ),
364 (
365 "BuildManifest".to_string(),
366 Value::Data(build_manifest.to_vec()),
367 ),
368 ]);
369 send_plist(&mut self.stream, &Value::Dictionary(req)).await?;
370 let resp = recv_plist(&mut self.stream).await?;
371
372 let status = resp.get("Status").and_then(|v| v.as_string()).unwrap_or("");
373
374 if status == "ReceiveBytesAck" {
375 self.stream.write_all(image_bytes).await?;
376 self.stream.flush().await?;
377 let resp2 = recv_plist(&mut self.stream).await?;
378 check_error(&resp2)?;
379 } else {
380 check_error(&resp)?;
381 }
382 Ok(())
383 }
384
385 pub async fn hangup(&mut self) -> Result<(), ImageMounterError> {
387 let req =
388 plist::Dictionary::from_iter([("Command".to_string(), Value::String("Hangup".into()))]);
389 send_plist(&mut self.stream, &Value::Dictionary(req)).await?;
390 Ok(())
391 }
392}
393
394fn check_error(resp: &plist::Dictionary) -> Result<(), ImageMounterError> {
395 if let Some(err) = resp.get("Error") {
396 let msg = err.as_string().unwrap_or("unknown error");
397 return Err(ImageMounterError::DeviceError(msg.to_string()));
398 }
399 Ok(())
400}
401
402async fn send_plist<S: AsyncWrite + Unpin>(
405 stream: &mut S,
406 value: &Value,
407) -> Result<(), ImageMounterError> {
408 let mut buf = Vec::new();
409 plist::to_writer_xml(&mut buf, value).map_err(|e| ImageMounterError::Plist(e.to_string()))?;
410 stream.write_all(&(buf.len() as u32).to_be_bytes()).await?;
411 stream.write_all(&buf).await?;
412 stream.flush().await?;
413 Ok(())
414}
415
416async fn recv_plist<S: AsyncRead + Unpin>(
417 stream: &mut S,
418) -> Result<plist::Dictionary, ImageMounterError> {
419 let mut len_buf = [0u8; 4];
420 stream.read_exact(&mut len_buf).await?;
421 let len = u32::from_be_bytes(len_buf) as usize;
422 const MAX_PLIST_SIZE: usize = 4 * 1024 * 1024;
423 if len > MAX_PLIST_SIZE {
424 return Err(ImageMounterError::Protocol(format!(
425 "plist length {len} exceeds maximum of {MAX_PLIST_SIZE}"
426 )));
427 }
428 let mut buf = vec![0u8; len];
429 stream.read_exact(&mut buf).await?;
430 let val: plist::Value =
431 plist::from_bytes(&buf).map_err(|e| ImageMounterError::Plist(e.to_string()))?;
432 val.into_dictionary()
433 .ok_or_else(|| ImageMounterError::Protocol("expected plist dictionary".into()))
434}
435
436#[cfg(test)]
437mod tests {
438 use crate::test_util::MockStream;
439
440 use super::*;
441
442 #[tokio::test]
443 async fn query_developer_mode_status_roundtrips_boolean() {
444 let response = Value::Dictionary(plist::Dictionary::from_iter([(
445 "DeveloperModeStatus".to_string(),
446 Value::Boolean(true),
447 )]));
448 let mut stream = MockStream::with_response(response);
449 let mut client = ImageMounterClient::new(&mut stream);
450
451 let enabled = client.query_developer_mode_status().await.unwrap();
452 assert!(enabled);
453
454 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
455 let payload = &stream.written[4..4 + len];
456 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
457 assert_eq!(
458 dict.get("Command").and_then(|v| v.as_string()),
459 Some("QueryDeveloperModeStatus")
460 );
461 }
462
463 #[tokio::test]
464 async fn lookup_image_signatures_roundtrips_data_array() {
465 let response = Value::Dictionary(plist::Dictionary::from_iter([(
466 "ImageSignature".to_string(),
467 Value::Array(vec![Value::Data(vec![0xde, 0xad, 0xbe, 0xef])]),
468 )]));
469 let mut stream = MockStream::with_response(response);
470 let mut client = ImageMounterClient::new(&mut stream);
471
472 let signatures = client.lookup_image_signatures("Developer").await.unwrap();
473 assert_eq!(signatures, vec![vec![0xde, 0xad, 0xbe, 0xef]]);
474
475 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
476 let payload = &stream.written[4..4 + len];
477 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
478 assert_eq!(
479 dict.get("Command").and_then(|v| v.as_string()),
480 Some("LookupImage")
481 );
482 assert_eq!(
483 dict.get("ImageType").and_then(|v| v.as_string()),
484 Some("Developer")
485 );
486 }
487
488 #[tokio::test]
489 async fn is_image_mounted_checks_both_image_types() {
490 let mut stream = MockStream::with_responses(vec![
491 Value::Dictionary(plist::Dictionary::new()),
492 Value::Dictionary(plist::Dictionary::from_iter([(
493 "ImageSignature".to_string(),
494 Value::Array(vec![Value::Data(vec![1, 2, 3])]),
495 )])),
496 ]);
497 let mut client = ImageMounterClient::new(&mut stream);
498
499 let mounted = client.is_image_mounted().await.unwrap();
500 assert!(mounted);
501 }
502
503 #[tokio::test]
504 async fn unmount_image_sends_mount_path() {
505 let response = Value::Dictionary(plist::Dictionary::new());
506 let mut stream = MockStream::with_response(response);
507 let mut client = ImageMounterClient::new(&mut stream);
508
509 client.unmount_image("/System/Developer").await.unwrap();
510
511 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
512 let payload = &stream.written[4..4 + len];
513 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
514 assert_eq!(
515 dict.get("Command").and_then(|v| v.as_string()),
516 Some("UnmountImage")
517 );
518 assert_eq!(
519 dict.get("MountPath").and_then(|v| v.as_string()),
520 Some("/System/Developer")
521 );
522 }
523
524 #[tokio::test]
525 async fn query_nonce_uses_query_nonce_command_and_personalization_nonce() {
526 let response = Value::Dictionary(plist::Dictionary::from_iter([(
527 "PersonalizationNonce".to_string(),
528 Value::Data(vec![0xde, 0xad, 0xbe, 0xef]),
529 )]));
530 let mut stream = MockStream::with_response(response);
531 let mut client = ImageMounterClient::new(&mut stream);
532
533 let nonce = client.query_nonce().await.unwrap();
534 assert_eq!(nonce, vec![0xde, 0xad, 0xbe, 0xef]);
535
536 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
537 let payload = &stream.written[4..4 + len];
538 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
539 assert_eq!(
540 dict.get("Command").and_then(|v| v.as_string()),
541 Some("QueryNonce")
542 );
543 assert_eq!(
544 dict.get("PersonalizedImageType")
545 .and_then(|v| v.as_string()),
546 Some("DeveloperDiskImage")
547 );
548 }
549
550 #[tokio::test]
551 async fn copy_devices_roundtrips_entry_list() {
552 let response = Value::Dictionary(plist::Dictionary::from_iter([(
553 "EntryList".to_string(),
554 Value::Array(vec![Value::Dictionary(plist::Dictionary::from_iter([
555 (
556 "ImageType".to_string(),
557 Value::String("Personalized".into()),
558 ),
559 ("ImageSignature".to_string(), Value::Data(vec![0xaa, 0xbb])),
560 ]))]),
561 )]));
562 let mut stream = MockStream::with_response(response);
563 let mut client = ImageMounterClient::new(&mut stream);
564
565 let entries = client.copy_devices().await.unwrap();
566 assert_eq!(entries.len(), 1);
567 assert_eq!(
568 entries[0].get("ImageType").and_then(|v| v.as_string()),
569 Some("Personalized")
570 );
571 assert_eq!(
572 entries[0].get("ImageSignature").and_then(|v| v.as_data()),
573 Some([0xaa, 0xbb].as_slice())
574 );
575
576 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
577 let payload = &stream.written[4..4 + len];
578 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
579 assert_eq!(
580 dict.get("Command").and_then(|v| v.as_string()),
581 Some("CopyDevices")
582 );
583 }
584
585 #[tokio::test]
586 async fn query_personalization_manifest_roundtrips_request_and_manifest_bytes() {
587 let response = Value::Dictionary(plist::Dictionary::from_iter([(
588 "ImageSignature".to_string(),
589 Value::Data(vec![0xfa, 0xce]),
590 )]));
591 let mut stream = MockStream::with_response(response);
592 let mut client = ImageMounterClient::new(&mut stream);
593
594 let manifest = client
595 .query_personalization_manifest("DeveloperDiskImage", &[0xaa, 0xbb])
596 .await
597 .unwrap();
598 assert_eq!(manifest, vec![0xfa, 0xce]);
599
600 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
601 let payload = &stream.written[4..4 + len];
602 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
603 assert_eq!(
604 dict.get("Command").and_then(|v| v.as_string()),
605 Some("QueryPersonalizationManifest")
606 );
607 assert_eq!(
608 dict.get("PersonalizedImageType")
609 .and_then(|v| v.as_string()),
610 Some("DeveloperDiskImage")
611 );
612 assert_eq!(
613 dict.get("ImageType").and_then(|v| v.as_string()),
614 Some("DeveloperDiskImage")
615 );
616 assert_eq!(
617 dict.get("ImageSignature").and_then(|v| v.as_data()),
618 Some([0xaa, 0xbb].as_slice())
619 );
620 }
621
622 #[tokio::test]
623 async fn query_nonce_with_custom_image_type_uses_provided_personalized_image_type() {
624 let response = Value::Dictionary(plist::Dictionary::from_iter([(
625 "PersonalizationNonce".to_string(),
626 Value::Data(vec![0xde, 0xad]),
627 )]));
628 let mut stream = MockStream::with_response(response);
629 let mut client = ImageMounterClient::new(&mut stream);
630
631 let nonce = client.query_nonce_with_type("Cryptex").await.unwrap();
632 assert_eq!(nonce, vec![0xde, 0xad]);
633
634 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
635 let payload = &stream.written[4..4 + len];
636 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
637 assert_eq!(
638 dict.get("Command").and_then(|v| v.as_string()),
639 Some("QueryNonce")
640 );
641 assert_eq!(
642 dict.get("PersonalizedImageType")
643 .and_then(|v| v.as_string()),
644 Some("Cryptex")
645 );
646 }
647
648 #[tokio::test]
649 async fn query_personalization_identifiers_with_custom_type_uses_provided_image_type() {
650 let response = Value::Dictionary(plist::Dictionary::from_iter([(
651 "PersonalizationIdentifiers".to_string(),
652 Value::Dictionary(plist::Dictionary::from_iter([(
653 "BoardId".to_string(),
654 Value::Integer(12.into()),
655 )])),
656 )]));
657 let mut stream = MockStream::with_response(response);
658 let mut client = ImageMounterClient::new(&mut stream);
659
660 let identifiers = client
661 .query_personalization_identifiers_with_type("Cryptex")
662 .await
663 .unwrap();
664 assert_eq!(
665 identifiers
666 .get("BoardId")
667 .and_then(|v| v.as_unsigned_integer()),
668 Some(12)
669 );
670
671 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
672 let payload = &stream.written[4..4 + len];
673 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
674 assert_eq!(
675 dict.get("Command").and_then(|v| v.as_string()),
676 Some("QueryPersonalizationIdentifiers")
677 );
678 assert_eq!(
679 dict.get("PersonalizedImageType")
680 .and_then(|v| v.as_string()),
681 Some("Cryptex")
682 );
683 }
684}