apksigner 1.0.0

APK signer and verifier CLI — a faithful Rust port of AOSP apksigner
//! A minimal stand-in for `java.security.Security`.
//!
//! `apksigner --provider-class` exists so that a `java.security.Provider` implementation can be
//! registered with the JVM's crypto framework, which then makes that provider's algorithms
//! available to every later `Signature.getInstance` call.
//!
//! Rust has no such framework — the signing path is compiled against RustCrypto and has no
//! pluggable algorithm registry — so the *effect* of the flag cannot be reproduced. What can be
//! reproduced is the observable state the flag manipulates: a priority-ordered list of installed
//! providers with the add/insert behaviour of `Security.addProvider` and
//! `Security.insertProviderAt`. That is what this module implements, so the flag is a real
//! operation with a real result instead of a placeholder.
//!
//! Documented differences from the JVM:
//!
//! * **No class loading.** Java does `Class.forName(class_name)` and rejects a name that does
//!   not resolve, or that is not a `java.security.Provider` subclass. Rust cannot load a JVM
//!   class, so a class name Java would reject is accepted here.
//! * **Provider name.** Java takes the name from `Provider.getName()`, which the provider class
//!   defines and which need not match the class name. Without loading the class the name cannot
//!   be known, so the class's simple name is used (`org.conscrypt.OpenSSLProvider` →
//!   `OpenSSLProvider`).
//! * **No consumers.** Nothing in the signing path reads this list, because the crypto backend
//!   is fixed at compile time.

use std::sync::{Mutex, OnceLock};

/// An installed JCA provider, mirroring the parts of `java.security.Provider` that
/// `--provider-class` / `--provider-arg` can set.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstalledProvider {
    /// The provider's name. Java's `Provider.getName()`; approximated from the class name.
    pub name: String,
    /// Fully-qualified class name passed to `--provider-class`.
    pub class_name: String,
    /// Optional `--provider-arg`, handed to the single-argument constructor or `configure`.
    pub constructor_param: Option<String>,
}

impl InstalledProvider {
    pub fn new(
        class_name: impl Into<String>,
        constructor_param: Option<String>,
    ) -> InstalledProvider {
        let class_name = class_name.into();
        InstalledProvider {
            name: provider_name_from_class_name(&class_name),
            class_name,
            constructor_param,
        }
    }
}

/// Derives a provider name from a class name the way Java convention usually lines them up:
/// the class's simple name. `com.android.apksig.kms.aws.AwsSignerEngineProvider` →
/// `AwsSignerEngineProvider`.
pub fn provider_name_from_class_name(class_name: &str) -> String {
    match class_name.rsplit('.').next() {
        Some(simple) if !simple.is_empty() => simple.to_string(),
        _ => class_name.to_string(),
    }
}

fn registry() -> &'static Mutex<Vec<InstalledProvider>> {
    static REGISTRY: OnceLock<Mutex<Vec<InstalledProvider>>> = OnceLock::new();
    REGISTRY.get_or_init(|| Mutex::new(Vec::new()))
}

fn lock() -> std::sync::MutexGuard<'static, Vec<InstalledProvider>> {
    match registry().lock() {
        Ok(guard) => guard,
        Err(poisoned) => poisoned.into_inner(),
    }
}

/// Mirrors `Security.addProvider(Provider)`.
///
/// Appends `provider` at the end of the list. Returns `false` if a provider of the same name is
/// already installed, in which case the list is left alone — Java signals that case by returning
/// `null`.
pub fn add_provider(provider: InstalledProvider) -> bool {
    let mut providers = lock();
    if providers.iter().any(|p| p.name == provider.name) {
        return false;
    }
    providers.push(provider);
    true
}

