use std::io::Write;
use std::path::{Path, PathBuf};
use apksig::apk_signer::{ApkSignerBuilder, ApkSignerError};
use apksig::internal::x509::Certificate as X509Certificate;
use apksig::signing_certificate_lineage::{
LineageError, SignerCapabilities, SignerConfig, SignerConfigBuilder, SigningCertificate,
SigningCertificateLineage, SigningCertificateLineageBuilder,
};
use apksig::util::FileDataSource;
use crate::hex_encoding::HexEncoding;
use crate::options_parser::{OptionsException, OptionsParser};
use crate::parameter_exception::ParameterException;
use crate::password_retriever::{PasswordRetriever, get_charset_by_name};
use crate::signer_params::{SignerLoadError, SignerParams, v1_signature_basename};
pub const VERSION: &str = "0.9";
pub const ZIP_MAGIC: i32 = 0x0403_4b50;
pub const BEGIN_CERTIFICATE: &str = "-----BEGIN CERTIFICATE-----";
pub const END_CERTIFICATE: &str = "-----END CERTIFICATE-----";
pub const SIGNING_CERTIFICATE_LINEAGE_MAGIC: i32 = 0x3eff_39d1;
pub const HELP_PAGE_GENERAL: &str = r#"USAGE: apksigner <command> [options]
apksigner --version
apksigner --help
EXAMPLE:
apksigner sign --ks release.jks app.apk
apksigner verify --verbose app.apk
apksigner is a tool for signing Android APK files and for checking whether
signatures of APK files will verify on Android devices.
COMMANDS
rotate Add a new signing certificate to the SigningCertificateLineage
sign Sign the provided APK
verify Check whether the provided APK is expected to verify on
Android
lineage Modify the capabilities of one or more signers in an existing
SigningCertificateLineage
version Show this tool's version number and exit
help Show this usage page and exit
"#;
pub const HELP_PAGE_VERIFY: &str = r#"USAGE: apksigner verify [options] apk
This checks whether the provided APK will verify on Android. By default, this
checks whether the APK will verify on all Android platform versions supported
by the APK (as declared using minSdkVersion in AndroidManifest.xml). Use
--min-sdk-version and/or --max-sdk-version to verify the APK against a custom
range of API Levels.
OPTIONS
--print-certs Show information about the APK's signing certificates
--print-certs-pem Show information about the APK's signing certificates and prints the PEM
encoding of each signing certificate to stdout.
-v, --verbose Verbose output mode
--min-sdk-version Lowest API Level on which this APK's signatures will be
verified. By default, the value from AndroidManifest.xml
is used.
--max-sdk-version Highest API Level on which this APK's signatures will be
verified. By default, the highest possible value is used.
-Werr Treat warnings as errors
--in APK file to verify. This is an alternative to specifying
the APK as the very last parameter, after all options.
-h, --help Show help about this command and exit
EXAMPLES
1. Check whether the APK's signatures are expected to verify on all Android
platforms declared as supported by this APK:
$ apksigner verify app.apk
2. Check whether the APK's signatures are expected to verify on Android
platforms with API Level 15 and higher:
$ apksigner verify --min-sdk-version 15 app.apk
"#;
pub const HELP_PAGE_SIGN: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/help/help_sign.txt"
));
pub const HELP_PAGE_ROTATE: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/help/help_rotate.txt"
));
pub const HELP_PAGE_LINEAGE: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/help/help_lineage.txt"
));
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CliError {
Parameter(ParameterException),
Options(OptionsException),
Message(String),
SignerLoad(ParameterException),
}
impl std::fmt::Display for CliError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CliError::Parameter(e) => write!(f, "{}", e),
CliError::Options(e) => write!(f, "{}", e),
CliError::Message(m) => write!(f, "{}", m),
CliError::SignerLoad(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for CliError {}
impl From<OptionsException> for CliError {
fn from(e: OptionsException) -> Self {
CliError::Options(e)
}
}
impl From<ParameterException> for CliError {
fn from(e: ParameterException) -> Self {
CliError::Parameter(e)
}
}
fn parameter<T>(message: &str) -> Result<T, CliError> {
Err(CliError::Parameter(ParameterException::new(message)))
}
pub fn print_usage(page: &str) {
print!("{}", page);
let _ = std::io::stdout().flush();
}
pub fn main_with_args(params: &[String]) -> i32 {
if params.is_empty() || params[0] == "--help" || params[0] == "-h" {
print_usage(HELP_PAGE_GENERAL);
return 0;
}
if params[0] == "--version" {
println!("{}", VERSION);
return 0;
}
let cmd = params[0].clone();
let rest: Vec<String> = params[1..].to_vec();
let result: Result<i32, CliError> = match cmd.as_str() {
"sign" => sign(&rest),
"verify" => verify(&rest),
"rotate" => rotate(&rest),
"lineage" => lineage(&rest),
"help" => {
print_usage(HELP_PAGE_GENERAL);
Ok(0)
}
"version" => {
println!("{}", VERSION);
Ok(0)
}
other => parameter(&format!(
"Unsupported command: {}. See --help for supported commands",
other
)),
};
match result {
Ok(code) => code,
Err(CliError::SignerLoad(e)) => {
eprintln!("{}", e);
2
}
Err(e) => {
eprintln!("{}", e);
1
}
}
}
struct VerifyOutcome {
verified: bool,
verified_v1: bool,
verified_v2: bool,
verified_v3: bool,
verified_v31: bool,
verified_v4: bool,
source_stamp_verified: bool,
signer_certificates: Vec<Vec<u8>>,
errors: Vec<String>,
warnings: Vec<String>,
result: apksig::apk_verifier::Result,
}
fn verify(params: &[String]) -> Result<i32, CliError> {
if params.is_empty() {
print_usage(HELP_PAGE_VERIFY);
return Ok(0);
}
let mut input_apk: Option<String> = None;
let mut min_sdk_version: i32 = 1;
let mut min_sdk_version_specified = false;
let mut max_sdk_version: i32 = i32::MAX;
let mut max_sdk_version_specified = false;
let mut print_certs = false;
let mut print_certs_pem = false;
let mut verbose = false;
let mut warnings_treated_as_errors = false;
let mut verify_source_stamp = false;
let mut v4_signature_file: Option<String> = None;
let mut source_cert_digest: Option<String> = None;
let mut options_parser = OptionsParser::new(params);
let mut option_original_form: Option<String> = None;
while let Some(option_name) = options_parser.next_option()? {
option_original_form = options_parser.get_option_original_form();
match option_name.as_str() {
"min-sdk-version" => {
min_sdk_version = options_parser.get_required_int_value("Mininimum API Level")?;
min_sdk_version_specified = true;
}
"max-sdk-version" => {
max_sdk_version = options_parser.get_required_int_value("Maximum API Level")?;
max_sdk_version_specified = true;
}
"print-certs" => {
print_certs = options_parser.get_optional_boolean_value(true)?;
}
"print-certs-pem" => {
print_certs_pem = options_parser.get_optional_boolean_value(true)?;
if print_certs_pem && !print_certs {
print_certs = true;
}
}
"v" | "verbose" => {
verbose = options_parser.get_optional_boolean_value(true)?;
}
"Werr" => {
warnings_treated_as_errors = options_parser.get_optional_boolean_value(true)?;
}
"help" | "h" => {
print_usage(HELP_PAGE_VERIFY);
return Ok(0);
}
"v4-signature-file" => {
v4_signature_file =
Some(options_parser.get_required_value("Input V4 Signature File")?);
}
"in" => {
input_apk = Some(options_parser.get_required_value("Input APK file")?);
}
"verify-source-stamp" => {
verify_source_stamp = options_parser.get_optional_boolean_value(true)?;
}
"stamp-cert-digest" => {
source_cert_digest = Some(
options_parser
.get_required_value("Expected source stamp certificate digest")?,
);
}
_ => {
return parameter(&format!(
"Unsupported option: {}. See --help for supported options.",
option_original_form.unwrap_or_else(|| "null".to_string())
));
}
}
}
let remaining = options_parser.get_remaining_params();
match input_apk {
Some(_) => {
if let Some(first) = remaining.first() {
return parameter(&format!(
"Unexpected parameter(s) after {}: {}",
option_original_form.unwrap_or_else(|| "null".to_string()),
first
));
}
}
None => {
if remaining.is_empty() {
return parameter("Missing APK");
} else if remaining.len() > 1 {
return parameter(&format!(
"Unexpected parameter(s) after APK ({})",
remaining[1]
));
}
input_apk = Some(remaining[0].clone());
}
}
if min_sdk_version_specified && max_sdk_version_specified && min_sdk_version > max_sdk_version {
return parameter(&format!(
"Min API Level ({}) > max API Level ({})",
min_sdk_version, max_sdk_version
));
}
if let Some(v4_signature_file) = &v4_signature_file
&& !std::path::Path::new(v4_signature_file).exists()
{
return parameter(&format!(
"V4 signature file does not exist: {}",
v4_signature_file
));
}
let input_apk = input_apk.expect("input APK resolved above");
let outcome = run_verification(
&input_apk,
min_sdk_version_specified,
min_sdk_version,
max_sdk_version_specified,
max_sdk_version,
v4_signature_file.as_deref(),
verify_source_stamp,
source_cert_digest.as_deref(),
)?;
let verified = outcome.verified;
let warnings_encountered = !outcome.warnings.is_empty()
|| outcome
.result
.get_v1_scheme_signers()
.iter()
.any(|s| !s.get_warnings().is_empty())
|| outcome
.result
.get_v2_scheme_signers()
.iter()
.any(|s| !s.get_warnings().is_empty())
|| outcome
.result
.get_v3_scheme_signers()
.iter()
.any(|s| !s.get_warnings().is_empty())
|| outcome
.result
.get_v31_scheme_signers()
.iter()
.any(|s| !s.get_warnings().is_empty())
|| outcome
.result
.get_source_stamp_info()
.map(|s| !s.get_warnings().is_empty())
.unwrap_or(false);
if verified {
if verbose {
println!("Verifies");
println!(
"Verified using v1 scheme (JAR signing): {}",
outcome.verified_v1
);
println!(
"Verified using v2 scheme (APK Signature Scheme v2): {}",
outcome.verified_v2
);
println!(
"Verified using v3 scheme (APK Signature Scheme v3): {}",
outcome.verified_v3
);
println!(
"Verified using v3.1 scheme (APK Signature Scheme v3.1): {}",
outcome.verified_v31
);
println!(
"Verified using v4 scheme (APK Signature Scheme v4): {}",
outcome.verified_v4
);
println!(
"Verified for SourceStamp: {}",
outcome.source_stamp_verified
);
if !verify_source_stamp {
println!("Number of signers: {}", outcome.signer_certificates.len());
}
}
if print_certs {
if outcome.result.is_verified_using_v31_scheme() {
for signer in outcome.result.get_v31_scheme_signers() {
if let Some(certificate) = signer.get_certificate() {
let dev_release = if signer.get_rotation_targets_dev_release() {
" (dev release=true)"
} else {
""
};
print_certificate(
certificate,
&format!(
"Signer (minSdkVersion={}{}, maxSdkVersion={})",
signer.get_min_sdk_version(),
dev_release,
signer.get_max_sdk_version()
),
verbose,
print_certs_pem,
);
}
}
for signer in outcome.result.get_v3_scheme_signers() {
if let Some(certificate) = signer.get_certificate() {
print_certificate(
certificate,
&format!(
"Signer (minSdkVersion={}, maxSdkVersion={})",
signer.get_min_sdk_version(),
signer.get_max_sdk_version()
),
verbose,
print_certs_pem,
);
}
}
} else {
let mut signer_number = 0;
for signer_cert in &outcome.signer_certificates {
signer_number += 1;
print_certificate(
signer_cert,
&format!("Signer #{}", signer_number),
verbose,
print_certs_pem,
);
}
}
if let Some(source_stamp_info) = outcome.result.get_source_stamp_info()
&& let Some(certificate) = source_stamp_info.get_certificate()
{
print_certificate(certificate, "Source Stamp Signer", verbose, print_certs_pem);
}
}
if let Some(source_stamp_info) = outcome.result.get_source_stamp_info()
&& verbose
{
println!(
"Source Stamp Timestamp: {}",
source_stamp_info.get_timestamp_epoch_seconds()
);
}
} else {
eprintln!("DOES NOT VERIFY");
}
for error in &outcome.errors {
eprintln!("ERROR: {}", error);
}
for warning in &outcome.warnings {
print_verification_warning(warnings_treated_as_errors, warning);
}
for signer in outcome.result.get_v1_scheme_signers() {
let prefix = format!("JAR signer {}", signer.get_name());
for error in signer.get_errors() {
eprintln!("ERROR: {}: {}", prefix, error.to_text());
}
for warning in signer.get_warnings() {
print_verification_warning(
warnings_treated_as_errors,
&format!("{}: {}", prefix, warning.to_text()),
);
}
}
for signer in outcome.result.get_v2_scheme_signers() {
let prefix = format!("APK Signature Scheme v2 signer #{}", signer.get_index() + 1);
for error in signer.get_errors() {
eprintln!("ERROR: {}: {}", prefix, error.to_text());
}
for warning in signer.get_warnings() {
print_verification_warning(
warnings_treated_as_errors,
&format!("{}: {}", prefix, warning.to_text()),
);
}
}
for signer in outcome.result.get_v3_scheme_signers() {
let prefix = format!("APK Signature Scheme v3 signer #{}", signer.get_index() + 1);
for error in signer.get_errors() {
eprintln!("ERROR: {}: {}", prefix, error.to_text());
}
for warning in signer.get_warnings() {
print_verification_warning(
warnings_treated_as_errors,
&format!("{}: {}", prefix, warning.to_text()),
);
}
}
for signer in outcome.result.get_v31_scheme_signers() {
let prefix = format!(
"APK Signature Scheme v3.1 signer #{}(minSdkVersion={}, maxSdkVersion={})",
signer.get_index() + 1,
signer.get_min_sdk_version(),
signer.get_max_sdk_version()
);
for error in signer.get_errors() {
eprintln!("ERROR: {}: {}", prefix, error.to_text());
}
for warning in signer.get_warnings() {
print_verification_warning(
warnings_treated_as_errors,
&format!("{}: {}", prefix, warning.to_text()),
);
}
}
if let Some(source_stamp_info) = outcome.result.get_source_stamp_info() {
for error in source_stamp_info.get_errors() {
eprintln!("ERROR: SourceStamp: {}", error.to_text());
}
for warning in source_stamp_info.get_warnings() {
print_verification_warning(
warnings_treated_as_errors,
&format!("SourceStamp: {}", warning.to_text()),
);
}
for info_message in source_stamp_info.get_info_messages() {
println!("INFO: SourceStamp: {}", info_message.to_text());
}
}
if !verified {
return Ok(1);
}
if warnings_treated_as_errors && warnings_encountered {
return Ok(1);
}
Ok(0)
}
fn run_verification(
apk_path: &str,
min_sdk_version_specified: bool,
min_sdk_version: i32,
max_sdk_version_specified: bool,
max_sdk_version: i32,
v4_signature_file: Option<&str>,
verify_source_stamp: bool,
source_cert_digest: Option<&str>,
) -> Result<VerifyOutcome, CliError> {
use apksig::apk_verifier::Builder as ApkVerifierBuilder;
let apk = FileDataSource::open(apk_path)
.map_err(|e| CliError::Message(format!("Failed to open APK {}: {}", apk_path, e)))?;
let mut builder = ApkVerifierBuilder::new(Box::new(apk));
if min_sdk_version_specified {
builder = builder.set_min_checked_platform_version(min_sdk_version);
}
if max_sdk_version_specified {
builder = builder.set_max_checked_platform_version(max_sdk_version);
}
if let Some(v4_signature_file) = v4_signature_file {
let bytes = std::fs::read(v4_signature_file).map_err(|e| {
CliError::Message(format!(
"Failed to read V4 signature file {}: {}",
v4_signature_file, e
))
})?;
builder = builder.set_v4_signature_file(Some(bytes));
}
let verifier = builder.build();
let result = if verify_source_stamp {
verifier.verify_source_stamp_with_expected_cert_digest(source_cert_digest)
} else {
verifier
.verify()
.map_err(|e| {
let rendered = crate::java_trace::render_apk_format_exception(&e.0)
.unwrap_or_else(|| e.to_string());
CliError::Message(rendered)
})?
};
let errors = result
.get_errors()
.iter()
.map(|error| error.to_text())
.collect();
let warnings = result
.get_warnings()
.iter()
.map(|warning| warning.to_text())
.collect();
Ok(VerifyOutcome {
verified: result.is_verified(),
verified_v1: result.is_verified_using_v1_scheme(),
verified_v2: result.is_verified_using_v2_scheme(),
verified_v3: result.is_verified_using_v3_scheme(),
verified_v31: result.is_verified_using_v31_scheme(),
verified_v4: result.is_verified_using_v4_scheme(),
source_stamp_verified: result.is_source_stamp_verified(),
signer_certificates: result.get_signer_certificates().to_vec(),
errors,
warnings,
result,
})
}
#[derive(Debug, Clone, Default)]
struct ProviderInstallSpec {
class_name: Option<String>,
constructor_param: Option<String>,
position: Option<i32>,
}
impl ProviderInstallSpec {
fn is_empty(&self) -> bool {
self.class_name.is_none() && self.constructor_param.is_none() && self.position.is_none()
}
fn install_provider(&self) -> Result<(), CliError> {
let Some(class_name) = self.class_name.clone() else {
return parameter("JCA Provider class name (--provider-class) must be specified");
};
let provider =
crate::security::InstalledProvider::new(class_name, self.constructor_param.clone());
match self.position {
None => {
crate::security::add_provider(provider);
}
Some(position) => {
crate::security::insert_provider_at(provider, position);
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
struct SignOptions {
output_apk: Option<String>,
input_apk: Option<String>,
verbose: bool,
v1_signing_enabled: bool,
v2_signing_enabled: bool,
v3_signing_enabled: bool,
v4_signing_enabled: bool,
v4_signing_flag_found: bool,
force_source_stamp_overwrite: bool,
source_stamp_timestamp_enabled: bool,
align_file_size: bool,
verity_enabled: bool,
debuggable_apk_permitted: bool,
alignment_preserved: bool,
lib_page_alignment: i32,
min_sdk_version: i32,
min_sdk_version_specified: bool,
rotation_min_sdk_version: i32,
rotation_targets_dev_release: bool,
signers: Vec<SignerParams>,
lineage: Option<SigningCertificateLineage>,
source_stamp_signer_params: Option<SignerParams>,
source_stamp_lineage: Option<SigningCertificateLineage>,
deterministic_dsa_signing: bool,
other_signers_signatures_preserved: bool,
}
const LIBRARY_PAGE_ALIGNMENT_BYTES: i32 = 16384;
fn sign(params: &[String]) -> Result<i32, CliError> {
if params.is_empty() {
print_usage(HELP_PAGE_SIGN);
return Ok(0);
}
let mut output_apk: Option<String> = None;
let mut input_apk: Option<String> = None;
let mut verbose = false;
let mut v1_signing_enabled = true;
let mut v2_signing_enabled = true;
let mut v3_signing_enabled = true;
let mut v4_signing_enabled = true;
let mut force_source_stamp_overwrite = false;
let mut source_stamp_timestamp_enabled = true;
let mut align_file_size = false;
let mut verity_enabled = false;
let mut debuggable_apk_permitted = true;
let mut alignment_preserved = false;
let mut lib_page_alignment: i32 = LIBRARY_PAGE_ALIGNMENT_BYTES;
let mut min_sdk_version: i32 = 1;
let mut min_sdk_version_specified = false;
let mut max_sdk_version: i32 = i32::MAX;
let mut rotation_min_sdk_version: i32 = 33; let mut rotation_targets_dev_release = false;
let mut signers: Vec<SignerParams> = Vec::new();
let mut signer_params = SignerParams::new();
let mut lineage: Option<SigningCertificateLineage> = None;
let mut source_stamp_signer_params: Option<SignerParams> = None;
let mut source_stamp_lineage: Option<SigningCertificateLineage> = None;
let mut providers: Vec<ProviderInstallSpec> = Vec::new();
let mut provider_params = ProviderInstallSpec::default();
let mut v4_signing_flag_found = false;
let mut source_stamp_flag_found = false;
let mut deterministic_dsa_signing = false;
let mut other_signers_signatures_preserved = false;
let mut options_parser = OptionsParser::new(params);
let mut option_original_form: Option<String> = None;
while let Some(option_name) = options_parser.next_option()? {
option_original_form = options_parser.get_option_original_form();
match option_name.as_str() {
"help" | "h" => {
print_usage(HELP_PAGE_SIGN);
return Ok(0);
}
"out" => output_apk = Some(options_parser.get_required_value("Output file name")?),
"in" => input_apk = Some(options_parser.get_required_value("Input file name")?),
"min-sdk-version" => {
min_sdk_version = options_parser.get_required_int_value("Mininimum API Level")?;
min_sdk_version_specified = true;
}
"max-sdk-version" => {
max_sdk_version = options_parser.get_required_int_value("Maximum API Level")?;
}
"rotation-min-sdk-version" => {
rotation_min_sdk_version =
options_parser.get_required_int_value("Minimum API Level for Rotation")?;
}
"rotation-targets-dev-release" => {
rotation_targets_dev_release = options_parser.get_optional_boolean_value(true)?;
}
"v1-signing-enabled" => {
v1_signing_enabled = options_parser.get_optional_boolean_value(true)?;
}
"v2-signing-enabled" => {
v2_signing_enabled = options_parser.get_optional_boolean_value(true)?;
}
"v3-signing-enabled" => {
v3_signing_enabled = options_parser.get_optional_boolean_value(true)?;
}
"v4-signing-enabled" => {
v4_signing_enabled = options_parser.get_optional_boolean_value(true)?;
v4_signing_flag_found = true;
}
"force-stamp-overwrite" => {
force_source_stamp_overwrite = options_parser.get_optional_boolean_value(true)?;
}
"stamp-timestamp-enabled" => {
source_stamp_timestamp_enabled = options_parser.get_optional_boolean_value(true)?;
}
"align-file-size" => {
align_file_size = true;
}
"verity-enabled" => {
verity_enabled = options_parser.get_optional_boolean_value(true)?;
}
"debuggable-apk-permitted" => {
debuggable_apk_permitted = options_parser.get_optional_boolean_value(true)?;
}
"alignment-preserved" => {
alignment_preserved = options_parser.get_optional_boolean_value(true)?;
}
"lib-page-alignment" => {
lib_page_alignment = options_parser
.get_required_int_value("Native library page alignment size in bytes")?;
}
"next-signer" => {
if !signer_params.is_empty() {
signers.push(signer_params);
signer_params = SignerParams::new();
}
}
"signer-for-min-sdk-version" => {
if !signer_params.is_empty() {
signers.push(signer_params);
signer_params = SignerParams::new();
}
signer_params.set_min_sdk_version(
options_parser
.get_required_int_value("Mininimum API Level for signing config")?,
);
}
"ks" => {
signer_params.set_keystore_file(options_parser.get_required_value("KeyStore file")?)
}
"ks-key-alias" => signer_params
.set_keystore_key_alias(options_parser.get_required_value("KeyStore key alias")?),
"ks-pass" => signer_params.set_keystore_password_spec(
options_parser.get_required_value("KeyStore password")?,
),
"key-pass" => signer_params
.set_key_password_spec(options_parser.get_required_value("Key password")?),
"pass-encoding" => {
let charset_name =
options_parser.get_required_value("Password character encoding")?;
match get_charset_by_name(&charset_name) {
Ok(charset) => signer_params.set_password_charset(charset),
Err(_) => {
return parameter(&format!(
"Unsupported password character encoding requested using \
--pass-encoding: {}",
charset_name
));
}
}
}
"v1-signer-name" => signer_params.set_v1_sig_file_basename(
options_parser.get_required_value("JAR signature file basename")?,
),
"ks-type" => {
signer_params.set_keystore_type(options_parser.get_required_value("KeyStore type")?)
}
"ks-provider-name" => signer_params.set_keystore_provider_name(
options_parser.get_required_value("JCA KeyStore Provider name")?,
),
"ks-provider-class" => signer_params.set_keystore_provider_class(
options_parser.get_required_value("JCA KeyStore Provider class name")?,
),
"ks-provider-arg" => signer_params.set_keystore_provider_arg(
options_parser.get_required_value("JCA KeyStore Provider constructor argument")?,
),
"kms-type" => signer_params.set_kms_type(
options_parser.get_required_value("Key Management Service (KMS) type")?,
),
"kms-key-alias" => signer_params.set_kms_key_alias(
options_parser.get_required_value("Key Management Service (KMS) key alias")?,
),
"key" => {
signer_params.set_key_file(options_parser.get_required_value("Private key file")?)
}
"cert" => {
signer_params.set_cert_file(options_parser.get_required_value("Certificate file")?)
}
"signer-lineage" => {
let lineage_file =
options_parser.get_required_value("Lineage file for signing config")?;
signer_params
.set_signing_certificate_lineage(get_lineage_from_input_file(&lineage_file)?);
}
"lineage" => {
let lineage_file = options_parser.get_required_value("Lineage file")?;
lineage = Some(get_lineage_from_input_file(&lineage_file)?);
}
"v" | "verbose" => {
verbose = options_parser.get_optional_boolean_value(true)?;
}
"next-provider" => {
if !provider_params.is_empty() {
providers.push(provider_params);
provider_params = ProviderInstallSpec::default();
}
}
"provider-class" => {
provider_params.class_name =
Some(options_parser.get_required_value("JCA Provider class name")?);
}
"provider-arg" => {
provider_params.constructor_param =
Some(options_parser.get_required_value("JCA Provider constructor argument")?);
}
"provider-pos" => {
provider_params.position =
Some(options_parser.get_required_int_value("JCA Provider position")?);
}
"stamp-signer" => {
source_stamp_flag_found = true;
source_stamp_signer_params = Some(process_signer_params(&mut options_parser)?);
}
"stamp-lineage" => {
let stamp_lineage_file = options_parser.get_required_value("Stamp Lineage File")?;
source_stamp_lineage = Some(get_lineage_from_input_file(&stamp_lineage_file)?);
}
"deterministic-dsa-signing" => {
deterministic_dsa_signing = options_parser.get_optional_boolean_value(false)?;
}
"append-signature" => {
other_signers_signatures_preserved =
options_parser.get_optional_boolean_value(true)?;
}
_ => {
return parameter(&format!(
"Unsupported option: {}. See --help for supported options.",
option_original_form.unwrap_or_else(|| "null".to_string())
));
}
}
}
if !signer_params.is_empty() {
signers.push(signer_params);
}
if !provider_params.is_empty() {
providers.push(provider_params);
}
if signers.is_empty() {
return parameter("At least one signer must be specified");
}
let remaining = options_parser.get_remaining_params();
match input_apk {
Some(_) => {
if let Some(first) = remaining.first() {
return parameter(&format!(
"Unexpected parameter(s) after {}: {}",
option_original_form.unwrap_or_else(|| "null".to_string()),
first
));
}
}
None => {
if remaining.is_empty() {
return parameter("Missing input APK");
} else if remaining.len() > 1 {
return parameter(&format!(
"Unexpected parameter(s) after input APK ({})",
remaining[1]
));
}
input_apk = Some(remaining[0].clone());
}
}
if min_sdk_version_specified && min_sdk_version > max_sdk_version {
return parameter(&format!(
"Min API Level ({}) > max API Level ({})",
min_sdk_version, max_sdk_version
));
}
for provider_install_spec in &providers {
provider_install_spec.install_provider()?;
}
if !source_stamp_flag_found {
source_stamp_signer_params = None;
}
let options = SignOptions {
output_apk,
input_apk,
verbose,
v1_signing_enabled,
v2_signing_enabled,
v3_signing_enabled,
v4_signing_enabled,
v4_signing_flag_found,
force_source_stamp_overwrite,
source_stamp_timestamp_enabled,
align_file_size,
verity_enabled,
debuggable_apk_permitted,
alignment_preserved,
lib_page_alignment,
min_sdk_version,
min_sdk_version_specified,
rotation_min_sdk_version,
rotation_targets_dev_release,
signers,
lineage,
source_stamp_signer_params,
source_stamp_lineage,
deterministic_dsa_signing,
other_signers_signatures_preserved,
};
run_sign(&options)?;
if options.verbose {
println!("Signed");
}
Ok(0)
}
fn run_sign(options: &SignOptions) -> Result<(), CliError> {
let mut password_retriever = PasswordRetriever::new();
let mut signer_configs = Vec::with_capacity(options.signers.len());
for (index, signer) in options.signers.iter().enumerate() {
let mut signer = signer.clone();
signer.set_name(format!("signer #{}", index + 1));
signer_configs.push(get_signer_config(
&mut signer,
&mut password_retriever,
options.deterministic_dsa_signing,
)?);
}
let source_stamp_signer_config = match &options.source_stamp_signer_params {
Some(stamp_signer) => {
let mut stamp_signer = stamp_signer.clone();
stamp_signer.set_name("stamp signer");
Some(get_signer_config(
&mut stamp_signer,
&mut password_retriever,
options.deterministic_dsa_signing,
)?)
}
None => None,
};
let input_apk = options
.input_apk
.as_deref()
.ok_or_else(|| CliError::Parameter(ParameterException::new("Missing input APK")))?;
let input_path = Path::new(input_apk);
let output_path = match options.output_apk.as_deref() {
Some(output_apk) => Path::new(output_apk),
None => input_path,
};
let tmp_output_apk: PathBuf =
if canonical_or_original(input_path) == canonical_or_original(output_path) {
let unique = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let mut path = std::env::temp_dir();
path.push(format!("apksigner{}-{}.apk", std::process::id(), unique));
path
} else {
output_path.to_path_buf()
};
let mut builder = ApkSignerBuilder::new(signer_configs).map_err(apk_signer_error)?;
builder = builder
.set_input_apk_file(input_path)
.set_output_apk_file(tmp_output_apk.clone());
builder = builder
.set_other_signers_signatures_preserved(options.other_signers_signatures_preserved)
.map_err(apk_signer_error)?
.set_v1_signing_enabled(options.v1_signing_enabled)
.map_err(apk_signer_error)?
.set_v2_signing_enabled(options.v2_signing_enabled)
.map_err(apk_signer_error)?
.set_v3_signing_enabled(options.v3_signing_enabled)
.map_err(apk_signer_error)?
.set_v4_signing_enabled(options.v4_signing_enabled)
.map_err(apk_signer_error)?
.set_force_source_stamp_overwrite(options.force_source_stamp_overwrite)
.set_source_stamp_timestamp_enabled(options.source_stamp_timestamp_enabled)
.set_align_file_size(options.align_file_size)
.map_err(apk_signer_error)?
.set_verity_enabled(options.verity_enabled)
.map_err(apk_signer_error)?
.set_v4_error_reporting_enabled(options.v4_signing_enabled && options.v4_signing_flag_found)
.map_err(apk_signer_error)?
.set_debuggable_apk_permitted(options.debuggable_apk_permitted)
.map_err(apk_signer_error)?
.set_signing_certificate_lineage(options.lineage.clone())
.set_min_sdk_version_for_rotation(options.rotation_min_sdk_version)
.map_err(apk_signer_error)?
.set_rotation_targets_dev_release(options.rotation_targets_dev_release)
.map_err(apk_signer_error)?
.set_alignment_preserved(options.alignment_preserved)
.set_library_page_alignment_bytes(options.lib_page_alignment);
if options.min_sdk_version_specified {
builder = builder
.set_min_sdk_version(options.min_sdk_version)
.map_err(apk_signer_error)?;
}
if options.v4_signing_enabled {
let v4_signature_output_file = PathBuf::from(format!(
"{}.idsig",
canonical_or_original(output_path).display()
));
let _ = std::fs::remove_file(&v4_signature_output_file);
builder = builder.set_v4_signature_output_file(v4_signature_output_file);
}
if let Some(source_stamp_signer_config) = source_stamp_signer_config {
builder = builder.set_source_stamp_signer_config(source_stamp_signer_config);
if let Some(source_stamp_lineage) = options.source_stamp_lineage.clone() {
builder = builder.set_source_stamp_signing_certificate_lineage(source_stamp_lineage);
}
}
let mut apk_signer = builder.build().map_err(apk_signer_error)?;
apk_signer.sign().map_err(|error| match error {
ApkSignerError::MinSdkVersion(_) => CliError::Message(
"Failed to determine APK's minimum supported platform version. \
Use --min-sdk-version to override"
.to_string(),
),
other => apk_signer_error(other),
})?;
if tmp_output_apk.as_path() != output_path {
std::fs::rename(&tmp_output_apk, output_path).map_err(|e| {
CliError::Message(format!(
"Failed to move output APK {} to {}: {}",
tmp_output_apk.display(),
output_path.display(),
e
))
})?;
}
Ok(())
}
fn print_verification_warning(treat_as_error: bool, warning: &str) {
if treat_as_error {
eprintln!("WARNING: {}", warning);
} else {
println!("WARNING: {}", warning);
}
}
fn canonical_or_original(path: &Path) -> PathBuf {
if let Ok(canonical) = std::fs::canonicalize(path) {
return canonical;
}
match (path.parent(), path.file_name()) {
(Some(parent), Some(name)) if !parent.as_os_str().is_empty() => {
match std::fs::canonicalize(parent) {
Ok(canonical_parent) => canonical_parent.join(name),
Err(_) => path.to_path_buf(),
}
}
_ => path.to_path_buf(),
}
}
fn get_signer_config(
signer: &mut SignerParams,
password_retriever: &mut PasswordRetriever,
deterministic_dsa_signing: bool,
) -> Result<apksig::apk_signer::SignerConfig, CliError> {
let name = signer.get_name().unwrap_or("null").to_string();
signer
.load_private_key_and_certs(password_retriever)
.map_err(|error| {
let message = match error {
SignerLoadError::Parameter(e) => e.to_string(),
SignerLoadError::Other(m) => m,
};
CliError::SignerLoad(ParameterException::new(format!(
"Failed to load signer \"{}\": {}",
name, message
)))
})?;
let v1_sig_basename = v1_signature_basename(signer).ok_or_else(|| {
CliError::Message("Neither KeyStore key alias nor private key file available".to_string())
})?;
let key_config = signer.get_key_config().cloned().ok_or_else(|| {
CliError::Message("No key configuration is available for the signer".to_string())
})?;
let certificates = signer
.get_certs()
.map(|certs| certs.to_vec())
.unwrap_or_default();
let mut builder = apksig::apk_signer::SignerConfigBuilder::with_deterministic_dsa_signing(
v1_sig_basename,
key_config,
certificates,
deterministic_dsa_signing,
)
.map_err(apk_signer_error)?;
let min_sdk_version = signer.get_min_sdk_version();
if min_sdk_version > 0 {
let lineage = signer.get_signing_certificate_lineage().cloned();
builder = builder
.set_lineage_for_min_sdk_version(lineage, min_sdk_version)
.map_err(apk_signer_error)?;
}
Ok(builder.build())
}
fn apk_signer_error(error: ApkSignerError) -> CliError {
CliError::Message(error.to_string())
}
fn rotate(params: &[String]) -> Result<i32, CliError> {
if params.is_empty() {
print_usage(HELP_PAGE_ROTATE);
return Ok(0);
}
let mut output_key_lineage: Option<String> = None;
let mut input_key_lineage: Option<String> = None;
let mut verbose = false;
let mut old_signer_params: Option<SignerParams> = None;
let mut new_signer_params: Option<SignerParams> = None;
let mut min_sdk_version: i32 = 0;
let mut providers: Vec<ProviderInstallSpec> = Vec::new();
let mut provider_params = ProviderInstallSpec::default();
let mut options_parser = OptionsParser::new(params);
let mut option_original_form: Option<String> = None;
while let Some(option_name) = options_parser.next_option()? {
option_original_form = options_parser.get_option_original_form();
match option_name.as_str() {
"help" | "h" => {
print_usage(HELP_PAGE_ROTATE);
return Ok(0);
}
"out" => {
output_key_lineage = Some(options_parser.get_required_value("Output file name")?)
}
"in" => input_key_lineage = Some(options_parser.get_required_value("Input file name")?),
"old-signer" => old_signer_params = Some(process_signer_params(&mut options_parser)?),
"new-signer" => new_signer_params = Some(process_signer_params(&mut options_parser)?),
"min-sdk-version" => {
min_sdk_version = options_parser.get_required_int_value("Mininimum API Level")?;
}
"v" | "verbose" => {
verbose = options_parser.get_optional_boolean_value(true)?;
}
"next-provider" => {
if !provider_params.is_empty() {
providers.push(provider_params);
provider_params = ProviderInstallSpec::default();
}
}
"provider-class" => {
provider_params.class_name =
Some(options_parser.get_required_value("JCA Provider class name")?);
}
"provider-arg" => {
provider_params.constructor_param =
Some(options_parser.get_required_value("JCA Provider constructor argument")?);
}
"provider-pos" => {
provider_params.position =
Some(options_parser.get_required_int_value("JCA Provider position")?);
}
_ => {
return parameter(&format!(
"Unsupported option: {}. See --help for supported options.",
option_original_form.unwrap_or_else(|| "null".to_string())
));
}
}
}
if !provider_params.is_empty() {
providers.push(provider_params);
}
let Some(mut old_signer_params) = old_signer_params else {
return parameter("Signer parameters for old signer not present");
};
let Some(mut new_signer_params) = new_signer_params else {
return parameter("Signer parameters for new signer not present");
};
let Some(output_key_lineage) = output_key_lineage else {
return parameter("Output lineage file parameter not present");
};
let remaining = options_parser.get_remaining_params();
if let Some(first) = remaining.first() {
return parameter(&format!(
"Unexpected parameter(s) after {}: {}",
option_original_form.unwrap_or_else(|| "null".to_string()),
first
));
}
for provider_install_spec in &providers {
provider_install_spec.install_provider()?;
}
run_rotate(
&mut old_signer_params,
&mut new_signer_params,
&output_key_lineage,
input_key_lineage.as_deref(),
min_sdk_version,
)?;
if verbose {
println!("Rotation entry generated.");
}
Ok(0)
}
fn run_rotate(
old_signer_params: &mut SignerParams,
new_signer_params: &mut SignerParams,
output_key_lineage: &str,
input_key_lineage: Option<&str>,
min_sdk_version: i32,
) -> Result<(), CliError> {
let mut password_retriever = PasswordRetriever::new();
old_signer_params.set_name("old signer");
old_signer_params
.load_private_key_and_certs(&mut password_retriever)
.map_err(signer_load_error)?;
let old_signer_config = signer_config(old_signer_params)?;
new_signer_params.set_name("new signer");
new_signer_params
.load_private_key_and_certs(&mut password_retriever)
.map_err(signer_load_error)?;
let new_signer_config = signer_config(new_signer_params)?;
let old_capabilities = old_signer_params.get_signer_capabilities_builder().build();
let new_capabilities = new_signer_params.get_signer_capabilities_builder().build();
let lineage = match input_key_lineage {
Some(input_key_lineage) => {
let mut lineage = get_lineage_from_input_file(input_key_lineage)?;
lineage
.update_signer_capabilities_for_config(&old_signer_config, &old_capabilities)
.map_err(lineage_error)?;
lineage
.spawn_descendant_with(&old_signer_config, &new_signer_config, &new_capabilities)
.map_err(lineage_error)?
}
None => {
let mut builder =
SigningCertificateLineageBuilder::new(old_signer_config, new_signer_config);
builder
.set_min_sdk_version(min_sdk_version)
.set_original_capabilities(old_capabilities)
.set_new_capabilities(new_capabilities);
builder.build().map_err(lineage_error)?
}
};
write_lineage_to_file(&lineage, output_key_lineage)
}
fn signer_config(signer_params: &SignerParams) -> Result<SignerConfig, CliError> {
let key_config = signer_params.get_key_config().cloned().ok_or_else(|| {
CliError::Message("No key configuration is available for the signer".into())
})?;
let certificate_der = signer_params
.get_certs()
.and_then(|certs| certs.first())
.ok_or_else(|| CliError::Message("No certificate is available for the signer".into()))?;
let certificate =
SigningCertificate::from_der(certificate_der.clone()).map_err(lineage_error)?;
Ok(SignerConfigBuilder::new(key_config, certificate).build())
}
fn write_lineage_to_file(
lineage: &SigningCertificateLineage,
output_key_lineage: &str,
) -> Result<(), CliError> {
std::fs::write(output_key_lineage, lineage.get_bytes()).map_err(|e| {
CliError::Message(format!(
"Failed to write lineage file {}: {}",
output_key_lineage, e
))
})
}
fn lineage_message(error: &LineageError) -> String {
match error {
LineageError::Io(m)
| LineageError::IllegalArgument(m)
| LineageError::Security(m)
| LineageError::Certificate(m)
| LineageError::Signing(m)
| LineageError::IllegalState(m)
| LineageError::InvalidKey(m) => m.clone(),
LineageError::Format(e) => e.0.clone(),
}
}
fn lineage_error(error: LineageError) -> CliError {
CliError::Message(lineage_message(&error))
}
fn lineage_to_parameter(error: LineageError) -> CliError {
CliError::Parameter(ParameterException::new(lineage_message(&error)))
}
fn signer_load_error(error: SignerLoadError) -> CliError {
match error {
SignerLoadError::Parameter(e) => CliError::Parameter(e),
SignerLoadError::Other(m) => CliError::Message(m),
}
}
fn lineage(params: &[String]) -> Result<i32, CliError> {
if params.is_empty() {
print_usage(HELP_PAGE_LINEAGE);
return Ok(0);
}
let mut verbose = false;
let mut print_certs = false;
let mut print_certs_pem = false;
let mut input_key_lineage: Option<String> = None;
let mut output_key_lineage: Option<String> = None;
let mut signers: Vec<SignerParams> = Vec::new();
let mut options_parser = OptionsParser::new(params);
while let Some(option_name) = options_parser.next_option()? {
match option_name.as_str() {
"help" | "h" => {
print_usage(HELP_PAGE_LINEAGE);
return Ok(0);
}
"in" => input_key_lineage = Some(options_parser.get_required_value("Input file name")?),
"out" => {
output_key_lineage = Some(options_parser.get_required_value("Output file name")?)
}
"signer" => {
signers.push(process_signer_params(&mut options_parser)?);
}
"v" | "verbose" => {
verbose = options_parser.get_optional_boolean_value(true)?;
}
"print-certs" => {
print_certs = options_parser.get_optional_boolean_value(true)?;
}
"print-certs-pem" => {
print_certs_pem = options_parser.get_optional_boolean_value(true)?;
if print_certs_pem && !print_certs {
print_certs = true;
}
}
_ => {
return parameter(&format!(
"Unsupported option: {}. See --help for supported options.",
options_parser
.get_option_original_form()
.unwrap_or_else(|| "null".to_string())
));
}
}
}
let Some(input_key_lineage) = input_key_lineage else {
return parameter("Input lineage file parameter not present");
};
run_lineage(
&input_key_lineage,
output_key_lineage.as_deref(),
&mut signers,
print_certs,
print_certs_pem,
verbose,
)?;
Ok(0)
}
fn run_lineage(
input_key_lineage: &str,
output_key_lineage: Option<&str>,
signers: &mut [SignerParams],
print_certs: bool,
print_certs_pem: bool,
verbose: bool,
) -> Result<(), CliError> {
let mut lineage = get_lineage_from_input_file(input_key_lineage)?;
let mut lineage_updated = false;
let mut password_retriever = PasswordRetriever::new();
for (index, signer_params) in signers.iter_mut().enumerate() {
let signer_name = format!("signer #{}", index + 1);
signer_params.set_name(signer_name.clone());
signer_params
.load_private_key_and_certs(&mut password_retriever)
.map_err(signer_load_error)?;
let signer_config = signer_config(signer_params)?;
let original = lineage.get_signer_capabilities_for_config(&signer_config);
let update = lineage.update_signer_capabilities_for_config(
&signer_config,
&signer_params.get_signer_capabilities_builder().build(),
);
let updated = lineage.get_signer_capabilities_for_config(&signer_config);
let (Ok(original), Ok(()), Ok(updated)) = (original, update, updated) else {
return parameter(&format!(
"The signer {} was not found in the specified lineage.",
signer_name
));
};
if original.get_flags() == updated.get_flags() {
if verbose {
println!(
"The provided signer capabilities for {} are unchanged.",
signer_name
);
}
} else {
lineage_updated = true;
if verbose {
println!("Updated signer capabilities for {}.", signer_name);
}
}
}
if print_certs {
let signing_certs = lineage.get_certificates_in_lineage();
for (index, signer_cert) in signing_certs.iter().enumerate() {
let signer_capabilities = lineage
.get_signer_capabilities(signer_cert)
.map_err(lineage_error)?;
print_certificate(
signer_cert.get_encoded(),
&format!("Signer #{} in lineage", index + 1),
verbose,
print_certs_pem,
);
print_lineage_capabilities(signer_capabilities);
}
}
if lineage_updated {
match output_key_lineage {
Some(output_key_lineage) => {
write_lineage_to_file(&lineage, output_key_lineage)?;
if verbose {
println!("Updated lineage saved to {}.", output_key_lineage);
}
}
None => {
return parameter(
"The lineage was modified but an output file for the lineage was not \
specified",
);
}
}
}
Ok(())
}
fn get_lineage_from_input_file(
input_lineage_file: &str,
) -> Result<SigningCertificateLineage, CliError> {
let bytes = std::fs::read(input_lineage_file)
.map_err(|e| CliError::Parameter(ParameterException::new(e.to_string())))?;
if bytes.len() < 4 {
return parameter("The input file is not a valid lineage file.");
}
let magic_value = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let data_source = FileDataSource::open(input_lineage_file)
.map_err(|e| CliError::Parameter(ParameterException::new(e.to_string())))?;
let lineage = if magic_value == SIGNING_CERTIFICATE_LINEAGE_MAGIC {
SigningCertificateLineage::read_from_data_source(&data_source)
} else if magic_value == ZIP_MAGIC {
SigningCertificateLineage::read_from_apk_data_source(&data_source)
} else {
return parameter("The input file is not a valid lineage file.");
};
lineage.map_err(lineage_to_parameter)
}
fn process_signer_params(options_parser: &mut OptionsParser) -> Result<SignerParams, CliError> {
let mut signer_params = SignerParams::new();
while let Some(option_name) = options_parser.next_option()? {
match option_name.as_str() {
"ks" => {
signer_params.set_keystore_file(options_parser.get_required_value("KeyStore file")?)
}
"ks-key-alias" => signer_params
.set_keystore_key_alias(options_parser.get_required_value("KeyStore key alias")?),
"ks-pass" => signer_params.set_keystore_password_spec(
options_parser.get_required_value("KeyStore password")?,
),
"key-pass" => signer_params
.set_key_password_spec(options_parser.get_required_value("Key password")?),
"pass-encoding" => {
let charset_name =
options_parser.get_required_value("Password character encoding")?;
match get_charset_by_name(&charset_name) {
Ok(charset) => signer_params.set_password_charset(charset),
Err(_) => {
return parameter(&format!(
"Unsupported password character encoding requested using \
--pass-encoding: {}",
charset_name
));
}
}
}
"ks-type" => {
signer_params.set_keystore_type(options_parser.get_required_value("KeyStore type")?)
}
"ks-provider-name" => signer_params.set_keystore_provider_name(
options_parser.get_required_value("JCA KeyStore Provider name")?,
),
"ks-provider-class" => signer_params.set_keystore_provider_class(
options_parser.get_required_value("JCA KeyStore Provider class name")?,
),
"ks-provider-arg" => signer_params.set_keystore_provider_arg(
options_parser.get_required_value("JCA KeyStore Provider constructor argument")?,
),
"kms-type" => {
signer_params.set_kms_type(options_parser.get_required_value("KMS Type")?)
}
"kms-key-alias" => {
signer_params.set_kms_key_alias(options_parser.get_required_value("KMS Key Alias")?)
}
"key" => {
signer_params.set_key_file(options_parser.get_required_value("Private key file")?)
}
"cert" => {
signer_params.set_cert_file(options_parser.get_required_value("Certificate file")?)
}
"set-installed-data" => {
let value = options_parser.get_optional_boolean_value(true)?;
signer_params
.get_signer_capabilities_builder_mut()
.set_installed_data(value);
}
"set-shared-uid" => {
let value = options_parser.get_optional_boolean_value(true)?;
signer_params
.get_signer_capabilities_builder_mut()
.set_shared_uid(value);
}
"set-permission" => {
let value = options_parser.get_optional_boolean_value(true)?;
signer_params
.get_signer_capabilities_builder_mut()
.set_permission(value);
}
"set-rollback" => {
let value = options_parser.get_optional_boolean_value(true)?;
signer_params
.get_signer_capabilities_builder_mut()
.set_rollback(value);
}
"set-auth" => {
let value = options_parser.get_optional_boolean_value(true)?;
signer_params
.get_signer_capabilities_builder_mut()
.set_auth(value);
}
_ => {
options_parser.put_option();
break;
}
}
}
if signer_params.is_empty() {
return parameter("Signer specified without arguments");
}
Ok(signer_params)
}
pub fn print_certificate(cert_der: &[u8], name: &str, verbose: bool, pem_output: bool) {
println!("{} certificate DN: {}", name, certificate_dn(cert_der));
println!(
"{} certificate SHA-256 digest: {}",
name,
HexEncoding::encode(&sha256(cert_der))
);
println!(
"{} certificate SHA-1 digest: {}",
name,
HexEncoding::encode(&sha1(cert_der))
);
println!(
"{} certificate MD5 digest: {}",
name,
HexEncoding::encode(&md5_digest(cert_der))
);
if verbose {
let (key_algorithm, key_size, encoded_key) = public_key_details(cert_der);
println!("{} key algorithm: {}", name, key_algorithm);
println!(
"{} key size (bits): {}",
name,
key_size
.map(|size| size.to_string())
.unwrap_or_else(|| "n/a".to_string())
);
if let Some(encoded_key) = &encoded_key {
println!(
"{} public key SHA-256 digest: {}",
name,
HexEncoding::encode(&sha256(encoded_key))
);
println!(
"{} public key SHA-1 digest: {}",
name,
HexEncoding::encode(&sha1(encoded_key))
);
println!(
"{} public key MD5 digest: {}",
name,
HexEncoding::encode(&md5_digest(encoded_key))
);
}
}
if pem_output {
println!("{}", BEGIN_CERTIFICATE);
let line_width = 64;
let pem_encoded_cert = base64::encode(cert_der);
let bytes = pem_encoded_cert.as_bytes();
let mut i = 0;
while i < bytes.len() {
let end = std::cmp::min(i + line_width, bytes.len());
println!("{}", &pem_encoded_cert[i..end]);
i += line_width;
}
println!("{}", END_CERTIFICATE);
}
}
fn print_lineage_capabilities(capabilities: SignerCapabilities) {
print_capability_flags(
capabilities.has_installed_data(),
capabilities.has_shared_uid(),
capabilities.has_permission(),
capabilities.has_rollback(),
capabilities.has_auth(),
);
}
fn print_capability_flags(
installed_data: bool,
shared_uid: bool,
permission: bool,
rollback: bool,
auth: bool,
) {
println!("Has installed data capability: {}", installed_data);
println!("Has shared UID capability : {}", shared_uid);
println!("Has permission capability : {}", permission);
println!("Has rollback capability : {}", rollback);
println!("Has auth capability : {}", auth);
}
pub fn certificate_dn(cert_der: &[u8]) -> String {
let Ok(cert) = X509Certificate::from_der(cert_der) else {
return "".to_string();
};
render_distinguished_name(&cert.certificate.subject)
}
fn render_distinguished_name(name: &apksig::internal::x509::Name) -> String {
let mut rdns = Vec::new();
for rdn in name.relative_distinguished_names.iter().rev() {
let mut parts = Vec::new();
for attribute in &rdn.attributes {
let short_name = short_attribute_name(&attribute.attr_type);
let value = render_attribute_value(attribute.attr_value.get_encoded());
parts.push(format!("{}={}", short_name, value));
}
rdns.push(parts.join("+"));
}
rdns.join(", ")
}
fn short_attribute_name(oid: &str) -> String {
match oid {
"2.5.4.3" => "CN".to_string(),
"2.5.4.4" => "SN".to_string(),
"2.5.4.5" => "SERIALNUMBER".to_string(),
"2.5.4.6" => "C".to_string(),
"2.5.4.7" => "L".to_string(),
"2.5.4.8" => "ST".to_string(),
"2.5.4.9" => "STREET".to_string(),
"2.5.4.10" => "O".to_string(),
"2.5.4.11" => "OU".to_string(),
"2.5.4.12" => "T".to_string(),
"2.5.4.17" => "POSTALCODE".to_string(),
"2.5.4.42" => "GIVENNAME".to_string(),
"1.2.840.113549.1.9.1" => "EMAILADDRESS".to_string(),
"0.9.2342.19200300.100.1.25" => "DC".to_string(),
"0.9.2342.19200300.100.1.1" => "UID".to_string(),
other => other.to_string(),
}
}
fn render_attribute_value(encoded: &[u8]) -> String {
let decoded = decode_directory_string(encoded);
match decoded {
Some(value) => escape_rfc2253(&value),
None => format!("#{}", HexEncoding::encode(encoded)),
}
}
fn decode_directory_string(encoded: &[u8]) -> Option<String> {
let (tag, contents) = split_tlv(encoded)?;
match tag {
0x0c => Some(String::from_utf8_lossy(contents).into_owned()), 0x13 => Some(String::from_utf8_lossy(contents).into_owned()), 0x16 => Some(String::from_utf8_lossy(contents).into_owned()), 0x14 => Some(contents.iter().map(|&b| b as char).collect()), 0x12 => Some(String::from_utf8_lossy(contents).into_owned()), 0x1e => {
let mut chars = Vec::with_capacity(contents.len() / 2);
let mut i = 0;
while i + 1 < contents.len() {
chars.push(u16::from_be_bytes([contents[i], contents[i + 1]]));
i += 2;
}
Some(String::from_utf16_lossy(&chars))
}
_ => None,
}
}
fn split_tlv(encoded: &[u8]) -> Option<(u8, &[u8])> {
let tag = *encoded.first()?;
let len_first = *encoded.get(1)?;
let (header_len, content_len) = if len_first & 0x80 == 0 {
(2usize, len_first as usize)
} else {
let num = (len_first & 0x7f) as usize;
if num == 0 || num > 4 || encoded.len() < 2 + num {
return None;
}
let mut len = 0usize;
for i in 0..num {
len = (len << 8) | encoded[2 + i] as usize;
}
(2 + num, len)
};
let end = header_len.checked_add(content_len)?;
if end > encoded.len() {
return None;
}
Some((tag, &encoded[header_len..end]))
}
fn escape_rfc2253(value: &str) -> String {
let mut out = String::with_capacity(value.len());
for (index, c) in value.chars().enumerate() {
let is_last = index + 1 == value.chars().count();
let must_escape = matches!(c, ',' | '+' | '"' | '\\' | '<' | '>' | ';' | '=' | '#')
|| (index == 0 && (c == ' ' || c == '#'))
|| (is_last && c == ' ');
if must_escape {
out.push('\\');
}
out.push(c);
}
out
}
fn public_key_details(cert_der: &[u8]) -> (String, Option<usize>, Option<Vec<u8>>) {
let Ok(cert) = X509Certificate::from_der(cert_der) else {
return ("n/a".to_string(), None, None);
};
let spki = &cert.certificate.subject_public_key_info;
let oid = spki.algorithm_oid();
let encoded_key = spki.to_der().ok();
match oid {
"1.2.840.113549.1.1.1" => (
"RSA".to_string(),
rsa_modulus_bit_length(&spki.subject_public_key),
encoded_key,
),
"1.2.840.10045.2.1" => {
let size = spki
.algorithm_identifier
.parameters
.as_ref()
.and_then(|p| split_tlv(p.get_encoded()))
.and_then(|(_, contents)| {
apksig::internal::pkcs7::der_utils::decode_oid(contents).ok()
})
.and_then(|curve_oid| ec_curve_order_bits(&curve_oid));
("EC".to_string(), size, encoded_key)
}
"1.2.840.10040.4.1" => ("DSA".to_string(), dsa_p_bit_length(spki), encoded_key),
_ => ("n/a".to_string(), None, None),
}
}
fn rsa_modulus_bit_length(rsa_public_key: &[u8]) -> Option<usize> {
let (tag, seq) = split_tlv(rsa_public_key)?;
if tag != 0x30 {
return None;
}
let (int_tag, modulus) = split_tlv(seq)?;
if int_tag != 0x02 || modulus.is_empty() {
return None;
}
let significant = match modulus.iter().position(|&b| b != 0) {
Some(index) => &modulus[index..],
None => return Some(0),
};
let leading_bits = 8 - significant[0].leading_zeros() as usize;
Some((significant.len() - 1) * 8 + leading_bits)
}
fn ec_curve_order_bits(curve_oid: &str) -> Option<usize> {
match curve_oid {
"1.2.840.10045.3.1.7" => Some(256),
"1.3.132.0.34" => Some(384),
"1.3.132.0.35" => Some(521),
"1.3.132.0.33" => Some(224),
"1.3.132.0.10" => Some(256),
_ => None,
}
}
fn dsa_p_bit_length(spki: &apksig::internal::x509::SubjectPublicKeyInfo) -> Option<usize> {
let parameters = spki.algorithm_identifier.parameters.as_ref()?;
let (tag, seq) = split_tlv(parameters.get_encoded())?;
if tag != 0x30 {
return None;
}
let (int_tag, p) = split_tlv(seq)?;
if int_tag != 0x02 || p.is_empty() {
return None;
}
let significant = match p.iter().position(|&b| b != 0) {
Some(index) => &p[index..],
None => return Some(0),
};
let leading_bits = 8 - significant[0].leading_zeros() as usize;
Some((significant.len() - 1) * 8 + leading_bits)
}
fn sha256(data: &[u8]) -> Vec<u8> {
use sha2::Digest;
sha2::Sha256::digest(data).to_vec()
}
fn sha1(data: &[u8]) -> Vec<u8> {
use sha1::Digest;
sha1::Sha1::digest(data).to_vec()
}
fn md5_digest(data: &[u8]) -> [u8; 16] {
md5::digest(data)
}
pub(crate) mod base64 {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
pub fn encode(data: &[u8]) -> String {
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = *chunk.get(1).unwrap_or(&0) as u32;
let b2 = *chunk.get(2).unwrap_or(&0) as u32;
let triple = (b0 << 16) | (b1 << 8) | b2;
out.push(ALPHABET[((triple >> 18) & 0x3f) as usize] as char);
out.push(ALPHABET[((triple >> 12) & 0x3f) as usize] as char);
if chunk.len() > 1 {
out.push(ALPHABET[((triple >> 6) & 0x3f) as usize] as char);
} else {
out.push('=');
}
if chunk.len() > 2 {
out.push(ALPHABET[(triple & 0x3f) as usize] as char);
} else {
out.push('=');
}
}
out
}
pub fn decode(data: &[u8]) -> Result<Vec<u8>, String> {
let mut out = Vec::with_capacity(data.len() / 4 * 3);
let mut accumulator: u32 = 0;
let mut bits = 0u32;
for &byte in data {
if byte == b'=' {
break;
}
let value = decode_digit(byte)
.ok_or_else(|| format!("Invalid Base64 character: 0x{:02x}", byte))?;
accumulator = (accumulator << 6) | value as u32;
bits += 6;
if bits >= 8 {
bits -= 8;
out.push((accumulator >> bits) as u8);
}
}
Ok(out)
}
fn decode_digit(byte: u8) -> Option<u8> {
match byte {
b'A'..=b'Z' => Some(byte - b'A'),
b'a'..=b'z' => Some(byte - b'a' + 26),
b'0'..=b'9' => Some(byte - b'0' + 52),
b'+' => Some(62),
b'/' => Some(63),
_ => None,
}
}
}
pub(crate) mod md5 {
const S: [u32; 64] = [
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5,
9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10,
15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
];
const K: [u32; 64] = [
0xd76a_a478,
0xe8c7_b756,
0x2420_70db,
0xc1bd_ceee,
0xf57c_0faf,
0x4787_c62a,
0xa830_4613,
0xfd46_9501,
0x6980_98d8,
0x8b44_f7af,
0xffff_5bb1,
0x895c_d7be,
0x6b90_1122,
0xfd98_7193,
0xa679_438e,
0x49b4_0821,
0xf61e_2562,
0xc040_b340,
0x265e_5a51,
0xe9b6_c7aa,
0xd62f_105d,
0x0244_1453,
0xd8a1_e681,
0xe7d3_fbc8,
0x21e1_cde6,
0xc337_07d6,
0xf4d5_0d87,
0x455a_14ed,
0xa9e3_e905,
0xfcef_a3f8,
0x676f_02d9,
0x8d2a_4c8a,
0xfffa_3942,
0x8771_f681,
0x6d9d_6122,
0xfde5_380c,
0xa4be_ea44,
0x4bde_cfa9,
0xf6bb_4b60,
0xbebf_bc70,
0x289b_7ec6,
0xeaa1_27fa,
0xd4ef_3085,
0x0488_1d05,
0xd9d4_d039,
0xe6db_99e5,
0x1fa2_7cf8,
0xc4ac_5665,
0xf429_2244,
0x432a_ff97,
0xab94_23a7,
0xfc93_a039,
0x655b_59c3,
0x8f0c_cc92,
0xffef_f47d,
0x8584_5dd1,
0x6fa8_7e4f,
0xfe2c_e6e0,
0xa301_4314,
0x4e08_11a1,
0xf753_7e82,
0xbd3a_f235,
0x2ad7_d2bb,
0xeb86_d391,
];
pub fn digest(data: &[u8]) -> [u8; 16] {
let mut message = data.to_vec();
let bit_len = (data.len() as u64).wrapping_mul(8);
message.push(0x80);
while message.len() % 64 != 56 {
message.push(0);
}
message.extend_from_slice(&bit_len.to_le_bytes());
let mut a0: u32 = 0x6745_2301;
let mut b0: u32 = 0xefcd_ab89;
let mut c0: u32 = 0x98ba_dcfe;
let mut d0: u32 = 0x1032_5476;
for chunk in message.as_chunks::<64>().0 {
let mut m = [0u32; 16];
for (i, word) in m.iter_mut().enumerate() {
*word = u32::from_le_bytes([
chunk[i * 4],
chunk[i * 4 + 1],
chunk[i * 4 + 2],
chunk[i * 4 + 3],
]);
}
let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
for i in 0..64 {
let (f, g) = match i / 16 {
0 => ((b & c) | (!b & d), i),
1 => ((d & b) | (!d & c), (5 * i + 1) % 16),
2 => (b ^ c ^ d, (3 * i + 5) % 16),
_ => (c ^ (b | !d), (7 * i) % 16),
};
let temp = d;
d = c;
c = b;
let sum = a.wrapping_add(f).wrapping_add(K[i]).wrapping_add(m[g]);
b = b.wrapping_add(sum.rotate_left(S[i]));
a = temp;
}
a0 = a0.wrapping_add(a);
b0 = b0.wrapping_add(b);
c0 = c0.wrapping_add(c);
d0 = d0.wrapping_add(d);
}
let mut out = [0u8; 16];
out[0..4].copy_from_slice(&a0.to_le_bytes());
out[4..8].copy_from_slice(&b0.to_le_bytes());
out[8..12].copy_from_slice(&c0.to_le_bytes());
out[12..16].copy_from_slice(&d0.to_le_bytes());
out
}
}
#[cfg(test)]
mod tests {
use super::*;
const CORPUS: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/data/corpus/");
fn params(list: &[&str]) -> Vec<String> {
list.iter().map(|s| s.to_string()).collect()
}
#[test]
fn version_command_prints_the_tool_version() {
assert_eq!(main_with_args(¶ms(&["version"])), 0);
assert_eq!(main_with_args(¶ms(&["--version"])), 0);
}
#[test]
fn help_command_succeeds() {
assert_eq!(main_with_args(¶ms(&["help"])), 0);
assert_eq!(main_with_args(¶ms(&["--help"])), 0);
}
#[test]
fn unsupported_command_reports_the_java_message() {
assert_eq!(main_with_args(¶ms(&["frobnicate"])), 1);
}
#[test]
fn verify_missing_option_value_is_reported() {
let err = verify(¶ms(&["--min-sdk-version"])).unwrap_err();
assert_eq!(
err.to_string(),
"Mininimum API Level missing after --min-sdk-version"
);
}
#[test]
fn verify_unsupported_option_is_reported() {
let err = verify(¶ms(&["--nope", "app.apk"])).unwrap_err();
assert_eq!(
err.to_string(),
"Unsupported option: --nope. See --help for supported options."
);
}
#[test]
fn verify_requires_an_apk() {
let err = verify(¶ms(&["--verbose"])).unwrap_err();
assert_eq!(err.to_string(), "Missing APK");
}
#[test]
fn verify_rejects_extra_positional_parameters() {
let err = verify(¶ms(&["a.apk", "b.apk"])).unwrap_err();
assert_eq!(err.to_string(), "Unexpected parameter(s) after APK (b.apk)");
}
#[test]
fn verify_rejects_inverted_sdk_range() {
let err = verify(¶ms(&[
"--min-sdk-version",
"30",
"--max-sdk-version",
"20",
"a.apk",
]))
.unwrap_err();
assert_eq!(err.to_string(), "Min API Level (30) > max API Level (20)");
}
#[test]
fn verify_help_flag_prints_usage_and_succeeds() {
assert_eq!(verify(¶ms(&["--help"])).unwrap(), 0);
}
#[test]
fn verify_with_no_arguments_prints_usage() {
assert_eq!(verify(&[]).unwrap(), 0);
}
#[test]
fn sign_requires_at_least_one_signer() {
let err = sign(¶ms(&["--out", "out.apk", "in.apk"])).unwrap_err();
assert_eq!(err.to_string(), "At least one signer must be specified");
}
#[test]
fn sign_requires_an_input_apk() {
let err = sign(¶ms(&["--ks", "release.jks"])).unwrap_err();
assert_eq!(err.to_string(), "Missing input APK");
}
#[test]
fn sign_rejects_unsupported_options() {
let err = sign(¶ms(&["--bogus", "in.apk"])).unwrap_err();
assert_eq!(
err.to_string(),
"Unsupported option: --bogus. See --help for supported options."
);
}
#[test]
fn sign_rejects_an_inverted_sdk_range() {
let err = sign(¶ms(&[
"--ks",
"release.jks",
"--min-sdk-version",
"30",
"--max-sdk-version",
"20",
"in.apk",
]))
.unwrap_err();
assert_eq!(err.to_string(), "Min API Level (30) > max API Level (20)");
}
#[test]
fn sign_rejects_an_unsupported_pass_encoding() {
let err = sign(¶ms(&[
"--ks",
"release.jks",
"--pass-encoding",
"ibm437",
"in.apk",
]))
.unwrap_err();
assert_eq!(
err.to_string(),
"Unsupported password character encoding requested using --pass-encoding: ibm437"
);
}
#[test]
fn sign_rejects_a_signer_without_arguments() {
let err = sign(¶ms(&[
"--ks",
"release.jks",
"--stamp-signer",
"--out",
"out.apk",
"in.apk",
]))
.unwrap_err();
assert_eq!(err.to_string(), "Signer specified without arguments");
}
#[test]
fn full_sign_option_set_parses_without_error() {
let mut parser = OptionsParser::new(¶ms(&[
"--ks",
"release.jks",
"--ks-key-alias",
"release",
"--ks-pass",
"pass:secret",
"--key-pass",
"pass:secret",
"--pass-encoding",
"utf-8",
"--v1-signing-enabled",
"false",
"--v2-signing-enabled",
"true",
"--v3-signing-enabled",
"true",
"--v4-signing-enabled",
"false",
"--min-sdk-version",
"24",
"--max-sdk-version",
"34",
"--rotation-min-sdk-version",
"33",
"--rotation-targets-dev-release",
"--lib-page-alignment",
"16384",
]));
let signer = process_signer_params(&mut parser).unwrap();
assert_eq!(signer.get_keystore_file(), Some("release.jks"));
assert_eq!(signer.get_keystore_key_alias(), Some("release"));
assert_eq!(signer.get_keystore_password_spec(), Some("pass:secret"));
assert_eq!(signer.get_key_password_spec(), Some("pass:secret"));
assert!(signer.get_password_charset().is_some());
assert_eq!(
parser.next_option().unwrap(),
Some("v1-signing-enabled".to_string())
);
assert!(!parser.get_optional_boolean_value(true).unwrap());
assert_eq!(
parser.next_option().unwrap(),
Some("v2-signing-enabled".to_string())
);
assert!(parser.get_optional_boolean_value(true).unwrap());
assert_eq!(
parser.next_option().unwrap(),
Some("v3-signing-enabled".to_string())
);
assert!(parser.get_optional_boolean_value(true).unwrap());
assert_eq!(
parser.next_option().unwrap(),
Some("v4-signing-enabled".to_string())
);
assert!(!parser.get_optional_boolean_value(true).unwrap());
assert_eq!(
parser.next_option().unwrap(),
Some("min-sdk-version".to_string())
);
assert_eq!(
parser
.get_required_int_value("Mininimum API Level")
.unwrap(),
24
);
assert_eq!(
parser.next_option().unwrap(),
Some("max-sdk-version".to_string())
);
assert_eq!(
parser.get_required_int_value("Maximum API Level").unwrap(),
34
);
assert_eq!(
parser.next_option().unwrap(),
Some("rotation-min-sdk-version".to_string())
);
assert_eq!(
parser
.get_required_int_value("Minimum API Level for Rotation")
.unwrap(),
33
);
assert_eq!(
parser.next_option().unwrap(),
Some("rotation-targets-dev-release".to_string())
);
assert!(parser.get_optional_boolean_value(true).unwrap());
assert_eq!(
parser.next_option().unwrap(),
Some("lib-page-alignment".to_string())
);
assert_eq!(
parser
.get_required_int_value("Native library page alignment size in bytes")
.unwrap(),
16384
);
assert_eq!(parser.next_option().unwrap(), None);
}
#[test]
fn rotate_requires_old_and_new_signers() {
let err = rotate(¶ms(&["--out", "lineage"])).unwrap_err();
assert_eq!(
err.to_string(),
"Signer parameters for old signer not present"
);
}
#[test]
fn rotate_requires_an_output_lineage_file() {
let err = rotate(¶ms(&[
"--old-signer",
"--ks",
"old.jks",
"--new-signer",
"--ks",
"new.jks",
]))
.unwrap_err();
assert_eq!(err.to_string(), "Output lineage file parameter not present");
}
#[test]
fn lineage_requires_an_input_lineage_file() {
let err = lineage(¶ms(&["--out", "lineage"])).unwrap_err();
assert_eq!(err.to_string(), "Input lineage file parameter not present");
}
#[test]
fn process_signer_params_collects_capability_flags() {
let mut parser = OptionsParser::new(¶ms(&[
"--ks",
"release.jks",
"--set-installed-data",
"false",
"--set-rollback",
"true",
"--out",
]));
let signer = process_signer_params(&mut parser).unwrap();
let caps = signer.get_signer_capabilities_builder().build();
assert!(!caps.has_installed_data());
assert!(caps.has_rollback());
assert_eq!(parser.next_option().unwrap(), Some("out".to_string()));
}
#[test]
fn get_lineage_from_input_file_rejects_short_files() {
let dir = std::env::temp_dir();
let path = dir.join("apksigner_lineage_short_test.bin");
std::fs::write(&path, b"ab").unwrap();
let err = get_lineage_from_input_file(path.to_str().unwrap()).unwrap_err();
assert_eq!(
err.to_string(),
"The input file is not a valid lineage file."
);
std::fs::remove_file(&path).ok();
}
#[test]
fn get_lineage_from_input_file_rejects_unknown_magic() {
let dir = std::env::temp_dir();
let path = dir.join("apksigner_lineage_bad_magic_test.bin");
std::fs::write(&path, b"XXXXXXXX").unwrap();
let err = get_lineage_from_input_file(path.to_str().unwrap()).unwrap_err();
assert_eq!(
err.to_string(),
"The input file is not a valid lineage file."
);
std::fs::remove_file(&path).ok();
}
#[test]
fn get_lineage_from_input_file_dispatches_on_the_lineage_magic() {
let dir = std::env::temp_dir();
let path = dir.join("apksigner_lineage_magic_test.bin");
let mut bytes = SIGNING_CERTIFICATE_LINEAGE_MAGIC.to_le_bytes().to_vec();
bytes.extend_from_slice(b"payload");
std::fs::write(&path, &bytes).unwrap();
let err = get_lineage_from_input_file(path.to_str().unwrap()).unwrap_err();
assert_ne!(
err.to_string(),
"The input file is not a valid lineage file."
);
std::fs::remove_file(&path).ok();
}
#[test]
fn get_lineage_from_input_file_dispatches_on_the_zip_magic() {
let dir = std::env::temp_dir();
let path = dir.join("apksigner_lineage_zip_magic_test.bin");
let mut bytes = ZIP_MAGIC.to_le_bytes().to_vec();
bytes.extend_from_slice(b"payload");
std::fs::write(&path, &bytes).unwrap();
let err = get_lineage_from_input_file(path.to_str().unwrap()).unwrap_err();
assert_ne!(
err.to_string(),
"The input file is not a valid lineage file."
);
std::fs::remove_file(&path).ok();
}
#[test]
fn base64_round_trips() {
for case in [
Vec::new(),
b"f".to_vec(),
b"fo".to_vec(),
b"foo".to_vec(),
b"foob".to_vec(),
b"fooba".to_vec(),
b"foobar".to_vec(),
] {
let encoded = base64::encode(&case);
assert_eq!(base64::decode(encoded.as_bytes()).unwrap(), case);
}
}
#[test]
fn base64_matches_rfc_4648_test_vectors() {
assert_eq!(base64::encode(b""), "");
assert_eq!(base64::encode(b"f"), "Zg==");
assert_eq!(base64::encode(b"fo"), "Zm8=");
assert_eq!(base64::encode(b"foo"), "Zm9v");
assert_eq!(base64::encode(b"foob"), "Zm9vYg==");
assert_eq!(base64::encode(b"fooba"), "Zm9vYmE=");
assert_eq!(base64::encode(b"foobar"), "Zm9vYmFy");
}
#[test]
fn base64_rejects_invalid_characters() {
assert!(base64::decode(b"Zm9v!!").is_err());
}
#[test]
fn md5_matches_rfc_1321_test_vectors() {
assert_eq!(
HexEncoding::encode(&md5::digest(b"")),
"d41d8cd98f00b204e9800998ecf8427e"
);
assert_eq!(
HexEncoding::encode(&md5::digest(b"abc")),
"900150983cd24fb0d6963f7d28e17f72"
);
assert_eq!(
HexEncoding::encode(&md5::digest(b"message digest")),
"f96b697d7cb7938d525a2f31aaf161d0"
);
assert_eq!(
HexEncoding::encode(&md5::digest(b"abcdefghijklmnopqrstuvwxyz")),
"c3fcd3d76192e4007dfb496cca67e13b"
);
assert_eq!(
HexEncoding::encode(&md5::digest(
b"12345678901234567890123456789012345678901234567890123456789012345678901234567890"
)),
"57edf4a22be3c955ac49da2e2107b67a"
);
}
#[test]
fn sha_helpers_match_known_values() {
assert_eq!(
HexEncoding::encode(&sha1(b"abc")),
"a9993e364706816aba3e25717850c26c9cd0d89d"
);
assert_eq!(
HexEncoding::encode(&sha256(b"abc")),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
#[test]
fn short_attribute_names_follow_rfc_2253() {
assert_eq!(short_attribute_name("2.5.4.3"), "CN");
assert_eq!(short_attribute_name("2.5.4.10"), "O");
assert_eq!(short_attribute_name("1.2.840.113549.1.9.1"), "EMAILADDRESS");
assert_eq!(short_attribute_name("1.2.3.4"), "1.2.3.4");
}
#[test]
fn escapes_reserved_characters_in_dn_values() {
assert_eq!(escape_rfc2253("a,b"), "a\\,b");
assert_eq!(escape_rfc2253(" a"), "\\ a");
assert_eq!(escape_rfc2253("a "), "a\\ ");
assert_eq!(escape_rfc2253("plain"), "plain");
}
#[test]
fn ec_curve_oids_map_to_order_bit_lengths() {
assert_eq!(ec_curve_order_bits("1.2.840.10045.3.1.7"), Some(256));
assert_eq!(ec_curve_order_bits("1.3.132.0.34"), Some(384));
assert_eq!(ec_curve_order_bits("1.3.132.0.35"), Some(521));
assert_eq!(ec_curve_order_bits("1.2.3.4"), None);
}
#[test]
fn escaping_apk_format_exceptions_render_the_recorded_jvm_report() {
const FIXTURES: &[&str] = &[
"empty-unsigned",
"v1-only-empty",
"v2-only-empty",
"v3-only-empty",
"invalid_manifest",
"v2-only-truncated-cd",
"v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip",
"incorrect-manifest-size",
];
for fixture in FIXTURES {
let apk = format!("{CORPUS}{fixture}.apk");
let recorded = std::fs::read_to_string(format!(
"{}/tests/data/java-traces/{fixture}.stderr",
env!("CARGO_MANIFEST_DIR")
))
.expect("recorded oracle stderr");
let error = run_verification(&apk, true, 24, false, i32::MAX, None, false, None)
.err()
.unwrap_or_else(|| panic!("{fixture}: expected verification to fail outright"));
assert_eq!(
error,
CliError::Message(recorded.trim_end_matches('\n').to_string()),
"fixture {fixture}"
);
}
}
#[test]
fn distinguished_name_is_rendered_in_rfc2253_order() {
const RDN_SEQUENCE: &[u8] = &[
0x30, 0x1c, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x03, b'R', b'o',
b't', 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x03, b'T', b'w',
b'o', ];
let name = apksig::internal::x509::Name::from_der(RDN_SEQUENCE).expect("valid RDNSequence");
assert_eq!(render_distinguished_name(&name), "CN=Two, O=Rot");
}
#[test]
fn unobserved_apk_format_failures_keep_the_plain_message() {
let error = run_verification(
"/nonexistent/corpus.apk",
true,
24,
false,
i32::MAX,
None,
false,
None,
)
.err()
.expect("a missing file must be an error");
assert!(
matches!(&error, CliError::Message(m) if m.starts_with("Failed to open APK ")),
"unexpected error: {error:?}"
);
}
#[test]
fn rsa_modulus_bit_length_handles_the_leading_zero_pad() {
let mut modulus = vec![0x02, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff];
let exponent = vec![0x02, 0x03, 0x01, 0x00, 0x01];
let mut body = Vec::new();
body.append(&mut modulus);
body.extend_from_slice(&exponent);
let mut der = vec![0x30, body.len() as u8];
der.extend_from_slice(&body);
assert_eq!(rsa_modulus_bit_length(&der), Some(32));
}
}