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)?;
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 = plist::from_bytes(&buf)?;
431 val.into_dictionary()
432 .ok_or_else(|| ImageMounterError::Protocol("expected plist dictionary".into()))
433}
434
435#[cfg(test)]
436mod tests {
437 use crate::test_util::MockStream;
438
439 use super::*;
440
441 #[tokio::test]
442 async fn query_developer_mode_status_roundtrips_boolean() {
443 let response = Value::Dictionary(plist::Dictionary::from_iter([(
444 "DeveloperModeStatus".to_string(),
445 Value::Boolean(true),
446 )]));
447 let mut stream = MockStream::with_response(response);
448 let mut client = ImageMounterClient::new(&mut stream);
449
450 let enabled = client.query_developer_mode_status().await.unwrap();
451 assert!(enabled);
452
453 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
454 let payload = &stream.written[4..4 + len];
455 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
456 assert_eq!(
457 dict.get("Command").and_then(|v| v.as_string()),
458 Some("QueryDeveloperModeStatus")
459 );
460 }
461
462 #[tokio::test]
463 async fn lookup_image_signatures_roundtrips_data_array() {
464 let response = Value::Dictionary(plist::Dictionary::from_iter([(
465 "ImageSignature".to_string(),
466 Value::Array(vec![Value::Data(vec![0xde, 0xad, 0xbe, 0xef])]),
467 )]));
468 let mut stream = MockStream::with_response(response);
469 let mut client = ImageMounterClient::new(&mut stream);
470
471 let signatures = client.lookup_image_signatures("Developer").await.unwrap();
472 assert_eq!(signatures, vec![vec![0xde, 0xad, 0xbe, 0xef]]);
473
474 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
475 let payload = &stream.written[4..4 + len];
476 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
477 assert_eq!(
478 dict.get("Command").and_then(|v| v.as_string()),
479 Some("LookupImage")
480 );
481 assert_eq!(
482 dict.get("ImageType").and_then(|v| v.as_string()),
483 Some("Developer")
484 );
485 }
486
487 #[tokio::test]
488 async fn is_image_mounted_checks_both_image_types() {
489 let mut stream = MockStream::with_responses(vec![
490 Value::Dictionary(plist::Dictionary::new()),
491 Value::Dictionary(plist::Dictionary::from_iter([(
492 "ImageSignature".to_string(),
493 Value::Array(vec![Value::Data(vec![1, 2, 3])]),
494 )])),
495 ]);
496 let mut client = ImageMounterClient::new(&mut stream);
497
498 let mounted = client.is_image_mounted().await.unwrap();
499 assert!(mounted);
500 }
501
502 #[tokio::test]
503 async fn unmount_image_sends_mount_path() {
504 let response = Value::Dictionary(plist::Dictionary::new());
505 let mut stream = MockStream::with_response(response);
506 let mut client = ImageMounterClient::new(&mut stream);
507
508 client.unmount_image("/System/Developer").await.unwrap();
509
510 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
511 let payload = &stream.written[4..4 + len];
512 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
513 assert_eq!(
514 dict.get("Command").and_then(|v| v.as_string()),
515 Some("UnmountImage")
516 );
517 assert_eq!(
518 dict.get("MountPath").and_then(|v| v.as_string()),
519 Some("/System/Developer")
520 );
521 }
522
523 #[tokio::test]
524 async fn query_nonce_uses_query_nonce_command_and_personalization_nonce() {
525 let response = Value::Dictionary(plist::Dictionary::from_iter([(
526 "PersonalizationNonce".to_string(),
527 Value::Data(vec![0xde, 0xad, 0xbe, 0xef]),
528 )]));
529 let mut stream = MockStream::with_response(response);
530 let mut client = ImageMounterClient::new(&mut stream);
531
532 let nonce = client.query_nonce().await.unwrap();
533 assert_eq!(nonce, vec![0xde, 0xad, 0xbe, 0xef]);
534
535 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
536 let payload = &stream.written[4..4 + len];
537 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
538 assert_eq!(
539 dict.get("Command").and_then(|v| v.as_string()),
540 Some("QueryNonce")
541 );
542 assert_eq!(
543 dict.get("PersonalizedImageType")
544 .and_then(|v| v.as_string()),
545 Some("DeveloperDiskImage")
546 );
547 }
548
549 #[tokio::test]
550 async fn copy_devices_roundtrips_entry_list() {
551 let response = Value::Dictionary(plist::Dictionary::from_iter([(
552 "EntryList".to_string(),
553 Value::Array(vec![Value::Dictionary(plist::Dictionary::from_iter([
554 (
555 "ImageType".to_string(),
556 Value::String("Personalized".into()),
557 ),
558 ("ImageSignature".to_string(), Value::Data(vec![0xaa, 0xbb])),
559 ]))]),
560 )]));
561 let mut stream = MockStream::with_response(response);
562 let mut client = ImageMounterClient::new(&mut stream);
563
564 let entries = client.copy_devices().await.unwrap();
565 assert_eq!(entries.len(), 1);
566 assert_eq!(
567 entries[0].get("ImageType").and_then(|v| v.as_string()),
568 Some("Personalized")
569 );
570 assert_eq!(
571 entries[0].get("ImageSignature").and_then(|v| v.as_data()),
572 Some([0xaa, 0xbb].as_slice())
573 );
574
575 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
576 let payload = &stream.written[4..4 + len];
577 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
578 assert_eq!(
579 dict.get("Command").and_then(|v| v.as_string()),
580 Some("CopyDevices")
581 );
582 }
583
584 #[tokio::test]
585 async fn query_personalization_manifest_roundtrips_request_and_manifest_bytes() {
586 let response = Value::Dictionary(plist::Dictionary::from_iter([(
587 "ImageSignature".to_string(),
588 Value::Data(vec![0xfa, 0xce]),
589 )]));
590 let mut stream = MockStream::with_response(response);
591 let mut client = ImageMounterClient::new(&mut stream);
592
593 let manifest = client
594 .query_personalization_manifest("DeveloperDiskImage", &[0xaa, 0xbb])
595 .await
596 .unwrap();
597 assert_eq!(manifest, vec![0xfa, 0xce]);
598
599 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
600 let payload = &stream.written[4..4 + len];
601 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
602 assert_eq!(
603 dict.get("Command").and_then(|v| v.as_string()),
604 Some("QueryPersonalizationManifest")
605 );
606 assert_eq!(
607 dict.get("PersonalizedImageType")
608 .and_then(|v| v.as_string()),
609 Some("DeveloperDiskImage")
610 );
611 assert_eq!(
612 dict.get("ImageType").and_then(|v| v.as_string()),
613 Some("DeveloperDiskImage")
614 );
615 assert_eq!(
616 dict.get("ImageSignature").and_then(|v| v.as_data()),
617 Some([0xaa, 0xbb].as_slice())
618 );
619 }
620
621 #[tokio::test]
622 async fn query_nonce_with_custom_image_type_uses_provided_personalized_image_type() {
623 let response = Value::Dictionary(plist::Dictionary::from_iter([(
624 "PersonalizationNonce".to_string(),
625 Value::Data(vec![0xde, 0xad]),
626 )]));
627 let mut stream = MockStream::with_response(response);
628 let mut client = ImageMounterClient::new(&mut stream);
629
630 let nonce = client.query_nonce_with_type("Cryptex").await.unwrap();
631 assert_eq!(nonce, vec![0xde, 0xad]);
632
633 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
634 let payload = &stream.written[4..4 + len];
635 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
636 assert_eq!(
637 dict.get("Command").and_then(|v| v.as_string()),
638 Some("QueryNonce")
639 );
640 assert_eq!(
641 dict.get("PersonalizedImageType")
642 .and_then(|v| v.as_string()),
643 Some("Cryptex")
644 );
645 }
646
647 #[tokio::test]
648 async fn query_personalization_identifiers_with_custom_type_uses_provided_image_type() {
649 let response = Value::Dictionary(plist::Dictionary::from_iter([(
650 "PersonalizationIdentifiers".to_string(),
651 Value::Dictionary(plist::Dictionary::from_iter([(
652 "BoardId".to_string(),
653 Value::Integer(12.into()),
654 )])),
655 )]));
656 let mut stream = MockStream::with_response(response);
657 let mut client = ImageMounterClient::new(&mut stream);
658
659 let identifiers = client
660 .query_personalization_identifiers_with_type("Cryptex")
661 .await
662 .unwrap();
663 assert_eq!(
664 identifiers
665 .get("BoardId")
666 .and_then(|v| v.as_unsigned_integer()),
667 Some(12)
668 );
669
670 let len = u32::from_be_bytes(stream.written[..4].try_into().unwrap()) as usize;
671 let payload = &stream.written[4..4 + len];
672 let dict: plist::Dictionary = plist::from_bytes(payload).unwrap();
673 assert_eq!(
674 dict.get("Command").and_then(|v| v.as_string()),
675 Some("QueryPersonalizationIdentifiers")
676 );
677 assert_eq!(
678 dict.get("PersonalizedImageType")
679 .and_then(|v| v.as_string()),
680 Some("Cryptex")
681 );
682 }
683}