SSH key storage in scp/sftp file transfers

This commit is contained in:
ChristianVisintin
2020-12-24 17:27:57 +01:00
parent 920d3b4af4
commit 6bf503331e
3 changed files with 129 additions and 71 deletions

View File

@@ -30,6 +30,7 @@ extern crate ssh2;
// Locals // Locals
use super::{FileTransfer, FileTransferError, FileTransferErrorType}; use super::{FileTransfer, FileTransferError, FileTransferErrorType};
use crate::fs::{FsDirectory, FsEntry, FsFile}; use crate::fs::{FsDirectory, FsEntry, FsFile};
use crate::system::sshkey_storage::SshKeyStorage;
use crate::utils::parser::parse_lstime; use crate::utils::parser::parse_lstime;
// Includes // Includes
@@ -46,22 +47,18 @@ use std::time::SystemTime;
pub struct ScpFileTransfer { pub struct ScpFileTransfer {
session: Option<Session>, session: Option<Session>,
wrkdir: PathBuf, wrkdir: PathBuf,
} key_storage: SshKeyStorage,
impl Default for ScpFileTransfer {
fn default() -> Self {
Self::new()
}
} }
impl ScpFileTransfer { impl ScpFileTransfer {
/// ### new /// ### new
/// ///
/// Instantiates a new ScpFileTransfer /// Instantiates a new ScpFileTransfer
pub fn new() -> ScpFileTransfer { pub fn new(key_storage: SshKeyStorage) -> ScpFileTransfer {
ScpFileTransfer { ScpFileTransfer {
session: None, session: None,
wrkdir: PathBuf::from("~"), wrkdir: PathBuf::from("~"),
key_storage,
} }
} }
@@ -345,9 +342,27 @@ impl FileTransfer for ScpFileTransfer {
Some(u) => u, Some(u) => u,
None => String::from(""), None => String::from(""),
}; };
// Try authenticating with user agent // Check if it is possible to authenticate using a RSA key
if session.userauth_agent(username.as_str()).is_err() { match self
// Try authentication with password then .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( if let Err(err) = session.userauth_password(
username.as_str(), username.as_str(),
password.unwrap_or_else(|| String::from("")).as_str(), password.unwrap_or_else(|| String::from("")).as_str(),
@@ -358,6 +373,7 @@ impl FileTransfer for ScpFileTransfer {
)); ));
} }
} }
}
// Get banner // Get banner
let banner: Option<String> = match session.banner() { let banner: Option<String> = match session.banner() {
Some(s) => Some(String::from(s)), Some(s) => Some(String::from(s)),
@@ -823,14 +839,14 @@ mod tests {
#[test] #[test]
fn test_filetransfer_scp_new() { fn test_filetransfer_scp_new() {
let client: ScpFileTransfer = ScpFileTransfer::new(); let client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
assert!(client.session.is_none()); assert!(client.session.is_none());
assert_eq!(client.is_connected(), false); assert_eq!(client.is_connected(), false);
} }
#[test] #[test]
fn test_filetransfer_scp_connect() { 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_eq!(client.is_connected(), false);
assert!(client assert!(client
.connect( .connect(
@@ -849,7 +865,7 @@ mod tests {
} }
#[test] #[test]
fn test_filetransfer_scp_bad_auth() { fn test_filetransfer_scp_bad_auth() {
let mut client: ScpFileTransfer = ScpFileTransfer::new(); let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -862,7 +878,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_scp_no_credentials() { fn test_filetransfer_scp_no_credentials() {
let mut client: ScpFileTransfer = ScpFileTransfer::new(); let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect(String::from("test.rebex.net"), 22, None, None) .connect(String::from("test.rebex.net"), 22, None, None)
.is_err()); .is_err());
@@ -870,7 +886,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_scp_bad_server() { fn test_filetransfer_scp_bad_server() {
let mut client: ScpFileTransfer = ScpFileTransfer::new(); let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("mybadserver.veryverybad.awful"), String::from("mybadserver.veryverybad.awful"),
@@ -882,7 +898,7 @@ mod tests {
} }
#[test] #[test]
fn test_filetransfer_scp_pwd() { fn test_filetransfer_scp_pwd() {
let mut client: ScpFileTransfer = ScpFileTransfer::new(); let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -902,7 +918,7 @@ mod tests {
#[test] #[test]
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
fn test_filetransfer_scp_cwd() { fn test_filetransfer_scp_cwd() {
let mut client: ScpFileTransfer = ScpFileTransfer::new(); let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -923,7 +939,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_scp_cwd_error() { fn test_filetransfer_scp_cwd_error() {
let mut client: ScpFileTransfer = ScpFileTransfer::new(); let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -946,7 +962,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_scp_ls() { fn test_filetransfer_scp_ls() {
let mut client: ScpFileTransfer = ScpFileTransfer::new(); let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -967,7 +983,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_scp_stat() { fn test_filetransfer_scp_stat() {
let mut client: ScpFileTransfer = ScpFileTransfer::new(); let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -991,7 +1007,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_scp_recv() { fn test_filetransfer_scp_recv() {
let mut client: ScpFileTransfer = ScpFileTransfer::new(); let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -1023,7 +1039,7 @@ mod tests {
} }
#[test] #[test]
fn test_filetransfer_scp_recv_failed_nosuchfile() { fn test_filetransfer_scp_recv_failed_nosuchfile() {
let mut client: ScpFileTransfer = ScpFileTransfer::new(); let mut client: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -1058,7 +1074,7 @@ mod tests {
/* NOTE: the server doesn't allow you to create directories /* NOTE: the server doesn't allow you to create directories
#[test] #[test]
fn test_filetransfer_scp_mkdir() { 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()); 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"); let dir: String = String::from("foo");
// Mkdir // Mkdir
@@ -1087,7 +1103,7 @@ mod tests {
group: Some(0), // UNIX only group: Some(0), // UNIX only
unix_pex: Some((6, 4, 4)), // 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.change_dir(Path::new("/tmp")).is_err());
assert!(scp.disconnect().is_err()); assert!(scp.disconnect().is_err());
assert!(scp.list_dir(Path::new("/tmp")).is_err()); assert!(scp.list_dir(Path::new("/tmp")).is_err());

View File

@@ -29,6 +29,7 @@ extern crate ssh2;
// Locals // Locals
use super::{FileTransfer, FileTransferError, FileTransferErrorType}; use super::{FileTransfer, FileTransferError, FileTransferErrorType};
use crate::fs::{FsDirectory, FsEntry, FsFile}; use crate::fs::{FsDirectory, FsEntry, FsFile};
use crate::system::sshkey_storage::SshKeyStorage;
// Includes // Includes
use ssh2::{FileStat, OpenFlags, OpenType, Session, Sftp}; use ssh2::{FileStat, OpenFlags, OpenType, Session, Sftp};
@@ -44,23 +45,19 @@ pub struct SftpFileTransfer {
session: Option<Session>, session: Option<Session>,
sftp: Option<Sftp>, sftp: Option<Sftp>,
wrkdir: PathBuf, wrkdir: PathBuf,
} key_storage: SshKeyStorage,
impl Default for SftpFileTransfer {
fn default() -> Self {
Self::new()
}
} }
impl SftpFileTransfer { impl SftpFileTransfer {
/// ### new /// ### new
/// ///
/// Instantiates a new SftpFileTransfer /// Instantiates a new SftpFileTransfer
pub fn new() -> SftpFileTransfer { pub fn new(key_storage: SshKeyStorage) -> SftpFileTransfer {
SftpFileTransfer { SftpFileTransfer {
session: None, session: None,
sftp: None, sftp: None,
wrkdir: PathBuf::from("~"), wrkdir: PathBuf::from("~"),
key_storage,
} }
} }
@@ -238,9 +235,27 @@ impl FileTransfer for SftpFileTransfer {
Some(u) => u, Some(u) => u,
None => String::from(""), None => String::from(""),
}; };
// Try authenticating with user agent // Check if it is possible to authenticate using a RSA key
if session.userauth_agent(username.as_str()).is_err() { match self
// Try authentication with password then .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( if let Err(err) = session.userauth_password(
username.as_str(), username.as_str(),
password.unwrap_or_else(|| String::from("")).as_str(), password.unwrap_or_else(|| String::from("")).as_str(),
@@ -251,6 +266,7 @@ impl FileTransfer for SftpFileTransfer {
)); ));
} }
} }
}
// Set blocking to true // Set blocking to true
session.set_blocking(true); session.set_blocking(true);
// Get Sftp client // Get Sftp client
@@ -600,7 +616,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_new() { fn test_filetransfer_sftp_new() {
let client: SftpFileTransfer = SftpFileTransfer::new(); let client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client.session.is_none()); assert!(client.session.is_none());
assert!(client.sftp.is_none()); assert!(client.sftp.is_none());
assert_eq!(client.wrkdir, PathBuf::from("~")); assert_eq!(client.wrkdir, PathBuf::from("~"));
@@ -609,7 +625,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_connect() { 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_eq!(client.is_connected(), false);
assert!(client assert!(client
.connect( .connect(
@@ -631,7 +647,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_bad_auth() { fn test_filetransfer_sftp_bad_auth() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -644,7 +660,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_no_credentials() { fn test_filetransfer_sftp_no_credentials() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect(String::from("test.rebex.net"), 22, None, None) .connect(String::from("test.rebex.net"), 22, None, None)
.is_err()); .is_err());
@@ -652,7 +668,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_bad_server() { fn test_filetransfer_sftp_bad_server() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("mybadserver.veryverybad.awful"), String::from("mybadserver.veryverybad.awful"),
@@ -665,7 +681,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_pwd() { fn test_filetransfer_sftp_pwd() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -686,7 +702,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_cwd() { fn test_filetransfer_sftp_cwd() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -713,7 +729,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_copy() { fn test_filetransfer_sftp_copy() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -748,7 +764,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_cwd_error() { fn test_filetransfer_sftp_cwd_error() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -771,7 +787,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_ls() { fn test_filetransfer_sftp_ls() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -794,7 +810,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_stat() { fn test_filetransfer_sftp_stat() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -820,7 +836,7 @@ mod tests {
#[test] #[test]
fn test_filetransfer_sftp_recv() { fn test_filetransfer_sftp_recv() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -854,7 +870,7 @@ mod tests {
} }
#[test] #[test]
fn test_filetransfer_sftp_recv_failed_nosuchfile() { fn test_filetransfer_sftp_recv_failed_nosuchfile() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty());
assert!(client assert!(client
.connect( .connect(
String::from("test.rebex.net"), String::from("test.rebex.net"),
@@ -892,7 +908,7 @@ mod tests {
/* NOTE: the server doesn't allow you to create directories /* NOTE: the server doesn't allow you to create directories
#[test] #[test]
fn test_filetransfer_sftp_mkdir() { 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()); 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"); let dir: String = String::from("foo");
// Mkdir // Mkdir
@@ -921,7 +937,7 @@ mod tests {
group: Some(0), // UNIX only group: Some(0), // UNIX only
unix_pex: Some((6, 4, 4)), // 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.change_dir(Path::new("/tmp")).is_err());
assert!(sftp.disconnect().is_err()); assert!(sftp.disconnect().is_err());
assert!(sftp.list_dir(Path::new("/tmp")).is_err()); assert!(sftp.list_dir(Path::new("/tmp")).is_err());

View File

@@ -39,14 +39,14 @@ extern crate unicode_width;
// locals // locals
use super::{Activity, Context}; use super::{Activity, Context};
use crate::filetransfer::FileTransferProtocol;
// File transfer
use crate::filetransfer::ftp_transfer::FtpFileTransfer; use crate::filetransfer::ftp_transfer::FtpFileTransfer;
use crate::filetransfer::scp_transfer::ScpFileTransfer; use crate::filetransfer::scp_transfer::ScpFileTransfer;
use crate::filetransfer::sftp_transfer::SftpFileTransfer; use crate::filetransfer::sftp_transfer::SftpFileTransfer;
use crate::filetransfer::FileTransfer; use crate::filetransfer::{FileTransfer, FileTransferProtocol};
use crate::fs::FsEntry; use crate::fs::FsEntry;
use crate::system::config_client::ConfigClient;
use crate::system::environment;
use crate::system::sshkey_storage::SshKeyStorage;
// Includes // Includes
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
@@ -311,9 +311,13 @@ impl FileTransferActivity {
quit: false, quit: false,
context: None, context: None,
client: match protocol { 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::Ftp(ftps) => Box::new(FtpFileTransfer::new(ftps)),
FileTransferProtocol::Scp => Box::new(ScpFileTransfer::new()), FileTransferProtocol::Scp => {
Box::new(ScpFileTransfer::new(Self::make_ssh_storage()))
}
}, },
params, params,
local: FileExplorer::new(), local: FileExplorer::new(),
@@ -329,6 +333,28 @@ impl FileTransferActivity {
transfer: TransferStates::default(), 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(),
}
}
} }
/** /**