use crate::{Ed25519Signer, P256Signer, RsaSigner, Signer, SignerError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PemKind {
Pkcs8,
Pkcs1Rsa,
Sec1Ec,
OpenSsh,
Ed25519HexSeed,
Ed25519Base64Seed,
Unknown,
}
pub fn classify_pem(pem: &str) -> PemKind {
let trimmed = pem.trim();
if trimmed.contains("-----BEGIN PRIVATE KEY-----") {
return PemKind::Pkcs8;
}
if trimmed.contains("-----BEGIN RSA PRIVATE KEY-----") {
return PemKind::Pkcs1Rsa;
}
if trimmed.contains("-----BEGIN EC PRIVATE KEY-----") {
return PemKind::Sec1Ec;
}
if trimmed.contains("-----BEGIN OPENSSH PRIVATE KEY-----") {
return PemKind::OpenSsh;
}
if hex::decode(trimmed).is_ok_and(|b| b.len() == 32) {
return PemKind::Ed25519HexSeed;
}
use base64::Engine;
if let Ok(bytes) = base64::engine::general_purpose::STANDARD.decode(trimmed)
&& (bytes.len() == 32 || bytes.len() == 64)
{
return PemKind::Ed25519Base64Seed;
}
PemKind::Unknown
}
pub fn load_signer_from_pem(pem: &str) -> Result<Box<dyn Signer>, SignerError> {
match classify_pem(pem) {
PemKind::Pkcs8 => {
if pem.contains("MC4CAQ")
&& let Ok(s) = Ed25519Signer::from_pem(pem)
{
return Ok(Box::new(s) as Box<dyn Signer>);
}
if let Ok(s) = RsaSigner::from_pem(pem) {
return Ok(Box::new(s) as Box<dyn Signer>);
}
if let Ok(s) = P256Signer::from_pem(pem) {
return Ok(Box::new(s) as Box<dyn Signer>);
}
if let Ok(s) = Ed25519Signer::from_pem(pem) {
return Ok(Box::new(s) as Box<dyn Signer>);
}
Err(SignerError::UnknownKeyFormat)
}
PemKind::Pkcs1Rsa => RsaSigner::from_pem(pem).map(|s| Box::new(s) as Box<dyn Signer>),
PemKind::Sec1Ec => P256Signer::from_pem(pem).map(|s| Box::new(s) as Box<dyn Signer>),
PemKind::Ed25519HexSeed | PemKind::Ed25519Base64Seed => {
Ed25519Signer::from_pem(pem).map(|s| Box::new(s) as Box<dyn Signer>)
}
PemKind::OpenSsh => Err(SignerError::Pem(
"OpenSSH private keys are not yet supported".to_string(),
)),
PemKind::Unknown => Err(SignerError::UnknownKeyFormat),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classifies_pkcs8_header() {
let pem = "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBC...\n-----END PRIVATE KEY-----";
assert_eq!(classify_pem(pem), PemKind::Pkcs8);
}
#[test]
fn classifies_pkcs1_rsa_header() {
let pem = "-----BEGIN RSA PRIVATE KEY-----\nMIIBOg...\n-----END RSA PRIVATE KEY-----";
assert_eq!(classify_pem(pem), PemKind::Pkcs1Rsa);
}
#[test]
fn classifies_sec1_ec_header() {
let pem = "-----BEGIN EC PRIVATE KEY-----\nMHc...\n-----END EC PRIVATE KEY-----";
assert_eq!(classify_pem(pem), PemKind::Sec1Ec);
}
#[test]
fn classifies_openssh_header() {
let pem = "-----BEGIN OPENSSH PRIVATE KEY-----\nb3Bl...\n-----END OPENSSH PRIVATE KEY-----";
assert_eq!(classify_pem(pem), PemKind::OpenSsh);
}
#[test]
fn classifies_hex_seed() {
let pem = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
assert_eq!(classify_pem(pem), PemKind::Ed25519HexSeed);
}
#[test]
fn unknown_input_classified_as_such() {
assert_eq!(classify_pem(""), PemKind::Unknown);
assert_eq!(classify_pem("not a key"), PemKind::Unknown);
}
}