Move KeychainChunk class from gui to libsync
authorMichael Schuster <michael@schuster.ms>
Wed, 24 Jun 2020 23:08:59 +0000 (01:08 +0200)
committerMichael Schuster <michael@schuster.ms>
Mon, 6 Jul 2020 19:51:39 +0000 (21:51 +0200)
Signed-off-by: Michael Schuster <michael@schuster.ms>
src/gui/CMakeLists.txt
src/gui/creds/keychainchunk.cpp [deleted file]
src/gui/creds/keychainchunk.h [deleted file]
src/gui/creds/webflowcredentials.cpp
src/libsync/CMakeLists.txt
src/libsync/creds/keychainchunk.cpp [new file with mode: 0644]
src/libsync/creds/keychainchunk.h [new file with mode: 0644]

index b83106441d308311e97d797c4a6badd258bebcc5..57c876c33ca2cb81f7102878fef23fe1a7d3842d 100644 (file)
@@ -112,7 +112,6 @@ 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
deleted file mode 100644 (file)
index 836be0b..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * 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)
-{
-    auto *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());
-
-        auto *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());
-
-    auto *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?
-    auto *readJob = static_cast<QKeychain::ReadPasswordJob *>(incomingJob);
-
-    if (readJob) {
-        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 {
-#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
-            if (!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(lcKeychainChunk) << "Backend unavailable (yet?) Retrying in a few seconds." << readJob->errorString();
-                    QTimer::singleShot(10000, this, &ReadJob::start);
-                    _retryOnKeyChainError = false;
-                    readJob->deleteLater();
-                    return;
-                }
-                _retryOnKeyChainError = false;
-            }
-#endif
-
-            if (readJob->error() != QKeychain::EntryNotFound ||
-                ((readJob->error() == QKeychain::EntryNotFound) && _chunkCount == 0)) {
-                _error = readJob->error();
-                _errorString = readJob->errorString();
-                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
deleted file mode 100644 (file)
index d1ae1e9..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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);
-
-#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
-private:
-    bool _retryOnKeyChainError = true; // true if we haven't done yet any reading from keychain
-#endif
-}; // class ReadJob
-
-} // namespace KeychainChunk
-
-} // namespace OCC
-
-#endif // KEYCHAINCHUNK_H
index ff5c2eb2abe3405b5a6dcda63feab14d1a6aaa5f..e6c1f141a3fe460bfb35d6e063c3128f9193a6bf 100644 (file)
@@ -1,13 +1,13 @@
 #include "webflowcredentials.h"
 
 #include "creds/httpcredentials.h"
+#include "creds/keychainchunk.h"
 
 #include <QAuthenticator>
 #include <QNetworkAccessManager>
 #include <QNetworkReply>
 #include <QPointer>
 #include <QTimer>
-#include <keychain.h>
 #include <QDialog>
 #include <QVBoxLayout>
 #include <QLabel>
@@ -18,7 +18,6 @@
 #include "theme.h"
 #include "wizard/webview.h"
 #include "webflowcredentialsdialog.h"
-#include "keychainchunk.h"
 
 using namespace QKeychain;
 
index 146e0910ab1b014f4b4972ce356303f4659b86c2..aef76d246b2cd14f7e696ec9607883cdea5931ac 100644 (file)
@@ -59,6 +59,7 @@ set(libsync_SRCS
     creds/dummycredentials.cpp
     creds/abstractcredentials.cpp
     creds/credentialscommon.cpp
+    creds/keychainchunk.cpp
 )
 
 if(TOKEN_AUTH_ONLY)
diff --git a/src/libsync/creds/keychainchunk.cpp b/src/libsync/creds/keychainchunk.cpp
new file mode 100644 (file)
index 0000000..836be0b
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * 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)
+{
+    auto *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());
+
+        auto *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());
+
+    auto *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?
+    auto *readJob = static_cast<QKeychain::ReadPasswordJob *>(incomingJob);
+
+    if (readJob) {
+        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 {
+#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
+            if (!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(lcKeychainChunk) << "Backend unavailable (yet?) Retrying in a few seconds." << readJob->errorString();
+                    QTimer::singleShot(10000, this, &ReadJob::start);
+                    _retryOnKeyChainError = false;
+                    readJob->deleteLater();
+                    return;
+                }
+                _retryOnKeyChainError = false;
+            }
+#endif
+
+            if (readJob->error() != QKeychain::EntryNotFound ||
+                ((readJob->error() == QKeychain::EntryNotFound) && _chunkCount == 0)) {
+                _error = readJob->error();
+                _errorString = readJob->errorString();
+                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/libsync/creds/keychainchunk.h b/src/libsync/creds/keychainchunk.h
new file mode 100644 (file)
index 0000000..d1d207b
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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 OWNCLOUDSYNC_EXPORT 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 OWNCLOUDSYNC_EXPORT 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);
+
+#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
+private:
+    bool _retryOnKeyChainError = true; // true if we haven't done yet any reading from keychain
+#endif
+}; // class ReadJob
+
+} // namespace KeychainChunk
+
+} // namespace OCC
+
+#endif // KEYCHAINCHUNK_H