From 512b556a4542ee727219fa911c0473f2e5d73546 Mon Sep 17 00:00:00 2001 From: neo <164915254+neoarz@users.noreply.github.com> Date: Mon, 5 May 2025 17:35:35 -0400 Subject: [PATCH] Add PEM headers during pairing file serialization (#4) Fixes regression from rustls migration. * Update Cargo.toml * Update pairing_file.rs * base64 --- idevice/Cargo.toml | 3 +- idevice/src/pairing_file.rs | 114 +++++++++++++++++++++++++++++++----- 2 files changed, 99 insertions(+), 18 deletions(-) diff --git a/idevice/Cargo.toml b/idevice/Cargo.toml index c91d294..8073738 100644 --- a/idevice/Cargo.toml +++ b/idevice/Cargo.toml @@ -22,11 +22,11 @@ ns-keyed-archive = { version = "0.1.3", optional = true } thiserror = { version = "2" } log = { version = "0.4" } env_logger = { version = "0.11" } +base64 = { version = "0.22" } indexmap = { version = "2.7", features = ["serde"], optional = true } uuid = { version = "1.12", features = ["serde", "v4"], optional = true } async-recursion = { version = "1.1", optional = true } -base64 = { version = "0.22", optional = true } chrono = { version = "0.4.40", optional = true, default-features = false } serde_json = { version = "1", optional = true } @@ -76,7 +76,6 @@ xpc = [ "dep:indexmap", "dep:uuid", "dep:async-recursion", - "dep:base64", "dep:json", ] full = [ diff --git a/idevice/src/pairing_file.rs b/idevice/src/pairing_file.rs index 53903a4..77cc6ca 100644 --- a/idevice/src/pairing_file.rs +++ b/idevice/src/pairing_file.rs @@ -149,18 +149,28 @@ impl TryFrom for PairingFile { /// /// Performs validation of cryptographic materials during conversion. fn try_from(value: RawPairingFile) -> Result { + // Convert raw data into certificates and keys with proper PEM format + let device_cert_data = Into::>::into(value.device_certificate); + let host_private_key_data = Into::>::into(value.host_private_key); + let host_cert_data = Into::>::into(value.host_certificate); + let root_private_key_data = Into::>::into(value.root_private_key); + let root_cert_data = Into::>::into(value.root_certificate); + + // Ensure device certificate has proper PEM headers + let device_certificate_pem = ensure_pem_headers(&device_cert_data, "CERTIFICATE"); + + // Ensure host certificate has proper PEM headers + let host_certificate_pem = ensure_pem_headers(&host_cert_data, "CERTIFICATE"); + + // Ensure root certificate has proper PEM headers + let root_certificate_pem = ensure_pem_headers(&root_cert_data, "CERTIFICATE"); + Ok(Self { - device_certificate: CertificateDer::from_pem_slice(&Into::>::into( - value.device_certificate, - ))?, - host_private_key: Into::>::into(value.host_private_key), - host_certificate: CertificateDer::from_pem_slice(&Into::>::into( - value.host_certificate, - ))?, - root_private_key: Into::>::into(value.root_private_key), - root_certificate: CertificateDer::from_pem_slice(&Into::>::into( - value.root_certificate, - ))?, + device_certificate: CertificateDer::from_pem_slice(&device_certificate_pem)?, + host_private_key: host_private_key_data, + host_certificate: CertificateDer::from_pem_slice(&host_certificate_pem)?, + root_private_key: root_private_key_data, + root_certificate: CertificateDer::from_pem_slice(&root_certificate_pem)?, system_buid: value.system_buid, host_id: value.host_id, escrow_bag: value.escrow_bag.into(), @@ -173,12 +183,21 @@ impl TryFrom for PairingFile { impl From for RawPairingFile { /// Converts a structured pairing file into a raw pairing file for serialization fn from(value: PairingFile) -> Self { + // Ensure certificates include proper PEM format + let device_cert_data = ensure_pem_headers(&value.device_certificate.to_vec(), "CERTIFICATE"); + let host_cert_data = ensure_pem_headers(&value.host_certificate.to_vec(), "CERTIFICATE"); + let root_cert_data = ensure_pem_headers(&value.root_certificate.to_vec(), "CERTIFICATE"); + + // Ensure private keys include proper PEM format + let host_private_key_data = ensure_pem_headers(&value.host_private_key, "PRIVATE KEY"); + let root_private_key_data = ensure_pem_headers(&value.root_private_key, "PRIVATE KEY"); + Self { - device_certificate: Data::new(value.device_certificate.to_vec()), - host_private_key: Data::new(value.host_private_key), - host_certificate: Data::new(value.host_certificate.to_vec()), - root_private_key: Data::new(value.root_private_key), - root_certificate: Data::new(value.root_certificate.to_vec()), + device_certificate: Data::new(device_cert_data), + host_private_key: Data::new(host_private_key_data), + host_certificate: Data::new(host_cert_data), + root_private_key: Data::new(root_private_key_data), + root_certificate: Data::new(root_cert_data), system_buid: value.system_buid, host_id: value.host_id.clone(), escrow_bag: Data::new(value.escrow_bag), @@ -188,6 +207,69 @@ impl From for RawPairingFile { } } +/// Helper function to ensure data has proper PEM headers +/// If the data already has headers, it returns it as is +/// If not, it adds the appropriate BEGIN and END headers +fn ensure_pem_headers(data: &[u8], pem_type: &str) -> Vec { + if is_pem_formatted(data) { + return data.to_vec(); + } + + // If it's just base64 data, add PEM headers + let mut result = Vec::new(); + + // Add header + let header = format!("-----BEGIN {}-----\n", pem_type); + result.extend_from_slice(header.as_bytes()); + + // Add base64 content with line breaks every 64 characters + let base64_content = if is_base64(data) { + // Clean up any existing whitespace/newlines + let data_str = String::from_utf8_lossy(data); + data_str.replace('\n', "").replace('\r', "").replace(' ', "").into_bytes() + } else { + base64::encode(data).into_bytes() + }; + + // Format base64 content with proper line breaks (64 chars per line) + for (i, chunk) in base64_content.chunks(64).enumerate() { + if i > 0 { + result.push(b'\n'); + } + result.extend_from_slice(chunk); + } + + // Add a final newline before the footer + result.push(b'\n'); + + // Add footer + let footer = format!("-----END {}-----", pem_type); + result.extend_from_slice(footer.as_bytes()); + + result +} + +/// Check if data is already in PEM format +fn is_pem_formatted(data: &[u8]) -> bool { + if let Ok(data_str) = std::str::from_utf8(data) { + data_str.contains("-----BEGIN") && data_str.contains("-----END") + } else { + false + } +} + +/// Check if data is already base64 encoded +fn is_base64(data: &[u8]) -> bool { + if let Ok(data_str) = std::str::from_utf8(data) { + // Simple check to see if string contains only valid base64 characters + data_str.chars().all(|c| { + c.is_ascii_alphanumeric() || c == '+' || c == '/' || c == '=' || c.is_whitespace() + }) + } else { + false + } +} + #[test] fn test_pairing_file_roundtrip() { let f = std::fs::read("/var/lib/lockdown/test.plist").unwrap();