bssh_russh/keys/
mod.rs

1//! This crate contains methods to deal with SSH keys, as defined in
2//! crate Russh. This includes in particular various functions for
3//! opening key files, deciphering encrypted keys, and dealing with
4//! agents.
5//!
6//! The following example shows how to do all these in a single example:
7//! start and SSH agent server, connect to it with a client, decipher
8//! an encrypted ED25519 private key (the password is `b"blabla"`), send it to
9//! the agent, and ask the agent to sign a piece of data
10//! (`b"Please sign this"`, below).
11//!
12//!```
13//! use russh::keys::*;
14//! use futures::Future;
15//!
16//! #[derive(Clone)]
17//! struct X{}
18//! impl agent::server::Agent for X {
19//!     fn confirm(self, _: std::sync::Arc<PrivateKey>) -> Box<dyn Future<Output = (Self, bool)> + Send + Unpin> {
20//!         Box::new(futures::future::ready((self, true)))
21//!     }
22//! }
23//!
24//! const PKCS8_ENCRYPTED: &'static str = "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIGjMF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBAWQiUHKoocuxfoZ/hF\nYTjkAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQ83d1d5/S2wz475uC\nCUrE7QRAvdVpD5e3zKH/MZjilWrMOm6cyI1LKBCssLztPyvOALtroLAPlp7WYWfu\n9Sncmm7u14n2lia7r1r5I3VBsVuH0g==\n-----END ENCRYPTED PRIVATE KEY-----\n";
25//!
26//! #[cfg(unix)]
27//! fn main() {
28//!    env_logger::try_init().unwrap_or(());
29//!    let dir = tempfile::tempdir().unwrap();
30//!    let agent_path = dir.path().join("agent");
31//!
32//!    let mut core = tokio::runtime::Runtime::new().unwrap();
33//!    let agent_path_ = agent_path.clone();
34//!    // Starting a server
35//!    core.spawn(async move {
36//!        let mut listener = tokio::net::UnixListener::bind(&agent_path_)
37//!            .unwrap();
38//!        russh::keys::agent::server::serve(tokio_stream::wrappers::UnixListenerStream::new(listener), X {}).await
39//!    });
40//!    let key = decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
41//!    let public = key.public_key().clone();
42//!    core.block_on(async move {
43//!        let stream = tokio::net::UnixStream::connect(&agent_path).await?;
44//!        let mut client = agent::client::AgentClient::connect(stream);
45//!        client.add_identity(&key, &[agent::Constraint::KeyLifetime { seconds: 60 }]).await?;
46//!        client.request_identities().await?;
47//!        let buf = b"signed message";
48//!        let sig = client.sign_request(&public, None, russh_cryptovec::CryptoVec::from_slice(&buf[..])).await.unwrap();
49//!        // Here, `sig` is encoded in a format usable internally by the SSH protocol.
50//!        Ok::<(), Error>(())
51//!    }).unwrap()
52//! }
53//!
54//! #[cfg(not(unix))]
55//! fn main() {}
56//!
57//! ```
58
59use 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::*;
76// Reexports
77pub use signature;
78pub use ssh_encoding;
79pub use ssh_key::{self, Algorithm, Certificate, EcdsaCurve, HashAlg, PrivateKey, PublicKey};
80
81/// OpenSSH agent protocol implementation
82pub 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    /// The key could not be read, for an unknown reason
93    #[error("Could not read key")]
94    CouldNotReadKey,
95    /// The type of the key is unsupported
96    #[error("Unsupported key type {}", key_type_string)]
97    UnsupportedKeyType {
98        key_type_string: String,
99        key_type_raw: Vec<u8>,
100    },
101    /// The type of the key is unsupported
102    #[error("Invalid Ed25519 key data")]
103    Ed25519KeyError(#[from] ed25519_dalek::SignatureError),
104    /// The type of the key is unsupported
105    #[error("Invalid ECDSA key data")]
106    EcdsaKeyError(#[from] p256::elliptic_curve::Error),
107    /// The key is encrypted (should supply a password?)
108    #[error("The key is encrypted")]
109    KeyIsEncrypted,
110    /// The key contents are inconsistent
111    #[error("The key is corrupt")]
112    KeyIsCorrupt,
113    /// Home directory could not be found
114    #[error("No home directory found")]
115    NoHomeDir,
116    /// The server key has changed
117    #[error("The server key changed at line {}", line)]
118    KeyChanged { line: usize },
119    /// The key uses an unsupported algorithm
120    #[error("Unknown key algorithm: {0}")]
121    UnknownAlgorithm(::pkcs8::ObjectIdentifier),
122    /// Index out of bounds
123    #[error("Index out of bounds")]
124    IndexOutOfBounds,
125    /// Unknown signature type
126    #[error("Unknown signature type: {}", sig_type)]
127    UnknownSignatureType { sig_type: String },
128    #[error("Invalid signature")]
129    InvalidSignature,
130    #[error("Invalid parameters")]
131    InvalidParameters,
132    /// Agent protocol error
133    #[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
199/// Load a public key from a file. Ed25519, EC-DSA and RSA keys are supported.
200///
201/// ```
202/// russh::keys::load_public_key("../files/id_ed25519.pub").unwrap();
203/// ```
204pub 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
217/// Reads a public key from the standard encoding. In some cases, the
218/// encoding is prefixed with a key type identifier and a space (such
219/// as `ssh-ed25519 AAAAC3N...`).
220///
221/// ```
222/// russh::keys::parse_public_key_base64("AAAAC3NzaC1lZDI1NTE5AAAAIJdD7y3aLq454yWBdwLWbieU1ebz9/cu7/QEXn9OIeZJ").is_ok();
223/// ```
224pub 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    /// Create the base64 part of the public key blob.
231    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
252/// Load a secret key, deciphering it with the supplied password if necessary.
253pub 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
263/// Load a openssh certificate
264pub 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    // password is 'test'
300    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    // Key from RFC 8410 Section 10.3. This is a key using PrivateKeyInfo structure.
351    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        // We always encode public key, skip test_decode_encode_symmetry.
365    }
366
367    // Key from RFC 8410 Section 10.3. This is a key using OneAsymmetricKey structure.
368    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        // We can't encode attributes, skip test_decode_encode_symmetry.
384    }
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        // Generated using: ssh-keygen -t ecdsa -b 256 -m rfc4716 -f $file
396        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        // Generated using: ssh-keygen -t ecdsa -b 384 -m rfc4716 -f $file
417        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        // Generated using: ssh-keygen -t ecdsa -b 521 -m rfc4716 -f $file
439        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        // Generated using: ssh-keygen -t rsa -b 1024 -m pkcs8 -f $file
558        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        // Generated using: ssh-keygen -t ecdsa -b 256 -m pkcs8 -f $file
594        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        // Generated using: ssh-keygen -t ecdsa -b 384 -m pkcs8 -f $file
612        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        // Generated using: ssh-keygen -t ecdsa -b 521 -m pkcs8 -f $file
631        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        // Generated by russh 0.43
653        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        // Wait for the socket to be created
855        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            // make sure the listener created the file handle
949            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}