From 0e4caaecfd3329408228a89eb53646f5aceb2343 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 16 Jan 2021 16:07:11 +0100 Subject: [PATCH] Keyring storage --- Cargo.lock | 205 +++++++++++++++++++++++++++++- Cargo.toml | 3 + src/system/keys/filestorage.rs | 11 +- src/system/keys/keyringstorage.rs | 135 ++++++++++++++++++++ src/system/keys/mod.rs | 40 ++++-- 5 files changed, 371 insertions(+), 23 deletions(-) create mode 100644 src/system/keys/keyringstorage.rs diff --git a/Cargo.lock b/Cargo.lock index c1169a0..e3b522f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,16 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher", +] + [[package]] name = "aes-soft" version = "0.6.4" @@ -10,6 +21,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher", + "opaque-debug", +] + [[package]] name = "aho-corasick" version = "0.7.15" @@ -205,16 +226,32 @@ dependencies = [ "memchr", ] +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + [[package]] name = "core-foundation" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.2", "libc", ] +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + [[package]] name = "core-foundation-sys" version = "0.8.2" @@ -272,6 +309,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-mac" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "dbus" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a0c10ea61042b7555729ab0608727bbbb06ce709c11e6047cfa4e10f6d052d" +dependencies = [ + "libc", +] + [[package]] name = "debug-helper" version = "0.3.10" @@ -423,6 +479,26 @@ dependencies = [ "wasi 0.10.0+wasi-snapshot-preview1", ] +[[package]] +name = "hkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f" +dependencies = [ + "digest 0.9.0", + "hmac", +] + +[[package]] +name = "hmac" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + [[package]] name = "hostname" version = "0.3.1" @@ -452,6 +528,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keyring" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bcd64f48199f69993c705fd2f76882e53969db93bc6345021bc8bb6462a9ffa" +dependencies = [ + "byteorder", + "secret-service", + "security-framework 0.4.4", + "winapi", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -594,8 +682,8 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", - "security-framework-sys", + "security-framework 2.0.0", + "security-framework-sys 2.0.0", "tempfile", ] @@ -608,6 +696,40 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -618,6 +740,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -908,6 +1053,35 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "secret-service" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d752040301c251d653aa740dec847e95767ce312cfc469bee85eb13cbf81d8a" +dependencies = [ + "aes", + "block-modes", + "dbus", + "hkdf", + "lazy_static", + "num", + "rand 0.7.3", + "sha2", +] + +[[package]] +name = "security-framework" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "core-foundation-sys 0.7.0", + "libc", + "security-framework-sys 0.4.3", +] + [[package]] name = "security-framework" version = "2.0.0" @@ -915,10 +1089,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69" dependencies = [ "bitflags", - "core-foundation", - "core-foundation-sys", + "core-foundation 0.9.1", + "core-foundation-sys 0.8.2", + "libc", + "security-framework-sys 2.0.0", +] + +[[package]] +name = "security-framework-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" +dependencies = [ + "core-foundation-sys 0.7.0", "libc", - "security-framework-sys", ] [[package]] @@ -927,7 +1111,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.2", "libc", ] @@ -1019,6 +1203,12 @@ dependencies = [ "parking_lot 0.10.2", ] +[[package]] +name = "subtle" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" + [[package]] name = "syn" version = "1.0.58" @@ -1058,6 +1248,7 @@ dependencies = [ "ftp4", "getopts", "hostname", + "keyring", "lazy_static", "magic-crypt", "rand 0.8.1", diff --git a/Cargo.toml b/Cargo.toml index 9e1578a..3182414 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,9 @@ whoami = "1.0.1" [target.'cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))'.dependencies] users = "0.11.0" +[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies] +keyring = "0.10.1" + [[bin]] name = "termscp" path = "src/main.rs" diff --git a/src/system/keys/filestorage.rs b/src/system/keys/filestorage.rs index da14682..cce2ff1 100644 --- a/src/system/keys/filestorage.rs +++ b/src/system/keys/filestorage.rs @@ -23,8 +23,9 @@ * */ +// Local use super::{KeyStorage, KeyStorageError}; - +// Ext use std::fs::{OpenOptions, Permissions}; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; @@ -75,10 +76,10 @@ impl KeyStorage for FileStorage { let mut key: String = String::new(); match file.read_to_string(&mut key) { Ok(_) => Ok(key), - Err(_) => Err(KeyStorageError::Io), + Err(_) => Err(KeyStorageError::ProviderError), } } - Err(_) => Err(KeyStorageError::Io), + Err(_) => Err(KeyStorageError::ProviderError), } } @@ -97,7 +98,7 @@ impl KeyStorage for FileStorage { Ok(mut file) => { // Write key to file if let Err(_) = file.write_all(key.as_bytes()) { - return Err(KeyStorageError::Io); + return Err(KeyStorageError::ProviderError); } // Set file to readonly let mut permissions: Permissions = file.metadata().unwrap().permissions(); @@ -105,7 +106,7 @@ impl KeyStorage for FileStorage { let _ = file.set_permissions(permissions); Ok(()) } - Err(_) => Err(KeyStorageError::Io), + Err(_) => Err(KeyStorageError::ProviderError), } } diff --git a/src/system/keys/keyringstorage.rs b/src/system/keys/keyringstorage.rs new file mode 100644 index 0000000..e46bc2e --- /dev/null +++ b/src/system/keys/keyringstorage.rs @@ -0,0 +1,135 @@ +//! ## KeyringStorage +//! +//! `keyringstorage` provides an implementation of the `KeyStorage` trait using the OS keyring + +/* +* +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "TermSCP" +* +* TermSCP is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* TermSCP is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with TermSCP. If not, see . +* +*/ + +// Deps +extern crate keyring; +// Local +use super::{KeyStorage, KeyStorageError}; +// Ext +use keyring::{Keyring, KeyringError}; + +/// ## KeyringStorage +/// +/// provides a `KeyStorage` implementation using the keyring crate +pub struct KeyringStorage { + username: String, +} + +impl KeyringStorage { + /// ### new + /// + /// Instantiates a new KeyringStorage + pub fn new(username: &str) -> Self { + KeyringStorage { + username: username.to_string(), + } + } +} + +impl KeyStorage for KeyringStorage { + /// ### get_key + /// + /// Retrieve key from the key storage. + /// The key might be acccess through an identifier, which identifies + /// the key in the storage + fn get_key(&self, storage_id: &str) -> Result { + let storage: Keyring = Keyring::new(storage_id, self.username.as_str()); + match storage.get_password() { + Ok(s) => Ok(s), + Err(e) => match e { + KeyringError::NoPasswordFound => Err(KeyStorageError::NoSuchKey), + #[cfg(target_os = "windows")] + KeyringError::WindowsVaultError => Err(KeyStorageError::NoSuchKey), + #[cfg(target_os = "macos")] + KeyringError::MacOsKeychainError(_) => Err(KeyStorageError::NoSuchKey), + _ => panic!("{}", e), + }, + } + } + + /// ### set_key + /// + /// Set the key into the key storage + fn set_key(&self, storage_id: &str, key: &str) -> Result<(), KeyStorageError> { + let storage: Keyring = Keyring::new(storage_id, self.username.as_str()); + match storage.set_password(key) { + Ok(_) => Ok(()), + Err(_) => Err(KeyStorageError::ProviderError), + } + } + + /// is_supported + /// + /// Returns whether the key storage is supported on the host system + fn is_supported(&self) -> bool { + let dummy: String = String::from("dummy-service"); + let storage: Keyring = Keyring::new(dummy.as_str(), self.username.as_str()); + // Check what kind of error is returned + match storage.get_password() { + Ok(_) => true, + Err(err) => match err { + KeyringError::NoBackendFound => false, + //#[cfg(target_os = "macos")] + //KeyringError::MacOsKeychainError(_) => false, + //#[cfg(target_os = "windows")] + //KeyringError::WindowsVaultError => false, + _ => true, + }, + } + } +} + +#[cfg(test)] +mod tests { + + extern crate whoami; + use super::*; + + use whoami::username; + + #[test] + fn test_system_keys_keyringstorage() { + let username: String = username(); + let storage: KeyringStorage = KeyringStorage::new(username.as_str()); + let app_name: &str = "termscp"; + let secret: &str = "Th15-15/My-Супер-Секрет"; + let kring: Keyring = Keyring::new(app_name, username.as_str()); + let _ = kring.delete_password(); + drop(kring); + // Secret should not exist + assert_eq!( + storage.get_key(app_name).err().unwrap(), + KeyStorageError::NoSuchKey + ); + // Write secret + assert!(storage.set_key(app_name, secret).is_ok()); + // Get secret + assert_eq!(storage.get_key(app_name).ok().unwrap().as_str(), secret); + + // Delete the key manually... + let kring: Keyring = Keyring::new(app_name, username.as_str()); + assert!(kring.delete_password().is_ok()); + } +} diff --git a/src/system/keys/mod.rs b/src/system/keys/mod.rs index 5ef9802..02c66b7 100644 --- a/src/system/keys/mod.rs +++ b/src/system/keys/mod.rs @@ -25,22 +25,24 @@ // Storages pub mod filestorage; +#[cfg(any(target_os = "windows", target_os = "macos"))] +pub mod keyringstorage; /// ## KeyStorageError -/// +/// /// defines the error type for the `KeyStorage` #[derive(PartialEq, std::fmt::Debug)] pub enum KeyStorageError { - BadKey, - Io, + //BadKey, + ProviderError, NoSuchKey, } impl std::fmt::Display for KeyStorageError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let err: String = String::from(match &self { - KeyStorageError::BadKey => "Bad key syntax", - KeyStorageError::Io => "Input/Output error", + //KeyStorageError::BadKey => "Bad key syntax", + KeyStorageError::ProviderError => "Provider service error", KeyStorageError::NoSuchKey => "No such key", }); write!(f, "{}", err) @@ -48,25 +50,41 @@ impl std::fmt::Display for KeyStorageError { } /// ## KeyStorage -/// +/// /// this traits provides the methods to communicate and interact with the key storage. pub trait KeyStorage { - /// ### get_key - /// + /// /// Retrieve key from the key storage. /// The key might be acccess through an identifier, which identifies /// the key in the storage fn get_key(&self, storage_id: &str) -> Result; /// ### set_key - /// + /// /// Set the key into the key storage fn set_key(&self, storage_id: &str, key: &str) -> Result<(), KeyStorageError>; /// is_supported - /// + /// /// Returns whether the key storage is supported on the host system fn is_supported(&self) -> bool; - +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_system_keys_mod_errors() { + assert_eq!( + format!("{}", KeyStorageError::ProviderError), + String::from("Provider service error") + ); + assert_eq!( + format!("{}", KeyStorageError::NoSuchKey), + String::from("No such key") + ); + } }