Skip to main content
The Signer struct orchestrates the complete iOS app signing process, including bundle modification, provisioning profile generation, and code signing.

Signer struct

pub struct Signer {
    certificate: Option<CertificateIdentity>,
    pub options: SignerOptions,
    pub provisioning_files: Vec<MobileProvision>,
}
certificate
Option<CertificateIdentity>
Code signing certificate and private key (not needed for adhoc signing)
options
SignerOptions
Configuration for the signing operation
provisioning_files
Vec<MobileProvision>
Provisioning profiles generated or loaded during signing

Creating a signer

use plume_utils::{Signer, SignerOptions};

let options = SignerOptions::default();
let signer = Signer::new(None, options);

Methods

new

pub fn new(
    certificate: Option<CertificateIdentity>,
    options: SignerOptions
) -> Self
Creates a new signer instance with the specified certificate and options.

modify_bundle

pub async fn modify_bundle(
    &mut self,
    bundle: &Bundle,
    team_id: &Option<String>,
) -> Result<(), Error>
Modifies the app bundle according to the signer options. This includes:
  • Changing app name, version, and bundle identifier
  • Setting minimum OS version
  • Enabling features (file sharing, iPad fullscreen, game mode, ProMotion)
  • Injecting custom icons
  • Installing tweaks and ElleKit
  • Embedding certificate for sideloading apps
  • Liquid Glass support (SDK version modification)
let mut signer = Signer::new(None, options);
let bundle = Bundle::new("/path/to/App.app")?;

signer.modify_bundle(&bundle, &Some(team_id)).await?;

register_bundle

pub async fn register_bundle(
    &mut self,
    bundle: &Bundle,
    session: &DeveloperSession,
    team_id: &String,
    is_refresh: bool,
) -> Result<(), Error>
Registers the app bundle with Apple’s Developer API and generates provisioning profiles. This method:
  • Creates app IDs for the main app and extensions
  • Enables required capabilities based on entitlements
  • Creates and assigns app groups
  • Generates provisioning profiles
  • Embeds profiles in the bundle
bundle
&Bundle
The app bundle to register
session
&DeveloperSession
Authenticated developer session
team_id
&String
Apple Developer team ID
is_refresh
bool
Whether this is refreshing an existing app (preserves app group names)
use plume_core::developer::DeveloperSession;

let session = DeveloperSession::using_account(account).await?;
let teams = session.qh_list_teams().await?;
let team_id = &teams.teams[0].team_id;

signer.register_bundle(
    &bundle,
    &session,
    team_id,
    false  // not a refresh
).await?;

sign_bundle

pub async fn sign_bundle(&self, bundle: &Bundle) -> Result<(), Error>
Performs code signing on the bundle and all nested components. Signs in the correct order (deepest bundles first):
  1. Frameworks and dylibs
  2. App extensions
  3. Main app bundle
signer.sign_bundle(&bundle).await?;
println!("Bundle signed successfully");

Signing modes

pub enum SignerMode {
    Pem,    // Sign with Apple Developer account
    Adhoc,  // Adhoc signing (no developer account)
    None,   // No modifications
}

Signing options

pub struct SignerOptions {
    pub custom_name: Option<String>,
    pub custom_identifier: Option<String>,
    pub custom_version: Option<String>,
    pub custom_icon: Option<PathBuf>,
    pub custom_entitlements: Option<PathBuf>,
    pub features: SignerFeatures,
    pub embedding: SignerEmbedding,
    pub mode: SignerMode,
    pub install_mode: SignerInstallMode,
    pub tweaks: Option<Vec<PathBuf>>,
    pub app: SignerApp,
    pub refresh: bool,
}

SignerFeatures

pub struct SignerFeatures {
    pub support_minimum_os_version: bool,
    pub support_file_sharing: bool,
    pub support_ipad_fullscreen: bool,
    pub support_game_mode: bool,
    pub support_pro_motion: bool,
    pub support_liquid_glass: bool,
    pub support_ellekit: bool,
    pub remove_url_schemes: bool,
}

SignerEmbedding

pub struct SignerEmbedding {
    pub single_profile: bool,  // Use one profile for all bundles
}

SignerApp

pub enum SignerApp {
    Default,
    Antrag,
    Feather,
    Protokolle,
    AltStore,
    SideStore,
    LiveContainer,
    LiveContainerAndSideStore,
    StikDebug,
    SparseBox,
    EnsWilde,
    ByeTunes,
    StikStore,
}
Special handling for specific sideloading apps (certificate embedding, pairing file paths, etc.).

Complete example

use plume_core::auth::Account;
use plume_core::developer::DeveloperSession;
use plume_utils::{
    Bundle, Signer, SignerOptions, SignerMode,
    SignerFeatures, SignerApp
};
use omnisette::AnisetteConfiguration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Authenticate
    let account = Account::login(
        || Ok(("[email protected]".to_string(), "password".to_string())),
        || Ok("123456".to_string()),
        AnisetteConfiguration::default()
    ).await?;
    
    // 2. Get developer session and team
    let session = DeveloperSession::using_account(account).await?;
    let teams = session.qh_list_teams().await?;
    let team_id = teams.teams[0].team_id.clone();
    
    // 3. Load bundle
    let bundle = Bundle::new("/path/to/MyApp.app")?;
    
    // 4. Configure options
    let mut options = SignerOptions::default();
    options.mode = SignerMode::Pem;
    options.custom_name = Some("Custom Name".to_string());
    options.custom_version = Some("1.0.0".to_string());
    options.features.support_file_sharing = true;
    options.features.support_pro_motion = true;
    options.app = SignerApp::Default;
    
    // 5. Create signer and sign
    let mut signer = Signer::new(None, options);
    
    // Modify bundle (name, version, features)
    signer.modify_bundle(&bundle, &Some(team_id.clone())).await?;
    
    // Register with Apple and get provisioning profiles
    signer.register_bundle(&bundle, &session, &team_id, false).await?;
    
    // Sign all bundles
    signer.sign_bundle(&bundle).await?;
    
    println!("✓ Bundle signed successfully!");
    
    Ok(())
}

Special app support

The signer has special handling for popular sideloading apps:
let mut options = SignerOptions::new_for_app(SignerApp::SideStore);
// Automatically:
// - Embeds certificate for app refresh
// - Sets single_profile = true
// - Configures pairing file path

Build docs developers (and LLMs) love