From 01a84f1e145d5a9f62ae455fa5d4d0fa970f46d8 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 27 Sep 2023 16:30:42 +0200 Subject: [PATCH] wip Signed-off-by: tobiasKaminsky --- .../datamodel/e2e/v1/decrypted/Data.java | 4 +- .../encrypted/EncryptedFolderMetadataFile.kt | 4 +- .../operations/CreateFolderOperation.java | 12 ++--- .../CreateShareWithShareeOperation.java | 3 +- .../operations/RefreshFolderOperation.java | 7 ++- .../operations/UploadFileOperation.java | 32 ++++++++--- .../ui/fragment/OCFileListFragment.java | 10 ++-- .../android/utils/EncryptionUtils.java | 53 ++++++++++++------- .../android/utils/EncryptionUtilsV2.kt | 47 ++++++++++++++-- settings.gradle | 5 ++ 10 files changed, 133 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/datamodel/e2e/v1/decrypted/Data.java b/app/src/main/java/com/owncloud/android/datamodel/e2e/v1/decrypted/Data.java index 897303864c75..6424ac5f5e62 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/e2e/v1/decrypted/Data.java +++ b/app/src/main/java/com/owncloud/android/datamodel/e2e/v1/decrypted/Data.java @@ -26,7 +26,7 @@ public class Data { private String filename; private String mimetype; private String key; - private int version; + private double version; public String getKey() { return this.key; @@ -40,7 +40,7 @@ public String getMimetype() { return this.mimetype; } - public int getVersion() { + public double getVersion() { return this.version; } diff --git a/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt b/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt index aa771cf9f45e..648e723dccc8 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/e2e/v2/encrypted/EncryptedFolderMetadataFile.kt @@ -21,12 +21,14 @@ */ package com.owncloud.android.datamodel.e2e.v2.encrypted +import com.owncloud.android.datamodel.EncryptedFiledrop + /** * Decrypted class representation of metadata json of folder metadata. */ data class EncryptedFolderMetadataFile( val metadata: EncryptedMetadata, val users: List, - // val filedrop: Map, TODO re-enable in v2.1 + val filedrop: MutableMap, val version: String = "2.0" ) diff --git a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java index f85232eac335..8ddbb23a7387 100644 --- a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java @@ -156,9 +156,9 @@ private RemoteOperationResult encryptedCreateV1(OCFile parent, OwnCloudClient cl String encryptedFileName = createRandomFileName(metadata); encryptedRemotePath = parent.getRemotePath() + encryptedFileName; - RemoteOperationResult result = new CreateFolderRemoteOperation(encryptedRemotePath, - true, - token) + RemoteOperationResult result = new CreateFolderRemoteOperation(encryptedRemotePath, + true, + token) .execute(client); if (result.isSuccess()) { @@ -184,7 +184,7 @@ private RemoteOperationResult encryptedCreateV1(OCFile parent, OwnCloudClient cl // unlock folder if (token != null) { - RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token); + RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolderV1(parent, client, token); if (unlockFolderResult.isSuccess()) { token = null; @@ -217,7 +217,7 @@ private RemoteOperationResult encryptedCreateV1(OCFile parent, OwnCloudClient cl return result; } catch (Exception e) { - if (!EncryptionUtils.unlockFolder(parent, client, token).isSuccess()) { + if (!EncryptionUtils.unlockFolderV1(parent, client, token).isSuccess()) { throw new RuntimeException("Could not clean up after failing folder creation!", e); } @@ -241,7 +241,7 @@ private RemoteOperationResult encryptedCreateV1(OCFile parent, OwnCloudClient cl } finally { // unlock folder if (token != null) { - RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token); + RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolderV1(parent, client, token); if (!unlockFolderResult.isSuccess()) { // TODO E2E: do better diff --git a/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java index a6e955d015f2..3b770162ee67 100644 --- a/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java @@ -184,7 +184,8 @@ protected RemoteOperationResult run(OwnCloudClient client) { client, context, user, - token); + token + ); if (object instanceof DecryptedFolderMetadataFileV1) { throw new RuntimeException("Trying to share on e2e v1!"); diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index d8a22e6974e9..4d0a87db7198 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -58,6 +58,7 @@ import com.owncloud.android.utils.FileStorageUtils; import com.owncloud.android.utils.MimeType; import com.owncloud.android.utils.MimeTypeUtil; +import com.owncloud.android.utils.theme.CapabilityUtils; import java.util.ArrayList; import java.util.HashMap; @@ -484,8 +485,10 @@ private void synchronizeData(List folderAndFiles) { mContext); } - if (encryptedAncestor && object == null) { - throw new IllegalStateException("metadata is null!"); + if (CapabilityUtils.getCapability(mContext).getEndToEndEncryptionApiVersion().compareTo(E2EVersion.V2_0) >= 0) { + if (encryptedAncestor && object == null) { + throw new IllegalStateException("metadata is null!"); + } } // get current data about local contents of the folder to synchronize diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index c37b2bea482c..d175dd5c1de1 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -40,6 +40,7 @@ import com.owncloud.android.datamodel.e2e.v1.decrypted.Data; import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile; import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1; +import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedMetadata; import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFile; import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1; import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile; @@ -68,6 +69,7 @@ import com.owncloud.android.utils.MimeType; import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.UriUtils; +import com.owncloud.android.utils.theme.CapabilityUtils; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.RequestEntity; @@ -86,6 +88,7 @@ import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -461,8 +464,11 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare return result; } /***** E2E *****/ - // whenever we change something, increase counter - long counter = parentFile.getE2eCounter() + 1; + // Only on V2+: whenever we change something, increase counter + long counter = -1; + if (CapabilityUtils.getCapability(mContext).getEndToEndEncryptionApiVersion().compareTo(E2EVersion.V2_0) >= 0) { + counter = parentFile.getE2eCounter() + 1; + } // we might have an old token from interrupted upload if (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()) { @@ -484,11 +490,25 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare Object object = EncryptionUtils.downloadFolderMetadata(parentFile, client, mContext, user, token); - if (object == null) { - // TODO return error - return new RemoteOperationResult(new IllegalStateException("Metadata does not exist")); + if (CapabilityUtils.getCapability(mContext).getEndToEndEncryptionApiVersion().compareTo(E2EVersion.V2_0) >= 0) { + if (object == null) { + // TODO return error + return new RemoteOperationResult(new IllegalStateException("Metadata does not exist")); + } else { + metadataExists = true; + } } else { - metadataExists = true; + // v1 is allowed to be null, thus create it + DecryptedFolderMetadataFileV1 metadata = new DecryptedFolderMetadataFileV1(); + metadata.setMetadata(new DecryptedMetadata()); + metadata.getMetadata().setVersion(1.2); + metadata.getMetadata().setMetadataKeys(new HashMap<>()); + String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey()); + String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey); + metadata.getMetadata().setMetadataKey(encryptedMetadataKey); + + object = metadata; + metadataExists = false; } // todo fail if no metadata diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index b8f42d912fa9..c6c08ead2736 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -1772,18 +1772,20 @@ private void encryptFolder(OCFile folder, requireContext(), user, storageManager); + + // unlock folder + EncryptionUtils.unlockFolder(folder, client, token); + } else if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V1_0 || ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V1_1 || ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V1_2 ) { - // TODO encrypt on V1 + // unlock folder + EncryptionUtils.unlockFolderV1(folder, client, token); } else if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.UNKNOWN) { throw new IllegalArgumentException("Unknown E2E version"); } - // unlock folder - EncryptionUtils.unlockFolder(folder, client, token); - mAdapter.setEncryptionAttributeForItemID(remoteId, shouldBeEncrypted); } else if (remoteOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) { Snackbar.make(getRecyclerView(), diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java index 5542e9b4a850..ace52a3f4b42 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java @@ -60,7 +60,9 @@ import com.owncloud.android.lib.resources.e2ee.UpdateMetadataV2RemoteOperation; import com.owncloud.android.lib.resources.status.E2EVersion; import com.owncloud.android.lib.resources.status.NextcloudVersion; +import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.operations.UploadException; +import com.owncloud.android.utils.theme.CapabilityUtils; import org.apache.commons.httpclient.HttpStatus; @@ -415,19 +417,19 @@ public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolde return null; } + OCCapability capability = CapabilityUtils.getCapability(context); + // decrypt metadata EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2(); String serializedEncryptedMetadata = getMetadataOperationResult.getResultData().getMetadata(); - return encryptionUtilsV2.parseAnyMetadata(getMetadataOperationResult.getResultData(), - user, - client, - context, - folder); - /* - E2EVersion version = determinateVersion(serializedEncryptedMetadata); +// return encryptionUtilsV2.parseAnyMetadata(getMetadataOperationResult.getResultData(), +// user, +// client, +// context, +// folder); - + E2EVersion version = determinateVersion(serializedEncryptedMetadata); switch (version) { case UNKNOWN: @@ -437,16 +439,32 @@ public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolde case V1_0, V1_1, V1_2: ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context); String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY); + String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY); EncryptedFolderMetadataFileV1 encryptedFolderMetadata = EncryptionUtils.deserializeJSON( serializedEncryptedMetadata, new TypeToken<>() { }); try { - return decryptFolderMetaData(encryptedFolderMetadata, - privateKey, - arbitraryDataProvider, - user, - folder.getLocalId()); + DecryptedFolderMetadataFileV1 v1 = decryptFolderMetaData(encryptedFolderMetadata, + privateKey, + arbitraryDataProvider, + user, + folder.getLocalId()); + + if (capability.getEndToEndEncryptionApiVersion().compareTo(E2EVersion.V2_0) >= 0) { + new EncryptionUtilsV2().migrateV1ToV2( + v1, + client.getUserId(), + publicKey, + folder, + new FileDataStorageManager(user, context.getContentResolver()), + client, + user, + context + ); + } else { + return v1; + } } catch (Exception e) { // TODO do not crash, but show meaningful error Log_OC.e(TAG, "Could not decrypt metadata for " + folder.getDecryptedFileName(), e); @@ -461,8 +479,6 @@ public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolde folder); } return null; - - */ } public static E2EVersion determinateVersion(String metadata) { @@ -1278,17 +1294,17 @@ public static Pair retrieveMetadataV1(OC long localId = parentFile.getLocalId(); GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(localId); - RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client); + RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client); DecryptedFolderMetadataFileV1 metadata; if (getMetadataOperationResult.isSuccess()) { // decrypt metadata - String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0); + String serializedEncryptedMetadata = getMetadataOperationResult.getResultData().getMetadata(); EncryptedFolderMetadataFileV1 encryptedFolderMetadata = EncryptionUtils.deserializeJSON( - serializedEncryptedMetadata, new TypeToken() { + serializedEncryptedMetadata, new TypeToken<>() { }); return new Pair<>(Boolean.TRUE, decryptFolderMetaData(encryptedFolderMetadata, @@ -1298,6 +1314,7 @@ public static Pair retrieveMetadataV1(OC localId)); } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) { + // TODO extract // new metadata metadata = new DecryptedFolderMetadataFileV1(); metadata.setMetadata(new DecryptedMetadata()); diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt index 9e845d17608a..1b1ac45b3513 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt @@ -29,8 +29,10 @@ import com.google.gson.reflect.TypeToken import com.nextcloud.client.account.User import com.owncloud.android.datamodel.ArbitraryDataProvider import com.owncloud.android.datamodel.ArbitraryDataProviderImpl +import com.owncloud.android.datamodel.EncryptedFiledrop import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.datamodel.e2e.v1.decrypted.Data import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1 import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1 import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFile @@ -145,7 +147,8 @@ class EncryptionUtilsV2 { return EncryptedFolderMetadataFile( encryptedMetadata, - encryptedUsers + encryptedUsers, + mutableMapOf() ) // if (metadataFile.users.isEmpty()) { @@ -183,8 +186,6 @@ class EncryptionUtilsV2 { val parent = storageManager.getFileById(ocFile.parentId) ?: throw IllegalStateException("Cannot retrieve metadata") - // val decryptedFolderMetadataFile = if (encryptedUser == null) { - val decryptedFolderMetadataFile = if (parent.isEncrypted) { // we are in a subfolder, decrypt information is in top most encrypted folder val topMostMetadata = retrieveTopMostMetadata( @@ -199,10 +200,48 @@ class EncryptionUtilsV2 { decryptedMetadata.metadataKey = topMostMetadata.metadata.metadataKey decryptedMetadata.keyChecksums.addAll(topMostMetadata.metadata.keyChecksums) + val fileDrop = metadataFile.filedrop + if (fileDrop.isNotEmpty()) { + for (entry in fileDrop) { + val key: String = entry.key + val encryptedFile: EncryptedFiledrop = entry.value + + // decrypt key + val encryptedKey = EncryptionUtils.decryptStringAsymmetric( + encryptedFile.encryptedKey, + privateKey + ) + + // decrypt encrypted blob with key + val decryptedData = EncryptionUtils.decryptStringSymmetricAsString( + encryptedFile.encrypted, + EncryptionUtils.decodeStringToBase64Bytes(encryptedKey), + EncryptionUtils.decodeStringToBase64Bytes(encryptedFile.encryptedInitializationVector), + EncryptionUtils.decodeStringToBase64Bytes(encryptedFile.encryptedTag) + ) + + val data = EncryptionUtils.deserializeJSON(decryptedData, + object : TypeToken() {}) + + val decryptedFile = DecryptedFile( + data.filename, + data.mimetype, + encryptedFile.initializationVector, + encryptedFile.authenticationTag, + encryptedFile.encryptedKey + ) + + decryptedMetadata.files[key] = decryptedFile + + // remove from filedrop + fileDrop.remove(key) + } + } + DecryptedFolderMetadataFile( decryptedMetadata, topMostMetadata.users, - topMostMetadata.filedrop + mutableMapOf() ) } else { val encryptedUser = metadataFile.users.find { it.userId == userId } diff --git a/settings.gradle b/settings.gradle index 367bea707464..04f8de40c4a5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,3 +8,8 @@ include ':appscan' // substitute module('com.github.nextcloud.android-common:ui') using project(':ui') // } //} +includeBuild('../android-library') { + dependencySubstitution { + substitute module('com.github.nextcloud:android-library') using project(':library') + } +}