rust-apk 1.0.0

Build, align, sign, verify, and inspect Android APKs in pure Rust
Documentation
//! APK v1/v2/v3 signing, verification, and identity loading.

mod block;
mod certificate;
mod error;
mod identity;
mod jar;
mod key;
#[cfg(feature = "keystore")]
mod keystore;
mod scheme;
mod signer;
mod verify;

pub use crate::digest::DigestAlgorithm;
pub use certificate::Certificate;
pub use error::{KeyError, SignError, VerifyError};
pub use identity::SigningIdentity;
pub use jar::JarError;
pub use key::{KeyKind, MIN_RSA_SIGNING_BITS, PrivateKey, PublicKey, SignatureAlgorithm, SignatureError};
#[cfg(feature = "keystore")]
pub use keystore::Keystore;
pub use signer::Signer;
pub use verify::{Verification, VerifiedSigner, verify, verify_bytes};

use crate::types::ApiLevel;
use bitflags::bitflags;
pub use block::{BlockId, SigningBlock};
use std::fmt;
/// One APK signature scheme.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Scheme {
    /// JAR signing, verified by every Android release.
    V1,
    /// APK Signature Scheme v2, verified from Android 7.0 (API 24).
    V2,
    /// APK Signature Scheme v3, verified from Android 9 (API 28).
    V3,
}

impl Scheme {
    const ALL: [Self; 3] = [Self::V1, Self::V2, Self::V3];

    /// The scheme number used by stripping protection markers.
    pub const fn number(self) -> u32 {
        match self {
            Self::V1 => 1,
            Self::V2 => 2,
            Self::V3 => 3,
        }
    }

    /// The first API level that verifies this scheme.
    pub const fn min_sdk(self) -> ApiLevel {
        match self {
            Self::V1 => ApiLevel::BASE,
            Self::V2 => ApiLevel::N,
            Self::V3 => ApiLevel::P,
        }
    }

    fn from_number(number: u32) -> Option<Self> {
        Self::ALL.into_iter().find(|s| s.number() == number)
    }

    fn block_id(self) -> Option<BlockId> {
        match self {
            Self::V1 => None,
            Self::V2 => Some(BlockId::V2),
            Self::V3 => Some(BlockId::V3),
        }
    }

    fn flag(self) -> Schemes {
        match self {
            Self::V1 => Schemes::V1,
            Self::V2 => Schemes::V2,
            Self::V3 => Schemes::V3,
        }
    }
}

impl fmt::Display for Scheme {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Self::V1 => "JAR signature (v1)",
            Self::V2 => "APK Signature Scheme v2",
            Self::V3 => "APK Signature Scheme v3",
        })
    }
}

bitflags! {
    /// A set of signature schemes.
    #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
    pub struct Schemes: u8 {
        const V1 = 1 << 0;
        const V2 = 1 << 1;
        const V3 = 1 << 2;
    }
}

impl Schemes {
    /// The schemes `apksigner` uses by default: v2 and v3, plus v1 when the
    /// app supports releases older than Android 7.0.
    pub fn for_min_sdk(min_sdk: ApiLevel) -> Self {
        if min_sdk < ApiLevel::N { Self::V1 | Self::V2 | Self::V3 } else { Self::V2 | Self::V3 }
    }

    /// The schemes in this set, weakest first.
    pub fn iter_schemes(self) -> impl Iterator<Item = Scheme> {
        Scheme::ALL.into_iter().filter(move |s| self.contains(s.flag()))
    }

    /// Checks that an app with `min_sdk` and `target_sdk` can rely on these
    /// schemes: every release it supports verifies at least one, and modern
    /// targets have a v2 or v3 signature.
    pub fn check_coverage(self, min_sdk: ApiLevel, target_sdk: ApiLevel) -> Result<(), CoverageError> {
        let covered_from = self.iter_schemes().map(Scheme::min_sdk).min().ok_or(CoverageError::NoSchemes)?;
        if covered_from > min_sdk {
            let needed = if min_sdk < ApiLevel::N { Scheme::V1 } else { Scheme::V2 };
            return Err(CoverageError::UnverifiableOnMinSdk { schemes: self, min_sdk, needed });
        }
        if target_sdk >= ApiLevel::R && !self.intersects(Self::V2 | Self::V3) {
            return Err(CoverageError::TargetRequiresV2 { target_sdk });
        }
        Ok(())
    }
}

/// Why a set of signature schemes does not cover an app.
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum CoverageError {
    #[error("no signature schemes were selected")]
    NoSchemes,
    #[error("{schemes:?} cannot be verified by Android {min_sdk}; add {needed}")]
    UnverifiableOnMinSdk { schemes: Schemes, min_sdk: ApiLevel, needed: Scheme },
    #[error("targetSdkVersion {target_sdk} requires APK Signature Scheme v2 or v3")]
    TargetRequiresV2 { target_sdk: ApiLevel },
}