use apksig::key_config::{Jca, KeyConfig, Kms, PrivateKey};
use apksig::signing_certificate_lineage::{SignerCapabilitiesBuilder, SigningCertificateLineage};
use std::path::Path;
use crate::encrypted_private_key_info;
use crate::keystore::KeyStoreError;
use crate::parameter_exception::ParameterException;
use crate::password_retriever::{Charset, PasswordRetriever, SPEC_STDIN};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SignerLoadError {
Parameter(ParameterException),
Other(String),
}
impl std::fmt::Display for SignerLoadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SignerLoadError::Parameter(e) => write!(f, "{}", e),
SignerLoadError::Other(m) => write!(f, "{}", m),
}
}
}
impl std::error::Error for SignerLoadError {}
impl From<ParameterException> for SignerLoadError {
fn from(e: ParameterException) -> Self {
SignerLoadError::Parameter(e)
}
}
#[derive(Debug, Clone, Default)]
pub struct SignerParams {
name: Option<String>,
keystore_file: Option<String>,
keystore_key_alias: Option<String>,
keystore_password_spec: Option<String>,
key_password_spec: Option<String>,
password_charset: Option<Charset>,
keystore_type: Option<String>,
keystore_provider_name: Option<String>,
keystore_provider_class: Option<String>,
keystore_provider_arg: Option<String>,
key_file: Option<String>,
cert_file: Option<String>,
kms_type: Option<String>,
kms_key_alias: Option<String>,
v1_sig_file_basename: Option<String>,
key_config: Option<KeyConfig>,
certs: Option<Vec<Vec<u8>>>,
signer_capabilities_builder: SignerCapabilitiesBuilder,
min_sdk_version: i32,
signing_certificate_lineage: Option<SigningCertificateLineage>,
}
impl SignerParams {
pub fn new() -> Self {
SignerParams::default()
}
pub fn get_name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn set_name(&mut self, name: impl Into<String>) {
self.name = Some(name.into());
}
pub fn set_keystore_file(&mut self, keystore_file: impl Into<String>) {
self.keystore_file = Some(keystore_file.into());
}
#[cfg(test)]
pub fn get_keystore_file(&self) -> Option<&str> {
self.keystore_file.as_deref()
}
pub fn get_keystore_key_alias(&self) -> Option<&str> {
self.keystore_key_alias.as_deref()
}
pub fn set_keystore_key_alias(&mut self, keystore_key_alias: impl Into<String>) {
self.keystore_key_alias = Some(keystore_key_alias.into());
}
pub fn set_keystore_password_spec(&mut self, keystore_password_spec: impl Into<String>) {
self.keystore_password_spec = Some(keystore_password_spec.into());
}
#[cfg(test)]
pub fn get_keystore_password_spec(&self) -> Option<&str> {
self.keystore_password_spec.as_deref()
}
pub fn set_key_password_spec(&mut self, key_password_spec: impl Into<String>) {
self.key_password_spec = Some(key_password_spec.into());
}
#[cfg(test)]
pub fn get_key_password_spec(&self) -> Option<&str> {
self.key_password_spec.as_deref()
}
pub fn set_password_charset(&mut self, password_charset: Charset) {
self.password_charset = Some(password_charset);
}
#[cfg(test)]
pub fn get_password_charset(&self) -> Option<Charset> {
self.password_charset
}
pub fn set_keystore_type(&mut self, keystore_type: impl Into<String>) {
self.keystore_type = Some(keystore_type.into());
}
pub fn set_keystore_provider_name(&mut self, keystore_provider_name: impl Into<String>) {
self.keystore_provider_name = Some(keystore_provider_name.into());
}
pub fn set_keystore_provider_class(&mut self, keystore_provider_class: impl Into<String>) {
self.keystore_provider_class = Some(keystore_provider_class.into());
}
pub fn set_keystore_provider_arg(&mut self, keystore_provider_arg: impl Into<String>) {
self.keystore_provider_arg = Some(keystore_provider_arg.into());
}
pub fn get_key_file(&self) -> Option<&str> {
self.key_file.as_deref()
}
pub fn set_key_file(&mut self, key_file: impl Into<String>) {
self.key_file = Some(key_file.into());
}
pub fn set_cert_file(&mut self, cert_file: impl Into<String>) {
self.cert_file = Some(cert_file.into());
}
pub fn set_kms_type(&mut self, kms_type: impl Into<String>) {
self.kms_type = Some(kms_type.into());
}
pub fn get_kms_key_alias(&self) -> Option<&str> {
self.kms_key_alias.as_deref()
}
pub fn set_kms_key_alias(&mut self, kms_key_alias: impl Into<String>) {
self.kms_key_alias = Some(kms_key_alias.into());
}
pub fn get_v1_sig_file_basename(&self) -> Option<&str> {
self.v1_sig_file_basename.as_deref()
}
pub fn set_v1_sig_file_basename(&mut self, v1_sig_file_basename: impl Into<String>) {
self.v1_sig_file_basename = Some(v1_sig_file_basename.into());
}
pub fn get_key_config(&self) -> Option<&KeyConfig> {
self.key_config.as_ref()
}
pub fn get_certs(&self) -> Option<&[Vec<u8>]> {
self.certs.as_deref()
}
pub fn get_signer_capabilities_builder(&self) -> &SignerCapabilitiesBuilder {
&self.signer_capabilities_builder
}
pub fn get_signer_capabilities_builder_mut(&mut self) -> &mut SignerCapabilitiesBuilder {
&mut self.signer_capabilities_builder
}
pub fn get_min_sdk_version(&self) -> i32 {
self.min_sdk_version
}
pub fn set_min_sdk_version(&mut self, min_sdk_version: i32) {
self.min_sdk_version = min_sdk_version;
}
pub fn get_signing_certificate_lineage(&self) -> Option<&SigningCertificateLineage> {
self.signing_certificate_lineage.as_ref()
}
pub fn set_signing_certificate_lineage(&mut self, lineage: SigningCertificateLineage) {
self.signing_certificate_lineage = Some(lineage);
}
pub fn is_empty(&self) -> bool {
self.name.is_none()
&& self.keystore_file.is_none()
&& self.keystore_key_alias.is_none()
&& self.keystore_password_spec.is_none()
&& self.key_password_spec.is_none()
&& self.password_charset.is_none()
&& self.keystore_type.is_none()
&& self.keystore_provider_name.is_none()
&& self.keystore_provider_class.is_none()
&& self.keystore_provider_arg.is_none()
&& self.key_file.is_none()
&& self.cert_file.is_none()
&& self.v1_sig_file_basename.is_none()
&& self.key_config.is_none()
&& self.certs.is_none()
&& self.kms_type.is_none()
&& self.kms_key_alias.is_none()
}
pub fn load_private_key_and_certs(
&mut self,
password_retriever: &mut PasswordRetriever,
) -> Result<(), SignerLoadError> {
if let Some(kms_type) = self.kms_type.clone() {
let Some(kms_key_alias) = self.kms_key_alias.clone() else {
return Err(ParameterException::new(
"kms key alias (--kms-key-alias) is required if kms type (--kms-type) is \
provided",
)
.into());
};
self.certs = Some(load_certs_from_file(self.cert_file.as_deref())?);
self.key_config = Some(KeyConfig::Kms(Kms::new(kms_type, kms_key_alias)));
return Ok(());
}
if self.keystore_file.is_some() {
if self.key_file.is_some() {
return Err(ParameterException::new(
"--ks and --key may not be specified at the same time",
)
.into());
}
if self.cert_file.is_some() {
return Err(ParameterException::new(
"--ks and --cert may not be specified at the same time",
)
.into());
}
return self.load_private_key_and_certs_from_keystore(password_retriever);
}
if self.key_file.is_some() {
return self.load_private_key_and_certs_from_files(password_retriever);
}
Err(ParameterException::new(
"KeyStore (--ks), private key file (--key), or KMS key alias and type \
(--kms-key-alias and --kms-type) must be specified",
)
.into())
}
fn display_name(&self) -> String {
self.name.clone().unwrap_or_else(|| "null".to_string())
}
fn keystore_error(&self, keystore_file: &str, error: &KeyStoreError) -> SignerLoadError {
match error {
KeyStoreError::NoKeyEntry(alias) | KeyStoreError::NotAKeyEntry(alias) => {
ParameterException::new(format!(
"{} entry \"{}\" does not contain a key",
keystore_file, alias
))
.into()
}
KeyStoreError::NotAPrivateKey { alias, algorithm } => ParameterException::new(format!(
"{} entry \"{}\" does not contain a private key. It contains a key of \
algorithm: {}",
keystore_file, alias, algorithm
))
.into(),
other => SignerLoadError::Other(other.to_string()),
}
}
fn load_private_key_and_certs_from_keystore(
&mut self,
password_retriever: &mut PasswordRetriever,
) -> Result<(), SignerLoadError> {
let Some(keystore_file) = self.keystore_file.clone() else {
return Err(ParameterException::new("KeyStore (--ks) must be specified").into());
};
let additional_password_encodings: Vec<Charset> =
self.password_charset.into_iter().collect();
let _ = (
&self.keystore_provider_name,
&self.keystore_provider_class,
&self.keystore_provider_arg,
);
if keystore_file == "NONE" {
return Err(SignerLoadError::Other(
"KeyStore file \"NONE\" (a non-file-backed KeyStore, such as PKCS #11) is not \
supported: this port has no JCA provider registry. Specify a JKS or PKCS #12 \
file instead."
.to_string(),
));
}
let keystore_password_spec = self
.keystore_password_spec
.clone()
.unwrap_or_else(|| SPEC_STDIN.to_string());
let keystore_passwords = password_retriever
.get_passwords(
&keystore_password_spec,
&format!("Keystore password for {}", self.display_name()),
&additional_password_encodings,
)
.map_err(|e| SignerLoadError::Other(e.to_string()))?;
let keystore = crate::keystore::KeyStore::load_from_file(
&keystore_file,
&keystore_passwords,
self.keystore_type.as_deref(),
)
.map_err(|e| self.keystore_error(&keystore_file, &e))?;
let mut key_alias = self.keystore_key_alias.clone();
if key_alias.is_none() {
let mut found: Option<String> = None;
for entry_alias in keystore.aliases() {
if keystore.is_key_entry(&entry_alias) {
if found.is_some() {
return Err(ParameterException::new(format!(
"{} contains multiple key entries. --ks-key-alias option must be used \
to specify which entry to use.",
keystore_file
))
.into());
}
found = Some(entry_alias);
}
}
let Some(found) = found else {
return Err(ParameterException::new(format!(
"{} does not contain key entries",
keystore_file
))
.into());
};
key_alias = Some(found);
}
let key_alias = key_alias.expect("assigned when the alias was absent");
if !keystore.is_key_entry(&key_alias) {
return Err(ParameterException::new(format!(
"{} entry \"{}\" does not contain a key",
keystore_file, key_alias
))
.into());
}
let key_description = format!("Key \"{}\" password for {}", key_alias, self.display_name());
let entry_key = if let Some(key_password_spec) = self.key_password_spec.clone() {
let key_passwords = password_retriever
.get_passwords(
&key_password_spec,
&key_description,
&additional_password_encodings,
)
.map_err(|e| SignerLoadError::Other(e.to_string()))?;
keystore.get_key(&key_alias, &key_passwords)
} else {
match keystore.get_key(&key_alias, &keystore_passwords) {
Ok(key) => Ok(key),
Err(_unrecoverable_key) => {
let key_passwords = password_retriever
.get_passwords(SPEC_STDIN, &key_description, &additional_password_encodings)
.map_err(|e| SignerLoadError::Other(e.to_string()))?;
keystore.get_key(&key_alias, &key_passwords)
}
}
};
let key = entry_key.map_err(|e| match e {
KeyStoreError::UnrecoverableKey(_) => SignerLoadError::Other(format!(
"Failed to obtain key with alias \"{}\" from {}. Wrong password?",
key_alias, keystore_file
)),
other => self.keystore_error(&keystore_file, &other),
})?;
let cert_chain = keystore
.get_certificate_chain(&key_alias)
.map_err(|e| self.keystore_error(&keystore_file, &e))?;
if cert_chain.is_empty() {
return Err(ParameterException::new(format!(
"{} entry \"{}\" does not contain certificates",
keystore_file, key_alias
))
.into());
}
let private_key = encrypted_private_key_info::load_pkcs8_encoded_private_key(&key)
.map_err(|e| {
SignerLoadError::Other(format!(
"Failed to load PKCS #8 encoded private key from {}: {}",
keystore_file, e
))
})?;
self.key_config = Some(KeyConfig::Jca(Jca::new(private_key)));
self.certs = Some(cert_chain);
Ok(())
}
fn load_private_key_and_certs_from_files(
&mut self,
password_retriever: &mut PasswordRetriever,
) -> Result<(), SignerLoadError> {
self.certs = Some(load_certs_from_file(self.cert_file.as_deref())?);
let private_key = self.load_private_key_from_file(password_retriever)?;
self.key_config = Some(KeyConfig::Jca(Jca::new(private_key)));
Ok(())
}
fn load_private_key_from_file(
&self,
password_retriever: &mut PasswordRetriever,
) -> Result<PrivateKey, SignerLoadError> {
let Some(key_file) = self.key_file.clone() else {
return Err(
ParameterException::new("Private key file (--key) must be specified").into(),
);
};
let private_key_blob = std::fs::read(&key_file).map_err(|e| {
SignerLoadError::Other(format!(
"Failed to read private key file {}: {}",
key_file, e
))
})?;
let key_spec: Vec<u8> =
if encrypted_private_key_info::is_encrypted_private_key_info(&private_key_blob) {
let password_spec = self
.key_password_spec
.clone()
.unwrap_or_else(|| SPEC_STDIN.to_string());
let additional_password_encodings: Vec<Charset> =
self.password_charset.into_iter().collect();
let key_passwords = password_retriever
.get_passwords(
&password_spec,
&format!("Private key password for {}", self.display_name()),
&additional_password_encodings,
)
.map_err(|e| SignerLoadError::Other(e.to_string()))?;
encrypted_private_key_info::decrypt_pkcs8_encoded_key(
&private_key_blob,
&key_passwords,
)
.map_err(|e| {
SignerLoadError::Other(format!(
"Failed to parse encrypted private key blob {}: {}",
key_file, e
))
})?
} else if self.key_password_spec.is_none() {
private_key_blob
} else {
return Err(SignerLoadError::Other(format!(
"Failed to parse encrypted private key blob {}",
key_file
)));
};
encrypted_private_key_info::load_pkcs8_encoded_private_key(&key_spec).map_err(|e| {
SignerLoadError::Other(format!(
"Failed to load PKCS #8 encoded private key from {}: {}",
key_file, e
))
})
}
}
pub fn load_certs_from_file(cert_file: Option<&str>) -> Result<Vec<Vec<u8>>, SignerLoadError> {
let Some(cert_file) = cert_file else {
return Err(ParameterException::new("Certificate file (--cert) must be specified").into());
};
let bytes = std::fs::read(cert_file).map_err(|e| {
SignerLoadError::Other(format!(
"Failed to read certificate file {}: {}",
cert_file, e
))
})?;
read_certificates(&bytes)
.map_err(|e| SignerLoadError::Other(format!("Failed to read {}: {}", cert_file, e)))
}
pub fn read_certificates(bytes: &[u8]) -> Result<Vec<Vec<u8>>, String> {
let pems = pem_blocks(bytes);
let certs: Vec<Vec<u8>> = if pems.is_empty() {
split_concatenated_der(bytes)?
} else {
pems
};
if certs.is_empty() {
return Err("No certificates found".to_string());
}
for cert in &certs {
parse_certificate(cert)?;
}
Ok(certs)
}
fn pem_blocks(data: &[u8]) -> Vec<Vec<u8>> {
const BEGIN: &[u8] = b"-----BEGIN CERTIFICATE-----";
const END: &[u8] = b"-----END CERTIFICATE-----";
let mut blocks = Vec::new();
let mut rest = data;
while let Some(start) = find_subslice(rest, BEGIN) {
rest = &rest[start + BEGIN.len()..];
let Some(end) = find_subslice(rest, END) else {
break;
};
let body = &rest[..end];
rest = &rest[end + END.len()..];
let cleaned: Vec<u8> = body
.iter()
.copied()
.filter(|b| !b.is_ascii_whitespace())
.collect();
if let Ok(decoded) = crate::apk_signer_tool::base64::decode(&cleaned) {
blocks.push(decoded);
}
}
blocks
}
fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
if needle.is_empty() || haystack.len() < needle.len() {
return None;
}
haystack
.windows(needle.len())
.position(|window| window == needle)
}
fn split_concatenated_der(bytes: &[u8]) -> Result<Vec<Vec<u8>>, String> {
let mut reader = apksig::internal::pkcs7::der_utils::DerReader::new(bytes);
let mut out = Vec::new();
while !reader.is_empty() {
let Some(tlv_len) = reader.peek_tlv_len() else {
return Err("Malformed DER certificate".to_string());
};
let encoded = reader.read_any_encoded().map_err(|e| e.to_string())?;
debug_assert_eq!(encoded.len(), tlv_len);
out.push(encoded);
}
Ok(out)
}
fn parse_certificate(der: &[u8]) -> Result<(), String> {
apksig::internal::x509::Certificate::from_der(der)
.map(|_| ())
.map_err(|e| e.to_string())
}
pub fn v1_signature_basename(signer: &SignerParams) -> Option<String> {
if let Some(basename) = signer.get_v1_sig_file_basename() {
return Some(basename.to_string());
}
if let Some(alias) = signer.get_keystore_key_alias() {
return Some(alias.to_string());
}
if let Some(key_file) = signer.get_key_file() {
return Some(trim_extension(&file_name(key_file)));
}
if let Some(alias) = signer.get_kms_key_alias() {
return Some(alias.to_string());
}
None
}
pub fn file_name(path: &str) -> String {
Path::new(path)
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| path.to_string())
}
pub fn trim_extension(name: &str) -> String {
match name.find('.') {
Some(index) => name[..index].to_string(),
None => name.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_signer_params_is_empty() {
assert!(SignerParams::new().is_empty());
}
#[test]
fn setting_any_option_makes_signer_params_non_empty() {
let mut p = SignerParams::new();
p.set_keystore_file("release.jks");
assert!(!p.is_empty());
}
#[test]
fn name_alone_makes_signer_params_non_empty() {
let mut p = SignerParams::new();
p.set_name("signer #1");
assert!(!p.is_empty());
}
const PAST_CERT_INSTALLED_DATA: i32 = 1;
const PAST_CERT_SHARED_USER_ID: i32 = 2;
const PAST_CERT_PERMISSION: i32 = 4;
const PAST_CERT_AUTH: i32 = 16;
#[test]
fn default_capabilities_match_the_java_default_flags() {
let caps = SignerCapabilitiesBuilder::new().build();
assert_eq!(
caps.get_flags(),
PAST_CERT_INSTALLED_DATA
| PAST_CERT_PERMISSION
| PAST_CERT_SHARED_USER_ID
| PAST_CERT_AUTH
);
assert!(caps.has_installed_data());
assert!(caps.has_shared_uid());
assert!(caps.has_permission());
assert!(caps.has_auth());
assert!(!caps.has_rollback());
}
#[test]
fn setting_a_capability_false_clears_it_and_records_the_override() {
let mut builder = SignerCapabilitiesBuilder::new();
builder.set_installed_data(false);
let caps = builder.build();
assert!(!caps.has_installed_data());
assert_eq!(caps.get_caller_configured_flags(), PAST_CERT_INSTALLED_DATA);
}
#[test]
fn load_private_key_and_certs_requires_a_key_source() {
let mut p = SignerParams::new();
let mut retriever = PasswordRetriever::new();
let err = p.load_private_key_and_certs(&mut retriever).unwrap_err();
assert!(matches!(err, SignerLoadError::Parameter(_)));
}
#[test]
fn ks_and_key_may_not_both_be_specified() {
let mut p = SignerParams::new();
p.set_keystore_file("release.jks");
p.set_key_file("release.pk8");
let mut retriever = PasswordRetriever::new();
let err = p.load_private_key_and_certs(&mut retriever).unwrap_err();
assert_eq!(
err.to_string(),
"--ks and --key may not be specified at the same time"
);
}
#[test]
fn ks_and_cert_may_not_both_be_specified() {
let mut p = SignerParams::new();
p.set_keystore_file("release.jks");
p.set_cert_file("release.x509.pem");
let mut retriever = PasswordRetriever::new();
let err = p.load_private_key_and_certs(&mut retriever).unwrap_err();
assert_eq!(
err.to_string(),
"--ks and --cert may not be specified at the same time"
);
}
#[test]
fn kms_requires_a_key_alias() {
let mut p = SignerParams::new();
p.set_kms_type("GCP");
let mut retriever = PasswordRetriever::new();
let err = p.load_private_key_and_certs(&mut retriever).unwrap_err();
assert!(err.to_string().contains("kms key alias"));
}
#[test]
fn v1_basename_prefers_the_explicit_name() {
let mut p = SignerParams::new();
p.set_v1_sig_file_basename("CERT");
p.set_keystore_key_alias("release");
assert_eq!(v1_signature_basename(&p).as_deref(), Some("CERT"));
}
#[test]
fn v1_basename_falls_back_to_the_key_alias() {
let mut p = SignerParams::new();
p.set_keystore_key_alias("release");
assert_eq!(v1_signature_basename(&p).as_deref(), Some("release"));
}
#[test]
fn v1_basename_strips_the_key_file_extension() {
let mut p = SignerParams::new();
p.set_key_file("/path/to/release.pk8");
assert_eq!(v1_signature_basename(&p).as_deref(), Some("release"));
}
#[test]
fn v1_basename_falls_back_to_the_kms_alias() {
let mut p = SignerParams::new();
p.set_kms_key_alias("projects/p/locations/l/keyRings/r/cryptoKeys/k");
assert_eq!(
v1_signature_basename(&p).as_deref(),
Some("projects/p/locations/l/keyRings/r/cryptoKeys/k")
);
}
#[test]
fn v1_basename_is_absent_without_any_key_source() {
assert_eq!(v1_signature_basename(&SignerParams::new()), None);
}
#[test]
fn pem_blocks_extracts_der_payloads() {
let der = vec![0x30, 0x03, 0x02, 0x01, 0x2a];
let b64 = crate::apk_signer_tool::base64::encode(&der);
let pem = format!(
"-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----\n",
b64
);
let blocks = pem_blocks(pem.as_bytes());
assert_eq!(blocks, vec![der]);
}
#[test]
fn pem_blocks_ignores_non_certificate_blocks() {
let data = b"-----BEGIN PRIVATE KEY-----\nAAAA\n-----END PRIVATE KEY-----\n";
assert!(pem_blocks(data).is_empty());
}
#[test]
fn file_name_and_extension_trimming_match_the_java_basename_logic() {
assert_eq!(file_name("/a/b/release.pk8"), "release.pk8");
assert_eq!(trim_extension("release.pk8"), "release");
assert_eq!(trim_extension("release"), "release");
}
fn p256_pkcs8_der() -> Vec<u8> {
use pkcs8::EncodePrivateKey;
let scalar = [7u8; 32];
let key = p256::ecdsa::SigningKey::from_bytes(&scalar.into()).expect("valid scalar");
key.to_pkcs8_der().expect("PKCS #8").as_bytes().to_vec()
}
fn p256_public_key_spki() -> Vec<u8> {
let scalar = [7u8; 32];
let key = p256::ecdsa::SigningKey::from_bytes(&scalar.into()).expect("valid scalar");
PrivateKey::EcP256(Box::new(key))
.public_key_spki()
.expect("public key")
}
#[track_caller]
fn assert_key_config_is_the_p256_key(key_config: Option<&KeyConfig>) {
let Some(KeyConfig::Jca(jca)) = key_config else {
panic!("expected a JCA key config, got {:?}", key_config);
};
assert_eq!(
jca.private_key.public_key_spki().expect("public key"),
p256_public_key_spki()
);
}
fn write_jks(
file_name: &str,
keystore_password: &str,
entries: &[(&str, &str, Vec<u8>, Vec<Vec<u8>>)],
) -> std::path::PathBuf {
let mut store = jks::KeyStore::new();
for (alias, key_password, key, chain) in entries {
store
.set_private_key_entry(
alias,
jks::PrivateKeyEntry {
creation_time: std::time::SystemTime::UNIX_EPOCH,
private_key: key.clone(),
certificate_chain: chain
.iter()
.map(|content| jks::Certificate {
cert_type: "X509".to_string(),
content: content.clone(),
})
.collect(),
},
key_password.as_bytes(),
)
.expect("write key entry");
}
let mut encoded = Vec::new();
store
.store(&mut encoded, keystore_password.as_bytes())
.expect("encode keystore");
let path = std::env::temp_dir().join(file_name);
std::fs::write(&path, &encoded).expect("write keystore file");
path
}
#[test]
fn a_jks_keystore_supplies_the_key_and_certificate_chain() {
const CERT: [u8; 5] = [0x30, 0x03, 0x02, 0x01, 0x2a];
let key = p256_pkcs8_der();
let path = write_jks(
"apksigner_signer_params_one_entry.jks",
"store123",
&[("release", "key12345", key, vec![CERT.to_vec()])],
);
let mut params = SignerParams::new();
params.set_name("signer #1");
params.set_keystore_file(path.to_str().unwrap());
params.set_keystore_key_alias("release");
params.set_keystore_password_spec("pass:store123");
params.set_key_password_spec("pass:key12345");
let mut retriever = PasswordRetriever::new();
params
.load_private_key_and_certs(&mut retriever)
.expect("key material must load");
assert_key_config_is_the_p256_key(params.get_key_config());
assert_eq!(params.get_certs(), Some(&[CERT.to_vec()][..]));
std::fs::remove_file(&path).ok();
}
#[test]
fn the_key_entry_and_key_password_are_discovered_without_explicit_options() {
let path = write_jks(
"apksigner_signer_params_discovered.jks",
"samepass",
&[(
"release",
"samepass",
p256_pkcs8_der(),
vec![vec![0x30, 0x01, 0x00]],
)],
);
let mut params = SignerParams::new();
params.set_keystore_file(path.to_str().unwrap());
params.set_keystore_password_spec("pass:samepass");
let mut retriever = PasswordRetriever::new();
params
.load_private_key_and_certs(&mut retriever)
.expect("key material must load");
assert_key_config_is_the_p256_key(params.get_key_config());
std::fs::remove_file(&path).ok();
}
#[test]
fn a_wrong_keystore_password_is_reported() {
let path = write_jks(
"apksigner_signer_params_wrong_store_password.jks",
"store123",
&[("release", "key12345", vec![0x30, 0x01, 0x00], vec![])],
);
let mut params = SignerParams::new();
params.set_keystore_file(path.to_str().unwrap());
params.set_keystore_key_alias("release");
params.set_keystore_password_spec("pass:wrongpass");
params.set_key_password_spec("pass:key12345");
let mut retriever = PasswordRetriever::new();
let err = params
.load_private_key_and_certs(&mut retriever)
.unwrap_err();
assert!(err.to_string().contains("Failed to load the KeyStore"));
std::fs::remove_file(&path).ok();
}
#[test]
fn a_wrong_key_password_reports_the_wrong_password_message() {
let path = write_jks(
"apksigner_signer_params_wrong_key_password.jks",
"store123",
&[("release", "key12345", vec![0x30, 0x01, 0x00], vec![])],
);
let mut params = SignerParams::new();
params.set_keystore_file(path.to_str().unwrap());
params.set_keystore_key_alias("release");
params.set_keystore_password_spec("pass:store123");
params.set_key_password_spec("pass:notthekey");
let mut retriever = PasswordRetriever::new();
let err = params
.load_private_key_and_certs(&mut retriever)
.unwrap_err();
assert_eq!(
err.to_string(),
format!(
"Failed to obtain key with alias \"release\" from {}. Wrong password?",
path.to_str().unwrap()
)
);
std::fs::remove_file(&path).ok();
}
#[test]
fn multiple_key_entries_require_an_explicit_alias() {
let path = write_jks(
"apksigner_signer_params_two_entries.jks",
"store123",
&[
("one", "key12345", vec![0x30, 0x01, 0x01], vec![]),
("two", "key12345", vec![0x30, 0x01, 0x02], vec![]),
],
);
let mut params = SignerParams::new();
params.set_keystore_file(path.to_str().unwrap());
params.set_keystore_password_spec("pass:store123");
params.set_key_password_spec("pass:key12345");
let mut retriever = PasswordRetriever::new();
let err = params
.load_private_key_and_certs(&mut retriever)
.unwrap_err();
assert_eq!(
err.to_string(),
format!(
"{} contains multiple key entries. --ks-key-alias option must be used to specify \
which entry to use.",
path.to_str().unwrap()
)
);
std::fs::remove_file(&path).ok();
}
#[test]
fn an_unknown_alias_is_reported() {
let path = write_jks(
"apksigner_signer_params_unknown_alias.jks",
"store123",
&[("release", "key12345", vec![0x30, 0x01, 0x00], vec![])],
);
let mut params = SignerParams::new();
params.set_keystore_file(path.to_str().unwrap());
params.set_keystore_key_alias("nope");
params.set_keystore_password_spec("pass:store123");
params.set_key_password_spec("pass:key12345");
let mut retriever = PasswordRetriever::new();
let err = params
.load_private_key_and_certs(&mut retriever)
.unwrap_err();
assert_eq!(
err.to_string(),
format!(
"{} entry \"nope\" does not contain a key",
path.to_str().unwrap()
)
);
std::fs::remove_file(&path).ok();
}
#[test]
fn an_entry_without_certificates_is_reported() {
let path = write_jks(
"apksigner_signer_params_no_certs.jks",
"store123",
&[("release", "key12345", p256_pkcs8_der(), vec![])],
);
let mut params = SignerParams::new();
params.set_keystore_file(path.to_str().unwrap());
params.set_keystore_key_alias("release");
params.set_keystore_password_spec("pass:store123");
params.set_key_password_spec("pass:key12345");
let mut retriever = PasswordRetriever::new();
let err = params
.load_private_key_and_certs(&mut retriever)
.unwrap_err();
assert_eq!(
err.to_string(),
format!(
"{} entry \"release\" does not contain certificates",
path.to_str().unwrap()
)
);
std::fs::remove_file(&path).ok();
}
#[test]
fn a_non_file_backed_keystore_is_reported() {
let mut params = SignerParams::new();
params.set_keystore_file("NONE");
let mut retriever = PasswordRetriever::new();
let err = params
.load_private_key_and_certs(&mut retriever)
.unwrap_err();
assert!(err.to_string().contains("not supported"));
}
#[test]
fn an_unsupported_keystore_type_is_reported() {
let mut params = SignerParams::new();
params.set_keystore_file("/nonexistent/keystore.bks");
params.set_keystore_password_spec("pass:store123");
params.set_keystore_type("BKS");
let mut retriever = PasswordRetriever::new();
let err = params
.load_private_key_and_certs(&mut retriever)
.unwrap_err();
assert!(err.to_string().contains("not found"));
}
#[test]
fn an_unencrypted_pkcs8_key_file_loads() {
let path = std::env::temp_dir().join("apksigner_signer_params_plain.pk8");
std::fs::write(&path, p256_pkcs8_der()).expect("write key file");
let mut params = SignerParams::new();
params.set_key_file(path.to_str().unwrap());
let mut retriever = PasswordRetriever::new();
let loaded = params
.load_private_key_from_file(&mut retriever)
.expect("load private key");
assert_eq!(
loaded.public_key_spki().expect("public key"),
p256_public_key_spki()
);
std::fs::remove_file(&path).ok();
}
#[test]
fn a_blob_that_is_not_a_private_key_is_reported() {
let path = std::env::temp_dir().join("apksigner_signer_params_not_a_key.pk8");
std::fs::write(&path, [0x30u8, 0x01, 0x00]).expect("write key file");
let mut params = SignerParams::new();
params.set_key_file(path.to_str().unwrap());
let mut retriever = PasswordRetriever::new();
let err = params
.load_private_key_from_file(&mut retriever)
.unwrap_err();
assert!(
err.to_string()
.contains("Failed to load PKCS #8 encoded private key from")
);
std::fs::remove_file(&path).ok();
}
#[test]
fn a_key_password_spec_with_a_plain_blob_is_reported() {
let path = std::env::temp_dir().join("apksigner_signer_params_plain_with_password.pk8");
std::fs::write(&path, [0x30u8, 0x01, 0x00]).expect("write key file");
let mut params = SignerParams::new();
params.set_key_file(path.to_str().unwrap());
params.set_key_password_spec("pass:x");
let mut retriever = PasswordRetriever::new();
let err = params
.load_private_key_from_file(&mut retriever)
.unwrap_err();
assert_eq!(
err.to_string(),
format!(
"Failed to parse encrypted private key blob {}",
path.to_str().unwrap()
)
);
std::fs::remove_file(&path).ok();
}
}