im questioning everything I thought I knew about this stuff

This commit is contained in:
nab138
2025-11-23 11:03:52 -05:00
parent 92085d878b
commit cdedc84e8b
8 changed files with 258 additions and 134 deletions

32
Cargo.lock generated
View File

@@ -155,7 +155,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.17", "thiserror 2.0.17",
"x509-certificate 0.24.0", "x509-certificate",
] ]
[[package]] [[package]]
@@ -246,7 +246,7 @@ dependencies = [
"widestring", "widestring",
"windows-sys 0.59.0", "windows-sys 0.59.0",
"x509", "x509",
"x509-certificate 0.24.0", "x509-certificate",
"xml-rs", "xml-rs",
"yasna", "yasna",
"zeroize", "zeroize",
@@ -294,7 +294,7 @@ dependencies = [
"signature 2.2.0", "signature 2.2.0",
"thiserror 2.0.17", "thiserror 2.0.17",
"url", "url",
"x509-certificate 0.24.0", "x509-certificate",
"xml-rs", "xml-rs",
"xz2", "xz2",
] ]
@@ -1390,7 +1390,7 @@ dependencies = [
"reqwest 0.12.24", "reqwest 0.12.24",
"ring", "ring",
"signature 2.2.0", "signature 2.2.0",
"x509-certificate 0.24.0", "x509-certificate",
] ]
[[package]] [[package]]
@@ -2504,12 +2504,10 @@ name = "isideload"
version = "0.1.21" version = "0.1.21"
dependencies = [ dependencies = [
"apple-codesign", "apple-codesign",
"der 0.7.10",
"hex", "hex",
"idevice", "idevice",
"nab138_icloud_auth", "nab138_icloud_auth",
"p12", "p12",
"pkcs8 0.10.2",
"plist", "plist",
"rand 0.8.5", "rand 0.8.5",
"rcgen", "rcgen",
@@ -2517,8 +2515,9 @@ dependencies = [
"serde", "serde",
"sha1", "sha1",
"thiserror 2.0.17", "thiserror 2.0.17",
"tokio",
"uuid", "uuid",
"x509-certificate 0.25.0", "x509-certificate",
"zip 4.6.1", "zip 4.6.1",
] ]
@@ -5437,25 +5436,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "x509-certificate"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca9eb9a0c822c67129d5b8fcc2806c6bc4f50496b420825069a440669bcfbf7f"
dependencies = [
"bcder",
"bytes",
"chrono",
"der 0.7.10",
"hex",
"pem",
"ring",
"signature 2.2.0",
"spki 0.7.3",
"thiserror 2.0.17",
"zeroize",
]
[[package]] [[package]]
name = "xml-rs" name = "xml-rs"
version = "0.8.28" version = "0.8.28"

View File

@@ -58,7 +58,9 @@ async fn main() {
let dev_session = DeveloperSession::new(Arc::new(account)); let dev_session = DeveloperSession::new(Arc::new(account));
// You can change the machine name, store directory (for certs, anisette data, & provision files), and logger // You can change the machine name, store directory (for certs, anisette data, & provision files), and logger
let config = SideloadConfiguration::default().set_machine_name("isideload-demo".to_string()); let config = SideloadConfiguration::default()
.set_machine_name("isideload-demo".to_string())
.set_force_sidestore(true);
sideload_app(&provider, &dev_session, app_path, config) sideload_app(&provider, &dev_session, app_path, config)
.await .await

View File

@@ -24,10 +24,9 @@ sha1 = "0.10"
idevice = { version = "0.1.46", features = ["afc", "installation_proxy", "ring"], default-features = false } idevice = { version = "0.1.46", features = ["afc", "installation_proxy", "ring"], default-features = false }
thiserror = "2" thiserror = "2"
apple-codesign = "0.29.0" apple-codesign = "0.29.0"
x509-certificate = "0.25.0" x509-certificate = "0.24.0"
rsa = "0.9" rsa = "0.9"
pkcs8 = "0.10"
rcgen = "0.13" rcgen = "0.13"
p12 = "0.6"
der = "0.7"
rand = "0.8" rand = "0.8"
tokio = "1.48.0"
p12 = "0.6.3"

View File

@@ -7,12 +7,13 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
#[derive(Debug, Clone)]
pub struct Bundle { pub struct Bundle {
pub app_info: Dictionary, pub app_info: Dictionary,
pub bundle_dir: PathBuf, pub bundle_dir: PathBuf,
pub bundle_type: BundleType,
app_extensions: Vec<Bundle>, app_extensions: Vec<Bundle>,
_frameworks: Vec<Bundle>, frameworks: Vec<Bundle>,
_libraries: Vec<String>, _libraries: Vec<String>,
} }
@@ -79,9 +80,15 @@ impl Bundle {
Ok(Bundle { Ok(Bundle {
app_info, app_info,
bundle_type: BundleType::from_extension(
bundle_path
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or(""),
),
bundle_dir: bundle_path, bundle_dir: bundle_path,
app_extensions, app_extensions,
_frameworks: frameworks, frameworks,
_libraries: libraries, _libraries: libraries,
}) })
} }
@@ -125,6 +132,15 @@ impl Bundle {
} }
Ok(()) Ok(())
} }
pub fn embedded_bundles(&self) -> Vec<&Bundle> {
let mut bundles = Vec::new();
bundles.extend(self.app_extensions.iter());
bundles.extend(self.frameworks.iter());
bundles.push(self);
bundles.sort_by_key(|b| b.bundle_dir.components().count());
bundles
}
} }
fn assert_bundle(condition: bool, msg: &str) -> Result<(), Error> { fn assert_bundle(condition: bool, msg: &str) -> Result<(), Error> {
@@ -178,3 +194,23 @@ fn find_dylibs(dir: &Path, bundle_root: &Path) -> Result<Vec<String>, Error> {
collect_dylibs(dir, bundle_root, &mut libraries)?; collect_dylibs(dir, bundle_root, &mut libraries)?;
Ok(libraries) Ok(libraries)
} }
// Borrowed from https://github.com/khcrysalis/PlumeImpactor/blob/main/crates/utils/src/bundle.rs
#[derive(Debug, Clone)]
pub enum BundleType {
App,
AppExtension,
Framework,
Unknown,
}
impl BundleType {
pub fn from_extension(ext: &str) -> Self {
match ext {
"app" => BundleType::App,
"appex" => BundleType::AppExtension,
"framework" => BundleType::Framework,
_ => BundleType::Unknown,
}
}
}

View File

@@ -1,8 +1,8 @@
// This file was made using https://github.com/Dadoum/Sideloader as a reference. // This file was made using https://github.com/Dadoum/Sideloader as a reference.
use apple_codesign::SigningSettings;
use hex; use hex;
use rcgen::{CertificateParams, DnType, KeyPair}; use rcgen::{CertificateParams, DnType, KeyPair};
use rsa::pkcs1::EncodeRsaPublicKey;
use rsa::{ use rsa::{
RsaPrivateKey, RsaPrivateKey,
pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}, pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding},
@@ -11,16 +11,16 @@ use sha1::{Digest, Sha1};
use std::{ use std::{
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command,
}; };
use x509_certificate::X509Certificate; use x509_certificate::{CapturedX509Certificate, InMemorySigningKeyPair, Sign, X509Certificate};
use crate::Error; use crate::Error;
use crate::developer_session::{DeveloperDeviceType, DeveloperSession, DeveloperTeam}; use crate::developer_session::{DeveloperDeviceType, DeveloperSession, DeveloperTeam};
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct CertificateIdentity { pub struct CertificateIdentity {
pub certificate: Option<X509Certificate>, pub certificate: Option<X509Certificate>,
pub key_pair: InMemorySigningKeyPair,
pub private_key: RsaPrivateKey, pub private_key: RsaPrivateKey,
pub key_file: PathBuf, pub key_file: PathBuf,
pub cert_file: PathBuf, pub cert_file: PathBuf,
@@ -65,8 +65,17 @@ impl CertificateIdentity {
private_key private_key
}; };
let key_pair = InMemorySigningKeyPair::from_pkcs8_der(
private_key
.to_pkcs8_der()
.map_err(|e| Error::Certificate(format!("Failed to encode private key: {}", e)))?
.as_bytes(),
)
.map_err(|e| Error::Certificate(format!("Failed to decode private key: {}", e)))?;
let mut cert_identity = CertificateIdentity { let mut cert_identity = CertificateIdentity {
certificate: None, certificate: None,
key_pair,
private_key, private_key,
key_file, key_file,
cert_file, cert_file,
@@ -105,12 +114,7 @@ impl CertificateIdentity {
.await .await
.map_err(|e| Error::Certificate(format!("Failed to list certificates: {:?}", e)))?; .map_err(|e| Error::Certificate(format!("Failed to list certificates: {:?}", e)))?;
let our_public_key_der = self let our_public_key_der = self.key_pair.public_key_data().to_vec();
.private_key
.to_public_key()
.to_pkcs1_der()
.map_err(|e| Error::Certificate(format!("Failed to get public key: {}", e)))?
.to_vec();
for cert in certificates for cert in certificates
.iter() .iter()
@@ -233,29 +237,65 @@ impl CertificateIdentity {
let serial = &cert.tbs_certificate().serial_number; let serial = &cert.tbs_certificate().serial_number;
let hex_str = hex::encode(serial.as_slice()); let hex_str = hex::encode(serial.as_slice());
Ok(hex_str.trim_start_matches("0").to_string()) Ok(hex_str.trim_start_matches("0").to_string().to_uppercase())
} }
pub fn to_pkcs12(&self, password: &str) -> Result<Vec<u8>, Error> { pub fn to_pkcs12(&self, password: &str) -> Result<Vec<u8>, Error> {
let output = Command::new("openssl") let cert = self
.arg("pkcs12") .certificate
.arg("-export") .as_ref()
.arg("-inkey") .ok_or(Error::Certificate("Certificate not found".to_string()))?;
.arg(&self.key_file)
.arg("-in")
.arg(&self.cert_file)
.arg("-passout")
.arg(format!("pass:{}", password))
.output()
.map_err(|e| Error::Certificate(format!("Failed to execute openssl: {}", e)))?;
if !output.status.success() { let cert_der = cert
return Err(Error::Certificate(format!( .encode_der()
"openssl failed: {}", .map_err(|e| Error::Certificate(format!("Failed to encode certificate: {}", e)))?;
String::from_utf8_lossy(&output.stderr)
))); let key_der = self
.private_key
.to_pkcs8_der()
.map_err(|e| Error::Certificate(format!("Failed to encode private key: {}", e)))?;
let pfx = p12::PFX::new(
&cert_der,
key_der.as_bytes(),
None,
password,
&self.machine_name,
)
.ok_or(Error::Certificate("Failed to create PKCS#12".to_string()))?;
Ok(pfx.to_der())
} }
Ok(output.stdout) pub fn to_signing_settings(&self) -> Result<SigningSettings<'_>, Error> {
let mut settings = SigningSettings::default();
let certificate = self
.certificate
.as_ref()
.ok_or(Error::Certificate("Certificate not found".to_string()))?;
settings.set_signing_key(
&self.key_pair,
CapturedX509Certificate::from_der(
certificate.encode_der().map_err(|e| {
Error::Certificate(format!("Failed to encode certificate: {}", e))
})?,
)
.map_err(|e| {
Error::Certificate(format!("Failed to create captured certificate: {}", e))
})?,
);
settings.chain_apple_certificates();
settings.set_for_notarization(false);
settings.set_shallow(true);
settings.set_team_id_from_signing_certificate().ok_or({
Error::Certificate("Failed to set team ID from signing certificate".to_string())
})?;
settings
.set_time_stamp_url("http://timestamp.apple.com/ts01")
.map_err(|e| Error::AppleCodesignError(Box::new(e)))?;
Ok(settings)
} }
} }

View File

@@ -724,3 +724,56 @@ pub struct ProvisioningProfile {
pub _name: String, pub _name: String,
pub encoded_profile: Vec<u8>, pub encoded_profile: Vec<u8>,
} }
impl ProvisioningProfile {
// TODO: I'm not sure if this is the proper way to parse this but it works so...
pub fn profile_plist(&self) -> Result<plist::Dictionary, Error> {
let start_marker = b"<?xml";
let end_marker = b"</plist>";
let start = self
.encoded_profile
.windows(start_marker.len())
.position(|w| w == start_marker)
.ok_or_else(|| {
Error::Generic("Failed to find start of plist in provisioning profile".to_string())
})?;
let end = self
.encoded_profile
.windows(end_marker.len())
.position(|w| w == end_marker)
.ok_or_else(|| {
Error::Generic("Failed to find end of plist in provisioning profile".to_string())
})?
+ end_marker.len();
plist::from_bytes::<plist::Dictionary>(&self.encoded_profile[start..end]).map_err(|e| {
Error::Generic(format!("Failed to parse provisioning profile plist: {}", e))
})
}
pub fn entitlements_xml(&self) -> Result<String, Error> {
let profile_plist = self.profile_plist()?;
let entitlements = profile_plist.get("Entitlements").ok_or_else(|| {
Error::Generic("No Entitlements found in provisioning profile".to_string())
})?;
let mut buf = vec![];
entitlements.to_writer_xml(&mut buf).map_err(|e| {
Error::Generic(format!(
"Failed to convert entitlements to XML for codesigning: {}",
e
))
})?;
let entitlements = std::str::from_utf8(&buf)
.map_err(|e| {
Error::Generic(format!(
"Failed to convert entitlements to UTF-8 for codesigning: {}",
e
))
})?
.to_string();
Ok(entitlements)
}
}

