Start switching to apple-codesign

This commit is contained in:
nab138
2025-11-21 00:10:15 -05:00
parent 3dd4395017
commit 92085d878b
7 changed files with 3007 additions and 181 deletions

2938
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -104,6 +104,6 @@ This project is licensed under the MPL-2.0 License. See the [LICENSE](LICENSE) f
- Packages from [`apple-private-apis`](https://github.com/SideStore/apple-private-apis) were used for authentication, but the original project was left unfinished. To support isideload, `apple-private-apis` was forked and modified to add missing features. With permission from the original developers, the fork was published to crates.io until the official project is published. - Packages from [`apple-private-apis`](https://github.com/SideStore/apple-private-apis) were used for authentication, but the original project was left unfinished. To support isideload, `apple-private-apis` was forked and modified to add missing features. With permission from the original developers, the fork was published to crates.io until the official project is published.
- [ZSign](https://github.com/zhlynn/zsign) was used for code signing with [custom rust bindings](https://github.com/nab138/zsign-rust) - [apple-codesign](https://crates.io/crates/apple-codesign) was used for code signing, which is licensed under MPL-2.0.
- [Sideloader](https://github.com/Dadoum/Sideloader) was used as a reference for how the private API endpoints work - [Sideloader](https://github.com/Dadoum/Sideloader) was used as a reference for how the private API endpoints work

View File

@@ -5,6 +5,6 @@ edition = "2024"
publish = false publish = false
[dependencies] [dependencies]
isideload = { path = "../../isideload", features = ["vendored-openssl"] } isideload = { path = "../../isideload" }
idevice = { version = "0.1.46", features = ["usbmuxd", "ring"], default-features = false} idevice = { version = "0.1.46", features = ["usbmuxd", "ring"], default-features = false}
tokio = { version = "1.43", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.43", features = ["macros", "rt-multi-thread"] }

View File

@@ -12,7 +12,6 @@ readme = "../README.md"
[features] [features]
default = [] default = []
vendored-openssl = ["openssl/vendored", "zsign-rust/vendored-openssl"]
[dependencies] [dependencies]
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
@@ -23,6 +22,12 @@ zip = { version = "4.3", default-features = false, features = ["deflate"] }
hex = "0.4" hex = "0.4"
sha1 = "0.10" 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 }
openssl = "0.10"
zsign-rust = "0.1.6"
thiserror = "2" thiserror = "2"
apple-codesign = "0.29.0"
x509-certificate = "0.25.0"
rsa = "0.9"
pkcs8 = "0.10"
rcgen = "0.13"
p12 = "0.6"
der = "0.7"
rand = "0.8"

View File

@@ -1,26 +1,27 @@
// 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 hex; use hex;
use openssl::{ use rcgen::{CertificateParams, DnType, KeyPair};
hash::MessageDigest, use rsa::pkcs1::EncodeRsaPublicKey;
pkcs12::Pkcs12, use rsa::{
pkey::{PKey, Private}, RsaPrivateKey,
rsa::Rsa, pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding},
x509::{X509, X509Name, X509ReqBuilder},
}; };
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use std::{ use std::{
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command,
}; };
use x509_certificate::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, Clone)]
pub struct CertificateIdentity { pub struct CertificateIdentity {
pub certificate: Option<X509>, pub certificate: Option<X509Certificate>,
pub private_key: PKey<Private>, pub private_key: RsaPrivateKey,
pub key_file: PathBuf, pub key_file: PathBuf,
pub cert_file: PathBuf, pub cert_file: PathBuf,
pub machine_name: String, pub machine_name: String,
@@ -46,21 +47,22 @@ impl CertificateIdentity {
let team = teams let team = teams
.first() .first()
.ok_or(Error::Certificate("No teams found".to_string()))?; .ok_or(Error::Certificate("No teams found".to_string()))?;
let private_key = if key_file.exists() { let private_key = if key_file.exists() {
let key_data = fs::read_to_string(&key_file) let key_data = fs::read_to_string(&key_file)
.map_err(|e| Error::Certificate(format!("Failed to read key file: {}", e)))?; .map_err(|e| Error::Certificate(format!("Failed to read key file: {}", e)))?;
PKey::private_key_from_pem(key_data.as_bytes()) RsaPrivateKey::from_pkcs8_pem(&key_data)
.map_err(|e| Error::Certificate(format!("Failed to load private key: {}", e)))? .map_err(|e| Error::Certificate(format!("Failed to load private key: {}", e)))?
} else { } else {
let rsa = Rsa::generate(2048) let mut rng = rand::thread_rng();
let private_key = RsaPrivateKey::new(&mut rng, 2048)
.map_err(|e| Error::Certificate(format!("Failed to generate RSA key: {}", e)))?; .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 = private_key
let pem_data = key .to_pkcs8_pem(LineEnding::LF)
.private_key_to_pem_pkcs8()
.map_err(|e| Error::Certificate(format!("Failed to encode private key: {}", e)))?; .map_err(|e| Error::Certificate(format!("Failed to encode private key: {}", e)))?;
fs::write(&key_file, pem_data).map_err(Error::Filesystem)?; fs::write(&key_file, pem_data.as_bytes()).map_err(Error::Filesystem)?;
key private_key
}; };
let mut cert_identity = CertificateIdentity { let mut cert_identity = CertificateIdentity {
@@ -78,9 +80,10 @@ impl CertificateIdentity {
{ {
cert_identity.certificate = Some(cert.clone()); cert_identity.certificate = Some(cert.clone());
cert_identity.machine_id = machine_id; 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)) let cert_pem = cert
})?; .encode_pem()
.map_err(|e| Error::Certificate(format!("Failed to encode cert: {}", e)))?;
fs::write(&cert_identity.cert_file, cert_pem).map_err(Error::Filesystem)?; fs::write(&cert_identity.cert_file, cert_pem).map_err(Error::Filesystem)?;
return Ok(cert_identity); return Ok(cert_identity);
@@ -96,29 +99,35 @@ impl CertificateIdentity {
&self, &self,
dev_session: &DeveloperSession, dev_session: &DeveloperSession,
team: &DeveloperTeam, team: &DeveloperTeam,
) -> Result<(X509, String), Error> { ) -> Result<(X509Certificate, String), Error> {
let certificates = dev_session let certificates = dev_session
.list_all_development_certs(DeveloperDeviceType::Ios, team) .list_all_development_certs(DeveloperDeviceType::Ios, team)
.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 = self let our_public_key_der = self
.private_key .private_key
.public_key_to_der() .to_public_key()
.map_err(|e| Error::Certificate(format!("Failed to get public key: {}", e)))?; .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()
.filter(|c| c.machine_name == self.machine_name) .filter(|c| c.machine_name == self.machine_name)
{ {
if let Ok(x509_cert) = X509::from_der(&cert.cert_content) if let Ok(x509_cert) = X509Certificate::from_der(&cert.cert_content) {
&& let Ok(cert_public_key) = x509_cert.public_key() let cert_public_key_der: Vec<u8> = x509_cert
&& let Ok(cert_public_key_der) = cert_public_key.public_key_to_der() .tbs_certificate()
&& cert_public_key_der == our_public_key .subject_public_key_info
{ .subject_public_key
.octets()
.collect();
if cert_public_key_der == our_public_key_der {
return Ok((x509_cert, cert.machine_id.clone())); return Ok((x509_cert, cert.machine_id.clone()));
} }
} }
}
Err(Error::Certificate( Err(Error::Certificate(
"No matching certificate found".to_string(), "No matching certificate found".to_string(),
)) ))
@@ -129,47 +138,39 @@ impl CertificateIdentity {
dev_session: &DeveloperSession, dev_session: &DeveloperSession,
team: &DeveloperTeam, team: &DeveloperTeam,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut req_builder = X509ReqBuilder::new() let mut params = CertificateParams::new(vec!["CN".to_string()])
.map_err(|e| Error::Certificate(format!("Failed to create request builder: {}", e)))?; .map_err(|e| Error::Certificate(format!("Failed to create params: {}", e)))?;
let mut name_builder = X509Name::builder() params.distinguished_name.push(DnType::CountryName, "US");
.map_err(|e| Error::Certificate(format!("Failed to create name builder: {}", e)))?; params
.distinguished_name
.push(DnType::StateOrProvinceName, "STATE");
params
.distinguished_name
.push(DnType::LocalityName, "LOCAL");
params
.distinguished_name
.push(DnType::OrganizationName, "ORGNIZATION");
params.distinguished_name.push(DnType::CommonName, "CN");
name_builder let key_pem = self
.append_entry_by_text("C", "US") .private_key
.map_err(|e| Error::Certificate(format!("Failed to set country: {}", e)))?; .to_pkcs8_pem(LineEnding::LF)
name_builder .map_err(|e| Error::Certificate(format!("Failed to encode private key: {}", e)))?;
.append_entry_by_text("ST", "STATE") let key_pair = KeyPair::from_pem(&key_pem)
.map_err(|e| Error::Certificate(format!("Failed to set state: {}", e)))?; .map_err(|e| Error::Certificate(format!("Failed to load key pair for CSR: {}", 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 let csr = params
.set_subject_name(&name_builder.build()) .serialize_request(&key_pair)
.map_err(|e| Error::Certificate(format!("Failed to set subject name: {}", e)))?; .map_err(|e| Error::Certificate(format!("Failed to generate CSR: {}", e)))?;
req_builder let csr_pem = csr
.set_pubkey(&self.private_key) .pem()
.map_err(|e| Error::Certificate(format!("Failed to set public key: {}", e)))?; .map_err(|e| Error::Certificate(format!("Failed to encode CSR to PEM: {}", 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 let certificate_id = dev_session
.submit_development_csr( .submit_development_csr(
DeveloperDeviceType::Ios, DeveloperDeviceType::Ios,
team, team,
String::from_utf8_lossy(&csr_pem).to_string(), csr_pem,
self.machine_name.clone(), self.machine_name.clone(),
) )
.await .await
@@ -196,13 +197,13 @@ impl CertificateIdentity {
"Certificate not found after submission".to_string(), "Certificate not found after submission".to_string(),
))?; ))?;
let certificate = X509::from_der(&apple_cert.cert_content) let certificate = X509Certificate::from_der(&apple_cert.cert_content)
.map_err(|e| Error::Certificate(format!("Failed to parse certificate: {}", e)))?; .map_err(|e| Error::Certificate(format!("Failed to parse certificate: {}", e)))?;
// Write certificate to disk // Write certificate to disk
let cert_pem = certificate.to_pem().map_err(|e| { let cert_pem = certificate
Error::Certificate(format!("Failed to encode certificate to PEM: {}", e)) .encode_pem()
})?; .map_err(|e| Error::Certificate(format!("Failed to encode cert: {}", e)))?;
fs::write(&self.cert_file, cert_pem).map_err(Error::Filesystem)?; fs::write(&self.cert_file, cert_pem).map_err(Error::Filesystem)?;
self.certificate = Some(certificate); self.certificate = Some(certificate);
@@ -228,46 +229,33 @@ impl CertificateIdentity {
)); ));
} }
}; };
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()) let serial = &cert.tbs_certificate().serial_number;
let hex_str = hex::encode(serial.as_slice());
Ok(hex_str.trim_start_matches("0").to_string())
} }
pub fn to_pkcs12(&self, password: &str) -> Result<Vec<u8>, Error> { pub fn to_pkcs12(&self, password: &str) -> Result<Vec<u8>, Error> {
let cert = match &self.certificate { let output = Command::new("openssl")
Some(c) => c, .arg("pkcs12")
None => { .arg("-export")
return Err(Error::Certificate( .arg("-inkey")
"No certificate available to create PKCS#12".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() {
return Err(Error::Certificate(format!(
"openssl failed: {}",
String::from_utf8_lossy(&output.stderr)
)));
} }
};
let mut pkcs12_builder = Pkcs12::builder(); Ok(output.stdout)
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

@@ -7,12 +7,12 @@ pub mod sideload;
use std::io::Error as IOError; use std::io::Error as IOError;
use apple_codesign::AppleCodesignError;
pub use icloud_auth::{AnisetteConfiguration, AppleAccount}; pub use icloud_auth::{AnisetteConfiguration, AppleAccount};
use developer_session::DeveloperTeam; use developer_session::DeveloperTeam;
use idevice::IdeviceError; use idevice::IdeviceError;
use thiserror::Error as ThisError; use thiserror::Error as ThisError;
use zsign_rust::ZSignError;
#[derive(Debug, ThisError)] #[derive(Debug, ThisError)]
pub enum Error { pub enum Error {
@@ -33,7 +33,7 @@ pub enum Error {
#[error(transparent)] #[error(transparent)]
IdeviceError(#[from] IdeviceError), IdeviceError(#[from] IdeviceError),
#[error(transparent)] #[error(transparent)]
ZSignError(#[from] ZSignError), AppleCodesignError(#[from] Box<AppleCodesignError>),
} }
pub trait SideloadLogger: Send + Sync { pub trait SideloadLogger: Send + Sync {

View File

@@ -1,9 +1,10 @@
// 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 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 zsign_rust::ZSignOptions;
use crate::application::Application; use crate::application::Application;
use crate::device::install_app; use crate::device::install_app;
@@ -385,18 +386,32 @@ pub async fn sideload_app(
ext.write_info()?; ext.write_info()?;
} }
match ZSignOptions::new(app.bundle.bundle_dir.to_str().unwrap()) // match ZSignOptions::new(app.bundle.bundle_dir.to_str().unwrap())
.with_cert_file(cert.get_certificate_file_path().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_pkey_file(cert.get_private_key_file_path().to_str().unwrap())
.with_prov_file(profile_path.to_str().unwrap()) // .with_prov_file(profile_path.to_str().unwrap())
.sign() // .sign()
{ // {
Ok(_) => {} // Ok(_) => {}
Err(e) => { // Err(e) => {
return error_and_return(logger, Error::ZSignError(e)); // return error_and_return(logger, Error::ZSignError(e));
} // }
}; // };
let mut signer = BundleSigner::new_from_path(&app.bundle.bundle_dir)
.map_err(|e| Error::AppleCodesignError(Box::new(e)))?;
signer
.collect_nested_bundles()
.map_err(|e| Error::AppleCodesignError(Box::new(e)))?;
let mut settings = SigningSettings::default();
settings.set_signing_key(cert.private_key.clone(), cert.certificate.unwrap());
signer
.write_signed_bundle(&app.bundle.bundle_dir, &settings)
.map_err(|e| Error::AppleCodesignError(Box::new(e)))?;
logger.log("App signed!"); logger.log("App signed!");
logger.log("Installing app (Transfer)... 0%"); logger.log("Installing app (Transfer)... 0%");