Retrieve certificates

This commit is contained in:
nab138
2026-02-08 22:43:33 -05:00
parent 0509f00257
commit ec9af89d74
10 changed files with 373 additions and 83 deletions

219
Cargo.lock generated
View File

@@ -26,7 +26,7 @@ checksum = "04097e08a47d9ad181c2e1f4a5fabc9ae06ce8839a333ba9a949bcb0d31fd2a3"
dependencies = [ dependencies = [
"cipher", "cipher",
"cpubits", "cpubits",
"cpufeatures", "cpufeatures 0.2.17",
] ]
[[package]] [[package]]
@@ -43,6 +43,15 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.100" version = "1.0.100"
@@ -164,6 +173,16 @@ version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "bcder"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f7c42c9913f68cf9390a225e81ad56a5c515347287eb98baa710090ca1de86d"
dependencies = [
"bytes",
"smallvec",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.10.0" version = "2.10.0"
@@ -254,6 +273,28 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chacha20"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
dependencies = [
"cfg-if",
"cpufeatures 0.3.0",
"rand_core 0.10.0",
]
[[package]]
name = "chrono"
version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
dependencies = [
"iana-time-zone",
"num-traits",
"windows-link",
]
[[package]] [[package]]
name = "cipher" name = "cipher"
version = "0.5.0" version = "0.5.0"
@@ -307,6 +348,12 @@ version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.10.2" version = "0.10.2"
@@ -354,6 +401,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.5.0" version = "1.5.0"
@@ -455,13 +511,23 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid 0.9.6",
"zeroize",
]
[[package]] [[package]]
name = "der" name = "der"
version = "0.8.0-rc.10" version = "0.8.0-rc.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653" checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653"
dependencies = [ dependencies = [
"const-oid", "const-oid 0.10.2",
"pem-rfc7468", "pem-rfc7468",
"zeroize", "zeroize",
] ]
@@ -506,7 +572,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b42f1d9edf5207c137646b568a0168ca0ec25b7f9eaf7f9961da51a3d91cea" checksum = "02b42f1d9edf5207c137646b568a0168ca0ec25b7f9eaf7f9961da51a3d91cea"
dependencies = [ dependencies = [
"block-buffer 0.11.0", "block-buffer 0.11.0",
"const-oid", "const-oid 0.10.2",
"crypto-common 0.2.0", "crypto-common 0.2.0",
"subtle", "subtle",
] ]
@@ -901,6 +967,30 @@ dependencies = [
"windows-registry", "windows-registry",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "icu_collections" name = "icu_collections"
version = "2.1.1" version = "2.1.1"
@@ -1082,7 +1172,7 @@ dependencies = [
"pbkdf2", "pbkdf2",
"plist", "plist",
"plist-macro", "plist-macro",
"rand", "rand 0.10.0",
"rcgen", "rcgen",
"reqwest", "reqwest",
"rootcause", "rootcause",
@@ -1096,7 +1186,7 @@ dependencies = [
"tokio-tungstenite", "tokio-tungstenite",
"tracing", "tracing",
"uuid", "uuid",
"x509-parser", "x509-certificate",
] ]
[[package]] [[package]]
@@ -1403,8 +1493,8 @@ version = "0.8.0-rc.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e"
dependencies = [ dependencies = [
"der", "der 0.8.0-rc.10",
"spki", "spki 0.8.0-rc.4",
] ]
[[package]] [[package]]
@@ -1413,8 +1503,8 @@ version = "0.11.0-rc.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b226d2cc389763951db8869584fd800cbbe2962bf454e2edeb5172b31ee99774" checksum = "b226d2cc389763951db8869584fd800cbbe2962bf454e2edeb5172b31ee99774"
dependencies = [ dependencies = [
"der", "der 0.8.0-rc.10",
"spki", "spki 0.8.0-rc.4",
] ]
[[package]] [[package]]
@@ -1451,7 +1541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63641a86fddf4b5274f31c43734458ec7acd3133016dbaa37e4e247e1e9acd46" checksum = "63641a86fddf4b5274f31c43734458ec7acd3133016dbaa37e4e247e1e9acd46"
dependencies = [ dependencies = [
"cpubits", "cpubits",
"cpufeatures", "cpufeatures 0.2.17",
"universal-hash", "universal-hash",
] ]
@@ -1537,7 +1627,7 @@ dependencies = [
"bytes", "bytes",
"getrandom 0.3.4", "getrandom 0.3.4",
"lru-slab", "lru-slab",
"rand", "rand 0.9.2",
"ring", "ring",
"rustc-hash", "rustc-hash",
"rustls", "rustls",
@@ -1588,6 +1678,17 @@ dependencies = [
"rand_core 0.9.5", "rand_core 0.9.5",
] ]
[[package]]
name = "rand"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
dependencies = [
"chacha20",
"getrandom 0.4.1",
"rand_core 0.10.0",
]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.9.0" version = "0.9.0"
@@ -1598,6 +1699,15 @@ dependencies = [
"rand_core 0.9.5", "rand_core 0.9.5",
] ]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.17",
]
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.9.5" version = "0.9.5"
@@ -1709,15 +1819,15 @@ version = "0.10.0-rc.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b342b99544549f37509ed7fd42b0cea04bfd9ce07c16ca56094cf0fbeefbbcd" checksum = "1b342b99544549f37509ed7fd42b0cea04bfd9ce07c16ca56094cf0fbeefbbcd"
dependencies = [ dependencies = [
"const-oid", "const-oid 0.10.2",
"crypto-bigint", "crypto-bigint",
"crypto-primes", "crypto-primes",
"digest 0.11.0-rc.11", "digest 0.11.0-rc.11",
"pkcs1", "pkcs1",
"pkcs8", "pkcs8",
"rand_core 0.10.0", "rand_core 0.10.0",
"signature", "signature 3.0.0-rc.10",
"spki", "spki 0.8.0-rc.4",
"zeroize", "zeroize",
] ]
@@ -1935,7 +2045,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures 0.2.17",
"digest 0.10.7", "digest 0.10.7",
] ]
@@ -1946,7 +2056,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures 0.2.17",
"digest 0.11.0-rc.11", "digest 0.11.0-rc.11",
] ]
@@ -1965,6 +2075,15 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"rand_core 0.6.4",
]
[[package]] [[package]]
name = "signature" name = "signature"
version = "3.0.0-rc.10" version = "3.0.0-rc.10"
@@ -2003,6 +2122,16 @@ dependencies = [
"windows-sys 0.60.2", "windows-sys 0.60.2",
] ]
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der 0.7.10",
]
[[package]] [[package]]
name = "spki" name = "spki"
version = "0.8.0-rc.4" version = "0.8.0-rc.4"
@@ -2010,7 +2139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80"
dependencies = [ dependencies = [
"base64ct", "base64ct",
"der", "der 0.8.0-rc.10",
] ]
[[package]] [[package]]
@@ -2389,7 +2518,7 @@ dependencies = [
"http", "http",
"httparse", "httparse",
"log", "log",
"rand", "rand 0.9.2",
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"sha1", "sha1",
@@ -2676,6 +2805,41 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.2.1" version = "0.2.1"
@@ -3036,6 +3200,25 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[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.18",
"zeroize",
]
[[package]] [[package]]
name = "x509-parser" name = "x509-parser"
version = "0.18.1" version = "0.18.1"

View File

@@ -6,6 +6,12 @@ A Rust library for sideloading iOS applications using an Apple ID. Used in [Cros
This branch is home to isideload-next, the next major version of isideload. It features a redesigned API, improved error handling, better entitlement handling, and more. It is not ready! This branch is home to isideload-next, the next major version of isideload. It features a redesigned API, improved error handling, better entitlement handling, and more. It is not ready!
## TODO
- [ ] Signing apps
- [ ] Installing apps
- [ ] Remove dependency on ring
## Licensing ## Licensing
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

View File

@@ -10,4 +10,4 @@ plist-macro = "0.1.3"
tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros"] }
tracing = "0.1.44" tracing = "0.1.44"
tracing-subscriber = "0.3.22" tracing-subscriber = "0.3.22"
idevice = { version = "0.1.52", features = ["usbmuxd"]} idevice = { version = "0.1.52", features = ["usbmuxd"] }

View File

@@ -42,10 +42,10 @@ async fn main() {
.login(apple_password, get_2fa_code) .login(apple_password, get_2fa_code)
.await; .await;
match &account { // match &account {
Ok(a) => println!("Logged in. {}", a), // Ok(a) => println!("Logged in. {}", a),
Err(e) => panic!("Failed to log in to Apple ID: {:?}", e), // Err(e) => panic!("Failed to log in to Apple ID: {:?}", e),
} // }
let mut account = account.unwrap(); let mut account = account.unwrap();
@@ -70,7 +70,7 @@ async fn main() {
.unwrap() .unwrap()
.to_provider(UsbmuxdAddr::from_env_var().unwrap(), "isideload-demo"); .to_provider(UsbmuxdAddr::from_env_var().unwrap(), "isideload-demo");
let mut sideloader = SideloaderBuilder::new(dev_session) let mut sideloader = SideloaderBuilder::new(dev_session, apple_id.to_string())
.team_selection(TeamSelection::Prompt(|teams| { .team_selection(TeamSelection::Prompt(|teams| {
println!("Please select a team:"); println!("Please select a team:");
for (index, team) in teams.iter().enumerate() { for (index, team) in teams.iter().enumerate() {

View File

@@ -23,7 +23,7 @@ reqwest = { version = "0.13.1", features = ["json", "gzip"] }
thiserror = "2.0.17" thiserror = "2.0.17"
async-trait = "0.1.89" async-trait = "0.1.89"
serde = "1.0.228" serde = "1.0.228"
rand = "0.9.2" rand = "0.10.0"
uuid = {version = "1.20.0", features = ["v4"] } uuid = {version = "1.20.0", features = ["v4"] }
tracing = "0.1.44" tracing = "0.1.44"
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-webpki-roots"] } tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-webpki-roots"] }
@@ -42,5 +42,5 @@ aes-gcm = "0.11.0-rc.3"
rsa = { version = "0.10.0-rc.15" } rsa = { version = "0.10.0-rc.15" }
tokio = "1.49.0" tokio = "1.49.0"
keyring = { version = "3.6.3", features = ["apple-native", "linux-native-sync-persistent", "windows-native"], optional = true } keyring = { version = "3.6.3", features = ["apple-native", "linux-native-sync-persistent", "windows-native"], optional = true }
x509-parser = "0.18.1" x509-certificate = "0.25"
rcgen = { version = "0.14.7", default-features = false, features = ["aws_lc_rs", "pem"] } rcgen = { version = "0.14.7", default-features = false, features = ["aws_lc_rs", "pem"] }

View File

@@ -1,7 +1,7 @@
// Serialization/Desieralization borrowed from https://github.com/SideStore/apple-private-apis/blob/master/omnisette/src/remote_anisette_v3.rs // Serialization/Desieralization borrowed from https://github.com/SideStore/apple-private-apis/blob/master/omnisette/src/remote_anisette_v3.rs
use plist::Data; use plist::Data;
use rand::Rng; use rand::RngExt;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use uuid::Uuid; use uuid::Uuid;

View File

@@ -26,32 +26,58 @@ impl Display for TeamSelection {
} }
} }
pub enum MaxCertsBehavior {
/// If the maximum number of certificates is reached, delete all existing certificates and create a new one
Revoke,
/// If the maximum number of certificates is reached, return an error instead of creating a new certificate
Error,
}
pub struct SideloaderBuilder { pub struct SideloaderBuilder {
team_selection: TeamSelection,
storage: Box<dyn SideloadingStorage>,
developer_session: DeveloperSession, developer_session: DeveloperSession,
apple_email: String,
team_selection: Option<TeamSelection>,
max_certs_behavior: Option<MaxCertsBehavior>,
storage: Option<Box<dyn SideloadingStorage>>,
machine_name: Option<String>,
} }
impl SideloaderBuilder { impl SideloaderBuilder {
pub fn new(developer_session: DeveloperSession) -> Self { pub fn new(developer_session: DeveloperSession, apple_email: String) -> Self {
SideloaderBuilder { SideloaderBuilder {
team_selection: TeamSelection::First, team_selection: None,
storage: Box::new(crate::util::storage::new_storage()), storage: None,
developer_session, developer_session,
machine_name: None,
apple_email,
max_certs_behavior: None,
} }
} }
pub fn team_selection(mut self, selection: TeamSelection) -> Self { pub fn team_selection(mut self, selection: TeamSelection) -> Self {
self.team_selection = selection; self.team_selection = Some(selection);
self self
} }
pub fn storage(mut self, storage: Box<dyn SideloadingStorage>) -> Self { pub fn storage(mut self, storage: Box<dyn SideloadingStorage>) -> Self {
self.storage = storage; self.storage = Some(storage);
self
}
pub fn machine_name(mut self, machine_name: String) -> Self {
self.machine_name = Some(machine_name);
self self
} }
pub fn build(self) -> Sideloader { pub fn build(self) -> Sideloader {
Sideloader::new(self.team_selection, self.storage, self.developer_session) Sideloader::new(
self.developer_session,
self.apple_email,
self.team_selection.unwrap_or(TeamSelection::First),
self.max_certs_behavior.unwrap_or(MaxCertsBehavior::Revoke),
self.machine_name.unwrap_or_else(|| "isideload".to_string()),
self.storage
.unwrap_or_else(|| Box::new(crate::util::storage::new_storage())),
)
} }
} }

View File

@@ -1,22 +1,27 @@
use rcgen::{CertificateParams, DistinguishedName, DnType, PKCS_RSA_SHA256}; use rcgen::{CertificateParams, DistinguishedName, DnType, KeyPair, PKCS_RSA_SHA256};
use rootcause::prelude::*; use rootcause::prelude::*;
use rsa::{ use rsa::{
RsaPrivateKey, RsaPrivateKey,
pkcs8::{DecodePrivateKey, EncodePublicKey}, pkcs1::EncodeRsaPublicKey,
pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding},
}; };
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use tracing::{error, info}; use tracing::{error, info};
use x509_certificate::X509Certificate;
use crate::{ use crate::{
dev::{ dev::{
certificates::CertificatesApi, developer_session::DeveloperSession, teams::DeveloperTeam, certificates::CertificatesApi, developer_session::DeveloperSession, teams::DeveloperTeam,
}, },
sideload::builder::MaxCertsBehavior,
util::storage::SideloadingStorage, util::storage::SideloadingStorage,
}; };
pub struct CertificateIdentity { pub struct CertificateIdentity {
pub machine_id: String, pub machine_id: String,
pub machine_name: String, pub machine_name: String,
pub certificate: X509Certificate,
} }
impl CertificateIdentity { impl CertificateIdentity {
@@ -26,70 +31,82 @@ impl CertificateIdentity {
developer_session: &mut DeveloperSession, developer_session: &mut DeveloperSession,
team: &DeveloperTeam, team: &DeveloperTeam,
storage: &dyn SideloadingStorage, storage: &dyn SideloadingStorage,
max_certs_behavior: &MaxCertsBehavior,
) -> Result<Self, Report> { ) -> Result<Self, Report> {
let stored = Self::retrieve_from_storage( let pr = Self::retrieve_private_key(apple_email, storage).await?;
machine_name,
apple_email, let found = Self::find_matching(&pr, machine_name, developer_session, team).await;
developer_session, if let Ok(Some(cert)) = found {
team, info!("Found matching certificate");
storage,
)
.await;
if let Ok(Some(cert)) = stored {
return Ok(cert); return Ok(cert);
} }
if let Err(e) = stored { if let Err(e) = found {
error!("Failed to load stored certificate: {:?}", e); error!("Failed to check for matching certificate: {:?}", e);
} else {
info!("No stored certificate found");
} }
info!("Requesting new certificate");
todo!("generate CSR") Self::request_certificate(
&pr,
machine_name.to_string(),
developer_session,
team,
max_certs_behavior,
)
.await
} }
async fn retrieve_from_storage( async fn retrieve_private_key(
machine_name: &str,
apple_email: &str, apple_email: &str,
developer_session: &mut DeveloperSession,
team: &DeveloperTeam,
storage: &dyn SideloadingStorage, storage: &dyn SideloadingStorage,
) -> Result<Option<Self>, Report> { ) -> Result<RsaPrivateKey, Report> {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(apple_email.as_bytes()); hasher.update(apple_email.as_bytes());
let email_hash = hex::encode(hasher.finalize()); let email_hash = hex::encode(hasher.finalize());
let private_key = storage.retrieve(&format!("{}/key", email_hash))?; let private_key = storage.retrieve(&format!("{}/key", email_hash))?;
if private_key.is_none() { if private_key.is_some() {
return Ok(None); return Ok(RsaPrivateKey::from_pkcs8_pem(&private_key.unwrap())?);
} }
let private_key = RsaPrivateKey::from_pkcs8_pem(&private_key.unwrap())?;
let public_key_der = private_key.to_public_key().to_public_key_der()?;
let mut rng = rand::rng();
let private_key = RsaPrivateKey::new(&mut rng, 2048)?;
storage.store(
&format!("{}/key", email_hash),
&private_key.to_pkcs8_pem(Default::default())?.to_string(),
)?;
Ok(private_key)
}
async fn find_matching(
private_key: &RsaPrivateKey,
machine_name: &str,
developer_session: &mut DeveloperSession,
team: &DeveloperTeam,
) -> Result<Option<Self>, Report> {
let public_key_der = private_key
.to_public_key()
.to_pkcs1_der()?
.as_bytes()
.to_vec();
for cert in developer_session for cert in developer_session
.list_ios_certs(team) .list_ios_certs(team)
.await? .await?
.iter() .iter()
.filter(|c| { .filter(|c| {
c.cert_content.is_some() && c.machine_name.as_deref().unwrap_or("") == machine_name c.cert_content.is_some()
&& c.machine_name.as_deref().unwrap_or("") == machine_name
&& c.machine_id.is_some()
}) })
{ {
let x509_cert = let x509_cert =
x509_parser::parse_x509_certificate(cert.cert_content.as_ref().unwrap().as_ref())?; X509Certificate::from_der(cert.cert_content.as_ref().unwrap().as_ref())?;
if x509_cert
.1 if public_key_der == x509_cert.public_key_data().as_ref() {
.tbs_certificate
.subject_pki
.subject_public_key
.data
== public_key_der.as_ref()
{
return Ok(Some(Self { return Ok(Some(Self {
machine_id: cert machine_id: cert.machine_id.clone().unwrap_or_default(),
.machine_id machine_name: cert.machine_name.clone().unwrap_or_default(),
.clone() certificate: x509_cert,
.unwrap_or_else(|| x509_cert.1.tbs_certificate.subject.to_string()),
machine_name: machine_name.to_string(),
})); }));
} }
} }
@@ -98,16 +115,46 @@ impl CertificateIdentity {
} }
async fn request_certificate( async fn request_certificate(
machine_name: &str, private_key: &RsaPrivateKey,
machine_name: String,
developer_session: &mut DeveloperSession, developer_session: &mut DeveloperSession,
team: &DeveloperTeam, team: &DeveloperTeam,
max_certs_behavior: &MaxCertsBehavior,
) -> Result<Self, Report> { ) -> Result<Self, Report> {
Ok(()) let csr = Self::build_csr(private_key).context("Failed to generate CSR")?;
let request = developer_session
.submit_development_csr(team, csr, machine_name, None)
.await?;
// TODO: Handle max certs behavior properly instead of just always revoking
let apple_certs = developer_session.list_ios_certs(team).await?;
let apple_cert = apple_certs
.iter()
.find(|c| c.certificate_id == Some(request.cert_request_id.clone()))
.ok_or_else(|| report!("Failed to find certificate after submitting CSR"))?;
let x509_cert = X509Certificate::from_der(
apple_cert
.cert_content
.as_ref()
.ok_or_else(|| report!("Certificate content missing"))?
.as_ref(),
)?;
Ok(Self {
machine_id: apple_cert.machine_id.clone().unwrap_or_default(),
machine_name: apple_cert.machine_name.clone().unwrap_or_default(),
certificate: x509_cert,
})
} }
fn build_csr(private_key: &RsaPrivateKey) -> Result<String, Report> { fn build_csr(private_key: &RsaPrivateKey) -> Result<String, Report> {
let mut params = CertificateParams::new(vec![])?; let mut params = CertificateParams::new(vec![])?;
let mut dn = DistinguishedName::new(); let mut dn = DistinguishedName::new();
dn.push(DnType::CountryName, "US"); dn.push(DnType::CountryName, "US");
dn.push(DnType::StateOrProvinceName, "STATE"); dn.push(DnType::StateOrProvinceName, "STATE");
dn.push(DnType::LocalityName, "LOCAL"); dn.push(DnType::LocalityName, "LOCAL");
@@ -115,6 +162,11 @@ impl CertificateIdentity {
dn.push(DnType::CommonName, "CN"); dn.push(DnType::CommonName, "CN");
params.distinguished_name = dn; params.distinguished_name = dn;
Ok(()) let subject_key = KeyPair::from_pkcs8_pem_and_sign_algo(
&private_key.to_pkcs8_pem(LineEnding::LF)?,
&PKCS_RSA_SHA256,
)?;
Ok(params.serialize_request(&subject_key)?.pem()?)
} }
} }

View File

@@ -4,7 +4,7 @@ use crate::{
devices::DevicesApi, devices::DevicesApi,
teams::{DeveloperTeam, TeamsApi}, teams::{DeveloperTeam, TeamsApi},
}, },
sideload::TeamSelection, sideload::{TeamSelection, builder::MaxCertsBehavior, certificate::CertificateIdentity},
util::{device::IdeviceInfo, storage::SideloadingStorage}, util::{device::IdeviceInfo, storage::SideloadingStorage},
}; };
@@ -18,18 +18,27 @@ pub struct Sideloader {
team_selection: TeamSelection, team_selection: TeamSelection,
storage: Box<dyn SideloadingStorage>, storage: Box<dyn SideloadingStorage>,
dev_session: DeveloperSession, dev_session: DeveloperSession,
machine_name: String,
apple_email: String,
max_certs_behavior: MaxCertsBehavior,
} }
impl Sideloader { impl Sideloader {
pub fn new( pub fn new(
team_selection: TeamSelection,
storage: Box<dyn SideloadingStorage>,
dev_session: DeveloperSession, dev_session: DeveloperSession,
apple_email: String,
team_selection: TeamSelection,
max_certs_behavior: MaxCertsBehavior,
machine_name: String,
storage: Box<dyn SideloadingStorage>,
) -> Self { ) -> Self {
Sideloader { Sideloader {
team_selection, team_selection,
storage, storage,
dev_session, dev_session,
machine_name,
apple_email,
max_certs_behavior,
} }
} }
@@ -46,6 +55,21 @@ impl Sideloader {
.ensure_device_registered(&team, &device_info.name, &device_info.udid, None) .ensure_device_registered(&team, &device_info.name, &device_info.udid, None)
.await?; .await?;
let cert_identity = CertificateIdentity::retrieve(
&self.machine_name,
&self.apple_email,
&mut self.dev_session,
&team,
self.storage.as_ref(),
&self.max_certs_behavior,
)
.await?;
// info!(
// "Using certificate for machine {} with ID {}",
// cert_identity.machine_name, cert_identity.machine_id
// );
Ok(()) Ok(())
} }

View File

@@ -1,6 +1,5 @@
use std::{collections::HashMap, sync::Mutex}; use std::{collections::HashMap, sync::Mutex};
use base64::prelude::*;
use rootcause::prelude::*; use rootcause::prelude::*;
pub trait SideloadingStorage: Send + Sync { pub trait SideloadingStorage: Send + Sync {