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