fix(encryption): remove currently broken certificate hash handling

we may not currently properly store certificate hash for encrypted items
and later fails to find which public or private key pairs to use

for now, it is better to remove it and ensure we have a reliable way to
handle certificate migration

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
This commit is contained in:
Matthieu Gallien 2025-05-23 10:52:53 +02:00
parent 63f39a6bad
commit 67bf4054dc
17 changed files with 12 additions and 69 deletions

View File

@ -56,7 +56,6 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que
rec._checksumHeader = query.baValue(9);
rec._e2eMangledName = query.baValue(10);
rec._e2eEncryptionStatus = static_cast<SyncJournalFileRecord::EncryptionStatus>(query.intValue(11));
rec._e2eCertificateFingerprint = query.baValue(12);
rec._lockstate._locked = query.intValue(13) > 0;
rec._lockstate._lockOwnerDisplayName = query.stringValue(14);
rec._lockstate._lockOwnerId = query.stringValue(15);
@ -1013,7 +1012,7 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
query->bindValue(16, contentChecksumTypeId);
query->bindValue(17, record._e2eMangledName);
query->bindValue(18, static_cast<int>(record._e2eEncryptionStatus));
query->bindValue(19, record._e2eCertificateFingerprint);
query->bindValue(19, {});
query->bindValue(20, record._lockstate._locked ? 1 : 0);
query->bindValue(21, record._lockstate._lockOwnerType);
query->bindValue(22, record._lockstate._lockOwnerDisplayName);

View File

@ -72,7 +72,6 @@ public:
QByteArray _checksumHeader;
QByteArray _e2eMangledName;
EncryptionStatus _e2eEncryptionStatus = EncryptionStatus::NotEncrypted;
QByteArray _e2eCertificateFingerprint;
SyncJournalFileLockInfo _lockstate;
bool _isShared = false;
qint64 _lastShareStateFetchedTimestamp = 0;

View File

