1use ed25519;
2use pem_rfc7468::PemLabel;
3use pkcs8::{DecodePrivateKey, PrivateKeyInfo, SecretDocument, der::Decode, pkcs5};
4use ssh_key::{
5 private::{Ed25519Keypair, RsaKeypair},
6 sec1,
7};
8
9use crate::{SshKeyData, error::SshKeyImportError, ssh_private_key_to_data};
10
11pub fn import_key(
20 encoded_key: String,
21 password: Option<String>,
22) -> Result<SshKeyData, SshKeyImportError> {
23 let label = pem_rfc7468::decode_label(encoded_key.as_bytes())
24 .map_err(|_| SshKeyImportError::Parsing)?;
25
26 match label {
27 pkcs8::PrivateKeyInfo::<(), (), ()>::PEM_LABEL => import_pkcs8_key(encoded_key, None),
28 pkcs8::EncryptedPrivateKeyInfo::<()>::PEM_LABEL => import_pkcs8_key(
29 encoded_key,
30 Some(password.ok_or(SshKeyImportError::PasswordRequired)?),
31 ),
32 ssh_key::PrivateKey::PEM_LABEL => import_openssh_key(encoded_key, password),
33 _ => Err(SshKeyImportError::UnsupportedKeyType),
34 }
35}
36
37fn import_pkcs8_key(
38 encoded_key: String,
39 password: Option<String>,
40) -> Result<SshKeyData, SshKeyImportError> {
41 match parse_pkcs8_pem(&encoded_key, password.as_deref()) {
42 Err(SshKeyImportError::Parsing) => {
46 let rewrapped = rewrap_pem(&encoded_key).ok_or(SshKeyImportError::Parsing)?;
47 parse_pkcs8_pem(&rewrapped, password.as_deref())
48 }
49 result => result,
50 }
51}
52
53fn parse_pkcs8_pem(
54 encoded_key: &str,
55 password: Option<&str>,
56) -> Result<SshKeyData, SshKeyImportError> {
57 let doc = if let Some(password) = password {
58 SecretDocument::from_pkcs8_encrypted_pem(encoded_key, password.as_bytes()).map_err(
59 |err| match err {
60 pkcs8::Error::EncryptedPrivateKey(pkcs5::Error::DecryptFailed) => {
61 SshKeyImportError::WrongPassword
62 }
63 _ => SshKeyImportError::Parsing,
64 },
65 )?
66 } else {
67 SecretDocument::from_pkcs8_pem(encoded_key).map_err(|_| SshKeyImportError::Parsing)?
68 };
69
70 import_pkcs8_der_key(doc.as_bytes())
71}
72
73fn rewrap_pem(pem: &str) -> Option<String> {
79 let mut lines = pem.lines();
80
81 let header = lines
82 .by_ref()
83 .find(|line| line.starts_with("-----BEGIN "))?;
84
85 let mut body = String::new();
87 let mut footer = None;
88 for line in lines.by_ref() {
89 if line.starts_with("-----END ") {
90 footer = Some(line);
91 break;
92 }
93 body.extend(line.split_whitespace());
94 }
95 let footer = footer?;
96
97 let mut out = String::with_capacity(body.len() + body.len() / 64 + header.len() + 16);
98 out.push_str(header);
99 out.push('\n');
100 let mut chars = body.chars();
102 loop {
103 let chunk: String = chars.by_ref().take(64).collect();
104 if chunk.is_empty() {
105 break;
106 }
107 out.push_str(&chunk);
108 out.push('\n');
109 }
110 out.push_str(footer);
111 out.push('\n');
112
113 Some(out)
114}
115
116pub fn import_pkcs8_der_key(encoded_key: &[u8]) -> Result<SshKeyData, SshKeyImportError> {
119 let private_key_info =
120 PrivateKeyInfo::from_der(encoded_key).map_err(|_| SshKeyImportError::Parsing)?;
121
122 let private_key = match private_key_info.algorithm.oid {
123 ed25519::pkcs8::ALGORITHM_OID => {
124 let private_key: ed25519::KeypairBytes = private_key_info
125 .try_into()
126 .map_err(|_| SshKeyImportError::Parsing)?;
127
128 ssh_key::private::PrivateKey::from(Ed25519Keypair::from(&private_key.secret_key.into()))
129 }
130 rsa::pkcs1::ALGORITHM_OID => {
131 let private_key: rsa::RsaPrivateKey = private_key_info
132 .try_into()
133 .map_err(|_| SshKeyImportError::Parsing)?;
134
135 ssh_key::private::PrivateKey::from(
136 RsaKeypair::try_from(private_key).map_err(|_| SshKeyImportError::Parsing)?,
137 )
138 }
139 sec1::ALGORITHM_OID => import_ecdsa_pkcs8_der(encoded_key)?,
140 _ => return Err(SshKeyImportError::UnsupportedKeyType),
141 };
142
143 ssh_private_key_to_data(private_key).map_err(|_| SshKeyImportError::Parsing)
144}
145
146fn import_openssh_key(
147 encoded_key: String,
148 password: Option<String>,
149) -> Result<SshKeyData, SshKeyImportError> {
150 let private_key =
151 ssh_key::private::PrivateKey::from_openssh(&encoded_key).map_err(|err| match err {
152 ssh_key::Error::AlgorithmUnknown | ssh_key::Error::AlgorithmUnsupported { .. } => {
153 SshKeyImportError::UnsupportedKeyType
154 }
155 _ => SshKeyImportError::Parsing,
156 })?;
157
158 let private_key = if private_key.is_encrypted() {
159 let password = password.ok_or(SshKeyImportError::PasswordRequired)?;
160 private_key
161 .decrypt(password.as_bytes())
162 .map_err(|_| SshKeyImportError::WrongPassword)?
163 } else {
164 private_key
165 };
166
167 ssh_private_key_to_data(private_key).map_err(|_| SshKeyImportError::Parsing)
168}
169
170fn import_ecdsa_pkcs8_der(encoded_key: &[u8]) -> Result<ssh_key::PrivateKey, SshKeyImportError> {
171 use pkcs8::DecodePrivateKey as _;
172
173 if let Ok(sk) = p256::SecretKey::from_pkcs8_der(encoded_key) {
174 let public_key = sk.public_key();
175 let keypair = ssh_key::private::EcdsaKeypair::NistP256 {
176 public: public_key.into(),
177 private: ssh_key::private::EcdsaPrivateKey::from(sk),
178 };
179 return ssh_key::PrivateKey::new(ssh_key::private::KeypairData::Ecdsa(keypair), "")
180 .map_err(|_| SshKeyImportError::Parsing);
181 }
182 if let Ok(sk) = p384::SecretKey::from_pkcs8_der(encoded_key) {
183 let public_key = sk.public_key();
184 let keypair = ssh_key::private::EcdsaKeypair::NistP384 {
185 public: public_key.into(),
186 private: ssh_key::private::EcdsaPrivateKey::from(sk),
187 };
188 return ssh_key::PrivateKey::new(ssh_key::private::KeypairData::Ecdsa(keypair), "")
189 .map_err(|_| SshKeyImportError::Parsing);
190 }
191 if let Ok(sk) = p521::SecretKey::from_pkcs8_der(encoded_key) {
192 let public_key = sk.public_key();
193 let keypair = ssh_key::private::EcdsaKeypair::NistP521 {
194 public: public_key.into(),
195 private: ssh_key::private::EcdsaPrivateKey::from(sk),
196 };
197 return ssh_key::PrivateKey::new(ssh_key::private::KeypairData::Ecdsa(keypair), "")
198 .map_err(|_| SshKeyImportError::Parsing);
199 }
200 Err(SshKeyImportError::UnsupportedKeyType)
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn import_key_ed25519_openssh_unencrypted() {
209 let private_key = include_str!("../resources/import/ed25519_openssh_unencrypted");
210 let public_key = include_str!("../resources/import/ed25519_openssh_unencrypted.pub").trim();
211 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
212 assert_eq!(result.public_key, public_key);
213 }
214
215 #[test]
216 fn import_key_ed25519_openssh_encrypted() {
217 let private_key = include_str!("../resources/import/ed25519_openssh_encrypted");
218 let public_key = include_str!("../resources/import/ed25519_openssh_encrypted.pub").trim();
219 let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap();
220 assert_eq!(result.public_key, public_key);
221 }
222
223 #[test]
224 fn import_key_rsa_openssh_unencrypted() {
225 let private_key = include_str!("../resources/import/rsa_openssh_unencrypted");
226 let public_key = include_str!("../resources/import/rsa_openssh_unencrypted.pub").trim();
227 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
228 assert_eq!(result.public_key, public_key);
229 }
230
231 #[test]
232 fn import_key_rsa_openssh_encrypted() {
233 let private_key = include_str!("../resources/import/rsa_openssh_encrypted");
234 let public_key = include_str!("../resources/import/rsa_openssh_encrypted.pub").trim();
235 let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap();
236 assert_eq!(result.public_key, public_key);
237 }
238
239 #[test]
240 fn import_key_ed25519_pkcs8_unencrypted() {
241 let private_key = include_str!("../resources/import/ed25519_pkcs8_unencrypted");
242 let public_key = include_str!("../resources/import/ed25519_pkcs8_unencrypted.pub")
243 .replace("testkey", "");
244 let public_key = public_key.trim();
245 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
246 assert_eq!(result.public_key, public_key);
247 }
248
249 #[test]
250 fn import_key_rsa_pkcs8_unencrypted() {
251 let private_key = include_str!("../resources/import/rsa_pkcs8_unencrypted");
252 let public_key =
254 include_str!("../resources/import/rsa_pkcs8_unencrypted.pub").replace("testkey", "");
255 let public_key = public_key.trim();
256 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
257 assert_eq!(result.public_key, public_key);
258 }
259
260 #[test]
261 fn import_key_rsa_pkcs8_encrypted() {
262 let private_key = include_str!("../resources/import/rsa_pkcs8_encrypted");
263 let public_key =
264 include_str!("../resources/import/rsa_pkcs8_encrypted.pub").replace("testkey", "");
265 let public_key = public_key.trim();
266 let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap();
267 assert_eq!(result.public_key, public_key);
268 }
269
270 #[test]
271 fn import_key_ed25519_openssh_encrypted_wrong_password() {
272 let private_key = include_str!("../resources/import/ed25519_openssh_encrypted");
273 let result = import_key(private_key.to_string(), Some("wrongpassword".to_string()));
274 assert_eq!(result.unwrap_err(), SshKeyImportError::WrongPassword);
275 }
276
277 #[test]
282 fn import_key_ed25519_pkcs8_unencrypted_single_line() {
283 let private_key =
286 include_str!("../resources/import/ed25519_pkcs8_1password_single_line_unencrypted");
287 let public_key = include_str!("../resources/import/ed25519_pkcs8_unencrypted.pub").trim();
288
289 let result = import_key(private_key.to_string(), None).unwrap();
290 assert_eq!(result.public_key, public_key);
291 }
292
293 #[test]
294 fn import_non_key_error() {
295 let result = import_key("not a key".to_string(), Some("".to_string()));
296 assert_eq!(result.unwrap_err(), SshKeyImportError::Parsing);
297 }
298
299 #[test]
300 fn import_wrong_label_error() {
301 let private_key = include_str!("../resources/import/wrong_label");
302 let result = import_key(private_key.to_string(), Some("".to_string()));
303 assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType);
304 }
305
306 #[test]
307 fn import_ecdsa_p256_openssh_unencrypted() {
308 let private_key = include_str!("../resources/import/ecdsa_openssh_unencrypted");
309 let public_key = include_str!("../resources/import/ecdsa_openssh_unencrypted.pub").trim();
310 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
311 assert_eq!(result.public_key, public_key);
312 }
313
314 #[test]
315 fn import_ecdsa_p384_openssh_unencrypted() {
316 let private_key = include_str!("../resources/import/ecdsa_p384_openssh_unencrypted");
317 let public_key =
318 include_str!("../resources/import/ecdsa_p384_openssh_unencrypted.pub").trim();
319 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
320 assert_eq!(result.public_key, public_key);
321 }
322
323 #[test]
324 fn import_ecdsa_p521_openssh_unencrypted() {
325 let private_key = include_str!("../resources/import/ecdsa_p521_openssh_unencrypted");
326 let public_key =
327 include_str!("../resources/import/ecdsa_p521_openssh_unencrypted.pub").trim();
328 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
329 assert_eq!(result.public_key, public_key);
330 }
331
332 #[test]
333 fn import_key_ed25519_putty() {
334 let private_key = include_str!("../resources/import/ed25519_putty_openssh_unencrypted");
335 let public_key =
336 include_str!("../resources/import/ed25519_putty_openssh_unencrypted.pub").trim();
337 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
338 assert_eq!(result.public_key, public_key);
339 }
340
341 #[test]
342 fn import_key_rsa_openssh_putty() {
343 let private_key = include_str!("../resources/import/rsa_putty_openssh_unencrypted");
344 let public_key =
345 include_str!("../resources/import/rsa_putty_openssh_unencrypted.pub").trim();
346 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
347 assert_eq!(result.public_key, public_key);
348 }
349
350 #[test]
351 fn import_key_rsa_pkcs8_putty() {
352 let private_key = include_str!("../resources/import/rsa_putty_pkcs1_unencrypted");
353 let result = import_key(private_key.to_string(), Some("".to_string()));
354 assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType);
355 }
356
357 #[test]
358 fn import_ed25519_key_regression_17028() {
359 let private_key = include_str!("../resources/import/ed25519_regression_17028");
361 let public_key = include_str!("../resources/import/ed25519_regression_17028.pub").trim();
362 let result = import_key(private_key.to_string(), None).unwrap();
363 assert_eq!(result.public_key, public_key);
364 }
365}