diff --git a/kded/CMakeLists.txt b/kded/CMakeLists.txt --- a/kded/CMakeLists.txt +++ b/kded/CMakeLists.txt @@ -17,6 +17,7 @@ engine/backends/encfs/encfsbackend.cpp engine/backends/cryfs/cryfsbackend.cpp + engine/backends/gocryptfs/gocryptfsbackend.cpp ui/dialogdsl.cpp ui/activitieslinkingwidget.cpp diff --git a/kded/engine/backend_p.cpp b/kded/engine/backend_p.cpp --- a/kded/engine/backend_p.cpp +++ b/kded/engine/backend_p.cpp @@ -24,6 +24,7 @@ #include "backends/encfs/encfsbackend.h" #include "backends/cryfs/cryfsbackend.h" +#include "backends/gocryptfs/gocryptfsbackend.h" #include @@ -43,7 +44,7 @@ QStringList Backend::availableBackends() { - return { "encfs", "cryfs" }; + return { "encfs", "cryfs", "gocryptfs" }; } @@ -53,6 +54,7 @@ return backend == QLatin1String("encfs") ? PlasmaVault::EncFsBackend::instance() : backend == QLatin1String("cryfs") ? PlasmaVault::CryFsBackend::instance() : + backend == QLatin1String("gocryptfs") ? PlasmaVault::GocryptfsBackend::instance() : /* unknown backend */ nullptr; } diff --git a/kded/engine/backends/gocryptfs/gocryptfsbackend.h b/kded/engine/backends/gocryptfs/gocryptfsbackend.h new file mode 100644 --- /dev/null +++ b/kded/engine/backends/gocryptfs/gocryptfsbackend.h @@ -0,0 +1,54 @@ +/* + * Copyright 2020 by Martino Pilia + * + * This program 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 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program 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 this program. If not, see . + */ + +#ifndef PLASMAVAULT_KDED_ENGINE_BACKENDS_GOCRYPTFS_BACKEND_H +#define PLASMAVAULT_KDED_ENGINE_BACKENDS_GOCRYPTFS_BACKEND_H + +#include "../../fusebackend_p.h" + +namespace PlasmaVault { + +class GocryptfsBackend: public FuseBackend { +public: + GocryptfsBackend(); + ~GocryptfsBackend() override; + + static Backend::Ptr instance(); + + bool isInitialized(const Device &device) const override; + + FutureResult<> validateBackend() override; + + QString name() const override { return "gocryptfs"; } + +protected: + FutureResult<> mount(const Device &device, + const MountPoint &mountPoint, + const Vault::Payload &payload) override; + +private: + QProcess *gocryptfs(const QStringList &arguments) const; + QString getConfigFilePath(const Device &device) const; +}; + +} // namespace PlasmaVault + +#endif // include guard + diff --git a/kded/engine/backends/gocryptfs/gocryptfsbackend.cpp b/kded/engine/backends/gocryptfs/gocryptfsbackend.cpp new file mode 100644 --- /dev/null +++ b/kded/engine/backends/gocryptfs/gocryptfsbackend.cpp @@ -0,0 +1,212 @@ +/* + * Copyright 2020 by Martino Pilia + * + * This program 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 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program 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 this program. If not, see . + */ + +#include "gocryptfsbackend.h" + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +using namespace AsynQt; + +namespace PlasmaVault { + +// See `man gocryptfs`, section EXIT CODES. +enum class ExitCode : int{ + Success = 0, + + // CIPHERDIR is not an emtpy directory (on "-init") + NonEmptyCipherDir = 6, + + // MOUNTPOINT is not an empty directory + NonEmptyMountPoint = 10, + + // Password incorrect + WrongPassword = 12, + + // Password is empty (on "-init") + EmptyPassword = 22, + + // Could not read gocryptfs.conf + CannotReadConfig = 23, + + // Could not write gocryptfs.conf (on "-init" or "-password") + CannotWriteConfig = 24, + + // fsck found errors + FsckError = 26, +}; + + + +GocryptfsBackend::GocryptfsBackend() +{ +} + + + +GocryptfsBackend::~GocryptfsBackend() +{ +} + + + +Backend::Ptr GocryptfsBackend::instance() +{ + return singleton::instance(); +} + + + +FutureResult<> GocryptfsBackend::mount(const Device &device, + const MountPoint &mountPoint, + const Vault::Payload &payload) +{ + QDir dir; + + const auto password = payload[KEY_PASSWORD].toString(); + + if (!dir.mkpath(device.data()) || !dir.mkpath(mountPoint.data())) { + return errorResult(Error::BackendError, i18n("Failed to create directories, check your permissions")); + } + + if (isInitialized(device)) { + auto mountProcess = gocryptfs({ + device.data(), // cypher data directory + mountPoint.data() // mount point + }); + + auto mountResult = makeFuture(mountProcess, hasProcessFinishedSuccessfully); + + // Write password + mountProcess->write(password.toUtf8() + "\n"); + + return mountResult; + } else { + // Initialise cipherdir + auto initProcess = gocryptfs({ + "-init", + device.data(), + }); + + auto initResult = makeFuture(initProcess, [=] (QProcess *process) { + auto const exitCode = static_cast(process->exitCode()); + + switch (exitCode) { + case ExitCode::Success: + return AsynQt::await(mount(device, mountPoint, payload)); + + case ExitCode::NonEmptyCipherDir: + return Result<>::error(Error::BackendError, + i18n("The cipher directory is not empty, cannot initialise the vault.")); + + case ExitCode::EmptyPassword: + return Result<>::error(Error::BackendError, + i18n("The password is empty, cannot initialise the vault.")); + + case ExitCode::CannotWriteConfig: + return Result<>::error(Error::BackendError, + i18n("Cannot write gocryptfs.conf inside cipher directory, check your permissions.")); + + default: + return Result<>::error(Error::CommandError, + i18n("Unable to perform the operation (error code %1).", QString::number((int) exitCode)), + process->readAllStandardOutput(), + process->readAllStandardError()); + } + }); + + // Write password twice (insert and confirm) + for (int i = 0; i < 2; ++i) { + initProcess->write(password.toUtf8() + "\n"); + } + + return initResult; + } +} + + + +FutureResult<> GocryptfsBackend::validateBackend() +{ + using namespace AsynQt::operators; + + // We need to check whether all the commands are installed + // and whether the user has permissions to run them + return + collect(checkVersion(gocryptfs({ "--version" }), std::make_tuple(1, 7, 1)), + checkVersion(fusermount({ "--version" }), std::make_tuple(2, 9, 7))) + + | transform([this] (const QPair &gocryptfs, + const QPair &fusermount) { + + bool success = gocryptfs.first && fusermount.first; + QString message = formatMessageLine("gocryptfs", gocryptfs) + + formatMessageLine("fusermount", fusermount); + + return success ? Result<>::success() + : Result<>::error(Error::BackendError, message); + }); +} + + + +bool GocryptfsBackend::isInitialized(const Device &device) const +{ + QFile gocryptfsConfig(getConfigFilePath(device)); + return gocryptfsConfig.exists(); +} + + + +QProcess *GocryptfsBackend::gocryptfs(const QStringList &arguments) const +{ + auto config = KSharedConfig::openConfig(PLASMAVAULT_CONFIG_FILE); + KConfigGroup backendConfig(config, "GocryptfsBackend"); + + return process("gocryptfs", + arguments + backendConfig.readEntry("extraMountOptions", QStringList{}), + {}); +} + + + +QString GocryptfsBackend::getConfigFilePath(const Device &device) const +{ + return device.data() + QStringLiteral("/gocryptfs.conf"); +} + + + +} // namespace PlasmaVault + diff --git a/kded/engine/fusebackend_p.cpp b/kded/engine/fusebackend_p.cpp --- a/kded/engine/fusebackend_p.cpp +++ b/kded/engine/fusebackend_p.cpp @@ -55,7 +55,7 @@ Result<>::success() : // If we tried to mount into a non-empty location, report - err.contains("'nonempty'") ? + (err.contains("'nonempty'") || err.contains("non empty")) ? Result<>::error(Error::CommandError, i18n("The mount point directory is not empty, refusing to open the vault")) : @@ -113,9 +113,9 @@ FutureResult<> FuseBackend::initialize(const QString &name, - const Device &device, - const MountPoint &mountPoint, - const Vault::Payload &payload) + const Device &device, + const MountPoint &mountPoint, + const Vault::Payload &payload) { Q_UNUSED(name); @@ -157,8 +157,8 @@ FutureResult<> FuseBackend::open(const Device &device, - const MountPoint &mountPoint, - const Vault::Payload &payload) + const MountPoint &mountPoint, + const Vault::Payload &payload) { return isOpened(mountPoint) ? diff --git a/kded/ui/vaultconfigurationdialog.cpp b/kded/ui/vaultconfigurationdialog.cpp --- a/kded/ui/vaultconfigurationdialog.cpp +++ b/kded/ui/vaultconfigurationdialog.cpp @@ -82,6 +82,10 @@ { "cryfs" / i18n("CryFS"), defaultSteps + }, + + { "gocryptfs" / i18n("gocryptfs"), + defaultSteps } }; diff --git a/kded/ui/vaultcreationwizard.cpp b/kded/ui/vaultcreationwizard.cpp --- a/kded/ui/vaultcreationwizard.cpp +++ b/kded/ui/vaultcreationwizard.cpp @@ -110,6 +110,37 @@ offlineOnlyChooser() } } + }, + + { "gocryptfs" / i18n("gocryptfs"), + { + step { notice("gocryptfs-message", + i18n("Security notice:\n\ + Gocryptfs encrypts your files, so you can safely store them anywhere.\n\ + It works well together with cloud services like Dropbox, iCloud, OneDrive and others.\n\ +

