use std::borrow::Cow; use std::sync::Arc; use async_trait::async_trait; use russh::client::Handler; use russh::kex::DH_G1_SHA1; use russh::kex::ECDH_SHA2_NISTP256; use russh_keys::key::SSH_RSA; use super::BrocadeOptions; use super::Error; #[derive(Default, Clone, Debug)] pub struct SshOptions { pub preferred_algorithms: russh::Preferred, } impl SshOptions { fn ecdhsa_sha2_nistp256() -> Self { Self { preferred_algorithms: russh::Preferred { kex: Cow::Borrowed(&[ECDH_SHA2_NISTP256]), key: Cow::Borrowed(&[SSH_RSA]), ..Default::default() }, } } fn legacy() -> Self { Self { preferred_algorithms: russh::Preferred { kex: Cow::Borrowed(&[DH_G1_SHA1]), key: Cow::Borrowed(&[SSH_RSA]), ..Default::default() }, } } } pub struct Client; #[async_trait] impl Handler for Client { type Error = Error; async fn check_server_key( &mut self, _server_public_key: &russh_keys::key::PublicKey, ) -> Result { Ok(true) } } pub async fn try_init_client( username: &str, password: &str, ip: &std::net::IpAddr, base_options: BrocadeOptions, ) -> Result { let ssh_options = vec![ SshOptions::default(), SshOptions::ecdhsa_sha2_nistp256(), SshOptions::legacy(), ]; for ssh in ssh_options { let opts = BrocadeOptions { ssh, ..base_options.clone() }; let client = create_client(*ip, 22, username, password, &opts).await; match client { Ok(_) => { return Ok(opts); } Err(e) => match e { Error::NetworkError(e) => { if e.contains("No common key exchange algorithm") { continue; } else { return Err(Error::NetworkError(e)); } } _ => return Err(e), }, } } Err(Error::NetworkError( "Could not establish ssh connection: wrong key exchange algorithm)".to_string(), )) } pub async fn create_client( ip: std::net::IpAddr, port: u16, username: &str, password: &str, options: &BrocadeOptions, ) -> Result, Error> { let config = russh::client::Config { preferred: options.ssh.preferred_algorithms.clone(), ..Default::default() }; let mut client = russh::client::connect(Arc::new(config), (ip, port), Client {}).await?; if !client.authenticate_password(username, password).await? { return Err(Error::AuthenticationError( "ssh authentication failed".to_string(), )); } Ok(client) }