From 6bf503331e69381f6b1496ad710506edcec8e1ad Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Thu, 24 Dec 2020 17:27:57 +0100 Subject: [PATCH] SSH key storage in scp/sftp file transfers --- src/filetransfer/scp_transfer.rs | 80 ++++++++++-------- src/filetransfer/sftp_transfer.rs | 82 +++++++++++-------- .../activities/filetransfer_activity/mod.rs | 38 +++++++-- 3 files changed, 129 insertions(+), 71 deletions(-) diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index b7a1deb..3eae73e 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -30,6 +30,7 @@ extern crate ssh2; // Locals use super::{FileTransfer, FileTransferError, FileTransferErrorType}; use crate::fs::{FsDirectory, FsEntry, FsFile}; +use crate::system::sshkey_storage::SshKeyStorage; use crate::utils::parser::parse_lstime; // Includes @@ -46,22 +47,18 @@ use std::time::SystemTime; pub struct ScpFileTransfer { session: Option, wrkdir: PathBuf, -} - -impl Default for ScpFileTransfer { - fn default() -> Self { - Self::new() - } + key_storage: SshKeyStorage, } impl ScpFileTransfer { /// ### new /// /// Instantiates a new ScpFileTransfer - pub fn new() -> ScpFileTransfer { + pub fn new(key_storage: SshKeyStorage) -> ScpFileTransfer { ScpFileTransfer { session: None, wrkdir: PathBuf::from("~"), + key_storage, } } @@ -345,17 +342,36 @@ impl FileTransfer for ScpFileTransfer { Some(u) => u, None => String::from(""), }; - // Try authenticating with user agent - if session.userauth_agent(username.as_str()).is_err() { - // Try authentication with password then - if let Err(err) = session.userauth_password( - username.as_str(), - password.unwrap_or_else(|| String::from("")).as_str(), - ) { - return Err(FileTransferError::new_ex( - FileTransferErrorType::AuthenticationFailed, - format!("{}", err), - )); + // Check if it is possible to authenticate using a RSA key + match self + .key_storage + .resolve(address.as_str(), username.as_str()) + { + Some(rsa_key) => { + // Authenticate with RSA key + if let Err(err) = session.userauth_pubkey_file( + username.as_str(), + None, + rsa_key.as_path(), + password.as_deref(), + ) { + return Err(FileTransferError::new_ex( + FileTransferErrorType::AuthenticationFailed, + format!("{}", err), + )); + } + } + None => { + // Proceeed with username/password authentication + if let Err(err) = session.userauth_password( + username.as_str(), + password.unwrap_or_else(|| String::from("")).as_str(), + ) { + return Err(FileTransferError::new_ex( + FileTransferErrorType::AuthenticationFailed, + format!("{}", err), + )); + } } } // Get banner @@ -823,14 +839,14 @@ mod tests { #[test] fn test_filetransfer_scp_new() { - let client: ScpFileTransfer = ScpFileTransfer::new(); + let client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client.session.is_none()); assert_eq!(client.is_connected(), false); } #[test] fn test_filetransfer_scp_connect() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert_eq!(client.is_connected(), false); assert!(client .connect( @@ -849,7 +865,7 @@ mod tests { } #[test] fn test_filetransfer_scp_bad_auth() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -862,7 +878,7 @@ mod tests { #[test] fn test_filetransfer_scp_no_credentials() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect(String::from("test.rebex.net"), 22, None, None) .is_err()); @@ -870,7 +886,7 @@ mod tests { #[test] fn test_filetransfer_scp_bad_server() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("mybadserver.veryverybad.awful"), @@ -882,7 +898,7 @@ mod tests { } #[test] fn test_filetransfer_scp_pwd() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -902,7 +918,7 @@ mod tests { #[test] #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] fn test_filetransfer_scp_cwd() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -923,7 +939,7 @@ mod tests { #[test] fn test_filetransfer_scp_cwd_error() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -946,7 +962,7 @@ mod tests { #[test] fn test_filetransfer_scp_ls() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -967,7 +983,7 @@ mod tests { #[test] fn test_filetransfer_scp_stat() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -991,7 +1007,7 @@ mod tests { #[test] fn test_filetransfer_scp_recv() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -1023,7 +1039,7 @@ mod tests { } #[test] fn test_filetransfer_scp_recv_failed_nosuchfile() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -1058,7 +1074,7 @@ mod tests { /* NOTE: the server doesn't allow you to create directories #[test] fn test_filetransfer_scp_mkdir() { - let mut client: ScpFileTransfer = ScpFileTransfer::new(); + let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(client.connect(String::from("test.rebex.net"), 22, Some(String::from("demo")), Some(String::from("password"))).is_ok()); let dir: String = String::from("foo"); // Mkdir @@ -1087,7 +1103,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: Some((6, 4, 4)), // UNIX only }; - let mut scp: ScpFileTransfer = ScpFileTransfer::new(); + let mut scp: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(scp.change_dir(Path::new("/tmp")).is_err()); assert!(scp.disconnect().is_err()); assert!(scp.list_dir(Path::new("/tmp")).is_err()); diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index 421d149..f17f75f 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -29,6 +29,7 @@ extern crate ssh2; // Locals use super::{FileTransfer, FileTransferError, FileTransferErrorType}; use crate::fs::{FsDirectory, FsEntry, FsFile}; +use crate::system::sshkey_storage::SshKeyStorage; // Includes use ssh2::{FileStat, OpenFlags, OpenType, Session, Sftp}; @@ -44,23 +45,19 @@ pub struct SftpFileTransfer { session: Option, sftp: Option, wrkdir: PathBuf, -} - -impl Default for SftpFileTransfer { - fn default() -> Self { - Self::new() - } + key_storage: SshKeyStorage, } impl SftpFileTransfer { /// ### new /// /// Instantiates a new SftpFileTransfer - pub fn new() -> SftpFileTransfer { + pub fn new(key_storage: SshKeyStorage) -> SftpFileTransfer { SftpFileTransfer { session: None, sftp: None, wrkdir: PathBuf::from("~"), + key_storage, } } @@ -238,17 +235,36 @@ impl FileTransfer for SftpFileTransfer { Some(u) => u, None => String::from(""), }; - // Try authenticating with user agent - if session.userauth_agent(username.as_str()).is_err() { - // Try authentication with password then - if let Err(err) = session.userauth_password( - username.as_str(), - password.unwrap_or_else(|| String::from("")).as_str(), - ) { - return Err(FileTransferError::new_ex( - FileTransferErrorType::AuthenticationFailed, - format!("{}", err), - )); + // Check if it is possible to authenticate using a RSA key + match self + .key_storage + .resolve(address.as_str(), username.as_str()) + { + Some(rsa_key) => { + // Authenticate with RSA key + if let Err(err) = session.userauth_pubkey_file( + username.as_str(), + None, + rsa_key.as_path(), + password.as_deref(), + ) { + return Err(FileTransferError::new_ex( + FileTransferErrorType::AuthenticationFailed, + format!("{}", err), + )); + } + } + None => { + // Proceeed with username/password authentication + if let Err(err) = session.userauth_password( + username.as_str(), + password.unwrap_or_else(|| String::from("")).as_str(), + ) { + return Err(FileTransferError::new_ex( + FileTransferErrorType::AuthenticationFailed, + format!("{}", err), + )); + } } } // Set blocking to true @@ -600,7 +616,7 @@ mod tests { #[test] fn test_filetransfer_sftp_new() { - let client: SftpFileTransfer = SftpFileTransfer::new(); + let client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client.session.is_none()); assert!(client.sftp.is_none()); assert_eq!(client.wrkdir, PathBuf::from("~")); @@ -609,7 +625,7 @@ mod tests { #[test] fn test_filetransfer_sftp_connect() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert_eq!(client.is_connected(), false); assert!(client .connect( @@ -631,7 +647,7 @@ mod tests { #[test] fn test_filetransfer_sftp_bad_auth() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -644,7 +660,7 @@ mod tests { #[test] fn test_filetransfer_sftp_no_credentials() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect(String::from("test.rebex.net"), 22, None, None) .is_err()); @@ -652,7 +668,7 @@ mod tests { #[test] fn test_filetransfer_sftp_bad_server() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("mybadserver.veryverybad.awful"), @@ -665,7 +681,7 @@ mod tests { #[test] fn test_filetransfer_sftp_pwd() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -686,7 +702,7 @@ mod tests { #[test] fn test_filetransfer_sftp_cwd() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -713,7 +729,7 @@ mod tests { #[test] fn test_filetransfer_sftp_copy() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -748,7 +764,7 @@ mod tests { #[test] fn test_filetransfer_sftp_cwd_error() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -771,7 +787,7 @@ mod tests { #[test] fn test_filetransfer_sftp_ls() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -794,7 +810,7 @@ mod tests { #[test] fn test_filetransfer_sftp_stat() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -820,7 +836,7 @@ mod tests { #[test] fn test_filetransfer_sftp_recv() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -854,7 +870,7 @@ mod tests { } #[test] fn test_filetransfer_sftp_recv_failed_nosuchfile() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client .connect( String::from("test.rebex.net"), @@ -892,7 +908,7 @@ mod tests { /* NOTE: the server doesn't allow you to create directories #[test] fn test_filetransfer_sftp_mkdir() { - let mut client: SftpFileTransfer = SftpFileTransfer::new(); + let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(client.connect(String::from("test.rebex.net"), 22, Some(String::from("demo")), Some(String::from("password"))).is_ok()); let dir: String = String::from("foo"); // Mkdir @@ -921,7 +937,7 @@ mod tests { group: Some(0), // UNIX only unix_pex: Some((6, 4, 4)), // UNIX only }; - let mut sftp: SftpFileTransfer = SftpFileTransfer::new(); + let mut sftp: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(sftp.change_dir(Path::new("/tmp")).is_err()); assert!(sftp.disconnect().is_err()); assert!(sftp.list_dir(Path::new("/tmp")).is_err()); diff --git a/src/ui/activities/filetransfer_activity/mod.rs b/src/ui/activities/filetransfer_activity/mod.rs index 56ecd29..d138fa2 100644 --- a/src/ui/activities/filetransfer_activity/mod.rs +++ b/src/ui/activities/filetransfer_activity/mod.rs @@ -39,14 +39,14 @@ extern crate unicode_width; // locals use super::{Activity, Context}; -use crate::filetransfer::FileTransferProtocol; - -// File transfer use crate::filetransfer::ftp_transfer::FtpFileTransfer; use crate::filetransfer::scp_transfer::ScpFileTransfer; use crate::filetransfer::sftp_transfer::SftpFileTransfer; -use crate::filetransfer::FileTransfer; +use crate::filetransfer::{FileTransfer, FileTransferProtocol}; use crate::fs::FsEntry; +use crate::system::config_client::ConfigClient; +use crate::system::environment; +use crate::system::sshkey_storage::SshKeyStorage; // Includes use chrono::{DateTime, Local}; @@ -311,9 +311,13 @@ impl FileTransferActivity { quit: false, context: None, client: match protocol { - FileTransferProtocol::Sftp => Box::new(SftpFileTransfer::new()), + FileTransferProtocol::Sftp => { + Box::new(SftpFileTransfer::new(Self::make_ssh_storage())) + } FileTransferProtocol::Ftp(ftps) => Box::new(FtpFileTransfer::new(ftps)), - FileTransferProtocol::Scp => Box::new(ScpFileTransfer::new()), + FileTransferProtocol::Scp => { + Box::new(ScpFileTransfer::new(Self::make_ssh_storage())) + } }, params, local: FileExplorer::new(), @@ -329,6 +333,28 @@ impl FileTransferActivity { transfer: TransferStates::default(), } } + + /// ### make_ssh_storage + /// + /// Make ssh storage using ConfigClient if possible, otherwise provide an empty storage. + /// This activity doesn't care of errors related to config client. + fn make_ssh_storage() -> SshKeyStorage { + match environment::init_config_dir() { + Ok(termscp_dir) => match termscp_dir { + Some(termscp_dir) => { + // Make configuration file path and ssh keys path + let (config_path, ssh_keys_path): (PathBuf, PathBuf) = + environment::get_config_paths(termscp_dir.as_path()); + match ConfigClient::new(config_path.as_path(), ssh_keys_path.as_path()) { + Ok(config_client) => SshKeyStorage::storage_from_config(&config_client), + Err(_) => SshKeyStorage::empty(), + } + } + None => SshKeyStorage::empty(), + }, + Err(_) => SshKeyStorage::empty(), + } + } } /**