Bookmarks client

This commit is contained in:
ChristianVisintin
2020-12-16 12:09:34 +01:00
parent dcc289153f
commit ee55d1fd31

View File

@@ -24,17 +24,21 @@
*/ */
// Deps // Deps
extern crate magic_crypt;
extern crate rand; extern crate rand;
// Local // Local
use crate::bookmarks::serializer::BookmarkSerializer; use crate::bookmarks::serializer::BookmarkSerializer;
use crate::bookmarks::{Bookmark, SerializerError, SerializerErrorKind, UserHosts}; use crate::bookmarks::{Bookmark, SerializerError, SerializerErrorKind, UserHosts};
use crate::filetransfer::FileTransferProtocol; use crate::filetransfer::FileTransferProtocol;
use crate::utils::time_to_str;
// Ext // Ext
use magic_crypt::MagicCryptTrait;
use rand::{distributions::Alphanumeric, Rng}; use rand::{distributions::Alphanumeric, Rng};
use std::fs::{OpenOptions, Permissions}; use std::fs::{OpenOptions, Permissions};
use std::io::Write; use std::io::{Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::SystemTime;
/// ## BookmarksClient /// ## BookmarksClient
/// ///
@@ -42,7 +46,7 @@ use std::path::{Path, PathBuf};
pub struct BookmarksClient { pub struct BookmarksClient {
pub hosts: UserHosts, pub hosts: UserHosts,
bookmarks_file: PathBuf, bookmarks_file: PathBuf,
key_file: PathBuf, key: String,
} }
impl BookmarksClient { impl BookmarksClient {
@@ -54,10 +58,21 @@ impl BookmarksClient {
pub fn new(bookmarks_file: &Path, key_file: &Path) -> Result<BookmarksClient, SerializerError> { pub fn new(bookmarks_file: &Path, key_file: &Path) -> Result<BookmarksClient, SerializerError> {
// Create default hosts // Create default hosts
let default_hosts: UserHosts = Default::default(); let default_hosts: UserHosts = Default::default();
// If key file doesn't exist, create key, otherwise read it
let key: String = match key_file.exists() {
true => match BookmarksClient::load_key(key_file) {
Ok(key) => key,
Err(err) => return Err(err),
},
false => match BookmarksClient::generate_key(key_file) {
Ok(key) => key,
Err(err) => return Err(err),
},
};
let client: BookmarksClient = BookmarksClient { let client: BookmarksClient = BookmarksClient {
hosts: default_hosts, hosts: default_hosts,
bookmarks_file: PathBuf::from(bookmarks_file), bookmarks_file: PathBuf::from(bookmarks_file),
key_file: PathBuf::from(key_file), key,
}; };
// If bookmark file doesn't exist, initialize it // If bookmark file doesn't exist, initialize it
if !bookmarks_file.exists() { if !bookmarks_file.exists() {
@@ -65,12 +80,7 @@ impl BookmarksClient {
return Err(err); return Err(err);
} }
} }
// If key file doesn't exist, create key // Load key
if !key_file.exists() {
if let Err(err) = client.generate_key() {
return Err(err);
}
}
Ok(client) Ok(client)
} }
@@ -82,7 +92,6 @@ impl BookmarksClient {
key: &String, key: &String,
) -> Option<(String, u16, FileTransferProtocol, String, Option<String>)> { ) -> Option<(String, u16, FileTransferProtocol, String, Option<String>)> {
let entry: &Bookmark = self.hosts.bookmarks.get(key)?; let entry: &Bookmark = self.hosts.bookmarks.get(key)?;
// TODO: decrypt password
Some(( Some((
entry.address.clone(), entry.address.clone(),
entry.port, entry.port,
@@ -93,58 +102,91 @@ impl BookmarksClient {
_ => FileTransferProtocol::Sftp, _ => FileTransferProtocol::Sftp,
}, },
entry.username.clone(), entry.username.clone(),
None, match entry.password {
)) // Decrypted password if Some; if decryption fails return None
} Some(pwd) => match self.decrypt_str(pwd.as_str()) {
Ok(decrypted_pwd) => Some(decrypted_pwd),
/// ### get_recent Err(_) => None,
/// },
/// Get recent associated to key None => None,
pub fn get_recent(
&self,
key: &String,
) -> Option<(String, u16, FileTransferProtocol, String, Option<String>)> {
let entry: &Bookmark = self.hosts.recents.get(key)?;
// TODO: decrypt password
Some((
entry.address.clone(),
entry.port,
match entry.protocol.to_ascii_uppercase().as_str() {
"FTP" => FileTransferProtocol::Ftp(false),
"FTPS" => FileTransferProtocol::Ftp(true),
"SCP" => FileTransferProtocol::Scp,
_ => FileTransferProtocol::Sftp,
}, },
entry.username.clone(),
None,
)) ))
} }
/// ### make_bookmark /// ### add_recent
/// ///
/// Make bookmark from credentials /// Add a new recent to bookmarks
pub fn make_bookmark( pub fn add_bookmark(
&self, &mut self,
name: String,
addr: String, addr: String,
port: u16, port: u16,
protocol: FileTransferProtocol, protocol: FileTransferProtocol,
username: String, username: String,
password: Option<String>, password: Option<String>,
) -> Bookmark { ) {
// TODO: crypt password // Make bookmark
Bookmark { let host: Bookmark = self.make_bookmark(addr, port, protocol, username, password);
address: addr, self.hosts.bookmarks.insert(name, host);
port,
username,
protocol: match protocol {
FileTransferProtocol::Ftp(secure) => match secure {
true => String::from("FTPS"),
false => String::from("FTP"),
},
FileTransferProtocol::Scp => String::from("SCP"),
FileTransferProtocol::Sftp => String::from("SFTP"),
},
} }
/// ### get_recent
///
/// Get recent associated to key
pub fn get_recent(&self, key: &String) -> Option<(String, u16, FileTransferProtocol, String)> {
// NOTE: password is not decrypted; recents will never have password
let entry: &Bookmark = self.hosts.recents.get(key)?;
Some((
entry.address.clone(),
entry.port,
match entry.protocol.to_ascii_uppercase().as_str() {
"FTP" => FileTransferProtocol::Ftp(false),
"FTPS" => FileTransferProtocol::Ftp(true),
"SCP" => FileTransferProtocol::Scp,
_ => FileTransferProtocol::Sftp,
},
entry.username.clone(),
))
}
/// ### add_recent
///
/// Add a new recent to bookmarks
pub fn add_recent(
&mut self,
addr: String,
port: u16,
protocol: FileTransferProtocol,
username: String,
) {
// Make bookmark
let host: Bookmark = self.make_bookmark(addr, port, protocol, username, None);
// Check if duplicated
for recent_host in self.hosts.recents.values() {
if *recent_host == host {
// Don't save duplicates
return;
}
}
// If hosts size is bigger than 16; pop last
if self.hosts.recents.len() >= 16 {
let mut keys: Vec<String> = Vec::with_capacity(self.hosts.recents.len());
for key in self.hosts.recents.keys() {
keys.push(key.clone());
}
// Sort keys; NOTE: most recent is the last element
keys.sort();
// Delete keys starting from the last one
for key in keys.iter() {
let _ = self.hosts.recents.remove(key);
// If length is < 16; break
if self.hosts.recents.len() < 16 {
break;
}
}
}
let name: String = time_to_str(SystemTime::now(), "ISO%Y%m%dT%H%M%S");
self.hosts.recents.insert(name, host);
} }
/// ### write_bookmarks /// ### write_bookmarks
@@ -172,7 +214,7 @@ impl BookmarksClient {
/// ### generate_key /// ### generate_key
/// ///
/// Generate a new AES key and write it to key file /// Generate a new AES key and write it to key file
fn generate_key(&self) -> Result<(), SerializerError> { fn generate_key(key_file: &Path) -> Result<String, SerializerError> {
// Generate 256 bytes (2048 bits) key // Generate 256 bytes (2048 bits) key
let key: String = rand::thread_rng() let key: String = rand::thread_rng()
.sample_iter(Alphanumeric) .sample_iter(Alphanumeric)
@@ -183,7 +225,7 @@ impl BookmarksClient {
.create(true) .create(true)
.write(true) .write(true)
.truncate(true) .truncate(true)
.open(self.key_file.as_path()) .open(key_file)
{ {
Ok(mut file) => { Ok(mut file) => {
// Write key to file // Write key to file
@@ -197,7 +239,7 @@ impl BookmarksClient {
let mut permissions: Permissions = file.metadata().unwrap().permissions(); let mut permissions: Permissions = file.metadata().unwrap().permissions();
permissions.set_readonly(true); permissions.set_readonly(true);
let _ = file.set_permissions(permissions); let _ = file.set_permissions(permissions);
Ok(()) Ok(key)
} }
Err(err) => Err(SerializerError::new_ex( Err(err) => Err(SerializerError::new_ex(
SerializerErrorKind::IoError, SerializerErrorKind::IoError,
@@ -205,4 +247,73 @@ impl BookmarksClient {
)), )),
} }
} }
/// ### make_bookmark
///
/// Make bookmark from credentials
fn make_bookmark(
&self,
addr: String,
port: u16,
protocol: FileTransferProtocol,
username: String,
password: Option<String>,
) -> Bookmark {
Bookmark {
address: addr,
port,
username,
protocol: match protocol {
FileTransferProtocol::Ftp(secure) => match secure {
true => String::from("FTPS"),
false => String::from("FTP"),
},
FileTransferProtocol::Scp => String::from("SCP"),
FileTransferProtocol::Sftp => String::from("SFTP"),
},
password: match password {
Some(p) => Some(self.encrypt_str(p.as_str())), // Encrypt password if provided
None => None,
},
}
}
/// ### load_key
///
/// Load key from key_file
fn load_key(key_file: &Path) -> Result<String, SerializerError> {
match OpenOptions::new().read(true).open(key_file) {
Ok(mut file) => {
let mut key: String = String::with_capacity(256);
file.read_to_string(&mut key);
Ok(key)
}
Err(err) => Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
err.to_string(),
)),
}
}
/// ### encrypt_str
///
/// Encrypt provided string using AES-128. Encrypted buffer is then converted to BASE64
fn encrypt_str(&self, txt: &str) -> String {
let crypter = new_magic_crypt!(self.key.clone(), 128);
crypter.encrypt_str_to_base64(txt.to_string())
}
/// ### decrypt_str
///
/// Decrypt provided string using AES-128
fn decrypt_str(&self, secret: &str) -> Result<String, SerializerError> {
let crypter = new_magic_crypt!(self.key.clone(), 128);
match crypter.decrypt_base64_to_string(secret.to_string()) {
Ok(txt) => Ok(txt),
Err(err) => Err(SerializerError::new_ex(
SerializerErrorKind::SyntaxError,
err.to_string(),
)),
}
}
} }