diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 49fff47..396d62e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -22,7 +22,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --all-features --lib --no-fail-fast + args: --lib --no-default-features --features github-actions --features with-containers --no-fail-fast env: CARGO_INCREMENTAL: "0" RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests" diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index f23ba3e..a1855cb 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -18,5 +18,5 @@ jobs: chmod +x /tmp/rustup.sh && \ /tmp/rustup.sh -y . $HOME/.cargo/env - cargo build - cargo test --verbose --lib --features github-actions -- --test-threads 1 + cargo build --no-default-features + cargo test --no-default-features --verbose --lib --features github-actions -- --test-threads 1 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9ac9f46..69f4b06 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -22,7 +22,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --all-features --lib --no-fail-fast + args: --lib --no-default-features --features github-actions --features with-containers --no-fail-fast - name: Format run: cargo fmt --all -- --check - name: Clippy diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb2d8e..80eeac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,9 +28,17 @@ Released on FIXME: ?? - **Open any file** in explorer: - Open file with default program for file type with `` - Open file with a specific program with `` +- **Keyring support for Linux** + - From now on keyring will be available for Linux only + - Read the manual to find out if your system supports the keyring and how you can enable it + - libdbus is now a dependency + - added `with-keyring` feature + - **❗ BREAKING CHANGE ❗**: if you start using keyring on Linux, all the saved password will be lost - **In-app release notes** - Possibility to see the release note of the new available release whenever a new version is available - Just press `` when a new version is available from the auth activity to read the release notes +- **Installation script**: + - From now on, in case cargo is used to install termscp, all the cargo dependencies will be installed - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Dependencies: diff --git a/Cargo.toml b/Cargo.toml index 989b13e..20db1b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ edit = "0.1.3" ftp4 = { version = "4.0.2", features = [ "secure" ] } getopts = "0.2.21" hostname = "0.3.1" +keyring = { version = "0.10.1", optional = true } lazy_static = "1.4.0" log = "0.4.14" magic-crypt = "3.1.7" @@ -60,17 +61,15 @@ wildmatch = "2.0.0" pretty_assertions = "0.7.2" [features] +default = [ "with-keyring" ] github-actions = [] with-containers = [] +with-keyring = [ "keyring" ] [target."cfg(target_family = \"unix\")"] [target."cfg(target_family = \"unix\")".dependencies] users = "0.11.0" -[target."cfg(any(target_os = \"windows\", target_os = \"macos\"))"] -[target."cfg(any(target_os = \"windows\", target_os = \"macos\"))".dependencies] -keyring = "0.10.1" - [target."cfg(target_os = \"windows\")"] [target."cfg(target_os = \"windows\")".dependencies] path-slash = "0.1.4" diff --git a/README.md b/README.md index eb4d920..bd6cda0 100644 --- a/README.md +++ b/README.md @@ -69,16 +69,23 @@ while if you're a Windows user, you can install termscp with [Chocolatey](https: For more information or other platforms, please visit [veeso.github.io](https://veeso.github.io/termscp/#get-started) to view all installation methods. +### Requirements ❗ + +- **Linux/BSD** users: + - libssh + - libdbus-1 + ### Soft Requirements ✔️ These requirements are not forcely required to run termscp, but to enjoy all of its features -- **Linux** users +- **Linux/BSD** users: - To **open** files via `V` (at least one of these) - *xdg-open* - *gio* - *gnome-open* - *kde-open* + - A keyring manager: read more in the [User manual](docs/man.md#linux-keyring) - **WSL** users - To **open** files via `V` (at least one of these) - [wslu](https://github.com/wslutilities/wslu) diff --git a/dist/build/x86_64_centos7/Dockerfile b/dist/build/x86_64_centos7/Dockerfile index 570f3ec..b7716dd 100644 --- a/dist/build/x86_64_centos7/Dockerfile +++ b/dist/build/x86_64_centos7/Dockerfile @@ -7,6 +7,7 @@ RUN yum -y install \ gcc \ openssl \ pkgconfig \ + libdbus-devel \ openssl-devel # Install rust RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rust.sh && \ diff --git a/dist/build/x86_64_debian8/Dockerfile b/dist/build/x86_64_debian8/Dockerfile index 26fa74c..e3971e2 100644 --- a/dist/build/x86_64_debian8/Dockerfile +++ b/dist/build/x86_64_debian8/Dockerfile @@ -8,6 +8,7 @@ RUN apt update && apt install -y \ pkg-config \ libssl-dev \ libssh2-1-dev \ + libdbus-1-dev \ curl # Install rust diff --git a/dist/build/x86_64_debian9/Dockerfile b/dist/build/x86_64_debian9/Dockerfile index 01e0d9a..e78c72c 100644 --- a/dist/build/x86_64_debian9/Dockerfile +++ b/dist/build/x86_64_debian9/Dockerfile @@ -8,6 +8,7 @@ RUN apt update && apt install -y \ pkg-config \ libssl-dev \ libssh2-1-dev \ + libdbus-1-dev \ curl # Install rust diff --git a/docs/man.md b/docs/man.md index 4e2a7a9..33ee42f 100644 --- a/docs/man.md +++ b/docs/man.md @@ -187,13 +187,35 @@ whenever you want to use the previously saved connection, just press `` to ### Are my passwords Safe 😈 -Well, kinda. -As said before, bookmarks are saved in your configuration directory along with passwords. Passwords are obviously not plain text, they are encrypted with **AES-128**. Does this make them safe? Well, depends on your operating system: +Well, Yep 😉. +As said before, bookmarks are saved in your configuration directory along with passwords. Passwords are obviously not plain text, they are encrypted with **AES-128**. Does this make them safe? Absolutely! (except for BSD and WSL users 😢) -On Windows and MacOS the passwords are stored, if possible (but should be), in respectively the Windows Vault and the Keychain. This is actually super-safe and is directly managed by your operating system. +On **Windows**, **Linux** and **MacOS** the passwords are stored, if possible (but should be), respectively in the *Windows Vault*, in the *system keyring* and into the *Keychain*. This is actually super-safe and is directly managed by your operating system. -On Linux and BSD, on the other hand, the key used to encrypt your passwords is stored on your drive (at $HOME/.config/termscp). It is then, still possible to retrieve the key to decrypt passwords. Luckily, the location of the key guarantees your key can't be read by users different from yours, but yeah, I still wouldn't save the password for a server exposed on the internet 😉. -Actually [keyring-rs](https://github.com/hwchen/keyring-rs), supports Linux, but for different reasons I preferred not to make it available for this configuration. If you want to read more about my decision read [this issue](https://github.com/veeso/termscp/issues/2), while if you think this might have been implemented differently feel free to open an issue with your proposal. +❗ Please, notice that if you're a Linux user, you should really read the [chapter below 👀](#linux-keyring), because the keyring might not be enabled or supported on your system! + +On *BSD* and *WSL*, on the other hand, the key used to encrypt your passwords is stored on your drive (at $HOME/.config/termscp). It is then, still possible to retrieve the key to decrypt passwords. Luckily, the location of the key guarantees your key can't be read by users different from yours, but yeah, I still wouldn't save the password for a server exposed on the internet 😉. + +#### Linux Keyring + +We all love Linux thanks to the freedom it gives to the users. You can basically do anything you want as a Linux user, but this has also some cons, such as the fact that often there is no standard applications across different distributions. And this involves keyring too. +This means that on Linux there might be no keyring installed on your system. Unfortunately the library we use to work with the key storage requires a service which exposes `org.freedesktop.secrets` on D-BUS and the worst fact is that there only two services exposing it. + +- ❗ If you use GNOME as desktop environment (e.g. ubuntu users), you should already be fine, since keyring is already provided by `gnome-keyring` and everything should already be working. +- ❗ For other desktop environment users there is a nice program you can use to get a keyring which is [KeepassXC](https://keepassxc.org/), which I use on my Manjaro installation (with KDE) and works fine. The only problem is that you have to setup it to be used along with termscp (but it's quite simple). To get started with KeepassXC read more [here](#keepassxc-setup-for-termscp). +- ❗ What about you don't want to install any of these services? Well, there's no problem! **termscp will keep working as usual**, but it will save the key in a file, as it usually does for BSD and WSL. + +##### KeepassXC setup for termscp + +Follow these steps in order to setup keepassXC for termscp: + +1. Install KeepassXC +2. Go to "tools" > "settings" in toolbar +3. Select "Secret service integration" and toggle "Enable KeepassXC freedesktop.org secret service integration" +4. Create a database, if you don't have one yet: from toolbar "Database" > "New database" +5. From toolbar: "Database" > "Database settings" +6. Select "Secret service integration" and toggle "Expose entries under this group" +7. Select the group in the list where you want the termscp secret to be kept. Remember that this group might be used by any other application to store secrets via DBUS. --- @@ -217,7 +239,8 @@ These parameters can be changed: - **Show Hidden Files**: select whether hidden files shall be displayed by default. You will be able to decide whether to show or not hidden files at runtime pressing `A` anyway. - **Check for updates**: if set to `yes`, termscp will fetch the Github API to check if there is a new version of termscp available. - **Group Dirs**: select whether directories should be groupped or not in file explorers. If `Display first` is selected, directories will be sorted using the configured method but displayed before files, viceversa if `Display last` is selected. -- **File formatter syntax**: syntax to display file info for each file in the explorer. See [File explorer format](#file-explorer-format) +- **Remote File formatter syntax**: syntax to display file info for each file in the remote explorer. See [File explorer format](#file-explorer-format) +- **Local File formatter syntax**: syntax to display file info for each file in the local explorer. See [File explorer format](#file-explorer-format) ### SSH Key Storage 🔐 diff --git a/install.sh b/install.sh index 7e09cff..d180823 100755 --- a/install.sh +++ b/install.sh @@ -81,6 +81,17 @@ download() { return $rc } +test_writeable() { + local path + path="${1:-}/test.txt" + if touch "${path}" 2>/dev/null; then + rm "${path}" + return 0 + else + return 1 + fi +} + elevate_priv() { if ! has sudo; then error 'Could not find the command "sudo", needed to install termscp on your system.' @@ -95,15 +106,16 @@ elevate_priv() { fi } -test_writeable() { - local path - path="${1:-}/test.txt" - if touch "${path}" 2>/dev/null; then - rm "${path}" - return 0 - else - return 1 - fi +elevate_priv_ex() { + check_dir="$1" + if test_writeable "$check_dir"; then + sudo="" + else + warn "Root permissions are required to install dependecies" + elevate_priv + sudo="sudo" + fi + echo $sudo } # Currently supporting: @@ -275,11 +287,92 @@ install_on_macos() { fi } +# -- cargo installation + +install_bsd_cargo_deps() { + set -e + confirm "${YELLOW}libssh, gcc${NO_COLOR} are required to install ${GREEN}termscp${NO_COLOR}; would you like to proceed?" + sudo="$(elevate_priv_ex /usr/local/bin)" + $sudo pkg install -y curl wget libssh gcc + info "Dependencies installed successfully" +} + +install_linux_cargo_deps() { + local debian_deps="gcc pkg-config libssl-dev libssh2-1-dev libdbus-1-dev" + local rpm_deps="gcc openssl pkgconfig libdbus-devel openssl-devel" + local arch_deps="gcc openssl pkg-config dbus" + local deps_cmd="" + # Get pkg manager + if has apt; then + deps_cmd="apt install -y $debian_deps" + elif has apt-get; then + deps_cmd="apt-get install -y $debian_deps" + elif has yum; then + deps_cmd="yum -y install $rpm_deps" + elif has dnf; then + deps_cmd="dnf -y install $rpm_deps" + elif has pacman; then + deps_cmd="pacman -S --noconfirm $arch_deps" + else + error "Could not find any suitable package manager for your linux distro 🙄" + error "Supported package manager are: 'apt', 'apt-get', 'yum', 'dnf', 'pacman'" + exit 1 + fi + set -e + confirm "${YELLOW}libssh, gcc, openssl, pkg-config, libdbus${NO_COLOR} are required to install ${GREEN}termscp${NO_COLOR}. The following command will be used to install the dependencies: '${BOLD}${YELLOW}${deps_cmd}${NO_COLOR}'. Would you like to proceed?" + sudo="$(elevate_priv_ex /usr/local/bin)" + $sudo $deps_cmd + info "Dependencies installed successfully" +} + +install_cargo() { + if has cargo; then + return 0 + fi + cargo_env="$HOME/.cargo/env" + # Check if cargo is already installed (actually), but not loaded + if [ -f $cargo_env ]; then + . $cargo_env + fi + # Check again cargo + if has cargo; then + return 0 + else + confirm "${YELLOW}rust${NO_COLOR} is required to build termscp with cargo; would you like to install it now?" + set -e + rustup=$(get_tmpfile "sh") + info "Downloading rustup.sh…" + download "${rustup}" "https://sh.rustup.rs" + chmod +x $rustup + $rustup -y + info "Rust installed with success" + . $cargo_env + fi + +} + try_with_cargo() { err="$1" + # Install cargo + install_cargo if has cargo; then info "Installing ${GREEN}termscp${NO_COLOR} via Cargo…" - cargo install termscp + case $PLATFORM in + "freebsd") + install_bsd_cargo_deps + cargo install --no-default-features termscp + ;; + + "linux") + install_linux_cargo_deps + cargo install termscp + ;; + + *) + cargo install termscp + ;; + + esac else error "$err" error "Alternatively you can opt for installing Cargo " diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index e9afd48..c12cd67 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -26,7 +26,7 @@ * SOFTWARE. */ // Crate -#[cfg(any(target_os = "windows", target_os = "macos"))] +#[cfg(feature = "with-keyring")] use super::keys::keyringstorage::KeyringStorage; use super::keys::{filestorage::FileStorage, KeyStorage, KeyStorageError}; // Local @@ -67,8 +67,8 @@ impl BookmarksClient { // Create default hosts let default_hosts: UserHosts = Default::default(); debug!("Setting up bookmarks client..."); - // Make a key storage (windows / macos) - #[cfg(any(target_os = "windows", target_os = "macos"))] + // Make a key storage (with-keyring) + #[cfg(feature = "with-keyring")] let (key_storage, service_id): (Box, &str) = { debug!("Setting up KeyStorage"); let username: String = whoami::username(); @@ -89,13 +89,8 @@ impl BookmarksClient { } } }; - // Make a key storage (linux / unix) - #[cfg(any( - target_os = "linux", - target_os = "freebsd", - target_os = "netbsd", - target_os = "netbsd" - ))] + // Make a key storage (wno-keyring) + #[cfg(not(feature = "with-keyring"))] let (key_storage, service_id): (Box, &str) = { #[cfg(not(test))] let app_name: &str = "bookmarks"; @@ -707,6 +702,22 @@ mod tests { ); } + #[test] + fn test_system_bookmarks_decrypt_str() { + let tmp_dir: tempfile::TempDir = TempDir::new().ok().unwrap(); + let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); + // Initialize a new bookmarks client + let mut client: BookmarksClient = + BookmarksClient::new(cfg_path.as_path(), key_path.as_path(), 16).unwrap(); + client.key = "MYSUPERSECRETKEY".to_string(); + let input: &str = "Hello world!"; + assert_eq!( + client.decrypt_str("z4Z6LpcpYqBW4+bkIok+5A==").ok().unwrap(), + "Hello world!" + ); + assert!(client.decrypt_str("bidoof").is_err()); + } + /// ### get_paths /// /// Get paths for configuration and key for bookmarks diff --git a/src/system/keys/keyringstorage.rs b/src/system/keys/keyringstorage.rs index 349357f..1e59647 100644 --- a/src/system/keys/keyringstorage.rs +++ b/src/system/keys/keyringstorage.rs @@ -37,7 +37,6 @@ pub struct KeyringStorage { username: String, } -#[cfg(not(tarpaulin_include))] impl KeyringStorage { /// ### new /// @@ -49,7 +48,6 @@ impl KeyringStorage { } } -#[cfg(not(tarpaulin_include))] impl KeyStorage for KeyringStorage { /// ### get_key /// @@ -66,7 +64,10 @@ impl KeyStorage for KeyringStorage { KeyringError::WindowsVaultError => Err(KeyStorageError::NoSuchKey), #[cfg(target_os = "macos")] KeyringError::MacOsKeychainError(_) => Err(KeyStorageError::NoSuchKey), - _ => panic!("{}", e), + #[cfg(target_os = "linux")] + KeyringError::SecretServiceError(_) => Err(KeyStorageError::ProviderError), + KeyringError::Parse(_) => Err(KeyStorageError::BadSytax), + _ => Err(KeyStorageError::ProviderError), }, } } @@ -91,7 +92,13 @@ impl KeyStorage for KeyringStorage { // Check what kind of error is returned match storage.get_password() { Ok(_) => true, + #[cfg(not(target_os = "linux"))] Err(err) => !matches!(err, KeyringError::NoBackendFound), + #[cfg(target_os = "linux")] + Err(err) => !matches!( + err, + KeyringError::NoBackendFound | KeyringError::SecretServiceError(_) + ), } } } diff --git a/src/system/keys/mod.rs b/src/system/keys/mod.rs index 4933a8e..66ac480 100644 --- a/src/system/keys/mod.rs +++ b/src/system/keys/mod.rs @@ -27,30 +27,25 @@ */ // Storages pub mod filestorage; -#[cfg(any(target_os = "windows", target_os = "macos"))] +#[cfg(feature = "with-keyring")] pub mod keyringstorage; +// ext +use thiserror::Error; /// ## KeyStorageError /// /// defines the error type for the `KeyStorage` -#[derive(PartialEq, std::fmt::Debug)] +#[derive(Debug, Error, PartialEq)] pub enum KeyStorageError { - //BadKey, + #[cfg(feature = "with-keyring")] + #[error("Key has a bad syntax")] + BadSytax, + #[error("Provider service error")] ProviderError, + #[error("No such key")] 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::ProviderError => "Provider service error", - KeyStorageError::NoSuchKey => "No such key", - }); - write!(f, "{}", err) - } -} - /// ## KeyStorage /// /// this traits provides the methods to communicate and interact with the key storage. @@ -82,12 +77,17 @@ mod tests { #[test] fn test_system_keys_mod_errors() { + #[cfg(feature = "with-keyring")] assert_eq!( - format!("{}", KeyStorageError::ProviderError), + KeyStorageError::BadSytax.to_string(), + String::from("Key has a bad syntax") + ); + assert_eq!( + KeyStorageError::ProviderError.to_string(), String::from("Provider service error") ); assert_eq!( - format!("{}", KeyStorageError::NoSuchKey), + KeyStorageError::NoSuchKey.to_string(), String::from("No such key") ); }