use std::fmt;
use crate::password_retriever::Password;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PrivateKeyLoadError {
NotEncryptedPrivateKey(String),
Other(String),
}
impl fmt::Display for PrivateKeyLoadError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PrivateKeyLoadError::NotEncryptedPrivateKey(m) => write!(f, "{}", m),
PrivateKeyLoadError::Other(m) => write!(f, "{}", m),
}
}
}
impl std::error::Error for PrivateKeyLoadError {}
pub fn password_to_bytes(password: &[char]) -> Vec<u8> {
let mut out = Vec::with_capacity(password.len());
for &c in password {
let mut buf = [0u8; 4];
out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
}
out
}
pub fn is_encrypted_private_key_info(blob: &[u8]) -> bool {
pkcs8::EncryptedPrivateKeyInfo::try_from(blob).is_ok()
}
pub fn decrypt_pkcs8_encoded_key(
encrypted_private_key_info: &[u8],
passwords: &[Password],
) -> Result<Vec<u8>, PrivateKeyLoadError> {
let info = pkcs8::EncryptedPrivateKeyInfo::try_from(encrypted_private_key_info)
.map_err(|e| PrivateKeyLoadError::NotEncryptedPrivateKey(e.to_string()))?;
if passwords.is_empty() {
return Err(PrivateKeyLoadError::Other("No passwords".to_string()));
}
let mut last_failure: Option<String> = None;
for password in passwords {
match info.decrypt(password_to_bytes(password)) {
Ok(document) => return Ok(document.as_bytes().to_vec()),
Err(e) => last_failure = Some(e.to_string()),
}
}
Err(PrivateKeyLoadError::Other(
last_failure.expect("at least one password was tried"),
))
}
pub fn load_pkcs8_encoded_private_key(
pkcs8_der: &[u8],
) -> Result<apksig::key_config::PrivateKey, String> {
use pkcs8::DecodePrivateKey;
if let Ok(key) = rsa::RsaPrivateKey::from_pkcs8_der(pkcs8_der) {
return Ok(apksig::key_config::PrivateKey::Rsa(Box::new(key)));
}
if let Ok(key) = p256::ecdsa::SigningKey::from_pkcs8_der(pkcs8_der) {
return Ok(apksig::key_config::PrivateKey::EcP256(Box::new(key)));
}
if let Ok(key) = p384::ecdsa::SigningKey::from_pkcs8_der(pkcs8_der) {
return Ok(apksig::key_config::PrivateKey::EcP384(Box::new(key)));
}
if let Ok(secret) = p521::SecretKey::from_pkcs8_der(pkcs8_der)
&& let Ok(key) = p521::ecdsa::SigningKey::from_slice(&secret.to_bytes())
{
return Ok(apksig::key_config::PrivateKey::EcP521(Box::new(key)));
}
if let Ok(key) = apksig::internal::dsa::DsaPrivateKey::from_pkcs8_der(pkcs8_der) {
return Ok(apksig::key_config::PrivateKey::Dsa(Box::new(key)));
}
Err("Not an RSA, EC, or DSA private key".to_string())
}
#[cfg(test)]
mod tests {
use super::*;
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()
}
#[test]
fn password_characters_become_utf8_octets() {
assert_eq!(
password_to_bytes(&"hunter2".chars().collect::<Vec<_>>()),
b"hunter2"
);
assert_eq!(password_to_bytes(&['\u{00e4}']), vec![0xc3, 0xa4]);
assert!(password_to_bytes(&[]).is_empty());
}
#[test]
fn a_plain_pkcs8_key_is_not_mistaken_for_an_encrypted_one() {
assert!(!is_encrypted_private_key_info(&p256_pkcs8_der()));
}
#[test]
fn garbage_is_not_an_encrypted_private_key() {
assert!(!is_encrypted_private_key_info(&[0x00, 0x01, 0x02]));
assert!(!is_encrypted_private_key_info(&[]));
assert!(!is_encrypted_private_key_info(&[0x30, 0x01, 0x00]));
}
#[test]
fn decrypting_without_passwords_reports_the_java_runtime_exception() {
let info = vec![
0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
];
let err = decrypt_pkcs8_encoded_key(&info, &[]).unwrap_err();
assert!(matches!(
err,
PrivateKeyLoadError::NotEncryptedPrivateKey(_) | PrivateKeyLoadError::Other(_)
));
}
#[test]
fn an_unencrypted_blob_is_rejected_by_the_decryptor() {
let err = decrypt_pkcs8_encoded_key(&p256_pkcs8_der(), &[vec!['x']]).unwrap_err();
assert!(matches!(
err,
PrivateKeyLoadError::NotEncryptedPrivateKey(_)
));
}
#[test]
fn a_p256_key_loads_as_an_ec_key() {
let key = load_pkcs8_encoded_private_key(&p256_pkcs8_der()).expect("P-256 key");
assert!(matches!(key, apksig::key_config::PrivateKey::EcP256(_)));
assert_eq!(key.key_algorithm(), "EC");
}
#[test]
fn a_p521_key_loads_as_an_ec_key() {
use pkcs8::EncodePrivateKey;
let mut scalar = [0x11u8; 66];
scalar[0] = 0;
let secret = p521::SecretKey::from_slice(&scalar).expect("valid scalar");
let der = secret.to_pkcs8_der().expect("PKCS #8");
let loaded = load_pkcs8_encoded_private_key(der.as_bytes()).expect("P-521 key");
assert!(matches!(loaded, apksig::key_config::PrivateKey::EcP521(_)));
assert_eq!(loaded.key_algorithm(), "EC");
assert!(!loaded.public_key_spki().expect("SPKI").is_empty());
}
#[test]
fn an_unrecognised_blob_reports_the_java_algorithm_message() {
let err = load_pkcs8_encoded_private_key(&[0x30, 0x01, 0x00]).unwrap_err();
assert_eq!(err, "Not an RSA, EC, or DSA private key");
}
#[test]
fn a_p384_key_is_distinguished_from_a_p256_key() {
use pkcs8::EncodePrivateKey;
let scalar = [9u8; 48];
let key = p384::ecdsa::SigningKey::from_bytes(&scalar.into()).expect("valid scalar");
let der = key.to_pkcs8_der().expect("PKCS #8").as_bytes().to_vec();
let loaded = load_pkcs8_encoded_private_key(&der).expect("P-384 key");
assert!(matches!(loaded, apksig::key_config::PrivateKey::EcP384(_)));
}
}