start implementing cert identity

This commit is contained in:
nab138
2026-02-06 11:13:01 -05:00
parent 6d0f644e93
commit a726511630
7 changed files with 245 additions and 40 deletions

103
Cargo.lock generated
View File

@@ -118,6 +118,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.10.0" version = "2.10.0"
@@ -397,6 +403,30 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "der"
version = "0.8.0-rc.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653"
dependencies = [
"const-oid",
"der_derive",
"flagset",
"pem-rfc7468",
"zeroize",
]
[[package]]
name = "der_derive"
version = "0.8.0-rc.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be645fee2afe89d293b96c19e4456e6ac69520fc9c6b8a58298550138e361ffe"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.5.5" version = "0.5.5"
@@ -466,6 +496,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "flagset"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.9" version = "1.1.9"
@@ -1004,6 +1040,7 @@ dependencies = [
"rootcause", "rootcause",
"serde", "serde",
"serde_json", "serde_json",
"sha1 0.11.0-rc.5",
"sha2", "sha2",
"srp", "srp",
"thiserror 2.0.18", "thiserror 2.0.18",
@@ -1011,6 +1048,7 @@ dependencies = [
"tokio-tungstenite", "tokio-tungstenite",
"tracing", "tracing",
"uuid", "uuid",
"x509-cert",
] ]
[[package]] [[package]]
@@ -1224,6 +1262,15 @@ dependencies = [
"hmac", "hmac",
] ]
[[package]]
name = "pem-rfc7468"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
dependencies = [
"base64ct",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.2" version = "2.3.2"
@@ -1723,6 +1770,17 @@ dependencies = [
"digest 0.10.7", "digest 0.10.7",
] ]
[[package]]
name = "sha1"
version = "0.11.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b167252f3c126be0d8926639c4c4706950f01445900c4b3db0fd7e89fcb750a"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.11.0-rc.11",
]
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.11.0-rc.5" version = "0.11.0-rc.5"
@@ -1777,6 +1835,16 @@ dependencies = [
"windows-sys 0.60.2", "windows-sys 0.60.2",
] ]
[[package]]
name = "spki"
version = "0.8.0-rc.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80"
dependencies = [
"base64ct",
"der",
]
[[package]] [[package]]
name = "srp" name = "srp"
version = "0.7.0-rc.1" version = "0.7.0-rc.1"
@@ -1958,6 +2026,27 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tls_codec"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b"
dependencies = [
"tls_codec_derive",
"zeroize",
]
[[package]]
name = "tls_codec_derive"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.49.0" version = "1.49.0"
@@ -2156,7 +2245,7 @@ dependencies = [
"rand", "rand",
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"sha1", "sha1 0.10.6",
"thiserror 2.0.18", "thiserror 2.0.18",
"utf-8", "utf-8",
] ]
@@ -2794,6 +2883,18 @@ 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-cert"
version = "0.3.0-rc.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e21aad3a769f25f3d2d0cbf30ea8b50a1d602354bd6ab687fad112821608ba6"
dependencies = [
"const-oid",
"der",
"spki",
"tls_codec",
]
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.8.1" version = "0.8.1"

View File