@ -78,16 +78,6 @@ void ProcessDirectoryJob::start()
{
qCInfo(lcDisco) << "STARTING" << _currentFolder._server << _queryServer << _currentFolder._local << _queryLocal;
if (isInsideEncryptedTree()) {
auto folderDbRecord = SyncJournalFileRecord{};
if (_discoveryData->_statedb->getFileRecord(_currentFolder._local, &folderDbRecord) && folderDbRecord.isValid()) {
if (_discoveryData->_account->encryptionCertificateFingerprint() != folderDbRecord._e2eCertificateFingerprint) {
qCDebug(lcDisco) << "encryption certificate needs update. Forcing full discovery";
_queryServer = NormalQuery;
}
}
}
_discoveryData->_noCaseConflictRecordsInDb = _discoveryData->_statedb->caseClashConflictRecordPaths().isEmpty();
if (_queryServer == NormalQuery) {
@ -741,7 +731,6 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(const SyncFileItemPtr &it
item->_e2eEncryptionStatus = serverEntry.isE2eEncrypted() ? SyncFileItem::EncryptionStatus::Encrypted : SyncFileItem::EncryptionStatus::NotEncrypted;
if (serverEntry.isE2eEncrypted()) {
item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(_discoveryData->_account->capabilities().clientSideEncryptionVersion());
item->_e2eCertificateFingerprint = serverEntry.e2eCertificateFingerprint;
}
item->_encryptedFileName = [=, this] {
if (serverEntry.e2eMangledName.isEmpty()) {
@ -1278,10 +1267,7 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
if (dbEntry.isValid()) {
bool typeChange = localEntry.isDirectory != dbEntry.isDirectory();
if (localEntry.isDirectory && dbEntry.isValid() && dbEntry.isE2eEncrypted() && dbEntry._e2eCertificateFingerprint != _discoveryData->_account->encryptionCertificateFingerprint()) {
item->_instruction = CSYNC_INSTRUCTION_UPDATE_ENCRYPTION_METADATA;
item->_direction = SyncFileItem::Up;
} else if (!typeChange && localEntry.isVirtualFile) {
if (!typeChange && localEntry.isVirtualFile) {
if (noServerEntry) {
item->_instruction = CSYNC_INSTRUCTION_REMOVE;
item->_direction = SyncFileItem::Down;
@ -1535,7 +1521,6 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
// base is a record in the SyncJournal database that contains the data about the being-renamed folder with it's old name and encryption information
item->_e2eEncryptionStatus = EncryptionStatusEnums::fromDbEncryptionStatus(base._e2eEncryptionStatus);
item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(_discoveryData->_account->capabilities().clientSideEncryptionVersion());
item->_e2eCertificateFingerprint = base._e2eCertificateFingerprint;
}
postProcessLocalNew();
finalize();
@ -2237,7 +2222,6 @@ DiscoverySingleDirectoryJob *ProcessDirectoryJob::startAsyncServerQuery()
const auto alreadyDownloaded = _discoveryData->_statedb->getFileRecord(_dirItem->_file, &record) && record.isValid();
// we need to make sure we first download all e2ee files/folders before migrating
_dirItem->_isEncryptedMetadataNeedUpdate = alreadyDownloaded && serverJob->encryptedMetadataNeedUpdate();
_dirItem->_e2eCertificateFingerprint = serverJob->certificateSha256Fingerprint();
_dirItem->_e2eEncryptionStatus = serverJob->currentEncryptionStatus();
_dirItem->_e2eEncryptionStatusRemote = serverJob->currentEncryptionStatus();
_dirItem->_e2eEncryptionServerCapability = serverJob->requiredEncryptionStatus();

View File

@ -476,11 +476,6 @@ SyncFileItem::EncryptionStatus DiscoverySingleDirectoryJob::requiredEncryptionSt
return _encryptionStatusRequired;
}
QByteArray DiscoverySingleDirectoryJob::certificateSha256Fingerprint() const
{
return _e2eCertificateFingerprint;
}
static void propertyMapToRemoteInfo(const QMap<QString, QString> &map, RemotePermissions::MountedPermissionAlgorithm algorithm, RemoteInfo &result)
{
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
@ -768,7 +763,6 @@ void DiscoverySingleDirectoryJob::metadataReceived(const QJsonDocument &json, in
}
_isFileDropDetected = e2EeFolderMetadata->isFileDropPresent();
_encryptedMetadataNeedUpdate = e2EeFolderMetadata->encryptedMetadataNeedUpdate();
_e2eCertificateFingerprint = e2EeFolderMetadata->certificateSha256Fingerprint();
_encryptionStatusRequired = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(_account->capabilities().clientSideEncryptionVersion());
_encryptionStatusCurrent = e2EeFolderMetadata->existingMetadataEncryptionStatus();

View File

@ -77,7 +77,6 @@ struct RemoteInfo
bool _isE2eEncrypted = false;
bool isFileDropDetected = false;
QString e2eMangledName;
QByteArray e2eCertificateFingerprint;
bool sharedByMe = false;
[[nodiscard]] bool isValid() const { return !name.isNull(); }
@ -171,7 +170,6 @@ public:
void abort();
[[nodiscard]] bool isFileDropDetected() const;
[[nodiscard]] bool encryptedMetadataNeedUpdate() const;
[[nodiscard]] QByteArray certificateSha256Fingerprint() const;
[[nodiscard]] SyncFileItem::EncryptionStatus currentEncryptionStatus() const;
[[nodiscard]] SyncFileItem::EncryptionStatus requiredEncryptionStatus() const;
@ -213,7 +211,6 @@ private:
bool _isFileDropDetected = false;
bool _encryptedMetadataNeedUpdate = false;
SyncFileItem::EncryptionStatus _encryptionStatusRequired = SyncFileItem::EncryptionStatus::NotEncrypted;
QByteArray _e2eCertificateFingerprint;
// If set, the discovery will finish with an error
int64_t _size = 0;

View File

@ -64,7 +64,6 @@ void EncryptFolderJob::slotEncryptionFlagSuccess(const QByteArray &fileId)
if (_propagator && _item) {
qCWarning(lcEncryptFolderJob) << "No valid record found in local DB for fileId" << fileId << "going to create it now...";
_item->_e2eEncryptionStatus = EncryptionStatusEnums::ItemEncryptionStatus::EncryptedMigratedV2_0;
_item->_e2eCertificateFingerprint = _account->e2e()->certificateSha256Fingerprint();
const auto updateResult = _propagator->updateMetadata(*_item.data());
if (updateResult) {
[[maybe_unused]] const auto result = _journal->getFileRecord(currentPath, &rec);
@ -76,7 +75,6 @@ void EncryptFolderJob::slotEncryptionFlagSuccess(const QByteArray &fileId)
if (rec.isValid() && !rec.isE2eEncrypted()) {
rec._e2eEncryptionStatus = SyncJournalFileRecord::EncryptionStatus::Encrypted;
rec._e2eCertificateFingerprint = _account->e2e()->certificateSha256Fingerprint();
const auto result = _journal->setFileRecord(rec);
if (!result) {
qCWarning(lcEncryptFolderJob) << "Error when setting the file record to the database" << rec._path << result.error();

View File

@ -180,8 +180,7 @@ void FolderMetadata::setupExistingMetadata(const QByteArray &metadata)
if (_folderUsers.contains(_account->davUser())) {
const auto currentFolderUser = _folderUsers.value(_account->davUser());
_e2eCertificateFingerprint = QSslCertificate{currentFolderUser.certificatePem}.digest(QCryptographicHash::Sha256).toBase64();
_metadataKeyForEncryption = QByteArray::fromBase64(decryptDataWithPrivateKey(currentFolderUser.encryptedMetadataKey, _e2eCertificateFingerprint));
_metadataKeyForEncryption = QByteArray::fromBase64(decryptDataWithPrivateKey(currentFolderUser.encryptedMetadataKey));
_metadataKeyForDecryption = _metadataKeyForEncryption;
}
@ -276,7 +275,7 @@ void FolderMetadata::setupExistingMetadataLegacy(const QByteArray &metadata)
const auto metadataKeyFromJson = metadataObj[metadataKeyKey].toString().toLocal8Bit();
if (!metadataKeyFromJson.isEmpty()) {
// parse version 1.1 and 1.2 (both must have a single "metadataKey"), not "metadataKeys" as 1.0
const auto decryptedMetadataKeyBase64 = decryptDataWithPrivateKey(metadataKeyFromJson, _account->e2e()->certificateSha256Fingerprint());
const auto decryptedMetadataKeyBase64 = decryptDataWithPrivateKey(metadataKeyFromJson);
if (!decryptedMetadataKeyBase64.isEmpty()) {
// fromBase64() multiple times just to stick with the old wrong way
_metadataKeyForDecryption = QByteArray::fromBase64(QByteArray::fromBase64(decryptedMetadataKeyBase64));
@ -298,7 +297,7 @@ void FolderMetadata::setupExistingMetadataLegacy(const QByteArray &metadata)
if (!lastMetadataKeyFromJson.isEmpty()) {
const auto lastMetadataKeyValueFromJson = metadataKeys.value(lastMetadataKeyFromJson).toString().toLocal8Bit();
if (!lastMetadataKeyValueFromJson.isEmpty()) {
const auto lastMetadataKeyValueFromJsonBase64 = decryptDataWithPrivateKey(lastMetadataKeyValueFromJson, _account->e2e()->certificateSha256Fingerprint());
const auto lastMetadataKeyValueFromJsonBase64 = decryptDataWithPrivateKey(lastMetadataKeyValueFromJson);
if (!lastMetadataKeyValueFromJsonBase64.isEmpty()) {
_metadataKeyForDecryption = QByteArray::fromBase64(QByteArray::fromBase64(lastMetadataKeyValueFromJsonBase64));
}
@ -436,10 +435,9 @@ QByteArray FolderMetadata::encryptDataWithPublicKey(const QByteArray &binaryData
return {};
}
QByteArray FolderMetadata::decryptDataWithPrivateKey(const QByteArray &base64Data,
const QByteArray &certificateFingerprint) const
QByteArray FolderMetadata::decryptDataWithPrivateKey(const QByteArray &base64Data) const
{
const auto decryptBase64Result = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getCertificateInformationByFingerprint(certificateFingerprint), _account->e2e()->paddingMode(), *_account->e2e(), base64Data);
const auto decryptBase64Result = EncryptionHelper::decryptStringAsymmetric(_account->e2e()->getCertificateInformation(), _account->e2e()->paddingMode(), *_account->e2e(), base64Data);
if (!decryptBase64Result) {
qCDebug(lcCseMetadata()) << "ERROR. Could not decrypt the metadata key";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
@ -796,7 +794,7 @@ bool FolderMetadata::parseFileDropPart(const QJsonDocument &doc)
if (userParsedId == _account->davUser()) {
const auto fileDropEntryUser = UserWithFileDropEntryAccess{
userParsedId,
decryptDataWithPrivateKey(QByteArray::fromBase64(userParsed.value(usersEncryptedFiledropKey).toByteArray()), _e2eCertificateFingerprint)};
decryptDataWithPrivateKey(QByteArray::fromBase64(userParsed.value(usersEncryptedFiledropKey).toByteArray()))};
if (!fileDropEntryUser.isValid()) {
qCDebug(lcCseMetadata()) << "Could not parse filedrop data. encryptedFiledropKey decryption failed";
_account->reportClientStatus(OCC::ClientStatusReportingStatus::E2EeError_GeneralError);
@ -949,11 +947,6 @@ bool FolderMetadata::encryptedMetadataNeedUpdate() const
return latestSupportedMetadataVersion() > _existingMetadataVersion;
}
QByteArray FolderMetadata::certificateSha256Fingerprint() const
{
return _e2eCertificateFingerprint;
}
bool FolderMetadata::moveFromFileDropToFiles()
{
if (_fileDropEntries.isEmpty()) {

View File

@ -113,8 +113,6 @@ public:
[[nodiscard]] bool encryptedMetadataNeedUpdate() const;
[[nodiscard]] QByteArray certificateSha256Fingerprint() const;
[[nodiscard]] bool moveFromFileDropToFiles();
// adds a user to have access to this folder (always generates new metadata key)
@ -151,7 +149,7 @@ private:
[[nodiscard]] QByteArray encryptDataWithPublicKey(const QByteArray &data,
const CertificateInformation &shareUserCertificate) const;
[[nodiscard]] QByteArray decryptDataWithPrivateKey(const QByteArray &data, const QByteArray &certificateFingerprint) const;
[[nodiscard]] QByteArray decryptDataWithPrivateKey(const QByteArray &data) const;
[[nodiscard]] QByteArray encryptJsonObject(const QByteArray& obj, const QByteArray pass) const;
[[nodiscard]] QByteArray decryptJsonObject(const QByteArray& encryptedJsonBlob, const QByteArray& pass) const;
@ -232,8 +230,6 @@ private:
// signature from server-side metadata
QByteArray _initialSignature;
QByteArray _e2eCertificateFingerprint;
// both files and folders info
QVector<EncryptedFile> _files;

View File

@ -1520,9 +1520,6 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status)
}
}
if (!_item->_isAnyCaseClashChild && !_item->_isAnyInvalidCharChild) {
if (_item->isEncrypted()) {
_item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint();
}
const auto result = propagator()->updateMetadata(*_item);
if (!result) {
status = _item->_status = SyncFileItem::FatalError;

View File

@ -1182,7 +1182,6 @@ void PropagateDownloadFile::finalizeDownload()
{
if (isEncrypted()) {
if (_downloadEncryptedHelper->decryptFile(_tmpFile)) {
_item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint();
downloadFinished();
} else {
done(SyncFileItem::NormalError, _downloadEncryptedHelper->errorString(), ErrorCategory::GenericError);

View File

@ -254,7 +254,6 @@ void PropagateRemoteMkdir::slotEncryptFolderFinished(int status, EncryptionStatu
qCDebug(lcPropagateRemoteMkdir) << "Success making the new folder encrypted";
propagator()->_activeJobList.removeOne(this);
_item->_e2eEncryptionStatus = encryptionStatus;
_item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint();
_item->_e2eEncryptionStatusRemote = encryptionStatus;
if (_item->isEncrypted()) {
_item->_e2eEncryptionServerCapability = EncryptionStatusEnums::fromEndToEndEncryptionApiVersion(propagator()->account()->capabilities().clientSideEncryptionVersion());

View File

@ -820,10 +820,6 @@ void PropagateUploadFileCommon::finalize()
if (quotaIt != propagator()->_folderQuota.end())
quotaIt.value() -= _fileToUpload._size;
if (_item->isEncrypted() && _uploadingEncrypted) {
_item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint();
}
// Update the database entry
const auto result = propagator()->updateMetadata(*_item, Vfs::DatabaseMetadata);
if (!result) {

View File

@ -413,10 +413,6 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
}
}
if (rec.isE2eEncrypted()) {
rec._e2eCertificateFingerprint = _account->encryptionCertificateFingerprint();
}
// Updating the db happens on success
if (!_journal->setFileRecord(rec)) {
item->_status = SyncFileItem::Status::NormalError;

View File

@ -110,7 +110,6 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri
rec._checksumHeader = _checksumHeader;
rec._e2eMangledName = _encryptedFileName.toUtf8();
rec._e2eEncryptionStatus = EncryptionStatusEnums::toDbEncryptionStatus(_e2eEncryptionStatus);
rec._e2eCertificateFingerprint = _e2eCertificateFingerprint;
rec._lockstate._locked = _locked == LockStatus::LockedItem;
rec._lockstate._lockOwnerDisplayName = _lockOwnerDisplayName;
rec._lockstate._lockOwnerId = _lockOwnerId;
@ -151,7 +150,6 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec
item->_encryptedFileName = rec.e2eMangledName();
item->_e2eEncryptionStatus = EncryptionStatusEnums::fromDbEncryptionStatus(rec._e2eEncryptionStatus);
item->_e2eEncryptionServerCapability = item->_e2eEncryptionStatus;
item->_e2eCertificateFingerprint = rec._e2eCertificateFingerprint;
item->_locked = rec._lockstate._locked ? LockStatus::LockedItem : LockStatus::UnlockedItem;
item->_lockOwnerDisplayName = rec._lockstate._lockOwnerDisplayName;
item->_lockOwnerId = rec._lockstate._lockOwnerId;

View File

@ -278,7 +278,6 @@ public:
EncryptionStatus _e2eEncryptionStatus = EncryptionStatus::NotEncrypted; // The file is E2EE or the content of the directory should be E2EE
EncryptionStatus _e2eEncryptionServerCapability = EncryptionStatus::NotEncrypted;
EncryptionStatus _e2eEncryptionStatusRemote = EncryptionStatus::NotEncrypted;
QByteArray _e2eCertificateFingerprint;
quint16 _httpErrorCode = 0;
RemotePermissions _remotePerm;
QString _errorString; // Contains a string only in case of error

View File

@ -47,7 +47,6 @@ void UpdateMigratedE2eeMetadataJob::start()
if (code == 200) {
_item->_e2eEncryptionStatus = updateMedatadaAndSubfoldersJob->encryptionStatus();
_item->_e2eEncryptionStatusRemote = updateMedatadaAndSubfoldersJob->encryptionStatus();
_item->_e2eCertificateFingerprint = propagator()->account()->encryptionCertificateFingerprint();
propagator()->updateMetadata(*_item, Vfs::UpdateMetadataType::DatabaseMetadata);
emit finished(SyncFileItem::Status::Success);
} else {

View File

@ -130,7 +130,7 @@ private slots:
const auto encryptedMetadataKey = QByteArray::fromBase64(folderUserObject.value("encryptedMetadataKey").toString().toUtf8());
if (!encryptedMetadataKey.isEmpty()) {
const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey, certificate.digest(QCryptographicHash::Sha256));
const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey);
if (decryptedMetadataKey.isEmpty()) {
break;
}
@ -269,7 +269,7 @@ private slots:
const auto encryptedMetadataKey = QByteArray::fromBase64(folderUserObject.value("encryptedMetadataKey").toString().toUtf8());
if (!encryptedMetadataKey.isEmpty()) {
const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey, certificate.digest(QCryptographicHash::Sha256));
const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey);
if (decryptedMetadataKey.isEmpty()) {
break;
}
@ -370,7 +370,7 @@ private slots:
const auto encryptedMetadataKey = QByteArray::fromBase64(folderUserObject.value("encryptedMetadataKey").toString().toUtf8());
if (!encryptedMetadataKey.isEmpty()) {
const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey, certificate.digest(QCryptographicHash::Sha256));
const auto decryptedMetadataKey = metadata->decryptDataWithPrivateKey(encryptedMetadataKey);
if (decryptedMetadataKey.isEmpty()) {
break;
}