CountDehydratedFilesQuery,
SetPinStateQuery,
WipePinStateQuery,
+ SetE2EeLockedFolderQuery,
+ GetE2EeLockedFolderQuery,
+ GetE2EeLockedFoldersQuery,
+ DeleteE2EeLockedFolderQuery,
PreparedQueryCount
};
return sqlFail(QStringLiteral("Create table version"), createQuery);
}
+ // create the e2EeLockedFolders table.
+ createQuery.prepare(
+ "CREATE TABLE IF NOT EXISTS e2EeLockedFolders("
+ "folderId VARCHAR(128) PRIMARY KEY,"
+ "token VARCHAR(4096)"
+ ");");
+ if (!createQuery.exec()) {
+ return sqlFail(QStringLiteral("Create table e2EeLockedFolders"), createQuery);
+ }
+
bool forceRemoteDiscovery = false;
SqlQuery versionQuery("SELECT major, minor, patch FROM version;", _db);
}
}
+void SyncJournalDb::setE2EeLockedFolder(const QByteArray &folderId, const QByteArray &folderToken)
+{
+ QMutexLocker locker(&_mutex);
+ if (!checkConnect()) {
+ return;
+ }
+
+ const auto query = _queryManager.get(PreparedSqlQueryManager::SetE2EeLockedFolderQuery,
+ QByteArrayLiteral("INSERT OR REPLACE INTO e2EeLockedFolders "
+ "(folderId, token) "
+ "VALUES (?1, ?2);"),
+ _db);
+ ASSERT(query)
+ query->bindValue(1, folderId);
+ query->bindValue(2, folderToken);
+ ASSERT(query->exec())
+}
+
+QByteArray SyncJournalDb::e2EeLockedFolder(const QByteArray &folderId)
+{
+ QMutexLocker locker(&_mutex);
+ if (!checkConnect()) {
+ return {};
+ }
+ const auto query = _queryManager.get(PreparedSqlQueryManager::GetE2EeLockedFolderQuery,
+ QByteArrayLiteral("SELECT token FROM e2EeLockedFolders WHERE folderId=?1;"),
+ _db);
+ ASSERT(query)
+ query->bindValue(1, folderId);
+ ASSERT(query->exec())
+ if (!query->next().hasData) {
+ return {};
+ }
+
+ return query->baValue(0);
+}
+
+QList<QPair<QByteArray, QByteArray>> SyncJournalDb::e2EeLockedFolders()
+{
+ QMutexLocker locker(&_mutex);
+
+ QList<QPair<QByteArray, QByteArray>> res;
+
+ if (!checkConnect()) {
+ return res;
+ }
+
+ const auto query = _queryManager.get(PreparedSqlQueryManager::GetE2EeLockedFoldersQuery, QByteArrayLiteral("SELECT * FROM e2EeLockedFolders"), _db);
+ ASSERT(query)
+
+ if (!query->exec()) {
+ return res;
+ }
+
+ while (query->next().hasData) {
+ res.append({query->baValue(0), query->baValue(1)});
+ }
+ return res;
+}
+
+void SyncJournalDb::deleteE2EeLockedFolder(const QByteArray &folderId)
+{
+ QMutexLocker locker(&_mutex);
+ if (!checkConnect()) {
+ return;
+ }
+
+ const auto query = _queryManager.get(PreparedSqlQueryManager::DeleteE2EeLockedFolderQuery, QByteArrayLiteral("DELETE FROM e2EeLockedFolders WHERE folderId=?1;"), _db);
+ ASSERT(query)
+ query->bindValue(1, folderId);
+ ASSERT(query->exec())
+}
+
Optional<PinState> SyncJournalDb::PinStateInterface::rawForPath(const QByteArray &path)
{
QMutexLocker lock(&_db->_mutex);
*/
void markVirtualFileForDownloadRecursively(const QByteArray &path);
+ void setE2EeLockedFolder(const QByteArray &folderId, const QByteArray &folderToken);
+ QByteArray e2EeLockedFolder(const QByteArray &folderId);
+ QList<QPair<QByteArray, QByteArray>> e2EeLockedFolders();
+ void deleteE2EeLockedFolder(const QByteArray &folderId);
+
/** Grouping for all functions relating to pin states,
*
* Use internalPinStates() to get at them.
void AbstractPropagateRemoteDeleteEncrypted::slotTryLock(const QByteArray &folderId)
{
- auto lockJob = new LockEncryptFolderApiJob(_propagator->account(), folderId, this);
+ auto lockJob = new LockEncryptFolderApiJob(_propagator->account(), folderId, _propagator->_journal, _propagator->account()->e2e()->_publicKey, this);
connect(lockJob, &LockEncryptFolderApiJob::success, this, &AbstractPropagateRemoteDeleteEncrypted::slotFolderLockedSuccessfully);
connect(lockJob, &LockEncryptFolderApiJob::error, this, &AbstractPropagateRemoteDeleteEncrypted::taskFailed);
lockJob->start();
}
qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Unlocking folder" << _folderId;
- auto unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(), _folderId, _folderToken, this);
+ auto unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(), _folderId, _folderToken, _propagator->_journal, this);
connect(unlockJob, &UnlockEncryptFolderApiJob::success, this, &AbstractPropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully);
connect(unlockJob, &UnlockEncryptFolderApiJob::error, this, [this] (const QByteArray& fileId, int httpReturnCode) {
return pem;
}
+QByteArray encryptStringAsymmetric(const QSslKey key, const QByteArray &data)
+{
+ Q_ASSERT(!key.isNull());
+ if (key.isNull()) {
+ qCDebug(lcCse) << "Public key is null. Could not encrypt.";
+ return {};
+ }
+ Bio publicKeyBio;
+ const auto publicKeyPem = key.toPem();
+ BIO_write(publicKeyBio, publicKeyPem.constData(), publicKeyPem.size());
+ const auto publicKey = ClientSideEncryption::PKey::readPublicKey(publicKeyBio);
+ return EncryptionHelper::encryptStringAsymmetric(publicKey, data.toBase64());
+}
+
+QByteArray decryptStringAsymmetric(const QByteArray &privateKeyPem, const QByteArray &data)
+{
+ Q_ASSERT(!privateKeyPem.isEmpty());
+ if (privateKeyPem.isEmpty()) {
+ qCDebug(lcCse) << "Private key is empty. Could not encrypt.";
+ return {};
+ }
+
+ Bio privateKeyBio;
+ BIO_write(privateKeyBio, privateKeyPem.constData(), privateKeyPem.size());
+ const auto key = ClientSideEncryption::PKey::readPrivateKey(privateKeyBio);
+
+ // Also base64 decode the result
+ const auto decryptResult = EncryptionHelper::decryptStringAsymmetric(key, QByteArray::fromBase64(data));
+
+ if (decryptResult.isEmpty()) {
+ qCDebug(lcCse()) << "ERROR. Could not decrypt data";
+ return {};
+ }
+ return QByteArray::fromBase64(decryptResult);
+}
+
QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data) {
QByteArray iv = generateRandom(16);
const QByteArray& key,
const QByteArray& data
);
+ OWNCLOUDSYNC_EXPORT QByteArray encryptStringAsymmetric(const QSslKey key, const QByteArray &data);
+ OWNCLOUDSYNC_EXPORT QByteArray decryptStringAsymmetric(const QByteArray &privateKeyPem, const QByteArray &data);
QByteArray privateKeyToPem(const QByteArray key);
#include "clientsideencryptionjobs.h"
#include "theme.h"
#include "creds/abstractcredentials.h"
+#include "common/syncjournaldb.h"
Q_LOGGING_CATEGORY(lcSignPublicKeyApiJob, "nextcloud.sync.networkjob.sendcsr", QtInfoMsg)
Q_LOGGING_CATEGORY(lcStorePrivateKeyApiJob, "nextcloud.sync.networkjob.storeprivatekey", QtInfoMsg)
UnlockEncryptFolderApiJob::UnlockEncryptFolderApiJob(const AccountPtr& account,
const QByteArray& fileId,
const QByteArray& token,
+ SyncJournalDb *journalDb,
QObject* parent)
-: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId), _token(token)
+ : AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("lock/") + fileId, parent)
+ , _fileId(fileId)
+ , _token(token)
+ , _journalDb(journalDb)
{
}
AbstractNetworkJob::start();
qCInfo(lcCseJob()) << "Starting the request to unlock.";
+
+ qCInfo(lcCseJob()) << "unlock folder started for:" << path() << " for fileId: " << _fileId;
}
bool UnlockEncryptFolderApiJob::finished()
{
int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+ qCInfo(lcCseJob()) << "unlock folder finished with code" << retCode << " for:" << path() << " for fileId: " << _fileId;
+
+ if (retCode != 0) {
+ _journalDb->deleteE2EeLockedFolder(_fileId);
+ }
+
+ emit done();
+
if (retCode != 200) {
qCInfo(lcCseJob()) << "error unlocking file" << path() << errorString() << retCode;
qCInfo(lcCseJob()) << "Full Error Log" << reply()->readAll();
return true;
}
-LockEncryptFolderApiJob::LockEncryptFolderApiJob(const AccountPtr& account, const QByteArray& fileId, QObject* parent)
-: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId)
+LockEncryptFolderApiJob::LockEncryptFolderApiJob(const AccountPtr &account,
+ const QByteArray &fileId,
+ SyncJournalDb *journalDb,
+ const QSslKey publicKey,
+ QObject *parent)
+ : AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("lock/") + fileId, parent)
+ , _fileId(fileId)
+ , _journalDb(journalDb)
+ , _publicKey(publicKey)
{
}
void LockEncryptFolderApiJob::start()
{
+ const auto folderTokenEncrypted = _journalDb->e2EeLockedFolder(_fileId);
+
+ if (!folderTokenEncrypted.isEmpty()) {
+ qCInfo(lcCseJob()) << "lock folder started for:" << path() << " for fileId: " << _fileId << " but we need to first lift the previous lock";
+ const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->_privateKey, folderTokenEncrypted);
+ const auto unlockJob = new OCC::UnlockEncryptFolderApiJob(_account, _fileId, folderToken, _journalDb, this);
+ connect(unlockJob, &UnlockEncryptFolderApiJob::done, this, [this]() {
+ this->start();
+ });
+ unlockJob->start();
+ return;
+ }
+
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
QUrlQuery query;
qCInfo(lcCseJob()) << "locking the folder with id" << _fileId << "as encrypted";
sendRequest("POST", url, req);
AbstractNetworkJob::start();
+
+ qCInfo(lcCseJob()) << "lock folder started for:" << path() << " for fileId: " << _fileId;
}
bool LockEncryptFolderApiJob::finished()
{
int retCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
if (retCode != 200) {
qCInfo(lcCseJob()) << "error locking file" << path() << errorString() << retCode;
emit error(_fileId, retCode, errorString());
+ qCInfo(lcCseJob()) << "lock folder finished with code" << retCode << " for:" << path() << " for fileId: " << _fileId;
return true;
}
QJsonParseError error;
- auto json = QJsonDocument::fromJson(reply()->readAll(), &error);
- auto obj = json.object().toVariantMap();
- auto token = obj["ocs"].toMap()["data"].toMap()["e2e-token"].toByteArray();
+ const auto json = QJsonDocument::fromJson(reply()->readAll(), &error);
+ const auto obj = json.object().toVariantMap();
+ const auto token = obj["ocs"].toMap()["data"].toMap()["e2e-token"].toByteArray();
qCInfo(lcCseJob()) << "got json:" << token;
+ qCInfo(lcCseJob()) << "lock folder finished with code" << retCode << " for:" << path() << " for fileId: " << _fileId << " token:" << token;
+
+ const auto folderTokenEncrypted = EncryptionHelper::encryptStringAsymmetric(_publicKey, token);
+ _journalDb->setE2EeLockedFolder(_fileId, folderTokenEncrypted);
+
//TODO: Parse the token and submit.
emit success(_fileId, token);
return true;
#include "accountfwd.h"
#include <QString>
#include <QJsonDocument>
+#include <QSslKey>
namespace OCC {
/* Here are all of the network jobs for the client side encryption.
*
* @ingroup libsync
*/
+
+class SyncJournalDb;
class OWNCLOUDSYNC_EXPORT SignPublicKeyApiJob : public AbstractNetworkJob
{
Q_OBJECT
{
Q_OBJECT
public:
- explicit LockEncryptFolderApiJob(const AccountPtr &account, const QByteArray& fileId, QObject *parent = nullptr);
+ explicit LockEncryptFolderApiJob(const AccountPtr &account, const QByteArray &fileId, SyncJournalDb *journalDb, const QSslKey publicKey, QObject *parent = nullptr);
public slots:
void start() override;
private:
QByteArray _fileId;
+ QPointer<SyncJournalDb> _journalDb;
+ QSslKey _publicKey;
};
const AccountPtr &account,
const QByteArray& fileId,
const QByteArray& token,
+ SyncJournalDb *journalDb,
QObject *parent = nullptr);
public slots:
void error(const QByteArray& fileId,
const int httpReturnCode,
const QString &errorMessage);
+ void done();
private:
QByteArray _fileId;
QByteArray _token;
QBuffer *_tokenBuf;
+ QPointer<SyncJournalDb> _journalDb;
};
qCWarning(lcEncryptFolderJob) << "Error when setting the file record to the database" << rec._path << result.error();
}
- auto lockJob = new LockEncryptFolderApiJob(_account, fileId, this);
+ const auto lockJob = new LockEncryptFolderApiJob(_account, fileId, _journal, _account->e2e()->_publicKey, this);
connect(lockJob, &LockEncryptFolderApiJob::success,
this, &EncryptFolderJob::slotLockForEncryptionSuccess);
connect(lockJob, &LockEncryptFolderApiJob::error,
void EncryptFolderJob::slotUploadMetadataSuccess(const QByteArray &folderId)
{
- auto unlockJob = new UnlockEncryptFolderApiJob(_account, folderId, _folderToken, this);
+ auto unlockJob = new UnlockEncryptFolderApiJob(_account, folderId, _folderToken, _journal, this);
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
this, &EncryptFolderJob::slotUnlockFolderSuccess);
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
{
Q_UNUSED(httpReturnCode);
- auto unlockJob = new UnlockEncryptFolderApiJob(_account, folderId, _folderToken, this);
+ const auto unlockJob = new UnlockEncryptFolderApiJob(_account, folderId, _folderToken, _journal, this);
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
this, &EncryptFolderJob::slotUnlockFolderSuccess);
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
void PropagateUploadEncrypted::slotTryLock(const QByteArray& fileId)
{
- auto *lockJob = new LockEncryptFolderApiJob(_propagator->account(), fileId, this);
+ const auto lockJob = new LockEncryptFolderApiJob(_propagator->account(), fileId, _propagator->_journal, _propagator->account()->e2e()->_publicKey, this);
connect(lockJob, &LockEncryptFolderApiJob::success, this, &PropagateUploadEncrypted::slotFolderLockedSuccessfully);
connect(lockJob, &LockEncryptFolderApiJob::error, this, &PropagateUploadEncrypted::slotFolderLockedError);
lockJob->start();
_isUnlockRunning = true;
qDebug() << "Calling Unlock";
- auto *unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(),
- _folderId, _folderToken, this);
+ auto *unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(), _folderId, _folderToken, _propagator->_journal, this);
connect(unlockJob, &UnlockEncryptFolderApiJob::success, [this](const QByteArray &folderId) {
qDebug() << "Successfully Unlocked";
#include "configfile.h"
#include "discovery.h"
#include "common/vfs.h"
+#include "clientsideencryption.h"
+#include "clientsideencryptionjobs.h"
#ifdef Q_OS_WIN
#include <windows.h>
job->start();
return;
}
+
+ const auto e2EeLockedFolders = _journal->e2EeLockedFolders();
+
+ if (!e2EeLockedFolders.isEmpty()) {
+ for (const auto &e2EeLockedFolder : e2EeLockedFolders) {
+ const auto folderId = e2EeLockedFolder.first;
+ qCInfo(lcEngine()) << "start unlock job for folderId:" << folderId;
+ const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->_privateKey, e2EeLockedFolder.second);
+ const auto unlockJob = new OCC::UnlockEncryptFolderApiJob(_account, folderId, folderToken, _journal, this);
+ unlockJob->start();
+ }
+ }
}
if (s_anySyncRunning || _syncRunning) {