Commit 853b0be5 authored by Martino Pilia's avatar Martino Pilia Committed by Ivan Čukić

Add gocryptfs support

Summary:
Implement basic support for gocryptfs backend.

BUG: 398323

Reviewers: ivan, #plasma

Reviewed By: ivan, #plasma

Subscribers: ngraham, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D29595
parent 2befe1ff
......@@ -17,6 +17,7 @@ set (
engine/backends/encfs/encfsbackend.cpp
engine/backends/cryfs/cryfsbackend.cpp
engine/backends/gocryptfs/gocryptfsbackend.cpp
ui/dialogdsl.cpp
ui/activitieslinkingwidget.cpp
......
......@@ -24,6 +24,7 @@
#include "backends/encfs/encfsbackend.h"
#include "backends/cryfs/cryfsbackend.h"
#include "backends/gocryptfs/gocryptfsbackend.h"
#include <KLocalizedString>
......@@ -43,7 +44,7 @@ Backend::~Backend()
QStringList Backend::availableBackends()
{
return { "encfs", "cryfs" };
return { "encfs", "cryfs", "gocryptfs" };
}
......@@ -53,6 +54,7 @@ Backend::Ptr Backend::instance(const QString &backend)
return
backend == QLatin1String("encfs") ? PlasmaVault::EncFsBackend::instance() :
backend == QLatin1String("cryfs") ? PlasmaVault::CryFsBackend::instance() :
backend == QLatin1String("gocryptfs") ? PlasmaVault::GocryptfsBackend::instance() :
/* unknown backend */ nullptr;
}
......
/*
* Copyright 2020 by Martino Pilia <martino.pilia (at) gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "gocryptfsbackend.h"
#include <QDir>
#include <QProcess>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMountPoint>
#include <KSharedConfig>
#include <algorithm>
#include <asynqt/basic/all.h>
#include <asynqt/operations/collect.h>
#include <asynqt/operations/transform.h>
#include <asynqt/wrappers/process.h>
#include <singleton_p.h>
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<GocryptfsBackend>();
}
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<ExitCode>(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<bool, QString> &gocryptfs,
const QPair<bool, QString> &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
/*
* Copyright 2020 by Martino Pilia <martino.pilia (at) gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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
......@@ -55,7 +55,7 @@ Result<> FuseBackend::hasProcessFinishedSuccessfully(QProcess *process)
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 @@ QProcess *FuseBackend::fusermount(const QStringList &arguments) const
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::import(const QString &name,
FutureResult<> FuseBackend::open(const Device &device,
const MountPoint &mountPoint,
const Vault::Payload &payload)
const MountPoint &mountPoint,
const Vault::Payload &payload)
{
return
isOpened(mountPoint) ?
......
......@@ -82,6 +82,10 @@ public:
{ "cryfs" / i18n("CryFS"),
defaultSteps
},
{ "gocryptfs" / i18n("gocryptfs"),
defaultSteps
}
};
......
......@@ -110,6 +110,37 @@ public:
offlineOnlyChooser()
}
}
},
{ "gocryptfs" / i18n("gocryptfs"),
{
step { notice("gocryptfs-message",
i18n("<b>Security notice:</b>\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\
<br /><br />\n\
A threat model for gocryptfs is provided by the author at \
<a href='https://nuetzlich.net/gocryptfs/threat_model'>nuetzlich.net/gocryptfs/threat_model</a>. \
<br /><br />\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. \
<br /><br />\n\
See <a href='https://defuse.ca/audits/gocryptfs.htm'>defuse.ca/audits/gocryptfs.htm</a> for more information."))
},
step { passwordChooser() },
step { directoryPairChooser(
DirectoryPairChooserWidget::AutoFillPaths |
DirectoryPairChooserWidget::ShowDevicePicker |
DirectoryPairChooserWidget::ShowMountPointPicker |
DirectoryPairChooserWidget::RequireEmptyDevice |
DirectoryPairChooserWidget::RequireEmptyMountPoint
) },
step {
activitiesChooser(),
offlineOnlyChooser()
}
}
}
};
......
......@@ -77,6 +77,22 @@ public:
offlineOnlyChooser()
}
}
},
{ "gocryptfs" / i18n("gocryptfs"),
{
step { directoryPairChooser(
DirectoryPairChooserWidget::ShowDevicePicker |
DirectoryPairChooserWidget::ShowMountPointPicker |
DirectoryPairChooserWidget::RequireExistingDevice |
DirectoryPairChooserWidget::RequireEmptyMountPoint
) },
step { passwordChooser() },
step {
activitiesChooser(),
offlineOnlyChooser()
}
}
}
};
......
......@@ -60,8 +60,9 @@ public:
// to suggest the highest priority to the user as a starting value
QMap<QString, int> priorities = {
{ "encfs", 1 },
{ "cryfs", 2 }
{ "gocryptfs", 1 },
{ "encfs", 2 },
{ "cryfs", 3 },
};
template <typename ClickHandler>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment