Keyring storage in bookmarks client (if possible)

This commit is contained in:
ChristianVisintin
2021-01-16 16:57:00 +01:00
parent 0192b86422
commit 08d8a3621c
4 changed files with 74 additions and 81 deletions

View File

@@ -23,6 +23,12 @@
* *
*/ */
// Deps
extern crate whoami;
// Crate
#[cfg(any(target_os = "windows", target_os = "macos"))]
use super::keys::keyringstorage::KeyringStorage;
use super::keys::{filestorage::FileStorage, KeyStorage, KeyStorageError};
// 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};
@@ -31,8 +37,7 @@ use crate::utils::crypto;
use crate::utils::fmt::fmt_time; use crate::utils::fmt::fmt_time;
use crate::utils::random::random_alphanumeric_with_len; use crate::utils::random::random_alphanumeric_with_len;
// Ext // Ext
use std::fs::{OpenOptions, Permissions}; use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use std::string::ToString; use std::string::ToString;
@@ -53,23 +58,60 @@ impl BookmarksClient {
/// ///
/// Instantiates a new BookmarksClient /// Instantiates a new BookmarksClient
/// Bookmarks file path must be provided /// Bookmarks file path must be provided
/// Key file must be provided /// Storage path for file provider must be provided
pub fn new( pub fn new(
bookmarks_file: &Path, bookmarks_file: &Path,
key_file: &Path, storage_path: &Path,
recents_size: usize, recents_size: usize,
) -> Result<BookmarksClient, SerializerError> { ) -> 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 // Make a key storage (windows / macos)
let key: String = match key_file.exists() { #[cfg(any(target_os = "windows", target_os = "macos"))]
true => match BookmarksClient::load_key(key_file) { let (key_storage, service_id): (Box<dyn KeyStorage>, &str) = {
Ok(key) => key, let username: String = whoami::username();
Err(err) => return Err(err), let storage: KeyringStorage = KeyringStorage::new(username.as_str());
}, // Check if keyring storage is supported
false => match BookmarksClient::generate_key(key_file) { #[cfg(not(test))]
Ok(key) => key, let app_name: &str = "termscp";
Err(err) => return Err(err), #[cfg(test)] // NOTE: when running test, add -test
let app_name: &str = "termscp-test";
match storage.is_supported() {
true => (Box::new(storage), app_name),
false => (Box::new(FileStorage::new(storage_path)), "bookmarks"),
}
};
// Make a key storage (linux / unix)
#[cfg(any(target_os = "linux", target_os = "unix"))]
let (key_storage, service_id): (Box<dyn KeyStorage>, &str) = {
#[cfg(not(test))]
let app_name: &str = "bookmarks";
#[cfg(test)] // NOTE: when running test, add -test
let app_name: &str = "bookmarks-test";
(Box::new(FileStorage::new(storage_path)), app_name)
};
// Load key
let key: String = match key_storage.get_key(service_id) {
Ok(k) => k,
Err(e) => match e {
KeyStorageError::NoSuchKey => {
// If no such key, generate key and set it into the storage
let key: String = Self::generate_key();
if let Err(e) = key_storage.set_key(service_id, key.as_str()) {
return Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
format!("Could not write key to storage: {}", e),
));
}
// Return key
key
}
_ => {
return Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
format!("Could not get key from storage: {}", e),
))
}
}, },
}; };
let mut client: BookmarksClient = BookmarksClient { let mut client: BookmarksClient = BookmarksClient {
@@ -276,36 +318,10 @@ impl BookmarksClient {
/// ### generate_key /// ### generate_key
/// ///
/// Generate a new AES key and write it to key file /// Generate a new AES key
fn generate_key(key_file: &Path) -> Result<String, SerializerError> { fn generate_key() -> String {
// Generate 256 bytes (2048 bits) key // Generate 256 bytes (2048 bits) key
let key: String = random_alphanumeric_with_len(256); random_alphanumeric_with_len(256)
// Write file
match OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(key_file)
{
Ok(mut file) => {
// Write key to file
if let Err(err) = file.write_all(key.as_bytes()) {
return Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
err.to_string(),
));
}
// Set file to readonly
let mut permissions: Permissions = file.metadata().unwrap().permissions();
permissions.set_readonly(true);
let _ = file.set_permissions(permissions);
Ok(key)
}
Err(err) => Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
err.to_string(),
)),
}
} }
/// ### make_bookmark /// ### make_bookmark
@@ -331,28 +347,6 @@ impl BookmarksClient {
} }
} }
/// ### 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);
match file.read_to_string(&mut key) {
Ok(_) => Ok(key),
Err(err) => Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
err.to_string(),
)),
}
}
Err(err) => Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
err.to_string(),
)),
}
}
/// ### encrypt_str /// ### encrypt_str
/// ///
/// Encrypt provided string using AES-128. Encrypted buffer is then converted to BASE64 /// Encrypt provided string using AES-128. Encrypted buffer is then converted to BASE64
@@ -397,6 +391,7 @@ mod tests {
} }
#[test] #[test]
#[cfg(any(target_os = "unix", target_os = "linux"))]
fn test_system_bookmarks_new_err() { fn test_system_bookmarks_new_err() {
assert!(BookmarksClient::new( assert!(BookmarksClient::new(
Path::new("/tmp/oifoif/omar"), Path::new("/tmp/oifoif/omar"),
@@ -647,9 +642,8 @@ mod tests {
/// ///
/// Get paths for configuration and key for bookmarks /// Get paths for configuration and key for bookmarks
fn get_paths(dir: &Path) -> (PathBuf, PathBuf) { fn get_paths(dir: &Path) -> (PathBuf, PathBuf) {
let mut k: PathBuf = PathBuf::from(dir); let k: PathBuf = PathBuf::from(dir);
let mut c: PathBuf = k.clone(); let mut c: PathBuf = k.clone();
k.push("bookmarks.key");
c.push("bookmarks.toml"); c.push("bookmarks.toml");
(c, k) (c, k)
} }

View File

@@ -59,14 +59,12 @@ pub fn init_config_dir() -> Result<Option<PathBuf>, String> {
/// ### get_bookmarks_paths /// ### get_bookmarks_paths
/// ///
/// Get paths for bookmarks client /// Get paths for bookmarks client
/// Returns: path of bookmarks.toml and path of key /// Returns: path of bookmarks.toml
pub fn get_bookmarks_paths(config_dir: &Path) -> (PathBuf, PathBuf) { pub fn get_bookmarks_paths(config_dir: &Path) -> PathBuf {
// Prepare paths // Prepare paths
let mut bookmarks_file: PathBuf = PathBuf::from(config_dir); let mut bookmarks_file: PathBuf = PathBuf::from(config_dir);
bookmarks_file.push("bookmarks.toml"); bookmarks_file.push("bookmarks.toml");
let mut key_file: PathBuf = PathBuf::from(config_dir); bookmarks_file
key_file.push(".bookmarks.key"); // key file is hidden
(bookmarks_file, key_file)
} }
/// ### get_config_paths /// ### get_config_paths
@@ -123,10 +121,7 @@ mod tests {
fn test_system_environment_get_bookmarks_paths() { fn test_system_environment_get_bookmarks_paths() {
assert_eq!( assert_eq!(
get_bookmarks_paths(&Path::new("/home/omar/.config/termscp/")), get_bookmarks_paths(&Path::new("/home/omar/.config/termscp/")),
(
PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"), PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"),
PathBuf::from("/home/omar/.config/termscp/.bookmarks.key")
)
); );
} }

View File

@@ -114,7 +114,7 @@ mod tests {
let username: String = username(); let username: String = username();
let storage: KeyringStorage = KeyringStorage::new(username.as_str()); let storage: KeyringStorage = KeyringStorage::new(username.as_str());
assert!(storage.is_supported()); assert!(storage.is_supported());
let app_name: &str = "termscp"; let app_name: &str = "termscp-test2";
let secret: &str = "Th15-15/My-Супер-Секрет"; let secret: &str = "Th15-15/My-Супер-Секрет";
let kring: Keyring = Keyring::new(app_name, username.as_str()); let kring: Keyring = Keyring::new(app_name, username.as_str());
let _ = kring.delete_password(); let _ = kring.delete_password();

View File

@@ -224,11 +224,15 @@ impl AuthActivity {
match environment::init_config_dir() { match environment::init_config_dir() {
Ok(path) => { Ok(path) => {
// If some configure client, otherwise do nothing; don't bother users telling them that bookmarks are not supported on their system. // If some configure client, otherwise do nothing; don't bother users telling them that bookmarks are not supported on their system.
if let Some(path) = path { if let Some(config_dir_path) = path {
let (bookmarks_file, key_file): (PathBuf, PathBuf) = let bookmarks_file: PathBuf =
environment::get_bookmarks_paths(path.as_path()); environment::get_bookmarks_paths(config_dir_path.as_path());
// Initialize client // Initialize client
match BookmarksClient::new(bookmarks_file.as_path(), key_file.as_path(), 16) { match BookmarksClient::new(
bookmarks_file.as_path(),
config_dir_path.as_path(),
16,
) {
Ok(cli) => self.bookmarks_client = Some(cli), Ok(cli) => self.bookmarks_client = Some(cli),
Err(err) => { Err(err) => {
self.popup = Some(Popup::Alert( self.popup = Some(Popup::Alert(
@@ -236,7 +240,7 @@ impl AuthActivity {
format!( format!(
"Could not initialize bookmarks (at \"{}\", \"{}\"): {}", "Could not initialize bookmarks (at \"{}\", \"{}\"): {}",
bookmarks_file.display(), bookmarks_file.display(),
key_file.display(), config_dir_path.display(),
err err
), ),
)) ))