Skip to content
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-FileCopyrightText: 2023 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
Expand Down Expand Up @@ -300,8 +301,7 @@ class EncryptionUtilsV2IT : EncryptionIT() {
// random string, not real tag
EncryptionUtils.generateUid(),
EncryptionUtils.generateKey(),
metadataFile,
storageManager
metadataFile
)

assertEquals(3, updatedMetadata.metadata.files.size)
Expand Down Expand Up @@ -341,7 +341,6 @@ class EncryptionUtilsV2IT : EncryptionIT() {

@Test
fun addFolder() {
val folder = OCFile("/e/")
val enc1 = MockUser("enc1", "Nextcloud")
val metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert)
assertEquals(2, metadataFile.metadata.files.size)
Expand All @@ -350,9 +349,7 @@ class EncryptionUtilsV2IT : EncryptionIT() {
val updatedMetadata = encryptionUtilsV2.addFolderToMetadata(
EncryptionUtils.generateUid(),
"new subfolder",
metadataFile,
folder,
storageManager
metadataFile
)

assertEquals(2, updatedMetadata.metadata.files.size)
Expand All @@ -361,7 +358,6 @@ class EncryptionUtilsV2IT : EncryptionIT() {

@Test
fun removeFolder() {
val folder = OCFile("/e/")
val enc1 = MockUser("enc1", "Nextcloud")
val metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert)
assertEquals(2, metadataFile.metadata.files.size)
Expand All @@ -371,9 +367,7 @@ class EncryptionUtilsV2IT : EncryptionIT() {
var updatedMetadata = encryptionUtilsV2.addFolderToMetadata(
encryptedFileName,
"new subfolder",
metadataFile,
folder,
storageManager
metadataFile
)

assertEquals(2, updatedMetadata.metadata.files.size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.media.MediaScannerConnection;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.MediaStore;
Expand All @@ -50,8 +50,8 @@
import com.nextcloud.utils.e2ee.E2EVersionHelper;
import com.nextcloud.utils.extensions.DateExtensionsKt;
import com.nextcloud.utils.extensions.FileExtensionsKt;
import com.nextcloud.utils.extensions.StringExtensionsKt;
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
import com.owncloud.android.lib.common.network.WebdavEntry;
import com.owncloud.android.lib.common.utils.Log_OC;
Expand Down Expand Up @@ -2919,4 +2919,14 @@ public FileEntity getFileEntity(OCFile file) {
public void updateFileEntity(@NonNull FileEntity entity) {
fileDao.update(entity);
}

public void updateE2EECounter(OCFile file, DecryptedFolderMetadataFile metadata) {
updateE2EECounter(file, metadata.getMetadata().getCounter());
}

public void updateE2EECounter(OCFile file, long counter) {
Log_OC.d(TAG, "e2ee counter stored: " + counter + " for " + file.getDecryptedRemotePath());
file.setE2eCounter(counter);
saveFile(file);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-FileCopyrightText: 2020-2023 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2020 Andy Scherzinger <info@andy-scherzinger.de>
Expand Down Expand Up @@ -336,9 +337,7 @@ private RemoteOperationResult encryptedCreateV2(OCFile parent, OwnCloudClient cl
// update metadata
DecryptedFolderMetadataFile updatedMetadataFile = encryptionUtilsV2.addFolderToMetadata(encryptedFileName,
filename,
metadata,
parent,
getStorageManager());
metadata);

// upload metadata
encryptionUtilsV2.serializeAndUploadMetadata(parent,
Expand All @@ -350,6 +349,8 @@ private RemoteOperationResult encryptedCreateV2(OCFile parent, OwnCloudClient cl
user,
getStorageManager());

getStorageManager().updateE2EECounter(parent, metadata);

// unlock folder
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,10 +554,8 @@ private void synchronizeData(List<Object> folderAndFiles) {
localFilesMap = prefillLocalFilesMap(metadataFileV1, fileDataStorageManager.getFolderContent(mLocalFolder, false));
} else {
localFilesMap = prefillLocalFilesMap(object, fileDataStorageManager.getFolderContent(mLocalFolder, false));

// update counter
if (object != null) {
mLocalFolder.setE2eCounter(((DecryptedFolderMetadataFile) object).getMetadata().getCounter());
if (object instanceof DecryptedFolderMetadataFile metadataFile) {
mLocalFolder.setE2eCounter(metadataFile.getMetadata().getCounter());
}
}

Expand Down Expand Up @@ -599,14 +597,10 @@ private void synchronizeData(List<Object> folderAndFiles) {
FileStorageUtils.searchForLocalFileInDefaultPath(updatedFile, user.getAccountName());

// update file name for encrypted files
if (E2EVersionHelper.INSTANCE.isV1(e2EVersion)) {
updateFileNameForEncryptedFileV1(fileDataStorageManager,
(DecryptedFolderMetadataFileV1) object,
updatedFile);
} else if (object != null) {
updateFileNameForEncryptedFile(fileDataStorageManager,
(DecryptedFolderMetadataFile) object,
updatedFile);
if (E2EVersionHelper.INSTANCE.isV1(e2EVersion) && object instanceof DecryptedFolderMetadataFileV1 metadata) {
updateFileNameForEncryptedFileV1(fileDataStorageManager, metadata, updatedFile);
} else if (object instanceof DecryptedFolderMetadataFile metadata) {
updateFileNameForEncryptedFile(fileDataStorageManager, metadata, updatedFile);
if (localFile != null) {
updatedFile.setE2eCounter(localFile.getE2eCounter());
}
Expand All @@ -622,15 +616,12 @@ private void synchronizeData(List<Object> folderAndFiles) {

// save updated contents in local database
// update file name for encrypted files
if (E2EVersionHelper.INSTANCE.isV1(e2EVersion)) {
updateFileNameForEncryptedFileV1(fileDataStorageManager,
(DecryptedFolderMetadataFileV1) object,
mLocalFolder);
} else {
updateFileNameForEncryptedFile(fileDataStorageManager,
(DecryptedFolderMetadataFile) object,
mLocalFolder);
if (E2EVersionHelper.INSTANCE.isV1(e2EVersion) && object instanceof DecryptedFolderMetadataFileV1 metadata) {
updateFileNameForEncryptedFileV1(fileDataStorageManager, metadata, mLocalFolder);
} else if (object instanceof DecryptedFolderMetadataFile metadata) {
updateFileNameForEncryptedFile(fileDataStorageManager, metadata, mLocalFolder);
}

fileDataStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());

mChildren = updatedFiles;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,7 @@ class RemoveRemoteEncryptedFileOperation internal constructor(
encryptionUtilsV2.removeFileFromMetadata(fileName, metadata)
}

parentFolder.setE2eCounter(metadata.metadata.counter)
val storageManager = FileDataStorageManager(user, context.contentResolver)
storageManager.saveFile(parentFolder)

@alperozturk96 alperozturk96 Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be after serializeAndUploadMetadata


encryptionUtilsV2.serializeAndUploadMetadata(
parentFolder,
Expand All @@ -198,6 +196,8 @@ class RemoveRemoteEncryptedFileOperation internal constructor(
storageManager
)

storageManager.updateE2EECounter(parentFolder, metadata)

return Pair(result, delete)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-FileCopyrightText: 2020-2023 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2021 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2017-2018 Andy Scherzinger <info@andy-scherzinger.de>
Expand Down Expand Up @@ -601,7 +602,7 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare
result = performE2EUpload(clientData);

if (result.isSuccess()) {
updateMetadataForE2E(object, e2eData, clientData, e2eFiles, arbitraryDataProvider, encryptionUtilsV2, isV1MetadataExists);
upsertMetadata(object, e2eData, clientData, e2eFiles, arbitraryDataProvider, encryptionUtilsV2, isV1MetadataExists);
}
} catch (FileNotFoundException e) {
Log_OC.e(TAG, mFile.getStoragePath() + " does not exist anymore");
Expand All @@ -613,13 +614,13 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare
Log_OC.e(TAG, "UploadFileOperation exception: " + e.getLocalizedMessage());
result = new RemoteOperationResult<>(e);
} finally {
result = cleanupE2EUpload(fileLock, channel, e2eFiles, result, object, client, token);
result = releaseLocksAndUnlockE2EFolder(fileLock, channel, e2eFiles, result, object, client, token);

// update upload status
uploadsStorageManager.updateDatabaseUploadResult(result, this);
}

completeE2EUpload(result, e2eFiles, client);
handleLocalBehaviourOrSaveConflictAndDeleteTemporalFile(result, e2eFiles, client);

return result;
}
Expand Down Expand Up @@ -811,7 +812,10 @@ private E2EData getE2EData(Object object) throws InvalidAlgorithmParameterExcept
return new E2EData(key, iv, encryptedFile, encryptedFileName);
}

private void updateMetadataForE2E(Object object, E2EData e2eData, E2EClientData clientData, E2EFiles e2eFiles, ArbitraryDataProvider arbitraryDataProvider, EncryptionUtilsV2 encryptionUtilsV2, boolean isV1MetadataExists)
/**
* Stores or updates metadata of the encrypted file along with e2ee counter.
*/
private void upsertMetadata(Object object, E2EData e2eData, E2EClientData clientData, E2EFiles e2eFiles, ArbitraryDataProvider arbitraryDataProvider, EncryptionUtilsV2 encryptionUtilsV2, boolean isV1MetadataExists)

throws InvalidAlgorithmParameterException, UploadException, NoSuchPaddingException, IllegalBlockSizeException, CertificateException,
NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
Expand Down Expand Up @@ -892,11 +896,7 @@ private void updateMetadataForV2(DecryptedFolderMetadataFile metadata, Encryptio
e2eData.getIv(),
e2eData.getEncryptedFile().getAuthenticationTag(),
e2eData.getKey(),
metadata,
getStorageManager());

parentFile.setE2eCounter(metadata.getMetadata().getCounter());

@alperozturk96 alperozturk96 Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be after serializeAndUploadMetadata

getStorageManager().saveFile(parentFile);
metadata);

// upload metadata
encryptionUtilsV2.serializeAndUploadMetadata(parentFile,
Expand All @@ -909,7 +909,7 @@ private void updateMetadataForV2(DecryptedFolderMetadataFile metadata, Encryptio
getStorageManager());
}

private void completeE2EUpload(RemoteOperationResult result, E2EFiles e2eFiles, OwnCloudClient client) {
private void handleLocalBehaviourOrSaveConflictAndDeleteTemporalFile(RemoteOperationResult result, E2EFiles e2eFiles, OwnCloudClient client) {
if (result.isSuccess()) {
handleLocalBehaviour(e2eFiles.getTemporalFile(), e2eFiles.getExpectedFile(), e2eFiles.getOriginalFile(), client);
} else if (RemoteOperationResultExtensionsKt.isConflict(result.getCode())) {
Expand All @@ -919,7 +919,7 @@ private void completeE2EUpload(RemoteOperationResult result, E2EFiles e2eFiles,
e2eFiles.deleteTemporalFile();
}

private RemoteOperationResult cleanupE2EUpload(FileLock fileLock, FileChannel channel, E2EFiles e2eFiles, RemoteOperationResult result, Object object, OwnCloudClient client, String token) {
private RemoteOperationResult releaseLocksAndUnlockE2EFolder(FileLock fileLock, FileChannel channel, E2EFiles e2eFiles, RemoteOperationResult result, Object object, OwnCloudClient client, String token) {
mUploadStarted.set(false);

if (fileLock != null) {
Expand Down Expand Up @@ -989,6 +989,12 @@ private RemoteOperationResult cleanupE2EUpload(FileLock fileLock, FileChannel ch
return Unit.INSTANCE;
});
}

// only update local counter after unlock
if (object instanceof DecryptedFolderMetadataFile metadata) {
final var parentFile = e2eFiles.getParentFile();
getStorageManager().updateE2EECounter(parentFile, metadata);
}
}

e2eFiles.deleteEncryptedTempFile();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ public void updateFileEncryptionById(String fileId, boolean encrypted) {
e2eCounter = EncryptionUtils.E2E_V2_INITIAL_COUNTER;
}

file.setE2eCounter(e2eCounter);
mStorageManager.saveFile(file);
mStorageManager.updateE2EECounter(file, e2eCounter);

int position = getItemPosition(file);
if (position != -1) {
notifyItemChanged(position);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,8 +423,7 @@ class FileDetailSharingFragment :
val client = clientFactory.create(user)
val metadata = RefreshFolderOperation.getDecryptedFolderMetadata(true, file, client, user, context)
if (metadata is DecryptedFolderMetadataFile) {
file?.setE2eCounter(metadata.metadata.counter)
fileDataStorageManager?.saveFile(file)
fileDataStorageManager?.updateE2EECounter(file, metadata)
}
true
} catch (e: Exception) {
Expand Down
17 changes: 6 additions & 11 deletions app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-FileCopyrightText: 2023 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
Expand All @@ -12,7 +13,6 @@ import android.content.Context
import androidx.annotation.VisibleForTesting
import com.google.gson.reflect.TypeToken
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManagerImpl
import com.nextcloud.utils.CmsSignatureVerifier
import com.nextcloud.utils.autoRename.AutoRename
import com.nextcloud.utils.e2ee.E2EVersionHelper
Expand Down Expand Up @@ -474,8 +474,7 @@ class EncryptionUtilsV2 {
initializationVector: ByteArray,
authenticationTag: String,
key: ByteArray,
metadataFile: DecryptedFolderMetadataFile,
fileDataStorageManager: FileDataStorageManager
metadataFile: DecryptedFolderMetadataFile
): DecryptedFolderMetadataFile {
val decryptedFile = DecryptedFile(
ocFile.decryptedFileName,
Expand All @@ -487,23 +486,17 @@ class EncryptionUtilsV2 {

metadataFile.metadata.files[encryptedFileName] = decryptedFile
metadataFile.metadata.counter++
ocFile.setE2eCounter(metadataFile.metadata.counter)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addFileToMetadata has one caller and that already updates counter. No need to double it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also aligns with other functions such as removeFolderFromMetadata

fileDataStorageManager.saveFile(ocFile)

return metadataFile
}

fun addFolderToMetadata(
encryptedFileName: String,
fileName: String,
metadataFile: DecryptedFolderMetadataFile,
ocFile: OCFile,
fileDataStorageManager: FileDataStorageManager
metadataFile: DecryptedFolderMetadataFile
): DecryptedFolderMetadataFile {
metadataFile.metadata.folders[encryptedFileName] = fileName
metadataFile.metadata.counter++
ocFile.setE2eCounter(metadataFile.metadata.counter)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addFolderToMetadata has one caller and that already updates counter. No need to double it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also aligns with other functions such as removeFolderFromMetadata

fileDataStorageManager.saveFile(ocFile)

return metadataFile
}
Expand Down Expand Up @@ -861,7 +854,9 @@ class EncryptionUtilsV2 {
oldCounter: Long,
signature: String
): Boolean {
if (decryptedFolderMetadataFile.metadata.counter < oldCounter) {
val metadataCounter = decryptedFolderMetadataFile.metadata.counter
if (metadataCounter < oldCounter) {
Log_OC.e(TAG, "old counter: $oldCounter, metadata counter $metadataCounter")
MainApp.showMessage(R.string.e2e_counter_too_old)
return false
}
Expand Down