\n\ + A threat model for gocryptfs is provided by the author at \ + nuetzlich.net/gocryptfs/threat_model. \ +

\n\ + According to a security audit performed in 2017 by Taylor Hornby (Defuse Security),\n\ + gocryptfs keeps file contents secret against an adversary that can read and modify the \ + ciphertext. \ +

\n\ + See defuse.ca/audits/gocryptfs.htm for more information.")) + }, + step { passwordChooser() }, + step { directoryPairChooser( + DirectoryPairChooserWidget::AutoFillPaths | + DirectoryPairChooserWidget::ShowDevicePicker | + DirectoryPairChooserWidget::ShowMountPointPicker | + DirectoryPairChooserWidget::RequireEmptyDevice | + DirectoryPairChooserWidget::RequireEmptyMountPoint + ) }, + step { + activitiesChooser(), + offlineOnlyChooser() + } + } } }; diff --git a/kded/ui/vaultimportingwizard.cpp b/kded/ui/vaultimportingwizard.cpp --- a/kded/ui/vaultimportingwizard.cpp +++ b/kded/ui/vaultimportingwizard.cpp @@ -77,6 +77,22 @@ offlineOnlyChooser() } } + }, + + { "gocryptfs" / i18n("gocryptfs"), + { + step { directoryPairChooser( + DirectoryPairChooserWidget::ShowDevicePicker | + DirectoryPairChooserWidget::ShowMountPointPicker | + DirectoryPairChooserWidget::RequireExistingDevice | + DirectoryPairChooserWidget::RequireEmptyMountPoint + ) }, + step { passwordChooser() }, + step { + activitiesChooser(), + offlineOnlyChooser() + } + } } }; diff --git a/kded/ui/vaultwizardbase.h b/kded/ui/vaultwizardbase.h --- a/kded/ui/vaultwizardbase.h +++ b/kded/ui/vaultwizardbase.h @@ -60,8 +60,9 @@ // to suggest the highest priority to the user as a starting value QMap priorities = { - { "encfs", 1 }, - { "cryfs", 2 } + { "gocryptfs", 1 }, + { "encfs", 2 }, + { "cryfs", 3 }, }; template