From 35c5ddee3c401c8afc643071301fe2d667613f51 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Fri, 8 Dec 2023 15:10:11 +0100 Subject: [PATCH] migrate of encrypted metadata from a previous to the current certificate search for a previous certificate matching the certifcate having been used when encoding the metadata to recover them and next time we need to encrypt the metadata, the current selected certificate is going to be used Signed-off-by: Matthieu Gallien --- src/libsync/clientsideencryption.cpp | 77 +++++++++++++++--------- src/libsync/clientsideencryption.h | 10 ++- src/libsync/clientsideencryptionjobs.cpp | 4 +- src/libsync/syncengine.cpp | 2 +- 4 files changed, 57 insertions(+), 36 deletions(-) diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp index e56367c6ff108..ee39e2ffea3f5 100644 --- a/src/libsync/clientsideencryption.cpp +++ b/src/libsync/clientsideencryption.cpp @@ -678,11 +678,13 @@ namespace internals { } -std::optional encryptStringAsymmetric(const ClientSideEncryption &encryptionEngine, const QByteArray &binaryData) +std::optional encryptStringAsymmetric(const CertificateInformation &selectedCertificate, + const ClientSideEncryption &encryptionEngine, + const QByteArray &binaryData) { if (encryptionEngine.useTokenBasedEncryption()) { auto encryptedBase64Result = internals::encryptStringAsymmetricWithToken(encryptionEngine.sslEngine(), - encryptionEngine.getTokenCertificate().getPublicKey(), + selectedCertificate.getPublicKey(), binaryData); if (!encryptedBase64Result) { @@ -724,9 +726,10 @@ std::optional encryptStringAsymmetric(const ClientSideEncryption &en } } -std::optional decryptStringAsymmetric(const QByteArray &certificateSha256Fingerprint, +std::optional decryptStringAsymmetric(const CertificateInformation &selectedCertificate, const ClientSideEncryption &encryptionEngine, - const QByteArray &base64Data) + const QByteArray &base64Data, + const QByteArray &expectedCertificateSha256Fingerprint) { if (!encryptionEngine.isInitialized()) { qCWarning(lcCse()) << "end-to-end encryption is disabled"; @@ -734,13 +737,13 @@ std::optional decryptStringAsymmetric(const QByteArray &certificateS } if (encryptionEngine.useTokenBasedEncryption()) { - if (encryptionEngine.getTokenCertificate().sha256Fingerprint() != certificateSha256Fingerprint) { - qCWarning(lcCse()) << "wrong certificate: cannot decrypt what has been encrypted with another certificate:" << certificateSha256Fingerprint << "current certificate" << encryptionEngine.getTokenCertificate().sha256Fingerprint(); + if (selectedCertificate.sha256Fingerprint() != expectedCertificateSha256Fingerprint) { + qCWarning(lcCse()) << "wrong certificate: cannot decrypt what has been encrypted with another certificate:" << expectedCertificateSha256Fingerprint << "current certificate" << selectedCertificate.sha256Fingerprint(); return {}; } const auto decryptBase64Result = internals::decryptStringAsymmetricWithToken(encryptionEngine.sslEngine(), - encryptionEngine.getTokenCertificate().getPrivateKey(), + selectedCertificate.getPrivateKey(), QByteArray::fromBase64(base64Data)); if (!decryptBase64Result) { qCWarning(lcCse()) << "decrypt failed"; @@ -1030,6 +1033,26 @@ const CertificateInformation &ClientSideEncryption::getTokenCertificate() const return _encryptionCertificate; } +CertificateInformation ClientSideEncryption::getTokenCertificateByFingerprint(const QByteArray &expectedFingerprint) const +{ + CertificateInformation result; + + if (_encryptionCertificate.sha256Fingerprint() == expectedFingerprint) { + result = _encryptionCertificate; + return result; + } + + const auto itCertificate = std::find_if(_otherCertificates.begin(), _otherCertificates.end(), [expectedFingerprint] (const auto &oneCertificate) { + return oneCertificate.sha256Fingerprint() == expectedFingerprint; + }); + if (itCertificate != _otherCertificates.end()) { + result = *itCertificate; + return result; + } + + return result; +} + bool ClientSideEncryption::useTokenBasedEncryption() const { return _encryptionCertificate.getPublicKey() && _encryptionCertificate.getPrivateKey(); @@ -1343,7 +1366,7 @@ bool ClientSideEncryption::checkPublicKeyValidity(const AccountPtr &account) con BIO_write(publicKeyBio, publicKeyPem.constData(), publicKeyPem.size()); auto publicKey = PKey::readPublicKey(publicKeyBio); - auto encryptedData = EncryptionHelper::encryptStringAsymmetric(*account->e2e(), data.toBase64()); + auto encryptedData = EncryptionHelper::encryptStringAsymmetric(account->e2e()->getTokenCertificate(), *account->e2e(), data.toBase64()); if (!encryptedData) { qCWarning(lcCse()) << "encryption error"; return false; @@ -1354,7 +1377,7 @@ bool ClientSideEncryption::checkPublicKeyValidity(const AccountPtr &account) con BIO_write(privateKeyBio, privateKeyPem.constData(), privateKeyPem.size()); auto key = PKey::readPrivateKey(privateKeyBio); - const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(account->e2e()->certificateSha256Fingerprint(), *account->e2e(), *encryptedData); + const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(account->e2e()->getTokenCertificate(), *account->e2e(), *encryptedData, account->e2e()->certificateSha256Fingerprint()); if (!decryptionResult) { qCWarning(lcCse()) << "encryption error"; return false; @@ -1373,13 +1396,13 @@ bool ClientSideEncryption::checkEncryptionIsWorking() const { QByteArray data = EncryptionHelper::generateRandom(64); - auto encryptedData = EncryptionHelper::encryptStringAsymmetric(*this, data); + auto encryptedData = EncryptionHelper::encryptStringAsymmetric(getTokenCertificate(), *this, data); if (!encryptedData) { qCWarning(lcCse()) << "encryption error"; return false; } - const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(certificateSha256Fingerprint(), *this, *encryptedData); + const auto decryptionResult = EncryptionHelper::decryptStringAsymmetric(getTokenCertificate(), *this, *encryptedData, getTokenCertificate().sha256Fingerprint()); if (!decryptionResult) { qCWarning(lcCse()) << "encryption error"; return false; @@ -2249,14 +2272,21 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata) // And because inside of the meta-data there's an object called metadata, without '-' // make it really different. - QString metaDataStr = doc.object()["ocs"] + const auto &metaDataStr = doc.object()["ocs"] .toObject()["data"] .toObject()["meta-data"] .toString(); - QJsonDocument metaDataDoc = QJsonDocument::fromJson(metaDataStr.toLocal8Bit()); - QJsonObject metadataObj = metaDataDoc.object()["metadata"].toObject(); - QJsonObject metadataKeys = metadataObj["metadataKeys"].toObject(); + const auto &metaDataDoc = QJsonDocument::fromJson(metaDataStr.toLocal8Bit()); + const auto &metadataObj = metaDataDoc.object()["metadata"].toObject(); + const auto &metadataKeys = metadataObj["metadataKeys"].toObject(); + + _metadataCertificateSha256Fingerprint = metadataObj[certificateSha256FingerprintKey].toString().toLatin1(); + + if (_metadataCertificateSha256Fingerprint.isEmpty() && _account->e2e()->useTokenBasedEncryption()) { + qCWarning(lcCseMetadata()) << "e2ee metadata are missing proper information about the certificate used to encrypt them"; + return; + } const auto metadataKeyFromJson = metadataObj[metadataKeyJsonKey].toString().toLocal8Bit(); if (!metadataKeyFromJson.isEmpty()) { @@ -2295,19 +2325,6 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata) const auto files = metaDataDoc.object()["files"].toObject(); const auto metadataKey = metaDataDoc.object()["metadata"].toObject()["metadataKey"].toString().toUtf8(); const auto metadataKeyChecksum = metaDataDoc.object()["metadata"].toObject()["checksum"].toString().toUtf8(); - _metadataCertificateSha256Fingerprint = metadataObj[certificateSha256FingerprintKey].toString().toLatin1(); - - if (!_metadataCertificateSha256Fingerprint.isEmpty()) { - if (!_account->e2e()->useTokenBasedEncryption()) { - qCWarning(lcCseMetadata()) << "e2ee metadata are missing proper information about the certificate used to encrypt them"; - return; - } - - if (_metadataCertificateSha256Fingerprint != _account->e2e()->usbTokenInformation()->sha256Fingerprint()) { - qCInfo(lcCseMetadata()) << "migration of the certificate used to encrypt metadata is needed"; - return; - } - } _fileDrop = metaDataDoc.object().value("filedrop").toObject(); // for unit tests @@ -2391,7 +2408,7 @@ void FolderMetadata::setupExistingMetadata(const QByteArray& metadata) // RSA/ECB/OAEPWithSHA-256AndMGF1Padding using private / public key. std::optional FolderMetadata::encryptData(const QByteArray& binaryDatadata) const { - const auto encryptBase64Result = EncryptionHelper::encryptStringAsymmetric(*_account->e2e(), binaryDatadata); + const auto encryptBase64Result = EncryptionHelper::encryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), binaryDatadata); if (!encryptBase64Result || encryptBase64Result->isEmpty()) { @@ -2404,7 +2421,7 @@ std::optional FolderMetadata::encryptData(const QByteArray& binaryDa std::optional FolderMetadata::decryptData(const QByteArray &base64Data) const { - const auto decryptBase64Result = EncryptionHelper::decryptStringAsymmetric(certificateSha256Fingerprint(), *_account->e2e(), base64Data); + const auto decryptBase64Result = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getTokenCertificateByFingerprint(_metadataCertificateSha256Fingerprint), *_account->e2e(), base64Data, _metadataCertificateSha256Fingerprint); if (!decryptBase64Result || decryptBase64Result->isEmpty()) { diff --git a/src/libsync/clientsideencryption.h b/src/libsync/clientsideencryption.h index 908b0019624ca..02cd33250ef03 100644 --- a/src/libsync/clientsideencryption.h +++ b/src/libsync/clientsideencryption.h @@ -121,12 +121,14 @@ OWNCLOUDSYNC_EXPORT QByteArray decryptStringSymmetric( const QByteArray& data ); -[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional encryptStringAsymmetric(const ClientSideEncryption &encryptionEngine, +[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional encryptStringAsymmetric(const CertificateInformation &selectedCertificate, + const ClientSideEncryption &encryptionEngine, const QByteArray &binaryData); -[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional decryptStringAsymmetric(const QByteArray &certificateSha256Fingerprint, +[[nodiscard]] OWNCLOUDSYNC_EXPORT std::optional decryptStringAsymmetric(const CertificateInformation &selectedCertificate, const ClientSideEncryption &encryptionEngine, - const QByteArray &base64Data); + const QByteArray &base64Data, + const QByteArray &expectedCertificateSha256Fingerprint); QByteArray privateKeyToPem(const QByteArray key); @@ -208,6 +210,8 @@ class OWNCLOUDSYNC_EXPORT ClientSideEncryption : public QObject { [[nodiscard]] const CertificateInformation& getTokenCertificate() const; + [[nodiscard]] CertificateInformation getTokenCertificateByFingerprint(const QByteArray &expectedFingerprint) const; + [[nodiscard]] bool useTokenBasedEncryption() const; [[nodiscard]] const QString &getMnemonic() const; diff --git a/src/libsync/clientsideencryptionjobs.cpp b/src/libsync/clientsideencryptionjobs.cpp index ae5c4e2d8f1da..ffd68af9123a0 100644 --- a/src/libsync/clientsideencryptionjobs.cpp +++ b/src/libsync/clientsideencryptionjobs.cpp @@ -253,7 +253,7 @@ void LockEncryptFolderApiJob::start() 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(_certificateSha256Fingerprint, *_account->e2e(), folderTokenEncrypted); + const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), folderTokenEncrypted, _certificateSha256Fingerprint); if (!folderToken) { qCWarning(lcCseJob()) << "decrypt failed"; return; @@ -300,7 +300,7 @@ bool LockEncryptFolderApiJob::finished() qCInfo(lcCseJob()) << "lock folder finished with code" << retCode << " for:" << path() << " for fileId: " << _fileId << " token:" << token; if (!_account->e2e()->getPublicKey().isNull()) { - const auto folderTokenEncrypted = EncryptionHelper::encryptStringAsymmetric(*_account->e2e(), token); + const auto folderTokenEncrypted = EncryptionHelper::encryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), token); if (!folderTokenEncrypted) { qCWarning(lcCseJob()) << "decrypt failed"; return false; diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 027185d9943df..6059b4b67e413 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -508,7 +508,7 @@ void SyncEngine::startSync() 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()->certificateSha256Fingerprint(), *_account->e2e(), e2EeLockedFolder.second); + const auto folderToken = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getTokenCertificate(), *_account->e2e(), e2EeLockedFolder.second, _account->e2e()->certificateSha256Fingerprint()); if (!folderToken) { qCWarning(lcEngine()) << "decrypt failed"; return;