1use openssl::asn1::Asn1Time;
19use openssl::bn::BigNum;
20use openssl::hash::MessageDigest;
21use openssl::pkcs12::Pkcs12;
22use openssl::pkcs7::{Pkcs7, Pkcs7Flags};
23use openssl::pkey::{HasPublic, PKey, Private};
24use openssl::rsa::Rsa;
25use openssl::stack::Stack;
26use openssl::x509::extension::{BasicConstraints, KeyUsage, SubjectKeyIdentifier};
27use openssl::x509::{X509Builder, X509NameBuilder, X509};
28use serde::{Deserialize, Serialize};
29use tokio::io::{AsyncRead, AsyncWrite};
30
31use crate::lockdown::protocol::{recv_lockdown, send_lockdown, GetValueRequest};
32use crate::lockdown::LockdownError;
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(rename_all = "PascalCase")]
42pub struct FullPairRecord {
43 #[serde(with = "serde_bytes")]
44 pub device_certificate: Vec<u8>,
45 #[serde(with = "serde_bytes")]
46 pub host_certificate: Vec<u8>,
47 #[serde(with = "serde_bytes")]
48 pub host_private_key: Vec<u8>,
49 #[serde(with = "serde_bytes")]
50 pub root_certificate: Vec<u8>,
51 #[serde(with = "serde_bytes")]
52 pub root_private_key: Vec<u8>,
53 #[serde(rename = "HostID")]
54 pub host_id: String,
55 #[serde(rename = "SystemBUID")]
56 pub system_buid: String,
57}
58
59pub fn generate_pair_certs(
70 device_public_key_pem: &[u8],
71 system_buid: &str,
72) -> Result<FullPairRecord, LockdownError> {
73 let device_pkey = PKey::public_key_from_pem(device_public_key_pem)
75 .map_err(|e| LockdownError::Protocol(format!("failed to parse device public key: {e}")))?;
76
77 let root_rsa = Rsa::generate(2048)
79 .map_err(|e| LockdownError::Protocol(format!("RSA key generation failed: {e}")))?;
80 let root_pkey = PKey::from_rsa(root_rsa)
81 .map_err(|e| LockdownError::Protocol(format!("PKey from RSA failed: {e}")))?;
82
83 let host_rsa = Rsa::generate(2048)
85 .map_err(|e| LockdownError::Protocol(format!("RSA key generation failed: {e}")))?;
86 let host_pkey = PKey::from_rsa(host_rsa)
87 .map_err(|e| LockdownError::Protocol(format!("PKey from RSA failed: {e}")))?;
88
89 let root_cert = build_root_cert(&root_pkey)?;
91
92 let host_cert = build_signed_cert(&host_pkey, &root_cert, &root_pkey)?;
94
95 let device_cert = build_signed_cert(&device_pkey, &root_cert, &root_pkey)?;
97
98 let host_id = uuid::Uuid::new_v4().to_string().to_uppercase();
100
101 Ok(FullPairRecord {
102 device_certificate: device_cert
103 .to_pem()
104 .map_err(|e| LockdownError::Protocol(format!("cert to PEM failed: {e}")))?,
105 host_certificate: host_cert
106 .to_pem()
107 .map_err(|e| LockdownError::Protocol(format!("cert to PEM failed: {e}")))?,
108 host_private_key: host_pkey
109 .private_key_to_pem_pkcs8()
110 .map_err(|e| LockdownError::Protocol(format!("key to PEM failed: {e}")))?,
111 root_certificate: root_cert
112 .to_pem()
113 .map_err(|e| LockdownError::Protocol(format!("cert to PEM failed: {e}")))?,
114 root_private_key: root_pkey
115 .private_key_to_pem_pkcs8()
116 .map_err(|e| LockdownError::Protocol(format!("key to PEM failed: {e}")))?,
117 host_id,
118 system_buid: system_buid.to_string(),
119 })
120}
121
122fn build_root_cert(pkey: &PKey<Private>) -> Result<X509, LockdownError> {
130 let mut builder = X509Builder::new()
131 .map_err(|e| LockdownError::Protocol(format!("X509Builder::new failed: {e}")))?;
132
133 builder
135 .set_version(2)
136 .map_err(|e| LockdownError::Protocol(format!("set_version failed: {e}")))?;
137
138 let serial = BigNum::from_u32(0)
140 .and_then(|bn| bn.to_asn1_integer())
141 .map_err(|e| LockdownError::Protocol(format!("serial number failed: {e}")))?;
142 builder
143 .set_serial_number(&serial)
144 .map_err(|e| LockdownError::Protocol(format!("set_serial_number failed: {e}")))?;
145
146 let name = X509NameBuilder::new()
148 .map(|b| b.build())
149 .map_err(|e| LockdownError::Protocol(format!("X509Name build failed: {e}")))?;
150 builder
151 .set_subject_name(&name)
152 .map_err(|e| LockdownError::Protocol(format!("set_subject_name failed: {e}")))?;
153 builder
154 .set_issuer_name(&name)
155 .map_err(|e| LockdownError::Protocol(format!("set_issuer_name failed: {e}")))?;
156
157 let not_before = Asn1Time::days_from_now(0)
159 .map_err(|e| LockdownError::Protocol(format!("Asn1Time failed: {e}")))?;
160 let not_after = Asn1Time::days_from_now(3650)
161 .map_err(|e| LockdownError::Protocol(format!("Asn1Time failed: {e}")))?;
162 builder
163 .set_not_before(¬_before)
164 .map_err(|e| LockdownError::Protocol(format!("set_not_before failed: {e}")))?;
165 builder
166 .set_not_after(¬_after)
167 .map_err(|e| LockdownError::Protocol(format!("set_not_after failed: {e}")))?;
168
169 builder
170 .set_pubkey(pkey)
171 .map_err(|e| LockdownError::Protocol(format!("set_pubkey failed: {e}")))?;
172
173 let basic = BasicConstraints::new()
175 .critical()
176 .ca()
177 .build()
178 .map_err(|e| LockdownError::Protocol(format!("BasicConstraints build failed: {e}")))?;
179 builder
180 .append_extension(basic)
181 .map_err(|e| LockdownError::Protocol(format!("append_extension failed: {e}")))?;
182
183 let ski = SubjectKeyIdentifier::new()
186 .build(&builder.x509v3_context(None, None))
187 .map_err(|e| LockdownError::Protocol(format!("SKI build failed: {e}")))?;
188 builder
189 .append_extension(ski)
190 .map_err(|e| LockdownError::Protocol(format!("append SKI failed: {e}")))?;
191
192 builder
194 .sign(pkey, MessageDigest::sha1())
195 .map_err(|e| LockdownError::Protocol(format!("sign failed: {e}")))?;
196
197 Ok(builder.build())
198}
199
200fn build_signed_cert(
210 subject_pkey: &PKey<impl HasPublic>,
211 issuer_cert: &X509,
212 issuer_pkey: &PKey<Private>,
213) -> Result<X509, LockdownError> {
214 let mut builder = X509Builder::new()
215 .map_err(|e| LockdownError::Protocol(format!("X509Builder::new failed: {e}")))?;
216
217 builder
219 .set_version(2)
220 .map_err(|e| LockdownError::Protocol(format!("set_version failed: {e}")))?;
221
222 let serial = BigNum::from_u32(0)
224 .and_then(|bn| bn.to_asn1_integer())
225 .map_err(|e| LockdownError::Protocol(format!("serial number failed: {e}")))?;
226 builder
227 .set_serial_number(&serial)
228 .map_err(|e| LockdownError::Protocol(format!("set_serial_number failed: {e}")))?;
229
230 let name = X509NameBuilder::new()
232 .map(|b| b.build())
233 .map_err(|e| LockdownError::Protocol(format!("X509Name build failed: {e}")))?;
234 builder
235 .set_subject_name(&name)
236 .map_err(|e| LockdownError::Protocol(format!("set_subject_name failed: {e}")))?;
237
238 builder
240 .set_issuer_name(issuer_cert.subject_name())
241 .map_err(|e| LockdownError::Protocol(format!("set_issuer_name failed: {e}")))?;
242
243 let not_before = Asn1Time::days_from_now(0)
245 .map_err(|e| LockdownError::Protocol(format!("Asn1Time failed: {e}")))?;
246 let not_after = Asn1Time::days_from_now(3650)
247 .map_err(|e| LockdownError::Protocol(format!("Asn1Time failed: {e}")))?;
248 builder
249 .set_not_before(¬_before)
250 .map_err(|e| LockdownError::Protocol(format!("set_not_before failed: {e}")))?;
251 builder
252 .set_not_after(¬_after)
253 .map_err(|e| LockdownError::Protocol(format!("set_not_after failed: {e}")))?;
254
255 builder
256 .set_pubkey(subject_pkey)
257 .map_err(|e| LockdownError::Protocol(format!("set_pubkey failed: {e}")))?;
258
259 let basic = BasicConstraints::new()
261 .critical()
262 .build()
263 .map_err(|e| LockdownError::Protocol(format!("BasicConstraints build failed: {e}")))?;
264 builder
265 .append_extension(basic)
266 .map_err(|e| LockdownError::Protocol(format!("append_extension failed: {e}")))?;
267
268 let key_usage = KeyUsage::new()
270 .digital_signature()
271 .key_encipherment()
272 .build()
273 .map_err(|e| LockdownError::Protocol(format!("KeyUsage build failed: {e}")))?;
274 builder
275 .append_extension(key_usage)
276 .map_err(|e| LockdownError::Protocol(format!("append KeyUsage failed: {e}")))?;
277
278 let ski = SubjectKeyIdentifier::new()
280 .build(&builder.x509v3_context(Some(issuer_cert), None))
281 .map_err(|e| LockdownError::Protocol(format!("SKI build failed: {e}")))?;
282 builder
283 .append_extension(ski)
284 .map_err(|e| LockdownError::Protocol(format!("append SKI failed: {e}")))?;
285
286 builder
288 .sign(issuer_pkey, MessageDigest::sha1())
289 .map_err(|e| LockdownError::Protocol(format!("sign failed: {e}")))?;
290
291 Ok(builder.build())
292}
293
294#[derive(Serialize)]
299#[serde(rename_all = "PascalCase")]
300struct PairRecordPayload {
301 #[serde(with = "serde_bytes")]
302 device_certificate: Vec<u8>,
303 #[serde(with = "serde_bytes")]
304 host_certificate: Vec<u8>,
305 #[serde(with = "serde_bytes")]
306 root_certificate: Vec<u8>,
307 #[serde(rename = "HostID")]
308 host_id: String,
309 #[serde(rename = "SystemBUID")]
310 system_buid: String,
311}
312
313#[derive(Serialize)]
315#[serde(rename_all = "PascalCase")]
316struct PairingOptionsFirst {
317 extended_pairing_errors: bool,
318 #[serde(with = "serde_bytes")]
319 supervisor_certificate: Vec<u8>,
320}
321
322#[derive(Serialize)]
324#[serde(rename_all = "PascalCase")]
325struct PairingOptionsChallenge {
326 #[serde(with = "serde_bytes")]
327 challenge_response: Vec<u8>,
328}
329
330#[derive(Serialize)]
332#[serde(rename_all = "PascalCase")]
333struct PairRequestFirst {
334 label: &'static str,
335 protocol_version: &'static str,
336 request: &'static str,
337 pair_record: PairRecordPayload,
338 pairing_options: PairingOptionsFirst,
339}
340
341#[derive(Serialize)]
343#[serde(rename_all = "PascalCase")]
344struct PairRequestChallenge {
345 label: &'static str,
346 protocol_version: &'static str,
347 request: &'static str,
348 pair_record: PairRecordPayload,
349 pairing_options: PairingOptionsChallenge,
350}
351
352#[derive(Debug, Deserialize)]
354#[serde(rename_all = "PascalCase")]
355struct GetValueRawResponse {
356 #[serde(default)]
357 error: Option<String>,
358 value: Option<plist::Value>,
359}
360
361fn pair_record_payload(record: &FullPairRecord) -> PairRecordPayload {
363 PairRecordPayload {
364 device_certificate: record.device_certificate.clone(),
365 host_certificate: record.host_certificate.clone(),
366 root_certificate: record.root_certificate.clone(),
367 host_id: record.host_id.clone(),
368 system_buid: record.system_buid.clone(),
369 }
370}
371
372pub async fn pair_supervised<S>(
390 stream: &mut S,
391 p12_bytes: &[u8],
392 p12_password: &str,
393 system_buid: &str,
394) -> Result<(FullPairRecord, Vec<u8>), LockdownError>
395where
396 S: AsyncRead + AsyncWrite + Unpin,
397{
398 let pkcs12 = Pkcs12::from_der(p12_bytes)
400 .map_err(|e| LockdownError::Protocol(format!("P12 parse failed: {e}")))?;
401 let parsed = pkcs12
402 .parse2(p12_password)
403 .map_err(|e| LockdownError::Protocol(format!("P12 parse2 failed: {e}")))?;
404 let supervisor_cert = parsed
405 .cert
406 .ok_or_else(|| LockdownError::Protocol("P12 missing certificate".into()))?;
407 let supervisor_pkey = parsed
408 .pkey
409 .ok_or_else(|| LockdownError::Protocol("P12 missing private key".into()))?;
410
411 let device_public_key_pem = get_device_public_key(stream).await?;
413
414 let pair_record = generate_pair_certs(&device_public_key_pem, system_buid)?;
416
417 let supervisor_cert_der = supervisor_cert
419 .to_der()
420 .map_err(|e| LockdownError::Protocol(format!("supervisor cert to DER failed: {e}")))?;
421
422 let payload = pair_record_payload(&pair_record);
423 let first_request = PairRequestFirst {
424 label: "ios-rs",
425 protocol_version: "2",
426 request: "Pair",
427 pair_record: payload,
428 pairing_options: PairingOptionsFirst {
429 extended_pairing_errors: true,
430 supervisor_certificate: supervisor_cert_der,
431 },
432 };
433 send_lockdown(stream, &first_request).await?;
434
435 let challenge = recv_pairing_challenge(stream).await?;
437
438 let certs =
440 Stack::new().map_err(|e| LockdownError::Protocol(format!("Stack::new failed: {e}")))?;
441 let signed = Pkcs7::sign(
442 &supervisor_cert,
443 &supervisor_pkey,
444 &certs,
445 &challenge,
446 Pkcs7Flags::BINARY,
447 )
448 .and_then(|p7| p7.to_der())
449 .map_err(|e| LockdownError::Protocol(format!("PKCS7 sign failed: {e}")))?;
450
451 let payload2 = pair_record_payload(&pair_record);
453 let challenge_request = PairRequestChallenge {
454 label: "ios-rs",
455 protocol_version: "2",
456 request: "Pair",
457 pair_record: payload2,
458 pairing_options: PairingOptionsChallenge {
459 challenge_response: signed,
460 },
461 };
462 send_lockdown(stream, &challenge_request).await?;
463
464 let escrow_bag = recv_pair_success(stream).await?;
466
467 Ok((pair_record, escrow_bag))
468}
469
470async fn get_device_public_key<S>(stream: &mut S) -> Result<Vec<u8>, LockdownError>
472where
473 S: AsyncRead + AsyncWrite + Unpin,
474{
475 let request = GetValueRequest {
476 label: "ios-rs",
477 request: "GetValue",
478 domain: None,
479 key: Some("DevicePublicKey"),
480 };
481 send_lockdown(stream, &request).await?;
482
483 let resp: GetValueRawResponse = recv_lockdown(stream).await?;
484 if let Some(err) = resp.error {
485 return Err(LockdownError::Protocol(format!(
486 "GetValue DevicePublicKey failed: {err}"
487 )));
488 }
489
490 match resp.value {
491 Some(plist::Value::Data(data)) => Ok(data),
492 other => Err(LockdownError::Protocol(format!(
493 "DevicePublicKey: expected Data, got {other:?}"
494 ))),
495 }
496}
497
498async fn recv_pairing_challenge<S>(stream: &mut S) -> Result<Vec<u8>, LockdownError>
501where
502 S: AsyncRead + AsyncWrite + Unpin,
503{
504 let resp: plist::Value = recv_lockdown(stream).await?;
506 let dict = resp
507 .as_dictionary()
508 .ok_or_else(|| LockdownError::Protocol("Pair response is not a dictionary".into()))?;
509
510 let error = dict
512 .get("Error")
513 .and_then(plist::Value::as_string)
514 .ok_or_else(|| {
515 LockdownError::Protocol(format!("Pair response missing Error field: {dict:?}"))
516 })?;
517
518 if error != "MCChallengeRequired" {
519 return Err(LockdownError::Protocol(format!(
520 "expected MCChallengeRequired error, got: {error}"
521 )));
522 }
523
524 let extended = dict
526 .get("ExtendedResponse")
527 .and_then(plist::Value::as_dictionary)
528 .ok_or_else(|| LockdownError::Protocol("Pair response missing ExtendedResponse".into()))?;
529
530 let challenge = extended
531 .get("PairingChallenge")
532 .and_then(plist::Value::as_data)
533 .ok_or_else(|| {
534 LockdownError::Protocol("ExtendedResponse missing PairingChallenge".into())
535 })?;
536
537 Ok(challenge.to_vec())
538}
539
540async fn recv_pair_success<S>(stream: &mut S) -> Result<Vec<u8>, LockdownError>
542where
543 S: AsyncRead + AsyncWrite + Unpin,
544{
545 let resp: plist::Value = recv_lockdown(stream).await?;
546 let dict = resp
547 .as_dictionary()
548 .ok_or_else(|| LockdownError::Protocol("Pair response is not a dictionary".into()))?;
549
550 if let Some(error) = dict.get("Error").and_then(plist::Value::as_string) {
552 return Err(LockdownError::Protocol(format!("Pair failed: {error}")));
553 }
554
555 let escrow_bag = dict
557 .get("EscrowBag")
558 .and_then(plist::Value::as_data)
559 .ok_or_else(|| LockdownError::Protocol("Pair success response missing EscrowBag".into()))?;
560
561 Ok(escrow_bag.to_vec())
562}
563
564pub fn save_pair_record(
570 record: &FullPairRecord,
571 escrow_bag: &[u8],
572 wifi_mac: Option<&str>,
573 path: &std::path::Path,
574) -> Result<(), LockdownError> {
575 use plist::Value;
576
577 let mut dict = plist::Dictionary::new();
578 dict.insert(
579 "DeviceCertificate".into(),
580 Value::Data(record.device_certificate.clone()),
581 );
582 dict.insert(
583 "HostCertificate".into(),
584 Value::Data(record.host_certificate.clone()),
585 );
586 dict.insert(
587 "HostPrivateKey".into(),
588 Value::Data(record.host_private_key.clone()),
589 );
590 dict.insert(
591 "RootCertificate".into(),
592 Value::Data(record.root_certificate.clone()),
593 );
594 dict.insert(
595 "RootPrivateKey".into(),
596 Value::Data(record.root_private_key.clone()),
597 );
598 dict.insert("HostID".into(), Value::String(record.host_id.clone()));
599 dict.insert(
600 "SystemBUID".into(),
601 Value::String(record.system_buid.clone()),
602 );
603 dict.insert("EscrowBag".into(), Value::Data(escrow_bag.to_vec()));
604
605 if let Some(mac) = wifi_mac {
606 dict.insert("WiFiMACAddress".into(), Value::String(mac.to_string()));
607 }
608
609 if let Some(parent) = path.parent() {
611 if !parent.as_os_str().is_empty() {
612 std::fs::create_dir_all(parent).map_err(|e| {
613 LockdownError::Protocol(format!(
614 "failed to create pair record directory {}: {e}",
615 parent.display()
616 ))
617 })?;
618 }
619 }
620
621 let plist_value = Value::Dictionary(dict);
622 let mut buf = Vec::new();
623 plist::to_writer_xml(&mut buf, &plist_value)
624 .map_err(|e| LockdownError::Protocol(format!("plist serialization failed: {e}")))?;
625 std::fs::write(path, &buf).map_err(|e| {
626 LockdownError::Protocol(format!(
627 "failed to write pair record to {}: {e}",
628 path.display()
629 ))
630 })?;
631
632 Ok(())
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638
639 #[test]
640 fn test_generate_pair_certs_structure() {
641 let device_rsa = Rsa::generate(2048).unwrap();
643 let device_pkey = PKey::from_rsa(device_rsa).unwrap();
644 let device_pub_pem = device_pkey.public_key_to_pem().unwrap();
645
646 let record = generate_pair_certs(&device_pub_pem, "TEST-BUID").unwrap();
647
648 assert!(!record.device_certificate.is_empty());
650 assert!(!record.host_certificate.is_empty());
651 assert!(!record.host_private_key.is_empty());
652 assert!(!record.root_certificate.is_empty());
653 assert!(!record.root_private_key.is_empty());
654 assert_eq!(record.host_id.len(), 36); assert_eq!(record.system_buid, "TEST-BUID");
656
657 let _ = X509::from_pem(&record.device_certificate).unwrap();
659 let _ = X509::from_pem(&record.host_certificate).unwrap();
660 let _ = X509::from_pem(&record.root_certificate).unwrap();
661
662 let _ = PKey::private_key_from_pem(&record.host_private_key).unwrap();
664 let _ = PKey::private_key_from_pem(&record.root_private_key).unwrap();
665 }
666
667 #[test]
668 fn test_root_cert_is_ca() {
669 let rsa = Rsa::generate(2048).unwrap();
670 let pkey = PKey::from_rsa(rsa).unwrap();
671 let cert = build_root_cert(&pkey).unwrap();
672
673 let pem = cert.to_pem().unwrap();
675 let parsed = X509::from_pem(&pem).unwrap();
676 assert_eq!(
678 parsed.subject_name().entries().count(),
679 parsed.issuer_name().entries().count()
680 );
681 }
682
683 #[test]
684 fn test_signed_cert_not_ca() {
685 let root_rsa = Rsa::generate(2048).unwrap();
686 let root_pkey = PKey::from_rsa(root_rsa).unwrap();
687 let root_cert = build_root_cert(&root_pkey).unwrap();
688
689 let host_rsa = Rsa::generate(2048).unwrap();
690 let host_pkey = PKey::from_rsa(host_rsa).unwrap();
691 let host_cert = build_signed_cert(&host_pkey, &root_cert, &root_pkey).unwrap();
692
693 let pem = host_cert.to_pem().unwrap();
695 let parsed = X509::from_pem(&pem).unwrap();
696 assert_eq!(
698 parsed.issuer_name().entries().count(),
699 root_cert.subject_name().entries().count()
700 );
701 }
702
703 #[test]
704 fn test_pair_record_payload_strips_keys() {
705 let record = FullPairRecord {
706 device_certificate: b"dev-cert".to_vec(),
707 host_certificate: b"host-cert".to_vec(),
708 host_private_key: b"SECRET-HOST-KEY".to_vec(),
709 root_certificate: b"root-cert".to_vec(),
710 root_private_key: b"SECRET-ROOT-KEY".to_vec(),
711 host_id: "HOST-ID".to_string(),
712 system_buid: "BUID".to_string(),
713 };
714 let payload = pair_record_payload(&record);
715 assert_eq!(payload.device_certificate, b"dev-cert");
716 assert_eq!(payload.host_id, "HOST-ID");
717 }
719}