From 75dd93ada14e0a3ccf2e8b81552a215f96b6a459 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Tue, 3 Mar 2026 13:58:44 +0100 Subject: [PATCH 1/5] chore(e2ee): modernize casts to use c++ cast style Signed-off-by: Matthieu Gallien --- src/libsync/clientsideencryption.cpp | 40 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp index 9a1d37f5d97a7..701c345644a23 100644 --- a/src/libsync/clientsideencryption.cpp +++ b/src/libsync/clientsideencryption.cpp @@ -340,7 +340,7 @@ QByteArray encryptPrivateKey( } /* Initialise key and IV */ - if(!EVP_EncryptInit_ex(ctx, nullptr, nullptr, (unsigned char *)key.constData(), (unsigned char *)iv.constData())) { + if(!EVP_EncryptInit_ex(ctx, nullptr, nullptr, reinterpret_cast(key.constData()), reinterpret_cast(iv.constData()))) { qCInfo(lcCse()) << "Error initialising key and iv" << handleErrors(); } @@ -352,7 +352,7 @@ QByteArray encryptPrivateKey( // Do the actual encryption int len = 0; - if(!EVP_EncryptUpdate(ctx, unsignedData(ctext), &len, (unsigned char *)privateKeyB64.constData(), privateKeyB64.size())) { + if(!EVP_EncryptUpdate(ctx, unsignedData(ctext), &len, reinterpret_cast(privateKeyB64.constData()), privateKeyB64.size())) { qCInfo(lcCse()) << "Error encrypting" << handleErrors(); } @@ -430,7 +430,7 @@ QByteArray decryptPrivateKey(const QByteArray& key, const QByteArray& data) { } /* Initialise key and IV */ - if(!EVP_DecryptInit_ex(ctx, nullptr, nullptr, (unsigned char *)key.constData(), (unsigned char *)iv.constData())) { + if(!EVP_DecryptInit_ex(ctx, nullptr, nullptr, reinterpret_cast(key.constData()), reinterpret_cast(iv.constData()))) { qCInfo(lcCse()) << "Error initialising key and iv"; return QByteArray(); } @@ -441,13 +441,13 @@ QByteArray decryptPrivateKey(const QByteArray& key, const QByteArray& data) { /* Provide the message to be decrypted, and obtain the plaintext output. * EVP_DecryptUpdate can be called multiple times if necessary */ - if(!EVP_DecryptUpdate(ctx, unsignedData(ptext), &plen, (unsigned char *)cipherTXT.constData(), cipherTXT.size())) { + if(!EVP_DecryptUpdate(ctx, unsignedData(ptext), &plen, reinterpret_cast(cipherTXT.constData()), cipherTXT.size())) { qCInfo(lcCse()) << "Could not decrypt"; return QByteArray(); } /* Set expected e2EeTag value. Works in OpenSSL 1.0.1d and later */ - if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, e2EeTag.size(), (unsigned char *)e2EeTag.constData())) { + if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, e2EeTag.size(), const_cast(reinterpret_cast(e2EeTag.constData())))) { qCInfo(lcCse()) << "Could not set e2EeTag"; return QByteArray(); } @@ -520,7 +520,7 @@ QByteArray decryptStringSymmetric(const QByteArray& key, const QByteArray& data) } /* Initialise key and IV */ - if(!EVP_DecryptInit_ex(ctx, nullptr, nullptr, (unsigned char *)key.constData(), (unsigned char *)iv.constData())) { + if(!EVP_DecryptInit_ex(ctx, nullptr, nullptr, reinterpret_cast(key.constData()), reinterpret_cast(iv.constData()))) { qCInfo(lcCse()) << "Error initialising key and iv"; return QByteArray(); } @@ -531,13 +531,13 @@ QByteArray decryptStringSymmetric(const QByteArray& key, const QByteArray& data) /* Provide the message to be decrypted, and obtain the plaintext output. * EVP_DecryptUpdate can be called multiple times if necessary */ - if(!EVP_DecryptUpdate(ctx, unsignedData(ptext), &plen, (unsigned char *)cipherTXT.constData(), cipherTXT.size())) { + if(!EVP_DecryptUpdate(ctx, unsignedData(ptext), &plen, reinterpret_cast(cipherTXT.constData()), cipherTXT.size())) { qCInfo(lcCse()) << "Could not decrypt"; return QByteArray(); } /* Set expected e2EeTag value. Works in OpenSSL 1.0.1d and later */ - if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, e2EeTag.size(), (unsigned char *)e2EeTag.constData())) { + if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, e2EeTag.size(), const_cast(reinterpret_cast(e2EeTag.constData())))) { qCInfo(lcCse()) << "Could not set e2EeTag"; return QByteArray(); } @@ -706,7 +706,7 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data) } /* Initialise key and IV */ - if(!EVP_EncryptInit_ex(ctx, nullptr, nullptr, (unsigned char *)key.constData(), (unsigned char *)iv.constData())) { + if(!EVP_EncryptInit_ex(ctx, nullptr, nullptr, reinterpret_cast(key.constData()), reinterpret_cast(iv.constData()))) { qCInfo(lcCse()) << "Error initialising key and iv" << handleErrors(); return {}; } @@ -719,7 +719,7 @@ QByteArray encryptStringSymmetric(const QByteArray& key, const QByteArray& data) // Do the actual encryption int len = 0; - if(!EVP_EncryptUpdate(ctx, unsignedData(ctext), &len, (unsigned char *)dataB64.constData(), dataB64.size())) { + if(!EVP_EncryptUpdate(ctx, unsignedData(ctext), &len, reinterpret_cast(dataB64.constData()), dataB64.size())) { qCInfo(lcCse()) << "Error encrypting" << handleErrors(); return {}; } @@ -792,7 +792,7 @@ OCC::Result decryptS } size_t outlen = 0; - err = EVP_PKEY_decrypt(ctx, nullptr, &outlen, (unsigned char *)binaryData.constData(), binaryData.size()); + err = EVP_PKEY_decrypt(ctx, nullptr, &outlen, reinterpret_cast(binaryData.constData()), binaryData.size()); if (err <= 0) { qCInfo(lcCseDecryption()) << "Could not determine the buffer length" << handleErrors(); return {OCC::ClientSideEncryption::EncryptionErrorType::FatalError}; @@ -800,7 +800,7 @@ OCC::Result decryptS QByteArray out(static_cast(outlen), '\0'); - if (EVP_PKEY_decrypt(ctx, unsignedData(out), &outlen, (unsigned char *)binaryData.constData(), binaryData.size()) <= 0) { + if (EVP_PKEY_decrypt(ctx, unsignedData(out), &outlen, reinterpret_cast(binaryData.constData()), binaryData.size()) <= 0) { const auto error = handleErrors(); if (ClientSideEncryption::checkEncryptionErrorForHardwareTokenResetState(error)) { return {OCC::ClientSideEncryption::EncryptionErrorType::RetryOnError}; @@ -846,7 +846,7 @@ OCC::Result encryptString } size_t outLen = 0; - if (EVP_PKEY_encrypt(ctx, nullptr, &outLen, (unsigned char *)binaryData.constData(), binaryData.size()) != 1) { + if (EVP_PKEY_encrypt(ctx, nullptr, &outLen, reinterpret_cast(binaryData.constData()), binaryData.size()) != 1) { const auto error = handleErrors(); if (ClientSideEncryption::checkEncryptionErrorForHardwareTokenResetState(error)) { encryptionEngine.initializeHardwareTokenEncryption(nullptr); @@ -857,7 +857,7 @@ OCC::Result encryptString } QByteArray out(static_cast(outLen), '\0'); - if (EVP_PKEY_encrypt(ctx, unsignedData(out), &outLen, (unsigned char *)binaryData.constData(), binaryData.size()) != 1) { + if (EVP_PKEY_encrypt(ctx, unsignedData(out), &outLen, reinterpret_cast(binaryData.constData()), binaryData.size()) != 1) { const auto error = handleErrors(); if (ClientSideEncryption::checkEncryptionErrorForHardwareTokenResetState(error)) { encryptionEngine.initializeHardwareTokenEncryption(nullptr); @@ -2455,7 +2455,7 @@ bool EncryptionHelper::fileEncryption(const QByteArray &key, const QByteArray &i return false; } - if(!EVP_EncryptUpdate(ctx, unsignedData(out), &len, (unsigned char *)data.constData(), data.size())) { + if(!EVP_EncryptUpdate(ctx, unsignedData(out), &len, reinterpret_cast(data.constData()), data.size())) { qCInfo(lcCse()) << "Could not encrypt"; return false; } @@ -2539,7 +2539,7 @@ bool EncryptionHelper::fileDecryption(const QByteArray &key, const QByteArray& i return false; } - if(!EVP_DecryptUpdate(ctx, unsignedData(out), &len, (unsigned char *)data.constData(), data.size())) { + if(!EVP_DecryptUpdate(ctx, unsignedData(out), &len, reinterpret_cast(data.constData()), data.size())) { qCInfo(lcCse()) << "Could not decrypt"; return false; } @@ -2550,7 +2550,7 @@ bool EncryptionHelper::fileDecryption(const QByteArray &key, const QByteArray& i const QByteArray e2EeTag = input->read(OCC::Constants::e2EeTagSize); /* Set expected e2EeTag value. Works in OpenSSL 1.0.1d and later */ - if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, e2EeTag.size(), (unsigned char *)e2EeTag.constData())) { + if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, e2EeTag.size(), const_cast(reinterpret_cast(e2EeTag.constData())))) { qCInfo(lcCse()) << "Could not set expected e2EeTag"; return false; } @@ -2626,7 +2626,7 @@ bool EncryptionHelper::dataEncryption(const QByteArray &key, const QByteArray &i return false; } - if (!EVP_EncryptUpdate(ctx, unsignedData(out), &len, (unsigned char *)data.constData(), data.size())) { + if (!EVP_EncryptUpdate(ctx, unsignedData(out), &len, reinterpret_cast(data.constData()), data.size())) { qCInfo(lcCse()) << "Could not encrypt"; return false; } @@ -2721,7 +2721,7 @@ bool EncryptionHelper::dataDecryption(const QByteArray &key, const QByteArray &i return false; } - if (!EVP_DecryptUpdate(ctx, unsignedData(out), &len, (unsigned char *)data.constData(), data.size())) { + if (!EVP_DecryptUpdate(ctx, unsignedData(out), &len, reinterpret_cast(data.constData()), data.size())) { qCWarning(lcCse()) << "Could not decrypt"; return false; } @@ -2732,7 +2732,7 @@ bool EncryptionHelper::dataDecryption(const QByteArray &key, const QByteArray &i const QByteArray e2EeTag = inputBuffer.read(OCC::Constants::e2EeTagSize); /* Set expected e2EeTag value. Works in OpenSSL 1.0.1d and later */ - if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, e2EeTag.size(), (unsigned char *)e2EeTag.constData())) { + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, e2EeTag.size(), const_cast(reinterpret_cast(e2EeTag.constData())))) { qCWarning(lcCse()) << "Could not set expected e2EeTag"; return false; } From 26f7e7a3f65c4482a7065f3606ad64aff834ca8c Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Tue, 3 Mar 2026 14:00:55 +0100 Subject: [PATCH 2/5] chore(e2ee): add some debug output related to keys management Signed-off-by: Matthieu Gallien --- src/libsync/clientsideencryption.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp index 701c345644a23..6a2713e489118 100644 --- a/src/libsync/clientsideencryption.cpp +++ b/src/libsync/clientsideencryption.cpp @@ -586,14 +586,14 @@ std::optional encryptStringAsymmetric(const CertificateInformation & const QByteArray &binaryData) { if (!encryptionEngine.isInitialized()) { - qCWarning(lcCseDecryption()) << "end-to-end encryption is disabled"; + qCWarning(lcCseEncryption()) << "end-to-end encryption is disabled"; return {}; } if (encryptionEngine.useTokenBasedEncryption()) { - qCDebug(lcCseDecryption()) << "use certificate on hardware token" << selectedCertificate.sha256Fingerprint(); + qCDebug(lcCseEncryption()) << "use certificate on hardware token" << selectedCertificate.sha256Fingerprint(); } else { - qCDebug(lcCseDecryption()) << "use certificate on software storage" << selectedCertificate.sha256Fingerprint(); + qCDebug(lcCseEncryption()) << "use certificate on software storage" << selectedCertificate.sha256Fingerprint(); } auto encryptedBase64Result = QByteArray{}; @@ -612,7 +612,7 @@ std::optional encryptStringAsymmetric(const CertificateInformation & encryptedBase64Result = *encryptionResult; break; } else if (encryptionResult.error() == ClientSideEncryption::EncryptionErrorType::RetryOnError) { - qCInfo(lcCseDecryption()) << "retry encryption after error"; + qCInfo(lcCseEncryption()) << "retry encryption after error"; needHardwareTokenEncryptionInit = true; continue; } else { From 905f1b0f5fc19a299a1a88cd34f0871535ff0d93 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Tue, 3 Mar 2026 14:01:28 +0100 Subject: [PATCH 3/5] fix(e2ee): ensure we update the user own certificate when needed we need updated user certificate before we upload new encryption metadata to ensure they are generated with the correct certificate and private key Signed-off-by: Matthieu Gallien --- src/libsync/encryptedfoldermetadatahandler.cpp | 1 + src/libsync/foldermetadata.cpp | 9 +++++++++ src/libsync/foldermetadata.h | 2 ++ 3 files changed, 12 insertions(+) diff --git a/src/libsync/encryptedfoldermetadatahandler.cpp b/src/libsync/encryptedfoldermetadatahandler.cpp index 7b141ab192ca3..6fa3fe52aef50 100644 --- a/src/libsync/encryptedfoldermetadatahandler.cpp +++ b/src/libsync/encryptedfoldermetadatahandler.cpp @@ -281,6 +281,7 @@ void EncryptedFolderMetadataHandler::startUploadMetadata() return; } + folderMetadata()->updateSelfCertificate(); const auto encryptedMetadata = folderMetadata()->encryptedMetadata(); if (_isNewMetadataCreated) { const auto job = new StoreMetaDataApiJob(_account, _folderId, _folderToken, encryptedMetadata, folderMetadata()->metadataSignature()); diff --git a/src/libsync/foldermetadata.cpp b/src/libsync/foldermetadata.cpp index 9c78605ca5126..6e37c4eb267ea 100644 --- a/src/libsync/foldermetadata.cpp +++ b/src/libsync/foldermetadata.cpp @@ -839,6 +839,15 @@ QByteArray FolderMetadata::initialMetadata() const return _initialMetadata; } +void FolderMetadata::updateSelfCertificate() +{ + for (auto &oneFolderUser : _folderUsers) { + if (oneFolderUser.userId == _account->davUser()) { + oneFolderUser.certificatePem = _account->e2e()->getCertificate().toPem(); + } + } +} + quint64 FolderMetadata::newCounter() const { return _counter + 1; diff --git a/src/libsync/foldermetadata.h b/src/libsync/foldermetadata.h index 0cb9aa615ad28..39c2105e80c76 100644 --- a/src/libsync/foldermetadata.h +++ b/src/libsync/foldermetadata.h @@ -141,6 +141,8 @@ class OWNCLOUDSYNC_EXPORT FolderMetadata : public QObject [[nodiscard]] QByteArray initialMetadata() const; + void updateSelfCertificate(); + public slots: void addEncryptedFile(const FolderMetadata::EncryptedFile &f); void removeEncryptedFile(const FolderMetadata::EncryptedFile &f); From cd44f4ba24c17292defd7d51b6f8645266863bff Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Tue, 3 Mar 2026 16:41:19 +0100 Subject: [PATCH 4/5] chore(e2ee): add more missing binary suffix for binary byte arrays Signed-off-by: Matthieu Gallien --- src/libsync/clientsideencryption.cpp | 6 +- src/libsync/foldermetadata.cpp | 76 +++++++++---------- src/libsync/foldermetadata.h | 8 +- src/libsync/rootencryptedfolderinfo.cpp | 6 +- src/libsync/rootencryptedfolderinfo.h | 4 +- .../updatee2eefolderusersmetadatajob.cpp | 4 +- test/testclientsideencryptionv2.cpp | 2 +- test/testsecurefiledrop.cpp | 2 +- 8 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp index 6a2713e489118..693c1bcee16b1 100644 --- a/src/libsync/clientsideencryption.cpp +++ b/src/libsync/clientsideencryption.cpp @@ -1364,9 +1364,9 @@ void ClientSideEncryption::fetchPublicKeyFromKeyChain() bool ClientSideEncryption::checkEncryptionIsWorking(const CertificateInformation ¤tCertificate) { qCInfo(lcCse) << "check encryption is working before enabling end-to-end encryption feature"; - QByteArray data = EncryptionHelper::generateRandom(64); + const auto binaryData = EncryptionHelper::generateRandom(64); - auto encryptedData = EncryptionHelper::encryptStringAsymmetric(currentCertificate, paddingMode(), *this, data); + auto encryptedData = EncryptionHelper::encryptStringAsymmetric(currentCertificate, paddingMode(), *this, binaryData); if (!encryptedData) { qCWarning(lcCse()) << "encryption error"; return false; @@ -1384,7 +1384,7 @@ bool ClientSideEncryption::checkEncryptionIsWorking(const CertificateInformation QByteArray decryptResult = QByteArray::fromBase64(*decryptionResult); - if (data != decryptResult) { + if (binaryData != decryptResult) { qCInfo(lcCse()) << "recovered data does not match the initial data after encryption and decryption of it"; return false; } diff --git a/src/libsync/foldermetadata.cpp b/src/libsync/foldermetadata.cpp index 6e37c4eb267ea..190577bc2c61e 100644 --- a/src/libsync/foldermetadata.cpp +++ b/src/libsync/foldermetadata.cpp @@ -74,8 +74,8 @@ FolderMetadata::FolderMetadata(AccountPtr account, , _remoteFolderRoot(Utility::noLeadingSlashPath(Utility::noTrailingSlashPath(remoteFolderRoot))) , _initialMetadata(metadata) , _isRootEncryptedFolder(rootEncryptedFolderInfo.path == QStringLiteral("/")) - , _metadataKeyForEncryption(rootEncryptedFolderInfo.keyForEncryption) - , _metadataKeyForDecryption(rootEncryptedFolderInfo.keyForDecryption) + , _binaryMetadataKeyForEncryption(rootEncryptedFolderInfo.binaryKeyForEncryption) + , _binaryMetadataKeyForDecryption(rootEncryptedFolderInfo.binaryKeyForDecryption) , _keyChecksums(rootEncryptedFolderInfo.keyChecksums) , _initialSignature(signature) { @@ -103,7 +103,7 @@ void FolderMetadata::initMetadata() qCDebug(lcCseMetadata()) << "Setting up existing metadata"; setupExistingMetadata(_initialMetadata); - if (metadataKeyForDecryption().isEmpty() || metadataKeyForEncryption().isEmpty()) { + if (binaryMetadataKeyForDecryption().isEmpty() || binaryMetadataKeyForEncryption().isEmpty()) { qCWarning(lcCseMetadata()) << "Failed to setup FolderMetadata. Could not parse/create metadataKey!"; } emitSetupComplete(); @@ -182,8 +182,8 @@ void FolderMetadata::setupExistingMetadata(const QByteArray &metadata) if (_folderUsers.contains(_account->davUser())) { const auto currentFolderUser = _folderUsers.value(_account->davUser()); const auto currentUserCertificate = QSslCertificate{currentFolderUser.certificatePem}; - _metadataKeyForEncryption = QByteArray::fromBase64(decryptDataWithPrivateKey(currentFolderUser.encryptedMetadataKey, currentUserCertificate.digest(QCryptographicHash::Sha256).toBase64())); - _metadataKeyForDecryption = _metadataKeyForEncryption; + _binaryMetadataKeyForEncryption = QByteArray::fromBase64(decryptDataWithPrivateKey(currentFolderUser.encryptedMetadataKey, currentUserCertificate.digest(QCryptographicHash::Sha256).toBase64())); + _binaryMetadataKeyForDecryption = _binaryMetadataKeyForEncryption; } if (!parseFileDropPart(metaDataDoc)) { @@ -191,7 +191,7 @@ void FolderMetadata::setupExistingMetadata(const QByteArray &metadata) return; } - if (metadataKeyForDecryption().isEmpty() || metadataKeyForEncryption().isEmpty()) { + if (binaryMetadataKeyForDecryption().isEmpty() || binaryMetadataKeyForEncryption().isEmpty()) { qCWarning(lcCseMetadata()) << "Could not setup metadata key!"; _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError); return; @@ -205,7 +205,7 @@ void FolderMetadata::setupExistingMetadata(const QByteArray &metadata) // for compatibility, the format is "cipheredpart|initializationVector", so we need to extract the "cipheredpart" const auto cipherTextPartExtracted = cipherTextEncrypted.split('|').at(0); - const auto cipherTextDecrypted = EncryptionHelper::decryptThenUnGzipData(metadataKeyForDecryption(), QByteArray::fromBase64(cipherTextPartExtracted), _metadataNonce); + const auto cipherTextDecrypted = EncryptionHelper::decryptThenUnGzipData(binaryMetadataKeyForDecryption(), QByteArray::fromBase64(cipherTextPartExtracted), _metadataNonce); if (cipherTextDecrypted.isEmpty()) { qCWarning(lcCseMetadata()) << "Could not decrypt cipher text!"; _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError); @@ -227,7 +227,7 @@ void FolderMetadata::setupExistingMetadata(const QByteArray &metadata) } } - if (!verifyMetadataKey(metadataKeyForDecryption())) { + if (!verifyMetadataKey(binaryMetadataKeyForDecryption())) { qCWarning(lcCseMetadata()) << "Could not verify metadataKey!"; _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError); return; @@ -275,7 +275,7 @@ void FolderMetadata::setupExistingMetadataLegacy(const QByteArray &metadata) const auto &fullMetaDataObj = metaDataObj[metadataJsonKey].toObject(); // we will use metadata key from metadata to decrypt legacy metadata, so let's clear the decryption key if any provided by top-level folder - _metadataKeyForDecryption.clear(); + _binaryMetadataKeyForDecryption.clear(); const auto metadataKeyFromJson = fullMetaDataObj[metadataKeyKey].toString().toLocal8Bit(); if (!metadataKeyFromJson.isEmpty()) { @@ -283,11 +283,11 @@ void FolderMetadata::setupExistingMetadataLegacy(const QByteArray &metadata) const auto decryptedMetadataKeyBase64 = decryptDataWithPrivateKey(metadataKeyFromJson, _account->e2e()->certificateSha256Fingerprint()); if (!decryptedMetadataKeyBase64.isEmpty()) { // fromBase64() multiple times just to stick with the old wrong way - _metadataKeyForDecryption = QByteArray::fromBase64(QByteArray::fromBase64(decryptedMetadataKeyBase64)); + _binaryMetadataKeyForDecryption = QByteArray::fromBase64(QByteArray::fromBase64(QByteArray::fromBase64(decryptedMetadataKeyBase64))); } } - if (metadataKeyForDecryption().isEmpty() && _existingMetadataVersion < MetadataVersion::Version1_2) { + if (binaryMetadataKeyForDecryption().isEmpty() && _existingMetadataVersion < MetadataVersion::Version1_2) { // parse version 1.0 (before security-vulnerability fix for metadata keys was released qCDebug(lcCseMetadata()) << "Migrating from" << _existingMetadataVersion << "to" << latestSupportedMetadataVersion(); @@ -305,20 +305,20 @@ void FolderMetadata::setupExistingMetadataLegacy(const QByteArray &metadata) if (!lastMetadataKeyValueFromJson.isEmpty()) { const auto lastMetadataKeyValueFromJsonBase64 = decryptDataWithPrivateKey(lastMetadataKeyValueFromJson, _account->e2e()->certificateSha256Fingerprint()); if (!lastMetadataKeyValueFromJsonBase64.isEmpty()) { - _metadataKeyForDecryption = QByteArray::fromBase64(QByteArray::fromBase64(lastMetadataKeyValueFromJsonBase64)); + _binaryMetadataKeyForDecryption = QByteArray::fromBase64(QByteArray::fromBase64(lastMetadataKeyValueFromJsonBase64)); } } } } - if (metadataKeyForDecryption().isEmpty()) { + if (binaryMetadataKeyForDecryption().isEmpty()) { qCWarning(lcCseMetadata()) << "Could not setup existing metadata with missing metadataKeys!"; _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError); return; } - if (metadataKeyForEncryption().isEmpty()) { - _metadataKeyForEncryption = metadataKeyForDecryption(); + if (binaryMetadataKeyForEncryption().isEmpty()) { + _binaryMetadataKeyForEncryption = binaryMetadataKeyForDecryption(); } const auto &files = metaDataObj[filesKey].toObject(); @@ -339,7 +339,7 @@ void FolderMetadata::setupExistingMetadataLegacy(const QByteArray &metadata) // Decrypt encrypted part const auto encryptedFile = fileObj[encryptedKey].toString().toLocal8Bit(); - const auto decryptedFile = decryptJsonObject(encryptedFile, metadataKeyForDecryption()); + const auto decryptedFile = decryptJsonObject(encryptedFile, binaryMetadataKeyForDecryption()); const auto decryptedFileDoc = QJsonDocument::fromJson(decryptedFile); const auto decryptedFileObj = decryptedFileDoc.object(); @@ -540,9 +540,9 @@ QJsonObject FolderMetadata::convertFileToJsonObject(const EncryptedFile *encrypt return file; } -const QByteArray FolderMetadata::metadataKeyForEncryption() const +const QByteArray FolderMetadata::binaryMetadataKeyForEncryption() const { - return _metadataKeyForEncryption; + return _binaryMetadataKeyForEncryption; } const QSet& FolderMetadata::keyChecksums() const @@ -565,7 +565,7 @@ void FolderMetadata::initEmptyMetadata() _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError); return; } - _metadataKeyForDecryption = _metadataKeyForEncryption; + _binaryMetadataKeyForDecryption = _binaryMetadataKeyForEncryption; } _isMetadataValid = true; @@ -574,8 +574,8 @@ void FolderMetadata::initEmptyMetadata() void FolderMetadata::initEmptyMetadataLegacy() { - _metadataKeyForEncryption = EncryptionHelper::generateRandom(metadataKeySize); - _metadataKeyForDecryption = _metadataKeyForEncryption; + _binaryMetadataKeyForEncryption = EncryptionHelper::generateRandom(metadataKeySize); + _binaryMetadataKeyForDecryption = _binaryMetadataKeyForEncryption; _isMetadataValid = true; @@ -599,7 +599,7 @@ QByteArray FolderMetadata::encryptedMetadata() createNewMetadataKeyForEncryption(); } - if (metadataKeyForEncryption().isEmpty()) { + if (binaryMetadataKeyForEncryption().isEmpty()) { qCWarning(lcCseMetadata()) << "Encrypting metadata failed! Empty metadata key!"; return {}; } @@ -644,7 +644,7 @@ QByteArray FolderMetadata::encryptedMetadata() QByteArray authenticationTag; const auto initializationVector = EncryptionHelper::generateRandom(metadataKeySize); const auto initializationVectorBase64 = initializationVector.toBase64(); - const auto gzippedThenEncryptData = EncryptionHelper::gzipThenEncryptData(metadataKeyForEncryption(), cipherTextDoc.toJson(QJsonDocument::Compact), initializationVector, authenticationTag).toBase64(); + const auto gzippedThenEncryptData = EncryptionHelper::gzipThenEncryptData(binaryMetadataKeyForEncryption(), cipherTextDoc.toJson(QJsonDocument::Compact), initializationVector, authenticationTag).toBase64(); // backwards compatible with old versions ("ciphertext|initializationVector") const auto encryptedCipherText = QByteArray(gzippedThenEncryptData + QByteArrayLiteral("|") + initializationVectorBase64); const QJsonObject metadata{{cipherTextKey, QJsonValue::fromVariant(encryptedCipherText)}, @@ -701,14 +701,14 @@ QByteArray FolderMetadata::encryptedMetadata() QByteArray FolderMetadata::encryptedMetadataLegacy() { - if (_metadataKeyForEncryption.isEmpty()) { + if (_binaryMetadataKeyForEncryption.isEmpty()) { qCWarning(lcCseMetadata) << "Metadata generation failed! Empty metadata key!"; _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError); return {}; } const auto version = _account->capabilities().clientSideEncryptionVersion(); // multiple toBase64() just to keep with the old (wrong way) - const auto encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption(), _account->e2e()->getCertificateInformation()).toBase64(); + const auto encryptedMetadataKey = encryptDataWithPublicKey(binaryMetadataKeyForEncryption(), _account->e2e()->getCertificateInformation()).toBase64(); const QJsonObject metadata{ {versionKey, version}, {metadataKeyKey, QJsonValue::fromVariant(encryptedMetadataKey)}, @@ -724,7 +724,7 @@ QByteArray FolderMetadata::encryptedMetadataLegacy() QJsonDocument encryptedDoc; encryptedDoc.setObject(encrypted); - QString encryptedEncrypted = encryptJsonObject(encryptedDoc.toJson(QJsonDocument::Compact), metadataKeyForEncryption()); + QString encryptedEncrypted = encryptJsonObject(encryptedDoc.toJson(QJsonDocument::Compact), binaryMetadataKeyForEncryption()); if (encryptedEncrypted.isEmpty()) { qCWarning(lcCseMetadata) << "Metadata generation failed!"; _account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError); @@ -925,9 +925,9 @@ void FolderMetadata::addEncryptedFile(const EncryptedFile &f) { _files.append(f); } -const QByteArray FolderMetadata::metadataKeyForDecryption() const +const QByteArray FolderMetadata::binaryMetadataKeyForDecryption() const { - return _metadataKeyForDecryption; + return _binaryMetadataKeyForDecryption; } void FolderMetadata::removeEncryptedFile(const EncryptedFile &f) @@ -1029,15 +1029,15 @@ void FolderMetadata::slotRootE2eeFolderMetadataReceived(int statusCode, const QS return; } - _metadataKeyForEncryption = rootE2eeFolderMetadata->metadataKeyForEncryption(); + _binaryMetadataKeyForEncryption = rootE2eeFolderMetadata->binaryMetadataKeyForEncryption(); if (!isVersion2AndUp()) { initMetadata(); return; } - _metadataKeyForDecryption = rootE2eeFolderMetadata->metadataKeyForDecryption(); - _metadataKeyForEncryption = rootE2eeFolderMetadata->metadataKeyForEncryption(); + _binaryMetadataKeyForDecryption = rootE2eeFolderMetadata->binaryMetadataKeyForDecryption(); + _binaryMetadataKeyForEncryption = rootE2eeFolderMetadata->binaryMetadataKeyForEncryption(); _keyChecksums = rootE2eeFolderMetadata->keyChecksums(); initMetadata(); } @@ -1077,7 +1077,7 @@ bool FolderMetadata::addUser(const QString &userId, UserWithFolderAccess newFolderUser; newFolderUser.userId = userId; newFolderUser.certificatePem = certificate.toPem(); - newFolderUser.encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption(), shareUserCertificate); + newFolderUser.encryptedMetadataKey = encryptDataWithPublicKey(binaryMetadataKeyForEncryption(), shareUserCertificate); _folderUsers[userId] = newFolderUser; updateUsersEncryptedMetadataKey(); @@ -1111,8 +1111,8 @@ void FolderMetadata::updateUsersEncryptedMetadataKey() qCWarning(lcCseMetadata()) << "Could not update folder users in a non top level folder."; return; } - Q_ASSERT(!metadataKeyForEncryption().isEmpty()); - if (metadataKeyForEncryption().isEmpty()) { + Q_ASSERT(!binaryMetadataKeyForEncryption().isEmpty()); + if (binaryMetadataKeyForEncryption().isEmpty()) { qCWarning(lcCseMetadata()) << "Could not update folder users with empty metadataKey!"; return; } @@ -1122,7 +1122,7 @@ void FolderMetadata::updateUsersEncryptedMetadataKey() const QSslCertificate certificate(folderUser.certificatePem); CertificateInformation shareUserCertificate = CertificateInformation{{}, QSslCertificate{certificate}}; - const auto encryptedMetadataKey = encryptDataWithPublicKey(metadataKeyForEncryption(), shareUserCertificate); + const auto encryptedMetadataKey = encryptDataWithPublicKey(binaryMetadataKeyForEncryption(), shareUserCertificate); if (encryptedMetadataKey.isEmpty()) { qCWarning(lcCseMetadata()) << "Could not update folder users with empty encryptedMetadataKey!"; continue; @@ -1139,9 +1139,9 @@ void FolderMetadata::createNewMetadataKeyForEncryption() if (!_isRootEncryptedFolder) { return; } - _metadataKeyForEncryption = EncryptionHelper::generateRandom(metadataKeySize); - if (!metadataKeyForEncryption().isEmpty()) { - _keyChecksums.insert(calcSha256(metadataKeyForEncryption())); + _binaryMetadataKeyForEncryption = EncryptionHelper::generateRandom(metadataKeySize); + if (!binaryMetadataKeyForEncryption().isEmpty()) { + _keyChecksums.insert(calcSha256(binaryMetadataKeyForEncryption())); } } diff --git a/src/libsync/foldermetadata.h b/src/libsync/foldermetadata.h index 39c2105e80c76..536de6e7bb990 100644 --- a/src/libsync/foldermetadata.h +++ b/src/libsync/foldermetadata.h @@ -124,8 +124,8 @@ class OWNCLOUDSYNC_EXPORT FolderMetadata : public QObject [[nodiscard]] bool updateUser(const QString &userId, const QSslCertificate &certificate, CertificateType certificateType); - [[nodiscard]] const QByteArray metadataKeyForEncryption() const; - [[nodiscard]] const QByteArray metadataKeyForDecryption() const; + [[nodiscard]] const QByteArray binaryMetadataKeyForEncryption() const; + [[nodiscard]] const QByteArray binaryMetadataKeyForDecryption() const; [[nodiscard]] const QSet &keyChecksums() const; [[nodiscard]] QByteArray encryptedMetadata(); @@ -208,9 +208,9 @@ private slots: bool _isRootEncryptedFolder = false; // always contains the last generated metadata key (non-encrypted and non-base64) - QByteArray _metadataKeyForEncryption; + QByteArray _binaryMetadataKeyForEncryption; // used for storing initial metadataKey to use for decryption, especially in nested folders when changing the metadataKey and re-encrypting nested dirs - QByteArray _metadataKeyForDecryption; + QByteArray _binaryMetadataKeyForDecryption; QByteArray _metadataNonce; // metadatakey checksums for validation during setting up from existing metadata QSet _keyChecksums; diff --git a/src/libsync/rootencryptedfolderinfo.cpp b/src/libsync/rootencryptedfolderinfo.cpp index d3133f6552cec..32052430dc9b2 100644 --- a/src/libsync/rootencryptedfolderinfo.cpp +++ b/src/libsync/rootencryptedfolderinfo.cpp @@ -18,8 +18,8 @@ RootEncryptedFolderInfo::RootEncryptedFolderInfo(const QString &remotePath, const QSet &checksums, const quint64 counter) : path(remotePath) - , keyForEncryption(encryptionKey) - , keyForDecryption(decryptionKey) + , binaryKeyForEncryption(encryptionKey) + , binaryKeyForDecryption(decryptionKey) , keyChecksums(checksums) , counter(counter) { @@ -40,6 +40,6 @@ QString RootEncryptedFolderInfo::createRootPath(const QString ¤tPath, cons bool RootEncryptedFolderInfo::keysSet() const { - return !keyForEncryption.isEmpty() && !keyForDecryption.isEmpty() && !keyChecksums.isEmpty(); + return !binaryKeyForEncryption.isEmpty() && !binaryKeyForDecryption.isEmpty() && !keyChecksums.isEmpty(); } } diff --git a/src/libsync/rootencryptedfolderinfo.h b/src/libsync/rootencryptedfolderinfo.h index 4e876cb3e5d78..4f842f7522f46 100644 --- a/src/libsync/rootencryptedfolderinfo.h +++ b/src/libsync/rootencryptedfolderinfo.h @@ -25,8 +25,8 @@ struct OWNCLOUDSYNC_EXPORT RootEncryptedFolderInfo { static QString createRootPath(const QString ¤tPath, const QString &topLevelPath); QString path; - QByteArray keyForEncryption; // it can be different from keyForDecryption when new metadatKey is generated in root E2EE foler - QByteArray keyForDecryption; // always storing previous metadataKey to be able to decrypt nested E2EE folders' previous metadata + QByteArray binaryKeyForEncryption; // it can be different from binaryKeyForDecryption when new metadatKey is generated in root E2EE foler + QByteArray binaryKeyForDecryption; // always storing previous metadataKey to be able to decrypt nested E2EE folders' previous metadata QSet keyChecksums; quint64 counter = 0; [[nodiscard]] bool keysSet() const; diff --git a/src/libsync/updatee2eefolderusersmetadatajob.cpp b/src/libsync/updatee2eefolderusersmetadatajob.cpp index 10e2759c7345d..94a88ec400760 100644 --- a/src/libsync/updatee2eefolderusersmetadatajob.cpp +++ b/src/libsync/updatee2eefolderusersmetadatajob.cpp @@ -201,8 +201,8 @@ void UpdateE2eeFolderUsersMetadataJob::scheduleSubJobs() if (record.isDirectory()) { const auto folderMetadata = _encryptedFolderMetadataHandler->folderMetadata(); const auto subJob = new UpdateE2eeFolderUsersMetadataJob(_account, _journalDb, _syncFolderRemotePath, UpdateE2eeFolderUsersMetadataJob::ReEncrypt, Utility::trailingSlashPath(_syncFolderRemotePath) + QString::fromUtf8(record._e2eMangledName)); - subJob->setMetadataKeyForEncryption(folderMetadata->metadataKeyForEncryption()); - subJob->setMetadataKeyForDecryption(folderMetadata->metadataKeyForDecryption()); + subJob->setMetadataKeyForEncryption(folderMetadata->binaryMetadataKeyForEncryption()); + subJob->setMetadataKeyForDecryption(folderMetadata->binaryMetadataKeyForDecryption()); subJob->setKeyChecksums(folderMetadata->keyChecksums()); subJob->setParent(this); subJob->setFolderToken(_encryptedFolderMetadataHandler->folderToken()); diff --git a/test/testclientsideencryptionv2.cpp b/test/testclientsideencryptionv2.cpp index 6dfc4a7dd3902..9e75af70e3ae7 100644 --- a/test/testclientsideencryptionv2.cpp +++ b/test/testclientsideencryptionv2.cpp @@ -215,7 +215,7 @@ private slots: QCOMPARE(metadataSetupExistingCompleteSpy.count(), 1); QVERIFY(metadataFromJson->metadataSignature().isEmpty()); - QVERIFY(metadataFromJson->metadataKeyForDecryption().isEmpty()); + QVERIFY(metadataFromJson->binaryMetadataKeyForDecryption().isEmpty()); QVERIFY(!metadataFromJson->isValid()); } diff --git a/test/testsecurefiledrop.cpp b/test/testsecurefiledrop.cpp index 016f5aadb278e..8e7162ae34d7d 100644 --- a/test/testsecurefiledrop.cpp +++ b/test/testsecurefiledrop.cpp @@ -123,7 +123,7 @@ private slots: QByteArray authenticationTag; const auto initializationVector = EncryptionHelper::generateRandom(16); - const auto cipherTextEncrypted = EncryptionHelper::gzipThenEncryptData(metadata->_metadataKeyForEncryption, + const auto cipherTextEncrypted = EncryptionHelper::gzipThenEncryptData(metadata->_binaryMetadataKeyForEncryption, fakeFileDropMetadata.toJson(QJsonDocument::JsonFormat::Compact), initializationVector, authenticationTag); From 750e435e9530fc4e0df6c67f1fccf6916991a0af Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Thu, 5 Mar 2026 09:51:41 +0100 Subject: [PATCH 5/5] fix(e2ee): fix certificate migration ensure we do store in the metadata file the properly encrypted metadata key ensure we would update the user own certificate before eventually encrypting again the metadata key do this before we would try to write the metadata file and ensure it is writtent with updated values instead of getting a copy of the old values (before the certificate change) Signed-off-by: Matthieu Gallien --- src/libsync/foldermetadata.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libsync/foldermetadata.cpp b/src/libsync/foldermetadata.cpp index 190577bc2c61e..77b949ca07a0a 100644 --- a/src/libsync/foldermetadata.cpp +++ b/src/libsync/foldermetadata.cpp @@ -604,6 +604,16 @@ QByteArray FolderMetadata::encryptedMetadata() return {}; } + if (_isRootEncryptedFolder) { + for (auto &folderUser : _folderUsers) { + if (folderUser.userId == _account->davUser()) { + folderUser.certificatePem = _account->e2e()->getCertificate().toPem(); + } + } + + updateUsersEncryptedMetadataKey(); + } + QJsonObject files, folders; for (auto it = _files.constBegin(), end = _files.constEnd(); it != end; ++it) { const auto file = convertFileToJsonObject(&(*it)); @@ -655,14 +665,7 @@ QByteArray FolderMetadata::encryptedMetadata() QJsonArray folderUsers; if (_isRootEncryptedFolder) { - for (auto it = _folderUsers.constBegin(), end = _folderUsers.constEnd(); it != end; ++it) { - auto folderUser = it.value(); - - if (folderUser.userId == _account->davUser()) { - folderUser.certificatePem = _account->e2e()->getCertificate().toPem(); - updateUsersEncryptedMetadataKey(); - } - + for (const auto &folderUser : _folderUsers) { const QJsonObject folderUserJson{{usersUserIdKey, folderUser.userId}, {usersCertificateKey, QJsonValue::fromVariant(folderUser.certificatePem)}, {usersEncryptedMetadataKey, QJsonValue::fromVariant(folderUser.encryptedMetadataKey)}};