ios_core/lockdown/
pair_record.rs1use std::path::PathBuf;
2
3use serde::Deserialize;
4
5#[derive(Debug, thiserror::Error)]
6pub enum PairRecordError {
7 #[error("pair record not found for UDID: {0}")]
8 NotFound(String),
9 #[error("failed to parse pair record: {0}")]
10 Parse(String),
11}
12
13#[derive(Debug, Deserialize)]
15#[serde(rename_all = "PascalCase")]
16pub struct PairRecord {
17 #[serde(with = "serde_bytes")]
19 pub device_certificate: Vec<u8>,
20 #[serde(with = "serde_bytes")]
22 pub host_certificate: Vec<u8>,
23 #[serde(with = "serde_bytes")]
25 pub host_private_key: Vec<u8>,
26 #[serde(with = "serde_bytes")]
28 pub root_certificate: Vec<u8>,
29 #[serde(rename = "HostID")]
31 pub host_id: String,
32 #[serde(rename = "SystemBUID")]
34 pub system_buid: String,
35 pub wifi_mac_address: Option<String>,
37}
38
39impl PairRecord {
40 pub fn load(udid: &str) -> Result<Self, PairRecordError> {
42 let path = default_pair_record_path(udid);
43 Self::load_from_path(&path, udid)
44 }
45
46 pub fn load_from_path(path: &std::path::Path, udid: &str) -> Result<Self, PairRecordError> {
48 let data = std::fs::read(path).map_err(|_| PairRecordError::NotFound(udid.to_string()))?;
49 plist::from_bytes(&data).map_err(|e| PairRecordError::Parse(e.to_string()))
50 }
51}
52
53pub fn default_pair_record_path(udid: &str) -> PathBuf {
54 default_pair_record_dir().join(format!("{udid}.plist"))
55}
56
57pub fn default_pair_record_dir() -> PathBuf {
58 pair_record_dir_for_platform(
59 cfg!(target_os = "macos"),
60 cfg!(windows),
61 &std::env::var("ALLUSERSPROFILE").unwrap_or_default(),
62 )
63}
64
65#[cfg(test)]
66pub(crate) fn pair_record_path_for_platform(
67 udid: &str,
68 is_macos: bool,
69 is_windows: bool,
70 all_users_profile: &str,
71) -> PathBuf {
72 pair_record_dir_for_platform(is_macos, is_windows, all_users_profile)
73 .join(format!("{udid}.plist"))
74}
75
76fn pair_record_dir_for_platform(
77 is_macos: bool,
78 is_windows: bool,
79 all_users_profile: &str,
80) -> PathBuf {
81 if is_windows {
82 PathBuf::from(all_users_profile)
83 .join("Apple")
84 .join("Lockdown")
85 } else if is_macos {
86 PathBuf::from("/var/db/lockdown")
87 } else {
88 PathBuf::from("/var/lib/lockdown")
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_pair_record_path_macos() {
98 let path = pair_record_path_for_platform("ABC123DEF", true, false, "");
99 assert_eq!(path, PathBuf::from("/var/db/lockdown/ABC123DEF.plist"));
100 }
101
102 #[test]
103 fn test_pair_record_path_windows() {
104 let path = pair_record_path_for_platform("ABC123DEF", false, true, "C:\\ProgramData");
105 let s = path.to_string_lossy();
106 assert!(s.contains("ABC123DEF"));
107 assert!(s.contains("Apple"));
108 assert!(s.contains("Lockdown"));
109 }
110
111 #[test]
112 fn test_pair_record_path_linux() {
113 let path = pair_record_path_for_platform("ABC123DEF", false, false, "");
114 assert_eq!(path, PathBuf::from("/var/lib/lockdown/ABC123DEF.plist"));
115 }
116
117 #[test]
118 fn test_pair_record_dir_windows() {
119 let path = pair_record_dir_for_platform(false, true, "C:\\ProgramData");
120 assert!(path.starts_with("C:\\ProgramData"));
121 assert!(path.ends_with(PathBuf::from("Apple").join("Lockdown")));
122 }
123}