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:
-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