1use std::fs::File;
60use std::io::Read;
61use std::path::Path;
62use std::string::FromUtf8Error;
63
64use aes::cipher::block_padding::UnpadError;
65use aes::cipher::inout::PadError;
66use data_encoding::BASE64_MIME;
67use thiserror::Error;
68
69use crate::helpers::EncodedExt;
70
71pub mod key;
72pub use key::PrivateKeyWithHashAlg;
73
74mod format;
75pub use format::*;
76pub use signature;
78pub use ssh_encoding;
79pub use ssh_key::{self, Algorithm, Certificate, EcdsaCurve, HashAlg, PrivateKey, PublicKey};
80
81pub mod agent;
83
84#[cfg(not(target_arch = "wasm32"))]
85pub mod known_hosts;
86
87#[cfg(not(target_arch = "wasm32"))]
88pub use known_hosts::{check_known_hosts, check_known_hosts_path};
89
90#[derive(Debug, Error)]
91pub enum Error {
92 #[error("Could not read key")]
94 CouldNotReadKey,
95 #[error("Unsupported key type {}", key_type_string)]
97 UnsupportedKeyType {
98 key_type_string: String,
99 key_type_raw: Vec<u8>,
100 },
101 #[error("Invalid Ed25519 key data")]
103 Ed25519KeyError(#[from] ed25519_dalek::SignatureError),
104 #[error("Invalid ECDSA key data")]
106 EcdsaKeyError(#[from] p256::elliptic_curve::Error),
107 #[error("The key is encrypted")]
109 KeyIsEncrypted,
110 #[error("The key is corrupt")]
112 KeyIsCorrupt,
113 #[error("No home directory found")]
115 NoHomeDir,
116 #[error("The server key changed at line {}", line)]
118 KeyChanged { line: usize },
119 #[error("Unknown key algorithm: {0}")]
121 UnknownAlgorithm(::pkcs8::ObjectIdentifier),
122 #[error("Index out of bounds")]
124 IndexOutOfBounds,
125 #[error("Unknown signature type: {}", sig_type)]
127 UnknownSignatureType { sig_type: String },
128 #[error("Invalid signature")]
129 InvalidSignature,
130 #[error("Invalid parameters")]
131 InvalidParameters,
132 #[error("Agent protocol error")]
134 AgentProtocolError,
135 #[error("Agent failure")]
136 AgentFailure,
137 #[error(transparent)]
138 IO(#[from] std::io::Error),
139
140 #[cfg(feature = "rsa")]
141 #[error("Rsa: {0}")]
142 Rsa(#[from] rsa::Error),
143
144 #[error(transparent)]
145 Pad(#[from] PadError),
146
147 #[error(transparent)]
148 Unpad(#[from] UnpadError),
149
150 #[error("Base64 decoding error: {0}")]
151 Decode(#[from] data_encoding::DecodeError),
152 #[error("Der: {0}")]
153 Der(#[from] der::Error),
154 #[error("Spki: {0}")]
155 Spki(#[from] spki::Error),
156 #[cfg(feature = "rsa")]
157 #[error("Pkcs1: {0}")]
158 Pkcs1(#[from] pkcs1::Error),
159 #[error("Pkcs8: {0}")]
160 Pkcs8(#[from] ::pkcs8::Error),
161 #[cfg(feature = "rsa")]
162 #[error("Pkcs8: {0}")]
163 Pkcs8Next(#[from] ::rsa::pkcs8::Error),
164 #[error("Sec1: {0}")]
165 Sec1(#[from] sec1::Error),
166
167 #[error("SshKey: {0}")]
168 SshKey(#[from] ssh_key::Error),
169 #[error("SshEncoding: {0}")]
170 SshEncoding(#[from] ssh_encoding::Error),
171
172 #[error("Environment variable `{0}` not found")]
173 EnvVar(&'static str),
174 #[error(
175 "Unable to connect to ssh-agent. The environment variable `SSH_AUTH_SOCK` was set, but it \
176 points to a nonexistent file or directory."
177 )]
178 BadAuthSock,
179
180 #[error(transparent)]
181 Utf8(#[from] FromUtf8Error),
182
183 #[error("ASN1 decoding error: {0}")]
184 #[cfg(feature = "legacy-ed25519-pkcs8-parser")]
185 LegacyASN1(::yasna::ASN1Error),
186
187 #[cfg(windows)]
188 #[error("Pageant: {0}")]
189 Pageant(#[from] pageant::Error),
190}
191
192#[cfg(feature = "legacy-ed25519-pkcs8-parser")]
193impl From<yasna::ASN1Error> for Error {
194 fn from(e: yasna::ASN1Error) -> Error {
195 Error::LegacyASN1(e)
196 }
197}
198
199pub fn load_public_key<P: AsRef<Path>>(path: P) -> Result<ssh_key::PublicKey, Error> {
205 let mut pubkey = String::new();
206 let mut file = File::open(path.as_ref())?;
207 file.read_to_string(&mut pubkey)?;
208
209 let mut split = pubkey.split_whitespace();
210 match (split.next(), split.next()) {
211 (Some(_), Some(key)) => parse_public_key_base64(key),
212 (Some(key), None) => parse_public_key_base64(key),
213 _ => Err(Error::CouldNotReadKey),
214 }
215}
216
217pub fn parse_public_key_base64(key: &str) -> Result<ssh_key::PublicKey, Error> {
225 let base = BASE64_MIME.decode(key.as_bytes())?;
226 key::parse_public_key(&base)
227}
228
229pub trait PublicKeyBase64 {
230 fn public_key_bytes(&self) -> Vec<u8>;
232 fn public_key_base64(&self) -> String {
233 let mut s = BASE64_MIME.encode(&self.public_key_bytes());
234 assert_eq!(s.pop(), Some('\n'));
235 assert_eq!(s.pop(), Some('\r'));
236 s.replace("\r\n", "")
237 }
238}
239
240impl PublicKeyBase64 for ssh_key::PublicKey {
241 fn public_key_bytes(&self) -> Vec<u8> {
242 self.key_data().encoded().unwrap_or_default()
243 }
244}
245
246impl PublicKeyBase64 for PrivateKey {
247 fn public_key_bytes(&self) -> Vec<u8> {
248 self.public_key().public_key_bytes()
249 }
250}
251
252pub fn load_secret_key<P: AsRef<Path>>(
254 secret_: P,
255 password: Option<&str>,
256) -> Result<PrivateKey, Error> {
257 let mut secret_file = std::fs::File::open(secret_)?;
258 let mut secret = String::new();
259 secret_file.read_to_string(&mut secret)?;
260 decode_secret_key(&secret, password)
261}
262
263pub fn load_openssh_certificate<P: AsRef<Path>>(cert_: P) -> Result<Certificate, ssh_key::Error> {
265 let mut cert_file = std::fs::File::open(cert_)?;
266 let mut cert = String::new();
267 cert_file.read_to_string(&mut cert)?;
268
269 Certificate::from_openssh(&cert)
270}
271
272fn is_base64_char(c: char) -> bool {
273 c.is_ascii_lowercase()
274 || c.is_ascii_uppercase()
275 || c.is_ascii_digit()
276 || c == '/'
277 || c == '+'
278 || c == '='
279}
280
281#[cfg(test)]
282mod test {
283
284 #[cfg(unix)]
285 use futures::Future;
286
287 use super::*;
288 use crate::keys::key::PublicKeyExt;
289
290 const ED25519_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
291b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABDLGyfA39
292J2FcJygtYqi5ISAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIN+Wjn4+4Fcvl2Jl
293KpggT+wCRxpSvtqqpVrQrKN1/A22AAAAkOHDLnYZvYS6H9Q3S3Nk4ri3R2jAZlQlBbUos5
294FkHpYgNw65KCWCTXtP7ye2czMC3zjn2r98pJLobsLYQgRiHIv/CUdAdsqbvMPECB+wl/UQ
295e+JpiSq66Z6GIt0801skPh20jxOO3F52SoX1IeO5D5PXfZrfSZlw6S8c7bwyp2FHxDewRx
2967/wNsnDM0T7nLv/Q==
297-----END OPENSSH PRIVATE KEY-----";
298
299 const ED25519_AESCTR_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
301b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABD1phlku5
302A2G7Q9iP+DcOc9AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIHeLC1lWiCYrXsf/
30385O/pkbUFZ6OGIt49PX3nw8iRoXEAAAAkKRF0st5ZI7xxo9g6A4m4l6NarkQre3mycqNXQ
304dP3jryYgvsCIBAA5jMWSjrmnOTXhidqcOy4xYCrAttzSnZ/cUadfBenL+DQq6neffw7j8r
3050tbCxVGp6yCQlKrgSZf6c0Hy7dNEIU2bJFGxLe6/kWChcUAt/5Ll5rI7DVQPJdLgehLzvv
306sJWR7W+cGvJ/vLsw==
307-----END OPENSSH PRIVATE KEY-----";
308
309 #[cfg(feature = "rsa")]
310 const RSA_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
311b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
312NhAAAAAwEAAQAAAQEAuSvQ9m76zhRB4m0BUKPf17lwccj7KQ1Qtse63AOqP/VYItqEH8un
313rxPogXNBgrcCEm/ccLZZsyE3qgp3DRQkkqvJhZ6O8VBPsXxjZesRCqoFNCczy+Mf0R/Qmv
314Rnpu5+4DDLz0p7vrsRZW9ji/c98KzxeUonWgkplQaCBYLN875WdeUYMGtb1MLfNCEj177j
315gZl3CzttLRK3su6dckowXcXYv1gPTPZAwJb49J43o1QhV7+1zdwXvuFM6zuYHdu9ZHSKir
3166k1dXOET3/U+LWG5ofAo8oxUWv/7vs6h7MeajwkUeIBOWYtD+wGYRvVpxvj7nyOoWtg+jm
3170X6ndnsD+QAAA8irV+ZAq1fmQAAAAAdzc2gtcnNhAAABAQC5K9D2bvrOFEHibQFQo9/XuX
318BxyPspDVC2x7rcA6o/9Vgi2oQfy6evE+iBc0GCtwISb9xwtlmzITeqCncNFCSSq8mFno7x
319UE+xfGNl6xEKqgU0JzPL4x/RH9Ca9Gem7n7gMMvPSnu+uxFlb2OL9z3wrPF5SidaCSmVBo
320IFgs3zvlZ15Rgwa1vUwt80ISPXvuOBmXcLO20tErey7p1ySjBdxdi/WA9M9kDAlvj0njej
321VCFXv7XN3Be+4UzrO5gd271kdIqKvqTV1c4RPf9T4tYbmh8CjyjFRa//u+zqHsx5qPCRR4
322gE5Zi0P7AZhG9WnG+PufI6ha2D6ObRfqd2ewP5AAAAAwEAAQAAAQAdELqhI/RsSpO45eFR
3239hcZtnrm8WQzImrr9dfn1w9vMKSf++rHTuFIQvi48Q10ZiOGH1bbvlPAIVOqdjAPtnyzJR
324HhzmyjhjasJlk30zj+kod0kz63HzSMT9EfsYNfmYoCyMYFCKz52EU3xc87Vhi74XmZz0D0
325CgIj6TyZftmzC4YJCiwwU8K+29nxBhcbFRxpgwAksFL6PCSQsPl4y7yvXGcX+7lpZD8547
326v58q3jIkH1g2tBOusIuaiphDDStVJhVdKA55Z0Kju2kvCqsRIlf1efrq43blRgJFFFCxNZ
3278Cpolt4lOHhg+o3ucjILlCOgjDV8dB21YLxmgN5q+xFNAAAAgQC1P+eLUkHDFXnleCEVrW
328xL/DFxEyneLQz3IawGdw7cyAb7vxsYrGUvbVUFkxeiv397pDHLZ5U+t5cOYDBZ7G43Mt2g
329YfWBuRNvYhHA9Sdf38m5qPA6XCvm51f+FxInwd/kwRKH01RHJuRGsl/4Apu4DqVob8y00V
330WTYyV6JBNDkQAAAIEA322lj7ZJXfK/oLhMM/RS+DvaMea1g/q43mdRJFQQso4XRCL6IIVn
331oZXFeOxrMIRByVZBw+FSeB6OayWcZMySpJQBo70GdJOc3pJb3js0T+P2XA9+/jwXS58K9a
332+IkgLkv9XkfxNGNKyPEEzXC8QQzvjs1LbmO59VLko8ypwHq/cAAACBANQqaULI0qdwa0vm
333d3Ae1+k3YLZ0kapSQGVIMT2lkrhKV35tj7HIFpUPa4vitHzcUwtjYhqFezVF+JyPbJ/Fsp
334XmEc0g1fFnQp5/SkUwoN2zm8Up52GBelkq2Jk57mOMzWO0QzzNuNV/feJk02b2aE8rrAqP
335QR+u0AypRPmzHnOPAAAAEXJvb3RAMTQwOTExNTQ5NDBkAQ==
336-----END OPENSSH PRIVATE KEY-----";
337
338 #[test]
339 fn test_decode_ed25519_secret_key() {
340 env_logger::try_init().unwrap_or(());
341 decode_secret_key(ED25519_KEY, Some("blabla")).unwrap();
342 }
343
344 #[test]
345 fn test_decode_ed25519_aesctr_secret_key() {
346 env_logger::try_init().unwrap_or(());
347 decode_secret_key(ED25519_AESCTR_KEY, Some("test")).unwrap();
348 }
349
350 const RFC8410_ED25519_PRIVATE_ONLY_KEY: &str = "-----BEGIN PRIVATE KEY-----
352MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
353-----END PRIVATE KEY-----";
354
355 #[test]
356 fn test_decode_rfc8410_ed25519_private_only_key() {
357 env_logger::try_init().unwrap_or(());
358 assert!(
359 decode_secret_key(RFC8410_ED25519_PRIVATE_ONLY_KEY, None)
360 .unwrap()
361 .algorithm()
362 == ssh_key::Algorithm::Ed25519,
363 );
364 }
366
367 const RFC8410_ED25519_PRIVATE_PUBLIC_KEY: &str = "-----BEGIN PRIVATE KEY-----
369MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
370oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
371Z9w7lshQhqowtrbLDFw4rXAxZuE=
372-----END PRIVATE KEY-----";
373
374 #[test]
375 fn test_decode_rfc8410_ed25519_private_public_key() {
376 env_logger::try_init().unwrap_or(());
377 assert!(
378 decode_secret_key(RFC8410_ED25519_PRIVATE_PUBLIC_KEY, None)
379 .unwrap()
380 .algorithm()
381 == ssh_key::Algorithm::Ed25519,
382 );
383 }
385
386 #[cfg(feature = "rsa")]
387 #[test]
388 fn test_decode_rsa_secret_key() {
389 env_logger::try_init().unwrap_or(());
390 decode_secret_key(RSA_KEY, None).unwrap();
391 }
392
393 #[test]
394 fn test_decode_openssh_p256_secret_key() {
395 let key = "-----BEGIN OPENSSH PRIVATE KEY-----
397b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
3981zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQ/i+HCsmZZPy0JhtT64vW7EmeA1DeA
399M/VnPq3vAhu+xooJ7IMMK3lUHlBDosyvA2enNbCWyvNQc25dVt4oh9RhAAAAqHG7WMFxu1
400jBAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBD+L4cKyZlk/LQmG
4011Pri9bsSZ4DUN4Az9Wc+re8CG77GignsgwwreVQeUEOizK8DZ6c1sJbK81Bzbl1W3iiH1G
402EAAAAgLAmXR6IlN0SdiD6o8qr+vUr0mXLbajs/m0UlegElOmoAAAANcm9iZXJ0QGJic2Rl
403dgECAw==
404-----END OPENSSH PRIVATE KEY-----
405";
406 assert!(
407 decode_secret_key(key, None).unwrap().algorithm()
408 == ssh_key::Algorithm::Ecdsa {
409 curve: ssh_key::EcdsaCurve::NistP256
410 },
411 );
412 }
413
414 #[test]
415 fn test_decode_openssh_p384_secret_key() {
416 let key = "-----BEGIN OPENSSH PRIVATE KEY-----
418b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS
4191zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTkLnKPk/1NZD9mQ8XoebD7ASv9/svh
4205jO75HF7RYAqKK3fl5wsHe4VTJAOT3qH841yTcK79l0dwhHhHeg60byL7F9xOEzr2kqGeY
421Uwrl7fVaL7hfHzt6z+sG8smSQ3tF8AAADYHjjBch44wXIAAAATZWNkc2Etc2hhMi1uaXN0
422cDM4NAAAAAhuaXN0cDM4NAAAAGEE5C5yj5P9TWQ/ZkPF6Hmw+wEr/f7L4eYzu+Rxe0WAKi
423it35ecLB3uFUyQDk96h/ONck3Cu/ZdHcIR4R3oOtG8i+xfcThM69pKhnmFMK5e31Wi+4Xx
42487es/rBvLJkkN7RfAAAAMFzt6053dxaQT0Ta/CGfZna0nibHzxa55zgBmje/Ho3QDNlBCH
425Ylv0h4Wyzto8NfLQAAAA1yb2JlcnRAYmJzZGV2AQID
426-----END OPENSSH PRIVATE KEY-----
427";
428 assert!(
429 decode_secret_key(key, None).unwrap().algorithm()
430 == ssh_key::Algorithm::Ecdsa {
431 curve: ssh_key::EcdsaCurve::NistP384
432 },
433 );
434 }
435
436 #[test]
437 fn test_decode_openssh_p521_secret_key() {
438 let key = "-----BEGIN OPENSSH PRIVATE KEY-----
440b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
4411zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA7a9awmFeDjzYiuUOwMfXkKTevfQI
442iGlduu8BkjBOWXpffJpKsdTyJI/xI05l34OvqfCCkPUcfFWHK+LVRGahMBgBcGB9ZZOEEq
443iKNIT6C9WcJTGDqcBSzQ2yTSOxPXfUmVTr4D76vbYu5bjd9aBKx8HdfMvPeo0WD0ds/LjX
444LdJoDXcAAAEQ9fxlIfX8ZSEAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
445AAAIUEAO2vWsJhXg482IrlDsDH15Ck3r30CIhpXbrvAZIwTll6X3yaSrHU8iSP8SNOZd+D
446r6nwgpD1HHxVhyvi1URmoTAYAXBgfWWThBKoijSE+gvVnCUxg6nAUs0Nsk0jsT131JlU6+
447A++r22LuW43fWgSsfB3XzLz3qNFg9HbPy41y3SaA13AAAAQgH4DaftY0e/KsN695VJ06wy
448Ve0k2ddxoEsSE15H4lgNHM2iuYKzIqZJOReHRCTff6QGgMYPDqDfFfL1Hc1Ntql0pwAAAA
4491yb2JlcnRAYmJzZGV2AQIDBAU=
450-----END OPENSSH PRIVATE KEY-----
451";
452 assert!(
453 decode_secret_key(key, None).unwrap().algorithm()
454 == ssh_key::Algorithm::Ecdsa {
455 curve: ssh_key::EcdsaCurve::NistP521
456 },
457 );
458 }
459
460 #[test]
461 fn test_fingerprint() {
462 let key = parse_public_key_base64(
463 "AAAAC3NzaC1lZDI1NTE5AAAAILagOJFgwaMNhBWQINinKOXmqS4Gh5NgxgriXwdOoINJ",
464 )
465 .unwrap();
466 assert_eq!(
467 format!("{}", key.fingerprint(ssh_key::HashAlg::Sha256)),
468 "SHA256:ldyiXa1JQakitNU5tErauu8DvWQ1dZ7aXu+rm7KQuog"
469 );
470 }
471
472 #[test]
473 fn test_parse_p256_public_key() {
474 env_logger::try_init().unwrap_or(());
475 let key = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMxBTpMIGvo7CnordO7wP0QQRqpBwUjOLl4eMhfucfE1sjTYyK5wmTl1UqoSDS1PtRVTBdl+0+9pquFb46U7fwg=";
476
477 assert!(
478 parse_public_key_base64(key).unwrap().algorithm()
479 == ssh_key::Algorithm::Ecdsa {
480 curve: ssh_key::EcdsaCurve::NistP256
481 },
482 );
483 }
484
485 #[test]
486 fn test_parse_p384_public_key() {
487 env_logger::try_init().unwrap_or(());
488 let key = "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBBVFgxJxpCaAALZG/S5BHT8/IUQ5mfuKaj7Av9g7Jw59fBEGHfPBz1wFtHGYw5bdLmfVZTIDfogDid5zqJeAKr1AcD06DKTXDzd2EpUjqeLfQ5b3erHuX758fgu/pSDGRA==";
489
490 assert!(
491 parse_public_key_base64(key).unwrap().algorithm()
492 == ssh_key::Algorithm::Ecdsa {
493 curve: ssh_key::EcdsaCurve::NistP384
494 }
495 );
496 }
497
498 #[test]
499 fn test_parse_p521_public_key() {
500 env_logger::try_init().unwrap_or(());
501 let key = "AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAQepXEpOrzlX22r4E5zEHjhHWeZUe//zaevTanOWRBnnaCGWJFGCdjeAbNOuAmLtXc+HZdJTCZGREeSLSrpJa71QDCgZl0N7DkDUanCpHZJe/DCK6qwtHYbEMn28iLMlGCOrCIa060EyJHbp1xcJx4I1SKj/f/fm3DhhID/do6zyf8Cg==";
502
503 assert!(
504 parse_public_key_base64(key).unwrap().algorithm()
505 == ssh_key::Algorithm::Ecdsa {
506 curve: ssh_key::EcdsaCurve::NistP521
507 }
508 );
509 }
510
511 #[test]
512 fn test_srhb() {
513 env_logger::try_init().unwrap_or(());
514 let key = "AAAAB3NzaC1yc2EAAAADAQABAAACAQC0Xtz3tSNgbUQAXem4d+d6hMx7S8Nwm/DOO2AWyWCru+n/+jQ7wz2b5+3oG2+7GbWZNGj8HCc6wJSA3jUsgv1N6PImIWclD14qvoqY3Dea1J0CJgXnnM1xKzBz9C6pDHGvdtySg+yzEO41Xt4u7HFn4Zx5SGuI2NBsF5mtMLZXSi33jCIWVIkrJVd7sZaY8jiqeVZBB/UvkLPWewGVuSXZHT84pNw4+S0Rh6P6zdNutK+JbeuO+5Bav4h9iw4t2sdRkEiWg/AdMoSKmo97Gigq2mKdW12ivnXxz3VfxrCgYJj9WwaUUWSfnAju5SiNly0cTEAN4dJ7yB0mfLKope1kRhPsNaOuUmMUqlu/hBDM/luOCzNjyVJ+0LLB7SV5vOiV7xkVd4KbEGKou8eeCR3yjFazUe/D1pjYPssPL8cJhTSuMc+/UC9zD8yeEZhB9V+vW4NMUR+lh5+XeOzenl65lWYd/nBZXLBbpUMf1AOfbz65xluwCxr2D2lj46iApSIpvE63i3LzFkbGl9GdUiuZJLMFJzOWdhGGc97cB5OVyf8umZLqMHjaImxHEHrnPh1MOVpv87HYJtSBEsN4/omINCMZrk++CRYAIRKRpPKFWV7NQHcvw3m7XLR3KaTYe+0/MINIZwGdou9fLUU3zSd521vDjA/weasH0CyDHq7sZw==";
515
516 parse_public_key_base64(key).unwrap();
517 }
518
519 #[cfg(feature = "rsa")]
520 #[test]
521 fn test_nikao() {
522 env_logger::try_init().unwrap_or(());
523 let key = "-----BEGIN RSA PRIVATE KEY-----
524MIIEpQIBAAKCAQEAw/FG8YLVoXhsUVZcWaY7iZekMxQ2TAfSVh0LTnRuzsumeLhb
5250fh4scIt4C4MLwpGe/u3vj290C28jLkOtysqnIpB4iBUrFNRmEz2YuvjOzkFE8Ju
5260l1VrTZ9APhpLZvzT2N7YmTXcLz1yWopCe4KqTHczEP4lfkothxEoACXMaxezt5o
527wIYfagDaaH6jXJgJk1SQ5VYrROVpDjjX8/Zg01H1faFQUikYx0M8EwL1fY5B80Hd
5286DYSok8kUZGfkZT8HQ54DBgocjSs449CVqkVoQC1aDB+LZpMWovY15q7hFgfQmYD
529qulbZRWDxxogS6ui/zUR2IpX7wpQMKKkBS1qdQIDAQABAoIBAQCodpcCKfS2gSzP
530uapowY1KvP/FkskkEU18EDiaWWyzi1AzVn5LRo+udT6wEacUAoebLU5K2BaMF+aW
531Lr1CKnDWaeA/JIDoMDJk+TaU0i5pyppc5LwXTXvOEpzi6rCzL/O++88nR4AbQ7sm
532Uom6KdksotwtGvttJe0ktaUi058qaoFZbels5Fwk5bM5GHDdV6De8uQjSfYV813P
533tM/6A5rRVBjC5uY0ocBHxPXkqAdHfJuVk0uApjLrbm6k0M2dg1X5oyhDOf7ZIzAg
534QGPgvtsVZkQlyrD1OoCMPwzgULPXTe8SktaP9EGvKdMf5kQOqUstqfyx+E4OZa0A
535T82weLjBAoGBAOUChhaLQShL3Vsml/Nuhhw5LsxU7Li34QWM6P5AH0HMtsSncH8X
536ULYcUKGbCmmMkVb7GtsrHa4ozy0fjq0Iq9cgufolytlvC0t1vKRsOY6poC2MQgaZ
537bqRa05IKwhZdHTr9SUwB/ngtVNWRzzbFKLkn2W5oCpQGStAKqz3LbKstAoGBANsJ
538EyrXPbWbG+QWzerCIi6shQl+vzOd3cxqWyWJVaZglCXtlyySV2eKWRW7TcVvaXQr
539Nzm/99GNnux3pUCY6szy+9eevjFLLHbd+knzCZWKTZiWZWr503h/ztfFwrMzhoAh
540z4nukD/OETugPvtG01c2sxZb/F8LH9KORznhlSlpAoGBAJnqg1J9j3JU4tZTbwcG
541fo5ThHeCkINp2owPc70GPbvMqf4sBzjz46QyDaM//9SGzFwocplhNhaKiQvrzMnR
542LSVucnCEm/xdXLr/y6S6tEiFCwnx3aJv1uQRw2bBYkcDmBTAjVXPdUcyOHU+BYXr
543Jv6ioMlKlel8/SUsNoFWypeVAoGAXhr3Bjf1xlm+0O9PRyZjQ0RR4DN5eHbB/XpQ
544cL8hclsaK3V5tuek79JL1f9kOYhVeVi74G7uzTSYbCY3dJp+ftGCjDAirNEMaIGU
545cEMgAgSqs/0h06VESwg2WRQZQ57GkbR1E2DQzuj9FG4TwSe700OoC9o3gqon4PHJ
546/j9CM8kCgYEAtPJf3xaeqtbiVVzpPAGcuPyajTzU0QHPrXEl8zr/+iSK4Thc1K+c
547b9sblB+ssEUQD5IQkhTWcsXdslINQeL77WhIMZ2vBAH8Hcin4jgcLmwUZfpfnnFs
548QaChXiDsryJZwsRnruvMRX9nedtqHrgnIsJLTXjppIhGhq5Kg4RQfOU=
549-----END RSA PRIVATE KEY-----
550";
551 decode_secret_key(key, None).unwrap();
552 }
553
554 #[cfg(feature = "rsa")]
555 #[test]
556 fn test_decode_pkcs8_rsa_secret_key() {
557 let key = "-----BEGIN PRIVATE KEY-----
559MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDTwWfiCKHw/1F6
560pvm6hZpFSjCVSu4Pp0/M4xT9Cec1+2uj/6uEE9Vh/UhlerkxVbrW/YaqjnlAiemZ
5610RGN+sq7b8LxsgvOAo7gdBv13TLkKxNFiRbSy8S257uA9/K7G4Uw+NW22zoLSKCp
562pdJOFzaYMIT/UX9EOq9hIIn4bS4nXJ4V5+aHBtMddHHDQPEDHBHuifpP2L4Wopzu
563WoQoVtN9cwHSLh0Bd7uT+X9useIJrFzcsxVXwD2WGfR59Ue3rxRu6JqC46Klf55R
5645NQ8OQ+7NHXjW5HO076W1GXcnhGKT5CGjglTdk5XxQkNZsz72cHu7RDaADdWAWnE
565hSyH7flrAgMBAAECggEAbFdpCjn2eTJ4grOJ1AflTYxO3SOQN8wXxTFuHKUDehgg
566E7GNFK99HnyTnPA0bmx5guQGEZ+BpCarsXpJbAYj0dC1wimhZo7igS6G272H+zua
567yZoBZmrBQ/++bJbvxxGmjM7TsZHq2bkYEpR3zGKOGUHB2kvdPJB2CNC4JrXdxl7q
568djjsr5f/SreDmHqcNBe1LcyWLSsuKTfwTKhsE1qEe6QA2uOpUuFrsdPoeYrfgapu
569sK6qnpxvOTJHCN/9jjetrP2fGl78FMBYfXzjAyKSKzLvzOwMAmcHxy50RgUvezx7
570A1RwMpB7VoV0MOpcAjlQ1T7YDH9avdPMzp0EZ24y+QKBgQD/MxDJjHu33w13MnIg
571R4BrgXvrgL89Zde5tML2/U9C2LRvFjbBvgnYdqLsuqxDxGY/8XerrAkubi7Fx7QI
572m2uvTOZF915UT/64T35zk8nAAFhzicCosVCnBEySvdwaaBKoj/ywemGrwoyprgFe
573r8LGSo42uJi0zNf5IxmVzrDlRwKBgQDUa3P/+GxgpUYnmlt63/7sII6HDssdTHa9
574x5uPy8/2ackNR7FruEAJR1jz6akvKnvtbCBeRxLeOFwsseFta8rb2vks7a/3I8ph
575gJlbw5Bttpc+QsNgC61TdSKVsfWWae+YT77cfGPM4RaLlxRnccW1/HZjP2AMiDYG
576WCiluO+svQKBgQC3a/yk4FQL1EXZZmigysOCgY6Ptfm+J3TmBQYcf/R4F0mYjl7M
5774coxyxNPEty92Gulieh5ey0eMhNsFB1SEmNTm/HmV+V0tApgbsJ0T8SyO41Xfar7
578lHZjlLN0xQFt+V9vyA3Wyh9pVGvFiUtywuE7pFqS+hrH2HNindfF1MlQAQKBgQDF
579YxBIxKzY5duaA2qMdMcq3lnzEIEXua0BTxGz/n1CCizkZUFtyqnetWjoRrGK/Zxp
580FDfDw6G50397nNPQXQEFaaZv5HLGYYC3N8vKJKD6AljqZxmsD03BprA7kEGYwtn8
581m+XMdt46TNMpZXt1YJiLMo1ETmjPXGdvX85tqLs2tQKBgQDCbwd+OBzSiic3IQlD
582E/OHAXH6HNHmUL3VD5IiRh4At2VAIl8JsmafUvvbtr5dfT3PA8HB6sDG4iXQsBbR
583oTSAo/DtIWt1SllGx6MvcPqL1hp1UWfoIGTnE3unHtgPId+DnjMbTcuZOuGl7evf
584abw8VeY2goORjpBXsfydBETbgQ==
585-----END PRIVATE KEY-----
586";
587 assert!(decode_secret_key(key, None).unwrap().algorithm().is_rsa());
588 test_decode_encode_symmetry(key);
589 }
590
591 #[test]
592 fn test_decode_pkcs8_p256_secret_key() {
593 let key = "-----BEGIN PRIVATE KEY-----
595MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgE0C7/pyJDcZTAgWo
596ydj6EE8QkZ91jtGoGmdYAVd7LaqhRANCAATWkGOof7R/PAUuOr2+ZPUgB8rGVvgr
597qa92U3p4fkJToKXku5eq/32OBj23YMtz76jO3yfMbtG3l1JWLowPA8tV
598-----END PRIVATE KEY-----
599";
600 assert!(
601 decode_secret_key(key, None).unwrap().algorithm()
602 == ssh_key::Algorithm::Ecdsa {
603 curve: ssh_key::EcdsaCurve::NistP256
604 },
605 );
606 test_decode_encode_symmetry(key);
607 }
608
609 #[test]
610 fn test_decode_pkcs8_p384_secret_key() {
611 let key = "-----BEGIN PRIVATE KEY-----
613MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCaqAL30kg+T5BUOYG9
614MrzeDXiUwy9LM8qJGNXiMYou0pVjFZPZT3jAsrUQo47PLQ6hZANiAARuEHbXJBYK
6159uyJj4PjT56OHjT2GqMa6i+FTG9vdLtu4OLUkXku+kOuFNjKvEI1JYBrJTpw9kSZ
616CI3WfCsQvVjoC7m8qRyxuvR3Rv8gGXR1coQciIoCurLnn9zOFvXCS2Y=
617-----END PRIVATE KEY-----
618";
619 assert!(
620 decode_secret_key(key, None).unwrap().algorithm()
621 == ssh_key::Algorithm::Ecdsa {
622 curve: ssh_key::EcdsaCurve::NistP384
623 },
624 );
625 test_decode_encode_symmetry(key);
626 }
627
628 #[test]
629 fn test_decode_pkcs8_p521_secret_key() {
630 let key = "-----BEGIN PRIVATE KEY-----
632MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB1As9UBUsCiMK7Rzs
633EoMgqDM/TK7y7+HgCWzw5UujXvSXCzYCeBgfJszn7dVoJE9G/1ejmpnVTnypdKEu
634iIvd4LyhgYkDgYYABAADBCrg7hkomJbCsPMuMcq68ulmo/6Tv8BDS13F8T14v5RN
635/0iT/+nwp6CnbBFewMI2TOh/UZNyPpQ8wOFNn9zBmAFCMzkQibnSWK0hrRstY5LT
636iaOYDwInbFDsHu8j3TGs29KxyVXMexeV6ROQyXzjVC/quT1R5cOQ7EadE4HvaWhT
637Ow==
638-----END PRIVATE KEY-----
639";
640 assert!(
641 decode_secret_key(key, None).unwrap().algorithm()
642 == ssh_key::Algorithm::Ecdsa {
643 curve: ssh_key::EcdsaCurve::NistP521
644 },
645 );
646 test_decode_encode_symmetry(key);
647 }
648
649 #[test]
650 #[cfg(feature = "legacy-ed25519-pkcs8-parser")]
651 fn test_decode_pkcs8_ed25519_generated_by_russh_0_43() -> Result<(), crate::keys::Error> {
652 let key = "-----BEGIN PRIVATE KEY-----
654MHMCAQEwBQYDK2VwBEIEQBHw4cXPpGgA+KdvPF5gxrzML+oa3yQk0JzIbWvmqM5H30RyBF8GrOWz
655p77UAd3O4PgYzzFcUc79g8yKtbKhzJGhIwMhAN9EcgRfBqzls6e+1AHdzuD4GM8xXFHO/YPMirWy
656ocyR
657
658-----END PRIVATE KEY-----
659";
660
661 assert!(decode_secret_key(key, None)?.algorithm() == ssh_key::Algorithm::Ed25519,);
662
663 let k = decode_secret_key(key, None)?;
664 let inner = k.key_data().ed25519().unwrap();
665
666 assert_eq!(
667 &inner.private.to_bytes(),
668 &[
669 17, 240, 225, 197, 207, 164, 104, 0, 248, 167, 111, 60, 94, 96, 198, 188, 204, 47,
670 234, 26, 223, 36, 36, 208, 156, 200, 109, 107, 230, 168, 206, 71
671 ]
672 );
673
674 Ok(())
675 }
676
677 fn test_decode_encode_symmetry(key: &str) {
678 let original_key_bytes = data_encoding::BASE64_MIME
679 .decode(
680 key.lines()
681 .filter(|line| !line.starts_with("-----"))
682 .collect::<Vec<&str>>()
683 .join("")
684 .as_bytes(),
685 )
686 .unwrap();
687 let decoded_key = decode_secret_key(key, None).unwrap();
688 let encoded_key_bytes = pkcs8::encode_pkcs8(&decoded_key).unwrap();
689 assert_eq!(original_key_bytes, encoded_key_bytes);
690 }
691
692 #[cfg(feature = "rsa")]
693 #[test]
694 fn test_o01eg() {
695 env_logger::try_init().unwrap_or(());
696
697 let key = "-----BEGIN RSA PRIVATE KEY-----
698Proc-Type: 4,ENCRYPTED
699DEK-Info: AES-128-CBC,EA77308AAF46981303D8C44D548D097E
700
701QR18hXmAgGehm1QMMYGF34PAtBpTj+8/ZPFx2zZxir7pzDpfYoNAIf/fzLsW1ruG
7020xo/ZK/T3/TpMgjmLsCR6q+KU4jmCcCqWQIGWYJt9ljFI5y/CXr5uqP3DKcqtdxQ
703fbBAfXJ8ITF+Tj0Cljm2S1KYHor+mkil5Lf/ZNiHxcLfoI3xRnpd+2cemN9Ly9eY
704HNTbeWbLosfjwdfPJNWFNV5flm/j49klx/UhXhr5HNFNgp/MlTrvkH4rBt4wYPpE
705cZBykt4Fo1KGl95pT22inGxQEXVHF1Cfzrf5doYWxjiRTmfhpPSz/Tt0ev3+jIb8
706Htx6N8tNBoVxwCiQb7jj3XNim2OGohIp5vgW9sh6RDfIvr1jphVOgCTFKSo37xk0
707156EoCVo3VcLf+p0/QitbUHR+RGW/PvUJV/wFR5ShYqjI+N2iPhkD24kftJ/MjPt
708AAwCm/GYoYjGDhIzQMB+FETZKU5kz23MQtZFbYjzkcI/RE87c4fkToekNCdQrsoZ
709wG0Ne2CxrwwEnipHCqT4qY+lZB9EbqQgbWOXJgxA7lfznBFjdSX7uDc/mnIt9Y6B
710MZRXH3PTfotHlHMe+Ypt5lfPBi/nruOl5wLo3L4kY5pUyqR0cXKNycIJZb/pJAnE
711ryIb59pZP7njvoHzRqnC9dycnTFW3geK5LU+4+JMUS32F636aorunRCl6IBmVQHL
712uZ+ue714fn/Sn6H4dw6IH1HMDG1hr8ozP4sNUCiAQ05LsjDMGTdrUsr2iBBpkQhu
713VhUDZy9g/5XF1EgiMbZahmqi5WaJ5K75ToINHb7RjOE7MEiuZ+RPpmYLE0HXyn9X
714HTx0ZGr022dDI6nkvUm6OvEwLUUmmGKRHKe0y1EdICGNV+HWqnlhGDbLWeMyUcIY
715M6Zh9Dw3WXD3kROf5MrJ6n9MDIXx9jy7nmBh7m6zKjBVIw94TE0dsRcWb0O1IoqS
716zLQ6ihno+KsQHDyMVLEUz1TuE52rIpBmqexDm3PdDfCgsNdBKP6QSTcoqcfHKeex
717K93FWgSlvFFQQAkJumJJ+B7ZWnK+2pdjdtWwTpflAKNqc8t//WmjWZzCtbhTHCXV
7181dnMk7azWltBAuXnjW+OqmuAzyh3ayKgqfW66mzSuyQNa1KqFhqpJxOG7IHvxVfQ
719kYeSpqODnL87Zd/dU8s0lOxz3/ymtjPMHlOZ/nHNqW90IIeUwWJKJ46Kv6zXqM1t
720MeD1lvysBbU9rmcUdop0D3MOgGpKkinR5gy4pUsARBiz4WhIm8muZFIObWes/GDS
721zmmkQRO1IcfXKAHbq/OdwbLBm4vM9nk8vPfszoEQCnfOSd7aWrLRjDR+q2RnzNzh
722K+fodaJ864JFIfB/A+aVviVWvBSt0eEbEawhTmNPerMrAQ8tRRhmNxqlDP4gOczi
723iKUmK5recsXk5us5Ik7peIR/f9GAghpoJkF0HrHio47SfABuK30pzcj62uNWGljS
7243d9UQLCepT6RiPFhks/lgimbtSoiJHql1H9Q/3q4MuO2PuG7FXzlTnui3zGw/Vvy
725br8gXU8KyiY9sZVbmplRPF+ar462zcI2kt0a18mr0vbrdqp2eMjb37QDbVBJ+rPE
726-----END RSA PRIVATE KEY-----
727";
728 decode_secret_key(key, Some("12345")).unwrap();
729 }
730
731 #[cfg(feature = "rsa")]
732 pub const PKCS8_RSA: &str = "-----BEGIN RSA PRIVATE KEY-----
733MIIEpAIBAAKCAQEAwBGetHjW+3bDQpVktdemnk7JXgu1NBWUM+ysifYLDBvJ9ttX
734GNZSyQKA4v/dNr0FhAJ8I9BuOTjYCy1YfKylhl5D/DiSSXFPsQzERMmGgAlYvU2U
735+FTxpBC11EZg69CPVMKKevfoUD+PZA5zB7Hc1dXFfwqFc5249SdbAwD39VTbrOUI
736WECvWZs6/ucQxHHXP2O9qxWqhzb/ddOnqsDHUNoeceiNiCf2anNymovrIMjAqq1R
737t2UP3f06/Zt7Jx5AxKqS4seFkaDlMAK8JkEDuMDOdKI36raHkKanfx8CnGMSNjFQ
738QtvnpD8VSGkDTJN3Qs14vj2wvS477BQXkBKN1QIDAQABAoIBABb6xLMw9f+2ENyJ
739hTggagXsxTjkS7TElCu2OFp1PpMfTAWl7oDBO7xi+UqvdCcVbHCD35hlWpqsC2Ui
7408sBP46n040ts9UumK/Ox5FWaiuYMuDpF6vnfJ94KRcb0+KmeFVf9wpW9zWS0hhJh
741jC+yfwpyfiOZ/ad8imGCaOguGHyYiiwbRf381T/1FlaOGSae88h+O8SKTG1Oahq4
7420HZ/KBQf9pij0mfVQhYBzsNu2JsHNx9+DwJkrXT7K9SHBpiBAKisTTCnQmS89GtE
7436J2+bq96WgugiM7X6OPnmBmE/q1TgV18OhT+rlvvNi5/n8Z1ag5Xlg1Rtq/bxByP
744CeIVHsECgYEA9dX+LQdv/Mg/VGIos2LbpJUhJDj0XWnTRq9Kk2tVzr+9aL5VikEb
74509UPIEa2ToL6LjlkDOnyqIMd/WY1W0+9Zf1ttg43S/6Rvv1W8YQde0Nc7QTcuZ1K
7469jSSP9hzsa3KZtx0fCtvVHm+ac9fP6u80tqumbiD2F0cnCZcSxOb4+UCgYEAyAKJ
74770nNKegH4rTCStAqR7WGAsdPE3hBsC814jguplCpb4TwID+U78Xxu0DQF8WtVJ10
748SJuR0R2q4L9uYWpo0MxdawSK5s9Am27MtJL0mkFQX0QiM7hSZ3oqimsdUdXwxCGg
749oktxCUUHDIPJNVd4Xjg0JTh4UZT6WK9hl1zLQzECgYEAiZRCFGc2KCzVLF9m0cXA
750kGIZUxFAyMqBv+w3+zq1oegyk1z5uE7pyOpS9cg9HME2TAo4UPXYpLAEZ5z8vWZp
75145sp/BoGnlQQsudK8gzzBtnTNp5i/MnnetQ/CNYVIVnWjSxRUHBqdMdRZhv0/Uga
752e5KA5myZ9MtfSJA7VJTbyHUCgYBCcS13M1IXaMAt3JRqm+pftfqVs7YeJqXTrGs/
753AiDlGQigRk4quFR2rpAV/3rhWsawxDmb4So4iJ16Wb2GWP4G1sz1vyWRdSnmOJGC
754LwtYrvfPHegqvEGLpHa7UsgDpol77hvZriwXwzmLO8A8mxkeW5dfAfpeR5o+mcxW
755pvnTEQKBgQCKx6Ln0ku6jDyuDzA9xV2/PET5D75X61R2yhdxi8zurY/5Qon3OWzk
756jn/nHT3AZghGngOnzyv9wPMKt9BTHyTB6DlB6bRVLDkmNqZh5Wi8U1/IjyNYI0t2
757xV/JrzLAwPoKk3bkqys3bUmgo6DxVC/6RmMwPQ0rmpw78kOgEej90g==
758-----END RSA PRIVATE KEY-----
759";
760
761 #[cfg(feature = "rsa")]
762 #[test]
763 fn test_pkcs8() {
764 env_logger::try_init().unwrap_or(());
765 println!("test");
766 decode_secret_key(PKCS8_RSA, Some("blabla")).unwrap();
767 }
768
769 #[cfg(feature = "rsa")]
770 const PKCS8_ENCRYPTED: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----
771MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITo1O0b8YrS0CAggA
772MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBtLH4T1KOfo1GGr7salhR8BIIE
7730KN9ednYwcTGSX3hg7fROhTw7JAJ1D4IdT1fsoGeNu2BFuIgF3cthGHe6S5zceI2
774MpkfwvHbsOlDFWMUIAb/VY8/iYxhNmd5J6NStMYRC9NC0fVzOmrJqE1wITqxtORx
775IkzqkgFUbaaiFFQPepsh5CvQfAgGEWV329SsTOKIgyTj97RxfZIKA+TR5J5g2dJY
776j346SvHhSxJ4Jc0asccgMb0HGh9UUDzDSql0OIdbnZW5KzYJPOx+aDqnpbz7UzY/
777P8N0w/pEiGmkdkNyvGsdttcjFpOWlLnLDhtLx8dDwi/sbEYHtpMzsYC9jPn3hnds
778TcotqjoSZ31O6rJD4z18FOQb4iZs3MohwEdDd9XKblTfYKM62aQJWH6cVQcg+1C7
779jX9l2wmyK26Tkkl5Qg/qSfzrCveke5muZgZkFwL0GCcgPJ8RixSB4GOdSMa/hAMU
780kvFAtoV2GluIgmSe1pG5cNMhurxM1dPPf4WnD+9hkFFSsMkTAuxDZIdDk3FA8zof
781Yhv0ZTfvT6V+vgH3Hv7Tqcxomy5Qr3tj5vvAqqDU6k7fC4FvkxDh2mG5ovWvc4Nb
782Xv8sed0LGpYitIOMldu6650LoZAqJVv5N4cAA2Edqldf7S2Iz1QnA/usXkQd4tLa
783Z80+sDNv9eCVkfaJ6kOVLk/ghLdXWJYRLenfQZtVUXrPkaPpNXgD0dlaTN8KuvML
784Uw/UGa+4ybnPsdVflI0YkJKbxouhp4iB4S5ACAwqHVmsH5GRnujf10qLoS7RjDAl
785o/wSHxdT9BECp7TT8ID65u2mlJvH13iJbktPczGXt07nBiBse6OxsClfBtHkRLzE
786QF6UMEXsJnIIMRfrZQnduC8FUOkfPOSXc8r9SeZ3GhfbV/DmWZvFPCpjzKYPsM5+
787N8Bw/iZ7NIH4xzNOgwdp5BzjH9hRtCt4sUKVVlWfEDtTnkHNOusQGKu7HkBF87YZ
788RN/Nd3gvHob668JOcGchcOzcsqsgzhGMD8+G9T9oZkFCYtwUXQU2XjMN0R4VtQgZ
789rAxWyQau9xXMGyDC67gQ5xSn+oqMK0HmoW8jh2LG/cUowHFAkUxdzGadnjGhMOI2
790zwNJPIjF93eDF/+zW5E1l0iGdiYyHkJbWSvcCuvTwma9FIDB45vOh5mSR+YjjSM5
791nq3THSWNi7Cxqz12Q1+i9pz92T2myYKBBtu1WDh+2KOn5DUkfEadY5SsIu/Rb7ub
7925FBihk2RN3y/iZk+36I69HgGg1OElYjps3D+A9AjVby10zxxLAz8U28YqJZm4wA/
793T0HLxBiVw+rsHmLP79KvsT2+b4Diqih+VTXouPWC/W+lELYKSlqnJCat77IxgM9e
794YIhzD47OgWl33GJ/R10+RDoDvY4koYE+V5NLglEhbwjloo9Ryv5ywBJNS7mfXMsK
795/uf+l2AscZTZ1mhtL38efTQCIRjyFHc3V31DI0UdETADi+/Omz+bXu0D5VvX+7c6
796b1iVZKpJw8KUjzeUV8yOZhvGu3LrQbhkTPVYL555iP1KN0Eya88ra+FUKMwLgjYr
797JkUx4iad4dTsGPodwEP/Y9oX/Qk3ZQr+REZ8lg6IBoKKqqrQeBJ9gkm1jfKE6Xkc
798Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux
799-----END ENCRYPTED PRIVATE KEY-----";
800
801 #[test]
802 fn test_gpg() {
803 env_logger::try_init().unwrap_or(());
804 let key = [
805 0, 0, 0, 7, 115, 115, 104, 45, 114, 115, 97, 0, 0, 0, 3, 1, 0, 1, 0, 0, 1, 129, 0, 163,
806 72, 59, 242, 4, 248, 139, 217, 57, 126, 18, 195, 170, 3, 94, 154, 9, 150, 89, 171, 236,
807 192, 178, 185, 149, 73, 210, 121, 95, 126, 225, 209, 199, 208, 89, 130, 175, 229, 163,
808 102, 176, 155, 69, 199, 155, 71, 214, 170, 61, 202, 2, 207, 66, 198, 147, 65, 10, 176,
809 20, 105, 197, 133, 101, 126, 193, 252, 245, 254, 182, 14, 250, 118, 113, 18, 220, 38,
810 220, 75, 247, 50, 163, 39, 2, 61, 62, 28, 79, 199, 238, 189, 33, 194, 190, 22, 87, 91,
811 1, 215, 115, 99, 138, 124, 197, 127, 237, 228, 170, 42, 25, 117, 1, 106, 36, 54, 163,
812 163, 207, 129, 133, 133, 28, 185, 170, 217, 12, 37, 113, 181, 182, 180, 178, 23, 198,
813 233, 31, 214, 226, 114, 146, 74, 205, 177, 82, 232, 238, 165, 44, 5, 250, 150, 236, 45,
814 30, 189, 254, 118, 55, 154, 21, 20, 184, 235, 223, 5, 20, 132, 249, 147, 179, 88, 146,
815 6, 100, 229, 200, 221, 157, 135, 203, 57, 204, 43, 27, 58, 85, 54, 219, 138, 18, 37,
816 80, 106, 182, 95, 124, 140, 90, 29, 48, 193, 112, 19, 53, 84, 201, 153, 52, 249, 15,
817 41, 5, 11, 147, 18, 8, 27, 31, 114, 45, 224, 118, 111, 176, 86, 88, 23, 150, 184, 252,
818 128, 52, 228, 90, 30, 34, 135, 234, 123, 28, 239, 90, 202, 239, 188, 175, 8, 141, 80,
819 59, 194, 80, 43, 205, 34, 137, 45, 140, 244, 181, 182, 229, 247, 94, 216, 115, 173,
820 107, 184, 170, 102, 78, 249, 4, 186, 234, 169, 148, 98, 128, 33, 115, 232, 126, 84, 76,
821 222, 145, 90, 58, 1, 4, 163, 243, 93, 215, 154, 205, 152, 178, 109, 241, 197, 82, 148,
822 222, 78, 44, 193, 248, 212, 157, 118, 217, 75, 211, 23, 229, 121, 28, 180, 208, 173,
823 204, 14, 111, 226, 25, 163, 220, 95, 78, 175, 189, 168, 67, 159, 179, 176, 200, 150,
824 202, 248, 174, 109, 25, 89, 176, 220, 226, 208, 187, 84, 169, 157, 14, 88, 217, 221,
825 117, 254, 51, 45, 93, 184, 80, 225, 158, 29, 76, 38, 69, 72, 71, 76, 50, 191, 210, 95,
826 152, 175, 26, 207, 91, 7,
827 ];
828 ssh_key::PublicKey::decode(&key).unwrap();
829 }
830
831 #[cfg(feature = "rsa")]
832 #[test]
833 fn test_pkcs8_encrypted() {
834 env_logger::try_init().unwrap_or(());
835 println!("test");
836 decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
837 }
838
839 #[cfg(unix)]
840 async fn test_client_agent(key: PrivateKey) -> Result<(), Box<dyn std::error::Error>> {
841 env_logger::try_init().unwrap_or(());
842 use std::process::Stdio;
843
844 let dir = tempfile::tempdir()?;
845 let agent_path = dir.path().join("agent");
846 let mut agent = tokio::process::Command::new("ssh-agent")
847 .arg("-a")
848 .arg(&agent_path)
849 .arg("-D")
850 .stdout(Stdio::null())
851 .stderr(Stdio::null())
852 .spawn()?;
853
854 while agent_path.canonicalize().is_err() {
856 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
857 }
858
859 let public = key.public_key();
860 let stream = tokio::net::UnixStream::connect(&agent_path).await?;
861 let mut client = agent::client::AgentClient::connect(stream);
862 client.add_identity(&key, &[]).await?;
863 client.request_identities().await?;
864 let buf = russh_cryptovec::CryptoVec::from_slice(b"blabla");
865 let len = buf.len();
866 let buf = client
867 .sign_request(public, Some(HashAlg::Sha256), buf)
868 .await
869 .unwrap();
870 let (a, b) = buf.split_at(len);
871
872 match key.public_key().key_data() {
873 ssh_key::public::KeyData::Ed25519 { .. } => {
874 let sig = &b[b.len() - 64..];
875 let sig = ssh_key::Signature::new(key.algorithm(), sig)?;
876 use signature::Verifier;
877 assert!(Verifier::verify(public, a, &sig).is_ok());
878 }
879 ssh_key::public::KeyData::Ecdsa { .. } => {}
880 _ => {}
881 }
882
883 agent.kill().await?;
884 agent.wait().await?;
885
886 Ok(())
887 }
888
889 #[tokio::test]
890 #[cfg(unix)]
891 async fn test_client_agent_ed25519() {
892 let key = decode_secret_key(ED25519_KEY, Some("blabla")).unwrap();
893 test_client_agent(key).await.expect("ssh-agent test failed")
894 }
895
896 #[tokio::test]
897 #[cfg(all(unix, feature = "rsa"))]
898 async fn test_client_agent_rsa() {
899 let key = decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
900 test_client_agent(key).await.expect("ssh-agent test failed")
901 }
902
903 #[tokio::test]
904 #[cfg(all(unix, feature = "rsa"))]
905 async fn test_client_agent_openssh_rsa() {
906 let key = decode_secret_key(RSA_KEY, None).unwrap();
907 test_client_agent(key).await.expect("ssh-agent test failed")
908 }
909
910 #[test]
911 #[cfg(all(unix, feature = "rsa"))]
912 fn test_agent() {
913 env_logger::try_init().unwrap_or(());
914 let dir = tempfile::tempdir().unwrap();
915 let agent_path = dir.path().join("agent");
916
917 let core = tokio::runtime::Runtime::new().unwrap();
918 use agent;
919 use signature::Verifier;
920
921 #[derive(Clone)]
922 struct X {}
923 impl agent::server::Agent for X {
924 fn confirm(
925 self,
926 _: std::sync::Arc<PrivateKey>,
927 ) -> Box<dyn Future<Output = (Self, bool)> + Send + Unpin> {
928 Box::new(futures::future::ready((self, true)))
929 }
930 }
931 let agent_path_ = agent_path.clone();
932 let (tx, rx) = tokio::sync::oneshot::channel();
933 core.spawn(async move {
934 let mut listener = tokio::net::UnixListener::bind(&agent_path_).unwrap();
935 let _ = tx.send(());
936 agent::server::serve(
937 Incoming {
938 listener: &mut listener,
939 },
940 X {},
941 )
942 .await
943 });
944
945 let key = decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
946 core.block_on(async move {
947 let public = key.public_key();
948 rx.await.unwrap();
950 let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
951 let mut client = agent::client::AgentClient::connect(stream);
952 client
953 .add_identity(&key, &[agent::Constraint::KeyLifetime { seconds: 60 }])
954 .await
955 .unwrap();
956 client.request_identities().await.unwrap();
957 let buf = russh_cryptovec::CryptoVec::from_slice(b"blabla");
958 let len = buf.len();
959 let buf = client.sign_request(public, None, buf).await.unwrap();
960 let (a, b) = buf.split_at(len);
961 if let ssh_key::public::KeyData::Ed25519 { .. } = public.key_data() {
962 let sig = &b[b.len() - 64..];
963 let sig = ssh_key::Signature::new(key.algorithm(), sig).unwrap();
964 assert!(Verifier::verify(public, a, &sig).is_ok());
965 }
966 })
967 }
968
969 #[cfg(unix)]
970 struct Incoming<'a> {
971 listener: &'a mut tokio::net::UnixListener,
972 }
973
974 #[cfg(unix)]
975 impl futures::stream::Stream for Incoming<'_> {
976 type Item = Result<tokio::net::UnixStream, std::io::Error>;
977
978 fn poll_next(
979 self: std::pin::Pin<&mut Self>,
980 cx: &mut std::task::Context<'_>,
981 ) -> std::task::Poll<Option<Self::Item>> {
982 let (sock, _addr) = futures::ready!(self.get_mut().listener.poll_accept(cx))?;
983 std::task::Poll::Ready(Some(Ok(sock)))
984 }
985 }
986}