1use openssl::asn1::Asn1Time;
6use openssl::bn::{BigNum, MsbOption};
7use openssl::hash::MessageDigest;
8use openssl::nid::Nid;
9use openssl::pkcs12::Pkcs12;
10use openssl::pkey::PKey;
11use openssl::rsa::Rsa;
12use openssl::x509::extension::{BasicConstraints, ExtendedKeyUsage, KeyUsage};
13use openssl::x509::{X509NameBuilder, X509};
14use uuid::Uuid;
15
16pub const DEFAULT_ORGANIZATION_NAME: &str = "ios-rs";
17pub const DEFAULT_LANGUAGE: &str = "en";
18pub const DEFAULT_LOCALE: &str = "en_US";
19pub const DEFAULT_SKIP_SETUP_KEYS: &[&str] = &[
20 "Accessibility",
21 "AccessibilityAppearance",
22 "ActionButton",
23 "AgeAssurance",
24 "AgeBasedSafetySettings",
25 "Android",
26 "Appearance",
27 "AppleID",
28 "AppStore",
29 "Avatar",
30 "Biometric",
31 "CameraButton",
32 "CloudStorage",
33 "DeviceProtection",
34 "DeviceToDeviceMigration",
35 "Diagnostics",
36 "Display",
37 "EnableLockdownMode",
38 "ExpressLanguage",
39 "FileVault",
40 "iCloudDiagnostics",
41 "iCloudStorage",
42 "iMessageAndFaceTime",
43 "IntendedUser",
44 "Intelligence",
45 "Keyboard",
46 "Language",
47 "LanguageAndLocale",
48 "Location",
49 "LockdownMode",
50 "MessagingActivationUsingPhoneNumber",
51 "Multitasking",
52 "OSShowCase",
53 "Passcode",
54 "Payment",
55 "PreferredLanguage",
56 "Privacy",
57 "Region",
58 "Registration",
59 "Restore",
60 "RestoreCompleted",
61 "Safety",
62 "SafetyAndHandling",
63 "ScreenSaver",
64 "ScreenTime",
65 "SIMSetup",
66 "Siri",
67 "SoftwareUpdate",
68 "SpokenLanguage",
69 "TapToSetup",
70 "TermsOfAddress",
71 "Tips",
72 "Tone",
73 "TOS",
74 "TouchID",
75 "TrueToneDisplay",
76 "TVHomeScreenSync",
77 "TVProviderSignIn",
78 "TVRoom",
79 "UnlockWithWatch",
80 "UpdateCompleted",
81 "Wallpaper",
82 "WatchMigration",
83 "WebContentFiltering",
84 "Welcome",
85 "WiFi",
86 "DisplayTone",
87 "HomeButtonSensitivity",
88 "OnBoarding",
89 "Zoom",
90];
91
92#[derive(Debug, thiserror::Error)]
93pub enum PrepareError {
94 #[error("crypto error: {0}")]
95 Crypto(String),
96 #[error("plist error: {0}")]
97 Plist(#[from] plist::Error),
98}
99
100#[derive(Debug, Clone)]
101pub struct SupervisionIdentity {
102 pub certificate_der: Vec<u8>,
103 pub certificate_pem: Vec<u8>,
104 pub private_key_pem: Vec<u8>,
105 pub pkcs12_der: Vec<u8>,
106}
107
108pub fn generate_supervision_identity(
109 common_name: &str,
110 password: &str,
111) -> Result<SupervisionIdentity, PrepareError> {
112 let rsa = Rsa::generate(2048).map_err(crypto_err)?;
113 let pkey = PKey::from_rsa(rsa).map_err(crypto_err)?;
114
115 let mut name_builder = X509NameBuilder::new().map_err(crypto_err)?;
116 let subject = if common_name.trim().is_empty() {
117 DEFAULT_ORGANIZATION_NAME
118 } else {
119 common_name
120 };
121 name_builder
122 .append_entry_by_nid(Nid::COMMONNAME, subject)
123 .map_err(crypto_err)?;
124 let name = name_builder.build();
125
126 let mut serial = BigNum::new().map_err(crypto_err)?;
127 serial
128 .rand(159, MsbOption::MAYBE_ZERO, false)
129 .map_err(crypto_err)?;
130 let serial = serial.to_asn1_integer().map_err(crypto_err)?;
131
132 let mut builder = X509::builder().map_err(crypto_err)?;
133 builder.set_version(2).map_err(crypto_err)?;
134 builder.set_serial_number(&serial).map_err(crypto_err)?;
135 builder.set_subject_name(&name).map_err(crypto_err)?;
136 builder.set_issuer_name(&name).map_err(crypto_err)?;
137 builder.set_pubkey(&pkey).map_err(crypto_err)?;
138
139 let not_before = Asn1Time::days_from_now(0).map_err(crypto_err)?;
140 let not_after = Asn1Time::days_from_now(3650).map_err(crypto_err)?;
141 builder.set_not_before(¬_before).map_err(crypto_err)?;
142 builder.set_not_after(¬_after).map_err(crypto_err)?;
143 builder
144 .append_extension(
145 BasicConstraints::new()
146 .critical()
147 .ca()
148 .build()
149 .map_err(crypto_err)?,
150 )
151 .map_err(crypto_err)?;
152 builder
153 .append_extension(
154 KeyUsage::new()
155 .digital_signature()
156 .key_cert_sign()
157 .build()
158 .map_err(crypto_err)?,
159 )
160 .map_err(crypto_err)?;
161 builder
162 .append_extension(
163 ExtendedKeyUsage::new()
164 .server_auth()
165 .client_auth()
166 .build()
167 .map_err(crypto_err)?,
168 )
169 .map_err(crypto_err)?;
170 builder
171 .sign(&pkey, MessageDigest::sha512())
172 .map_err(crypto_err)?;
173
174 let cert = builder.build();
175 #[allow(deprecated)]
176 let pkcs12 = Pkcs12::builder()
177 .build(password, subject, &pkey, &cert)
178 .map_err(crypto_err)?;
179
180 Ok(SupervisionIdentity {
181 certificate_der: cert.to_der().map_err(crypto_err)?,
182 certificate_pem: cert.to_pem().map_err(crypto_err)?,
183 private_key_pem: pkey.private_key_to_pem_pkcs8().map_err(crypto_err)?,
184 pkcs12_der: pkcs12.to_der().map_err(crypto_err)?,
185 })
186}
187
188pub fn build_cloud_configuration(
189 skip_setup: &[String],
190 supervision_certificate_der: Option<&[u8]>,
191 organization_name: Option<&str>,
192) -> plist::Dictionary {
193 let mut cloud = plist::Dictionary::from_iter([
194 ("AllowPairing".to_string(), plist::Value::Boolean(true)),
195 (
196 "SkipSetup".to_string(),
197 plist::Value::Array(
198 skip_setup
199 .iter()
200 .cloned()
201 .map(plist::Value::String)
202 .collect(),
203 ),
204 ),
205 ]);
206
207 if let Some(cert) = supervision_certificate_der {
208 cloud.insert(
209 "OrganizationName".to_string(),
210 plist::Value::String(
211 organization_name
212 .unwrap_or(DEFAULT_ORGANIZATION_NAME)
213 .to_string(),
214 ),
215 );
216 cloud.insert(
217 "OrganizationMagic".to_string(),
218 plist::Value::String(Uuid::new_v4().to_string()),
219 );
220 cloud.insert(
221 "SupervisorHostCertificates".to_string(),
222 plist::Value::Array(vec![plist::Value::Data(cert.to_vec())]),
223 );
224 cloud.insert("IsSupervised".to_string(), plist::Value::Boolean(true));
225 cloud.insert("IsMultiUser".to_string(), plist::Value::Boolean(false));
226 }
227
228 cloud
229}
230
231pub fn build_initial_profile() -> Result<Vec<u8>, PrepareError> {
232 let payload_uuid = Uuid::new_v4().to_string();
233 let content_uuid = Uuid::new_v4().to_string();
234 let payload = plist::Value::Dictionary(plist::Dictionary::from_iter([
235 (
236 "PayloadContent".to_string(),
237 plist::Value::Array(vec![plist::Value::Dictionary(
238 plist::Dictionary::from_iter([
239 (
240 "PayloadDescription".to_string(),
241 plist::Value::String("Configures Restrictions".into()),
242 ),
243 (
244 "PayloadDisplayName".to_string(),
245 plist::Value::String("Restrictions".into()),
246 ),
247 (
248 "PayloadIdentifier".to_string(),
249 plist::Value::String(format!("com.apple.applicationaccess.{content_uuid}")),
250 ),
251 (
252 "PayloadType".to_string(),
253 plist::Value::String("com.apple.applicationaccess".into()),
254 ),
255 (
256 "PayloadUUID".to_string(),
257 plist::Value::String(content_uuid),
258 ),
259 (
260 "PayloadVersion".to_string(),
261 plist::Value::Integer(1.into()),
262 ),
263 (
264 "allowAppInstallation".to_string(),
265 plist::Value::Boolean(true),
266 ),
267 ("allowAppRemoval".to_string(), plist::Value::Boolean(true)),
268 ("allowCamera".to_string(), plist::Value::Boolean(true)),
269 ("allowCloudBackup".to_string(), plist::Value::Boolean(true)),
270 (
271 "allowDiagnosticSubmission".to_string(),
272 plist::Value::Boolean(true),
273 ),
274 ]),
275 )]),
276 ),
277 (
278 "PayloadDisplayName".to_string(),
279 plist::Value::String("Device Preparation".into()),
280 ),
281 (
282 "PayloadIdentifier".to_string(),
283 plist::Value::String(format!("com.apple.prepare.{payload_uuid}")),
284 ),
285 (
286 "PayloadRemovalDisallowed".to_string(),
287 plist::Value::Boolean(false),
288 ),
289 (
290 "PayloadType".to_string(),
291 plist::Value::String("Configuration".into()),
292 ),
293 (
294 "PayloadUUID".to_string(),
295 plist::Value::String(payload_uuid),
296 ),
297 (
298 "PayloadVersion".to_string(),
299 plist::Value::Integer(1.into()),
300 ),
301 ]));
302
303 let mut buf = Vec::new();
304 plist::to_writer_xml(&mut buf, &payload)?;
305 Ok(buf)
306}
307
308fn crypto_err(err: openssl::error::ErrorStack) -> PrepareError {
309 PrepareError::Crypto(err.to_string())
310}