diff --git a/README.md b/README.md index 5851df0c8..439c71168 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,31 @@ qtpass QtPass is a gui for [pass](http://www.passwordstore.org/) +Security considerations +----------------------- +Using this program will not magically keep your passwords secure against +compromised computers even if you use it in combination with a smartcard. +It does protect future and changed passwords though against anyone with access to +your password store only but not your keys. +Used with a smartcard it also protects against anyone just monitoring/copying +all files/keystrokes on that machine and such an attacker would only gain access +to the passwords you actually use. +Once you plug in your smartcard and enter your PIN (or due to CVE-2015-3298 +even without your PIN) all your passwords available to the machine can be +decrypted by it, if there is malicious software targeted specifically against +it installed (or at least one that knows how to use a smartcard). +To get better protection out of use with a smartcard even against a targeted +attack I can think of at least two options: +* The smartcard must require explicit confirmation for each decryption operation. + Or if it just provides a counter for decrypted data you could at least notice + an attack afterwards, though at quite some effort on your part. +* Use a different smartcard for each (group of) key. +* If using a YubiKey or U2F module or similar that requires a "button" press for + other authentication methods you can use one OTP/U2F enabled WebDAV account per + password (or groups of passwords) as a quite inconvenient workaround. + Unfortunately I do not know of any WebDAV service with OTP support except ownCloud + (so you would have to run your own server). + Current state ------------- * Using pass or directly with git and gpg2 diff --git a/mainwindow.cpp b/mainwindow.cpp index b7495b13e..3074249be 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -7,6 +7,10 @@ #include #include #include +#ifdef Q_OS_WIN +#include +#undef DELETE +#endif /** * @brief MainWindow::MainWindow @@ -15,7 +19,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), - process(new QProcess(this)) + process(new QProcess(this)), + fusedav(this) { // connect(process.data(), SIGNAL(readyReadStandardOutput()), this, SLOT(readyRead())); connect(process.data(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); @@ -29,6 +34,14 @@ MainWindow::MainWindow(QWidget *parent) : */ MainWindow::~MainWindow() { +#ifdef Q_OS_WIN + if (useWebDav) WNetCancelConnection2A(passStore.toUtf8().constData(), 0, 1); +#else + if (fusedav.state() == QProcess::Running) { + fusedav.terminate(); + fusedav.waitForFinished(2000); + } +#endif } void MainWindow::normalizePassStore() { @@ -49,6 +62,59 @@ QSettings &MainWindow::getSettings() { return *settings; } +void MainWindow::mountWebDav() { +#ifdef Q_OS_WIN + char dst[20] = {0}; + NETRESOURCEA netres; + memset(&netres, 0, sizeof(netres)); + netres.dwType = RESOURCETYPE_DISK; + netres.lpLocalName = 0; + netres.lpRemoteName = webDavUrl.toUtf8().data(); + DWORD size = sizeof(dst); + DWORD r = WNetUseConnectionA(reinterpret_cast(effectiveWinId()), &netres, webDavPassword.toUtf8().constData(), + webDavUser.toUtf8().constData(), CONNECT_TEMPORARY | CONNECT_INTERACTIVE | CONNECT_REDIRECT, + dst, &size, 0); + if (r == NO_ERROR) { + passStore = dst; + } else { + char message[256] = {0}; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, r, 0, message, sizeof(message), 0); + ui->textBrowser->setTextColor(Qt::red); + ui->textBrowser->setText(tr("Failed to connect WebDAV:\n") + message + " (0x" + QString::number(r, 16) + ")"); + } +#else + fusedav.start("fusedav -o nonempty -u \"" + webDavUser + "\" " + webDavUrl + " \"" + passStore + '"'); + fusedav.waitForStarted(); + if (fusedav.state() == QProcess::Running) { + QString pwd = webDavPassword; + bool ok = true; + if (pwd.isEmpty()) { + pwd = QInputDialog::getText(this, tr("QtPass WebDAV password"), + tr("Enter password to connect to WebDAV:"), QLineEdit::Password, "", &ok); + } + if (ok && !pwd.isEmpty()) { + fusedav.write(pwd.toUtf8() + '\n'); + fusedav.closeWriteChannel(); + fusedav.waitForFinished(2000); + } else { + fusedav.terminate(); + } + } + QString error = fusedav.readAllStandardError(); + int prompt = error.indexOf("Password:"); + if (prompt >= 0) { + error.remove(0, prompt + 10); + } + if (fusedav.state() != QProcess::Running) { + error = tr("fusedav exited unexpectedly\n") + error; + } + if (error.size() > 0) { + ui->textBrowser->setTextColor(Qt::red); + ui->textBrowser->setText(tr("Failed to start fusedav to connect WebDAV:\n") + error); + } +#endif +} + /** * @brief MainWindow::checkConfig */ @@ -87,10 +153,22 @@ void MainWindow::checkConfig() { } gpgHome = settings.value("gpgHome").toString(); + useWebDav = (settings.value("useWebDav") == "true"); + webDavUrl = settings.value("webDavUrl").toString(); + webDavUser = settings.value("webDavUser").toString(); + webDavPassword = settings.value("webDavPassword").toString(); + if (passExecutable == "" && (gitExecutable == "" || gpgExecutable == "")) { config(); } + // TODO: this needs to be before we try to access the store, + // but it would be better to do it after the Window is shown, + // as the long delay it can cause is irritating otherwise. + if (useWebDav) { + mountWebDav(); + } + model.setNameFilters(QStringList() << "*.gpg"); model.setNameFilterDisables(false); diff --git a/mainwindow.h b/mainwindow.h index b7fa84b50..19243556d 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -70,6 +70,11 @@ private slots: QString gitExecutable; QString gpgExecutable; QString gpgHome; + bool useWebDav; + QString webDavUrl; + QString webDavUser; + QString webDavPassword; + QProcess fusedav; QString clippedPass; actionType currentAction; QString lastDecrypt; @@ -87,6 +92,7 @@ private slots: QSettings &getSettings(); QList listKeys(QString keystring = ""); QString getRecipientString(QString for_file, QString separator = " "); + void mountWebDav(); }; #endif // MAINWINDOW_H