1use crate::{Ed25519Signer, P256Signer, Signer, SignerError};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum PemKind {
20 Pkcs8,
22 Sec1Ec,
24 OpenSsh,
26 Ed25519HexSeed,
28 Ed25519Base64Seed,
30 Unknown,
31}
32
33pub fn classify_pem(pem: &str) -> PemKind {
37 let trimmed = pem.trim();
38 if trimmed.contains("-----BEGIN PRIVATE KEY-----") {
39 return PemKind::Pkcs8;
40 }
41 if trimmed.contains("-----BEGIN EC PRIVATE KEY-----") {
42 return PemKind::Sec1Ec;
43 }
44 if trimmed.contains("-----BEGIN OPENSSH PRIVATE KEY-----") {
45 return PemKind::OpenSsh;
46 }
47 if hex::decode(trimmed).is_ok_and(|b| b.len() == 32) {
48 return PemKind::Ed25519HexSeed;
49 }
50 use base64::Engine;
51 if let Ok(bytes) = base64::engine::general_purpose::STANDARD.decode(trimmed)
52 && (bytes.len() == 32 || bytes.len() == 64)
53 {
54 return PemKind::Ed25519Base64Seed;
55 }
56 PemKind::Unknown
57}
58
59pub fn load_signer_from_pem(pem: &str) -> Result<Box<dyn Signer>, SignerError> {
67 match classify_pem(pem) {
68 PemKind::Pkcs8 => {
69 if pem.contains("MC4CAQ")
74 && let Ok(s) = Ed25519Signer::from_pem(pem)
75 {
76 return Ok(Box::new(s) as Box<dyn Signer>);
77 }
78 if let Ok(s) = P256Signer::from_pem(pem) {
79 return Ok(Box::new(s) as Box<dyn Signer>);
80 }
81 if let Ok(s) = Ed25519Signer::from_pem(pem) {
82 return Ok(Box::new(s) as Box<dyn Signer>);
83 }
84 Err(SignerError::UnknownKeyFormat)
85 }
86 PemKind::Sec1Ec => P256Signer::from_pem(pem).map(|s| Box::new(s) as Box<dyn Signer>),
87 PemKind::Ed25519HexSeed | PemKind::Ed25519Base64Seed => {
88 Ed25519Signer::from_pem(pem).map(|s| Box::new(s) as Box<dyn Signer>)
89 }
90 PemKind::OpenSsh => Err(SignerError::Pem(
91 "OpenSSH private keys are not yet supported".to_string(),
92 )),
93 PemKind::Unknown => Err(SignerError::UnknownKeyFormat),
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn classifies_pkcs8_header() {
103 let pem = "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBC...\n-----END PRIVATE KEY-----";
104 assert_eq!(classify_pem(pem), PemKind::Pkcs8);
105 }
106
107 #[test]
108 fn rejects_pkcs1_rsa_header() {
109 let pem = "-----BEGIN RSA PRIVATE KEY-----\nMIIBOg...\n-----END RSA PRIVATE KEY-----";
110 assert_eq!(classify_pem(pem), PemKind::Unknown);
111 }
112
113 #[test]
114 fn classifies_sec1_ec_header() {
115 let pem = "-----BEGIN EC PRIVATE KEY-----\nMHc...\n-----END EC PRIVATE KEY-----";
116 assert_eq!(classify_pem(pem), PemKind::Sec1Ec);
117 }
118
119 #[test]
120 fn classifies_openssh_header() {
121 let pem = "-----BEGIN OPENSSH PRIVATE KEY-----\nb3Bl...\n-----END OPENSSH PRIVATE KEY-----";
122 assert_eq!(classify_pem(pem), PemKind::OpenSsh);
123 }
124
125 #[test]
126 fn classifies_hex_seed() {
127 let pem = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
128 assert_eq!(classify_pem(pem), PemKind::Ed25519HexSeed);
129 }
130
131 #[test]
132 fn unknown_input_classified_as_such() {
133 assert_eq!(classify_pem(""), PemKind::Unknown);
134 assert_eq!(classify_pem("not a key"), PemKind::Unknown);
135 }
136}