Cleanup merge leftovers

This commit is contained in:
nab138
2026-02-14 18:13:42 -05:00
parent 898de31275
commit c9ea34a0a1
6 changed files with 0 additions and 1906 deletions

View File

@@ -1,75 +0,0 @@
// This file was made using https://github.com/Dadoum/Sideloader as a reference.
use crate::Error;
use crate::bundle::Bundle;
use std::fs::File;
use std::path::PathBuf;
use zip::ZipArchive;
pub struct Application {
pub bundle: Bundle,
//pub temp_path: PathBuf,
}
impl Application {
pub fn new(path: PathBuf) -> Result<Self, Error> {
if !path.exists() {
return Err(Error::InvalidBundle(
"Application path does not exist".to_string(),
));
}
let mut bundle_path = path.clone();
//let mut temp_path = PathBuf::new();
if path.is_file() {
let temp_dir = std::env::temp_dir();
let temp_path = temp_dir
.join(path.file_name().unwrap().to_string_lossy().to_string() + "_extracted");
if temp_path.exists() {
std::fs::remove_dir_all(&temp_path).map_err(Error::Filesystem)?;
}
std::fs::create_dir_all(&temp_path).map_err(Error::Filesystem)?;
let file = File::open(&path).map_err(Error::Filesystem)?;
let mut archive = ZipArchive::new(file).map_err(|e| {
Error::Generic(format!("Failed to open application archive: {}", e))
})?;
archive.extract(&temp_path).map_err(|e| {
Error::Generic(format!("Failed to extract application archive: {}", e))
})?;
let payload_folder = temp_path.join("Payload");
if payload_folder.exists() && payload_folder.is_dir() {
let app_dirs: Vec<_> = std::fs::read_dir(&payload_folder)
.map_err(|e| {
Error::Generic(format!("Failed to read Payload directory: {}", e))
})?
.filter_map(Result::ok)
.filter(|entry| entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
.filter(|entry| entry.path().extension().is_some_and(|ext| ext == "app"))
.collect();
if app_dirs.len() == 1 {
bundle_path = app_dirs[0].path();
} else if app_dirs.is_empty() {
return Err(Error::InvalidBundle(
"No .app directory found in Payload".to_string(),
));
} else {
return Err(Error::InvalidBundle(
"Multiple .app directories found in Payload".to_string(),
));
}
} else {
return Err(Error::InvalidBundle(
"No Payload directory found in the application archive".to_string(),
));
}
}
let bundle = Bundle::new(bundle_path)?;
Ok(Application {
bundle, /*temp_path*/
})
}
}

View File

@@ -1,189 +0,0 @@
// This file was made using https://github.com/Dadoum/Sideloader as a reference.
use crate::Error;
use plist::{Dictionary, Value};
use std::{
fs,
path::{Path, PathBuf},
};
#[derive(Debug)]
pub struct Bundle {
pub app_info: Dictionary,
pub bundle_dir: PathBuf,
app_extensions: Vec<Bundle>,
frameworks: Vec<Bundle>,
_libraries: Vec<String>,
}
impl Bundle {
pub fn new(bundle_dir: PathBuf) -> Result<Self, Error> {
let mut bundle_path = bundle_dir;
// Remove trailing slash/backslash
if let Some(path_str) = bundle_path.to_str()
&& (path_str.ends_with('/') || path_str.ends_with('\\'))
{
bundle_path = PathBuf::from(&path_str[..path_str.len() - 1]);
}
let info_plist_path = bundle_path.join("Info.plist");
assert_bundle(
info_plist_path.exists(),
&format!("No Info.plist here: {}", info_plist_path.display()),
)?;
let plist_data = fs::read(&info_plist_path)
.map_err(|e| Error::InvalidBundle(format!("Failed to read Info.plist: {}", e)))?;
let app_info = plist::from_bytes(&plist_data)
.map_err(|e| Error::InvalidBundle(format!("Failed to parse Info.plist: {}", e)))?;
// Load app extensions from PlugIns directory
let plug_ins_dir = bundle_path.join("PlugIns");
let app_extensions = if plug_ins_dir.exists() {
fs::read_dir(&plug_ins_dir)
.map_err(|e| {
Error::InvalidBundle(format!("Failed to read PlugIns directory: {}", e))
})?
.filter_map(|entry| entry.ok())
.filter(|entry| {
entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false)
&& entry.path().join("Info.plist").exists()
})
.filter_map(|entry| Bundle::new(entry.path()).ok())
.collect()
} else {
Vec::new()
};
// Load frameworks from Frameworks directory
let frameworks_dir = bundle_path.join("Frameworks");
let frameworks = if frameworks_dir.exists() {
fs::read_dir(&frameworks_dir)
.map_err(|e| {
Error::InvalidBundle(format!("Failed to read Frameworks directory: {}", e))
})?
.filter_map(|entry| entry.ok())
.filter(|entry| {
entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false)
&& entry.path().join("Info.plist").exists()
})
.filter_map(|entry| Bundle::new(entry.path()).ok())
.collect()
} else {
Vec::new()
};
// Find all .dylib files in the bundle directory (recursive)
let libraries = find_dylibs(&bundle_path, &bundle_path)?;
Ok(Bundle {
app_info,
bundle_dir: bundle_path,
app_extensions,
frameworks,
_libraries: libraries,
})
}
pub fn set_bundle_identifier(&mut self, id: &str) {
self.app_info.insert(
"CFBundleIdentifier".to_string(),
Value::String(id.to_string()),
);
}
pub fn bundle_identifier(&self) -> Option<&str> {
self.app_info
.get("CFBundleIdentifier")
.and_then(|v| v.as_string())
}
pub fn bundle_name(&self) -> Option<&str> {
self.app_info
.get("CFBundleName")
.and_then(|v| v.as_string())
}
pub fn app_extensions(&self) -> &[Bundle] {
&self.app_extensions
}
pub fn app_extensions_mut(&mut self) -> &mut [Bundle] {
&mut self.app_extensions
}
pub fn frameworks(&self) -> &[Bundle] {
&self.frameworks
}
pub fn frameworks_mut(&mut self) -> &mut [Bundle] {
&mut self.frameworks
}
pub fn write_info(&self) -> Result<(), Error> {
let info_plist_path = self.bundle_dir.join("Info.plist");
let result = plist::to_file_binary(&info_plist_path, &self.app_info);
if let Err(e) = result {
return Err(Error::InvalidBundle(format!(
"Failed to write Info.plist: {}",
e
)));
}
Ok(())
}
}
fn assert_bundle(condition: bool, msg: &str) -> Result<(), Error> {
if !condition {
Err(Error::InvalidBundle(msg.to_string()))
} else {
Ok(())
}
}
fn find_dylibs(dir: &Path, bundle_root: &Path) -> Result<Vec<String>, Error> {
let mut libraries = Vec::new();
fn collect_dylibs(
dir: &Path,
bundle_root: &Path,
libraries: &mut Vec<String>,
) -> Result<(), Error> {
let entries = fs::read_dir(dir).map_err(|e| {
Error::InvalidBundle(format!("Failed to read directory {}: {}", dir.display(), e))
})?;
for entry in entries {
let entry = entry.map_err(|e| {
Error::InvalidBundle(format!("Failed to read directory entry: {}", e))
})?;
let path = entry.path();
let file_type = entry
.file_type()
.map_err(|e| Error::InvalidBundle(format!("Failed to get file type: {}", e)))?;
if file_type.is_file() {
if let Some(name) = path.file_name().and_then(|n| n.to_str())
&& name.ends_with(".dylib")
{
// Get relative path from bundle root
if let Ok(relative_path) = path.strip_prefix(bundle_root)
&& let Some(relative_str) = relative_path.to_str()
{
libraries.push(relative_str.to_string());
}
}
} else if file_type.is_dir() {
collect_dylibs(&path, bundle_root, libraries)?;
}
}
Ok(())
}
collect_dylibs(dir, bundle_root, &mut libraries)?;
Ok(libraries)
}

