Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/desktop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Schuster <michael@schuster.ms>2019-12-24 09:12:54 +0300
committerMichael Schuster <michael@schuster.ms>2019-12-24 11:16:52 +0300
commit73462e97aaba6ed8e352a06a48aeda6f8eac99aa (patch)
tree57440531ae221b43c0de6ea87801eb50050d8787
parent6f4144a464448ebefa25fc63c64267e309e1af1d (diff)
Heavy refactoring: Windows workaround for >= 4k (4096 bit) client-cert SSL keys and large certs
With QtKeychain on Windows, storing larger keys or certs in one keychain entry causes the following error due to limits in the Windows APIs: Error: "Credential size exceeds maximum size of 2560" This fix implements the new wrapper class KeychainChunk with wrapper jobs ReadJob and WriteJob to encapsulate the QKeychain handling of ReadPasswordJob and WritePasswordJob with binaryData but split every supplied keychain entry's data into 2048 byte chunks, on Windows only. The wrapper is used for all keychain operations in WebFlowCredentials, except for the server password. All finished keychain jobs now get deleted properly, to avoid memory leaks. For reference also see previous fixes: - https://github.com/nextcloud/desktop/pull/1389 - https://github.com/nextcloud/desktop/pull/1394 This should finally fix the re-opened issue: - https://github.com/nextcloud/desktop/issues/863 Signed-off-by: Michael Schuster <michael@schuster.ms> (cherry picked from commit 9b034a2eb02a4d12c6561b783b7685748b09a65e) Signed-off-by: Michael Schuster <michael@schuster.ms>
-rw-r--r--src/gui/CMakeLists.txt1
-rw-r--r--src/gui/creds/keychainchunk.cpp219
-rw-r--r--src/gui/creds/keychainchunk.h120
-rw-r--r--src/gui/creds/webflowcredentials.cpp271
-rw-r--r--src/gui/creds/webflowcredentials.h32
5 files changed, 450 insertions, 193 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 63618fc11..285057992 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -109,6 +109,7 @@ set(client_SRCS
creds/httpcredentialsgui.cpp
creds/oauth.cpp
creds/flow2auth.cpp
+ creds/keychainchunk.cpp
creds/webflowcredentials.cpp
creds/webflowcredentialsdialog.cpp
wizard/postfixlineedit.cpp
diff --git a/src/gui/creds/keychainchunk.cpp b/src/gui/creds/keychainchunk.cpp
new file mode 100644
index 000000000..4115c938d
--- /dev/null
+++ b/src/gui/creds/keychainchunk.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) by Michael Schuster <michael@nextcloud.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) any later version.
+ *
+ * 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.
+ */
+
+#include "account.h"
+#include "keychainchunk.h"
+#include "theme.h"
+#include "networkjobs.h"
+#include "configfile.h"
+#include "creds/abstractcredentials.h"
+
+using namespace QKeychain;
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcKeychainChunk, "nextcloud.sync.credentials.keychainchunk", QtInfoMsg)
+
+namespace KeychainChunk {
+
+#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
+static void addSettingsToJob(Account *account, QKeychain::Job *job)
+{
+ Q_UNUSED(account)
+ auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName());
+ settings->setParent(job); // make the job parent to make setting deleted properly
+ job->setSettings(settings.release());
+}
+#endif
+
+/*
+* Job
+*/
+Job::Job(QObject *parent)
+ : QObject(parent)
+{
+ _serviceName = Theme::instance()->appName();
+}
+
+/*
+* WriteJob
+*/
+WriteJob::WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent)
+ : Job(parent)
+{
+ _account = account;
+ _key = key;
+
+ // Windows workaround: Split the private key into chunks of 2048 bytes,
+ // to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
+ _chunkBuffer = data;
+ _chunkCount = 0;
+}
+
+void WriteJob::start()
+{
+ slotWriteJobDone(nullptr);
+}
+
+void WriteJob::slotWriteJobDone(QKeychain::Job *incomingJob)
+{
+ QKeychain::WritePasswordJob *writeJob = static_cast<QKeychain::WritePasswordJob *>(incomingJob);
+
+ // errors?
+ if (writeJob) {
+ _error = writeJob->error();
+ _errorString = writeJob->errorString();
+
+ if (writeJob->error() != NoError) {
+ qCWarning(lcKeychainChunk) << "Error while writing" << writeJob->key() << "chunk" << writeJob->errorString();
+ _chunkBuffer.clear();
+ }
+ }
+
+ // write a chunk if there is any in the buffer
+ if (!_chunkBuffer.isEmpty()) {
+#if defined(Q_OS_WIN)
+ // Windows workaround: Split the data into chunks of 2048 bytes,
+ // to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
+ auto chunk = _chunkBuffer.left(KeychainChunk::ChunkSize);
+
+ _chunkBuffer = _chunkBuffer.right(_chunkBuffer.size() - chunk.size());
+#else
+ // write full data in one chunk on non-Windows, as usual
+ auto chunk = _chunkBuffer;
+
+ _chunkBuffer.clear();
+#endif
+ auto index = (_chunkCount++);
+
+ // keep the limit
+ if (_chunkCount > KeychainChunk::MaxChunks) {
+ qCWarning(lcKeychainChunk) << "Maximum chunk count exceeded while writing" << writeJob->key() << "chunk" << QString::number(index) << "cutting off after" << QString::number(KeychainChunk::MaxChunks) << "chunks";
+
+ writeJob->deleteLater();
+
+ _chunkBuffer.clear();
+
+ emit finished(this);
+ return;
+ }
+
+ const QString kck = AbstractCredentials::keychainKey(
+ _account->url().toString(),
+ _key + (index > 0 ? (QString(".") + QString::number(index)) : QString()),
+ _account->id());
+
+ QKeychain::WritePasswordJob *job = new QKeychain::WritePasswordJob(_serviceName);
+#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
+ addSettingsToJob(_account, job);
+#endif
+ job->setInsecureFallback(_insecureFallback);
+ connect(job, &QKeychain::Job::finished, this, &KeychainChunk::WriteJob::slotWriteJobDone);
+ // only add the key's (sub)"index" after the first element, to stay compatible with older versions and non-Windows
+ job->setKey(kck);
+ job->setBinaryData(chunk);
+ job->start();
+
+ chunk.clear();
+ } else {
+ emit finished(this);
+ }
+
+ writeJob->deleteLater();
+}
+
+/*
+* ReadJob
+*/
+ReadJob::ReadJob(Account *account, const QString &key, const bool &keychainMigration, QObject *parent)
+ : Job(parent)
+{
+ _account = account;
+ _key = key;
+
+ _keychainMigration = keychainMigration;
+
+ _chunkCount = 0;
+ _chunkBuffer.clear();
+}
+
+void ReadJob::start()
+{
+ _chunkCount = 0;
+ _chunkBuffer.clear();
+
+ const QString kck = AbstractCredentials::keychainKey(
+ _account->url().toString(),
+ _key,
+ _keychainMigration ? QString() : _account->id());
+
+ QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(_serviceName);
+#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
+ addSettingsToJob(_account, job);
+#endif
+ job->setInsecureFallback(_insecureFallback);
+ job->setKey(kck);
+ connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone);
+ job->start();
+}
+
+void ReadJob::slotReadJobDone(QKeychain::Job *incomingJob)
+{
+ // Errors or next chunk?
+ QKeychain::ReadPasswordJob *readJob = static_cast<QKeychain::ReadPasswordJob *>(incomingJob);
+
+ if (readJob) {
+ _error = readJob->error();
+ _errorString = readJob->errorString();
+
+ if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
+ _chunkBuffer.append(readJob->binaryData());
+ _chunkCount++;
+
+#if defined(Q_OS_WIN)
+ // try to fetch next chunk
+ if (_chunkCount < KeychainChunk::MaxChunks) {
+ const QString kck = AbstractCredentials::keychainKey(
+ _account->url().toString(),
+ _key + QString(".") + QString::number(_chunkCount),
+ _keychainMigration ? QString() : _account->id());
+
+ QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(_serviceName);
+#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
+ addSettingsToJob(_account, job);
+#endif
+ job->setInsecureFallback(_insecureFallback);
+ job->setKey(kck);
+ connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone);
+ job->start();
+
+ readJob->deleteLater();
+ return;
+ } else {
+ qCWarning(lcKeychainChunk) << "Maximum chunk count for" << readJob->key() << "reached, ignoring after" << KeychainChunk::MaxChunks;
+ }
+#endif
+ } else {
+ qCWarning(lcKeychainChunk) << "Unable to read" << readJob->key() << "chunk" << QString::number(_chunkCount) << readJob->errorString();
+ }
+
+ readJob->deleteLater();
+ }
+
+ emit finished(this);
+}
+
+} // namespace KeychainChunk
+
+} // namespace OCC
diff --git a/src/gui/creds/keychainchunk.h b/src/gui/creds/keychainchunk.h
new file mode 100644
index 000000000..875ab5037
--- /dev/null
+++ b/src/gui/creds/keychainchunk.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) by Michael Schuster <michael@nextcloud.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) any later version.
+ *
+ * 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.
+ */
+
+#pragma once
+#ifndef KEYCHAINCHUNK_H
+#define KEYCHAINCHUNK_H
+
+#include <QObject>
+#include <keychain.h>
+#include "accountfwd.h"
+
+// We don't support insecure fallback
+// #define KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK
+
+namespace OCC {
+
+namespace KeychainChunk {
+
+/*
+* Workaround for Windows:
+*
+* Split the keychain entry's data into chunks of 2048 bytes,
+* to allow 4k (4096 bit) keys / large certs to be saved (see limits in webflowcredentials.h)
+*/
+static constexpr int ChunkSize = 2048;
+static constexpr int MaxChunks = 10;
+
+/*
+ * @brief: Abstract base class for KeychainChunk jobs.
+ */
+class Job : public QObject {
+ Q_OBJECT
+public:
+ Job(QObject *parent = nullptr);
+
+ const QKeychain::Error error() const {
+ return _error;
+ }
+ const QString errorString() const {
+ return _errorString;
+ }
+
+ QByteArray binaryData() const {
+ return _chunkBuffer;
+ }
+
+ const bool insecureFallback() const {
+ return _insecureFallback;
+ }
+
+// If we use it but don't support insecure fallback, give us nice compilation errors ;p
+#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
+ void setInsecureFallback(const bool &insecureFallback)
+ {
+ _insecureFallback = insecureFallback;
+ }
+#endif
+
+protected:
+ QString _serviceName;
+ Account *_account;
+ QString _key;
+ bool _insecureFallback = false;
+ bool _keychainMigration = false;
+
+ QKeychain::Error _error = QKeychain::NoError;
+ QString _errorString;
+
+ int _chunkCount = 0;
+ QByteArray _chunkBuffer;
+}; // class Job
+
+/*
+* @brief: Simple wrapper class for QKeychain::WritePasswordJob, splits too large keychain entry's data into chunks on Windows
+*/
+class WriteJob : public KeychainChunk::Job {
+ Q_OBJECT
+public:
+ WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent = nullptr);
+ void start();
+
+signals:
+ void finished(KeychainChunk::WriteJob *incomingJob);
+
+private slots:
+ void slotWriteJobDone(QKeychain::Job *incomingJob);
+}; // class WriteJob
+
+/*
+* @brief: Simple wrapper class for QKeychain::ReadPasswordJob, splits too large keychain entry's data into chunks on Windows
+*/
+class ReadJob : public KeychainChunk::Job {
+ Q_OBJECT
+public:
+ ReadJob(Account *account, const QString &key, const bool &keychainMigration, QObject *parent = nullptr);
+ void start();
+
+signals:
+ void finished(KeychainChunk::ReadJob *incomingJob);
+
+private slots:
+ void slotReadJobDone(QKeychain::Job *incomingJob);
+}; // class ReadJob
+
+} // namespace KeychainChunk
+
+} // namespace OCC
+
+#endif // KEYCHAINCHUNK_H
diff --git a/src/gui/creds/webflowcredentials.cpp b/src/gui/creds/webflowcredentials.cpp
index c82798abf..4993a0dd0 100644
--- a/src/gui/creds/webflowcredentials.cpp
+++ b/src/gui/creds/webflowcredentials.cpp
@@ -18,6 +18,7 @@
#include "theme.h"
#include "wizard/webview.h"
#include "webflowcredentialsdialog.h"
+#include "keychainchunk.h"
using namespace QKeychain;
@@ -75,6 +76,7 @@ private:
QPointer<const WebFlowCredentials> _cred;
};
+#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
static void addSettingsToJob(Account *account, QKeychain::Job *job)
{
Q_UNUSED(account)
@@ -82,6 +84,7 @@ static void addSettingsToJob(Account *account, QKeychain::Job *job)
settings->setParent(job); // make the job parent to make setting deleted properly
job->setSettings(settings.release());
}
+#endif
WebFlowCredentials::WebFlowCredentials()
: _ready(false)
@@ -238,86 +241,32 @@ void WebFlowCredentials::persist() {
// write cert if there is one
if (!_clientSslCertificate.isNull()) {
- WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
- addSettingsToJob(_account, job);
- job->setInsecureFallback(false);
- connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientCertPEMJobDone);
- job->setKey(keychainKey(_account->url().toString(), _user + clientCertificatePEMC, _account->id()));
- job->setBinaryData(_clientSslCertificate.toPem());
+ auto *job = new KeychainChunk::WriteJob(_account,
+ _user + clientCertificatePEMC,
+ _clientSslCertificate.toPem());
+ connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientCertPEMJobDone);
job->start();
} else {
// no cert, just write credentials
- slotWriteClientCertPEMJobDone();
+ slotWriteClientCertPEMJobDone(nullptr);
}
}
-void WebFlowCredentials::slotWriteClientCertPEMJobDone()
+void WebFlowCredentials::slotWriteClientCertPEMJobDone(KeychainChunk::WriteJob *writeJob)
{
+ if(writeJob)
+ writeJob->deleteLater();
+
// write ssl key if there is one
if (!_clientSslKey.isNull()) {
- // Windows workaround: Split the private key into chunks of 2048 bytes,
- // to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
- _clientSslKeyChunkBufferPEM = _clientSslKey.toPem();
- _clientSslKeyChunkCount = 0;
-
- writeSingleClientKeyChunkPEM(nullptr);
- } else {
- // no key, just write credentials
- slotWriteClientKeyPEMJobDone();
- }
-}
-
-void WebFlowCredentials::writeSingleClientKeyChunkPEM(QKeychain::Job *incomingJob)
-{
- // errors?
- if (incomingJob) {
- WritePasswordJob *writeJob = static_cast<WritePasswordJob *>(incomingJob);
-
- if (writeJob->error() != NoError) {
- qCWarning(lcWebFlowCredentials) << "Error while writing client CA key chunk" << writeJob->errorString();
-
- _clientSslKeyChunkBufferPEM.clear();
- }
- }
-
- // write a key chunk if there is any in the buffer
- if (!_clientSslKeyChunkBufferPEM.isEmpty()) {
-#if defined(Q_OS_WIN)
- // Windows workaround: Split the private key into chunks of 2048 bytes,
- // to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
- auto chunk = _clientSslKeyChunkBufferPEM.left(_clientSslKeyChunkSize);
-
- _clientSslKeyChunkBufferPEM = _clientSslKeyChunkBufferPEM.right(_clientSslKeyChunkBufferPEM.size() - chunk.size());
-#else
- // write full key in one slot on non-Windows, as usual
- auto chunk = _clientSslKeyChunkBufferPEM;
-
- _clientSslKeyChunkBufferPEM.clear();
-#endif
- auto index = (_clientSslKeyChunkCount++);
-
- // keep the limit
- if (_clientSslKeyChunkCount > _clientSslKeyMaxChunks) {
- qCWarning(lcWebFlowCredentials) << "Maximum client key chunk count exceeded while writing slot" << QString::number(index) << "cutting off after" << QString::number(_clientSslKeyMaxChunks) << "chunks";
-
- _clientSslKeyChunkBufferPEM.clear();
-
- slotWriteClientKeyPEMJobDone();
- return;
- }
-
- WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
- addSettingsToJob(_account, job);
- job->setInsecureFallback(false);
- connect(job, &Job::finished, this, &WebFlowCredentials::writeSingleClientKeyChunkPEM);
- // only add the key's (sub)"index" after the first element, to stay compatible with older versions and non-Windows
- job->setKey(keychainKey(_account->url().toString(), _user + clientKeyPEMC + (index > 0 ? (QString(".") + QString::number(index)) : QString()), _account->id()));
- job->setBinaryData(chunk);
+ auto *job = new KeychainChunk::WriteJob(_account,
+ _user + clientKeyPEMC,
+ _clientSslKey.toPem());
+ connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientKeyPEMJobDone);
job->start();
-
- chunk.clear();
} else {
- slotWriteClientKeyPEMJobDone();
+ // no key, just write credentials
+ slotWriteClientKeyPEMJobDone(nullptr);
}
}
@@ -340,20 +289,21 @@ void WebFlowCredentials::writeSingleClientCaCertPEM()
return;
}
- WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
- addSettingsToJob(_account, job);
- job->setInsecureFallback(false);
- connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientCaCertsPEMJobDone);
- job->setKey(keychainKey(_account->url().toString(), _user + clientCaCertificatePEMC + QString::number(index), _account->id()));
- job->setBinaryData(cert.toPem());
+ auto *job = new KeychainChunk::WriteJob(_account,
+ _user + clientCaCertificatePEMC + QString::number(index),
+ cert.toPem());
+ connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientCaCertsPEMJobDone);
job->start();
} else {
slotWriteClientCaCertsPEMJobDone(nullptr);
}
}
-void WebFlowCredentials::slotWriteClientKeyPEMJobDone()
+void WebFlowCredentials::slotWriteClientKeyPEMJobDone(KeychainChunk::WriteJob *writeJob)
{
+ if(writeJob)
+ writeJob->deleteLater();
+
_clientSslCaCertificatesWriteQueue.clear();
// write ca certs if there are any
@@ -368,16 +318,16 @@ void WebFlowCredentials::slotWriteClientKeyPEMJobDone()
}
}
-void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob)
+void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(KeychainChunk::WriteJob *writeJob)
{
// errors / next ca cert?
- if (incomingJob && !_clientSslCaCertificates.isEmpty()) {
- WritePasswordJob *writeJob = static_cast<WritePasswordJob *>(incomingJob);
-
+ if (writeJob && !_clientSslCaCertificates.isEmpty()) {
if (writeJob->error() != NoError) {
qCWarning(lcWebFlowCredentials) << "Error while writing client CA cert" << writeJob->errorString();
}
+ writeJob->deleteLater();
+
if (!_clientSslCaCertificatesWriteQueue.isEmpty()) {
// next ca cert
writeSingleClientCaCertPEM();
@@ -387,7 +337,9 @@ void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomi
// done storing ca certs, time for the password
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
+#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
+#endif
job->setInsecureFallback(false);
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteJobDone);
job->setKey(keychainKey(_account->url().toString(), _user, _account->id()));
@@ -437,6 +389,10 @@ void WebFlowCredentials::forgetSensitiveData() {
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
job->setInsecureFallback(false);
job->setKey(kck);
+ connect(job, &Job::finished, this, [](QKeychain::Job *job) {
+ DeletePasswordJob *djob = qobject_cast<DeletePasswordJob *>(job);
+ djob->deleteLater();
+ });
job->start();
invalidateToken();
@@ -487,29 +443,23 @@ void WebFlowCredentials::slotFinished(QNetworkReply *reply) {
void WebFlowCredentials::fetchFromKeychainHelper() {
// Read client cert from keychain
- const QString kck = keychainKey(
- _account->url().toString(),
- _user + clientCertificatePEMC,
- _keychainMigration ? QString() : _account->id());
-
- ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
- addSettingsToJob(_account, job);
- job->setInsecureFallback(false);
- job->setKey(kck);
- connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientCertPEMJobDone);
+ auto *job = new KeychainChunk::ReadJob(_account,
+ _user + clientCertificatePEMC,
+ _keychainMigration);
+ connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientCertPEMJobDone);
job->start();
}
-void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob)
+void WebFlowCredentials::slotReadClientCertPEMJobDone(KeychainChunk::ReadJob *readJob)
{
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
- Q_ASSERT(!incomingJob->insecureFallback()); // If insecureFallback is set, the next test would be pointless
- if (_retryOnKeyChainError && (incomingJob->error() == QKeychain::NoBackendAvailable
- || incomingJob->error() == QKeychain::OtherError)) {
+ Q_ASSERT(!readJob->insecureFallback()); // If insecureFallback is set, the next test would be pointless
+ if (_retryOnKeyChainError && (readJob->error() == QKeychain::NoBackendAvailable
+ || readJob->error() == QKeychain::OtherError)) {
// Could be that the backend was not yet available. Wait some extra seconds.
// (Issues #4274 and #6522)
// (For kwallet, the error is OtherError instead of NoBackendAvailable, maybe a bug in QtKeychain)
- qCInfo(lcWebFlowCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << incomingJob->errorString();
+ qCInfo(lcWebFlowCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << readJob->errorString();
QTimer::singleShot(10000, this, &WebFlowCredentials::fetchFromKeychainHelper);
_retryOnKeyChainError = false;
return;
@@ -518,7 +468,6 @@ void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJo
#endif
// Store PEM in memory
- ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
if (sslCertificateList.length() >= 1) {
@@ -526,79 +475,40 @@ void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJo
}
}
- // Load key too
- _clientSslKeyChunkCount = 0;
- _clientSslKeyChunkBufferPEM.clear();
+ readJob->deleteLater();
- const QString kck = keychainKey(
- _account->url().toString(),
- _user + clientKeyPEMC,
- _keychainMigration ? QString() : _account->id());
-
- ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
- addSettingsToJob(_account, job);
- job->setInsecureFallback(false);
- job->setKey(kck);
- connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone);
+ // Load key too
+ auto *job = new KeychainChunk::ReadJob(_account,
+ _user + clientKeyPEMC,
+ _keychainMigration);
+ connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone);
job->start();
}
-void WebFlowCredentials::slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob)
+void WebFlowCredentials::slotReadClientKeyPEMJobDone(KeychainChunk::ReadJob *readJob)
{
- // Errors or next key chunk?
- ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
-
- if (readJob) {
- if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
- _clientSslKeyChunkBufferPEM.append(readJob->binaryData());
- _clientSslKeyChunkCount++;
-
-#if defined(Q_OS_WIN)
- // try to fetch next chunk
- if (_clientSslKeyChunkCount < _clientSslKeyMaxChunks) {
- const QString kck = keychainKey(
- _account->url().toString(),
- _user + clientKeyPEMC + QString(".") + QString::number(_clientSslKeyChunkCount),
- _keychainMigration ? QString() : _account->id());
-
- ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
- addSettingsToJob(_account, job);
- job->setInsecureFallback(false);
- job->setKey(kck);
- connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone);
- job->start();
-
- return;
- } else {
- qCWarning(lcWebFlowCredentials) << "Maximum client key chunk count reached, ignoring after" << _clientSslKeyMaxChunks;
- }
-#endif
- } else {
- if (readJob->error() != QKeychain::Error::EntryNotFound ||
- ((readJob->error() == QKeychain::Error::EntryNotFound) && _clientSslKeyChunkCount == 0)) {
- qCWarning(lcWebFlowCredentials) << "Unable to read client key chunk slot" << QString::number(_clientSslKeyChunkCount) << readJob->errorString();
- }
- }
- }
-
// Store key in memory
- if (_clientSslKeyChunkBufferPEM.size() > 0) {
+ if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
+ QByteArray clientKeyPEM = readJob->binaryData();
// FIXME Unfortunately Qt has a bug and we can't just use QSsl::Opaque to let it
// load whatever we have. So we try until it works.
- _clientSslKey = QSslKey(_clientSslKeyChunkBufferPEM, QSsl::Rsa);
+ _clientSslKey = QSslKey(clientKeyPEM, QSsl::Rsa);
if (_clientSslKey.isNull()) {
- _clientSslKey = QSslKey(_clientSslKeyChunkBufferPEM, QSsl::Dsa);
+ _clientSslKey = QSslKey(clientKeyPEM, QSsl::Dsa);
}
if (_clientSslKey.isNull()) {
- _clientSslKey = QSslKey(_clientSslKeyChunkBufferPEM, QSsl::Ec);
+ _clientSslKey = QSslKey(clientKeyPEM, QSsl::Ec);
}
if (_clientSslKey.isNull()) {
qCWarning(lcWebFlowCredentials) << "Could not load SSL key into Qt!";
}
- // clear key chunk buffer, but don't set _clientSslKeyChunkCount to zero because we need it for deleteKeychainEntries
- _clientSslKeyChunkBufferPEM.clear();
+ clientKeyPEM.clear();
+ } else {
+ qCWarning(lcWebFlowCredentials) << "Unable to read client key" << readJob->errorString();
}
+ readJob->deleteLater();
+
// Start fetching client CA certs
_clientSslCaCertificates.clear();
@@ -609,16 +519,10 @@ void WebFlowCredentials::readSingleClientCaCertPEM()
{
// try to fetch a client ca cert
if (_clientSslCaCertificates.count() < _clientSslCaCertificatesMaxCount) {
- const QString kck = keychainKey(
- _account->url().toString(),
- _user + clientCaCertificatePEMC + QString::number(_clientSslCaCertificates.count()),
- _keychainMigration ? QString() : _account->id());
-
- ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
- addSettingsToJob(_account, job);
- job->setInsecureFallback(false);
- job->setKey(kck);
- connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientCaCertsPEMJobDone);
+ auto *job = new KeychainChunk::ReadJob(_account,
+ _user + clientCaCertificatePEMC + QString::number(_clientSslCaCertificates.count()),
+ _keychainMigration);
+ connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientCaCertsPEMJobDone);
job->start();
} else {
qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while reading, ignoring after" << _clientSslCaCertificatesMaxCount;
@@ -627,10 +531,8 @@ void WebFlowCredentials::readSingleClientCaCertPEM()
}
}
-void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomingJob) {
- // Store key in memory
- ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
-
+void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob *readJob) {
+ // Store cert in memory
if (readJob) {
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
@@ -638,6 +540,8 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin
_clientSslCaCertificates.append(sslCertificateList.at(0));
}
+ readJob->deleteLater();
+
// try next cert
readSingleClientCaCertPEM();
return;
@@ -647,6 +551,8 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin
qCWarning(lcWebFlowCredentials) << "Unable to read client CA cert slot" << QString::number(_clientSslCaCertificates.count()) << readJob->errorString();
}
}
+
+ readJob->deleteLater();
}
// Now fetch the actual server password
@@ -656,7 +562,9 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin
_keychainMigration ? QString() : _account->id());
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
+#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
+#endif
job->setInsecureFallback(false);
job->setKey(kck);
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadPasswordJobDone);
@@ -664,7 +572,7 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin
}
void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
- QKeychain::ReadPasswordJob *job = static_cast<ReadPasswordJob *>(incomingJob);
+ QKeychain::ReadPasswordJob *job = qobject_cast<ReadPasswordJob *>(incomingJob);
QKeychain::Error error = job->error();
// If we could not find the entry try the old entries
@@ -687,6 +595,8 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
}
emit fetched();
+ job->deleteLater();
+
// If keychain data was read from legacy location, wipe these entries and store new ones
if (_keychainMigration && _ready) {
_keychainMigration = false;
@@ -697,13 +607,20 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
}
void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) {
- auto startDeleteJob = [this, oldKeychainEntries](QString user) {
+ auto startDeleteJob = [this, oldKeychainEntries](QString key) {
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
+#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
+#endif
job->setInsecureFallback(false);
job->setKey(keychainKey(_account->url().toString(),
- user,
+ key,
oldKeychainEntries ? QString() : _account->id()));
+
+ connect(job, &Job::finished, this, [](QKeychain::Job *job) {
+ DeletePasswordJob *djob = qobject_cast<DeletePasswordJob *>(job);
+ djob->deleteLater();
+ });
job->start();
};
@@ -728,9 +645,17 @@ void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) {
}
#if defined(Q_OS_WIN)
- // also delete key sub-chunks (Windows workaround)
- for (auto i = 1; i < _clientSslKeyChunkCount; i++) {
- startDeleteJob(_user + clientKeyPEMC + QString(".") + QString::number(i));
+ // Also delete key / cert sub-chunks (Windows workaround)
+ // The first chunk (0) has no suffix, to stay compatible with older versions and non-Windows
+ for (auto chunk = 1; chunk < KeychainChunk::MaxChunks; chunk++) {
+ const QString strChunkSuffix = QString(".") + QString::number(chunk);
+
+ startDeleteJob(_user + clientKeyPEMC + strChunkSuffix);
+ startDeleteJob(_user + clientCertificatePEMC + strChunkSuffix);
+
+ for (auto i = 0; i < _clientSslCaCertificates.count(); i++) {
+ startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i));
+ }
}
#endif
// FIXME MS@2019-12-07 -->
@@ -738,4 +663,4 @@ void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) {
// <-- FIXME MS@2019-12-07
}
-}
+} // namespace OCC
diff --git a/src/gui/creds/webflowcredentials.h b/src/gui/creds/webflowcredentials.h
index 50c392ae6..4b2414b01 100644
--- a/src/gui/creds/webflowcredentials.h
+++ b/src/gui/creds/webflowcredentials.h
@@ -19,6 +19,11 @@ namespace QKeychain {
namespace OCC {
+namespace KeychainChunk {
+ class ReadJob;
+ class WriteJob;
+}
+
class WebFlowCredentialsDialog;
class WebFlowCredentials : public AbstractCredentials
@@ -63,14 +68,14 @@ private slots:
void slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host);
void slotAskFromUserCancelled();
- void slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob);
- void slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob);
- void slotReadClientCaCertsPEMJobDone(QKeychain::Job *incommingJob);
+ void slotReadClientCertPEMJobDone(KeychainChunk::ReadJob *readJob);
+ void slotReadClientKeyPEMJobDone(KeychainChunk::ReadJob *readJob);
+ void slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob *readJob);
void slotReadPasswordJobDone(QKeychain::Job *incomingJob);
- void slotWriteClientCertPEMJobDone();
- void slotWriteClientKeyPEMJobDone();
- void slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob);
+ void slotWriteClientCertPEMJobDone(KeychainChunk::WriteJob *writeJob);
+ void slotWriteClientKeyPEMJobDone(KeychainChunk::WriteJob *writeJob);
+ void slotWriteClientCaCertsPEMJobDone(KeychainChunk::WriteJob *writeJob);
void slotWriteJobDone(QKeychain::Job *);
private:
@@ -92,19 +97,6 @@ private:
static constexpr int _clientSslCaCertificatesMaxCount = 10;
QQueue<QSslCertificate> _clientSslCaCertificatesWriteQueue;
- /*
- * Workaround: ...and this time only on Windows:
- *
- * Split the private key into chunks of 2048 bytes,
- * to allow 4k (4096 bit) keys to be saved (see limits above)
- */
- void writeSingleClientKeyChunkPEM(QKeychain::Job *incomingJob);
-
- static constexpr int _clientSslKeyChunkSize = 2048;
- static constexpr int _clientSslKeyMaxChunks = 10;
- int _clientSslKeyChunkCount = 0;
- QByteArray _clientSslKeyChunkBufferPEM;
-
protected:
/** Reads data from keychain locations
*
@@ -135,6 +127,6 @@ protected:
WebFlowCredentialsDialog *_askDialog;
};
-}
+} // namespace OCC
#endif // WEBFLOWCREDENTIALS_H