View File

@@ -55,16 +55,12 @@ impl SideloadLogger for DefaultLogger {
/// Sideload configuration options. /// Sideload configuration options.
pub struct SideloadConfiguration<'a> { pub struct SideloadConfiguration<'a> {
/// An arbitrary machine name to appear on the certificate (e.x. "YCode")
pub machine_name: String, pub machine_name: String,
/// Logger for reporting progress and errors
pub logger: &'a dyn SideloadLogger, pub logger: &'a dyn SideloadLogger,
/// Directory used to store intermediate artifacts (profiles, certs, etc.). This directory will not be cleared at the end.
pub store_dir: std::path::PathBuf, pub store_dir: std::path::PathBuf,
/// Whether or not to revoke the certificate immediately after installation
pub revoke_cert: bool, pub revoke_cert: bool,
/// Whether or not to force SideStore App Group (fixes LiveContainer+SideStore issues) pub force_sidestore: bool,
pub force_sidestore_app_group: bool, pub skip_register_extensions: bool,
} }
impl Default for SideloadConfiguration<'_> { impl Default for SideloadConfiguration<'_> {
@@ -80,32 +76,48 @@ impl<'a> SideloadConfiguration<'a> {
logger: &DefaultLogger, logger: &DefaultLogger,
store_dir: std::env::current_dir().unwrap(), store_dir: std::env::current_dir().unwrap(),
revoke_cert: false, revoke_cert: false,
force_sidestore_app_group: false, force_sidestore: false,
skip_register_extensions: true,
} }
} }
/// An arbitrary machine name to appear on the certificate (e.x. "CrossCode")
pub fn set_machine_name(mut self, machine_name: String) -> Self { pub fn set_machine_name(mut self, machine_name: String) -> Self {
self.machine_name = machine_name; self.machine_name = machine_name;
self self
} }
/// Logger for reporting progress and errors
pub fn set_logger(mut self, logger: &'a dyn SideloadLogger) -> Self { pub fn set_logger(mut self, logger: &'a dyn SideloadLogger) -> Self {
self.logger = logger; self.logger = logger;
self self
} }
/// Directory used to store intermediate artifacts (profiles, certs, etc.). This directory will not be cleared at the end.
pub fn set_store_dir(mut self, store_dir: std::path::PathBuf) -> Self { pub fn set_store_dir(mut self, store_dir: std::path::PathBuf) -> Self {
self.store_dir = store_dir; self.store_dir = store_dir;
self self
} }
/// Whether or not to revoke the certificate immediately after installation
#[deprecated(
since = "0.1.0",
note = "Certificates will now be placed in SideStore automatically so there is no need to revoke"
)]
pub fn set_revoke_cert(mut self, revoke_cert: bool) -> Self { pub fn set_revoke_cert(mut self, revoke_cert: bool) -> Self {
self.revoke_cert = revoke_cert; self.revoke_cert = revoke_cert;
self self
} }
pub fn set_force_sidestore_app_group(mut self, force: bool) -> Self { /// Whether or not to treat the app as SideStore (fixes LiveContainer+SideStore issues)
self.force_sidestore_app_group = force; pub fn set_force_sidestore(mut self, force: bool) -> Self {
self.force_sidestore = force;
self
}
/// Whether or not to skip registering app extensions (save app IDs, default true)
pub fn set_skip_register_extensions(mut self, skip: bool) -> Self {
self.skip_register_extensions = skip;
self self
} }
} }

