mirror of
https://github.com/nab138/isideload.git
synced 2026-03-02 06:26:16 +01:00
im questioning everything I thought I knew about this stuff
This commit is contained in:
32
Cargo.lock
generated
32
Cargo.lock
generated
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(output.stdout)
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,37 +336,21 @@ 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
|
let provisioning_profile = match dev_session
|
||||||
// // 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, &app_id)
|
||||||
// .download_team_provisioning_profile(DeveloperDeviceType::Ios, &team, &main_app_id)
|
.await
|
||||||
// .await
|
{
|
||||||
// {
|
Ok(pp /* tee hee */) => pp,
|
||||||
// Ok(pp /* tee hee */) => pp,
|
Err(e) => {
|
||||||
// Err(e) => {
|
return error_and_return(logger, e);
|
||||||
// return emit_error_and_return(
|
}
|
||||||
// &window,
|
};
|
||||||
// &format!("Failed to download provisioning profile: {:?}", e),
|
provisioning_profiles.insert(app_id.identifier.clone(), provisioning_profile);
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// provisioning_profiles.insert(app_id.identifier.clone(), provisioning_profile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log("Successfully registered app groups");
|
logger.log("Successfully registered app groups");
|
||||||
|
|
||||||
let provisioning_profile = match dev_session
|
let profile_path = app.bundle.bundle_dir.join("embedded.mobileprovision");
|
||||||
.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() {
|
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.
|
||||||
.map_err(|e| Error::AppleCodesignError(Box::new(e)))?;
|
for (id, _) in &embedded_bundles_info {
|
||||||
|
logger.log(&format!("Signing bundle: {}", id));
|
||||||
|
}
|
||||||
|
|
||||||
signer
|
// Move owned data (cert, provisioning_profile, embedded_bundles_info) into the blocking task.
|
||||||
.collect_nested_bundles()
|
tokio::task::spawn_blocking(move || {
|
||||||
.map_err(|e| Error::AppleCodesignError(Box::new(e)))?;
|
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)))?;
|
||||||
|
|
||||||
let mut settings = SigningSettings::default();
|
let signer = UnifiedSigner::new(settings);
|
||||||
|
|
||||||
settings.set_signing_key(cert.private_key.clone(), cert.certificate.unwrap());
|
signer
|
||||||
|
.sign_path_in_place(&bundle_dir)
|
||||||
|
.map_err(|e| Error::AppleCodesignError(Box::new(e)))?;
|
||||||
|
}
|
||||||
|
Ok::<(), Error>(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::Generic(format!("Signing task failed: {}", e)))??;
|
||||||
|
|
||||||
signer
|
logger.log("Sucessfully signed app");
|
||||||
.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%");
|
logger.log("Installing app... 0%");
|
||||||
|
|
||||||
let res = install_app(device_provider, &app.bundle.bundle_dir, |percentage| {
|
let res = install_app(device_provider, &main_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(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user