@@ -49,7 +49,7 @@ async fn main() {
let mut account = account.unwrap(); let mut account = account.unwrap();
let mut dev_session = DeveloperSession::from_account(&mut account) let dev_session = DeveloperSession::from_account(&mut account)
.await .await
.expect("Failed to create developer session"); .expect("Failed to create developer session");
@@ -70,7 +70,8 @@ async fn main() {
.unwrap() .unwrap()
.to_provider(UsbmuxdAddr::from_env_var().unwrap(), "isideload-demo"); .to_provider(UsbmuxdAddr::from_env_var().unwrap(), "isideload-demo");
let builder = SideloaderBuilder::new().team_selection(TeamSelection::Prompt(|teams| { let mut sideloader = SideloaderBuilder::new(dev_session)
.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() {
println!( println!(
@@ -87,9 +88,10 @@ async fn main() {
return None; return None;
} }
Some(teams[selection - 1].team_id.clone()) Some(teams[selection - 1].team_id.clone())
})); }))
.build();
// let result = bu(&provider, &mut dev_session, app_path, &sideload_config).await; let result = sideloader.install_app(&provider, app_path).await;
match result { match result {
Ok(_) => println!("App installed successfully"), Ok(_) => println!("App installed successfully"),
Err(e) => panic!("Failed to install app: {:?}", e), Err(e) => panic!("Failed to install app: {:?}", e),

View File

@@ -25,7 +25,6 @@ async-trait = "0.1.89"
serde = "1.0.228" serde = "1.0.228"
rand = "0.9.2" rand = "0.9.2"
uuid = {version = "1.20.0", features = ["v4"] } uuid = {version = "1.20.0", features = ["v4"] }
sha2 = "0.11.0-rc.5"
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"] }
rootcause = "0.11.1" rootcause = "0.11.1"
@@ -33,6 +32,8 @@ futures-util = "0.3.31"
serde_json = "1.0.149" serde_json = "1.0.149"
base64 = "0.22.1" base64 = "0.22.1"
hex = "0.4.3" hex = "0.4.3"
sha1 = "0.11.0-rc.5"
sha2 = "0.11.0-rc.5"
srp = "0.7.0-rc.1" srp = "0.7.0-rc.1"
pbkdf2 = "0.13.0-rc.9" pbkdf2 = "0.13.0-rc.9"
hmac = "0.13.0-rc.5" hmac = "0.13.0-rc.5"
@@ -41,3 +42,4 @@ aes = "0.9.0-rc.4"
aes-gcm = "0.11.0-rc.3" aes-gcm = "0.11.0-rc.3"
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-cert = "0.3.0-rc.4"

View File

@@ -97,6 +97,33 @@ pub trait CertificatesApi {
Ok(certs) Ok(certs)
} }
async fn list_ios_certs(
&mut self,
team: &DeveloperTeam,
) -> Result<Vec<DevelopmentCertificate>, Report> {
let certs = self
.list_all_development_certs(team, DeveloperDeviceType::Ios)
.await?;
Ok(certs
.into_iter()
.filter(|c| {
if let Some(platform) = &c.certificate_platform {
platform.to_lowercase() == "ios"
} else if let Some(cert_type) = &c.certificate_type {
if let Some(platform) = &cert_type.platform {
platform.to_lowercase() == "ios"
} else {
// I don't know how consistently these field is populated because apple apis are stupid, and I don't want to break things so just assume
true
}
} else {
true
}
})
.collect())
}
async fn revoke_development_cert( async fn revoke_development_cert(
&mut self, &mut self,
team: &DeveloperTeam, team: &DeveloperTeam,

View File

@@ -1,6 +1,10 @@
use std::fmt::Display; use std::fmt::Display;
use crate::{dev::teams::DeveloperTeam, util::storage::SideloadingStorage}; use crate::{
dev::{developer_session::DeveloperSession, teams::DeveloperTeam},
sideload::sideloader::Sideloader,
util::storage::SideloadingStorage,
};
/// Configuration for selecting a developer team during sideloading /// Configuration for selecting a developer team during sideloading
/// ///
@@ -23,22 +27,18 @@ impl Display for TeamSelection {
} }
pub struct SideloaderBuilder { pub struct SideloaderBuilder {
pub team_selection: TeamSelection, team_selection: TeamSelection,
pub storage: Box<dyn SideloadingStorage>, storage: Box<dyn SideloadingStorage>,
} developer_session: DeveloperSession,
impl Default for SideloaderBuilder {
fn default() -> Self {
SideloaderBuilder {
team_selection: TeamSelection::First,
storage: Box::new(crate::util::storage::new_storage()),
}
}
} }
impl SideloaderBuilder { impl SideloaderBuilder {
pub fn new() -> Self { pub fn new(developer_session: DeveloperSession) -> Self {
Self::default() SideloaderBuilder {
team_selection: TeamSelection::First,
storage: Box::new(crate::util::storage::new_storage()),
developer_session,
}
} }
pub fn team_selection(mut self, selection: TeamSelection) -> Self { pub fn team_selection(mut self, selection: TeamSelection) -> Self {
@@ -50,4 +50,8 @@ impl SideloaderBuilder {
self.storage = storage; self.storage = storage;
self self
} }
pub fn build(self) -> Sideloader {
Sideloader::new(self.team_selection, self.storage, self.developer_session)
}
} }

View File

@@ -1,6 +1,63 @@
use rootcause::prelude::*;
use tracing::{error, info};
use crate::{
dev::{
certificates::CertificatesApi, developer_session::DeveloperSession, teams::DeveloperTeam,
},
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,
} }
impl CertificateIdentity {} impl CertificateIdentity {
pub async fn retrieve(
machine_name: &str,
developer_session: DeveloperSession,
team: &DeveloperTeam,
storage: &dyn SideloadingStorage,
) -> Result<Self, Report> {
let stored =
Self::retrieve_from_storage(machine_name, developer_session, team, storage).await;
if let Ok(Some(cert)) = stored {
return Ok(cert);
}
if let Err(e) = stored {
error!("Failed to load certificate from storage: {:?}", e);
} else {
info!("No stored certificate found, generating");
}
todo!("generate CSR")
}
async fn retrieve_from_storage(
machine_name: &str,
developer_session: DeveloperSession,
team: &DeveloperTeam,
storage: &dyn SideloadingStorage,
) -> Result<Option<Self>, Report> {
let cert = storage.retrieve_data("cert")?;
if cert.is_none() {
return Ok(None);
}
let cert = cert.unwrap();
let private_key = storage.retrieve_data("key")?;
if private_key.is_none() {
return Ok(None);
}
for cert in developer_session
.list_ios_certs(team)
.await?
.iter()
.filter(|c| c.machine_name.unwrap_or_default() == machine_name)
{}
Ok(())
}
}

View File

@@ -15,12 +15,24 @@ use rootcause::prelude::*;
use tracing::info; use tracing::info;
pub struct Sideloader { pub struct Sideloader {
pub team_selection: TeamSelection, team_selection: TeamSelection,
pub storage: Box<dyn SideloadingStorage>, storage: Box<dyn SideloadingStorage>,
pub dev_session: DeveloperSession, dev_session: DeveloperSession,
} }
impl Sideloader { impl Sideloader {
pub fn new(
team_selection: TeamSelection,
storage: Box<dyn SideloadingStorage>,
dev_session: DeveloperSession,
) -> Self {
Sideloader {
team_selection,
storage,
dev_session,
}
}
pub async fn install_app( pub async fn install_app(
&mut self, &mut self,
device_provider: &impl IdeviceProvider, device_provider: &impl IdeviceProvider,