/// Mirrors `Security.insertProviderAt(Provider, int)`.
///
/// `position` is 1-based, 1 being the highest priority. An already-installed provider of the
/// same name is removed first, and an out-of-range position is clamped into the list. Returns
/// the 1-based position the provider ended up at.
pub fn insert_provider_at(provider: InstalledProvider, position: i32) -> i32 {
    let mut providers = lock();
    if let Some(index) = providers.iter().position(|p| p.name == provider.name) {
        providers.remove(index);
    }
    // Java clamps rather than rejecting: below the start goes to the front, past the end goes to
    // the back of the list.
    let position = position.max(1) as usize;
    let index = (position - 1).min(providers.len());
    providers.insert(index, provider);
    (index + 1) as i32
}

/// Returns the installed providers in priority order.
///
/// Mirrors `Security.getProviders()`. `apksigner` never reads the list back -- Java's
/// `ProviderInstallSpec` only ever adds, and nothing prints the registry -- so this exists for
/// the registry's own tests rather than for the CLI.
#[cfg(test)]
pub fn installed_providers() -> Vec<InstalledProvider> {
    lock().clone()
}

/// Empties the list.
///
/// As with [`installed_providers`], nothing in the CLI removes a provider; only the tests need a
/// way to reset the process-global registry between cases.
#[cfg(test)]
pub fn clear_providers() {
    lock().clear();
}

#[cfg(test)]
mod tests {
    use super::*;

    fn provider(class_name: &str) -> InstalledProvider {
        InstalledProvider::new(class_name, None)
    }

    /// The provider list is process-global, so the tests that mutate it are serialised against
    /// each other rather than racing under the default parallel test harness.
    fn test_lock() -> std::sync::MutexGuard<'static, ()> {
        static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
        match LOCK.lock() {
            Ok(guard) => guard,
            Err(poisoned) => poisoned.into_inner(),
        }
    }

    #[test]
    fn the_name_is_the_class_simple_name() {
        assert_eq!(
            provider_name_from_class_name("org.conscrypt.OpenSSLProvider"),
            "OpenSSLProvider"
        );
        assert_eq!(
            provider_name_from_class_name("NoPackageProvider"),
            "NoPackageProvider"
        );
        assert_eq!(provider_name_from_class_name(""), "");
    }

    #[test]
    fn add_appends_and_refuses_a_duplicate_name() {
        let _guard = test_lock();
        clear_providers();
        assert!(add_provider(provider("a.A")));
        assert!(add_provider(provider("b.B")));
        assert!(!add_provider(provider("c.A")), "same simple name");
        let names: Vec<String> = installed_providers().into_iter().map(|p| p.name).collect();
        assert_eq!(names, vec!["A", "B"]);
        clear_providers();
    }

    #[test]
    fn insert_places_at_a_one_based_position() {
        let _guard = test_lock();
        clear_providers();
        add_provider(provider("a.A"));
        add_provider(provider("b.B"));
        add_provider(provider("c.C"));
        assert_eq!(insert_provider_at(provider("d.D"), 1), 1);
        let names: Vec<String> = installed_providers().into_iter().map(|p| p.name).collect();
        assert_eq!(names, vec!["D", "A", "B", "C"]);
        clear_providers();
    }

    #[test]
    fn insert_clamps_an_out_of_range_position() {
        let _guard = test_lock();
        clear_providers();
        add_provider(provider("a.A"));
        // Below the start: front of the list.
        assert_eq!(insert_provider_at(provider("b.B"), 0), 1);
        // Past the end: back of the list.
        assert_eq!(insert_provider_at(provider("c.C"), 99), 3);
        let names: Vec<String> = installed_providers().into_iter().map(|p| p.name).collect();
        assert_eq!(names, vec!["B", "A", "C"]);
        clear_providers();
    }

    #[test]
    fn insert_replaces_an_already_installed_provider() {
        let _guard = test_lock();
        clear_providers();
        add_provider(provider("a.A"));
        add_provider(provider("b.B"));
        assert_eq!(insert_provider_at(provider("c.A"), 2), 2);
        let names: Vec<String> = installed_providers().into_iter().map(|p| p.name).collect();
        assert_eq!(names, vec!["B", "A"]);
        clear_providers();
    }
}