Git · dadoum.zone
Repositories
Help
Report an Issue
apple-crates
Code
Commits
Branches
Tags
Search
Tree:
ccef2d0
Branches
Tags
main
apple-crates
super-sideloader
src
backend
developer
certificates.rs
Refactor super-sideloader code and finish up the UI/UX
Dadoum
commited
ccef2d0
at 2026-07-09 18:05:35
certificates.rs
Blame
History
Raw
use crate::backend::paths::app_data_dir; use crate::backend::{BackendError, BackendResult}; use sha1::Digest as _; use std::fs; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use uuid::Uuid; #[derive(Clone, Debug)] pub(crate) struct GeneratedCertificateSigningRequest { pub(crate) machine_id: String, pub(crate) machine_name: String, pub(crate) csr_content: String, pub(crate) public_key_fingerprint: String, pub(crate) private_key_pem: Vec<u8>, } pub(crate) fn generate_development_certificate_signing_request( ) -> BackendResult<GeneratedCertificateSigningRequest> { let machine_id = Uuid::new_v4().to_string().to_uppercase(); let machine_name = "Super Sideloader".to_string(); let temp_dir = certificate_temp_dir()?; fs::create_dir_all(&temp_dir).map_err(|source| BackendError::Io { action: "Create certificate work folder", path: temp_dir.clone(), source, })?; let file_id = Uuid::new_v4().to_string(); let key_path = temp_dir.join(format!("{file_id}.key.pem")); let csr_path = temp_dir.join(format!("{file_id}.csr.pem")); let subject = format!("/CN={machine_name}"); let mut generate = Command::new("openssl"); generate .args(["req", "-new", "-newkey", "rsa:2048", "-nodes"]) .arg("-keyout") .arg(&key_path) .arg("-out") .arg(&csr_path) .arg("-subj") .arg(subject) .arg("-batch"); run_command(generate, "generate a development certificate CSR")?; let private_key_pem = fs::read(&key_path).map_err(|source| BackendError::Io { action: "Read generated private key", path: key_path.clone(), source, })?; let csr_content = fs::read_to_string(&csr_path).map_err(|source| BackendError::Io { action: "Read generated CSR", path: csr_path.clone(), source, })?; let public_key_der = public_key_der_from_private_key(&key_path)?; let public_key_fingerprint = certificate_fingerprint(&public_key_der); let _ = fs::remove_file(&key_path); let _ = fs::remove_file(&csr_path); Ok(GeneratedCertificateSigningRequest { machine_id, machine_name, csr_content, public_key_fingerprint, private_key_pem, }) } pub(crate) fn import_app_managed_private_key( certificate_id: &str, expected_public_key_fingerprint: &str, private_key_path: &Path, ) -> BackendResult<()> { let private_key_pem = fs::read(private_key_path).map_err(|source| BackendError::Io { action: "Read private key", path: private_key_path.to_path_buf(), source, })?; let public_key_der = public_key_der_from_private_key(private_key_path)?; let public_key_fingerprint = certificate_fingerprint(&public_key_der); if !public_key_fingerprint.eq_ignore_ascii_case(expected_public_key_fingerprint) { return Err(BackendError::Keychain(format!( "The selected PEM private key does not match certificate {certificate_id}." ))); } save_app_managed_private_key(&public_key_fingerprint, &private_key_pem) } pub(crate) fn certificate_fingerprint(contents: &[u8]) -> String { sha1::Sha1::digest(contents) .iter() .map(|byte| format!("{byte:02X}")) .collect() } pub(crate) fn certificate_public_key_fingerprint(certificate_der: &[u8]) -> Option<String> { certificate_public_key_der(certificate_der) .ok() .map(|public_key_der| certificate_fingerprint(&public_key_der)) } pub(crate) fn app_managed_private_key_fingerprints() -> Vec<String> { let Ok(keys_dir) = certificate_keys_dir() else { return Vec::new(); }; let Ok(entries) = fs::read_dir(keys_dir) else { return Vec::new(); }; entries .filter_map(Result::ok) .filter_map(|entry| { entry .path() .file_stem() .and_then(|stem| stem.to_str()) .map(str::to_ascii_uppercase) }) .filter(|fingerprint| { fingerprint.len() == 40 && fingerprint.bytes().all(|byte| byte.is_ascii_hexdigit()) }) .collect() } #[cfg(target_os = "macos")] pub(crate) fn local_code_signing_identity_fingerprints() -> Vec<String> { let output = match Command::new("security") .args(["find-identity", "-v", "-p", "codesigning"]) .output() { Ok(output) if output.status.success() => output, _ => return Vec::new(), }; String::from_utf8_lossy(&output.stdout) .split_whitespace() .filter_map(|part| { let fingerprint = part.trim(); (fingerprint.len() == 40 && fingerprint.bytes().all(|byte| byte.is_ascii_hexdigit())) .then(|| fingerprint.to_ascii_uppercase()) }) .collect() } #[cfg(not(target_os = "macos"))] pub(crate) fn local_code_signing_identity_fingerprints() -> Vec<String> { Vec::new() } fn public_key_der_from_private_key(key_path: &Path) -> BackendResult<Vec<u8>> { let mut command = Command::new("openssl"); command .args(["pkey", "-in"]) .arg(key_path) .args(["-pubout", "-outform", "DER"]); command_output(command, "extract the private key public key") } fn certificate_public_key_der(certificate_der: &[u8]) -> BackendResult<Vec<u8>> { let temp_dir = certificate_temp_dir()?; fs::create_dir_all(&temp_dir).map_err(|source| BackendError::Io { action: "Create certificate work folder", path: temp_dir.clone(), source, })?; let file_id = Uuid::new_v4().to_string(); let certificate_path = temp_dir.join(format!("{file_id}.cer")); let public_key_path = temp_dir.join(format!("{file_id}.pub.pem")); fs::write(&certificate_path, certificate_der).map_err(|source| BackendError::Io { action: "Stage certificate", path: certificate_path.clone(), source, })?; let mut extract_public_key = Command::new("openssl"); extract_public_key .args(["x509", "-inform", "DER", "-in"]) .arg(&certificate_path) .args(["-pubkey", "-noout", "-out"]) .arg(&public_key_path); let result = run_command(extract_public_key, "extract the certificate public key").and_then(|_| { let mut convert_public_key = Command::new("openssl"); convert_public_key .args(["pkey", "-pubin", "-in"]) .arg(&public_key_path) .args(["-outform", "DER"]); command_output(convert_public_key, "encode the certificate public key") }); let _ = fs::remove_file(&certificate_path); let _ = fs::remove_file(&public_key_path); result } pub(crate) fn save_app_managed_private_key( fingerprint: &str, private_key_pem: &[u8], ) -> BackendResult<()> { let keys_dir = certificate_keys_dir()?; fs::create_dir_all(&keys_dir).map_err(|source| BackendError::Io { action: "Create certificate key folder", path: keys_dir.clone(), source, })?; let key_path = keys_dir.join(format!("{fingerprint}.pem")); fs::write(&key_path, private_key_pem).map_err(|source| BackendError::Io { action: "Save certificate private key", path: key_path, source, }) } fn certificate_keys_dir() -> BackendResult<PathBuf> { app_data_dir() .map(|path| path.join("certificates").join("keys")) .ok_or_else(|| { BackendError::Unsupported("The application data folder is not available.".to_string()) }) } fn certificate_temp_dir() -> BackendResult<PathBuf> { app_data_dir() .map(|path| path.join("certificates").join("tmp")) .ok_or_else(|| { BackendError::Unsupported("The application data folder is not available.".to_string()) }) } fn run_command(mut command: Command, action: &'static str) -> BackendResult<()> { let output = command .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::piped()) .output() .map_err(|source| BackendError::Command { action, source })?; if output.status.success() { return Ok(()); } Err(command_error(action, &output.stderr)) } fn command_output(mut command: Command, action: &'static str) -> BackendResult<Vec<u8>> { let output = command .stdin(Stdio::null()) .stderr(Stdio::piped()) .output() .map_err(|source| BackendError::Command { action, source })?; if output.status.success() { return Ok(output.stdout); } Err(command_error(action, &output.stderr)) } fn command_error(action: &'static str, stderr: &[u8]) -> BackendError { let stderr = String::from_utf8_lossy(stderr).trim().to_string(); let detail = if stderr.is_empty() { "Process exited with a non-zero status.".to_string() } else { stderr }; BackendError::CommandFailed { action, detail } }