View File

@@ -1,273 +0,0 @@
// This file was made using https://github.com/Dadoum/Sideloader as a reference.
use hex;
use openssl::{
hash::MessageDigest,
pkcs12::Pkcs12,
pkey::{PKey, Private},
rsa::Rsa,
x509::{X509, X509Name, X509ReqBuilder},
};
use sha1::{Digest, Sha1};
use std::{
fs,
path::{Path, PathBuf},
};
use crate::Error;
use crate::developer_session::{DeveloperDeviceType, DeveloperSession, DeveloperTeam};
#[derive(Debug, Clone)]
pub struct CertificateIdentity {
pub certificate: Option<X509>,
pub private_key: PKey<Private>,
pub key_file: PathBuf,
pub cert_file: PathBuf,
pub machine_name: String,
pub machine_id: String,
}
impl CertificateIdentity {
pub async fn new(
configuration_path: &Path,
dev_session: &DeveloperSession,
apple_id: String,
machine_name: String,
) -> Result<Self, Error> {
let mut hasher = Sha1::new();
hasher.update(apple_id.as_bytes());
let hash_string = hex::encode(hasher.finalize()).to_lowercase();
let key_path = configuration_path.join("keys").join(hash_string);
fs::create_dir_all(&key_path).map_err(Error::Filesystem)?;
let key_file = key_path.join("key.pem");
let cert_file = key_path.join("cert.pem");
let teams = dev_session.list_teams().await?;
let team = teams
.first()
.ok_or(Error::Certificate("No teams found".to_string()))?;
let private_key = if key_file.exists() {
let key_data = fs::read_to_string(&key_file)
.map_err(|e| Error::Certificate(format!("Failed to read key file: {}", e)))?;
PKey::private_key_from_pem(key_data.as_bytes())
.map_err(|e| Error::Certificate(format!("Failed to load private key: {}", e)))?
} else {
let rsa = Rsa::generate(2048)
.map_err(|e| Error::Certificate(format!("Failed to generate RSA key: {}", e)))?;
let key = PKey::from_rsa(rsa)
.map_err(|e| Error::Certificate(format!("Failed to create private key: {}", e)))?;
let pem_data = key
.private_key_to_pem_pkcs8()
.map_err(|e| Error::Certificate(format!("Failed to encode private key: {}", e)))?;
fs::write(&key_file, pem_data).map_err(Error::Filesystem)?;
key
};
let mut cert_identity = CertificateIdentity {
certificate: None,
private_key,
key_file,
cert_file,
machine_name,
machine_id: "".to_owned(),
};
if let Ok((cert, machine_id)) = cert_identity
.find_matching_certificate(dev_session, team)
.await
{
cert_identity.certificate = Some(cert.clone());
cert_identity.machine_id = machine_id;
let cert_pem = cert.to_pem().map_err(|e| {
Error::Certificate(format!("Failed to encode certificate to PEM: {}", e))
})?;
fs::write(&cert_identity.cert_file, cert_pem).map_err(Error::Filesystem)?;
return Ok(cert_identity);
}
cert_identity
.request_new_certificate(dev_session, team)
.await?;
Ok(cert_identity)
}
async fn find_matching_certificate(
&self,
dev_session: &DeveloperSession,
team: &DeveloperTeam,
) -> Result<(X509, String), Error> {
let certificates = dev_session
.list_all_development_certs(DeveloperDeviceType::Ios, team)
.await
.map_err(|e| Error::Certificate(format!("Failed to list certificates: {:?}", e)))?;
let our_public_key = self
.private_key
.public_key_to_der()
.map_err(|e| Error::Certificate(format!("Failed to get public key: {}", e)))?;
for cert in certificates
.iter()
.filter(|c| c.machine_name == self.machine_name)
{
if let Ok(x509_cert) = X509::from_der(&cert.cert_content)
&& let Ok(cert_public_key) = x509_cert.public_key()
&& let Ok(cert_public_key_der) = cert_public_key.public_key_to_der()
&& cert_public_key_der == our_public_key
{
return Ok((x509_cert, cert.machine_id.clone()));
}
}
Err(Error::Certificate(
"No matching certificate found".to_string(),
))
}
async fn request_new_certificate(
&mut self,
dev_session: &DeveloperSession,
team: &DeveloperTeam,
) -> Result<(), Error> {
let mut req_builder = X509ReqBuilder::new()
.map_err(|e| Error::Certificate(format!("Failed to create request builder: {}", e)))?;
let mut name_builder = X509Name::builder()
.map_err(|e| Error::Certificate(format!("Failed to create name builder: {}", e)))?;
name_builder
.append_entry_by_text("C", "US")
.map_err(|e| Error::Certificate(format!("Failed to set country: {}", e)))?;
name_builder
.append_entry_by_text("ST", "STATE")
.map_err(|e| Error::Certificate(format!("Failed to set state: {}", e)))?;
name_builder
.append_entry_by_text("L", "LOCAL")
.map_err(|e| Error::Certificate(format!("Failed to set locality: {}", e)))?;
name_builder
.append_entry_by_text("O", "ORGNIZATION")
.map_err(|e| Error::Certificate(format!("Failed to set organization: {}", e)))?;
name_builder
.append_entry_by_text("CN", "CN")
.map_err(|e| Error::Certificate(format!("Failed to set common name: {}", e)))?;
req_builder
.set_subject_name(&name_builder.build())
.map_err(|e| Error::Certificate(format!("Failed to set subject name: {}", e)))?;
req_builder
.set_pubkey(&self.private_key)
.map_err(|e| Error::Certificate(format!("Failed to set public key: {}", e)))?;
req_builder
.sign(&self.private_key, MessageDigest::sha256())
.map_err(|e| Error::Certificate(format!("Failed to sign request: {}", e)))?;
let csr_pem = req_builder
.build()
.to_pem()
.map_err(|e| Error::Certificate(format!("Failed to encode CSR: {}", e)))?;
let certificate_id = dev_session
.submit_development_csr(
DeveloperDeviceType::Ios,
team,
String::from_utf8_lossy(&csr_pem).to_string(),
self.machine_name.clone(),
)
.await
.map_err(|e| {
let is_7460 = match &e {
Error::DeveloperSession(code, _) => *code == 7460,
_ => false,
};
if is_7460 {
Error::Certificate("You have too many certificates!".to_string())
} else {
Error::Certificate(format!("Failed to submit CSR: {:?}", e))
}
})?;
let certificates = dev_session
.list_all_development_certs(DeveloperDeviceType::Ios, team)
.await?;
let apple_cert = certificates
.iter()
.find(|cert| cert.certificate_id == certificate_id)
.ok_or(Error::Certificate(
"Certificate not found after submission".to_string(),
))?;
let certificate = X509::from_der(&apple_cert.cert_content)
.map_err(|e| Error::Certificate(format!("Failed to parse certificate: {}", e)))?;
// Write certificate to disk
let cert_pem = certificate.to_pem().map_err(|e| {
Error::Certificate(format!("Failed to encode certificate to PEM: {}", e))
})?;
fs::write(&self.cert_file, cert_pem).map_err(Error::Filesystem)?;
self.certificate = Some(certificate);
self.machine_id = apple_cert.machine_id.clone();
Ok(())
}
pub fn get_certificate_file_path(&self) -> &Path {
&self.cert_file
}
pub fn get_private_key_file_path(&self) -> &Path {
&self.key_file
}
pub fn get_serial_number(&self) -> Result<String, Error> {
let cert = match &self.certificate {
Some(c) => c,
None => {
return Err(Error::Certificate(
"No certificate available to get serial number".to_string(),
));
}
};
let num = cert
.serial_number()
.to_bn()
.map_err(|e| {
Error::Certificate(format!("Failed to convert serial number to bn: {}", e))
})?
.to_hex_str()
.map_err(|e| {
Error::Certificate(format!(
"Failed to convert serial number to hex string: {}",
e
))
})?
.to_string();
Ok(num.trim_start_matches("0").to_string())
}
pub fn to_pkcs12(&self, password: &str) -> Result<Vec<u8>, Error> {
let cert = match &self.certificate {
Some(c) => c,
None => {
return Err(Error::Certificate(
"No certificate available to create PKCS#12".to_string(),
));
}
};
let mut pkcs12_builder = Pkcs12::builder();
pkcs12_builder.pkey(&self.private_key);
pkcs12_builder.cert(cert);
pkcs12_builder.name("certificate");
let pkcs12 = pkcs12_builder
.build2(password)
.map_err(|e| Error::Certificate(format!("Failed to create PKCS#12 bundle: {}", e)))?;
let der_bytes = pkcs12
.to_der()
.map_err(|e| Error::Certificate(format!("Failed to encode PKCS#12 to DER: {}", e)))?;
Ok(der_bytes)
}
}

