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.
- [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

View File

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

View File

@@ -12,7 +12,6 @@ readme = "../README.md"
[features]
default = []
vendored-openssl = ["openssl/vendored", "zsign-rust/vendored-openssl"]
[dependencies]
serde = { version = "1", features = ["derive"] }
@@ -23,6 +22,12 @@ zip = { version = "4.3", default-features = false, features = ["deflate"] }
hex = "0.4"
sha1 = "0.10"
idevice = { version = "0.1.46", features = ["afc", "installation_proxy", "ring"], default-features = false }
openssl = "0.10"
zsign-rust = "0.1.6"
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.
use hex;
use openssl::{
hash::MessageDigest,
pkcs12::Pkcs12,
pkey::{PKey, Private},
rsa::Rsa,
x509::{X509, X509Name, X509ReqBuilder},
use rcgen::{CertificateParams, DnType, KeyPair};
use rsa::pkcs1::EncodeRsaPublicKey;
use rsa::{
RsaPrivateKey,
pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding},
};
use sha1::{Digest, Sha1};
use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
use x509_certificate::X509Certificate;
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 certificate: Option<X509Certificate>,
pub private_key: RsaPrivateKey,
pub key_file: PathBuf,
pub cert_file: PathBuf,
pub machine_name: String,
@@ -46,21 +47,22 @@ impl CertificateIdentity {
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())
RsaPrivateKey::from_pkcs8_pem(&key_data)
.map_err(|e| Error::Certificate(format!("Failed to load private key: {}", e)))?
} 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)))?;
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()
let pem_data = private_key
.to_pkcs8_pem(LineEnding::LF)
.map_err(|e| Error::Certificate(format!("Failed to encode private key: {}", e)))?;
fs::write(&key_file, pem_data).map_err(Error::Filesystem)?;
key
fs::write(&key_file, pem_data.as_bytes()).map_err(Error::Filesystem)?;
private_key
};
let mut cert_identity = CertificateIdentity {
@@ -78,9 +80,10 @@ impl CertificateIdentity {
{
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))
})?;
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)?;
return Ok(cert_identity);
@@ -96,29 +99,35 @@ impl CertificateIdentity {
&self,
dev_session: &DeveloperSession,
team: &DeveloperTeam,
) -> Result<(X509, String), Error> {
) -> Result<(X509Certificate, 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
let our_public_key_der = self
.private_key
.public_key_to_der()
.map_err(|e| Error::Certificate(format!("Failed to get public key: {}", e)))?;
.to_public_key()
.to_pkcs1_der()
.map_err(|e| Error::Certificate(format!("Failed to get public key: {}", e)))?
.to_vec();
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
{
if let Ok(x509_cert) = X509Certificate::from_der(&cert.cert_content) {
let cert_public_key_der: Vec<u8> = x509_cert
.tbs_certificate()
.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()));
}
}
}
Err(Error::Certificate(
"No matching certificate found".to_string(),
))
@@ -129,47 +138,39 @@ impl CertificateIdentity {
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)))?;
let mut params = CertificateParams::new(vec!["CN".to_string()])
.map_err(|e| Error::Certificate(format!("Failed to create params: {}", e)))?;
params.distinguished_name.push(DnType::CountryName, "US");
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
.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)))?;
let key_pem = self
.private_key
.to_pkcs8_pem(LineEnding::LF)
.map_err(|e| Error::Certificate(format!("Failed to encode private key: {}", e)))?;
let key_pair = KeyPair::from_pem(&key_pem)
.map_err(|e| Error::Certificate(format!("Failed to load key pair for CSR: {}", 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 csr = params
.serialize_request(&key_pair)
.map_err(|e| Error::Certificate(format!("Failed to generate CSR: {}", e)))?;
let csr_pem = csr
.pem()
.map_err(|e| Error::Certificate(format!("Failed to encode CSR to PEM: {}", e)))?;
let certificate_id = dev_session
.submit_development_csr(
DeveloperDeviceType::Ios,
team,
String::from_utf8_lossy(&csr_pem).to_string(),
csr_pem,
self.machine_name.clone(),
)
.await
@@ -196,13 +197,13 @@ impl CertificateIdentity {
"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)))?;
// Write certificate to disk
let cert_pem = certificate.to_pem().map_err(|e| {
Error::Certificate(format!("Failed to encode certificate to PEM: {}", e))
})?;
let cert_pem = certificate
.encode_pem()
.map_err(|e| Error::Certificate(format!("Failed to encode cert: {}", e)))?;
fs::write(&self.cert_file, cert_pem).map_err(Error::Filesystem)?;
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> {
let cert = match &self.certificate {
Some(c) => c,
None => {
return Err(Error::Certificate(
"No certificate available to create PKCS#12".to_string(),
));
let output = Command::new("openssl")
.arg("pkcs12")
.arg("-export")
.arg("-inkey")
.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();
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)
Ok(output.stdout)
}
}

View File

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

View File

@@ -1,9 +1,10 @@
// 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::lockdown::LockdownClient;
use idevice::provider::IdeviceProvider;
use zsign_rust::ZSignOptions;
use crate::application::Application;
use crate::device::install_app;
@@ -385,18 +386,32 @@ pub async fn sideload_app(
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())
.sign()
{
Ok(_) => {}
Err(e) => {
return error_and_return(logger, Error::ZSignError(e));
}
};
// 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())
// .sign()
// {
// Ok(_) => {}
// Err(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("Installing app (Transfer)... 0%");