View File

@@ -1,18 +1,19 @@
// This file was made using https://github.com/Dadoum/Sideloader as a reference. // This file was made using https://github.com/Dadoum/Sideloader as a reference.
use apple_codesign::{BundleSigner, SigningSettings}; use apple_codesign::{SettingsScope, UnifiedSigner};
use der::Encode;
use idevice::IdeviceService; use idevice::IdeviceService;
use idevice::lockdown::LockdownClient; use idevice::lockdown::LockdownClient;
use idevice::provider::IdeviceProvider; use idevice::provider::IdeviceProvider;
use crate::application::Application; use crate::application::Application;
use crate::developer_session::ProvisioningProfile;
use crate::device::install_app; use crate::device::install_app;
use crate::{DeveloperTeam, Error, SideloadConfiguration, SideloadLogger}; use crate::{DeveloperTeam, Error, SideloadConfiguration, SideloadLogger};
use crate::{ use crate::{
certificate::CertificateIdentity, certificate::CertificateIdentity,
developer_session::{DeveloperDeviceType, DeveloperSession}, developer_session::{DeveloperDeviceType, DeveloperSession},
}; };
use std::collections::HashMap;
use std::{io::Write, path::PathBuf}; use std::{io::Write, path::PathBuf};
fn error_and_return(logger: &dyn SideloadLogger, error: Error) -> Result<(), Error> { fn error_and_return(logger: &dyn SideloadLogger, error: Error) -> Result<(), Error> {
@@ -106,7 +107,8 @@ pub async fn sideload_app(
}; };
let mut app = Application::new(app_path)?; let mut app = Application::new(app_path)?;
let is_sidestore = app.bundle.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore"; let is_sidestore = config.force_sidestore
|| app.bundle.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore";
let main_app_bundle_id = match app.bundle.bundle_identifier() { let main_app_bundle_id = match app.bundle.bundle_identifier() {
Some(id) => id.to_string(), Some(id) => id.to_string(),
None => { None => {
@@ -255,7 +257,7 @@ pub async fn sideload_app(
let group_identifier = format!( let group_identifier = format!(
"group.{}", "group.{}",
if config.force_sidestore_app_group { if config.force_sidestore {
format!("com.SideStore.SideStore.{}", team.team_id) format!("com.SideStore.SideStore.{}", team.team_id)
} else { } else {
main_app_id_str.clone() main_app_id_str.clone()
@@ -321,7 +323,7 @@ pub async fn sideload_app(
matching_app_groups[0].clone() matching_app_groups[0].clone()
}; };
//let mut provisioning_profiles: HashMap<String, ProvisioningProfile> = HashMap::new(); let mut provisioning_profiles: HashMap<String, ProvisioningProfile> = HashMap::new();
for app_id in app_ids { for app_id in app_ids {
let assign_res = dev_session let assign_res = dev_session
.assign_application_group_to_app_id( .assign_application_group_to_app_id(
@@ -334,26 +336,8 @@ pub async fn sideload_app(
if assign_res.is_err() { if assign_res.is_err() {
return error_and_return(logger, assign_res.err().unwrap()); 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 let provisioning_profile = match dev_session
.download_team_provisioning_profile(DeveloperDeviceType::Ios, &team, &main_app_id) .download_team_provisioning_profile(DeveloperDeviceType::Ios, &team, &app_id)
.await .await
{ {
Ok(pp /* tee hee */) => pp, Ok(pp /* tee hee */) => pp,
@@ -361,10 +345,12 @@ pub async fn sideload_app(
return error_and_return(logger, e); return error_and_return(logger, e);
} }
}; };
provisioning_profiles.insert(app_id.identifier.clone(), provisioning_profile);
}
let profile_path = config logger.log("Successfully registered app groups");
.store_dir
.join(format!("{}.mobileprovision", main_app_id_str)); let profile_path = app.bundle.bundle_dir.join("embedded.mobileprovision");
if profile_path.exists() { if profile_path.exists() {
std::fs::remove_file(&profile_path).map_err(Error::Filesystem)?; std::fs::remove_file(&profile_path).map_err(Error::Filesystem)?;
@@ -386,37 +372,53 @@ pub async fn sideload_app(
ext.write_info()?; ext.write_info()?;
} }
// match ZSignOptions::new(app.bundle.bundle_dir.to_str().unwrap()) // Collect owned bundle identifiers and directories so we don't capture `app` or `logger` by reference in the blocking thread.
// .with_cert_file(cert.get_certificate_file_path().to_str().unwrap()) let embedded_bundles_info: Vec<(String, PathBuf)> = app
// .with_pkey_file(cert.get_private_key_file_path().to_str().unwrap()) .bundle
// .with_prov_file(profile_path.to_str().unwrap()) .embedded_bundles()
// .sign() .iter()
// { .map(|bundle| {
// Ok(_) => {} (
// Err(e) => { bundle.bundle_identifier().unwrap_or("Unknown").to_string(),
// return error_and_return(logger, Error::ZSignError(e)); bundle.bundle_dir.clone(),
// } )
// }; })
.collect();
let main_bundle_dir = app.bundle.bundle_dir.clone();
let mut signer = BundleSigner::new_from_path(&app.bundle.bundle_dir) // Log bundle signing messages outside the blocking closure to avoid capturing non-'static references.
for (id, _) in &embedded_bundles_info {
logger.log(&format!("Signing bundle: {}", id));
}
// Move owned data (cert, provisioning_profile, embedded_bundles_info) into the blocking task.
tokio::task::spawn_blocking(move || {
for (_id, bundle_dir) in embedded_bundles_info {
// Recreate settings for each bundle so ownership is clear and we don't move settings across iterations.
let mut settings = cert.to_signing_settings()?;
settings
.set_entitlements_xml(
SettingsScope::Main,
provisioning_profile.entitlements_xml()?,
)
.map_err(|e| Error::AppleCodesignError(Box::new(e)))?; .map_err(|e| Error::AppleCodesignError(Box::new(e)))?;
let signer = UnifiedSigner::new(settings);
signer signer
.collect_nested_bundles() .sign_path_in_place(&bundle_dir)
.map_err(|e| Error::AppleCodesignError(Box::new(e)))?; .map_err(|e| Error::AppleCodesignError(Box::new(e)))?;
}
Ok::<(), Error>(())
})
.await
.map_err(|e| Error::Generic(format!("Signing task failed: {}", e)))??;
let mut settings = SigningSettings::default(); logger.log("Sucessfully signed app");
settings.set_signing_key(cert.private_key.clone(), cert.certificate.unwrap()); logger.log("Installing app... 0%");
signer let res = install_app(device_provider, &main_bundle_dir, |percentage| {
.write_signed_bundle(&app.bundle.bundle_dir, &settings)
.map_err(|e| Error::AppleCodesignError(Box::new(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)); logger.log(&format!("Installing app... {}%", percentage));
}) })
.await; .await;
@@ -424,12 +426,12 @@ pub async fn sideload_app(
return error_and_return(logger, e); return error_and_return(logger, e);
} }
if config.revoke_cert { // if config.revoke_cert {
dev_session // dev_session
.revoke_development_cert(DeveloperDeviceType::Ios, &team, &cert.get_serial_number()?) // .revoke_development_cert(DeveloperDeviceType::Ios, &team, &cert.get_serial_number()?)
.await?; // .await?;
logger.log("Certificate revoked"); // logger.log("Certificate revoked");
} // }
Ok(()) Ok(())
} }