View File

@@ -1,805 +0,0 @@
// This file was made using https://github.com/Dadoum/Sideloader as a reference for the apple private endpoints
use crate::{Error, obf};
use icloud_auth::AppleAccount;
use idevice::pretty_print_dictionary;
use plist::{Date, Dictionary, Value};
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use uuid::Uuid;
use std::str::FromStr;
pub struct DeveloperSession {
pub account: Arc<AppleAccount>,
team: Option<DeveloperTeam>,
}
impl DeveloperSession {
pub fn new(account: Arc<AppleAccount>) -> Self {
DeveloperSession {
account,
team: None,
}
}
pub async fn send_developer_request(
&self,
url: &str,
body: Option<Dictionary>,
) -> Result<Dictionary, Error> {
let mut request = Dictionary::new();
request.insert(
"clientId".to_string(),
Value::String(obf!("XABBG36SBA").to_string()),
);
request.insert(
"protocolVersion".to_string(),
Value::String(obf!("QH65B2").to_string()),
);
request.insert(
"requestId".to_string(),
Value::String(Uuid::new_v4().to_string().to_uppercase()),
);
request.insert(
"userLocale".to_string(),
Value::Array(vec![Value::String("en_US".to_string())]),
);
if let Some(body) = body {
for (key, value) in body {
request.insert(key, value);
}
}
let response = self
.account
.send_request(url, Some(request))
.await
.map_err(Error::ICloudError)?;
let status_code = response
.get("resultCode")
.and_then(|v| v.as_unsigned_integer())
.unwrap_or(0);
if status_code != 0 {
let description = response
.get("userString")
.and_then(|v| v.as_string())
.or_else(|| response.get("resultString").and_then(|v| v.as_string()))
.unwrap_or("(null)");
return Err(Error::DeveloperSession(
status_code as i64,
description.to_string(),
));
}
Ok(response)
}
pub async fn list_teams(&self) -> Result<Vec<DeveloperTeam>, Error> {
let url = obf!(
"https://developerservices2.apple.com/services/QH65B2/listTeams.action?clientId=XABBG36SBA"
);
let response = self
.send_developer_request(url.to_string().as_str(), None)
.await?;
let teams = response
.get("teams")
.and_then(|v| v.as_array())
.ok_or(Error::Parse("teams".to_string()))?;
let mut result = Vec::new();
for team in teams {
let dict = team
.as_dictionary()
.ok_or(Error::Parse("team".to_string()))?;
let name = dict
.get("name")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("name".to_string()))?
.to_string();
let team_id = dict
.get("teamId")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("teamId".to_string()))?
.to_string();
result.push(DeveloperTeam {
_name: name,
team_id,
});
}
Ok(result)
}
pub async fn get_team(&self) -> Result<DeveloperTeam, Error> {
if let Some(team) = &self.team {
return Ok(team.clone());
}
let teams = self.list_teams().await?;
if teams.is_empty() {
return Err(Error::DeveloperSession(
-1,
"No developer teams found".to_string(),
));
}
// TODO: Handle multiple teams
Ok(teams[0].clone())
}
pub fn set_team(&mut self, team: DeveloperTeam) {
self.team = Some(team);
}
pub async fn list_devices(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
) -> Result<Vec<DeveloperDevice>, Error> {
let url = dev_url(device_type, obf!("listDevices"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
let response = self.send_developer_request(&url, Some(body)).await?;
let devices = response
.get("devices")
.and_then(|v| v.as_array())
.ok_or(Error::Parse("devices".to_string()))?;
let mut result = Vec::new();
for device in devices {
let dict = device
.as_dictionary()
.ok_or(Error::Parse("device".to_string()))?;
let device_id = dict
.get("deviceId")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("deviceId".to_string()))?
.to_string();
let name = dict
.get("name")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("name".to_string()))?
.to_string();
let device_number = dict
.get("deviceNumber")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("deviceNumber".to_string()))?
.to_string();
result.push(DeveloperDevice {
_device_id: device_id,
_name: name,
device_number,
});
}
Ok(result)
}
pub async fn add_device(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
device_name: &str,
udid: &str,
) -> Result<DeveloperDevice, Error> {
let url = dev_url(device_type, obf!("addDevice"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
body.insert("name".to_string(), Value::String(device_name.to_string()));
body.insert("deviceNumber".to_string(), Value::String(udid.to_string()));
let response = self.send_developer_request(&url, Some(body)).await?;
let device_dict = response
.get("device")
.and_then(|v| v.as_dictionary())
.ok_or(Error::Parse("device".to_string()))?;
let device_id = device_dict
.get("deviceId")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("deviceId".to_string()))?
.to_string();
let name = device_dict
.get("name")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("name".to_string()))?
.to_string();
let device_number = device_dict
.get("deviceNumber")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("deviceNumber".to_string()))?
.to_string();
Ok(DeveloperDevice {
_device_id: device_id,
_name: name,
device_number,
})
}
pub async fn list_all_development_certs(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
) -> Result<Vec<DevelopmentCertificate>, Error> {
let url = dev_url(device_type, obf!("listAllDevelopmentCerts"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
let response = self.send_developer_request(&url, Some(body)).await?;
let certs = response
.get("certificates")
.and_then(|v| v.as_array())
.ok_or(Error::Parse("certificates".to_string()))?;
let mut result = Vec::new();
for cert in certs {
let dict = cert
.as_dictionary()
.ok_or(Error::Parse("certificate".to_string()))?;
let name = dict
.get("name")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("name".to_string()))?
.to_string();
let certificate_id = dict
.get("certificateId")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("certificateId".to_string()))?
.to_string();
let serial_number = dict
.get("serialNumber")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("serialNumber".to_string()))?
.to_string();
let machine_name = match dict.get("machineName").and_then(|v| v.as_string()) {
Some(name) => name.to_string(),
None => "".to_string(),
};
let machine_id = match dict.get("machineId").and_then(|v| v.as_string()) {
Some(id) => Ok(id.to_string()),
None => Err(Error::Parse(format!(
"machineId {:?}",
pretty_print_dictionary(dict)
))),
}?;
let cert_content = dict
.get("certContent")
.and_then(|v| v.as_data())
.ok_or(Error::Parse("certContent".to_string()))?
.to_vec();
result.push(DevelopmentCertificate {
name,
certificate_id,
serial_number,
machine_name,
machine_id,
cert_content,
});
}
Ok(result)
}
pub async fn revoke_development_cert(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
serial_number: &str,
) -> Result<(), Error> {
let url = dev_url(device_type, obf!("revokeDevelopmentCert"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
body.insert(
"serialNumber".to_string(),
Value::String(serial_number.to_string()),
);
self.send_developer_request(&url, Some(body)).await?;
Ok(())
}
pub async fn submit_development_csr(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
csr_content: String,
machine_name: String,
) -> Result<String, Error> {
let url = dev_url(device_type, obf!("submitDevelopmentCSR"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
body.insert("csrContent".to_string(), Value::String(csr_content));
body.insert(
"machineId".to_string(),
Value::String(uuid::Uuid::new_v4().to_string().to_uppercase()),
);
body.insert("machineName".to_string(), Value::String(machine_name));
let response = self.send_developer_request(&url, Some(body)).await?;
let cert_dict = response
.get("certRequest")
.and_then(|v| v.as_dictionary())
.ok_or(Error::Parse("certRequest".to_string()))?;
let id = cert_dict
.get("certRequestId")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("certRequestId".to_string()))?
.to_string();
Ok(id)
}
pub async fn list_app_ids(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
) -> Result<ListAppIdsResponse, Error> {
let url = dev_url(device_type, obf!("listAppIds"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
let response = self.send_developer_request(&url, Some(body)).await?;
let app_ids = response
.get("appIds")
.and_then(|v| v.as_array())
.ok_or(Error::Parse("appIds".to_string()))?;
let mut result = Vec::new();
for app_id in app_ids {
let dict = app_id
.as_dictionary()
.ok_or(Error::Parse("appId".to_string()))?;
let name = dict
.get("name")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("name".to_string()))?
.to_string();
let app_id_id = dict
.get("appIdId")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("appIdId".to_string()))?
.to_string();
let identifier = dict
.get("identifier")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("identifier".to_string()))?
.to_string();
let features = dict
.get("features")
.and_then(|v| v.as_dictionary())
.ok_or(Error::Parse("features".to_string()))?;
let expiration_date = if dict.contains_key("expirationDate") {
Some(
dict.get("expirationDate")
.and_then(|v| v.as_date())
.ok_or(Error::Parse("expirationDate".to_string()))?,
)
} else {
None
};
result.push(AppId {
name,
app_id_id,
identifier,
features: features.clone(),
expiration_date,
});
}
let max_quantity = if response.contains_key("maxQuantity") {
Some(
response
.get("maxQuantity")
.and_then(|v| v.as_unsigned_integer())
.ok_or(Error::Parse("maxQuantity".to_string()))?,
)
} else {
None
};
let available_quantity = if response.contains_key("availableQuantity") {
Some(
response
.get("availableQuantity")
.and_then(|v| v.as_unsigned_integer())
.ok_or(Error::Parse("availableQuantity".to_string()))?,
)
} else {
None
};
Ok(ListAppIdsResponse {
app_ids: result,
max_quantity,
available_quantity,
})
}
pub async fn add_app_id(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
name: &str,
identifier: &str,
) -> Result<(), Error> {
let url = dev_url(device_type, obf!("addAppId"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
body.insert("name".to_string(), Value::String(name.to_string()));
body.insert(
"identifier".to_string(),
Value::String(identifier.to_string()),
);
self.send_developer_request(&url, Some(body)).await?;
Ok(())
}
pub async fn update_app_id(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
app_id: &AppId,
features: &Dictionary,
) -> Result<Dictionary, Error> {
let url = dev_url(device_type, obf!("updateAppId"));
let mut body = Dictionary::new();
body.insert(
"appIdId".to_string(),
Value::String(app_id.app_id_id.clone()),
);
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
for (key, value) in features {
body.insert(key.clone(), value.clone());
}
let response = self.send_developer_request(&url, Some(body)).await?;
let cert_dict = response
.get("appId")
.and_then(|v| v.as_dictionary())
.ok_or(Error::Parse("appId".to_string()))?;
let feats = cert_dict
.get("features")
.and_then(|v| v.as_dictionary())
.ok_or(Error::Parse("features".to_string()))?;
Ok(feats.clone())
}
pub async fn delete_app_id(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
app_id_id: String,
) -> Result<(), Error> {
let url = dev_url(device_type, obf!("deleteAppId"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
body.insert("appIdId".to_string(), Value::String(app_id_id.clone()));
self.send_developer_request(&url, Some(body)).await?;
Ok(())
}
pub async fn list_application_groups(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
) -> Result<Vec<ApplicationGroup>, Error> {
let url = dev_url(device_type, obf!("listApplicationGroups"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
let response = self.send_developer_request(&url, Some(body)).await?;
let app_groups = response
.get("applicationGroupList")
.and_then(|v| v.as_array())
.ok_or(Error::Parse("applicationGroupList".to_string()))?;
let mut result = Vec::new();
for app_group in app_groups {
let dict = app_group
.as_dictionary()
.ok_or(Error::Parse("applicationGroup".to_string()))?;
let application_group = dict
.get("applicationGroup")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("applicationGroup".to_string()))?
.to_string();
let name = dict
.get("name")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("name".to_string()))?
.to_string();
let identifier = dict
.get("identifier")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("identifier".to_string()))?
.to_string();
result.push(ApplicationGroup {
application_group,
_name: name,
identifier,
});
}
Ok(result)
}
pub async fn add_application_group(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
group_identifier: &str,
name: &str,
) -> Result<ApplicationGroup, Error> {
let url = dev_url(device_type, obf!("addApplicationGroup"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
body.insert("name".to_string(), Value::String(name.to_string()));
body.insert(
"identifier".to_string(),
Value::String(group_identifier.to_string()),
);
let response = self.send_developer_request(&url, Some(body)).await?;
let app_group_dict = response
.get("applicationGroup")
.and_then(|v| v.as_dictionary())
.ok_or(Error::Parse("applicationGroup".to_string()))?;
let application_group = app_group_dict
.get("applicationGroup")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("applicationGroup".to_string()))?
.to_string();
let name = app_group_dict
.get("name")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("name".to_string()))?
.to_string();
let identifier = app_group_dict
.get("identifier")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("identifier".to_string()))?
.to_string();
Ok(ApplicationGroup {
application_group,
_name: name,
identifier,
})
}
pub async fn assign_application_group_to_app_id(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
app_id: &AppId,
app_group: &ApplicationGroup,
) -> Result<(), Error> {
let url = dev_url(device_type, obf!("assignApplicationGroupToAppId"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
body.insert(
"appIdId".to_string(),
Value::String(app_id.app_id_id.clone()),
);
body.insert(
"applicationGroups".to_string(),
Value::String(app_group.application_group.clone()),
);
self.send_developer_request(&url, Some(body)).await?;
Ok(())
}
pub async fn download_team_provisioning_profile(
&self,
device_type: DeveloperDeviceType,
team: &DeveloperTeam,
app_id: &AppId,
) -> Result<ProvisioningProfile, Error> {
let url = dev_url(device_type, obf!("downloadTeamProvisioningProfile"));
let mut body = Dictionary::new();
body.insert("teamId".to_string(), Value::String(team.team_id.clone()));
body.insert(
"appIdId".to_string(),
Value::String(app_id.app_id_id.clone()),
);
let response = self.send_developer_request(&url, Some(body)).await?;
let profile = response
.get("provisioningProfile")
.and_then(|v| v.as_dictionary())
.ok_or(Error::Parse("provisioningProfile".to_string()))?;
let name = profile
.get("name")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("name".to_string()))?
.to_string();
let provisioning_profile_id = profile
.get("provisioningProfileId")
.and_then(|v| v.as_string())
.ok_or(Error::Parse("provisioningProfileId".to_string()))?
.to_string();
let encoded_profile = profile
.get("encodedProfile")
.and_then(|v| v.as_data())
.ok_or(Error::Parse("encodedProfile".to_string()))?
.to_vec();
Ok(ProvisioningProfile {
_name: name,
_provisioning_profile_id: provisioning_profile_id,
encoded_profile,
})
}
// This is jank and I will do it better in the rewrite
pub async fn add_increased_memory_limit(
&self,
team: &DeveloperTeam,
app_id: &AppId,
) -> Result<(), Error> {
let spd = self.account.spd.as_ref().unwrap();
let app_token = self
.account
.get_app_token("com.apple.gs.xcode.auth")
.await?;
let valid_anisette = self.account.get_anisette().await;
let mut headers = HeaderMap::new();
headers.insert(
"Content-Type",
HeaderValue::from_static("application/vnd.api+json"),
);
headers.insert(
"Accept",
HeaderValue::from_static("application/vnd.api+json"),
);
headers.insert("Accept-Language", HeaderValue::from_static("en-us"));
headers.insert("User-Agent", HeaderValue::from_str("Xcode").unwrap());
headers.insert(
HeaderName::from_str("X-Apple-I-Identity-Id").unwrap(),
HeaderValue::from_str(spd.get("adsid").unwrap().as_string().unwrap()).unwrap(),
);
headers.insert(
HeaderName::from_str("X-Apple-GS-Token").unwrap(),
HeaderValue::from_str(&app_token.auth_token).unwrap(),
);
for (k, v) in valid_anisette.generate_headers(false, true, true) {
headers.insert(
HeaderName::from_bytes(k.as_bytes()).unwrap(),
HeaderValue::from_str(&v).unwrap(),
);
}
if let Ok(locale) = valid_anisette.get_header("x-apple-locale") {
headers.insert(
HeaderName::from_str("X-Apple-Locale").unwrap(),
HeaderValue::from_str(&locale).unwrap(),
);
}
let response = self
.account
.client
.patch(format!(
"https://developerservices2.apple.com/services/v1/bundleIds/{}",
app_id.app_id_id
))
.headers(headers)
.body(format!(
"{{\"data\":{{\"relationships\":{{\"bundleIdCapabilities\":{{\"data\":[{{\"relationships\":{{\"capability\":{{\"data\":{{\"id\":\"INCREASED_MEMORY_LIMIT\",\"type\":\"capabilities\"}}}}}},\"type\":\"bundleIdCapabilities\",\"attributes\":{{\"settings\":[],\"enabled\":true}}}}]}}}},\"id\":\"{}\",\"attributes\":{{\"hasExclusiveManagedCapabilities\":false,\"teamId\":\"{}\",\"bundleType\":\"bundle\",\"identifier\":\"{}\",\"seedId\":\"{}\",\"name\":\"{}\"}},\"type\":\"bundleIds\"}}}}",
app_id.app_id_id, team.team_id, app_id.identifier, team.team_id, app_id.name
))
.send()
.await
.map_err(|e| Error::ICloudError(icloud_auth::Error::ReqwestError(e)))?;
let _response = response
.text()
.await
.map_err(|e| Error::ICloudError(icloud_auth::Error::ReqwestError(e)))?;
//println!("Response: {:?}", response);
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum DeveloperDeviceType {
Any,
Ios,
Tvos,
Watchos,
}
impl DeveloperDeviceType {
pub fn url_segment(&self) -> &'static str {
match self {
DeveloperDeviceType::Any => "",
DeveloperDeviceType::Ios => "ios/",
DeveloperDeviceType::Tvos => "tvos/",
DeveloperDeviceType::Watchos => "watchos/",
}
}
}
fn dev_url(device_type: DeveloperDeviceType, endpoint: &str) -> String {
format!(
"{}{}{}{}",
obf!("https://developerservices2.apple.com/services/QH65B2/"),
device_type.url_segment(),
endpoint,
obf!(".action?clientId=XABBG36SBA")
)
}
#[derive(Debug, Clone)]
pub struct DeveloperDevice {
pub _device_id: String,
pub _name: String,
pub device_number: String,
}
#[derive(Debug, Clone)]
pub struct DeveloperTeam {
pub _name: String,
pub team_id: String,
}
#[derive(Debug, Clone)]
pub struct DevelopmentCertificate {
pub name: String,
pub certificate_id: String,
pub serial_number: String,
pub machine_name: String,
pub machine_id: String,
pub cert_content: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppId {
pub app_id_id: String,
pub identifier: String,
pub name: String,
pub features: Dictionary,
pub expiration_date: Option<Date>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListAppIdsResponse {
pub app_ids: Vec<AppId>,
pub max_quantity: Option<u64>,
pub available_quantity: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct ApplicationGroup {
pub application_group: String,
pub _name: String,
pub identifier: String,
}
#[derive(Debug, Clone)]
pub struct ProvisioningProfile {
pub _provisioning_profile_id: String,
pub _name: String,
pub encoded_profile: Vec<u8>,
}

View File

@@ -1,91 +0,0 @@
use idevice::{
IdeviceService, afc::AfcClient, installation_proxy::InstallationProxyClient,
provider::IdeviceProvider,
};
use std::pin::Pin;
use std::{future::Future, path::Path};
use crate::Error;
/// Installs an ***already signed*** app onto your device.
/// To sign and install an app, see [`crate::sideload::sideload_app`]
pub async fn install_app(
provider: &impl IdeviceProvider,
app_path: &Path,
progress_callback: impl Fn(u64),
) -> Result<(), Error> {
let mut afc_client = AfcClient::connect(provider)
.await
.map_err(Error::IdeviceError)?;
let dir = format!(
"PublicStaging/{}",
app_path.file_name().unwrap().to_string_lossy()
);
afc_upload_dir(&mut afc_client, app_path, &dir).await?;
let mut instproxy_client = InstallationProxyClient::connect(provider)
.await
.map_err(Error::IdeviceError)?;
let mut options = plist::Dictionary::new();
options.insert("PackageType".to_string(), "Developer".into());
instproxy_client
.install_with_callback(
dir,
Some(plist::Value::Dictionary(options)),
async |(percentage, _)| {
progress_callback(percentage);
},
(),
)
.await
.map_err(Error::IdeviceError)?;
Ok(())
}
fn afc_upload_dir<'a>(
afc_client: &'a mut AfcClient,
path: &'a Path,
afc_path: &'a str,
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'a>> {
Box::pin(async move {
let entries = std::fs::read_dir(path).map_err(Error::Filesystem)?;
afc_client
.mk_dir(afc_path)
.await
.map_err(Error::IdeviceError)?;
for entry in entries {
let entry = entry.map_err(Error::Filesystem)?;
let path = entry.path();
if path.is_dir() {
let new_afc_path = format!(
"{}/{}",
afc_path,
path.file_name().unwrap().to_string_lossy()
);
afc_upload_dir(afc_client, &path, &new_afc_path).await?;
} else {
let mut file_handle = afc_client
.open(
format!(
"{}/{}",
afc_path,
path.file_name().unwrap().to_string_lossy()
),
idevice::afc::opcode::AfcFopenMode::WrOnly,
)
.await
.map_err(Error::IdeviceError)?;
let bytes = std::fs::read(&path).map_err(Error::Filesystem)?;
file_handle
.write_entire(&bytes)
.await
.map_err(Error::IdeviceError)?;
file_handle.close().await.map_err(Error::IdeviceError)?;
}
}
Ok(())
})
}

View File

@@ -1,473 +0,0 @@
// This file was made using https://github.com/Dadoum/Sideloader as a reference.
use idevice::IdeviceService;
use idevice::lockdown::LockdownClient;
use idevice::provider::IdeviceProvider;
use zsign_rust::ZSignOptions;
use crate::application::Application;
use crate::device::install_app;
use crate::{DeveloperTeam, Error, SideloadConfiguration, SideloadLogger};
use crate::{
certificate::CertificateIdentity,
developer_session::{DeveloperDeviceType, DeveloperSession},
};
use std::{io::Write, path::PathBuf};
fn error_and_return(logger: &dyn SideloadLogger, error: Error) -> Result<(), Error> {
logger.error(&error);
Err(error)
}
/// Signs and installs an `.ipa` or `.app` onto a device.
///
/// # Arguments
/// - `device_provider` - [`idevice::provider::IdeviceProvider`] for the device
/// - `dev_session` - Authenticated Apple developer session ([`crate::developer_session::DeveloperSession`]).
/// - `app_path` - Path to the `.ipa` file or `.app` bundle to sign and install
/// - `config` - Sideload configuration options ([`crate::SideloadConfiguration`])
pub async fn sideload_app(
device_provider: &impl IdeviceProvider,
dev_session: &DeveloperSession,
app_path: PathBuf,
config: SideloadConfiguration<'_>,
) -> Result<(), Error> {
let logger = config.logger;
let mut lockdown_client = match LockdownClient::connect(device_provider).await {
Ok(l) => l,
Err(e) => {
return error_and_return(logger, Error::IdeviceError(e));
}
};
if let Ok(pairing_file) = device_provider.get_pairing_file().await {
lockdown_client
.start_session(&pairing_file)
.await
.map_err(Error::IdeviceError)?;
}
let device_name = lockdown_client
.get_value(Some("DeviceName"), None)
.await
.map_err(Error::IdeviceError)?
.as_string()
.ok_or(Error::Generic(
"Failed to convert DeviceName to string".to_string(),
))?
.to_string();
let device_uuid = lockdown_client
.get_value(Some("UniqueDeviceID"), None)
.await
.map_err(Error::IdeviceError)?
.as_string()
.ok_or(Error::Generic(
"Failed to convert UniqueDeviceID to string".to_string(),
))?
.to_string();
let team = match dev_session.get_team().await {
Ok(t) => t,
Err(e) => {
return error_and_return(logger, e);
}
};
logger.log("Successfully retrieved team");
ensure_device_registered(logger, dev_session, &team, &device_uuid, &device_name).await?;
let cert = match CertificateIdentity::new(
&config.store_dir,
dev_session,
dev_session.account.apple_id.clone(),
config.machine_name,
)
.await
{
Ok(c) => c,
Err(e) => {
return error_and_return(logger, e);
}
};
logger.log("Successfully acquired certificate");
let mut list_app_id_response = match dev_session
.list_app_ids(DeveloperDeviceType::Ios, &team)
.await
{
Ok(ids) => ids,
Err(e) => {
return error_and_return(logger, e);
}
};
let mut app = Application::new(app_path)?;
let is_sidestore = app.bundle.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore";
let is_lc_and_sidestore = app
.bundle
.frameworks()
.iter()
.any(|f| f.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore");
let main_app_bundle_id = match app.bundle.bundle_identifier() {
Some(id) => id.to_string(),
None => {
return error_and_return(
logger,
Error::InvalidBundle("No bundle identifier found in IPA".to_string()),
);
}
};
let main_app_id_str = format!("{}.{}", main_app_bundle_id, team.team_id);
let main_app_name = match app.bundle.bundle_name() {
Some(name) => name.to_string(),
None => {
return error_and_return(
logger,
Error::InvalidBundle("No bundle name found in IPA".to_string()),
);
}
};
let extensions = app.bundle.app_extensions_mut();
// for each extension, ensure it has a unique bundle identifier that starts with the main app's bundle identifier
for ext in extensions.iter_mut() {
if let Some(id) = ext.bundle_identifier() {
if !(id.starts_with(&main_app_bundle_id) && id.len() > main_app_bundle_id.len()) {
return error_and_return(
logger,
Error::InvalidBundle(format!(
"Extension {} is not part of the main app bundle identifier: {}",
ext.bundle_name().unwrap_or("Unknown"),
id
)),
);
} else {
ext.set_bundle_identifier(&format!(
"{}{}",
main_app_id_str,
&id[main_app_bundle_id.len()..]
));
}
}
}
app.bundle.set_bundle_identifier(&main_app_id_str);
let extension_refs: Vec<_> = app.bundle.app_extensions().iter().collect();
let mut bundles_with_app_id = vec![&app.bundle];
bundles_with_app_id.extend(extension_refs);
let app_ids_to_register = bundles_with_app_id
.iter()
.filter(|bundle| {
let bundle_id = bundle.bundle_identifier().unwrap_or("");
!list_app_id_response
.app_ids
.iter()
.any(|app_id| app_id.identifier == bundle_id)
})
.collect::<Vec<_>>();
if let Some(available) = list_app_id_response.available_quantity
&& app_ids_to_register.len() > available.try_into().unwrap()
{
return error_and_return(
logger,
Error::InvalidBundle(format!(
"This app requires {} app ids, but you only have {} available",
app_ids_to_register.len(),
available
)),
);
}
for bundle in app_ids_to_register {
let id = bundle.bundle_identifier().unwrap_or("");
let name = bundle.bundle_name().unwrap_or("");
if let Err(e) = dev_session
.add_app_id(DeveloperDeviceType::Ios, &team, name, id)
.await
{
return error_and_return(logger, e);
}
}
list_app_id_response = match dev_session
.list_app_ids(DeveloperDeviceType::Ios, &team)
.await
{
Ok(ids) => ids,
Err(e) => {
return error_and_return(logger, e);
}
};
let mut app_ids: Vec<_> = list_app_id_response
.app_ids
.into_iter()
.filter(|app_id| {
bundles_with_app_id
.iter()
.any(|bundle| app_id.identifier == bundle.bundle_identifier().unwrap_or(""))
})
.collect();
let main_app_id = match app_ids
.iter()
.find(|app_id| app_id.identifier == main_app_id_str)
.cloned()
{
Some(id) => id,
None => {
return error_and_return(
logger,
Error::Generic(format!(
"Main app ID {} not found in registered app IDs",
main_app_id_str
)),
);
}
};
logger.log("Successfully registered app IDs");
for app_id in app_ids.iter_mut() {
let app_group_feature_enabled = app_id
.features
.get(
"APG3427HIY", /* Gotta love apple and their magic strings! */
)
.and_then(|v| v.as_boolean())
.ok_or(Error::Generic(
"App group feature not found in app id".to_string(),
))?;
if !app_group_feature_enabled {
let mut body = plist::Dictionary::new();
body.insert("APG3427HIY".to_string(), plist::Value::Boolean(true));
let new_features = match dev_session
.update_app_id(DeveloperDeviceType::Ios, &team, app_id, &body)
.await
{
Ok(new_feats) => new_feats,
Err(e) => {
return error_and_return(logger, e);
}
};
app_id.features = new_features;
}
if config.add_increased_memory_limit {
dev_session
.add_increased_memory_limit(&team, app_id)
.await?;
}
}
let group_identifier = format!(
"group.{}",
if is_lc_and_sidestore {
format!("com.SideStore.SideStore.{}", team.team_id)
} else {
main_app_id_str.clone()
}
);
if is_sidestore || is_lc_and_sidestore {
app.bundle.app_info.insert(
"ALTAppGroups".to_string(),
plist::Value::Array(vec![plist::Value::String(group_identifier.clone())]),
);
let target_bundle = if is_lc_and_sidestore {
app.bundle
.frameworks_mut()
.iter_mut()
.find(|fw| fw.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore")
} else {
Some(&mut app.bundle)
};
if let Some(target_bundle) = target_bundle {
target_bundle.app_info.insert(
"ALTCertificateID".to_string(),
plist::Value::String(cert.get_serial_number().unwrap()),
);
match cert.to_pkcs12(&cert.machine_id) {
Ok(p12_bytes) => {
let alt_cert_path = target_bundle.bundle_dir.join("ALTCertificate.p12");
let mut file =
std::fs::File::create(&alt_cert_path).map_err(Error::Filesystem)?;
file.write_all(&p12_bytes).map_err(Error::Filesystem)?;
}
Err(e) => return error_and_return(logger, e),
}
}
}
let app_groups = match dev_session
.list_application_groups(DeveloperDeviceType::Ios, &team)
.await
{
Ok(groups) => groups,
Err(e) => {
return error_and_return(logger, e);
}
};
let matching_app_groups = app_groups
.iter()
.filter(|group| group.identifier == group_identifier.clone())
.collect::<Vec<_>>();
let app_group = if matching_app_groups.is_empty() {
match dev_session
.add_application_group(
DeveloperDeviceType::Ios,
&team,
&group_identifier,
&main_app_name,
)
.await
{
Ok(group) => group,
Err(e) => {
return error_and_return(logger, e);
}
}
} else {
matching_app_groups[0].clone()
};
//let mut provisioning_profiles: HashMap<String, ProvisioningProfile> = HashMap::new();
for app_id in app_ids {
let assign_res = dev_session
.assign_application_group_to_app_id(
DeveloperDeviceType::Ios,
&team,
&app_id,
&app_group,
)
.await;
if assign_res.is_err() {
return error_and_return(logger, assign_res.err().unwrap());
}
// let provisioning_profile = match account
// // This doesn't seem right to me, but it's what Sideloader does... Shouldn't it be downloading the provisioning profile for this app ID, not the main?
// .download_team_provisioning_profile(DeveloperDeviceType::Ios, &team, &main_app_id)
// .await
// {
// Ok(pp /* tee hee */) => pp,
// Err(e) => {
// return emit_error_and_return(
// &window,
// &format!("Failed to download provisioning profile: {:?}", e),
// );
// }
// };
// provisioning_profiles.insert(app_id.identifier.clone(), provisioning_profile);
}
logger.log("Successfully registered app groups");
let provisioning_profile = match dev_session
.download_team_provisioning_profile(DeveloperDeviceType::Ios, &team, &main_app_id)
.await
{
Ok(pp /* tee hee */) => pp,
Err(e) => {
return error_and_return(logger, e);
}
};
let profile_path = config
.store_dir
.join(format!("{}.mobileprovision", main_app_id_str));
if profile_path.exists() {
std::fs::remove_file(&profile_path).map_err(Error::Filesystem)?;
}
let mut file = std::fs::File::create(&profile_path).map_err(Error::Filesystem)?;
file.write_all(&provisioning_profile.encoded_profile)
.map_err(Error::Filesystem)?;
// Without this, zsign complains it can't find the provision file
#[cfg(target_os = "windows")]
{
file.sync_all().map_err(|e| Error::Filesystem(e))?;
drop(file);
}
app.bundle.write_info()?;
for ext in app.bundle.app_extensions_mut() {
ext.write_info()?;
}
for ext in app.bundle.frameworks_mut() {
ext.write_info()?;
}
match ZSignOptions::new(app.bundle.bundle_dir.to_str().unwrap())
.with_cert_file(cert.get_certificate_file_path().to_str().unwrap())
.with_pkey_file(cert.get_private_key_file_path().to_str().unwrap())
.with_prov_file(profile_path.to_str().unwrap())
.with_force()
.with_disable_cache()
.sign()
{
Ok(_) => {}
Err(e) => {
return error_and_return(logger, Error::ZSignError(e));
}
};
logger.log("App signed!");
logger.log("Installing app (Transfer)... 0%");
let res = install_app(device_provider, &app.bundle.bundle_dir, |percentage| {
logger.log(&format!("Installing app... {}%", percentage));
})
.await;
if let Err(e) = res {
return error_and_return(logger, e);
}
if config.revoke_cert {
dev_session
.revoke_development_cert(DeveloperDeviceType::Ios, &team, &cert.get_serial_number()?)
.await?;
logger.log("Certificate revoked");
}
Ok(())
}
pub async fn ensure_device_registered(
logger: &dyn SideloadLogger,
dev_session: &DeveloperSession,
team: &DeveloperTeam,
uuid: &str,
name: &str,
) -> Result<(), Error> {
let devices = dev_session
.list_devices(DeveloperDeviceType::Ios, team)
.await;
if let Err(e) = devices {
return error_and_return(logger, e);
}
let devices = devices.unwrap();
if !devices.iter().any(|d| d.device_number == uuid) {
logger.log("Device not found in your account");
// TODO: Actually test!
dev_session
.add_device(DeveloperDeviceType::Ios, team, name, uuid)
.await?;
logger.log("Successfully added device to your account");
}
logger.log("Device is a development device");
Ok(())
}