mirror of
https://github.com/nextcloud/android.git
synced 2024-11-21 12:45:32 +03:00
extract tests
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
parent
2382361e82
commit
218fbd7815
71 changed files with 6370 additions and 1617 deletions
|
@ -1,6 +1,9 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="ktlint" />
|
||||
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.nio.channels.FileChannel,position" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
</profile>
|
||||
|
|
|
@ -266,7 +266,7 @@ dependencies {
|
|||
implementation 'org.greenrobot:eventbus:3.3.1'
|
||||
implementation 'com.googlecode.ez-vcard:ez-vcard:0.12.0'
|
||||
implementation 'org.lukhnos:nnio:0.2'
|
||||
implementation 'org.bouncycastle:bcpkix-jdk15to18:1.72'
|
||||
implementation 'org.bouncycastle:bcpkix-jdk18on:1.75'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'com.github.nextcloud-deps:sectioned-recyclerview:0.6.1'
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
<issue id="TrustAllX509TrustManager">
|
||||
<ignore path="**/bouncycastle/est/jcajce/*.class" />
|
||||
<ignore path="**/bcpkix-jdk15to18-1.72.jar" />
|
||||
<ignore path="**/bcpkix-jdk18on-1.75.jar" />
|
||||
</issue>
|
||||
|
||||
<issue id="RestrictedApi" severity="error">
|
||||
|
|
1191
app/schemas/com.nextcloud.client.database.NextcloudDatabase/77.json
Normal file
1191
app/schemas/com.nextcloud.client.database.NextcloudDatabase/77.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.client;
|
||||
|
||||
public enum EndToEndAction {
|
||||
CREATE_FOLDER,
|
||||
GO_INTO_FOLDER,
|
||||
GO_UP,
|
||||
UPLOAD_FILE,
|
||||
DOWNLOAD_FILE,
|
||||
DELETE_FILE,
|
||||
}
|
|
@ -1,730 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2020 Tobias Kaminsky
|
||||
* Copyright (C) 2020 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.client;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
|
||||
import com.nextcloud.test.RandomStringGenerator;
|
||||
import com.nextcloud.test.RetryTestRule;
|
||||
import com.owncloud.android.AbstractOnServerIT;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.db.OCUpload;
|
||||
import com.owncloud.android.files.services.FileUploader;
|
||||
import com.owncloud.android.lib.common.accounts.AccountUtils;
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||
import com.owncloud.android.lib.ocs.responses.PrivateKey;
|
||||
import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.status.OCCapability;
|
||||
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
|
||||
import com.owncloud.android.lib.resources.users.DeletePrivateKeyOperation;
|
||||
import com.owncloud.android.lib.resources.users.DeletePublicKeyOperation;
|
||||
import com.owncloud.android.lib.resources.users.GetPrivateKeyOperation;
|
||||
import com.owncloud.android.lib.resources.users.GetPublicKeyOperation;
|
||||
import com.owncloud.android.lib.resources.users.SendCSROperation;
|
||||
import com.owncloud.android.lib.resources.users.StorePrivateKeyOperation;
|
||||
import com.owncloud.android.operations.DownloadFileOperation;
|
||||
import com.owncloud.android.operations.GetCapabilitiesOperation;
|
||||
import com.owncloud.android.operations.RemoveFileOperation;
|
||||
import com.owncloud.android.utils.CsrHelper;
|
||||
import com.owncloud.android.utils.EncryptionUtils;
|
||||
import com.owncloud.android.utils.FileStorageUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyPair;
|
||||
import java.security.interfaces.RSAPrivateCrtKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import static com.owncloud.android.lib.resources.status.OwnCloudVersion.nextcloud_19;
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertFalse;
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EndToEndRandomIT extends AbstractOnServerIT {
|
||||
public enum Action {
|
||||
CREATE_FOLDER,
|
||||
GO_INTO_FOLDER,
|
||||
GO_UP,
|
||||
UPLOAD_FILE,
|
||||
DOWNLOAD_FILE,
|
||||
DELETE_FILE,
|
||||
}
|
||||
|
||||
private static ArbitraryDataProvider arbitraryDataProvider;
|
||||
|
||||
private OCFile currentFolder;
|
||||
private int actionCount = 20;
|
||||
private String rootEncFolder = "/e/";
|
||||
|
||||
@Rule
|
||||
public RetryTestRule retryTestRule = new RetryTestRule();
|
||||
|
||||
@BeforeClass
|
||||
public static void initClass() throws Exception {
|
||||
arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
createKeys();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() throws IOException {
|
||||
OCCapability capability = getStorageManager().getCapability(account.name);
|
||||
|
||||
if (capability.getVersion().equals(new OwnCloudVersion("0.0.0"))) {
|
||||
// fetch new one
|
||||
assertTrue(new GetCapabilitiesOperation(getStorageManager())
|
||||
.execute(client)
|
||||
.isSuccess());
|
||||
}
|
||||
// tests only for NC19+
|
||||
assumeTrue(getStorageManager()
|
||||
.getCapability(account.name)
|
||||
.getVersion()
|
||||
.isNewerOrEqual(nextcloud_19)
|
||||
);
|
||||
|
||||
// make sure that every file is available, even after tests that remove source file
|
||||
createDummyFiles();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run() throws Exception {
|
||||
init();
|
||||
|
||||
for (int i = 0; i < actionCount; i++) {
|
||||
Action nextAction = Action.values()[new Random().nextInt(Action.values().length)];
|
||||
|
||||
switch (nextAction) {
|
||||
case CREATE_FOLDER:
|
||||
createFolder(i);
|
||||
break;
|
||||
|
||||
case GO_INTO_FOLDER:
|
||||
goIntoFolder(i);
|
||||
break;
|
||||
|
||||
case GO_UP:
|
||||
goUp(i);
|
||||
break;
|
||||
|
||||
case UPLOAD_FILE:
|
||||
uploadFile(i);
|
||||
break;
|
||||
|
||||
case DOWNLOAD_FILE:
|
||||
downloadFile(i);
|
||||
break;
|
||||
|
||||
case DELETE_FILE:
|
||||
deleteFile(i);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log_OC.d(this, "[" + i + "/" + actionCount + "]" + " Unknown action: " + nextAction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uploadOneFile() throws Exception {
|
||||
init();
|
||||
|
||||
uploadFile(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFolder() throws Exception {
|
||||
init();
|
||||
|
||||
currentFolder = createFolder(0);
|
||||
assertNotNull(currentFolder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSubFolders() throws Exception {
|
||||
init();
|
||||
|
||||
currentFolder = createFolder(0);
|
||||
assertNotNull(currentFolder);
|
||||
|
||||
currentFolder = createFolder(1);
|
||||
assertNotNull(currentFolder);
|
||||
|
||||
currentFolder = createFolder(2);
|
||||
assertNotNull(currentFolder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSubFoldersWithFiles() throws Exception {
|
||||
init();
|
||||
|
||||
currentFolder = createFolder(0);
|
||||
assertNotNull(currentFolder);
|
||||
|
||||
uploadFile(1);
|
||||
uploadFile(1);
|
||||
uploadFile(2);
|
||||
|
||||
currentFolder = createFolder(1);
|
||||
assertNotNull(currentFolder);
|
||||
uploadFile(11);
|
||||
uploadFile(12);
|
||||
uploadFile(13);
|
||||
|
||||
currentFolder = createFolder(2);
|
||||
assertNotNull(currentFolder);
|
||||
|
||||
uploadFile(21);
|
||||
uploadFile(22);
|
||||
uploadFile(23);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pseudoRandom() throws Exception {
|
||||
init();
|
||||
|
||||
uploadFile(1);
|
||||
createFolder(2);
|
||||
goIntoFolder(3);
|
||||
goUp(4);
|
||||
createFolder(5);
|
||||
uploadFile(6);
|
||||
goUp(7);
|
||||
goIntoFolder(8);
|
||||
goIntoFolder(9);
|
||||
uploadFile(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteFile() throws Exception {
|
||||
init();
|
||||
|
||||
uploadFile(1);
|
||||
deleteFile(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteFolder() throws Exception {
|
||||
init();
|
||||
|
||||
// create folder, go into it
|
||||
OCFile createdFolder = createFolder(0);
|
||||
assertNotNull(createdFolder);
|
||||
currentFolder = createdFolder;
|
||||
|
||||
uploadFile(1);
|
||||
goUp(1);
|
||||
|
||||
// delete folder
|
||||
assertTrue(new RemoveFileOperation(createdFolder,
|
||||
false,
|
||||
user,
|
||||
false,
|
||||
targetContext,
|
||||
getStorageManager())
|
||||
.execute(client)
|
||||
.isSuccess());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void downloadFile() throws Exception {
|
||||
init();
|
||||
|
||||
uploadFile(1);
|
||||
downloadFile(1);
|
||||
}
|
||||
|
||||
private void init() throws Exception {
|
||||
// create folder
|
||||
createFolder(rootEncFolder);
|
||||
OCFile encFolder = createFolder(rootEncFolder + RandomStringGenerator.make(5) + "/");
|
||||
|
||||
// encrypt it
|
||||
assertTrue(new ToggleEncryptionRemoteOperation(encFolder.getLocalId(),
|
||||
encFolder.getRemotePath(),
|
||||
true)
|
||||
.execute(client).isSuccess());
|
||||
encFolder.setEncrypted(true);
|
||||
getStorageManager().saveFolder(encFolder, new ArrayList<>(), new ArrayList<>());
|
||||
|
||||
useExistingKeys();
|
||||
|
||||
rootEncFolder = encFolder.getDecryptedRemotePath();
|
||||
currentFolder = encFolder;
|
||||
}
|
||||
|
||||
private OCFile createFolder(int i) {
|
||||
String path = currentFolder.getDecryptedRemotePath() + RandomStringGenerator.make(5) + "/";
|
||||
Log_OC.d(this, "[" + i + "/" + actionCount + "] " + "Create folder: " + path);
|
||||
|
||||
return createFolder(path);
|
||||
}
|
||||
|
||||
private void goIntoFolder(int i) {
|
||||
ArrayList<OCFile> folders = new ArrayList<>();
|
||||
for (OCFile file : getStorageManager().getFolderContent(currentFolder, false)) {
|
||||
if (file.isFolder()) {
|
||||
folders.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (folders.isEmpty()) {
|
||||
Log_OC.d(this, "[" + i + "/" + actionCount + "] " + "Go into folder: No folders");
|
||||
return;
|
||||
}
|
||||
|
||||
currentFolder = folders.get(new Random().nextInt(folders.size()));
|
||||
Log_OC.d(this,
|
||||
"[" + i + "/" + actionCount + "] " + "Go into folder: " + currentFolder.getDecryptedRemotePath());
|
||||
}
|
||||
|
||||
private void goUp(int i) {
|
||||
if (currentFolder.getRemotePath().equals(rootEncFolder)) {
|
||||
Log_OC.d(this,
|
||||
"[" + i + "/" + actionCount + "] " + "Go up to folder: " + currentFolder.getDecryptedRemotePath());
|
||||
return;
|
||||
}
|
||||
|
||||
currentFolder = getStorageManager().getFileById(currentFolder.getParentId());
|
||||
if (currentFolder == null) {
|
||||
throw new RuntimeException("Current folder is null");
|
||||
}
|
||||
|
||||
Log_OC.d(this,
|
||||
"[" + i + "/" + actionCount + "] " + "Go up to folder: " + currentFolder.getDecryptedRemotePath());
|
||||
}
|
||||
|
||||
private void uploadFile(int i) throws IOException {
|
||||
String fileName = RandomStringGenerator.make(5) + ".txt";
|
||||
|
||||
File file;
|
||||
if (new Random().nextBoolean()) {
|
||||
file = createFile(fileName, new Random().nextInt(50000));
|
||||
} else {
|
||||
file = createFile(fileName, 500000 + new Random().nextInt(50000));
|
||||
}
|
||||
|
||||
String remotePath = currentFolder.getRemotePath() + fileName;
|
||||
|
||||
Log_OC.d(this,
|
||||
"[" + i + "/" + actionCount + "] " +
|
||||
"Upload file to: " + currentFolder.getDecryptedRemotePath() + fileName);
|
||||
|
||||
OCUpload ocUpload = new OCUpload(file.getAbsolutePath(),
|
||||
remotePath,
|
||||
account.name);
|
||||
uploadOCUpload(ocUpload);
|
||||
shortSleep();
|
||||
|
||||
OCFile parentFolder = getStorageManager()
|
||||
.getFileByEncryptedRemotePath(new File(ocUpload.getRemotePath()).getParent() + "/");
|
||||
String uploadedFileName = new File(ocUpload.getRemotePath()).getName();
|
||||
|
||||
String decryptedPath = parentFolder.getDecryptedRemotePath() + uploadedFileName;
|
||||
|
||||
OCFile uploadedFile = getStorageManager().getFileByDecryptedRemotePath(decryptedPath);
|
||||
verifyStoragePath(uploadedFile);
|
||||
|
||||
// verify storage path
|
||||
refreshFolder(currentFolder.getRemotePath());
|
||||
uploadedFile = getStorageManager().getFileByDecryptedRemotePath(decryptedPath);
|
||||
verifyStoragePath(uploadedFile);
|
||||
|
||||
// verify that encrypted file is on server
|
||||
assertTrue(new ReadFileRemoteOperation(currentFolder.getRemotePath() + uploadedFile.getEncryptedFileName())
|
||||
.execute(client)
|
||||
.isSuccess());
|
||||
|
||||
// verify that unencrypted file is not on server
|
||||
assertFalse(new ReadFileRemoteOperation(currentFolder.getDecryptedRemotePath() + fileName)
|
||||
.execute(client)
|
||||
.isSuccess());
|
||||
}
|
||||
|
||||
private void downloadFile(int i) {
|
||||
ArrayList<OCFile> files = new ArrayList<>();
|
||||
for (OCFile file : getStorageManager().getFolderContent(currentFolder, false)) {
|
||||
if (!file.isFolder()) {
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (files.isEmpty()) {
|
||||
Log_OC.d(this, "[" + i + "/" + actionCount + "] No files in: " + currentFolder.getDecryptedRemotePath());
|
||||
return;
|
||||
}
|
||||
|
||||
OCFile fileToDownload = files.get(new Random().nextInt(files.size()));
|
||||
assertNotNull(fileToDownload.getRemoteId());
|
||||
|
||||
Log_OC.d(this,
|
||||
"[" + i + "/" + actionCount + "] " + "Download file: " +
|
||||
currentFolder.getDecryptedRemotePath() + fileToDownload.getDecryptedFileName());
|
||||
|
||||
assertTrue(new DownloadFileOperation(user, fileToDownload, targetContext)
|
||||
.execute(client)
|
||||
.isSuccess());
|
||||
|
||||
assertTrue(new File(fileToDownload.getStoragePath()).exists());
|
||||
verifyStoragePath(fileToDownload);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadWithCopy() throws Exception {
|
||||
init();
|
||||
|
||||
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
|
||||
currentFolder.getRemotePath() + "nonEmpty.txt",
|
||||
account.name);
|
||||
|
||||
uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_COPY);
|
||||
|
||||
File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
|
||||
OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(currentFolder.getRemotePath() +
|
||||
"nonEmpty.txt");
|
||||
|
||||
assertTrue(originalFile.exists());
|
||||
assertTrue(new File(uploadedFile.getStoragePath()).exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadWithMove() throws Exception {
|
||||
init();
|
||||
|
||||
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
|
||||
currentFolder.getRemotePath() + "nonEmpty.txt",
|
||||
account.name);
|
||||
|
||||
uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_MOVE);
|
||||
|
||||
File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
|
||||
OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(currentFolder.getRemotePath() +
|
||||
"nonEmpty.txt");
|
||||
|
||||
assertFalse(originalFile.exists());
|
||||
assertTrue(new File(uploadedFile.getStoragePath()).exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadWithForget() throws Exception {
|
||||
init();
|
||||
|
||||
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
|
||||
currentFolder.getRemotePath() + "nonEmpty.txt",
|
||||
account.name);
|
||||
|
||||
uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_FORGET);
|
||||
|
||||
File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
|
||||
OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(currentFolder.getRemotePath() +
|
||||
"nonEmpty.txt");
|
||||
|
||||
assertTrue(originalFile.exists());
|
||||
assertFalse(new File(uploadedFile.getStoragePath()).exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadWithDelete() throws Exception {
|
||||
init();
|
||||
|
||||
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt",
|
||||
currentFolder.getRemotePath() + "nonEmpty.txt",
|
||||
account.name);
|
||||
|
||||
uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_DELETE);
|
||||
|
||||
File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
|
||||
OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(currentFolder.getRemotePath() +
|
||||
"nonEmpty.txt");
|
||||
|
||||
assertFalse(originalFile.exists());
|
||||
assertFalse(new File(uploadedFile.getStoragePath()).exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckCSR() throws Exception {
|
||||
deleteKeys();
|
||||
|
||||
// Create public/private key pair
|
||||
KeyPair keyPair = EncryptionUtils.generateKeyPair();
|
||||
|
||||
// create CSR
|
||||
AccountManager accountManager = AccountManager.get(targetContext);
|
||||
String userId = accountManager.getUserData(account, AccountUtils.Constants.KEY_USER_ID);
|
||||
String urlEncoded = CsrHelper.generateCsrPemEncodedString(keyPair, userId);
|
||||
|
||||
SendCSROperation operation = new SendCSROperation(urlEncoded);
|
||||
RemoteOperationResult result = operation.execute(account, targetContext);
|
||||
|
||||
assertTrue(result.isSuccess());
|
||||
String publicKeyString = (String) result.getData().get(0);
|
||||
|
||||
// check key
|
||||
RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyPair.getPrivate();
|
||||
RSAPublicKey publicKey = EncryptionUtils.convertPublicKeyFromString(publicKeyString);
|
||||
|
||||
BigInteger modulusPublic = publicKey.getModulus();
|
||||
BigInteger modulusPrivate = privateKey.getModulus();
|
||||
|
||||
assertEquals(modulusPrivate, modulusPublic);
|
||||
|
||||
createKeys();
|
||||
}
|
||||
|
||||
private void deleteFile(int i) {
|
||||
ArrayList<OCFile> files = new ArrayList<>();
|
||||
for (OCFile file : getStorageManager().getFolderContent(currentFolder, false)) {
|
||||
if (!file.isFolder()) {
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (files.isEmpty()) {
|
||||
Log_OC.d(this, "[" + i + "/" + actionCount + "] No files in: " + currentFolder.getDecryptedRemotePath());
|
||||
return;
|
||||
}
|
||||
|
||||
OCFile fileToDelete = files.get(new Random().nextInt(files.size()));
|
||||
assertNotNull(fileToDelete.getRemoteId());
|
||||
|
||||
Log_OC.d(this,
|
||||
"[" + i + "/" + actionCount + "] " +
|
||||
"Delete file: " + currentFolder.getDecryptedRemotePath() + fileToDelete.getDecryptedFileName());
|
||||
|
||||
assertTrue(new RemoveFileOperation(fileToDelete,
|
||||
false,
|
||||
user,
|
||||
false,
|
||||
targetContext,
|
||||
getStorageManager())
|
||||
.execute(client)
|
||||
.isSuccess());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reInit() throws Exception {
|
||||
// create folder
|
||||
OCFile encFolder = createFolder(rootEncFolder);
|
||||
|
||||
// encrypt it
|
||||
assertTrue(new ToggleEncryptionRemoteOperation(encFolder.getLocalId(),
|
||||
encFolder.getRemotePath(),
|
||||
true)
|
||||
.execute(client).isSuccess());
|
||||
encFolder.setEncrypted(true);
|
||||
getStorageManager().saveFolder(encFolder, new ArrayList<>(), new ArrayList<>());
|
||||
|
||||
|
||||
// delete keys
|
||||
arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.PRIVATE_KEY);
|
||||
arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.PUBLIC_KEY);
|
||||
arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.MNEMONIC);
|
||||
|
||||
useExistingKeys();
|
||||
}
|
||||
|
||||
private void useExistingKeys() throws Exception {
|
||||
// download them from server
|
||||
GetPublicKeyOperation publicKeyOperation = new GetPublicKeyOperation();
|
||||
RemoteOperationResult<String> publicKeyResult = publicKeyOperation.execute(account, targetContext);
|
||||
|
||||
assertTrue("Result code:" + publicKeyResult.getHttpCode(), publicKeyResult.isSuccess());
|
||||
|
||||
String publicKeyFromServer = publicKeyResult.getResultData();
|
||||
arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
|
||||
EncryptionUtils.PUBLIC_KEY,
|
||||
publicKeyFromServer);
|
||||
|
||||
RemoteOperationResult<PrivateKey> privateKeyResult = new GetPrivateKeyOperation().execute(account,
|
||||
targetContext);
|
||||
assertTrue(privateKeyResult.isSuccess());
|
||||
|
||||
PrivateKey privateKey = privateKeyResult.getResultData();
|
||||
|
||||
String mnemonic = generateMnemonicString();
|
||||
String decryptedPrivateKey = EncryptionUtils.decryptPrivateKey(privateKey.getKey(), mnemonic);
|
||||
|
||||
arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
|
||||
EncryptionUtils.PRIVATE_KEY, decryptedPrivateKey);
|
||||
|
||||
Log_OC.d(this, "Private key successfully decrypted and stored");
|
||||
|
||||
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.MNEMONIC, mnemonic);
|
||||
}
|
||||
|
||||
/*
|
||||
TODO do not c&p code
|
||||
*/
|
||||
private static void createKeys() throws Exception {
|
||||
deleteKeys();
|
||||
|
||||
String publicKeyString;
|
||||
|
||||
// Create public/private key pair
|
||||
KeyPair keyPair = EncryptionUtils.generateKeyPair();
|
||||
|
||||
// create CSR
|
||||
AccountManager accountManager = AccountManager.get(targetContext);
|
||||
String userId = accountManager.getUserData(account, AccountUtils.Constants.KEY_USER_ID);
|
||||
String urlEncoded = CsrHelper.generateCsrPemEncodedString(keyPair, userId);
|
||||
|
||||
SendCSROperation operation = new SendCSROperation(urlEncoded);
|
||||
RemoteOperationResult result = operation.execute(account, targetContext);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
publicKeyString = (String) result.getData().get(0);
|
||||
|
||||
// check key
|
||||
RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyPair.getPrivate();
|
||||
RSAPublicKey publicKey = EncryptionUtils.convertPublicKeyFromString(publicKeyString);
|
||||
|
||||
BigInteger modulusPublic = publicKey.getModulus();
|
||||
BigInteger modulusPrivate = privateKey.getModulus();
|
||||
|
||||
if (modulusPrivate.compareTo(modulusPublic) != 0) {
|
||||
throw new RuntimeException("Wrong CSR returned");
|
||||
}
|
||||
} else {
|
||||
throw new Exception("failed to send CSR", result.getException());
|
||||
}
|
||||
|
||||
java.security.PrivateKey privateKey = keyPair.getPrivate();
|
||||
String privateKeyString = EncryptionUtils.encodeBytesToBase64String(privateKey.getEncoded());
|
||||
String privatePemKeyString = EncryptionUtils.privateKeyToPEM(privateKey);
|
||||
String encryptedPrivateKey = EncryptionUtils.encryptPrivateKey(privatePemKeyString,
|
||||
generateMnemonicString());
|
||||
|
||||
// upload encryptedPrivateKey
|
||||
StorePrivateKeyOperation storePrivateKeyOperation = new StorePrivateKeyOperation(encryptedPrivateKey);
|
||||
RemoteOperationResult storePrivateKeyResult = storePrivateKeyOperation.execute(account, targetContext);
|
||||
|
||||
if (storePrivateKeyResult.isSuccess()) {
|
||||
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.PRIVATE_KEY,
|
||||
privateKeyString);
|
||||
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.PUBLIC_KEY, publicKeyString);
|
||||
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.MNEMONIC,
|
||||
generateMnemonicString());
|
||||
} else {
|
||||
throw new RuntimeException("Error uploading private key!");
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteKeys() {
|
||||
RemoteOperationResult<PrivateKey> privateKeyRemoteOperationResult = new GetPrivateKeyOperation().execute(client);
|
||||
RemoteOperationResult<String> publicKeyRemoteOperationResult = new GetPublicKeyOperation().execute(client);
|
||||
|
||||
if (privateKeyRemoteOperationResult.isSuccess() || publicKeyRemoteOperationResult.isSuccess()) {
|
||||
// delete keys
|
||||
assertTrue(new DeletePrivateKeyOperation().execute(client).isSuccess());
|
||||
assertTrue(new DeletePublicKeyOperation().execute(client).isSuccess());
|
||||
|
||||
arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.PRIVATE_KEY);
|
||||
arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.PUBLIC_KEY);
|
||||
arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.MNEMONIC);
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateMnemonicString() {
|
||||
return "1 2 3 4 5 6";
|
||||
}
|
||||
|
||||
public void after() {
|
||||
// remove all encrypted files
|
||||
OCFile root = fileDataStorageManager.getFileByDecryptedRemotePath("/");
|
||||
removeFolder(root);
|
||||
|
||||
// List<OCFile> files = fileDataStorageManager.getFolderContent(root, false);
|
||||
//
|
||||
// for (OCFile child : files) {
|
||||
// removeFolder(child);
|
||||
// }
|
||||
|
||||
assertEquals(0, fileDataStorageManager.getFolderContent(root, false).size());
|
||||
|
||||
super.after();
|
||||
}
|
||||
|
||||
private void removeFolder(OCFile folder) {
|
||||
Log_OC.d(this, "Start removing content of folder: " + folder.getDecryptedRemotePath());
|
||||
|
||||
List<OCFile> children = fileDataStorageManager.getFolderContent(folder, false);
|
||||
|
||||
// remove children
|
||||
for (OCFile child : children) {
|
||||
if (child.isFolder()) {
|
||||
removeFolder(child);
|
||||
|
||||
// remove folder
|
||||
Log_OC.d(this, "Remove folder: " + child.getDecryptedRemotePath());
|
||||
if (!folder.isEncrypted() && child.isEncrypted()) {
|
||||
assertTrue(new ToggleEncryptionRemoteOperation(child.getLocalId(),
|
||||
child.getRemotePath(),
|
||||
false)
|
||||
.execute(client)
|
||||
.isSuccess());
|
||||
|
||||
OCFile f = getStorageManager().getFileByEncryptedRemotePath(child.getRemotePath());
|
||||
f.setEncrypted(false);
|
||||
getStorageManager().saveFile(f);
|
||||
|
||||
child.setEncrypted(false);
|
||||
}
|
||||
} else {
|
||||
Log_OC.d(this, "Remove file: " + child.getDecryptedRemotePath());
|
||||
}
|
||||
|
||||
assertTrue(new RemoveFileOperation(child, false, user, false, targetContext, getStorageManager())
|
||||
.execute(client)
|
||||
.isSuccess()
|
||||
);
|
||||
}
|
||||
|
||||
Log_OC.d(this, "Finished removing content of folder: " + folder.getDecryptedRemotePath());
|
||||
}
|
||||
|
||||
private void verifyStoragePath(OCFile file) {
|
||||
assertEquals(FileStorageUtils.getSavePath(account.name) +
|
||||
currentFolder.getDecryptedRemotePath() +
|
||||
file.getDecryptedFileName(),
|
||||
file.getStoragePath());
|
||||
}
|
||||
}
|
|
@ -28,6 +28,9 @@ import com.nextcloud.client.preferences.DarkMode;
|
|||
import com.nextcloud.common.NextcloudClient;
|
||||
import com.nextcloud.java.util.Optional;
|
||||
import com.nextcloud.test.GrantStoragePermissionRule;
|
||||
import com.nextcloud.test.RandomStringGenerator;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.UploadsStorageManager;
|
||||
|
@ -38,6 +41,7 @@ import com.owncloud.android.lib.common.OwnCloudClient;
|
|||
import com.owncloud.android.lib.common.OwnCloudClientFactory;
|
||||
import com.owncloud.android.lib.common.accounts.AccountUtils;
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||
import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.status.CapabilityBooleanType;
|
||||
import com.owncloud.android.lib.resources.status.GetCapabilitiesRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.status.OCCapability;
|
||||
|
@ -46,8 +50,6 @@ import com.owncloud.android.operations.CreateFolderOperation;
|
|||
import com.owncloud.android.operations.UploadFileOperation;
|
||||
import com.owncloud.android.utils.FileStorageUtils;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
@ -100,6 +102,8 @@ public abstract class AbstractIT {
|
|||
protected FileDataStorageManager fileDataStorageManager =
|
||||
new FileDataStorageManager(user, targetContext.getContentResolver());
|
||||
|
||||
protected ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeAll() {
|
||||
try {
|
||||
|
@ -118,14 +122,11 @@ public abstract class AbstractIT {
|
|||
|
||||
client = OwnCloudClientFactory.createOwnCloudClient(account, targetContext);
|
||||
nextcloudClient = OwnCloudClientFactory.createNextcloudClient(user, targetContext);
|
||||
} catch (OperationCanceledException e) {
|
||||
e.printStackTrace();
|
||||
} catch (AuthenticatorException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (AccountUtils.AccountNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (OperationCanceledException |
|
||||
IOException |
|
||||
AccountUtils.AccountNotFoundException |
|
||||
AuthenticatorException e) {
|
||||
throw new RuntimeException("Error setting up clients", e);
|
||||
}
|
||||
|
||||
Bundle arguments = androidx.test.platform.app.InstrumentationRegistry.getArguments();
|
||||
|
@ -334,11 +335,15 @@ public abstract class AbstractIT {
|
|||
}
|
||||
|
||||
public OCFile createFolder(String remotePath) {
|
||||
TestCase.assertTrue(new CreateFolderOperation(remotePath, user, targetContext, getStorageManager())
|
||||
.execute(client)
|
||||
.isSuccess());
|
||||
RemoteOperationResult check = new ExistenceCheckRemoteOperation(remotePath, false).execute(client);
|
||||
|
||||
return getStorageManager().getFileByDecryptedRemotePath(remotePath);
|
||||
if (!check.isSuccess()) {
|
||||
assertTrue(new CreateFolderOperation(remotePath, user, targetContext, getStorageManager())
|
||||
.execute(client)
|
||||
.isSuccess());
|
||||
}
|
||||
|
||||
return getStorageManager().getFileByDecryptedRemotePath(remotePath.endsWith("/") ? remotePath : remotePath + "/");
|
||||
}
|
||||
|
||||
public void uploadFile(File file, String remotePath) {
|
||||
|
@ -473,6 +478,14 @@ public abstract class AbstractIT {
|
|||
return AccountManager.get(targetContext).getUserData(user.toPlatformAccount(), KEY_USER_ID);
|
||||
}
|
||||
|
||||
public String getRandomName() {
|
||||
return getRandomName(5);
|
||||
}
|
||||
|
||||
public String getRandomName(int length) {
|
||||
return RandomStringGenerator.make(length);
|
||||
}
|
||||
|
||||
protected static User getUser(Account account) {
|
||||
Optional<User> optionalUser = UserAccountManagerImpl.fromContext(targetContext).getUser(account.name);
|
||||
return optionalUser.orElseThrow(IllegalAccessError::new);
|
||||
|
|
|
@ -94,21 +94,19 @@ public abstract class AbstractOnServerIT extends AbstractIT {
|
|||
user = optionalUser.orElseThrow(IllegalAccessError::new);
|
||||
|
||||
client = OwnCloudClientFactory.createOwnCloudClient(account, targetContext);
|
||||
nextcloudClient = OwnCloudClientFactory.createNextcloudClient(user, targetContext);
|
||||
|
||||
createDummyFiles();
|
||||
|
||||
waitForServer(client, baseUrl);
|
||||
|
||||
deleteAllFilesOnServer(); // makes sure that no file/folder is in root
|
||||
// deleteAllFilesOnServer(); // makes sure that no file/folder is in root
|
||||
|
||||
} catch (OperationCanceledException e) {
|
||||
e.printStackTrace();
|
||||
} catch (AuthenticatorException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (AccountUtils.AccountNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (OperationCanceledException |
|
||||
IOException |
|
||||
AccountUtils.AccountNotFoundException |
|
||||
AuthenticatorException e) {
|
||||
throw new RuntimeException("Error setting up clients", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,7 +142,7 @@ public abstract class AbstractOnServerIT extends AbstractIT {
|
|||
removeResult = new RemoveFileRemoteOperation(remoteFile.getRemotePath())
|
||||
.execute(client)
|
||||
.isSuccess();
|
||||
|
||||
|
||||
if (removeResult) {
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -26,12 +26,11 @@ import org.junit.Assert.assertEquals
|
|||
import org.junit.Test
|
||||
|
||||
class ArbitraryDataProviderIT : AbstractIT() {
|
||||
private val arbitraryDataProvider = ArbitraryDataProviderImpl(targetContext)
|
||||
|
||||
@Test
|
||||
fun testNull() {
|
||||
fun testEmpty() {
|
||||
val key = "DUMMY_KEY"
|
||||
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, null)
|
||||
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, "")
|
||||
|
||||
assertEquals("", arbitraryDataProvider.getValue(user.accountName, key))
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@ class FileDetailSharingFragmentIT : AbstractIT() {
|
|||
remoteId = "00000001"
|
||||
parentId = activity.storageManager.getFileByEncryptedRemotePath("/").fileId
|
||||
permissions = OCFile.PERMISSION_CAN_RESHARE
|
||||
fileDataStorageManager.saveFile(this)
|
||||
}
|
||||
|
||||
folder = OCFile("/test").apply {
|
||||
|
|
|
@ -31,16 +31,20 @@ import com.nextcloud.test.RetryTestRule;
|
|||
import com.owncloud.android.AbstractIT;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
||||
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.decrypted.Encrypted;
|
||||
import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFile;
|
||||
import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1;
|
||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||
import com.owncloud.android.utils.CsrHelper;
|
||||
import com.owncloud.android.lib.resources.e2ee.CsrHelper;
|
||||
import com.owncloud.android.utils.EncryptionUtils;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -63,9 +67,6 @@ import java.util.Set;
|
|||
|
||||
import javax.crypto.BadPaddingException;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import static com.owncloud.android.utils.EncryptionUtils.EncryptedFile;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.decodeStringToBase64Bytes;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.decryptFile;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.decryptFolderMetaData;
|
||||
|
@ -93,11 +94,12 @@ import static org.junit.Assert.assertEquals;
|
|||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EncryptionTestIT extends AbstractIT {
|
||||
@Rule public RetryTestRule retryTestRule = new RetryTestRule();
|
||||
|
||||
private String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo" +
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
|
||||
public static final String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo" +
|
||||
"IBAQDsn0JKS/THu328z1IgN0VzYU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzV" +
|
||||
"GzKFvGfZ03fwFrN7Q8P8R2e8SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7" +
|
||||
"Y0BJX9i/nW/L0L/VaE8CZTAqYBdcSJGgHJjY4UMf892ZPTa9T2Dl3ggdMZ7BQ2kiCi" +
|
||||
|
@ -123,7 +125,7 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
"JLecOFu3ZlQl/RStQb69QKb5MNOIMmQhg8WOxZxHcpmIDbkDAm/J/ovJXFSoBdOr5o" +
|
||||
"uQsYsDZhsWW97zvLMzg5pH9/3/1BNz5q3Vu4HgfBSwWGt4E2NENj+XA+QAVmGA==";
|
||||
|
||||
private String cert = "-----BEGIN CERTIFICATE-----\n" +
|
||||
public static final String publicKey = "-----BEGIN CERTIFICATE-----\n" +
|
||||
"MIIDpzCCAo+gAwIBAgIBADANBgkqhkiG9w0BAQUFADBuMRowGAYDVQQDDBF3d3cu\n" +
|
||||
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
|
||||
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
|
||||
|
@ -151,7 +153,7 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
byte[] key1 = generateKey();
|
||||
String base64encodedKey = encodeBytesToBase64String(key1);
|
||||
|
||||
String encryptedString = EncryptionUtils.encryptStringAsymmetric(base64encodedKey, cert);
|
||||
String encryptedString = EncryptionUtils.encryptStringAsymmetric(base64encodedKey, publicKey);
|
||||
String decryptedString = decryptStringAsymmetric(encryptedString, privateKey);
|
||||
|
||||
byte[] key2 = decodeStringToBase64Bytes(decryptedString);
|
||||
|
@ -207,9 +209,9 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
String encryptedString;
|
||||
if (new Random().nextBoolean()) {
|
||||
encryptedString = EncryptionUtils.encryptStringSymmetric(privateKey, key);
|
||||
encryptedString = EncryptionUtils.encryptStringSymmetricAsString(privateKey, key);
|
||||
} else {
|
||||
encryptedString = EncryptionUtils.encryptStringSymmetricOld(privateKey, key);
|
||||
encryptedString = EncryptionUtils.encryptStringSymmetricAsStringOld(privateKey, key);
|
||||
|
||||
if (encryptedString.indexOf(ivDelimiterOld) != encryptedString.lastIndexOf(ivDelimiterOld)) {
|
||||
Log_OC.d("EncryptionTestIT", "skip due to duplicated iv (old system) -> ignoring");
|
||||
|
@ -230,7 +232,7 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
for (int i = 0; i < max; i++) {
|
||||
Log_OC.d("EncryptionTestIT", i + " of " + max);
|
||||
|
||||
String encryptedString = EncryptionUtils.encryptStringSymmetric(privateKey, key);
|
||||
String encryptedString = EncryptionUtils.encryptStringSymmetricAsString(privateKey, key);
|
||||
|
||||
int delimiterPosition = encryptedString.indexOf(ivDelimiter);
|
||||
if (delimiterPosition == -1) {
|
||||
|
@ -286,43 +288,42 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
keyGen.initialize(2048, new SecureRandom());
|
||||
KeyPair keyPair = keyGen.generateKeyPair();
|
||||
|
||||
assertFalse(CsrHelper.generateCsrPemEncodedString(keyPair, "").isEmpty());
|
||||
assertFalse(new CsrHelper().generateCsrPemEncodedString(keyPair, "").isEmpty());
|
||||
assertFalse(encodeBytesToBase64String(keyPair.getPublic().getEncoded()).isEmpty());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DecryptedFolderMetadata -> EncryptedFolderMetadata -> JSON -> encrypt -> decrypt -> JSON ->
|
||||
* EncryptedFolderMetadata -> DecryptedFolderMetadata
|
||||
* DecryptedFolderMetadataFile -> EncryptedFolderMetadataFile -> JSON -> encrypt -> decrypt -> JSON ->
|
||||
* EncryptedFolderMetadataFile -> DecryptedFolderMetadataFile
|
||||
*/
|
||||
@Test
|
||||
public void encryptionMetadata() throws Exception {
|
||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
long folderID = 1;
|
||||
public void encryptionMetadataV1() throws Exception {
|
||||
DecryptedFolderMetadataFileV1 decryptedFolderMetadata1 = generateFolderMetadataV1_1();
|
||||
|
||||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
publicKey,
|
||||
1,
|
||||
user,
|
||||
folderID);
|
||||
arbitraryDataProvider);
|
||||
|
||||
// serialize
|
||||
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||
|
||||
// de-serialize
|
||||
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||
new TypeToken<EncryptedFolderMetadata>() {
|
||||
});
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||
new TypeToken<>() {
|
||||
});
|
||||
|
||||
// decrypt
|
||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||
DecryptedFolderMetadataFileV1 decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||
encryptedFolderMetadata2,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
1);
|
||||
|
||||
// compare
|
||||
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||
|
@ -331,29 +332,28 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
@Test
|
||||
public void testChangedMetadataKey() throws Exception {
|
||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
DecryptedFolderMetadataFileV1 decryptedFolderMetadata1 = generateFolderMetadataV1_1();
|
||||
long folderID = 1;
|
||||
|
||||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
publicKey,
|
||||
folderID,
|
||||
user,
|
||||
folderID);
|
||||
arbitraryDataProvider);
|
||||
|
||||
// store metadata key
|
||||
String oldMetadataKey = encryptedFolderMetadata1.getMetadata().getMetadataKey();
|
||||
|
||||
// do it again
|
||||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata2 = encryptFolderMetadata(
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
publicKey,
|
||||
folderID,
|
||||
user,
|
||||
folderID);
|
||||
arbitraryDataProvider);
|
||||
|
||||
String newMetadataKey = encryptedFolderMetadata2.getMetadata().getMetadataKey();
|
||||
|
||||
|
@ -362,17 +362,16 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
@Test
|
||||
public void testMigrateMetadataKey() throws Exception {
|
||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
DecryptedFolderMetadataFileV1 decryptedFolderMetadata1 = generateFolderMetadataV1_1();
|
||||
long folderID = 1;
|
||||
|
||||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
publicKey,
|
||||
folderID,
|
||||
user,
|
||||
folderID);
|
||||
arbitraryDataProvider);
|
||||
|
||||
// reset new metadata key, to mimic old version
|
||||
encryptedFolderMetadata1.getMetadata().setMetadataKey(null);
|
||||
|
@ -380,12 +379,12 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
// do it again
|
||||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata2 = encryptFolderMetadata(
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
publicKey,
|
||||
folderID,
|
||||
user,
|
||||
folderID);
|
||||
arbitraryDataProvider);
|
||||
|
||||
String newMetadataKey = encryptedFolderMetadata2.getMetadata().getMetadataKey();
|
||||
|
||||
|
@ -403,7 +402,7 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
@Test
|
||||
public void cryptFileWithMetadata() throws Exception {
|
||||
DecryptedFolderMetadata metadata = generateFolderMetadata();
|
||||
DecryptedFolderMetadataFileV1 metadata = generateFolderMetadataV1_1();
|
||||
|
||||
// n9WXAIXO2wRY4R8nXwmo
|
||||
assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r",
|
||||
|
@ -428,28 +427,27 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
@Test
|
||||
public void bigMetadata() throws Exception {
|
||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
DecryptedFolderMetadataFileV1 decryptedFolderMetadata1 = generateFolderMetadataV1_1();
|
||||
long folderID = 1;
|
||||
|
||||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
publicKey,
|
||||
folderID,
|
||||
user,
|
||||
folderID);
|
||||
arbitraryDataProvider);
|
||||
|
||||
// serialize
|
||||
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||
|
||||
// de-serialize
|
||||
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||
new TypeToken<EncryptedFolderMetadata>() {
|
||||
});
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||
new TypeToken<>() {
|
||||
});
|
||||
|
||||
// decrypt
|
||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||
DecryptedFolderMetadataFileV1 decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||
encryptedFolderMetadata2,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
|
@ -473,17 +471,17 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
// encrypt
|
||||
encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
publicKey,
|
||||
folderID,
|
||||
user,
|
||||
folderID);
|
||||
arbitraryDataProvider);
|
||||
|
||||
// serialize
|
||||
encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||
|
||||
// de-serialize
|
||||
encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||
new TypeToken<EncryptedFolderMetadata>() {
|
||||
new TypeToken<>() {
|
||||
});
|
||||
|
||||
// decrypt
|
||||
|
@ -502,21 +500,97 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bigMetadata2() throws Exception {
|
||||
long folderID = 1;
|
||||
DecryptedFolderMetadataFileV1 decryptedFolderMetadata1 = generateFolderMetadataV1_1();
|
||||
|
||||
// encrypt
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
publicKey,
|
||||
folderID,
|
||||
user,
|
||||
arbitraryDataProvider);
|
||||
|
||||
// serialize
|
||||
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||
|
||||
// de-serialize
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||
new TypeToken<EncryptedFolderMetadataFileV1>() {
|
||||
});
|
||||
|
||||
// decrypt
|
||||
DecryptedFolderMetadataFileV1 decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||
encryptedFolderMetadata2,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
|
||||
// compare
|
||||
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||
serializeJSON(decryptedFolderMetadata2)));
|
||||
|
||||
// prefill with 500
|
||||
for (int i = 0; i < 500; i++) {
|
||||
addFile(decryptedFolderMetadata1, i);
|
||||
}
|
||||
|
||||
int max = 505;
|
||||
for (int i = 500; i < max; i++) {
|
||||
Log_OC.d(this, "Big metadata: " + i + " of " + max);
|
||||
|
||||
addFile(decryptedFolderMetadata1, i);
|
||||
|
||||
// encrypt
|
||||
encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
publicKey,
|
||||
folderID,
|
||||
user,
|
||||
arbitraryDataProvider);
|
||||
|
||||
// serialize
|
||||
encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||
|
||||
// de-serialize
|
||||
encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||
new TypeToken<>() {
|
||||
});
|
||||
|
||||
// decrypt
|
||||
decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||
encryptedFolderMetadata2,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
|
||||
// compare
|
||||
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||
serializeJSON(decryptedFolderMetadata2)));
|
||||
|
||||
assertEquals(i + 3, decryptedFolderMetadata1.getFiles().size());
|
||||
assertEquals(i + 3, decryptedFolderMetadata2.getFiles().size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filedrop() throws Exception {
|
||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
DecryptedFolderMetadataFileV1 decryptedFolderMetadata1 = generateFolderMetadataV1_1();
|
||||
long folderID = 1;
|
||||
|
||||
// add filedrop
|
||||
Map<String, DecryptedFolderMetadata.DecryptedFile> filesdrop = new HashMap<>();
|
||||
Map<String, DecryptedFile> filesdrop = new HashMap<>();
|
||||
|
||||
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
|
||||
Data data = new Data();
|
||||
data.setKey("9dfzbIYDt28zTyZfbcll+g==");
|
||||
data.setFilename("test2.txt");
|
||||
data.setVersion(1);
|
||||
|
||||
DecryptedFolderMetadata.DecryptedFile file = new DecryptedFolderMetadata.DecryptedFile();
|
||||
DecryptedFile file = new DecryptedFile();
|
||||
file.setInitializationVector("hnJLF8uhDvDoFK4ajuvwrg==");
|
||||
file.setEncrypted(data);
|
||||
file.setMetadataKey(0);
|
||||
|
@ -527,24 +601,24 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
decryptedFolderMetadata1.setFiledrop(filesdrop);
|
||||
|
||||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
publicKey,
|
||||
folderID,
|
||||
user,
|
||||
folderID);
|
||||
EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, cert);
|
||||
arbitraryDataProvider);
|
||||
EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, publicKey);
|
||||
|
||||
// serialize
|
||||
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||
|
||||
// de-serialize
|
||||
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||
new TypeToken<EncryptedFolderMetadata>() {
|
||||
});
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||
new TypeToken<>() {
|
||||
});
|
||||
|
||||
// decrypt
|
||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||
DecryptedFolderMetadataFileV1 decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||
encryptedFolderMetadata2,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
|
@ -562,19 +636,19 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
assertNull(decryptedFolderMetadata2.getFiledrop());
|
||||
}
|
||||
|
||||
private void addFile(DecryptedFolderMetadata decryptedFolderMetadata, int counter) {
|
||||
private void addFile(DecryptedFolderMetadataFileV1 decryptedFolderMetadata, int counter) {
|
||||
// Add new file
|
||||
// Always generate new
|
||||
byte[] key = generateKey();
|
||||
byte[] iv = randomBytes(ivLength);
|
||||
byte[] authTag = randomBytes((128 / 8));
|
||||
|
||||
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
|
||||
Data data = new Data();
|
||||
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
|
||||
data.setFilename(counter + ".txt");
|
||||
data.setVersion(1);
|
||||
|
||||
DecryptedFolderMetadata.DecryptedFile file = new DecryptedFolderMetadata.DecryptedFile();
|
||||
DecryptedFile file = new DecryptedFile();
|
||||
file.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
|
||||
file.setEncrypted(data);
|
||||
file.setMetadataKey(0);
|
||||
|
@ -636,7 +710,7 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
@Test
|
||||
public void testExcludeGSON() throws Exception {
|
||||
DecryptedFolderMetadata metadata = generateFolderMetadata();
|
||||
DecryptedFolderMetadataFileV1 metadata = generateFolderMetadataV1_1();
|
||||
|
||||
String jsonWithKeys = serializeJSON(metadata);
|
||||
String jsonWithoutKeys = serializeJSON(metadata, true);
|
||||
|
@ -644,14 +718,28 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
assertTrue(jsonWithKeys.contains("metadataKeys"));
|
||||
assertFalse(jsonWithoutKeys.contains("metadataKeys"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsSign() {
|
||||
assertEquals("\"===\"", serializeJSON("==="));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBase64() {
|
||||
String originalString = "randomstring123";
|
||||
|
||||
String encodedString = EncryptionUtils.encodeStringToBase64String(originalString);
|
||||
String compare = EncryptionUtils.decodeBase64StringToString(encodedString);
|
||||
assertEquals(originalString, compare);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChecksum() throws Exception {
|
||||
DecryptedFolderMetadata metadata = new DecryptedFolderMetadata();
|
||||
DecryptedFolderMetadataFileV1 metadata = new DecryptedFolderMetadataFileV1();
|
||||
String mnemonic = "chimney potato joke science ridge trophy result estate spare vapor much room";
|
||||
|
||||
metadata.getFiles().put("n9WXAIXO2wRY4R8nXwmo", new DecryptedFolderMetadata.DecryptedFile());
|
||||
metadata.getFiles().put("ia7OEEEyXMoRa1QWQk8r", new DecryptedFolderMetadata.DecryptedFile());
|
||||
metadata.getFiles().put("n9WXAIXO2wRY4R8nXwmo", new DecryptedFile());
|
||||
metadata.getFiles().put("ia7OEEEyXMoRa1QWQk8r", new DecryptedFile());
|
||||
|
||||
String encryptedMetadataKey = "GuFPAULudgD49S4+VDFck3LiqQ8sx4zmbrBtdpCSGcT+T0W0z4F5gYQYPlzTG6WOkdW5LJZK/";
|
||||
metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
|
||||
|
@ -667,7 +755,7 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
String newChecksum = generateChecksum(metadata, newMnemonic);
|
||||
assertNotEquals(expectedChecksum, newChecksum);
|
||||
|
||||
metadata.getFiles().put("aeb34yXMoRa1QWQk8r", new DecryptedFolderMetadata.DecryptedFile());
|
||||
metadata.getFiles().put("aeb34yXMoRa1QWQk8r", new DecryptedFile());
|
||||
|
||||
newChecksum = generateChecksum(metadata, mnemonic);
|
||||
assertNotEquals(expectedChecksum, newChecksum);
|
||||
|
@ -675,8 +763,6 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
@Test
|
||||
public void testAddIdToMigratedIds() {
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
|
||||
// delete ids
|
||||
arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.MIGRATED_FOLDER_IDS);
|
||||
|
||||
|
@ -685,10 +771,22 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
assertTrue(isFolderMigrated(id, user, arbitraryDataProvider));
|
||||
}
|
||||
|
||||
// TODO E2E: more tests
|
||||
|
||||
// more tests
|
||||
// migrate v1 -> v2
|
||||
// migrate v1 -> v2 with filedrop
|
||||
|
||||
// migrate v1 -> v1.1
|
||||
// migrate v1 -> v1.1 with filedrop
|
||||
|
||||
// migrate v1.1 -> v2
|
||||
// migrate v1.1 -> v2 with filedrop
|
||||
|
||||
|
||||
// Helper
|
||||
private boolean compareJsonStrings(String expected, String actual) {
|
||||
public static boolean compareJsonStrings(String expected, String actual) {
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonElement o1 = parser.parse(expected);
|
||||
JsonElement o2 = parser.parse(actual);
|
||||
|
@ -702,29 +800,29 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
}
|
||||
}
|
||||
|
||||
private DecryptedFolderMetadata generateFolderMetadata() throws Exception {
|
||||
private DecryptedFolderMetadataFileV1 generateFolderMetadataV1_1() throws Exception {
|
||||
String metadataKey0 = encodeBytesToBase64String(generateKey());
|
||||
String metadataKey1 = encodeBytesToBase64String(generateKey());
|
||||
String metadataKey2 = encodeBytesToBase64String(generateKey());
|
||||
HashMap<Integer, String> metadataKeys = new HashMap<>();
|
||||
metadataKeys.put(0, EncryptionUtils.encryptStringAsymmetric(metadataKey0, cert));
|
||||
metadataKeys.put(1, EncryptionUtils.encryptStringAsymmetric(metadataKey1, cert));
|
||||
metadataKeys.put(2, EncryptionUtils.encryptStringAsymmetric(metadataKey2, cert));
|
||||
DecryptedFolderMetadata.Encrypted encrypted = new DecryptedFolderMetadata.Encrypted();
|
||||
metadataKeys.put(0, EncryptionUtils.encryptStringAsymmetric(metadataKey0, publicKey));
|
||||
metadataKeys.put(1, EncryptionUtils.encryptStringAsymmetric(metadataKey1, publicKey));
|
||||
metadataKeys.put(2, EncryptionUtils.encryptStringAsymmetric(metadataKey2, publicKey));
|
||||
Encrypted encrypted = new Encrypted();
|
||||
encrypted.setMetadataKeys(metadataKeys);
|
||||
|
||||
DecryptedFolderMetadata.Metadata metadata1 = new DecryptedFolderMetadata.Metadata();
|
||||
DecryptedMetadata metadata1 = new DecryptedMetadata();
|
||||
metadata1.setMetadataKeys(metadataKeys);
|
||||
metadata1.setVersion(1.1);
|
||||
metadata1.setVersion(1);
|
||||
|
||||
HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
|
||||
HashMap<String, DecryptedFile> files = new HashMap<>();
|
||||
|
||||
DecryptedFolderMetadata.Data data1 = new DecryptedFolderMetadata.Data();
|
||||
Data data1 = new Data();
|
||||
data1.setKey("WANM0gRv+DhaexIsI0T3Lg==");
|
||||
data1.setFilename("test.txt");
|
||||
data1.setVersion(1);
|
||||
|
||||
DecryptedFolderMetadata.DecryptedFile file1 = new DecryptedFolderMetadata.DecryptedFile();
|
||||
DecryptedFile file1 = new DecryptedFile();
|
||||
file1.setInitializationVector("gKm3n+mJzeY26q4OfuZEqg==");
|
||||
file1.setEncrypted(data1);
|
||||
file1.setMetadataKey(0);
|
||||
|
@ -732,12 +830,12 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
files.put("ia7OEEEyXMoRa1QWQk8r", file1);
|
||||
|
||||
DecryptedFolderMetadata.Data data2 = new DecryptedFolderMetadata.Data();
|
||||
Data data2 = new Data();
|
||||
data2.setKey("9dfzbIYDt28zTyZfbcll+g==");
|
||||
data2.setFilename("test2.txt");
|
||||
data2.setVersion(1);
|
||||
|
||||
DecryptedFolderMetadata.DecryptedFile file2 = new DecryptedFolderMetadata.DecryptedFile();
|
||||
DecryptedFile file2 = new DecryptedFile();
|
||||
file2.setInitializationVector("hnJLF8uhDvDoFK4ajuvwrg==");
|
||||
file2.setEncrypted(data2);
|
||||
file2.setMetadataKey(0);
|
||||
|
@ -745,9 +843,10 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
files.put("n9WXAIXO2wRY4R8nXwmo", file2);
|
||||
|
||||
return new DecryptedFolderMetadata(metadata1, files);
|
||||
return new DecryptedFolderMetadataFileV1(metadata1, files);
|
||||
}
|
||||
|
||||
|
||||
private boolean cryptFile(String fileName, String md5, byte[] key, byte[] iv, byte[] expectedAuthTag)
|
||||
throws Exception {
|
||||
File file = getFile(fileName);
|
||||
|
@ -757,10 +856,10 @@ public class EncryptionTestIT extends AbstractIT {
|
|||
|
||||
File encryptedTempFile = File.createTempFile("file", "tmp");
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
|
||||
fileOutputStream.write(encryptedFile.encryptedBytes);
|
||||
fileOutputStream.write(encryptedFile.getEncryptedBytes());
|
||||
fileOutputStream.close();
|
||||
|
||||
byte[] authenticationTag = decodeStringToBase64Bytes(encryptedFile.authenticationTag);
|
||||
byte[] authenticationTag = decodeStringToBase64Bytes(encryptedFile.getAuthenticationTag());
|
||||
|
||||
// verify authentication tag
|
||||
assertTrue(Arrays.equals(expectedAuthTag, authenticationTag));
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.utils
|
||||
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFile
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedMetadata
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedUser
|
||||
import com.owncloud.android.lib.resources.status.E2EVersion
|
||||
|
||||
class EncryptionTestUtils {
|
||||
val t1PrivateKey =
|
||||
"MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQC1p8eYMFwGoi7geYzEwNbePRLL5LRhorAecFG3zkpLBwSi/QHkU4" +
|
||||
"u4uSegEbHgOfe73eKVOFdfFpw8wd5cvtY+4CzbX8bu+yrC+tFGcJ25/4VQ78Bl4MI0SvOmxDwuZNrg9SWgs9RwialKOsfCEyz0" +
|
||||
"SS8RstGNt5KZKn1e8z7V9X/eORPmOQ5KcIXHlMbAY3m4erBSKvhRZdqy+Dbnc0rZeZaKkoIMJH1OYfVto/ek12iIKF2YStPVzo" +
|
||||
"TgNsFelPDxeA/lltgf6qDVRD+ELydEncPIJwcv52D8ZitoEyEOfjDZW+rvvE02g1ZD1xPkDLpwltAsFCglCKvKBAWuhthFAgMB" +
|
||||
"AAECgf8BN1MLcq+6m8C1tzNvN/UDd9c0rUpexM6D5eC4O+6B7YGidEqIhHVIzUj0e2HUgpRBbURxsvF1FWdIT2gu7dnULtOGWQ" +
|
||||
"xNujJ0kGwXfAnqxh/rACDFb5TS3sJawEExC5yJw14bCEbE/0uBF5uiTU/U9AV7PKHlqAKsS2RtcwPNceB8zDu0hh/Mb/uS7274" +
|
||||
"TsxUllx0WzGZrozO1K6AlOete9rXmmpghpFTNVhxgf0pxe3hrK+tZGSL9di+Wft9eCvSbdG/FzeXgwVqmGtWU7kSB7FqstEEJO" +
|
||||
"4VpOSyEfcXGHTHwdZjrhBUuAcjWE8E0mCKa8htRE52czb3C0f7ZYkCgYEA5eH3vmHEgQjXzSSEtbmDLRq9X9SB7pIAIXHj2UuE" +
|
||||
"OTkLUJ/7xLTHqt82jqZaZzns1RZIJXKZjH85CswQp/py2/qD240KvA/N+ELZaciaV+Wg+m4+iHdi0DyPkaKaBtFG1nsR2GbVWO" +
|
||||
"1OsaTUZTG4D7RCUErU6XVmNPQKSk5uRA0CgYEAykskpX3KKuWq5nxV4vwgPmxz+uAfCtaGhcPEUg764SR+n0ODAvGiEJU7B0Q2" +
|
||||
"oX621pDOQeRfFufiMWfD8ByhErs1HFCmW69YPlR8qamfc8tHG5UM+r3bb49sDEYU4qr1Ji5Zzs4XgfmToKLbWdzzhaW6YxqO7N" +
|
||||
"ntIIh2169PPxkCgYBF2TAWl8xGTLKNcYAlW1XBObO6z24fWBtUDi/mEWz+mheXCtVMAoX8pFAGbgNgBBiy8k8/mZ+QMgPaBQE2" +
|
||||
"mQGXV3oDFsrhM4go298Fpl9HP8126lJz0pqinRQecyKL2cDFYKWedDh1Cb30ehnTGZVMqD/R97rTqMlCY7hQtZ4JbQKBgEXpLD" +
|
||||
"QJQeoLT0GybJgyXA5WuspT1EaRlxH5cwqM5MUUMLJnyYol6cVjXXAIcfzj5tpGVxHMk9Q9tR0v6DY+HqhzjEpJ0QRUl+GKnz6f" +
|
||||
"QVzqPpvYqhCptoFahpPDUIp5XJmiYSUoclVX5F4aikYHJx3kBYMkdYqDUgDxSGkHzBJZAoGAHV44xgTW02dgeB5GfDJVWCJKAU" +
|
||||
"GsYOFuUehKUBXSJ0929hdP0sjOQDJN3DEDISzmgdWX5NyLJxEYgFWNivpePjWCWzOzyua3nPSpvxPIUB7xh27gjT91glj1hEmy" +
|
||||
"sCd7+9yoMPiCXR7iigRycxegI/Krd39QzISSk9O0finfytU="
|
||||
|
||||
val t1PublicKey = """-----BEGIN CERTIFICATE-----
|
||||
MIIC6DCCAdCgAwIBAgIBADANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDDAJ0MTAe
|
||||
Fw0yMzA3MjUwNzU3MTJaFw00MzA3MjAwNzU3MTJaMA0xCzAJBgNVBAMMAnQxMIIB
|
||||
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtafHmDBcBqIu4HmMxMDW3j0S
|
||||
y+S0YaKwHnBRt85KSwcEov0B5FOLuLknoBGx4Dn3u93ilThXXxacPMHeXL7WPuAs
|
||||
21/G7vsqwvrRRnCduf+FUO/AZeDCNErzpsQ8LmTa4PUloLPUcImpSjrHwhMs9Ekv
|
||||
EbLRjbeSmSp9XvM+1fV/3jkT5jkOSnCFx5TGwGN5uHqwUir4UWXasvg253NK2XmW
|
||||
ipKCDCR9TmH1baP3pNdoiChdmErT1c6E4DbBXpTw8XgP5ZbYH+qg1UQ/hC8nRJ3D
|
||||
yCcHL+dg/GYraBMhDn4w2Vvq77xNNoNWQ9cT5Ay6cJbQLBQoJQirygQFrobYRQID
|
||||
AQABo1MwUTAdBgNVHQ4EFgQUE9zCeA9/QMAtVgLxD23X6ZcodhMwHwYDVR0jBBgw
|
||||
FoAUE9zCeA9/QMAtVgLxD23X6ZcodhMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
|
||||
9w0BAQUFAAOCAQEAZdy/YjJlvnz3FQwxp6oVtMJccpdxveEPfLzgaverhtd/vP8O
|
||||
AvDzOLgQJHmrDS91SG503eU4cYGyuNKwd77OyTnqMg+GUEmJhGfPpSVrEIdh65jv
|
||||
q61T4oqBdehevVmBq54rGiwL0DGv1DlXQlwiJZP4qni2KnOEFcnvL3gVtRnQjXQ+
|
||||
kHvlMshkK6w021EMV5NfjG2zg67wC65rLaej5f6Ssp2S7g2VtmE4aXq1bjAuEbqk
|
||||
4TiyZHLDdsJuqzyGyyOpMV7i9ucXDoaZt9cGS9hT2vRxTrSH63vKR8Xeig9+stLw
|
||||
t9ONcUqCKP7hd8rajtxM4JIIRExwD8OkgARWGg==
|
||||
-----END CERTIFICATE-----"""
|
||||
|
||||
val johnPrivateKey =
|
||||
"""MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDuPcSvlhqElPQsCdJEuGmptGj4TUBWe33yu+ncOYR8Ec3M0H4NL0gE
|
||||
|ORJJcz9i18ByLpNzDy6NUGOtlf9YSat/zKdAfFiZJolKc/y4BPfTr8xx5ml2mu4Rz39LXRru+nnhluV3g1h2Z9LvWhUVUqAztz9W2H
|
||||
|H6uC7jx+7HNtYC9VgsVzHjuHPQMlOePPZlr9Hry5enF/Psn24RdiKqwCz8WhsOwtmW5PdHLLBVHAoF53URnFR4sgmLLGlS2GEZ8hvx
|
||||
|vdV/2NmhRWLebmCZziyklAe9gCR9lgfN32tqzyMG7VptBHFy7YJidWjpjSZPGEqFBL+fmCO/cTGJAXfCn9djAgMBAAECggEAV2QBCg
|
||||
|edopShHKZdoyeiWsX621o7B341LR0RI99VYc2GGGNCWcPGPwZQVvEXh0JtLXU4UTR4dw3OApbLG6+qYS7JCzaRqVwhcFYrlbT804Hh
|
||||
|FMbYWNFsEsxyfUqh3peyrbWUZsqfYI+lKHd61F+CtHW7nje3V6jISnXEeP78cgioKOX8gsCG8DEWsmaLrQz0PyMwdhucRfa8Bm6qeX
|
||||
|NY+wCMg8lyH/+OLlyCZTdkaWbTBBD5UXGbZly8iX17McmsYhdjFyx1l0NQnVMAYjOpXXEkeEixZpSfm3GYxmdaQqZFkpbI/FbQF0yD
|
||||
|7hLrGwiRTDcyPUz+QypUv8CZxpXbgQKBgQD3btuYmb+BpPZjryfa3worv/3XQCTs08V0TX3mDxHVQL95TgP+L8/Z/brxIMBNpwG1wk
|
||||
|iCWLYLer68+qioMTohuzeUx7hRKcoHa9ezW8m7m9AcPmAnzNticPYv835BQjEu/avU98rwIDihsYgxcjU3L7/P2ajVgUDigQxmE3gO
|
||||
|OwKBgQD2fXBLwch0P5g2GCyOPYvSgyF/umS7mcyUVTE4WOoJNDf8Q+Bx1dA2yAKQaoVghqW4uCfOAo/rERvGAYZ7fm7nFwx1jZ8ToT
|
||||
|dKZwybIFPjF/zkfuZLajYxVOPnzuQrsXnjcGg/ltMKZg3NqnGQGnD1S3eOhZ+dIOBmb7+jSO4A+QKBgASqnpGeNLJpPgxbPVEva62v
|
||||
|jUYF+6xLwimTXJB+MEPpWLMc+Y5NsInX8zKg/393atzWsS9kJOrKgdZmk8+4PfRs53ty2NMPCrRhIExNqtxS7/XYZ0/Y2TpeDwaQfQ
|
||||
|0WBn9wYVE+6yDkOq0x//OOx9ommGN/I2QDcAnVjTpPm7AJAoGAYT8cDsdlTnfIlY70BSpC/8q8bKgdFeaXz+3MfW6W5wqzC9O7uS2h
|
||||
|9/rxCAj+lhaJS1dcXOql3Rfi3Tu80vwOxR1SzQ4StKvmJHSDhLA8aFwOahemxBojR1M2lz4IxzQ94n12o5/dozygNYQJSdEkv6IGiT
|
||||
|QuxM8zuTZdZQ5g2AECgYAujetfkwgVW7/gumpMKytoY0VuTzF4Y/XZfqBMVIiPIuUl57JbDzrcx6YVXX3PavxNWmBLBmMq3SHMbdva
|
||||
|H7LnU/8rvkT8xRVLg/w/bRJc3Lb3oUjrdhkUQUYDoOfMoFA+ceZ2L6bnSXwm86KKV+xoXWpxAoL4AvdNrMhoWw3+yg=="""
|
||||
.trimMargin()
|
||||
|
||||
val johnPublicKey = """-----BEGIN CERTIFICATE-----
|
||||
MIIDkDCCAnigAwIBAgIBADANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJERTEb
|
||||
MBkGA1UECAwSQmFkZW4tV3VlcnR0ZW1iZXJnMRIwEAYDVQQHDAlTdHV0dGdhcnQx
|
||||
EjAQBgNVBAoMCU5leHRjbG91ZDENMAsGA1UEAwwEam9objAeFw0yMzA3MTQwNzM0
|
||||
NTZaFw00MzA3MDkwNzM0NTZaMGExCzAJBgNVBAYTAkRFMRswGQYDVQQIDBJCYWRl
|
||||
bi1XdWVydHRlbWJlcmcxEjAQBgNVBAcMCVN0dXR0Z2FydDESMBAGA1UECgwJTmV4
|
||||
dGNsb3VkMQ0wCwYDVQQDDARqb2huMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEA7j3Er5YahJT0LAnSRLhpqbRo+E1AVnt98rvp3DmEfBHNzNB+DS9IBDkS
|
||||
SXM/YtfAci6Tcw8ujVBjrZX/WEmrf8ynQHxYmSaJSnP8uAT306/MceZpdpruEc9/
|
||||
S10a7vp54Zbld4NYdmfS71oVFVKgM7c/Vthx+rgu48fuxzbWAvVYLFcx47hz0DJT
|
||||
njz2Za/R68uXpxfz7J9uEXYiqsAs/FobDsLZluT3RyywVRwKBed1EZxUeLIJiyxp
|
||||
UthhGfIb8b3Vf9jZoUVi3m5gmc4spJQHvYAkfZYHzd9ras8jBu1abQRxcu2CYnVo
|
||||
6Y0mTxhKhQS/n5gjv3ExiQF3wp/XYwIDAQABo1MwUTAdBgNVHQ4EFgQUmTeILVuB
|
||||
tv70fTGkXWGAueDp5kAwHwYDVR0jBBgwFoAUmTeILVuBtv70fTGkXWGAueDp5kAw
|
||||
DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAyVtq9XAvW7nxSW/8
|
||||
hp30z6xbzGiuviXhy/Jo91VEa8IRsWCCn3OmDFiVduTEowx76tf8clJP0gk7Pozi
|
||||
6dg/7Fin+FqQGXfCk8bLAh9gXKAikQ2GK8yRN3slRFwYC2mm23HrLdKXZHUqJcpB
|
||||
Mz2zsSrOGPj1YsYOl/U8FU6KA7Yj7U3q7kDMYTAgzUPZAH+d1DISGWpZsMa0RYid
|
||||
vigCCLByiccmS/Co4Sb1esF58H+YtV5+nFBRwx881U2g2TgDKF1lPMK/y3d8B8mh
|
||||
UtW+lFxRpvyNUDpsMjOErOrtNFEYbgoUJLtqwBMmyGR+nmmh6xna331QWcRAmw0P
|
||||
nDO4ew==
|
||||
-----END CERTIFICATE-----"""
|
||||
|
||||
@Throws(java.lang.Exception::class)
|
||||
fun generateFolderMetadataV2(userId: String, cert: String): DecryptedFolderMetadataFile {
|
||||
val metadata = DecryptedMetadata().apply {
|
||||
metadataKey = EncryptionUtils.generateKey()
|
||||
keyChecksums.add(EncryptionUtilsV2().hashMetadataKey(metadataKey))
|
||||
}
|
||||
|
||||
val file1 = DecryptedFile(
|
||||
"image1.png",
|
||||
"image/png",
|
||||
"gKm3n+mJzeY26q4OfuZEqg==",
|
||||
"PboI9tqHHX3QeAA22PIu4w==",
|
||||
"WANM0gRv+DhaexIsI0T3Lg=="
|
||||
)
|
||||
|
||||
val file2 = DecryptedFile(
|
||||
"image2.png",
|
||||
"image/png",
|
||||
"hnJLF8uhDvDoFK4ajuvwrg==",
|
||||
"qOQZdu5soFO77Y7y4rAOVA==",
|
||||
"9dfzbIYDt28zTyZfbcll+g=="
|
||||
)
|
||||
|
||||
val users = mutableListOf(
|
||||
DecryptedUser(userId, cert)
|
||||
)
|
||||
|
||||
// val filedrop = mutableMapOf(
|
||||
// Pair(
|
||||
// "eie8iaeiaes8e87td6",
|
||||
// DecryptedFile(
|
||||
// "test2.txt",
|
||||
// "txt/plain",
|
||||
// "hnJLF8uhDvDoFK4ajuvwrg==",
|
||||
// "qOQZdu5soFO77Y7y4rAOVA==",
|
||||
// "9dfzbIYDt28zTyZfbcll+g=="
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
|
||||
metadata.files["ia7OEEEyXMoRa1QWQk8r"] = file1
|
||||
metadata.files["n9WXAIXO2wRY4R8nXwmo"] = file2
|
||||
|
||||
return DecryptedFolderMetadataFile(metadata, users, mutableMapOf(), E2EVersion.V2_0.value)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.owncloud.android.utils
|
||||
|
||||
import com.owncloud.android.AbstractIT
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
|
||||
import com.owncloud.android.lib.resources.e2ee.CsrHelper
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class EncryptionUtilsIT : AbstractIT() {
|
||||
@Throws(
|
||||
java.security.NoSuchAlgorithmException::class,
|
||||
java.io.IOException::class,
|
||||
org.bouncycastle.operator.OperatorCreationException::class
|
||||
)
|
||||
@Test
|
||||
fun saveAndRestorePublicKey() {
|
||||
val arbitraryDataProvider = ArbitraryDataProviderImpl(targetContext)
|
||||
val keyPair = EncryptionUtils.generateKeyPair()
|
||||
val e2eUser = "e2e-user"
|
||||
val key = CsrHelper().generateCsrPemEncodedString(keyPair, e2eUser)
|
||||
|
||||
EncryptionUtils.savePublicKey(user, key, e2eUser, arbitraryDataProvider)
|
||||
|
||||
assertEquals(key, EncryptionUtils.getPublicKey(user, e2eUser, arbitraryDataProvider))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,911 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.utils
|
||||
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.nextcloud.client.account.MockUser
|
||||
import com.nextcloud.common.User
|
||||
import com.owncloud.android.AbstractIT
|
||||
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.v2.decrypted.DecryptedFile
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedMetadata
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedUser
|
||||
import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFiledrop
|
||||
import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFiledropUser
|
||||
import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFolderMetadataFile
|
||||
import com.owncloud.android.util.EncryptionTestIT
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Test
|
||||
|
||||
class EncryptionUtilsV2IT : AbstractIT() {
|
||||
private val encryptionTestUtils = EncryptionTestUtils()
|
||||
private val encryptionUtilsV2 = EncryptionUtilsV2()
|
||||
|
||||
private val enc1UserId = "enc1"
|
||||
private val enc1PrivateKey = """
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo
|
||||
IBAQDsn0JKS/THu328z1IgN0VzYU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzV
|
||||
GzKFvGfZ03fwFrN7Q8P8R2e8SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7
|
||||
Y0BJX9i/nW/L0L/VaE8CZTAqYBdcSJGgHJjY4UMf892ZPTa9T2Dl3ggdMZ7BQ2kiCi
|
||||
CC3qV99b0igRJGmmLQaGiAflhFzuDQPMifUMq75wI8RSRPdxUAtjTfkl68QHu7Umye
|
||||
yy33OQgdUKaTl5zcS3VSQbNjveVCNM4RDH1RlEc+7Wf1BY8APqT6jbiBcROJD2CeoL
|
||||
H2eiIJCi+61ZkSGfAgMBAAECggEBALFStCHrhBf+GL9a+qer4/8QZ/X6i91PmaBX/7
|
||||
SYk2jjjWVSXRNmex+V6+Y/jBRT2mvAgm8J+7LPwFdatE+lz0aZrMRD2gCWYF6Itpda
|
||||
90OlLkmQPVWWtGTgX2ta2tF5r2iSGzk0IdoL8zw98Q2UzpOcw30KnWtFMxuxWk0mHq
|
||||
pgp00g80cDWg3+RPbWOhdLp5bflQ36fKDfmjq05cGlIk6unnVyC5HXpvh4d4k2EWlX
|
||||
rjGsndVBPCjGkZePlLRgDHxT06r+5XdJ+1CBDZgCsmjGz3M8uOHyCfVW0WhB7ynzDT
|
||||
agVgz0iqpuhAi9sPt6iWWwpAnRw8cQgqEKw9bvKKECgYEA/WPi2PJtL6u/xlysh/H7
|
||||
A717CId6fPHCMDace39ZNtzUzc0nT5BemlcF0wZ74NeJSur3Q395YzB+eBMLs5p8mA
|
||||
95wgGvJhM65/J+HX+k9kt6Z556zLMvtG+j1yo4D0VEwm3xahB4SUUP+1kD7dNvo4+8
|
||||
xeSCyjzNllvYZZC0DrECgYEA7w8pEqhHHn0a+twkPCZJS+gQTB9Rm+FBNGJqB3XpWs
|
||||
TeLUxYRbVGk0iDve+eeeZ41drxcdyWP+WcL34hnrjgI1Fo4mK88saajpwUIYMy6+qM
|
||||
LY+jC2NRSBox56eH7nsVYvQQK9eKqv9wbB+PF9SwOIvuETN7fd8mAY02UnoaaU8CgY
|
||||
BoHRKocXPLkpZJuuppMVQiRUi4SHJbxDo19Tp2w+y0TihiJ1lvp7I3WGpcOt3LlMQk
|
||||
tEbExSvrRZGxZKH6Og/XqwQsYuTEkEIz679F/5yYVosE6GkskrOXQAfh8Mb3/04xVV
|
||||
tMaVgDQw0+CWVD4wyL+BNofGwBDNqsXTCdCsfxAQKBgQCDv2EtbRw0y1HRKv21QIxo
|
||||
ju5cZW4+cDfVPN+eWPdQFOs1H7wOPsc0aGRiiupV2BSEF3O1ApKziEE5U1QH+29bR4
|
||||
R8L1pemeGX8qCNj5bCubKjcWOz5PpouDcEqimZ3q98p3E6GEHN15UHoaTkx0yO/V8o
|
||||
j6zhQ9fYRxDHB5ACtQKBgQCOO7TJUO1IaLTjcrwS4oCfJyRnAdz49L1AbVJkIBK0fh
|
||||
JLecOFu3ZlQl/RStQb69QKb5MNOIMmQhg8WOxZxHcpmIDbkDAm/J/ovJXFSoBdOr5o
|
||||
uQsYsDZhsWW97zvLMzg5pH9/3/1BNz5q3Vu4HgfBSwWGt4E2NENj+XA+QAVmGA==
|
||||
""".trimIndent()
|
||||
|
||||
private val enc1Cert = """
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDpzCCAo+gAwIBAgIBADANBgkqhkiG9w0BAQUFADBuMRowGAYDVQQDDBF3d3cu
|
||||
bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0
|
||||
dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw
|
||||
HhcNMTcwOTI2MTAwNDMwWhcNMzcwOTIxMTAwNDMwWjBuMRowGAYDVQQDDBF3d3cu
|
||||
bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0
|
||||
dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDsn0JKS/THu328z1IgN0Vz
|
||||
YU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzVGzKFvGfZ03fwFrN7Q8P8R2e8
|
||||
SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7Y0BJX9i/nW/L0L/VaE8CZT
|
||||
AqYBdcSJGgHJjY4UMf892ZPTa9T2Dl3ggdMZ7BQ2kiCiCC3qV99b0igRJGmmLQaG
|
||||
iAflhFzuDQPMifUMq75wI8RSRPdxUAtjTfkl68QHu7Umyeyy33OQgdUKaTl5zcS3
|
||||
VSQbNjveVCNM4RDH1RlEc+7Wf1BY8APqT6jbiBcROJD2CeoLH2eiIJCi+61ZkSGf
|
||||
AgMBAAGjUDBOMB0GA1UdDgQWBBTFrXz2tk1HivD9rQ75qeoyHrAgIjAfBgNVHSME
|
||||
GDAWgBTFrXz2tk1HivD9rQ75qeoyHrAgIjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
|
||||
DQEBBQUAA4IBAQARQTX21QKO77gAzBszFJ6xVnjfa23YZF26Z4X1KaM8uV8TGzuN
|
||||
JA95XmReeP2iO3r8EWXS9djVCD64m2xx6FOsrUI8HZaw1JErU8mmOaLAe8q9RsOm
|
||||
9Eq37e4vFp2YUEInYUqs87ByUcA4/8g3lEYeIUnRsRsWsA45S3wD7wy07t+KAn7j
|
||||
yMmfxdma6hFfG9iN/egN6QXUAyIPXvUvlUuZ7/BhWBj/3sHMrF9quy9Q2DOI8F3t
|
||||
1wdQrkq4BtStKhciY5AIXz9SqsctFHTv4Lwgtkapoel4izJnO0ZqYTXVe7THwri9
|
||||
H/gua6uJDWH9jk2/CiZDWfsyFuNUuXvDSp05
|
||||
-----END CERTIFICATE-----
|
||||
""".trimIndent()
|
||||
|
||||
private val enc2Cert = """
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC7DCCAdSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDDARlbmMz
|
||||
MB4XDTIwMDcwODA3MzE1OFoXDTQwMDcwMzA3MzE1OFowDzENMAsGA1UEAwwEZW5j
|
||||
MzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAI/83eC/EF3xOocwjO+Z
|
||||
ZkPc1TFxt3aUgjEvrpZu45LOqesG67kkkVDYgjeg3Biz9XRUQXqtXaAyxRZH8GiH
|
||||
PFyKUiP1bUlCptd8X+hk9vxeN25YS5OS2RrxU9tDQ/dVOHr20427UvVCighotQnR
|
||||
/6+md1FQMV92PFxji7OP5TWOE1y389X6eb7kSPLs8Tu+2PpqaNVQ9C/89Y8KNYWs
|
||||
x9Zo+kbQhjfFFUikEpkuzMgT9QLaeq6xuXIPP+y1tzNmF6NTL0a2GoYULuxYWnCe
|
||||
joFyXj77LuLmK+KXfPdhvlxa5Kl9XHSxKPHBVVQpwPqNMT+b2T1VLE2l7M9NfImy
|
||||
iLcCAwEAAaNTMFEwHQYDVR0OBBYEFBKubDeR2lXwuyTrdyv6O7euPS4PMB8GA1Ud
|
||||
IwQYMBaAFBKubDeR2lXwuyTrdyv6O7euPS4PMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
|
||||
KoZIhvcNAQEFBQADggEBAChCOIH8CkEpm1eqjsuuNPa93aduLjtnZXat5eIKsKCl
|
||||
rL9nFslpg/DO5SeU5ynPY9F2QjX5CN/3RxDXum9vFfpXhTJphOv8N0uHU4ucmQxE
|
||||
DN388Vt5VtN3V2pzNUL3JSiG6qeYG047/r/zhGFVpcgb2465G5mEwFT0qnkEseCC
|
||||
VVZ63GN8hZgUobyRXxMIhkfWlbO1dgABB4VNyudq0CW8urmewkkbUBwCslvtUvPM
|
||||
WuzpQjq2A80bvbrAqO5VUfvMcqRiUWkDgfa6cHXyV0o4N11mMIoxsMgh+PFYr6lR
|
||||
BHkuQHqKEwP8kkWugIFj3TMcy9dYtXfMXWvzFaDoE4s=
|
||||
-----END CERTIFICATE-----
|
||||
""".trimIndent()
|
||||
|
||||
private val enc2PrivateKey = """
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCP/N3gvxBd8TqH
|
||||
MIzvmWZD3NUxcbd2lIIxL66WbuOSzqnrBuu5JJFQ2II3oNwYs/V0VEF6rV2gMsUW
|
||||
R/BohzxcilIj9W1JQqbXfF/oZPb8XjduWEuTktka8VPbQ0P3VTh69tONu1L1QooI
|
||||
aLUJ0f+vpndRUDFfdjxcY4uzj+U1jhNct/PV+nm+5Ejy7PE7vtj6amjVUPQv/PWP
|
||||
CjWFrMfWaPpG0IY3xRVIpBKZLszIE/UC2nqusblyDz/stbczZhejUy9GthqGFC7s
|
||||
WFpwno6Bcl4++y7i5ivil3z3Yb5cWuSpfVx0sSjxwVVUKcD6jTE/m9k9VSxNpezP
|
||||
TXyJsoi3AgMBAAECggEACWwKFtlZ2FPfORZ3unwGwZ0TRFOFJljMdiyBF6307Vfh
|
||||
rZP729clPS2Vw88eZ+1qu+yBhmYO0NtRo0Yc2LI0xHd2rYyzVI5sfYBRhFMLCHOf
|
||||
2/QiKet7knRFQP1TVr14Xy+Eo2slIBB1GNzFL/nSaeuSNjtxp6YEiCUpcJwTayAi
|
||||
Squ5QWMxhlciLKvwUkraFRBqkugvMz3jXzuk/i+DcYlOgoj+tytweNn/azOMH9MH
|
||||
mWI+3owYspjzE1rVpbrcWImvlnbInd0z9KaQPpBf7Njj7wtyBMaYww4K4GCMhboD
|
||||
SQCYgpnznWkPIN3jyXtmNVSsZ1nvD+Laod+0p7giOQKBgQDA6KEKctYpbt051yTe
|
||||
2UP8hpq+MUSS7FIXiHlUc8s0PSujouypUyzfrPeL6yquI0GtKHkMVCWwfT+otMZR
|
||||
VnklofrmPTPovvsUQFM4Di411NZwzfxEbBFyVXAUWcLd9NxJ1hZW7w+hLk/N5Bej
|
||||
DOa2CncZmifyMNIlvIn7T1vDyQKBgQC/FE8HaDBoN98m/3rEjx7/rVtX8dCei5By
|
||||
Fzg/yQ2u4ELbf/Qk/n4k75sy0690EwnFdJxVn2gdNgS1YDv8YP/N5Wfq8xnX9V9B
|
||||
irWY/W24cN2qDNXm5i8o5wklyt+fDVqMcEHFfONUpLC+RYmOdc1rrFxPaQOYYYpp
|
||||
dWsnuG0ofwKBgBm6rUf8ew35qG3/gP5sEgJLXbZCUfgapvRWkoAuFYs5IWno4BHR
|
||||
cym+IyI5Um75atgSjtqTGpfIjMYOnmjY1L2tNg6hWRwQ5OIVlkPiuE0bvyI6hwwF
|
||||
MeqC9LjyI+iAsSTz9fTQW9BOofw/ENwBa4AaMzpp8iv+UPkRhYHMWtvpAoGAX6As
|
||||
RMqxnxaHCR9GM2Rk4RPC6OpNu2qhKVfRgKp/vIrjKrKIXpM2UgnPo8oovnBgrX7E
|
||||
Vl1mX2gPRy4YFx/8JPCv5vcucdOMjmJ6q0v5QxrI9DdkPR/pbhDhlRZIf3LRZAMy
|
||||
B0GPC2c4RKDMTI1L9pzVvbASaoo2GLz4mXJEvsUCgYEAibwFNXz1H52sZtL6/1zQ
|
||||
1rHCTS8qkryBhxl5eYa6MV5YkbLJZZstF0w2nLxkPba8NttS/nJqjX/iJobD5uLb
|
||||
UzeD8jMeAWPNt4DZCtA4ossNYcXIMKqBVFKOANMvAAvLMpVdlNYSucNnTSQcLwI6
|
||||
2J9mW5WvAAaG+j28Q/GKSuE=
|
||||
""".trimIndent()
|
||||
|
||||
@Test
|
||||
fun testEncryptDecryptMetadata() {
|
||||
val metadataKey = EncryptionUtils.generateKey()
|
||||
|
||||
val metadata = DecryptedMetadata(
|
||||
mutableListOf("hash1", "hash of key 2"),
|
||||
false,
|
||||
1,
|
||||
mutableMapOf(
|
||||
Pair(EncryptionUtils.generateUid(), "Folder 1"),
|
||||
Pair(EncryptionUtils.generateUid(), "Folder 2"),
|
||||
Pair(EncryptionUtils.generateUid(), "Folder 3")
|
||||
),
|
||||
mutableMapOf(
|
||||
Pair(
|
||||
EncryptionUtils.generateUid(),
|
||||
DecryptedFile(
|
||||
"file 1.png",
|
||||
"image/png",
|
||||
"initializationVector",
|
||||
"authenticationTag",
|
||||
"key 1"
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
EncryptionUtils.generateUid(),
|
||||
DecryptedFile(
|
||||
"file 2.png",
|
||||
"image/png",
|
||||
"initializationVector 2",
|
||||
"authenticationTag 2",
|
||||
"key 2"
|
||||
)
|
||||
)
|
||||
),
|
||||
metadataKey
|
||||
)
|
||||
val encrypted = encryptionUtilsV2.encryptMetadata(metadata, metadataKey)
|
||||
val decrypted = encryptionUtilsV2.decryptMetadata(encrypted, metadataKey)
|
||||
|
||||
assertEquals(metadata, decrypted)
|
||||
}
|
||||
|
||||
@Throws(Throwable::class)
|
||||
@Test
|
||||
fun encryptDecryptSymmetric() {
|
||||
val string = "123"
|
||||
val metadataKey = EncryptionUtils.generateKeyString()
|
||||
|
||||
val e = EncryptionUtils.encryptStringSymmetricAsString(
|
||||
string,
|
||||
metadataKey.toByteArray()
|
||||
)
|
||||
|
||||
val d = EncryptionUtils.decryptStringSymmetric(e, metadataKey.toByteArray())
|
||||
assertEquals(string, d)
|
||||
|
||||
val encryptedMetadata = EncryptionUtils.encryptStringSymmetric(
|
||||
string,
|
||||
metadataKey.toByteArray(),
|
||||
EncryptionUtils.ivDelimiter
|
||||
)
|
||||
|
||||
val d2 = EncryptionUtils.decryptStringSymmetric(
|
||||
encryptedMetadata.ciphertext,
|
||||
metadataKey.toByteArray()
|
||||
)
|
||||
assertEquals(string, d2)
|
||||
|
||||
val decrypted = EncryptionUtils.decryptStringSymmetric(
|
||||
encryptedMetadata.ciphertext,
|
||||
metadataKey.toByteArray(),
|
||||
encryptedMetadata.authenticationTag,
|
||||
encryptedMetadata.nonce
|
||||
)
|
||||
|
||||
assertEquals(string, EncryptionUtils.decodeBase64BytesToString(decrypted))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEncryptDecryptUser() {
|
||||
val metadataKeyBase64 = EncryptionUtils.generateKeyString()
|
||||
val metadataKey = EncryptionUtils.decodeStringToBase64Bytes(metadataKeyBase64)
|
||||
|
||||
val user = DecryptedUser("t1", encryptionTestUtils.t1PublicKey)
|
||||
|
||||
val encryptedUser = encryptionUtilsV2.encryptUser(user, metadataKey)
|
||||
assertNotEquals(encryptedUser.encryptedMetadataKey, metadataKeyBase64)
|
||||
|
||||
val decryptedMetadataKey = encryptionUtilsV2.decryptMetadataKey(encryptedUser, encryptionTestUtils.t1PrivateKey)
|
||||
val decryptedMetadataKeyBase64 = EncryptionUtils.encodeBytesToBase64String(decryptedMetadataKey)
|
||||
|
||||
assertEquals(metadataKeyBase64, decryptedMetadataKeyBase64)
|
||||
}
|
||||
|
||||
@Throws(com.owncloud.android.operations.UploadException::class, Throwable::class)
|
||||
@Test
|
||||
fun testEncryptDecryptMetadataFile() {
|
||||
val enc1 = MockUser("enc1", "Nextcloud")
|
||||
|
||||
val root = OCFile("/")
|
||||
storageManager.saveFile(root)
|
||||
|
||||
val folder = OCFile("/enc/").apply {
|
||||
parentId = storageManager.getFileByDecryptedRemotePath("/")?.fileId ?: throw IllegalStateException()
|
||||
}
|
||||
|
||||
val metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert)
|
||||
|
||||
val encrypted = encryptionUtilsV2.encryptFolderMetadataFile(
|
||||
metadataFile,
|
||||
enc1.accountName,
|
||||
folder,
|
||||
storageManager,
|
||||
client,
|
||||
enc1PrivateKey,
|
||||
user,
|
||||
targetContext,
|
||||
arbitraryDataProvider
|
||||
)
|
||||
|
||||
val signature = encryptionUtilsV2.getMessageSignature(enc1Cert, enc1PrivateKey, encrypted)
|
||||
|
||||
val decrypted = encryptionUtilsV2.decryptFolderMetadataFile(
|
||||
encrypted,
|
||||
enc1.accountName,
|
||||
enc1PrivateKey,
|
||||
folder,
|
||||
storageManager,
|
||||
client,
|
||||
0,
|
||||
signature,
|
||||
user,
|
||||
targetContext,
|
||||
arbitraryDataProvider
|
||||
)
|
||||
|
||||
assertEquals(metadataFile, decrypted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addFile() {
|
||||
val enc1 = MockUser("enc1", "Nextcloud")
|
||||
val metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert)
|
||||
assertEquals(2, metadataFile.metadata.files.size)
|
||||
assertEquals(1, metadataFile.metadata.counter)
|
||||
|
||||
val updatedMetadata = encryptionUtilsV2.addFileToMetadata(
|
||||
EncryptionUtils.generateUid(),
|
||||
OCFile("/test.jpg").apply {
|
||||
mimeType = MimeType.JPEG
|
||||
},
|
||||
EncryptionUtils.generateIV(),
|
||||
EncryptionUtils.generateUid(), // random string, not real tag
|
||||
EncryptionUtils.generateKey(),
|
||||
metadataFile,
|
||||
storageManager
|
||||
)
|
||||
|
||||
assertEquals(3, updatedMetadata.metadata.files.size)
|
||||
assertEquals(2, updatedMetadata.metadata.counter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeFile() {
|
||||
val enc1 = MockUser("enc1", "Nextcloud")
|
||||
val metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert)
|
||||
assertEquals(2, metadataFile.metadata.files.size)
|
||||
|
||||
val filename = metadataFile.metadata.files.keys.first()
|
||||
|
||||
encryptionUtilsV2.removeFileFromMetadata(filename, metadataFile)
|
||||
|
||||
assertEquals(1, metadataFile.metadata.files.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun renameFile() {
|
||||
val enc1 = MockUser("enc1", "Nextcloud")
|
||||
val metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert)
|
||||
assertEquals(2, metadataFile.metadata.files.size)
|
||||
|
||||
val key = metadataFile.metadata.files.keys.first()
|
||||
val decryptedFile = metadataFile.metadata.files[key]
|
||||
val filename = decryptedFile?.filename
|
||||
val newFilename = "New File 1"
|
||||
|
||||
encryptionUtilsV2.renameFile(key, newFilename, metadataFile)
|
||||
|
||||
assertEquals(newFilename, metadataFile.metadata.files[key]?.filename)
|
||||
assertNotEquals(filename, newFilename)
|
||||
assertNotEquals(filename, metadataFile.metadata.files[key]?.filename)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addFolder() {
|
||||
val folder = OCFile("/e/")
|
||||
val enc1 = MockUser("enc1", "Nextcloud")
|
||||
val metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert)
|
||||
assertEquals(2, metadataFile.metadata.files.size)
|
||||
assertEquals(3, metadataFile.metadata.folders.size)
|
||||
|
||||
val updatedMetadata = encryptionUtilsV2.addFolderToMetadata(
|
||||
EncryptionUtils.generateUid(),
|
||||
"new subfolder",
|
||||
metadataFile,
|
||||
folder,
|
||||
storageManager
|
||||
)
|
||||
|
||||
assertEquals(2, updatedMetadata.metadata.files.size)
|
||||
assertEquals(4, updatedMetadata.metadata.folders.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeFolder() {
|
||||
val folder = OCFile("/e/")
|
||||
val enc1 = MockUser("enc1", "Nextcloud")
|
||||
val metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert)
|
||||
assertEquals(2, metadataFile.metadata.files.size)
|
||||
assertEquals(3, metadataFile.metadata.folders.size)
|
||||
|
||||
val encryptedFileName = EncryptionUtils.generateUid()
|
||||
var updatedMetadata = encryptionUtilsV2.addFolderToMetadata(
|
||||
encryptedFileName,
|
||||
"new subfolder",
|
||||
metadataFile,
|
||||
folder,
|
||||
storageManager
|
||||
)
|
||||
|
||||
assertEquals(2, updatedMetadata.metadata.files.size)
|
||||
assertEquals(4, updatedMetadata.metadata.folders.size)
|
||||
|
||||
updatedMetadata = encryptionUtilsV2.removeFolderFromMetadata(
|
||||
encryptedFileName,
|
||||
updatedMetadata
|
||||
)
|
||||
|
||||
assertEquals(2, updatedMetadata.metadata.files.size)
|
||||
assertEquals(3, updatedMetadata.metadata.folders.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyMetadata() {
|
||||
val folder = OCFile("/e/")
|
||||
val enc1 = MockUser("enc1", "Nextcloud")
|
||||
val metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert)
|
||||
val encrypted = encryptionUtilsV2.encryptFolderMetadataFile(
|
||||
metadataFile,
|
||||
enc1UserId,
|
||||
folder,
|
||||
storageManager,
|
||||
client,
|
||||
enc1PrivateKey,
|
||||
user,
|
||||
targetContext,
|
||||
arbitraryDataProvider
|
||||
)
|
||||
|
||||
val signature = encryptionUtilsV2.getMessageSignature(enc1Cert, enc1PrivateKey, encrypted)
|
||||
|
||||
encryptionUtilsV2.verifyMetadata(encrypted, metadataFile, 0, signature)
|
||||
|
||||
assertTrue(true) // if we reach this, test is successful
|
||||
}
|
||||
|
||||
private fun generateDecryptedFileV1(): com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile {
|
||||
return com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile().apply {
|
||||
encrypted = Data().apply {
|
||||
key = EncryptionUtils.generateKeyString()
|
||||
filename = "Random filename.jpg"
|
||||
mimetype = MimeType.JPEG
|
||||
version = 1.0
|
||||
}
|
||||
initializationVector = EncryptionUtils.generateKeyString()
|
||||
authenticationTag = EncryptionUtils.generateKeyString()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMigrateDecryptedV1ToV2() {
|
||||
val v1 = generateDecryptedFileV1()
|
||||
val v2 = encryptionUtilsV2.migrateDecryptedFileV1ToV2(v1)
|
||||
|
||||
assertEquals(v1.encrypted.filename, v2.filename)
|
||||
assertEquals(v1.encrypted.mimetype, v2.mimetype)
|
||||
assertEquals(v1.authenticationTag, v2.authenticationTag)
|
||||
assertEquals(v1.initializationVector, v2.nonce)
|
||||
assertEquals(v1.encrypted.key, v2.key)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMigrateMetadataV1ToV2() {
|
||||
OCFile("/").apply {
|
||||
storageManager.saveFile(this)
|
||||
}
|
||||
|
||||
val folder = OCFile("/enc/").apply {
|
||||
parentId = storageManager.getFileByDecryptedRemotePath("/")?.fileId ?: throw IllegalStateException()
|
||||
}
|
||||
|
||||
val v1 = DecryptedFolderMetadataFileV1().apply {
|
||||
metadata = com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedMetadata().apply {
|
||||
metadataKeys = mapOf(Pair(0, EncryptionUtils.generateKeyString()))
|
||||
}
|
||||
files = mapOf(
|
||||
Pair(EncryptionUtils.generateUid(), generateDecryptedFileV1()),
|
||||
Pair(EncryptionUtils.generateUid(), generateDecryptedFileV1()),
|
||||
Pair(
|
||||
EncryptionUtils.generateUid(),
|
||||
com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile().apply {
|
||||
encrypted = Data().apply {
|
||||
key = EncryptionUtils.generateKeyString()
|
||||
filename = "subFolder"
|
||||
mimetype = MimeType.WEBDAV_FOLDER
|
||||
}
|
||||
initializationVector = EncryptionUtils.generateKeyString()
|
||||
authenticationTag = null
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
val v2 = encryptionUtilsV2.migrateV1ToV2(
|
||||
v1,
|
||||
enc1UserId,
|
||||
enc1Cert,
|
||||
folder,
|
||||
storageManager
|
||||
)
|
||||
|
||||
assertEquals(2, v2.metadata.files.size)
|
||||
assertEquals(1, v2.metadata.folders.size)
|
||||
assertEquals(1, v2.users.size) // only one user upon migration
|
||||
}
|
||||
|
||||
@Throws(com.owncloud.android.operations.UploadException::class, Throwable::class)
|
||||
@Test
|
||||
fun addSharee() {
|
||||
val enc1 = MockUser("enc1", "Nextcloud")
|
||||
val enc2 = MockUser("enc2", "Nextcloud")
|
||||
|
||||
val root = OCFile("/")
|
||||
storageManager.saveFile(root)
|
||||
|
||||
val folder = OCFile("/enc/").apply {
|
||||
parentId = storageManager.getFileByDecryptedRemotePath("/")?.fileId ?: throw IllegalStateException()
|
||||
}
|
||||
|
||||
var metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert)
|
||||
|
||||
metadataFile = encryptionUtilsV2.addShareeToMetadata(metadataFile, enc2.accountName, enc2Cert)
|
||||
|
||||
val encryptedMetadataFile = encryptionUtilsV2.encryptFolderMetadataFile(
|
||||
metadataFile,
|
||||
client.userId,
|
||||
folder,
|
||||
storageManager,
|
||||
client,
|
||||
enc1PrivateKey,
|
||||
user,
|
||||
targetContext,
|
||||
arbitraryDataProvider
|
||||
)
|
||||
|
||||
val signature = encryptionUtilsV2.getMessageSignature(enc1Cert, enc1PrivateKey, encryptedMetadataFile)
|
||||
|
||||
val decryptedByEnc1 = encryptionUtilsV2.decryptFolderMetadataFile(
|
||||
encryptedMetadataFile,
|
||||
enc1.accountName,
|
||||
enc1PrivateKey,
|
||||
folder,
|
||||
storageManager,
|
||||
client,
|
||||
metadataFile.metadata.counter,
|
||||
signature,
|
||||
user,
|
||||
targetContext,
|
||||
arbitraryDataProvider
|
||||
)
|
||||
assertEquals(metadataFile.metadata, decryptedByEnc1.metadata)
|
||||
|
||||
val decryptedByEnc2 = encryptionUtilsV2.decryptFolderMetadataFile(
|
||||
encryptedMetadataFile,
|
||||
enc2.accountName,
|
||||
enc2PrivateKey,
|
||||
folder,
|
||||
storageManager,
|
||||
client,
|
||||
metadataFile.metadata.counter,
|
||||
signature,
|
||||
user,
|
||||
targetContext,
|
||||
arbitraryDataProvider
|
||||
)
|
||||
assertEquals(metadataFile.metadata, decryptedByEnc2.metadata)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeSharee() {
|
||||
val enc1 = MockUser("enc1", "Nextcloud")
|
||||
val enc2 = MockUser("enc2", "Nextcloud")
|
||||
var metadataFile = generateDecryptedFolderMetadataFile(enc1, enc1Cert)
|
||||
metadataFile = encryptionUtilsV2.addShareeToMetadata(metadataFile, enc2.accountName, enc2Cert)
|
||||
|
||||
assertEquals(2, metadataFile.users.size)
|
||||
|
||||
metadataFile = encryptionUtilsV2.removeShareeFromMetadata(metadataFile, enc2.accountName)
|
||||
|
||||
assertEquals(1, metadataFile.users.size)
|
||||
}
|
||||
|
||||
private fun generateDecryptedFolderMetadataFile(user: User, cert: String): DecryptedFolderMetadataFile {
|
||||
val metadata = DecryptedMetadata(
|
||||
mutableListOf("hash1", "hash of key 2"),
|
||||
false,
|
||||
1,
|
||||
mutableMapOf(
|
||||
Pair(EncryptionUtils.generateUid(), "Folder 1"),
|
||||
Pair(EncryptionUtils.generateUid(), "Folder 2"),
|
||||
Pair(EncryptionUtils.generateUid(), "Folder 3")
|
||||
),
|
||||
mutableMapOf(
|
||||
Pair(
|
||||
EncryptionUtils.generateUid(),
|
||||
DecryptedFile(
|
||||
"file 1.png",
|
||||
"image/png",
|
||||
"initializationVector",
|
||||
"authenticationTag",
|
||||
"key 1"
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
EncryptionUtils.generateUid(),
|
||||
DecryptedFile(
|
||||
"file 2.png",
|
||||
"image/png",
|
||||
"initializationVector 2",
|
||||
"authenticationTag 2",
|
||||
"key 2"
|
||||
)
|
||||
)
|
||||
),
|
||||
EncryptionUtils.generateKey()
|
||||
)
|
||||
|
||||
val users = mutableListOf(
|
||||
DecryptedUser(user.accountName, cert)
|
||||
)
|
||||
|
||||
metadata.keyChecksums.add(encryptionUtilsV2.hashMetadataKey(metadata.metadataKey))
|
||||
|
||||
return DecryptedFolderMetadataFile(metadata, users, mutableMapOf())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGZip() {
|
||||
val string = """
|
||||
This is a test.
|
||||
This is a test.
|
||||
This is a test.
|
||||
This is a test.
|
||||
This is a test.
|
||||
This is a test.
|
||||
This is a test.
|
||||
This is a test.
|
||||
This is a test.
|
||||
This is a test.
|
||||
This is a test.
|
||||
This is a test.
|
||||
This is a test.
|
||||
It contains linewraps and special characters:
|
||||
$$|²›³¥!’‘‘
|
||||
|
||||
""".trimIndent()
|
||||
|
||||
val gzipped = encryptionUtilsV2.gZipCompress(string)
|
||||
|
||||
val result = encryptionUtilsV2.gZipDecompress(gzipped)
|
||||
|
||||
assertEquals(string, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun gunzip() {
|
||||
val string = "H4sICNVkD2QAAwArycgsVgCiRIWS1OISPQDD9wZODwAAAA=="
|
||||
val decoded = EncryptionUtils.decodeStringToBase64Bytes(string)
|
||||
val gunzip = encryptionUtilsV2.gZipDecompress(decoded)
|
||||
|
||||
assertEquals("this is a test.\n", gunzip)
|
||||
}
|
||||
|
||||
// @Test
|
||||
// fun validate() {
|
||||
// // ALEX
|
||||
// val metadata1 = """{
|
||||
// "metadata": {
|
||||
// "authenticationTag": "zMozev5R09UopLrq7Je1lw==",
|
||||
// "ciphertext": "j0OBtUrEt4IveGiexjmGK7eKEaWrY70ZkteA5KxHDaZT/t2wwGy9j2FPQGpqXnW6OO3iAYPNgwFikI1smnfNvqdxzVDvhavl/IXa9Kg2niWyqK3D9zpz0YD6mDvl0XsOgTNVyGXNVREdWgzGEERCQoyHI1xowt/swe3KCXw+lf+XPF/t1PfHv0DiDVk70AeWGpPPPu6yggAIxB4Az6PEZhaQWweTC0an48l2FHj2MtB2PiMHtW2v7RMuE8Al3PtE4gOA8CMFrB+Npy6rKcFCXOgTZm5bp7q+J1qkhBDbiBYtvdsYujJ52Xa5SifTpEhGeWWLFnLLgPAQ8o6bXcWOyCoYfLfp4Jpft/Y7H8qzHbPewNSyD6maEv+xljjfU7hxibbszz5A4JjMdQy2BDGoTmJx7Mas+g6l6ZuHLVbdmgQOvD3waJBy6rOg0euux0Cn4bB4bIFEF2KvbhdGbY1Uiq9DYa7kEmSEnlcAYaHyroTkDg4ew7ER0vIBBMzKM3r+UdPVKKS66uyXtZc=",
|
||||
// "nonce": "W+lxQJeGq7XAJiGfcDohkg=="
|
||||
// },
|
||||
// "users": [{
|
||||
// "certificate": "-----BEGIN CERTIFICATE-----\nMIIDkDCCAnigAwIBAgIBADANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJERTEb\nMBkGA1UECAwSQmFkZW4tV3VlcnR0ZW1iZXJnMRIwEAYDVQQHDAlTdHV0dGdhcnQx\nEjAQBgNVBAoMCU5leHRjbG91ZDENMAsGA1UEAwwEam9objAeFw0yMzA3MTQwNzM0\nNTZaFw00MzA3MDkwNzM0NTZaMGExCzAJBgNVBAYTAkRFMRswGQYDVQQIDBJCYWRl\nbi1XdWVydHRlbWJlcmcxEjAQBgNVBAcMCVN0dXR0Z2FydDESMBAGA1UECgwJTmV4\ndGNsb3VkMQ0wCwYDVQQDDARqb2huMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEA7j3Er5YahJT0LAnSRLhpqbRo+E1AVnt98rvp3DmEfBHNzNB+DS9IBDkS\nSXM/YtfAci6Tcw8ujVBjrZX/WEmrf8ynQHxYmSaJSnP8uAT306/MceZpdpruEc9/\nS10a7vp54Zbld4NYdmfS71oVFVKgM7c/Vthx+rgu48fuxzbWAvVYLFcx47hz0DJT\nnjz2Za/R68uXpxfz7J9uEXYiqsAs/FobDsLZluT3RyywVRwKBed1EZxUeLIJiyxp\nUthhGfIb8b3Vf9jZoUVi3m5gmc4spJQHvYAkfZYHzd9ras8jBu1abQRxcu2CYnVo\n6Y0mTxhKhQS/n5gjv3ExiQF3wp/XYwIDAQABo1MwUTAdBgNVHQ4EFgQUmTeILVuB\ntv70fTGkXWGAueDp5kAwHwYDVR0jBBgwFoAUmTeILVuBtv70fTGkXWGAueDp5kAw\nDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAyVtq9XAvW7nxSW/8\nhp30z6xbzGiuviXhy/Jo91VEa8IRsWCCn3OmDFiVduTEowx76tf8clJP0gk7Pozi\n6dg/7Fin+FqQGXfCk8bLAh9gXKAikQ2GK8yRN3slRFwYC2mm23HrLdKXZHUqJcpB\nMz2zsSrOGPj1YsYOl/U8FU6KA7Yj7U3q7kDMYTAgzUPZAH+d1DISGWpZsMa0RYid\nvigCCLByiccmS/Co4Sb1esF58H+YtV5+nFBRwx881U2g2TgDKF1lPMK/y3d8B8mh\nUtW+lFxRpvyNUDpsMjOErOrtNFEYbgoUJLtqwBMmyGR+nmmh6xna331QWcRAmw0P\nnDO4ew==\n-----END CERTIFICATE-----\n",
|
||||
// "encryptedMetadataKey": "HVT49bYmwXbGs/dJ2avgU9unrKnPf03MYUI5ZysSR1Bz5pqz64gzH2GBAuUJ+Q4VmHtEfcMaWW7VXgzfCQv5xLBrk+RSgcLOKnlIya8jaDlfttWxbe8jJK+/0+QVPOc6ycA/t5HNCPg09hzj+gnb2L89UHxL5accZD0iEzb5cQbGrc/N6GthjgGrgFKtFf0HhDVplUr+DL9aTyKuKLBPjrjuZbv8M6ZfXO93mOMwSZH3c3rwDUHb/KEaTR/Og4pWQmrqr1VxGLqeV/+GKWhzMYThrOZAUz+5gsbckU2M5V9i+ph0yBI5BjOZVhNuDwW8yP8WtyRJwQc+UBRei/RGBQ==",
|
||||
// "userId": "john"
|
||||
// }],
|
||||
// "version": "2"
|
||||
// }
|
||||
//
|
||||
// """
|
||||
//
|
||||
// val signature1 =
|
||||
// "ewogICAgIm1ldGFkYXRhIjogewogICAgICAgICJhdXRoZW50aWNhdGlvblRhZyI6ICJ6TW96ZXY1UjA5VW9wTHJxN0plMWx3PT0iLAogICAgICAgICJjaXBoZXJ0ZXh0IjogImowT0J0VXJFdDRJdmVHaWV4am1HSzdlS0VhV3JZNzBaa3RlQTVLeEhEYVpUL3Qyd3dHeTlqMkZQUUdwcVhuVzZPTzNpQVlQTmd3RmlrSTFzbW5mTnZxZHh6VkR2aGF2bC9JWGE5S2cybmlXeXFLM0Q5enB6MFlENm1EdmwwWHNPZ1ROVnlHWE5WUkVkV2d6R0VFUkNRb3lISTF4b3d0L3N3ZTNLQ1h3K2xmK1hQRi90MVBmSHYwRGlEVms3MEFlV0dwUFBQdTZ5Z2dBSXhCNEF6NlBFWmhhUVd3ZVRDMGFuNDhsMkZIajJNdEIyUGlNSHRXMnY3Uk11RThBbDNQdEU0Z09BOENNRnJCK05weTZyS2NGQ1hPZ1RabTVicDdxK0oxcWtoQkRiaUJZdHZkc1l1ako1MlhhNVNpZlRwRWhHZVdXTEZuTExnUEFROG82YlhjV095Q29ZZkxmcDRKcGZ0L1k3SDhxekhiUGV3TlN5RDZtYUV2K3hsampmVTdoeGliYnN6ejVBNEpqTWRReTJCREdvVG1KeDdNYXMrZzZsNlp1SExWYmRtZ1FPdkQzd2FKQnk2ck9nMGV1dXgwQ240YkI0YklGRUYyS3ZiaGRHYlkxVWlxOURZYTdrRW1TRW5sY0FZYUh5cm9Ua0RnNGV3N0VSMHZJQkJNektNM3IrVWRQVktLUzY2dXlYdFpjPSIsCiAgICAgICAgIm5vbmNlIjogIlcrbHhRSmVHcTdYQUppR2ZjRG9oa2c9PSIKICAgIH0sCiAgICAidXNlcnMiOiB7CiAgICAgICAgImNlcnRpZmljYXRlIjogIi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLVxuTUlJRGtEQ0NBbmlnQXdJQkFnSUJBREFOQmdrcWhraUc5dzBCQVFVRkFEQmhNUXN3Q1FZRFZRUUdFd0pFUlRFYlxuTUJrR0ExVUVDQXdTUW1Ga1pXNHRWM1ZsY25SMFpXMWlaWEpuTVJJd0VBWURWUVFIREFsVGRIVjBkR2RoY25ReFxuRWpBUUJnTlZCQW9NQ1U1bGVIUmpiRzkxWkRFTk1Bc0dBMVVFQXd3RWFtOW9iakFlRncweU16QTNNVFF3TnpNMFxuTlRaYUZ3MDBNekEzTURrd056TTBOVFphTUdFeEN6QUpCZ05WQkFZVEFrUkZNUnN3R1FZRFZRUUlEQkpDWVdSbFxuYmkxWGRXVnlkSFJsYldKbGNtY3hFakFRQmdOVkJBY01DVk4wZFhSMFoyRnlkREVTTUJBR0ExVUVDZ3dKVG1WNFxuZEdOc2IzVmtNUTB3Q3dZRFZRUUREQVJxYjJodU1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQlxuQ2dLQ0FRRUE3ajNFcjVZYWhKVDBMQW5TUkxocHFiUm8rRTFBVm50OThydnAzRG1FZkJITnpOQitEUzlJQkRrU1xuU1hNL1l0ZkFjaTZUY3c4dWpWQmpyWlgvV0VtcmY4eW5RSHhZbVNhSlNuUDh1QVQzMDYvTWNlWnBkcHJ1RWM5L1xuUzEwYTd2cDU0WmJsZDROWWRtZlM3MW9WRlZLZ003Yy9WdGh4K3JndTQ4ZnV4emJXQXZWWUxGY3g0N2h6MERKVFxubmp6MlphL1I2OHVYcHhmejdKOXVFWFlpcXNBcy9Gb2JEc0xabHVUM1J5eXdWUndLQmVkMUVaeFVlTElKaXl4cFxuVXRoaEdmSWI4YjNWZjlqWm9VVmkzbTVnbWM0c3BKUUh2WUFrZlpZSHpkOXJhczhqQnUxYWJRUnhjdTJDWW5Wb1xuNlkwbVR4aEtoUVMvbjVnanYzRXhpUUYzd3AvWFl3SURBUUFCbzFNd1VUQWRCZ05WSFE0RUZnUVVtVGVJTFZ1QlxudHY3MGZUR2tYV0dBdWVEcDVrQXdId1lEVlIwakJCZ3dGb0FVbVRlSUxWdUJ0djcwZlRHa1hXR0F1ZURwNWtBd1xuRHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBeVZ0cTlYQXZXN254U1cvOFxuaHAzMHo2eGJ6R2l1dmlYaHkvSm85MVZFYThJUnNXQ0NuM09tREZpVmR1VEVvd3g3NnRmOGNsSlAwZ2s3UG96aVxuNmRnLzdGaW4rRnFRR1hmQ2s4YkxBaDlnWEtBaWtRMkdLOHlSTjNzbFJGd1lDMm1tMjNIckxkS1haSFVxSmNwQlxuTXoyenNTck9HUGoxWXNZT2wvVThGVTZLQTdZajdVM3E3a0RNWVRBZ3pVUFpBSCtkMURJU0dXcFpzTWEwUllpZFxudmlnQ0NMQnlpY2NtUy9DbzRTYjFlc0Y1OEgrWXRWNStuRkJSd3g4ODFVMmcyVGdES0YxbFBNSy95M2Q4QjhtaFxuVXRXK2xGeFJwdnlOVURwc01qT0VyT3J0TkZFWWJnb1VKTHRxd0JNbXlHUitubW1oNnhuYTMzMVFXY1JBbXcwUFxubkRPNGV3PT1cbi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS1cbiIsCiAgICAgICAgImVuY3J5cHRlZE1ldGFkYXRhS2V5IjogIkhWVDQ5Ylltd1hiR3MvZEoyYXZnVTl1bnJLblBmMDNNWVVJNVp5c1NSMUJ6NXBxejY0Z3pIMkdCQXVVSitRNFZtSHRFZmNNYVdXN1ZYZ3pmQ1F2NXhMQnJrK1JTZ2NMT0tubEl5YThqYURsZnR0V3hiZThqSksrLzArUVZQT2M2eWNBL3Q1SE5DUGcwOWh6aitnbmIyTDg5VUh4TDVhY2NaRDBpRXpiNWNRYkdyYy9ONkd0aGpnR3JnRkt0RmYwSGhEVnBsVXIrREw5YVR5S3VLTEJQanJqdVpidjhNNlpmWE85M21PTXdTWkgzYzNyd0RVSGIvS0VhVFIvT2c0cFdRbXJxcjFWeEdMcWVWLytHS1doek1ZVGhyT1pBVXorNWdzYmNrVTJNNVY5aStwaDB5Qkk1QmpPWlZoTnVEd1c4eVA4V3R5Ukp3UWMrVUJSZWkvUkdCUT09IiwKICAgICAgICAidXNlcklkIjogImpvaG4iCiAgICB9LAogICAgInZlcnNpb24iOiAiMiIKfQo="
|
||||
//
|
||||
// // TOBI
|
||||
// val metadata =
|
||||
// """{"metadata":{"authenticationTag":"qDcJnAAGtGDlHWiQMBfXgw\u003d\u003d","ciphertext":"3zUhwIgJWMB7DvrbsDaMvh8MbJdoTxL0OMPCCdYSfBt7gB+V/hwqelL1IOaLto3avhHGSebnrotF06iEP/jZwWg9hApIPTHc8B4XTOY0/kezqYyVqTyquTUZpDpqgVAheQskZZ8I4Ir0seajUkt4KtVRfzO6v8CePRrEg6uKwdYsqDcJnAAGtGDlHWiQMBfXgw\u003d\u003d|4hbOyn1ykQL+9D6SnPY3cQ\u003d\u003d","nonce":"4hbOyn1ykQL+9D6SnPY3cQ\u003d\u003d"},"users":[{"certificate":"-----BEGIN CERTIFICATE-----\nMIIC6DCCAdCgAwIBAgIBADANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDDAJ0MTAe\nFw0yMzA3MjUwNzU3MTJaFw00MzA3MjAwNzU3MTJaMA0xCzAJBgNVBAMMAnQxMIIB\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtafHmDBcBqIu4HmMxMDW3j0S\ny+S0YaKwHnBRt85KSwcEov0B5FOLuLknoBGx4Dn3u93ilThXXxacPMHeXL7WPuAs\n21/G7vsqwvrRRnCduf+FUO/AZeDCNErzpsQ8LmTa4PUloLPUcImpSjrHwhMs9Ekv\nEbLRjbeSmSp9XvM+1fV/3jkT5jkOSnCFx5TGwGN5uHqwUir4UWXasvg253NK2XmW\nipKCDCR9TmH1baP3pNdoiChdmErT1c6E4DbBXpTw8XgP5ZbYH+qg1UQ/hC8nRJ3D\nyCcHL+dg/GYraBMhDn4w2Vvq77xNNoNWQ9cT5Ay6cJbQLBQoJQirygQFrobYRQID\nAQABo1MwUTAdBgNVHQ4EFgQUE9zCeA9/QMAtVgLxD23X6ZcodhMwHwYDVR0jBBgw\nFoAUE9zCeA9/QMAtVgLxD23X6ZcodhMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG\n9w0BAQUFAAOCAQEAZdy/YjJlvnz3FQwxp6oVtMJccpdxveEPfLzgaverhtd/vP8O\nAvDzOLgQJHmrDS91SG503eU4cYGyuNKwd77OyTnqMg+GUEmJhGfPpSVrEIdh65jv\nq61T4oqBdehevVmBq54rGiwL0DGv1DlXQlwiJZP4qni2KnOEFcnvL3gVtRnQjXQ+\nkHvlMshkK6w021EMV5NfjG2zg67wC65rLaej5f6Ssp2S7g2VtmE4aXq1bjAuEbqk\n4TiyZHLDdsJuqzyGyyOpMV7i9ucXDoaZt9cGS9hT2vRxTrSH63vKR8Xeig9+stLw\nt9ONcUqCKP7hd8rajtxM4JIIRExwD8OkgARWGg\u003d\u003d\n-----END CERTIFICATE-----\n","encryptedMetadataKey":"s4kDkkLpk1mSmXedP7huiCNC4DYmDAmA2VYGem5M8jIGPC6miVQoo4WXZrEBhdsLw7Msf5iT3A3fTaHhwsI8Jf4McsFyM9/FXT1mCEaGOEpNjbKOlJY1uPUFNOhLqUfFiBos6oBT53hWwoXWjytYvLBbXuXY5YLOysjgBh6URrgFUZAJAmcOJ6OFKgfIIthoqkQc7CQUY97VsRzAXzeYTANBc2yW1pSN51HqftvMzvewFRsJQLcu7a9NjpTdG9LiLhn5eLXOLymXEE/aaPHKXeprlXLzrdWU1xwZRJqV+to2FEiH6CQNsO4+9h5m0VjXekiNeAFrsXB5cJgUipGuzQ\u003d\u003d","userId":"t1"}],"version":"2.0"}"""
|
||||
//
|
||||
// val base = EncryptionUtils.encodeStringToBase64String(metadata)
|
||||
//
|
||||
// val signature =
|
||||
// "MIAGCSqGSIb3DQEHAqCAMIACAQExDTALBglghkgBZQMEAgEwCwYJKoZIhvcNAQcBoIAwggLoMIIB0KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNVBAMMAnQxMB4XDTIzMDcyNTA3NTcxMloXDTQzMDcyMDA3NTcxMlowDTELMAkGA1UEAwwCdDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1p8eYMFwGoi7geYzEwNbePRLL5LRhorAecFG3zkpLBwSi/QHkU4u4uSegEbHgOfe73eKVOFdfFpw8wd5cvtY+4CzbX8bu+yrC+tFGcJ25/4VQ78Bl4MI0SvOmxDwuZNrg9SWgs9RwialKOsfCEyz0SS8RstGNt5KZKn1e8z7V9X/eORPmOQ5KcIXHlMbAY3m4erBSKvhRZdqy+Dbnc0rZeZaKkoIMJH1OYfVto/ek12iIKF2YStPVzoTgNsFelPDxeA/lltgf6qDVRD+ELydEncPIJwcv52D8ZitoEyEOfjDZW+rvvE02g1ZD1xPkDLpwltAsFCglCKvKBAWuhthFAgMBAAGjUzBRMB0GA1UdDgQWBBQT3MJ4D39AwC1WAvEPbdfplyh2EzAfBgNVHSMEGDAWgBQT3MJ4D39AwC1WAvEPbdfplyh2EzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBl3L9iMmW+fPcVDDGnqhW0wlxyl3G94Q98vOBq96uG13+8/w4C8PM4uBAkeasNL3VIbnTd5ThxgbK40rB3vs7JOeoyD4ZQSYmEZ8+lJWsQh2HrmO+rrVPiioF16F69WYGrnisaLAvQMa/UOVdCXCIlk/iqeLYqc4QVye8veBW1GdCNdD6Qe+UyyGQrrDTbUQxXk1+MbbODrvALrmstp6Pl/pKynZLuDZW2YThperVuMC4RuqThOLJkcsN2wm6rPIbLI6kxXuL25xcOhpm31wZL2FPa9HFOtIfre8pHxd6KD36y0vC3041xSoIo/uF3ytqO3EzgkghETHAPw6SABFYaAAAxggHUMIIB0AIBATASMA0xCzAJBgNVBAMMAnQxAgEAMAsGCWCGSAFlAwQCAaCBljAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMzA3MjgwNzMwMTJaMCsGCSqGSIb3DQEJNDEeMBwwCwYJYIZIAWUDBAIBoQ0GCSqGSIb3DQEBCwUAMC8GCSqGSIb3DQEJBDEiBCAx7RTJg7hbY5Mkzjw3f6qhX7k/J0FdVz2cL3ow0AmyYjANBgkqhkiG9w0BAQsFAASCAQAbUmb9e7eoIcPNzDSmnzbrueBzgT8YszNGEI+1YCq8XdWN4kDztvP1ZNV21VCO6BvcbfUAnXXgcX5BPeLZNsgXPj3c8TbD59GQl3oT/tIchgMsA20RdAtIwvItlZKh+X6sp0OHkRPYSk/mEYKCKPqrKdJicRWex8ItCwpDR91KSOiKJrN/+DKOGG0sVI9gjzbtrHsN8HmVKxOoNV+wwipcLsWsEmuV+wvPCQ9HJidLX9Q17Bgfc+qJg19aB6iKLWPhjgnfpKGbK5VJuQTdDWPUJ2O4G3W/iwxJ0hAJ7tks4zIATmgGzhgTWYx5LVXbKcuL04xhIOjqwedHeCSBZSSaAAAAAAAA"
|
||||
//
|
||||
// val metadataFile = EncryptionUtils.deserializeJSON(
|
||||
// metadata,
|
||||
// object : TypeToken<EncryptedFolderMetadataFile>() {}
|
||||
// )
|
||||
// assertNotNull(metadataFile)
|
||||
//
|
||||
// val certJohnString = metadataFile.users[0].certificate
|
||||
// val certJohn = EncryptionUtils.convertCertFromString(certJohnString)
|
||||
//
|
||||
// val t1String = """-----BEGIN CERTIFICATE-----
|
||||
// MIIC6DCCAdCgAwIBAgIBADANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDDAJ0MTAe
|
||||
// Fw0yMzA3MjUwNzU3MTJaFw00MzA3MjAwNzU3MTJaMA0xCzAJBgNVBAMMAnQxMIIB
|
||||
// IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtafHmDBcBqIu4HmMxMDW3j0S
|
||||
// y+S0YaKwHnBRt85KSwcEov0B5FOLuLknoBGx4Dn3u93ilThXXxacPMHeXL7WPuAs
|
||||
// 21/G7vsqwvrRRnCduf+FUO/AZeDCNErzpsQ8LmTa4PUloLPUcImpSjrHwhMs9Ekv
|
||||
// EbLRjbeSmSp9XvM+1fV/3jkT5jkOSnCFx5TGwGN5uHqwUir4UWXasvg253NK2XmW
|
||||
// ipKCDCR9TmH1baP3pNdoiChdmErT1c6E4DbBXpTw8XgP5ZbYH+qg1UQ/hC8nRJ3D
|
||||
// yCcHL+dg/GYraBMhDn4w2Vvq77xNNoNWQ9cT5Ay6cJbQLBQoJQirygQFrobYRQID
|
||||
// AQABo1MwUTAdBgNVHQ4EFgQUE9zCeA9/QMAtVgLxD23X6ZcodhMwHwYDVR0jBBgw
|
||||
// FoAUE9zCeA9/QMAtVgLxD23X6ZcodhMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
|
||||
// 9w0BAQUFAAOCAQEAZdy/YjJlvnz3FQwxp6oVtMJccpdxveEPfLzgaverhtd/vP8O
|
||||
// AvDzOLgQJHmrDS91SG503eU4cYGyuNKwd77OyTnqMg+GUEmJhGfPpSVrEIdh65jv
|
||||
// q61T4oqBdehevVmBq54rGiwL0DGv1DlXQlwiJZP4qni2KnOEFcnvL3gVtRnQjXQ+
|
||||
// kHvlMshkK6w021EMV5NfjG2zg67wC65rLaej5f6Ssp2S7g2VtmE4aXq1bjAuEbqk
|
||||
// 4TiyZHLDdsJuqzyGyyOpMV7i9ucXDoaZt9cGS9hT2vRxTrSH63vKR8Xeig9+stLw
|
||||
// t9ONcUqCKP7hd8rajtxM4JIIRExwD8OkgARWGg==
|
||||
// -----END CERTIFICATE-----"""
|
||||
//
|
||||
// val t1cert = EncryptionUtils.convertCertFromString(t1String)
|
||||
// val t1PrivateKeyKey = EncryptionUtils.PEMtoPrivateKey(encryptionTestUtils.t1PrivateKey)
|
||||
//
|
||||
// // val signed = encryptionUtilsV2.getMessageSignature(
|
||||
// // t1cert,
|
||||
// // t1PrivateKeyKey,
|
||||
// // metadataFile
|
||||
// // )
|
||||
//
|
||||
// assertTrue(encryptionUtilsV2.verifySignedMessage(signature1, metadata1, listOf(certJohn, t1cert)))
|
||||
// }
|
||||
|
||||
@Throws(Throwable::class)
|
||||
@Test
|
||||
fun testSigning() {
|
||||
val metadata =
|
||||
"""{"metadata": {"authenticationTag": "zMozev5R09UopLrq7Je1lw==","ciphertext": "j0OBtUrEt4IveGiexjm
|
||||
|GK7eKEaWrY70ZkteA5KxHDaZT/t2wwGy9j2FPQGpqXnW6OO3iAYPNgwFikI1smnfNvqdxzVDvhavl/IXa9Kg2niWyqK3D9
|
||||
|zpz0YD6mDvl0XsOgTNVyGXNVREdWgzGEERCQoyHI1xowt/swe3KCXw+lf+XPF/t1PfHv0DiDVk70AeWGpPPPu6yggAIxB4
|
||||
|Az6PEZhaQWweTC0an48l2FHj2MtB2PiMHtW2v7RMuE8Al3PtE4gOA8CMFrB+Npy6rKcFCXOgTZm5bp7q+J1qkhBDbiBYtv
|
||||
|dsYujJ52Xa5SifTpEhGeWWLFnLLgPAQ8o6bXcWOyCoYfLfp4Jpft/Y7H8qzHbPewNSyD6maEv+xljjfU7hxibbszz5A4Jj
|
||||
|MdQy2BDGoTmJx7Mas+g6l6ZuHLVbdmgQOvD3waJBy6rOg0euux0Cn4bB4bIFEF2KvbhdGbY1Uiq9DYa7kEmSEnlcAYaHyr
|
||||
|oTkDg4ew7ER0vIBBMzKM3r+UdPVKKS66uyXtZc=","nonce": "W+lxQJeGq7XAJiGfcDohkg=="},"users": [{"cert
|
||||
|ificate": "-----BEGIN CERTIFICATE-----\nMIIDkDCCAnigAwIBAgIBADANBgkqhkiG9w0BAQUFADBhMQswCQYDVQ
|
||||
|QGEwJERTEb\nMBkGA1UECAwSQmFkZW4tV3VlcnR0ZW1iZXJnMRIwEAYDVQQHDAlTdHV0dGdhcnQx\nEjAQBgNVBAoMCU5l
|
||||
|eHRjbG91ZDENMAsGA1UEAwwEam9objAeFw0yMzA3MTQwNzM0\nNTZaFw00MzA3MDkwNzM0NTZaMGExCzAJBgNVBAYTAkRF
|
||||
|MRswGQYDVQQIDBJCYWRl\nbi1XdWVydHRlbWJlcmcxEjAQBgNVBAcMCVN0dXR0Z2FydDESMBAGA1UECgwJTmV4\ndGNsb3
|
||||
|VkMQ0wCwYDVQQDDARqb2huMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEA7j3Er5YahJT0LAnSRLhpqbRo+E
|
||||
|1AVnt98rvp3DmEfBHNzNB+DS9IBDkS\nSXM/YtfAci6Tcw8ujVBjrZX/WEmrf8ynQHxYmSaJSnP8uAT306/MceZpdpruEc
|
||||
|9/\nS10a7vp54Zbld4NYdmfS71oVFVKgM7c/Vthx+rgu48fuxzbWAvVYLFcx47hz0DJT\nnjz2Za/R68uXpxfz7J9uEXYi
|
||||
|qsAs/FobDsLZluT3RyywVRwKBed1EZxUeLIJiyxp\nUthhGfIb8b3Vf9jZoUVi3m5gmc4spJQHvYAkfZYHzd9ras8jBu1a
|
||||
|bQRxcu2CYnVo\n6Y0mTxhKhQS/n5gjv3ExiQF3wp/XYwIDAQABo1MwUTAdBgNVHQ4EFgQUmTeILVuB\ntv70fTGkXWGAue
|
||||
|Dp5kAwHwYDVR0jBBgwFoAUmTeILVuBtv70fTGkXWGAueDp5kAw\nDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAA
|
||||
|OCAQEAyVtq9XAvW7nxSW/8\nhp30z6xbzGiuviXhy/Jo91VEa8IRsWCCn3OmDFiVduTEowx76tf8clJP0gk7Pozi\n6dg/
|
||||
|7Fin+FqQGXfCk8bLAh9gXKAikQ2GK8yRN3slRFwYC2mm23HrLdKXZHUqJcpB\nMz2zsSrOGPj1YsYOl/U8FU6KA7Yj7U3q
|
||||
|7kDMYTAgzUPZAH+d1DISGWpZsMa0RYid\nvigCCLByiccmS/Co4Sb1esF58H+YtV5+nFBRwx881U2g2TgDKF1lPMK/y3d8
|
||||
|B8mh\nUtW+lFxRpvyNUDpsMjOErOrtNFEYbgoUJLtqwBMmyGR+nmmh6xna331QWcRAmw0P\nnDO4ew==\n-----END CER
|
||||
|TIFICATE-----\n","encryptedMetadataKey": "HVT49bYmwXbGs/dJ2avgU9unrKnPf03MYUI5ZysSR1Bz5pqz64gz
|
||||
|H2GBAuUJ+Q4VmHtEfcMaWW7VXgzfCQv5xLBrk+RSgcLOKnlIya8jaDlfttWxbe8jJK+/0+QVPOc6ycA/t5HNCPg09hzj+g
|
||||
|nb2L89UHxL5accZD0iEzb5cQbGrc/N6GthjgGrgFKtFf0HhDVplUr+DL9aTyKuKLBPjrjuZbv8M6ZfXO93mOMwSZH3c3rw
|
||||
|DUHb/KEaTR/Og4pWQmrqr1VxGLqeV/+GKWhzMYThrOZAUz+5gsbckU2M5V9i+ph0yBI5BjOZVhNuDwW8yP8WtyRJwQc+UB
|
||||
|Rei/RGBQ==","userId": "john"}],"version": "2"}
|
||||
""".trimMargin()
|
||||
|
||||
val base64Metadata = EncryptionUtils.encodeStringToBase64String(metadata)
|
||||
|
||||
val privateKey = EncryptionUtils.PEMtoPrivateKey(encryptionTestUtils.t1PrivateKey)
|
||||
val certificateT1 = EncryptionUtils.convertCertFromString(encryptionTestUtils.t1PublicKey)
|
||||
val certificateEnc2 = EncryptionUtils.convertCertFromString(enc2Cert)
|
||||
|
||||
val signed = encryptionUtilsV2.signMessage(
|
||||
certificateT1,
|
||||
privateKey,
|
||||
metadata
|
||||
)
|
||||
|
||||
val base64Ans = encryptionUtilsV2.extractSignedString(signed)
|
||||
|
||||
// verify
|
||||
val certs = listOf(
|
||||
certificateEnc2,
|
||||
certificateT1
|
||||
)
|
||||
assertTrue(encryptionUtilsV2.verifySignedMessage(signed, certs))
|
||||
assertTrue(encryptionUtilsV2.verifySignedMessage(base64Ans, base64Metadata, certs))
|
||||
}
|
||||
|
||||
@Throws(Throwable::class)
|
||||
@Test
|
||||
fun sign() {
|
||||
val sut = "randomstring123"
|
||||
val json = "randomstring123"
|
||||
val jsonBase64 = EncryptionUtils.encodeStringToBase64String(json)
|
||||
|
||||
val privateKey = EncryptionUtils.PEMtoPrivateKey(encryptionTestUtils.t1PrivateKey)
|
||||
val certificate = EncryptionUtils.convertCertFromString(encryptionTestUtils.t1PublicKey)
|
||||
|
||||
val signed = encryptionUtilsV2.signMessage(
|
||||
certificate,
|
||||
privateKey,
|
||||
sut
|
||||
)
|
||||
|
||||
val base64Ans = encryptionUtilsV2.extractSignedString(signed)
|
||||
|
||||
// verify
|
||||
val certs = listOf(
|
||||
EncryptionUtils.convertCertFromString(enc2Cert),
|
||||
certificate
|
||||
)
|
||||
assertTrue(encryptionUtilsV2.verifySignedMessage(signed, certs))
|
||||
assertTrue(encryptionUtilsV2.verifySignedMessage(base64Ans, jsonBase64, certs))
|
||||
}
|
||||
|
||||
/**
|
||||
* DecryptedFolderMetadata -> EncryptedFolderMetadata -> JSON -> encrypt -> decrypt -> JSON ->
|
||||
* EncryptedFolderMetadata -> DecryptedFolderMetadata
|
||||
*/
|
||||
@Test
|
||||
@Throws(Exception::class, Throwable::class)
|
||||
fun encryptionMetadataV2() {
|
||||
val decryptedFolderMetadata1: DecryptedFolderMetadataFile =
|
||||
EncryptionTestUtils().generateFolderMetadataV2(client.userId, EncryptionTestIT.publicKey)
|
||||
val root = OCFile("/")
|
||||
storageManager.saveFile(root)
|
||||
|
||||
val folder = OCFile("/enc")
|
||||
folder.parentId = storageManager.getFileByDecryptedRemotePath("/")?.fileId ?: throw IllegalStateException()
|
||||
|
||||
storageManager.saveFile(folder)
|
||||
|
||||
decryptedFolderMetadata1.filedrop.clear()
|
||||
|
||||
// encrypt
|
||||
val encryptedFolderMetadata1 = encryptionUtilsV2.encryptFolderMetadataFile(
|
||||
decryptedFolderMetadata1,
|
||||
client.userId,
|
||||
folder,
|
||||
storageManager,
|
||||
client,
|
||||
EncryptionTestIT.publicKey,
|
||||
user,
|
||||
targetContext,
|
||||
arbitraryDataProvider
|
||||
)
|
||||
|
||||
val signature = encryptionUtilsV2.getMessageSignature(enc1Cert, enc1PrivateKey, encryptedFolderMetadata1)
|
||||
|
||||
// serialize
|
||||
val encryptedJson = EncryptionUtils.serializeJSON(encryptedFolderMetadata1)
|
||||
|
||||
// de-serialize
|
||||
val encryptedFolderMetadata2 = EncryptionUtils.deserializeJSON(
|
||||
encryptedJson,
|
||||
object : TypeToken<EncryptedFolderMetadataFile?>() {}
|
||||
)
|
||||
|
||||
// decrypt
|
||||
val decryptedFolderMetadata2 = encryptionUtilsV2.decryptFolderMetadataFile(
|
||||
encryptedFolderMetadata2!!,
|
||||
getUserId(user),
|
||||
EncryptionTestIT.privateKey,
|
||||
folder,
|
||||
fileDataStorageManager,
|
||||
client,
|
||||
decryptedFolderMetadata1.metadata.counter,
|
||||
signature,
|
||||
user,
|
||||
targetContext,
|
||||
arbitraryDataProvider
|
||||
)
|
||||
|
||||
// compare
|
||||
assertTrue(
|
||||
EncryptionTestIT.compareJsonStrings(
|
||||
EncryptionUtils.serializeJSON(decryptedFolderMetadata1),
|
||||
EncryptionUtils.serializeJSON(decryptedFolderMetadata2)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Throws(Throwable::class)
|
||||
@Test
|
||||
fun decryptFiledropV2() {
|
||||
val sut = EncryptedFiledrop(
|
||||
"""QE5nJmA8QC3rBJxbpsZu6MvkomwHMKTYf/3dEz9Zq3ITHLK/wNAIqWTbDehBJ7SlTfXakkKR9o0sOkUDI7PD8qJyv5hW7LzifszYGe
|
||||
|xE0V1daFcCFApKrIEBABHVOq+ZHJd8IzNSz3hdA9bWd2eiaEGyQzgdTPELE6Ie84IwFANJHcaRB5B43aaDdbUXNJ4/oMboOReKTJ
|
||||
|/vT6ZGhve4DRPEsez0quyDZDNlin5hD6UaUzw=
|
||||
""".trimMargin(),
|
||||
"HC87OgVzbR2CXdWp7rKI5A==",
|
||||
"7PSq7INkM2WKfmEPpRpTPA==",
|
||||
listOf(
|
||||
EncryptedFiledropUser(
|
||||
"android3",
|
||||
"""cNzk8cNyoTJ49Cj/x2WPlsMAnUWlZsfnKJ3VIRiczASeUYUFhaJpD8HDWE0uhkXSD7i9nzpe6pR7zllE7UE/QniDd+BQiF
|
||||
|80E5fSO1KVfFkLZRT+2pX5oPnl4CVtMnxb4xG7J1nAUqMhfS8PtQIr0+S7NKDdrUc41aNOB/4kH0D9LSo/bSC38L7ewv
|
||||
|mISM6ZFi1bfI1505kZV0HqcW12nZwHwe3s6rYkoSPBOPX1oPkvMYTVLkYuU+7DNL4HW7D9dc9X4bsSGLdj4joRi9FURi
|
||||
|mMv6MOrWOnYlX2zmMKAF3nEjLlhngKG7pUi/qMIlft2AhRM4cJuuIQ29vvTGFFDQ==
|
||||
""".trimMargin()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val privateKey =
|
||||
"""MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDPNCnYcPgGQwCzL8sxLTiE0atn5tiW1nfPQc1aY/+aXvkpF4h2vT
|
||||
|S/hQg2SCNFUlw8aYKksk5IH5FFcPv9QFG/TQDQOnZhi7moVPnVwLkx+cDfWQQs1EOhI/ZPdSo7MdaRLttbZZs/GJfnr1ziYZTxLO
|
||||
|UUxT541cnSpqGTKmUXhGMoX+jQTmcn1NyBD537NdetOxSdMfvBIobjRQ70/9c1HFGQSrJa+DmPiis6iFkd1LH6WWRbreC6DsRSqK
|
||||
|ne3sD1ujx39k+VxtBe035c2L9PbTMMW3kBdZxlRkV1tUQhDAys0K+CyvNIFsOjqvQKTnXNfWO+kVnpOkpbTK4imuPbAgMBAAECgf
|
||||
|9T537U/6TuwJLSj4bfYev8qYaakfVIpyMkL33e4YBQnUzhlCPBVYgpHkDPwznk2XhjQQiVcRAycmUHBmy4aPkcOjuBmd87aTj03k
|
||||
|niDk+doFDNU8myuwWTw/1fHdElRnLyZxEKrb391HD4SVVQMuxnw8UoC4iNcPnYneY/GTiTtB3dVcRKdabX3Oak2TFiJyJBtTz4RN
|
||||
|sRYVXM3jyCbxj8uV+XNr+3OuQe5u7cV5gkXOXHqcNczOrxGzSXVGULuw8FiHIlhId7tot3dGdyVvWD9YIwwGA/9/3g8JixqpQHKZ
|
||||
|6YJAeqltydisGa3CIIEzBAh52GJC7yzMKSC2ZAtW0CgYEA6B/O+EgtZthiXOwivqZmKKGgWGLSOGjVsExSa1iiTTz3EFwcdD54mU
|
||||
|TKc6hw787NFlfN1m7B7EDQxIldRDI3One1q2dj87taco/qFqKsHuAuC3gmZIp2F4l2P8NpdHHFMzUzsfs+grY/wLHZiJdfOTdulA
|
||||
|s9go5mDloMC96n0/UCgYEA5IQo7c4ZxwhlssIn89XaOlKGoIct07wsBMu47HZYFqgG2/NUN8zRfSdSvot+6zinAb6Z3iGZ2FBL+C
|
||||
|MmoEMGwuXSQjWxeD//UU6V5AZqlgis5s9WakKWmkTkVV3bPSwW0DuNcqbMk7BxAXcQ6QGIiBtzeaPuL/3gzA9e9vm8xo8CgYEAqL
|
||||
|I9S6nA/UZzLg8bLS1nf03/Z1ziZMajzk2ZdJRk1/dfow8eSskAAnvBGo8nDNFhsUQ8vwOdgeKVFtCx7JcGFkLbz+cC+CaIFExNFw
|
||||
|hASOwp6oH2fQk3y+FGBA8ze8IXTCD1IftzMbHb4WIfsyo3tTB497S3jkOJHhMJQDMgC2UCgYEAzjUgRe98vWkrdFLWAKfSxFxiFg
|
||||
|vF49JjGnTHy8HDHbbEccizD6NoyvooJb/1aMd3lRBtAtDpZhSXaTQ3D9lMCaWfxZV0LyH5AGLcyaasmfT8KU+iGEM8abuPHCWUyC
|
||||
|+36nJC4tn3s7I9V2gdP1Xd4Yx7+KFgN7huGVYpiM61dasCgYAQs5mPHRBeU+BHtPRyaLHhYq+jjYeocwyOpfw5wkiH3jsyUWTK9+
|
||||
|GlAoV75SYvQVIQS0VH1C1/ajz9yV02frAaUXbGtZJbyeAcyy3DjCc7iF0swJ4slP3gGVJipVF4aQ0d9wMoJ7SBaaTR0ohXeUWmTT
|
||||
|X+VGf+cZQ2IefKVnz9mg==
|
||||
""".trimMargin()
|
||||
|
||||
val decryptedFile = EncryptionUtilsV2().decryptFiledrop(sut, privateKey, arbitraryDataProvider, user)
|
||||
assertEquals("test.txt", decryptedFile.filename)
|
||||
}
|
||||
}
|
|
@ -69,7 +69,8 @@ import com.owncloud.android.db.ProviderMeta
|
|||
AutoMigration(from = 72, to = 73),
|
||||
AutoMigration(from = 73, to = 74, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
|
||||
AutoMigration(from = 74, to = 75),
|
||||
AutoMigration(from = 75, to = 76)
|
||||
AutoMigration(from = 75, to = 76),
|
||||
AutoMigration(from = 76, to = 77)
|
||||
],
|
||||
exportSchema = true
|
||||
)
|
||||
|
|
|
@ -98,6 +98,8 @@ data class CapabilityEntity(
|
|||
val endToEndEncryption: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_KEYS_EXIST)
|
||||
val endToEndEncryptionKeysExist: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_API_VERSION)
|
||||
val endToEndEncryptionApiVersion: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_ACTIVITY)
|
||||
val activity: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT)
|
||||
|
|
|
@ -128,5 +128,7 @@ data class FileEntity(
|
|||
@ColumnInfo(name = ProviderTableMeta.FILE_TAGS)
|
||||
val tags: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_METADATA_GPS)
|
||||
val metadataGPS: String?
|
||||
val metadataGPS: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_E2E_COUNTER)
|
||||
val e2eCounter: Long?
|
||||
)
|
||||
|
|
|
@ -54,6 +54,7 @@ import com.nextcloud.client.notifications.AppNotificationManager;
|
|||
import com.nextcloud.client.notifications.AppNotificationManagerImpl;
|
||||
import com.nextcloud.client.preferences.AppPreferences;
|
||||
import com.nextcloud.client.utils.Throttler;
|
||||
import com.owncloud.android.providers.UsersAndGroupsSearchConfig;
|
||||
import com.owncloud.android.authentication.PassCodeManager;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||
|
@ -261,4 +262,11 @@ class AppModule {
|
|||
PassCodeManager passCodeManager(AppPreferences preferences, Clock clock) {
|
||||
return new PassCodeManager(preferences, clock);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
UsersAndGroupsSearchConfig userAndGroupSearchConfig() {
|
||||
return new UsersAndGroupsSearchConfig();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ interface ArbitraryDataProvider {
|
|||
fun incrementValue(accountName: String, key: String)
|
||||
fun storeOrUpdateKeyValue(accountName: String, key: String, newValue: Boolean)
|
||||
fun storeOrUpdateKeyValue(accountName: String, key: String, newValue: String)
|
||||
fun storeOrUpdateKeyValue(user: User, key: String, newValue: String)
|
||||
|
||||
fun getLongValue(accountName: String, key: String): Long
|
||||
fun getLongValue(user: User, key: String): Long
|
||||
|
@ -45,6 +46,7 @@ interface ArbitraryDataProvider {
|
|||
const val DIRECT_EDITING = "DIRECT_EDITING"
|
||||
const val DIRECT_EDITING_ETAG = "DIRECT_EDITING_ETAG"
|
||||
const val PREDEFINED_STATUS = "PREDEFINED_STATUS"
|
||||
const val PUBLIC_KEY = "PUBLIC_KEY_"
|
||||
const val E2E_ERRORS = "E2E_ERRORS"
|
||||
const val E2E_ERRORS_TIMESTAMP = "E2E_ERRORS_TIMESTAMP"
|
||||
}
|
||||
|
|
|
@ -91,6 +91,13 @@ public class ArbitraryDataProviderImpl implements ArbitraryDataProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeOrUpdateKeyValue(@NonNull User user,
|
||||
@NonNull String key,
|
||||
@NonNull String newValue) {
|
||||
storeOrUpdateKeyValue(user.getAccountName(), key, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLongValue(@NonNull String accountName, @NonNull String key) {
|
||||
String value = getValue(accountName, key);
|
||||
|
|
|
@ -29,18 +29,18 @@ import androidx.annotation.VisibleForTesting;
|
|||
/**
|
||||
* Decrypted class representation of metadata json of folder metadata.
|
||||
*/
|
||||
public class DecryptedFolderMetadata {
|
||||
public class DecryptedFolderMetadataOld {
|
||||
private Metadata metadata;
|
||||
private Map<String, DecryptedFile> files;
|
||||
|
||||
private Map<String, DecryptedFile> filedrop;
|
||||
|
||||
public DecryptedFolderMetadata() {
|
||||
public DecryptedFolderMetadataOld() {
|
||||
this.metadata = new Metadata();
|
||||
this.files = new HashMap<>();
|
||||
}
|
||||
|
||||
public DecryptedFolderMetadata(Metadata metadata, Map<String, DecryptedFile> files) {
|
||||
public DecryptedFolderMetadataOld(Metadata metadata, Map<String, DecryptedFile> files) {
|
||||
this.metadata = metadata;
|
||||
this.files = files;
|
||||
}
|
|
@ -58,6 +58,7 @@ import com.owncloud.android.lib.resources.shares.OCShare;
|
|||
import com.owncloud.android.lib.resources.shares.ShareType;
|
||||
import com.owncloud.android.lib.resources.shares.ShareeUser;
|
||||
import com.owncloud.android.lib.resources.status.CapabilityBooleanType;
|
||||
import com.owncloud.android.lib.resources.status.E2EVersion;
|
||||
import com.owncloud.android.lib.resources.status.OCCapability;
|
||||
import com.owncloud.android.operations.RemoteOperationFailedException;
|
||||
import com.owncloud.android.utils.FileStorageUtils;
|
||||
|
@ -556,6 +557,7 @@ public class FileDataStorageManager {
|
|||
cv.put(ProviderTableMeta.FILE_METADATA_SIZE, gson.toJson(file.getImageDimension()));
|
||||
cv.put(ProviderTableMeta.FILE_METADATA_GPS, gson.toJson(file.getGeoLocation()));
|
||||
cv.put(ProviderTableMeta.FILE_METADATA_LIVE_PHOTO, file.getLinkedFileIdForLivePhoto());
|
||||
cv.put(ProviderTableMeta.FILE_E2E_COUNTER, file.getE2eCounter());
|
||||
|
||||
return cv;
|
||||
}
|
||||
|
@ -988,6 +990,7 @@ public class FileDataStorageManager {
|
|||
ocFile.setLockToken(fileEntity.getLockToken());
|
||||
ocFile.setLivePhoto(fileEntity.getMetadataLivePhoto());
|
||||
ocFile.setHidden(nullToZero(fileEntity.getHidden()) == 1);
|
||||
ocFile.setE2eCounter(fileEntity.getE2eCounter());
|
||||
|
||||
String sharees = fileEntity.getSharees();
|
||||
// Surprisingly JSON deserialization causes significant overhead.
|
||||
|
@ -1974,6 +1977,8 @@ public class FileDataStorageManager {
|
|||
capability.getEndToEndEncryption().getValue());
|
||||
contentValues.put(ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_KEYS_EXIST,
|
||||
capability.getEndToEndEncryptionKeysExist().getValue());
|
||||
contentValues.put(ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_API_VERSION,
|
||||
capability.getEndToEndEncryptionApiVersion().getValue());
|
||||
contentValues.put(ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT,
|
||||
capability.getServerBackgroundDefault().getValue());
|
||||
contentValues.put(ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_PLAIN,
|
||||
|
@ -2127,6 +2132,16 @@ public class FileDataStorageManager {
|
|||
getBoolean(cursor,
|
||||
ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_KEYS_EXIST)
|
||||
);
|
||||
|
||||
String e2eVersionString = getString(cursor, ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_API_VERSION);
|
||||
E2EVersion e2EVersion;
|
||||
if (e2eVersionString == null) {
|
||||
e2EVersion = E2EVersion.UNKNOWN;
|
||||
} else {
|
||||
e2EVersion = E2EVersion.fromValue(e2eVersionString);
|
||||
}
|
||||
capability.setEndToEndEncryptionApiVersion(e2EVersion);
|
||||
|
||||
capability.setServerBackgroundDefault(
|
||||
getBoolean(cursor, ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT));
|
||||
capability.setServerBackgroundPlain(getBoolean(cursor,
|
||||
|
|
|
@ -121,6 +121,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
|||
private String lockToken;
|
||||
@Nullable
|
||||
private ImageDimension imageDimension;
|
||||
private long e2eCounter = -1;
|
||||
@Nullable
|
||||
private GeoLocation geolocation;
|
||||
private List<String> tags = new ArrayList<>();
|
||||
|
@ -1056,4 +1057,15 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
|||
this.tags = tags;
|
||||
}
|
||||
|
||||
public long getE2eCounter() {
|
||||
return e2eCounter;
|
||||
}
|
||||
|
||||
public void setE2eCounter(@Nullable Long e2eCounter) {
|
||||
if (e2eCounter == null) {
|
||||
this.e2eCounter = -1;
|
||||
} else {
|
||||
this.e2eCounter = e2eCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel.e2e.v1.decrypted;
|
||||
|
||||
public class Data {
|
||||
private String filename;
|
||||
private String mimetype;
|
||||
private String key;
|
||||
private double version;
|
||||
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return this.filename;
|
||||
}
|
||||
|
||||
public String getMimetype() {
|
||||
return this.mimetype;
|
||||
}
|
||||
|
||||
public double getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public void setFilename(String filename) {
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
public void setMimetype(String mimetype) {
|
||||
this.mimetype = mimetype;
|
||||
}
|
||||
|
||||
public void setVersion(double version) {
|
||||
this.version = version;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel.e2e.v1.decrypted;
|
||||
|
||||
public class DecryptedFile {
|
||||
private Data encrypted;
|
||||
private String initializationVector;
|
||||
private String authenticationTag;
|
||||
private int metadataKey;
|
||||
|
||||
public Data getEncrypted() {
|
||||
return this.encrypted;
|
||||
}
|
||||
|
||||
public String getInitializationVector() {
|
||||
return this.initializationVector;
|
||||
}
|
||||
|
||||
public String getAuthenticationTag() {
|
||||
return this.authenticationTag;
|
||||
}
|
||||
|
||||
public int getMetadataKey() {
|
||||
return this.metadataKey;
|
||||
}
|
||||
|
||||
public void setEncrypted(Data encrypted) {
|
||||
this.encrypted = encrypted;
|
||||
}
|
||||
|
||||
public void setInitializationVector(String initializationVector) {
|
||||
this.initializationVector = initializationVector;
|
||||
}
|
||||
|
||||
public void setAuthenticationTag(String authenticationTag) {
|
||||
this.authenticationTag = authenticationTag;
|
||||
}
|
||||
|
||||
public void setMetadataKey(int metadataKey) {
|
||||
this.metadataKey = metadataKey;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel.e2e.v1.decrypted;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* Decrypted class representation of metadata json of folder metadata.
|
||||
*/
|
||||
public class DecryptedFolderMetadataFileV1 {
|
||||
private DecryptedMetadata metadata;
|
||||
private Map<String, DecryptedFile> files;
|
||||
private Map<String, DecryptedFile> filedrop;
|
||||
|
||||
public DecryptedFolderMetadataFileV1() {
|
||||
this.metadata = new DecryptedMetadata();
|
||||
this.files = new HashMap<>();
|
||||
}
|
||||
|
||||
public DecryptedFolderMetadataFileV1(DecryptedMetadata metadata, Map<String, DecryptedFile> files) {
|
||||
this.metadata = metadata;
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
public DecryptedMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
public Map<String, DecryptedFile> getFiles() {
|
||||
return this.files;
|
||||
}
|
||||
|
||||
public void setMetadata(DecryptedMetadata metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public void setFiles(Map<String, DecryptedFile> files) {
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setFiledrop(Map<String, DecryptedFile> filedrop) {
|
||||
this.filedrop = filedrop;
|
||||
}
|
||||
|
||||
public Map<String, DecryptedFile> getFiledrop() {
|
||||
return filedrop;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel.e2e.v1.decrypted;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class DecryptedMetadata {
|
||||
transient
|
||||
private Map<Integer, String> metadataKeys; // outdated with v1.1
|
||||
private String metadataKey;
|
||||
private String checksum;
|
||||
private double version;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(version);
|
||||
}
|
||||
|
||||
public Map<Integer, String> getMetadataKeys() {
|
||||
return this.metadataKeys;
|
||||
}
|
||||
|
||||
public String getMetadataKey() {
|
||||
if (metadataKey == null) {
|
||||
// fallback to old keys array
|
||||
return metadataKeys.get(0);
|
||||
}
|
||||
return metadataKey;
|
||||
}
|
||||
|
||||
public double getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public void setMetadataKeys(Map<Integer, String> metadataKeys) {
|
||||
this.metadataKeys = metadataKeys;
|
||||
}
|
||||
|
||||
public void setMetadataKey(String metadataKey) {
|
||||
this.metadataKey = metadataKey;
|
||||
}
|
||||
|
||||
public void setVersion(double version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getChecksum() {
|
||||
return checksum;
|
||||
}
|
||||
|
||||
public void setChecksum(String checksum) {
|
||||
this.checksum = checksum;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel.e2e.v1.decrypted;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class Encrypted {
|
||||
private Map<Integer, String> metadataKeys;
|
||||
|
||||
public Map<Integer, String> getMetadataKeys() {
|
||||
return this.metadataKeys;
|
||||
}
|
||||
|
||||
public void setMetadataKeys(Map<Integer, String> metadataKeys) {
|
||||
this.metadataKeys = metadataKeys;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel.e2e.v1.decrypted;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class Sharing {
|
||||
private Map<String, String> recipient;
|
||||
private String signature;
|
||||
|
||||
public Map<String, String> getRecipient() {
|
||||
return this.recipient;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public void setRecipient(Map<String, String> recipient) {
|
||||
this.recipient = recipient;
|
||||
}
|
||||
|
||||
public void setSignature(String signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.owncloud.android.datamodel.e2e.v1.encrypted
|
||||
|
||||
class EncryptedFile(var encryptedBytes: ByteArray, var authenticationTag: String)
|
|
@ -1,14 +1,15 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2017 Tobias Kaminsky
|
||||
* Copyright (C) 2017 Nextcloud GmbH.
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* at your option) any later version.
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
|
@ -16,31 +17,34 @@
|
|||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel;
|
||||
package com.owncloud.android.datamodel.e2e.v1.encrypted;
|
||||
|
||||
import com.owncloud.android.datamodel.EncryptedFiledrop;
|
||||
import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedMetadata;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Encrypted class representation of metadata json of folder metadata
|
||||
*/
|
||||
public class EncryptedFolderMetadata {
|
||||
private DecryptedFolderMetadata.Metadata metadata;
|
||||
public class EncryptedFolderMetadataFileV1 {
|
||||
private DecryptedMetadata metadata;
|
||||
private Map<String, EncryptedFile> files;
|
||||
|
||||
private Map<String, EncryptedFiledrop> filedrop;
|
||||
|
||||
public EncryptedFolderMetadata(DecryptedFolderMetadata.Metadata metadata,
|
||||
Map<String, EncryptedFile> files,
|
||||
Map<String, EncryptedFiledrop> filesdrop) {
|
||||
public EncryptedFolderMetadataFileV1(DecryptedMetadata metadata,
|
||||
Map<String, EncryptedFile> files,
|
||||
Map<String, EncryptedFiledrop> filesdrop) {
|
||||
this.metadata = metadata;
|
||||
this.files = files;
|
||||
this.filedrop = filesdrop;
|
||||
}
|
||||
|
||||
public DecryptedFolderMetadata.Metadata getMetadata() {
|
||||
public DecryptedMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
|
@ -52,7 +56,7 @@ public class EncryptedFolderMetadata {
|
|||
return filedrop;
|
||||
}
|
||||
|
||||
public void setMetadata(DecryptedFolderMetadata.Metadata metadata) {
|
||||
public void setMetadata(DecryptedMetadata metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.owncloud.android.datamodel.e2e.v2.decrypted
|
||||
|
||||
data class DecryptedFile(
|
||||
var filename: String,
|
||||
val mimetype: String,
|
||||
val nonce: String,
|
||||
val authenticationTag: String,
|
||||
val key: String
|
||||
)
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.owncloud.android.datamodel.e2e.v2.decrypted
|
||||
|
||||
/**
|
||||
* Decrypted class representation of metadata json of folder metadata.
|
||||
*/
|
||||
data class DecryptedFolderMetadataFile(
|
||||
val metadata: DecryptedMetadata,
|
||||
var users: MutableList<DecryptedUser> = mutableListOf(),
|
||||
@Transient
|
||||
val filedrop: MutableMap<String, DecryptedFile> = HashMap(),
|
||||
val version: String = "2.0"
|
||||
)
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel.e2e.v2.decrypted
|
||||
|
||||
import com.owncloud.android.utils.EncryptionUtils
|
||||
|
||||
data class DecryptedMetadata(
|
||||
val keyChecksums: MutableList<String> = mutableListOf(),
|
||||
val deleted: Boolean = false,
|
||||
var counter: Long = 0,
|
||||
val folders: MutableMap<String, String> = mutableMapOf(),
|
||||
val files: MutableMap<String, DecryptedFile> = mutableMapOf(),
|
||||
@Transient
|
||||
var metadataKey: ByteArray = EncryptionUtils.generateKey()
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as DecryptedMetadata
|
||||
|
||||
if (keyChecksums != other.keyChecksums) return false
|
||||
if (deleted != other.deleted) return false
|
||||
if (counter != other.counter) return false
|
||||
if (folders != other.folders) return false
|
||||
if (files != other.files) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = keyChecksums.hashCode()
|
||||
result = 31 * result + deleted.hashCode()
|
||||
result = 31 * result + counter.hashCode()
|
||||
result = 31 * result + folders.hashCode()
|
||||
result = 31 * result + files.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel.e2e.v2.decrypted
|
||||
|
||||
data class DecryptedUser(
|
||||
val userId: String,
|
||||
val certificate: String
|
||||
)
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.owncloud.android.datamodel.e2e.v2.encrypted
|
||||
|
||||
data class EncryptedFiledrop(
|
||||
val ciphertext: String,
|
||||
val nonce: String,
|
||||
val authenticationTag: String,
|
||||
val users: List<EncryptedFiledropUser>
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel.e2e.v2.encrypted
|
||||
|
||||
data class EncryptedFiledropUser(
|
||||
val userId: String,
|
||||
val encryptedFiledropKey: String
|
||||
)
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.owncloud.android.datamodel.e2e.v2.encrypted
|
||||
|
||||
/**
|
||||
* Decrypted class representation of metadata json of folder metadata.
|
||||
*/
|
||||
data class EncryptedFolderMetadataFile(
|
||||
val metadata: EncryptedMetadata,
|
||||
val users: List<EncryptedUser>,
|
||||
val filedrop: MutableMap<String, EncryptedFiledrop>?,
|
||||
val version: String = "2.0"
|
||||
)
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel.e2e.v2.encrypted
|
||||
|
||||
data class EncryptedMetadata(
|
||||
val ciphertext: String,
|
||||
val nonce: String,
|
||||
val authenticationTag: String
|
||||
)
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.owncloud.android.datamodel.e2e.v2.encrypted
|
||||
|
||||
data class EncryptedUser(
|
||||
val userId: String,
|
||||
val certificate: String,
|
||||
val encryptedMetadataKey: String
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2023 Tobias Kaminsky
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.owncloud.android.datamodel.e2e.v2.encrypted
|
||||
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFile
|
||||
|
||||
class FiledropData {
|
||||
private val files: Map<String, DecryptedFile> = mutableMapOf()
|
||||
}
|
|
@ -35,7 +35,7 @@ import java.util.List;
|
|||
*/
|
||||
public class ProviderMeta {
|
||||
public static final String DB_NAME = "filelist";
|
||||
public static final int DB_VERSION = 76;
|
||||
public static final int DB_VERSION = 77;
|
||||
|
||||
private ProviderMeta() {
|
||||
// No instance
|
||||
|
@ -129,6 +129,7 @@ public class ProviderMeta {
|
|||
public static final String FILE_LOCK_TIMEOUT = "lock_timeout";
|
||||
public static final String FILE_LOCK_TOKEN = "lock_token";
|
||||
public static final String FILE_TAGS = "tags";
|
||||
public static final String FILE_E2E_COUNTER = "e2e_counter";
|
||||
|
||||
public static final List<String> FILE_ALL_COLUMNS = Collections.unmodifiableList(Arrays.asList(
|
||||
_ID,
|
||||
|
@ -178,6 +179,7 @@ public class ProviderMeta {
|
|||
FILE_LOCK_TOKEN,
|
||||
FILE_METADATA_SIZE,
|
||||
FILE_METADATA_LIVE_PHOTO,
|
||||
FILE_E2E_COUNTER,
|
||||
FILE_TAGS,
|
||||
FILE_METADATA_GPS));
|
||||
public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc";
|
||||
|
@ -248,6 +250,7 @@ public class ProviderMeta {
|
|||
public static final String CAPABILITIES_SERVER_BACKGROUND_PLAIN = "background_plain";
|
||||
public static final String CAPABILITIES_END_TO_END_ENCRYPTION = "end_to_end_encryption";
|
||||
public static final String CAPABILITIES_END_TO_END_ENCRYPTION_KEYS_EXIST = "end_to_end_encryption_keys_exist";
|
||||
public static final String CAPABILITIES_END_TO_END_ENCRYPTION_API_VERSION = "end_to_end_encryption_api_version";
|
||||
public static final String CAPABILITIES_ACTIVITY = "activity";
|
||||
public static final String CAPABILITIES_RICHDOCUMENT = "richdocument";
|
||||
public static final String CAPABILITIES_RICHDOCUMENT_MIMETYPE_LIST = "richdocument_mimetype_list";
|
||||
|
|
|
@ -186,7 +186,7 @@ public class FileMenuFilter {
|
|||
|
||||
|
||||
private void filterShareFile(List<Integer> toHide, OCCapability capability) {
|
||||
if (!isSingleSelection() || containsEncryptedFile() ||
|
||||
if (!isSingleSelection() || containsEncryptedFile() || hasEncryptedParent() ||
|
||||
(!isShareViaLinkAllowed() && !isShareWithUsersAllowed()) ||
|
||||
!isShareApiEnabled(capability) || !files.iterator().next().canReshare()) {
|
||||
toHide.add(R.id.action_send_share_file);
|
||||
|
@ -220,7 +220,11 @@ public class FileMenuFilter {
|
|||
}
|
||||
|
||||
private void filterLock(List<Integer> toHide, boolean fileLockingEnabled) {
|
||||
if (files.isEmpty() || !isSingleSelection() || !fileLockingEnabled) {
|
||||
if (files.isEmpty() ||
|
||||
!isSingleSelection() ||
|
||||
!fileLockingEnabled ||
|
||||
containsEncryptedFile() ||
|
||||
containsEncryptedFolder()) {
|
||||
toHide.add(R.id.action_lock_file);
|
||||
} else {
|
||||
OCFile file = files.iterator().next();
|
||||
|
@ -340,7 +344,7 @@ public class FileMenuFilter {
|
|||
|
||||
private void filterRemove(List<Integer> toHide, boolean synchronizing) {
|
||||
if (files.isEmpty() || synchronizing || containsLockedFile()
|
||||
|| containsEncryptedFolder() || containsEncryptedFile()) {
|
||||
|| containsEncryptedFolder() || isFolderAndContainsEncryptedFile()) {
|
||||
toHide.add(R.id.action_remove_file);
|
||||
}
|
||||
}
|
||||
|
@ -485,6 +489,24 @@ public class FileMenuFilter {
|
|||
return isSingleSelection() && (MimeTypeUtil.isVideo(file) || MimeTypeUtil.isAudio(file));
|
||||
}
|
||||
|
||||
private boolean isFolderAndContainsEncryptedFile() {
|
||||
for (OCFile file : files) {
|
||||
if (!file.isFolder()) {
|
||||
continue;
|
||||
}
|
||||
if (file.isFolder()) {
|
||||
List<OCFile> children = storageManager.getFolderContent(file, false);
|
||||
for (OCFile child : children) {
|
||||
if (child.isEncrypted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private boolean containsEncryptedFile() {
|
||||
for (OCFile file : files) {
|
||||
if (!file.isFolder() && file.isEncrypted()) {
|
||||
|
|
|
@ -27,10 +27,13 @@ import android.util.Pair;
|
|||
import com.nextcloud.client.account.User;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
||||
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;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
|
||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperation;
|
||||
|
@ -40,8 +43,10 @@ import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
|
|||
import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.files.model.RemoteFile;
|
||||
import com.owncloud.android.lib.resources.status.E2EVersion;
|
||||
import com.owncloud.android.operations.common.SyncOperation;
|
||||
import com.owncloud.android.utils.EncryptionUtils;
|
||||
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||
import com.owncloud.android.utils.FileStorageUtils;
|
||||
import com.owncloud.android.utils.MimeType;
|
||||
|
||||
|
@ -54,8 +59,8 @@ import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
|
|||
import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
|
||||
|
||||
/**
|
||||
* Access to remote operation performing the creation of a new folder in the ownCloud server.
|
||||
* Save the new folder in Database.
|
||||
* Access to remote operation performing the creation of a new folder in the ownCloud server. Save the new folder in
|
||||
* Database.
|
||||
*/
|
||||
public class CreateFolderOperation extends SyncOperation implements OnRemoteOperationListener {
|
||||
|
||||
|
@ -100,20 +105,28 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
|||
boolean encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager());
|
||||
|
||||
if (encryptedAncestor) {
|
||||
return encryptedCreate(parent, client);
|
||||
E2EVersion e2EVersion = getStorageManager().getCapability(user).getEndToEndEncryptionApiVersion();
|
||||
if (e2EVersion == E2EVersion.V1_0 ||
|
||||
e2EVersion == E2EVersion.V1_1 ||
|
||||
e2EVersion == E2EVersion.V1_2) {
|
||||
return encryptedCreateV1(parent, client);
|
||||
} else if (e2EVersion == E2EVersion.V2_0) {
|
||||
return encryptedCreateV2(parent, client);
|
||||
}
|
||||
return new RemoteOperationResult(new IllegalStateException("E2E not supported"));
|
||||
} else {
|
||||
return normalCreate(client);
|
||||
}
|
||||
}
|
||||
|
||||
private RemoteOperationResult encryptedCreate(OCFile parent, OwnCloudClient client) {
|
||||
private RemoteOperationResult encryptedCreateV1(OCFile parent, OwnCloudClient client) {
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
|
||||
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
|
||||
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
|
||||
|
||||
String token = null;
|
||||
Boolean metadataExists;
|
||||
DecryptedFolderMetadata metadata;
|
||||
DecryptedFolderMetadataFileV1 metadata;
|
||||
String encryptedRemotePath = null;
|
||||
|
||||
String filename = new File(remotePath).getName();
|
||||
|
@ -123,12 +136,13 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
|||
token = EncryptionUtils.lockFolder(parent, client);
|
||||
|
||||
// get metadata
|
||||
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent,
|
||||
client,
|
||||
privateKey,
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user);
|
||||
Pair<Boolean, DecryptedFolderMetadataFileV1> metadataPair = EncryptionUtils.retrieveMetadataV1(parent,
|
||||
client,
|
||||
privateKey,
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user
|
||||
);
|
||||
|
||||
metadataExists = metadataPair.first;
|
||||
metadata = metadataPair.second;
|
||||
|
@ -142,20 +156,21 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
|||
String encryptedFileName = createRandomFileName(metadata);
|
||||
encryptedRemotePath = parent.getRemotePath() + encryptedFileName;
|
||||
|
||||
RemoteOperationResult result = new CreateFolderRemoteOperation(encryptedRemotePath,
|
||||
true,
|
||||
token)
|
||||
RemoteOperationResult<String> result = new CreateFolderRemoteOperation(encryptedRemotePath,
|
||||
true,
|
||||
token)
|
||||
.execute(client);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
// update metadata
|
||||
metadata.getFiles().put(encryptedFileName, createDecryptedFile(filename));
|
||||
|
||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
parent.getLocalId());
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
||||
publicKey,
|
||||
parent.getLocalId(),
|
||||
user,
|
||||
arbitraryDataProvider
|
||||
);
|
||||
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
||||
|
||||
// upload metadata
|
||||
|
@ -164,17 +179,19 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
|||
token,
|
||||
client,
|
||||
metadataExists,
|
||||
E2EVersion.V1_2,
|
||||
"",
|
||||
arbitraryDataProvider,
|
||||
user);
|
||||
|
||||
// unlock folder
|
||||
if (token != null) {
|
||||
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token);
|
||||
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolderV1(parent, client, token);
|
||||
|
||||
if (unlockFolderResult.isSuccess()) {
|
||||
token = null;
|
||||
} else {
|
||||
// TODO do better
|
||||
// TODO E2E: do better
|
||||
throw new RuntimeException("Could not unlock folder!");
|
||||
}
|
||||
}
|
||||
|
@ -202,6 +219,146 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
|||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
if (!EncryptionUtils.unlockFolderV1(parent, client, token).isSuccess()) {
|
||||
throw new RuntimeException("Could not clean up after failing folder creation!", e);
|
||||
}
|
||||
|
||||
// remove folder
|
||||
if (encryptedRemotePath != null) {
|
||||
RemoteOperationResult removeResult = new RemoveRemoteEncryptedFileOperation(encryptedRemotePath,
|
||||
user,
|
||||
context,
|
||||
filename,
|
||||
parent,
|
||||
true
|
||||
).execute(client);
|
||||
|
||||
if (!removeResult.isSuccess()) {
|
||||
throw new RuntimeException("Could not clean up after failing folder creation!");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO E2E: do better
|
||||
return new RemoteOperationResult(e);
|
||||
} finally {
|
||||
// unlock folder
|
||||
if (token != null) {
|
||||
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolderV1(parent, client, token);
|
||||
|
||||
if (!unlockFolderResult.isSuccess()) {
|
||||
// TODO E2E: do better
|
||||
throw new RuntimeException("Could not unlock folder!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RemoteOperationResult encryptedCreateV2(OCFile parent, OwnCloudClient client) {
|
||||
String token = null;
|
||||
Boolean metadataExists;
|
||||
DecryptedFolderMetadataFile metadata;
|
||||
String encryptedRemotePath = null;
|
||||
|
||||
String filename = new File(remotePath).getName();
|
||||
|
||||
try {
|
||||
// lock folder
|
||||
token = EncryptionUtils.lockFolder(parent, client);
|
||||
|
||||
// get metadata
|
||||
EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
|
||||
kotlin.Pair<Boolean, DecryptedFolderMetadataFile> metadataPair = encryptionUtilsV2.retrieveMetadata(parent,
|
||||
client,
|
||||
user,
|
||||
context);
|
||||
|
||||
metadataExists = metadataPair.getFirst();
|
||||
metadata = metadataPair.getSecond();
|
||||
|
||||
// check if filename already exists
|
||||
if (isFileExisting(metadata, filename)) {
|
||||
return new RemoteOperationResult(RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
// generate new random file name, check if it exists in metadata
|
||||
String encryptedFileName = createRandomFileName(metadata);
|
||||
encryptedRemotePath = parent.getRemotePath() + encryptedFileName;
|
||||
|
||||
RemoteOperationResult<String> result = new CreateFolderRemoteOperation(encryptedRemotePath,
|
||||
true,
|
||||
token)
|
||||
.execute(client);
|
||||
|
||||
String remoteId = result.getResultData();
|
||||
|
||||
if (result.isSuccess()) {
|
||||
DecryptedFolderMetadataFile subFolderMetadata = encryptionUtilsV2.createDecryptedFolderMetadataFile();
|
||||
|
||||
// upload metadata
|
||||
encryptionUtilsV2.serializeAndUploadMetadata(remoteId,
|
||||
subFolderMetadata,
|
||||
token,
|
||||
client,
|
||||
false,
|
||||
context,
|
||||
user,
|
||||
parent,
|
||||
getStorageManager());
|
||||
}
|
||||
|
||||
if (result.isSuccess()) {
|
||||
// update metadata
|
||||
DecryptedFolderMetadataFile updatedMetadataFile = encryptionUtilsV2.addFolderToMetadata(encryptedFileName,
|
||||
filename,
|
||||
metadata,
|
||||
parent,
|
||||
getStorageManager());
|
||||
|
||||
// upload metadata
|
||||
encryptionUtilsV2.serializeAndUploadMetadata(parent,
|
||||
updatedMetadataFile,
|
||||
token,
|
||||
client,
|
||||
metadataExists,
|
||||
context,
|
||||
user,
|
||||
getStorageManager());
|
||||
|
||||
// unlock folder
|
||||
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token);
|
||||
|
||||
if (unlockFolderResult.isSuccess()) {
|
||||
token = null;
|
||||
} else {
|
||||
// TODO E2E: do better
|
||||
throw new RuntimeException("Could not unlock folder!");
|
||||
}
|
||||
|
||||
RemoteOperationResult remoteFolderOperationResult = new ReadFolderRemoteOperation(encryptedRemotePath)
|
||||
.execute(client);
|
||||
|
||||
createdRemoteFolder = (RemoteFile) remoteFolderOperationResult.getData().get(0);
|
||||
OCFile newDir = createRemoteFolderOcFile(parent, filename, createdRemoteFolder);
|
||||
getStorageManager().saveFile(newDir);
|
||||
|
||||
RemoteOperationResult encryptionOperationResult = new ToggleEncryptionRemoteOperation(
|
||||
newDir.getLocalId(),
|
||||
newDir.getRemotePath(),
|
||||
true)
|
||||
.execute(client);
|
||||
|
||||
if (!encryptionOperationResult.isSuccess()) {
|
||||
throw new RuntimeException("Error creating encrypted subfolder!");
|
||||
}
|
||||
} else {
|
||||
// revert to sane state in case of any error
|
||||
Log_OC.e(TAG, remotePath + " hasn't been created");
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
// TODO remove folder
|
||||
|
||||
if (!EncryptionUtils.unlockFolder(parent, client, token).isSuccess()) {
|
||||
throw new RuntimeException("Could not clean up after failing folder creation!", e);
|
||||
}
|
||||
|
@ -209,17 +366,18 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
|||
// remove folder
|
||||
if (encryptedRemotePath != null) {
|
||||
RemoteOperationResult removeResult = new RemoveRemoteEncryptedFileOperation(encryptedRemotePath,
|
||||
parent.getLocalId(),
|
||||
user,
|
||||
context,
|
||||
filename).execute(client);
|
||||
filename,
|
||||
parent,
|
||||
true).execute(client);
|
||||
|
||||
if (!removeResult.isSuccess()) {
|
||||
throw new RuntimeException("Could not clean up after failing folder creation!");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO do better
|
||||
// TODO E2E: do better
|
||||
return new RemoteOperationResult(e);
|
||||
} finally {
|
||||
// unlock folder
|
||||
|
@ -227,21 +385,30 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
|||
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token);
|
||||
|
||||
if (!unlockFolderResult.isSuccess()) {
|
||||
// TODO do better
|
||||
// TODO E2E: do better
|
||||
throw new RuntimeException("Could not unlock folder!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFileExisting(DecryptedFolderMetadata metadata, String filename) {
|
||||
for (String key : metadata.getFiles().keySet()) {
|
||||
DecryptedFolderMetadata.DecryptedFile file = metadata.getFiles().get(key);
|
||||
|
||||
if (file != null && filename.equalsIgnoreCase(file.getEncrypted().getFilename())) {
|
||||
private boolean isFileExisting(DecryptedFolderMetadataFileV1 metadata, String filename) {
|
||||
for (com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile file : metadata.getFiles().values()) {
|
||||
if (filename.equalsIgnoreCase(file.getEncrypted().getFilename())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isFileExisting(DecryptedFolderMetadataFile metadata, String filename) {
|
||||
for (DecryptedFile file : metadata.getMetadata().getFiles().values()) {
|
||||
if (filename.equalsIgnoreCase(file.getFilename())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -261,15 +428,16 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
|||
}
|
||||
|
||||
@NonNull
|
||||
private DecryptedFolderMetadata.DecryptedFile createDecryptedFile(String filename) {
|
||||
private com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile createDecryptedFile(String filename) {
|
||||
// Key, always generate new one
|
||||
byte[] key = EncryptionUtils.generateKey();
|
||||
|
||||
// IV, always generate new one
|
||||
byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
|
||||
|
||||
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
|
||||
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
|
||||
com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile decryptedFile =
|
||||
new com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile();
|
||||
Data data = new Data();
|
||||
data.setFilename(filename);
|
||||
data.setMimetype(MimeType.WEBDAV_FOLDER);
|
||||
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
|
||||
|
@ -281,7 +449,32 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
|||
}
|
||||
|
||||
@NonNull
|
||||
private String createRandomFileName(DecryptedFolderMetadata metadata) {
|
||||
private DecryptedFile createDecryptedFolder(String filename) {
|
||||
// Key, always generate new one
|
||||
byte[] key = EncryptionUtils.generateKey();
|
||||
|
||||
// IV, always generate new one
|
||||
byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
|
||||
|
||||
return new DecryptedFile(filename,
|
||||
MimeType.WEBDAV_FOLDER,
|
||||
EncryptionUtils.encodeBytesToBase64String(iv),
|
||||
"",
|
||||
EncryptionUtils.encodeBytesToBase64String(key));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String createRandomFileName(DecryptedFolderMetadataFile metadata) {
|
||||
String encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
|
||||
while (metadata.getMetadata().getFiles().get(encryptedFileName) != null) {
|
||||
encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
}
|
||||
return encryptedFileName;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String createRandomFileName(DecryptedFolderMetadataFileV1 metadata) {
|
||||
String encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
|
||||
while (metadata.getFiles().get(encryptedFileName) != null) {
|
||||
|
|
|
@ -23,17 +23,30 @@
|
|||
|
||||
package com.owncloud.android.operations;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.nextcloud.client.account.User;
|
||||
import com.nextcloud.client.network.ClientFactory;
|
||||
import com.nextcloud.client.network.ClientFactoryImpl;
|
||||
import com.nextcloud.common.NextcloudClient;
|
||||
import com.owncloud.android.R;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedUser;
|
||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||
import com.owncloud.android.lib.resources.files.FileUtils;
|
||||
import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.shares.OCShare;
|
||||
import com.owncloud.android.lib.resources.shares.ShareType;
|
||||
import com.owncloud.android.lib.resources.users.GetPublicKeyRemoteOperation;
|
||||
import com.owncloud.android.operations.common.SyncOperation;
|
||||
import com.owncloud.android.utils.EncryptionUtils;
|
||||
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
@ -44,15 +57,19 @@ import java.util.Set;
|
|||
*/
|
||||
public class CreateShareWithShareeOperation extends SyncOperation {
|
||||
|
||||
private String path;
|
||||
private String shareeName;
|
||||
private ShareType shareType;
|
||||
private int permissions;
|
||||
private String noteMessage;
|
||||
private String sharePassword;
|
||||
private boolean hideFileDownload;
|
||||
private long expirationDateInMillis;
|
||||
private final String path;
|
||||
private final String shareeName;
|
||||
private final ShareType shareType;
|
||||
private final int permissions;
|
||||
private final String noteMessage;
|
||||
private final String sharePassword;
|
||||
private final boolean hideFileDownload;
|
||||
private final long expirationDateInMillis;
|
||||
private String label;
|
||||
private final Context context;
|
||||
private final User user;
|
||||
|
||||
private ArbitraryDataProvider arbitraryDataProvider;
|
||||
|
||||
private static final Set<ShareType> supportedShareTypes = new HashSet<>(Arrays.asList(ShareType.USER,
|
||||
ShareType.GROUP,
|
||||
|
@ -68,35 +85,9 @@ public class CreateShareWithShareeOperation extends SyncOperation {
|
|||
* @param shareeName User or group name of the target sharee.
|
||||
* @param shareType Type of share determines type of sharee; {@link ShareType#USER} and {@link ShareType#GROUP}
|
||||
* are the only valid values for the moment.
|
||||
* @param permissions Share permissions key as detailed in
|
||||
* https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#create-a-new-share
|
||||
* .
|
||||
*/
|
||||
public CreateShareWithShareeOperation(String path,
|
||||
String shareeName,
|
||||
ShareType shareType,
|
||||
int permissions,
|
||||
FileDataStorageManager storageManager) {
|
||||
super(storageManager);
|
||||
|
||||
if (!supportedShareTypes.contains(shareType)) {
|
||||
throw new IllegalArgumentException("Illegal share type " + shareType);
|
||||
}
|
||||
this.path = path;
|
||||
this.shareeName = shareeName;
|
||||
this.shareType = shareType;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param path Full path of the file/folder being shared.
|
||||
* @param shareeName User or group name of the target sharee.
|
||||
* @param shareType Type of share determines type of sharee; {@link ShareType#USER} and {@link ShareType#GROUP}
|
||||
* are the only valid values for the moment.
|
||||
* @param permissions Share permissions key as detailed in https://doc.owncloud.org/server/8.2/developer_manual/core/ocs-share-api.html
|
||||
* .
|
||||
* @param permissions Share permissions key as detailed in <a
|
||||
* href="https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html">OCS
|
||||
* Share API</a>.
|
||||
*/
|
||||
public CreateShareWithShareeOperation(String path,
|
||||
String shareeName,
|
||||
|
@ -106,7 +97,10 @@ public class CreateShareWithShareeOperation extends SyncOperation {
|
|||
String sharePassword,
|
||||
long expirationDateInMillis,
|
||||
boolean hideFileDownload,
|
||||
FileDataStorageManager storageManager) {
|
||||
FileDataStorageManager storageManager,
|
||||
Context context,
|
||||
User user,
|
||||
ArbitraryDataProvider arbitraryDataProvider) {
|
||||
super(storageManager);
|
||||
|
||||
if (!supportedShareTypes.contains(shareType)) {
|
||||
|
@ -120,10 +114,52 @@ public class CreateShareWithShareeOperation extends SyncOperation {
|
|||
this.hideFileDownload = hideFileDownload;
|
||||
this.noteMessage = noteMessage;
|
||||
this.sharePassword = sharePassword;
|
||||
this.context = context;
|
||||
this.user = user;
|
||||
this.arbitraryDataProvider = arbitraryDataProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RemoteOperationResult run(OwnCloudClient client) {
|
||||
OCFile folder = getStorageManager().getFileByDecryptedRemotePath(path);
|
||||
|
||||
if (folder == null) {
|
||||
throw new IllegalArgumentException("Trying to share on a null folder: " + path);
|
||||
}
|
||||
|
||||
boolean isEncrypted = folder.isEncrypted();
|
||||
String token = null;
|
||||
long newCounter = folder.getE2eCounter() + 1;
|
||||
|
||||
// E2E: lock folder
|
||||
if (isEncrypted) {
|
||||
try {
|
||||
String publicKey = EncryptionUtils.getPublicKey(user, shareeName, arbitraryDataProvider);
|
||||
|
||||
if (publicKey.equals("")) {
|
||||
NextcloudClient nextcloudClient = new ClientFactoryImpl(context).createNextcloudClient(user);
|
||||
RemoteOperationResult<String> result = new GetPublicKeyRemoteOperation(shareeName).execute(nextcloudClient);
|
||||
if (result.isSuccess()) {
|
||||
// store it
|
||||
EncryptionUtils.savePublicKey(
|
||||
user,
|
||||
result.getResultData(),
|
||||
shareeName,
|
||||
arbitraryDataProvider
|
||||
);
|
||||
} else {
|
||||
RemoteOperationResult e = new RemoteOperationResult(new IllegalStateException());
|
||||
e.setMessage(context.getString(R.string.secure_share_not_set_up));
|
||||
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
token = EncryptionUtils.lockFolder(folder, client, newCounter);
|
||||
} catch (UploadException | ClientFactory.CreationException e) {
|
||||
return new RemoteOperationResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
CreateShareRemoteOperation operation = new CreateShareRemoteOperation(
|
||||
path,
|
||||
|
@ -135,30 +171,88 @@ public class CreateShareWithShareeOperation extends SyncOperation {
|
|||
noteMessage
|
||||
);
|
||||
operation.setGetShareDetails(true);
|
||||
RemoteOperationResult result = operation.execute(client);
|
||||
RemoteOperationResult shareResult = operation.execute(client);
|
||||
|
||||
if (!shareResult.isSuccess() || shareResult.getData().size() == 0) {
|
||||
// something went wrong
|
||||
return shareResult;
|
||||
}
|
||||
|
||||
if (result.isSuccess() && result.getData().size() > 0) {
|
||||
OCShare share = (OCShare) result.getData().get(0);
|
||||
// E2E: update metadata
|
||||
if (isEncrypted) {
|
||||
Object object = EncryptionUtils.downloadFolderMetadata(folder,
|
||||
client,
|
||||
context,
|
||||
user
|
||||
);
|
||||
|
||||
//once creating share link update other information
|
||||
UpdateShareInfoOperation updateShareInfoOperation = new UpdateShareInfoOperation(share, getStorageManager());
|
||||
updateShareInfoOperation.setExpirationDateInMillis(expirationDateInMillis);
|
||||
updateShareInfoOperation.setHideFileDownload(hideFileDownload);
|
||||
updateShareInfoOperation.setLabel(label);
|
||||
if (object instanceof DecryptedFolderMetadataFileV1) {
|
||||
throw new RuntimeException("Trying to share on e2e v1!");
|
||||
}
|
||||
|
||||
//update permissions for external share (will otherwise default to read-only)
|
||||
updateShareInfoOperation.setPermissions(permissions);
|
||||
DecryptedFolderMetadataFile metadata = (DecryptedFolderMetadataFile) object;
|
||||
|
||||
//execute and save the result in database
|
||||
RemoteOperationResult updateShareInfoResult = updateShareInfoOperation.execute(client);
|
||||
if (updateShareInfoResult.isSuccess() && updateShareInfoResult.getData().size() > 0) {
|
||||
OCShare shareUpdated = (OCShare) updateShareInfoResult.getData().get(0);
|
||||
updateData(shareUpdated);
|
||||
boolean metadataExists;
|
||||
if (metadata == null) {
|
||||
String cert = EncryptionUtils.retrievePublicKeyForUser(user, context);
|
||||
metadata = new EncryptionUtilsV2().createDecryptedFolderMetadataFile();
|
||||
metadata.getUsers().add(new DecryptedUser(client.getUserId(), cert));
|
||||
|
||||
metadataExists = false;
|
||||
} else {
|
||||
metadataExists = true;
|
||||
}
|
||||
|
||||
EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
|
||||
|
||||
// add sharee to metadata
|
||||
String publicKey = EncryptionUtils.getPublicKey(user, shareeName, arbitraryDataProvider);
|
||||
DecryptedFolderMetadataFile newMetadata = encryptionUtilsV2.addShareeToMetadata(metadata,
|
||||
shareeName,
|
||||
publicKey);
|
||||
|
||||
// upload metadata
|
||||
metadata.getMetadata().setCounter(newCounter);
|
||||
try {
|
||||
encryptionUtilsV2.serializeAndUploadMetadata(folder,
|
||||
newMetadata,
|
||||
token,
|
||||
client,
|
||||
metadataExists,
|
||||
context,
|
||||
user,
|
||||
getStorageManager());
|
||||
} catch (UploadException e) {
|
||||
return new RemoteOperationResult<>(new RuntimeException("Uploading metadata failed"));
|
||||
}
|
||||
|
||||
// E2E: unlock folder
|
||||
RemoteOperationResult<Void> unlockResult = EncryptionUtils.unlockFolder(folder, client, token);
|
||||
if (!unlockResult.isSuccess()) {
|
||||
return new RemoteOperationResult<>(new RuntimeException("Unlock failed"));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
OCShare share = (OCShare) shareResult.getData().get(0);
|
||||
|
||||
// once creating share link update other information
|
||||
UpdateShareInfoOperation updateShareInfoOperation = new UpdateShareInfoOperation(share, getStorageManager());
|
||||
updateShareInfoOperation.setExpirationDateInMillis(expirationDateInMillis);
|
||||
updateShareInfoOperation.setHideFileDownload(hideFileDownload);
|
||||
updateShareInfoOperation.setNote(noteMessage);
|
||||
updateShareInfoOperation.setLabel(label);
|
||||
|
||||
//update permissions for external share (will otherwise default to read-only)
|
||||
updateShareInfoOperation.setPermissions(permissions);
|
||||
|
||||
// execute and save the result in database
|
||||
RemoteOperationResult updateShareInfoResult = updateShareInfoOperation.execute(client);
|
||||
if (updateShareInfoResult.isSuccess() && updateShareInfoResult.getData().size() > 0) {
|
||||
OCShare shareUpdated = (OCShare) updateShareInfoResult.getData().get(0);
|
||||
updateData(shareUpdated);
|
||||
}
|
||||
|
||||
return shareResult;
|
||||
}
|
||||
|
||||
private void updateData(OCShare share) {
|
||||
|
|
|
@ -28,9 +28,11 @@ import android.webkit.MimeTypeMap;
|
|||
|
||||
import com.nextcloud.client.account.User;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFile;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
|
||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
|
||||
import com.owncloud.android.lib.common.operations.OperationCancelledException;
|
||||
|
@ -50,6 +52,8 @@ import java.util.Iterator;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.owncloud.android.utils.EncryptionUtils.decodeStringToBase64Bytes;
|
||||
|
||||
/**
|
||||
* Remote DownloadOperation performing the download of a file to an ownCloud server
|
||||
*/
|
||||
|
@ -217,20 +221,49 @@ public class DownloadFileOperation extends RemoteOperation {
|
|||
|
||||
OCFile parent = fileDataStorageManager.getFileByEncryptedRemotePath(file.getParentRemotePath());
|
||||
|
||||
DecryptedFolderMetadata metadata = EncryptionUtils.downloadFolderMetadata(parent,
|
||||
client,
|
||||
operationContext,
|
||||
user);
|
||||
Object object = EncryptionUtils.downloadFolderMetadata(parent,
|
||||
client,
|
||||
operationContext,
|
||||
user);
|
||||
|
||||
if (metadata == null) {
|
||||
if (object == null) {
|
||||
return new RemoteOperationResult(RemoteOperationResult.ResultCode.METADATA_NOT_FOUND);
|
||||
}
|
||||
byte[] key = EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles()
|
||||
.get(file.getEncryptedFileName()).getEncrypted().getKey());
|
||||
byte[] iv = EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles()
|
||||
.get(file.getEncryptedFileName()).getInitializationVector());
|
||||
byte[] authenticationTag = EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles()
|
||||
.get(file.getEncryptedFileName()).getAuthenticationTag());
|
||||
|
||||
String keyString;
|
||||
String nonceString;
|
||||
String authenticationTagString;
|
||||
if (object instanceof DecryptedFolderMetadataFile) {
|
||||
DecryptedFile decryptedFile = ((DecryptedFolderMetadataFile) object)
|
||||
.getMetadata()
|
||||
.getFiles()
|
||||
.get(file.getEncryptedFileName());
|
||||
|
||||
if (decryptedFile == null) {
|
||||
return new RemoteOperationResult(RemoteOperationResult.ResultCode.METADATA_NOT_FOUND);
|
||||
}
|
||||
|
||||
keyString = decryptedFile.getKey();
|
||||
nonceString = decryptedFile.getNonce();
|
||||
authenticationTagString = decryptedFile.getAuthenticationTag();
|
||||
} else {
|
||||
com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile decryptedFile =
|
||||
((DecryptedFolderMetadataFileV1) object)
|
||||
.getFiles()
|
||||
.get(file.getEncryptedFileName());
|
||||
|
||||
if (decryptedFile == null) {
|
||||
return new RemoteOperationResult(RemoteOperationResult.ResultCode.METADATA_NOT_FOUND);
|
||||
}
|
||||
|
||||
keyString = decryptedFile.getEncrypted().getKey();
|
||||
nonceString = decryptedFile.getInitializationVector();
|
||||
authenticationTagString = decryptedFile.getAuthenticationTag();
|
||||
}
|
||||
|
||||
byte[] key = decodeStringToBase64Bytes(keyString);
|
||||
byte[] iv = decodeStringToBase64Bytes(nonceString);
|
||||
byte[] authenticationTag = decodeStringToBase64Bytes(authenticationTagString);
|
||||
|
||||
try {
|
||||
byte[] decryptedBytes = EncryptionUtils.decryptFile(tmpFile,
|
||||
|
|
|
@ -29,9 +29,11 @@ import com.nextcloud.client.account.User;
|
|||
import com.nextcloud.common.NextcloudClient;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFile;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
|
||||
import com.owncloud.android.lib.common.DirectEditing;
|
||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||
import com.owncloud.android.lib.common.OwnCloudClientFactory;
|
||||
|
@ -47,6 +49,7 @@ import com.owncloud.android.lib.resources.files.model.RemoteFile;
|
|||
import com.owncloud.android.lib.resources.shares.GetSharesForFileRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.shares.OCShare;
|
||||
import com.owncloud.android.lib.resources.shares.ShareType;
|
||||
import com.owncloud.android.lib.resources.status.E2EVersion;
|
||||
import com.owncloud.android.lib.resources.users.GetPredefinedStatusesRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.users.PredefinedStatus;
|
||||
import com.owncloud.android.syncadapter.FileSyncAdapter;
|
||||
|
@ -55,6 +58,7 @@ import com.owncloud.android.utils.EncryptionUtils;
|
|||
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;
|
||||
|
@ -236,6 +240,7 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
|
||||
if (result.isSuccess()) {
|
||||
if (mRemoteFolderChanged) {
|
||||
// TODO catch IllegalStateException, show properly to user
|
||||
result = fetchAndSyncRemoteFolder(client);
|
||||
} else {
|
||||
mChildren = mStorageManager.getFolderContent(mLocalFolder, false);
|
||||
|
@ -403,7 +408,8 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {
|
||||
String remotePath = mLocalFolder.getRemotePath();
|
||||
RemoteOperationResult result = new ReadFolderRemoteOperation(remotePath).execute(client);
|
||||
Log_OC.d(TAG, "Synchronizing " + user.getAccountName() + remotePath);
|
||||
Log_OC.d(TAG, "Refresh folder " + user.getAccountName() + remotePath);
|
||||
Log_OC.d(TAG, "Refresh folder with remote id" + mLocalFolder.getRemoteId());
|
||||
|
||||
if (result.isSuccess()) {
|
||||
synchronizeData(result.getData());
|
||||
|
@ -470,15 +476,38 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
// update size
|
||||
mLocalFolder.setFileLength(remoteFolder.getFileLength());
|
||||
|
||||
DecryptedFolderMetadata metadata = getDecryptedFolderMetadata(encryptedAncestor,
|
||||
mLocalFolder,
|
||||
getClient(),
|
||||
user,
|
||||
mContext);
|
||||
Object object = null;
|
||||
if (mLocalFolder.isEncrypted()) {
|
||||
object = getDecryptedFolderMetadata(encryptedAncestor,
|
||||
mLocalFolder,
|
||||
getClient(),
|
||||
user,
|
||||
mContext);
|
||||
}
|
||||
|
||||
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
|
||||
Map<String, OCFile> localFilesMap = prefillLocalFilesMap(metadata,
|
||||
mStorageManager.getFolderContent(mLocalFolder, false));
|
||||
Map<String, OCFile> localFilesMap;
|
||||
E2EVersion e2EVersion;
|
||||
if (object instanceof DecryptedFolderMetadataFileV1) {
|
||||
e2EVersion = E2EVersion.V1_2;
|
||||
localFilesMap = prefillLocalFilesMap((DecryptedFolderMetadataFileV1) object,
|
||||
mStorageManager.getFolderContent(mLocalFolder, false));
|
||||
} else {
|
||||
e2EVersion = E2EVersion.V2_0;
|
||||
localFilesMap = prefillLocalFilesMap((DecryptedFolderMetadataFile) object,
|
||||
mStorageManager.getFolderContent(mLocalFolder, false));
|
||||
|
||||
// update counter
|
||||
if (object != null) {
|
||||
mLocalFolder.setE2eCounter(((DecryptedFolderMetadataFile) object).getMetadata().getCounter());
|
||||
}
|
||||
}
|
||||
|
||||
// loop to update every child
|
||||
OCFile remoteFile;
|
||||
|
@ -518,8 +547,17 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
FileStorageUtils.searchForLocalFileInDefaultPath(updatedFile, user.getAccountName());
|
||||
|
||||
// update file name for encrypted files
|
||||
if (metadata != null) {
|
||||
updateFileNameForEncryptedFile(mStorageManager, metadata, updatedFile);
|
||||
if (e2EVersion == E2EVersion.V1_2) {
|
||||
updateFileNameForEncryptedFileV1(mStorageManager,
|
||||
(DecryptedFolderMetadataFileV1) object,
|
||||
updatedFile);
|
||||
} else {
|
||||
updateFileNameForEncryptedFile(mStorageManager,
|
||||
(DecryptedFolderMetadataFile) object,
|
||||
updatedFile);
|
||||
if (localFile != null) {
|
||||
updatedFile.setE2eCounter(localFile.getE2eCounter());
|
||||
}
|
||||
}
|
||||
|
||||
// we parse content, so either the folder itself or its direct parent (which we check) must be encrypted
|
||||
|
@ -531,8 +569,14 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
|
||||
// save updated contents in local database
|
||||
// update file name for encrypted files
|
||||
if (metadata != null) {
|
||||
updateFileNameForEncryptedFile(mStorageManager, metadata, mLocalFolder);
|
||||
if (e2EVersion == E2EVersion.V1_2) {
|
||||
updateFileNameForEncryptedFileV1(mStorageManager,
|
||||
(DecryptedFolderMetadataFileV1) object,
|
||||
mLocalFolder);
|
||||
} else {
|
||||
updateFileNameForEncryptedFile(mStorageManager,
|
||||
(DecryptedFolderMetadataFile) object,
|
||||
mLocalFolder);
|
||||
}
|
||||
mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
|
||||
|
||||
|
@ -540,12 +584,12 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
public static DecryptedFolderMetadata getDecryptedFolderMetadata(boolean encryptedAncestor,
|
||||
OCFile localFolder,
|
||||
OwnCloudClient client,
|
||||
User user,
|
||||
Context context) {
|
||||
DecryptedFolderMetadata metadata;
|
||||
public static Object getDecryptedFolderMetadata(boolean encryptedAncestor,
|
||||
OCFile localFolder,
|
||||
OwnCloudClient client,
|
||||
User user,
|
||||
Context context) {
|
||||
Object metadata;
|
||||
if (encryptedAncestor) {
|
||||
metadata = EncryptionUtils.downloadFolderMetadata(localFolder, client, context, user);
|
||||
} else {
|
||||
|
@ -554,13 +598,23 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
return metadata;
|
||||
}
|
||||
|
||||
public static void updateFileNameForEncryptedFile(FileDataStorageManager storageManager,
|
||||
@NonNull DecryptedFolderMetadata metadata,
|
||||
OCFile updatedFile) {
|
||||
public static void updateFileNameForEncryptedFileV1(FileDataStorageManager storageManager,
|
||||
@NonNull DecryptedFolderMetadataFileV1 metadata,
|
||||
OCFile updatedFile) {
|
||||
try {
|
||||
String decryptedFileName = metadata.getFiles().get(updatedFile.getFileName()).getEncrypted()
|
||||
.getFilename();
|
||||
String mimetype = metadata.getFiles().get(updatedFile.getFileName()).getEncrypted().getMimetype();
|
||||
String decryptedFileName;
|
||||
String mimetype;
|
||||
|
||||
if (updatedFile.isFolder()) {
|
||||
decryptedFileName = metadata.getFiles().get(updatedFile.getFileName()).getEncrypted().getFilename();
|
||||
mimetype = MimeType.DIRECTORY;
|
||||
} else {
|
||||
com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile decryptedFile =
|
||||
metadata.getFiles().get(updatedFile.getFileName());
|
||||
decryptedFileName = decryptedFile.getEncrypted().getFilename();
|
||||
mimetype = decryptedFile.getEncrypted().getMimetype();
|
||||
}
|
||||
|
||||
|
||||
OCFile parentFile = storageManager.getFileById(updatedFile.getParentId());
|
||||
String decryptedRemotePath = parentFile.getDecryptedRemotePath() + decryptedFileName;
|
||||
|
@ -580,7 +634,46 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
updatedFile.setMimeType(mimetype);
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
Log_OC.e(TAG, "Metadata for file " + updatedFile.getFileId() + " not found!");
|
||||
Log_OC.e(TAG, "DecryptedMetadata for file " + updatedFile.getFileId() + " not found!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateFileNameForEncryptedFile(FileDataStorageManager storageManager,
|
||||
@NonNull DecryptedFolderMetadataFile metadata,
|
||||
OCFile updatedFile) {
|
||||
try {
|
||||
String decryptedFileName;
|
||||
String mimetype;
|
||||
|
||||
if (updatedFile.isFolder()) {
|
||||
decryptedFileName = metadata.getMetadata().getFolders().get(updatedFile.getFileName());
|
||||
mimetype = MimeType.DIRECTORY;
|
||||
} else {
|
||||
DecryptedFile decryptedFile = metadata.getMetadata().getFiles().get(updatedFile.getFileName());
|
||||
decryptedFileName = decryptedFile.getFilename();
|
||||
mimetype = decryptedFile.getMimetype();
|
||||
}
|
||||
|
||||
|
||||
OCFile parentFile = storageManager.getFileById(updatedFile.getParentId());
|
||||
String decryptedRemotePath = parentFile.getDecryptedRemotePath() + decryptedFileName;
|
||||
|
||||
if (updatedFile.isFolder()) {
|
||||
decryptedRemotePath += "/";
|
||||
}
|
||||
updatedFile.setDecryptedRemotePath(decryptedRemotePath);
|
||||
|
||||
if (mimetype == null || mimetype.isEmpty()) {
|
||||
if (updatedFile.isFolder()) {
|
||||
updatedFile.setMimeType(MimeType.DIRECTORY);
|
||||
} else {
|
||||
updatedFile.setMimeType("application/octet-stream");
|
||||
}
|
||||
} else {
|
||||
updatedFile.setMimeType(mimetype);
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
Log_OC.e(TAG, "DecryptedMetadata for file " + updatedFile.getFileId() + " not found!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -634,7 +727,7 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
public static Map<String, OCFile> prefillLocalFilesMap(DecryptedFolderMetadata metadata, List<OCFile> localFiles) {
|
||||
public static Map<String, OCFile> prefillLocalFilesMap(Object metadata, List<OCFile> localFiles) {
|
||||
Map<String, OCFile> localFilesMap = Maps.newHashMapWithExpectedSize(localFiles.size());
|
||||
|
||||
for (OCFile file : localFiles) {
|
||||
|
|
|
@ -104,10 +104,11 @@ public class RemoveFileOperation extends SyncOperation {
|
|||
if (fileToRemove.isEncrypted()) {
|
||||
OCFile parent = getStorageManager().getFileByPath(fileToRemove.getParentRemotePath());
|
||||
operation = new RemoveRemoteEncryptedFileOperation(fileToRemove.getRemotePath(),
|
||||
parent.getLocalId(),
|
||||
user,
|
||||
context,
|
||||
fileToRemove.getEncryptedFileName());
|
||||
fileToRemove.getEncryptedFileName(),
|
||||
parent,
|
||||
fileToRemove.isFolder());
|
||||
} else {
|
||||
operation = new RemoveFileRemoteOperation(fileToRemove.getRemotePath());
|
||||
}
|
||||
|
|
|
@ -23,68 +23,58 @@ package com.owncloud.android.operations;
|
|||
|
||||
import android.content.Context;
|
||||
|
||||
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.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
|
||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperation;
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||
import com.owncloud.android.lib.resources.e2ee.GetMetadataRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.e2ee.LockFileRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
|
||||
import com.owncloud.android.utils.EncryptionUtils;
|
||||
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||
|
||||
import org.apache.commons.httpclient.HttpStatus;
|
||||
import org.apache.commons.httpclient.NameValuePair;
|
||||
import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import kotlin.Pair;
|
||||
|
||||
/**
|
||||
* Remote operation performing the removal of a remote encrypted file or folder
|
||||
*/
|
||||
public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
||||
public class RemoveRemoteEncryptedFileOperation extends RemoteOperation<Void> {
|
||||
private static final String TAG = RemoveRemoteEncryptedFileOperation.class.getSimpleName();
|
||||
|
||||
private static final int REMOVE_READ_TIMEOUT = 30000;
|
||||
private static final int REMOVE_CONNECTION_TIMEOUT = 5000;
|
||||
|
||||
private final String remotePath;
|
||||
private final long parentId;
|
||||
private User user;
|
||||
|
||||
private final ArbitraryDataProvider arbitraryDataProvider;
|
||||
private final OCFile parentFolder;
|
||||
private final User user;
|
||||
private final String fileName;
|
||||
private final Context context;
|
||||
private final boolean isFolder;
|
||||
private final ArbitraryDataProvider arbitraryDataProvider;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param remotePath RemotePath of the remote file or folder to remove from the server
|
||||
* @param parentId local id of parent folder
|
||||
* @param remotePath RemotePath of the remote file or folder to remove from the server
|
||||
* @param parentFolder parent folder
|
||||
*/
|
||||
RemoveRemoteEncryptedFileOperation(String remotePath,
|
||||
long parentId,
|
||||
User user,
|
||||
Context context,
|
||||
String fileName) {
|
||||
String fileName,
|
||||
OCFile parentFolder,
|
||||
boolean isFolder) {
|
||||
this.remotePath = remotePath;
|
||||
this.parentId = parentId;
|
||||
this.user = user;
|
||||
this.fileName = fileName;
|
||||
this.context = context;
|
||||
this.parentFolder = parentFolder;
|
||||
this.isFolder = isFolder;
|
||||
|
||||
arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
|
||||
}
|
||||
|
@ -93,46 +83,19 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
|||
* Performs the remove operation.
|
||||
*/
|
||||
@Override
|
||||
protected RemoteOperationResult run(OwnCloudClient client) {
|
||||
RemoteOperationResult result;
|
||||
protected RemoteOperationResult<Void> run(OwnCloudClient client) {
|
||||
RemoteOperationResult<Void> result;
|
||||
DeleteMethod delete = null;
|
||||
String token = null;
|
||||
DecryptedFolderMetadata metadata;
|
||||
|
||||
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
|
||||
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
|
||||
|
||||
try {
|
||||
// Lock folder
|
||||
RemoteOperationResult lockFileOperationResult = new LockFileRemoteOperation(parentId).execute(client);
|
||||
token = EncryptionUtils.lockFolder(parentFolder, client);
|
||||
|
||||
if (lockFileOperationResult.isSuccess()) {
|
||||
token = (String) lockFileOperationResult.getData().get(0);
|
||||
} else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
|
||||
throw new RemoteOperationFailedException("Forbidden! Please try again later.)");
|
||||
} else {
|
||||
throw new RemoteOperationFailedException("Unknown error!");
|
||||
}
|
||||
|
||||
// refresh metadata
|
||||
RemoteOperationResult getMetadataOperationResult = new GetMetadataRemoteOperation(parentId).execute(client);
|
||||
|
||||
if (getMetadataOperationResult.isSuccess()) {
|
||||
// decrypt metadata
|
||||
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
|
||||
|
||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
|
||||
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
||||
});
|
||||
|
||||
metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
parentId);
|
||||
} else {
|
||||
throw new RemoteOperationFailedException("No Metadata found!");
|
||||
}
|
||||
EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
|
||||
Pair<Boolean, DecryptedFolderMetadataFile> pair = encryptionUtilsV2.retrieveMetadata(parentFolder, client, user, context);
|
||||
boolean metadataExists = pair.getFirst();
|
||||
DecryptedFolderMetadataFile metadata = pair.getSecond();
|
||||
|
||||
// delete file remote
|
||||
delete = new DeleteMethod(client.getFilesDavUri(remotePath));
|
||||
|
@ -140,35 +103,29 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
|||
int status = client.executeMethod(delete, REMOVE_READ_TIMEOUT, REMOVE_CONNECTION_TIMEOUT);
|
||||
|
||||
delete.getResponseBodyAsString(); // exhaust the response, although not interesting
|
||||
result = new RemoteOperationResult(delete.succeeded() || status == HttpStatus.SC_NOT_FOUND, delete);
|
||||
result = new RemoteOperationResult<>(delete.succeeded() || status == HttpStatus.SC_NOT_FOUND, delete);
|
||||
Log_OC.i(TAG, "Remove " + remotePath + ": " + result.getLogMessage());
|
||||
|
||||
// remove file from metadata
|
||||
metadata.getFiles().remove(fileName);
|
||||
|
||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(
|
||||
metadata,
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
parentId);
|
||||
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
||||
if (isFolder) {
|
||||
encryptionUtilsV2.removeFolderFromMetadata(fileName, metadata);
|
||||
} else {
|
||||
encryptionUtilsV2.removeFileFromMetadata(fileName, metadata);
|
||||
}
|
||||
|
||||
// upload metadata
|
||||
RemoteOperationResult uploadMetadataOperationResult =
|
||||
new UpdateMetadataRemoteOperation(parentId,
|
||||
serializedFolderMetadata, token).execute(client);
|
||||
|
||||
if (!uploadMetadataOperationResult.isSuccess()) {
|
||||
throw new RemoteOperationFailedException("Metadata not uploaded!");
|
||||
}
|
||||
encryptionUtilsV2.serializeAndUploadMetadata(parentFolder,
|
||||
metadata,
|
||||
token,
|
||||
client,
|
||||
metadataExists,
|
||||
context,
|
||||
user,
|
||||
new FileDataStorageManager(user, context.getContentResolver()));
|
||||
|
||||
// return success
|
||||
return result;
|
||||
} catch (NoSuchAlgorithmException | IOException | InvalidKeyException | InvalidAlgorithmParameterException |
|
||||
NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeySpecException |
|
||||
CertificateException e) {
|
||||
result = new RemoteOperationResult(e);
|
||||
} catch (Exception e) {
|
||||
result = new RemoteOperationResult<>(e);
|
||||
Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e);
|
||||
|
||||
} finally {
|
||||
|
@ -178,11 +135,12 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
|||
|
||||
// unlock file
|
||||
if (token != null) {
|
||||
RemoteOperationResult unlockFileOperationResult = new UnlockFileRemoteOperation(parentId, token)
|
||||
.execute(client);
|
||||
RemoteOperationResult<Void> unlockFileOperationResult = EncryptionUtils.unlockFolder(parentFolder,
|
||||
client,
|
||||
token);
|
||||
|
||||
if (!unlockFileOperationResult.isSuccess()) {
|
||||
Log_OC.e(TAG, "Failed to unlock " + parentId);
|
||||
Log_OC.e(TAG, "Failed to unlock " + parentFolder.getLocalId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,10 @@ import android.text.TextUtils;
|
|||
|
||||
import com.nextcloud.client.account.User;
|
||||
import com.nextcloud.client.files.downloader.FileDownloadHelper;
|
||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
|
||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||
import com.owncloud.android.lib.common.operations.OperationCancelledException;
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||
|
@ -37,6 +38,7 @@ import com.owncloud.android.lib.common.utils.Log_OC;
|
|||
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.files.model.RemoteFile;
|
||||
import com.owncloud.android.lib.resources.status.E2EVersion;
|
||||
import com.owncloud.android.operations.common.SyncOperation;
|
||||
import com.owncloud.android.services.OperationsService;
|
||||
import com.owncloud.android.utils.FileStorageUtils;
|
||||
|
@ -49,6 +51,8 @@ import java.util.Map;
|
|||
import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
|
||||
|
||||
/**
|
||||
* Remote operation performing the synchronization of the list of files contained
|
||||
|
@ -215,6 +219,7 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
|||
ReadFolderRemoteOperation operation = new ReadFolderRemoteOperation(mRemotePath);
|
||||
RemoteOperationResult result = operation.execute(client);
|
||||
Log_OC.d(TAG, "Synchronizing " + user.getAccountName() + mRemotePath);
|
||||
Log_OC.d(TAG, "Synchronizing remote id" + mLocalFolder.getRemoteId());
|
||||
|
||||
if (result.isSuccess()) {
|
||||
synchronizeData(result.getData());
|
||||
|
@ -281,17 +286,29 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
|||
// update richWorkspace
|
||||
mLocalFolder.setRichWorkspace(remoteFolder.getRichWorkspace());
|
||||
|
||||
DecryptedFolderMetadata metadata = RefreshFolderOperation.getDecryptedFolderMetadata(encryptedAncestor,
|
||||
mLocalFolder,
|
||||
getClient(),
|
||||
user,
|
||||
mContext);
|
||||
|
||||
Object object = RefreshFolderOperation.getDecryptedFolderMetadata(encryptedAncestor,
|
||||
mLocalFolder,
|
||||
getClient(),
|
||||
user,
|
||||
mContext);
|
||||
if (mLocalFolder.isEncrypted() && object == null) {
|
||||
throw new IllegalStateException("metadata is null!");
|
||||
}
|
||||
|
||||
// get current data about local contents of the folder to synchronize
|
||||
Map<String, OCFile> localFilesMap =
|
||||
RefreshFolderOperation.prefillLocalFilesMap(metadata,
|
||||
storageManager.getFolderContent(mLocalFolder, false));
|
||||
Map<String, OCFile> localFilesMap;
|
||||
E2EVersion e2EVersion;
|
||||
|
||||
if (object instanceof DecryptedFolderMetadataFileV1) {
|
||||
e2EVersion = E2EVersion.V1_2;
|
||||
localFilesMap = RefreshFolderOperation.prefillLocalFilesMap((DecryptedFolderMetadataFileV1) object,
|
||||
storageManager.getFolderContent(mLocalFolder, false));
|
||||
} else {
|
||||
e2EVersion = E2EVersion.V2_0;
|
||||
localFilesMap = RefreshFolderOperation.prefillLocalFilesMap((DecryptedFolderMetadataFile) object,
|
||||
storageManager.getFolderContent(mLocalFolder, false));
|
||||
}
|
||||
|
||||
// loop to synchronize every child
|
||||
List<OCFile> updatedFiles = new ArrayList<>(folderAndFiles.size() - 1);
|
||||
OCFile remoteFile;
|
||||
|
@ -323,8 +340,14 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
|||
FileStorageUtils.searchForLocalFileInDefaultPath(updatedFile, user.getAccountName());
|
||||
|
||||
// update file name for encrypted files
|
||||
if (metadata != null) {
|
||||
RefreshFolderOperation.updateFileNameForEncryptedFile(storageManager, metadata, updatedFile);
|
||||
if (e2EVersion == E2EVersion.V1_2) {
|
||||
RefreshFolderOperation.updateFileNameForEncryptedFileV1(storageManager,
|
||||
(DecryptedFolderMetadataFileV1) object,
|
||||
updatedFile);
|
||||
} else {
|
||||
RefreshFolderOperation.updateFileNameForEncryptedFile(storageManager,
|
||||
(DecryptedFolderMetadataFile) object,
|
||||
updatedFile);
|
||||
}
|
||||
|
||||
// we parse content, so either the folder itself or its direct parent (which we check) must be encrypted
|
||||
|
@ -337,8 +360,15 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
|||
updatedFiles.add(updatedFile);
|
||||
}
|
||||
|
||||
if (metadata != null) {
|
||||
RefreshFolderOperation.updateFileNameForEncryptedFile(storageManager, metadata, mLocalFolder);
|
||||
// update file name for encrypted files
|
||||
if (e2EVersion == E2EVersion.V1_2) {
|
||||
RefreshFolderOperation.updateFileNameForEncryptedFileV1(storageManager,
|
||||
(DecryptedFolderMetadataFileV1) object,
|
||||
mLocalFolder);
|
||||
} else {
|
||||
RefreshFolderOperation.updateFileNameForEncryptedFile(storageManager,
|
||||
(DecryptedFolderMetadataFile) object,
|
||||
mLocalFolder);
|
||||
}
|
||||
|
||||
// save updated contents in local database
|
||||
|
@ -391,6 +421,7 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
|||
}
|
||||
|
||||
|
||||
@SuppressFBWarnings("JLM")
|
||||
private void prepareOpsFromLocalKnowledge() throws OperationCancelledException {
|
||||
List<OCFile> children = getStorageManager().getFolderContent(mLocalFolder, false);
|
||||
for (OCFile child : children) {
|
||||
|
|
|
@ -21,8 +21,13 @@
|
|||
|
||||
package com.owncloud.android.operations;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.nextcloud.client.account.User;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
|
||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
|
||||
|
@ -32,6 +37,8 @@ import com.owncloud.android.lib.resources.shares.OCShare;
|
|||
import com.owncloud.android.lib.resources.shares.RemoveShareRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.shares.ShareType;
|
||||
import com.owncloud.android.operations.common.SyncOperation;
|
||||
import com.owncloud.android.utils.EncryptionUtils;
|
||||
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -45,27 +52,89 @@ public class UnshareOperation extends SyncOperation {
|
|||
|
||||
private final String remotePath;
|
||||
private final long shareId;
|
||||
private final Context context;
|
||||
private final User user;
|
||||
|
||||
public UnshareOperation(String remotePath, long shareId, FileDataStorageManager storageManager) {
|
||||
public UnshareOperation(String remotePath,
|
||||
long shareId,
|
||||
FileDataStorageManager storageManager,
|
||||
User user,
|
||||
Context context) {
|
||||
super(storageManager);
|
||||
|
||||
this.remotePath = remotePath;
|
||||
this.shareId = shareId;
|
||||
this.user = user;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RemoteOperationResult run(OwnCloudClient client) {
|
||||
RemoteOperationResult result;
|
||||
String token = null;
|
||||
|
||||
// Get Share for a file
|
||||
OCShare share = getStorageManager().getShareById(shareId);
|
||||
|
||||
if (share != null) {
|
||||
OCFile file = getStorageManager().getFileByEncryptedRemotePath(remotePath);
|
||||
|
||||
if (file.isEncrypted() && share.getShareType() != ShareType.PUBLIC_LINK) {
|
||||
// E2E: lock folder
|
||||
try {
|
||||
token = EncryptionUtils.lockFolder(file, client, file.getE2eCounter() + 1);
|
||||
} catch (UploadException e) {
|
||||
return new RemoteOperationResult(e);
|
||||
}
|
||||
|
||||
// download metadata
|
||||
Object object = EncryptionUtils.downloadFolderMetadata(file,
|
||||
client,
|
||||
context,
|
||||
user);
|
||||
|
||||
if (object == null) {
|
||||
return new RemoteOperationResult(new RuntimeException("No metadata!"));
|
||||
}
|
||||
|
||||
if (object instanceof DecryptedFolderMetadataFileV1) {
|
||||
throw new RuntimeException("Trying to unshare on e2e v1!");
|
||||
}
|
||||
|
||||
DecryptedFolderMetadataFile metadata = (DecryptedFolderMetadataFile) object;
|
||||
|
||||
// remove sharee from metadata
|
||||
EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
|
||||
DecryptedFolderMetadataFile newMetadata = encryptionUtilsV2.removeShareeFromMetadata(metadata,
|
||||
share.getShareWith());
|
||||
|
||||
// upload metadata
|
||||
try {
|
||||
encryptionUtilsV2.serializeAndUploadMetadata(file,
|
||||
newMetadata,
|
||||
token,
|
||||
client,
|
||||
true,
|
||||
context,
|
||||
user,
|
||||
getStorageManager());
|
||||
} catch (UploadException e) {
|
||||
return new RemoteOperationResult(new RuntimeException("Upload of metadata failed!"));
|
||||
}
|
||||
}
|
||||
|
||||
RemoveShareRemoteOperation operation = new RemoveShareRemoteOperation(share.getRemoteId());
|
||||
result = operation.execute(client);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
// E2E: unlock folder
|
||||
if (file.isEncrypted() && share.getShareType() != ShareType.PUBLIC_LINK) {
|
||||
RemoteOperationResult<Void> unlockResult = EncryptionUtils.unlockFolder(file, client, token);
|
||||
if (!unlockResult.isSuccess()) {
|
||||
return new RemoteOperationResult<>(new RuntimeException("Unlock failed"));
|
||||
}
|
||||
}
|
||||
|
||||
Log_OC.d(TAG, "Share id = " + share.getRemoteId() + " deleted");
|
||||
|
||||
if (ShareType.PUBLIC_LINK == share.getShareType()) {
|
||||
|
|
|
@ -25,7 +25,6 @@ import android.annotation.SuppressLint;
|
|||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.nextcloud.client.account.User;
|
||||
import com.nextcloud.client.device.BatteryStatus;
|
||||
|
@ -34,12 +33,17 @@ import com.nextcloud.client.network.Connectivity;
|
|||
import com.nextcloud.client.network.ConnectivityService;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
|
||||
import com.owncloud.android.datamodel.UploadsStorageManager;
|
||||
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;
|
||||
import com.owncloud.android.db.OCUpload;
|
||||
import com.owncloud.android.files.services.FileUploader;
|
||||
import com.owncloud.android.files.services.NameCollisionPolicy;
|
||||
|
@ -56,13 +60,16 @@ import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
|
|||
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.files.model.RemoteFile;
|
||||
import com.owncloud.android.lib.resources.status.E2EVersion;
|
||||
import com.owncloud.android.operations.common.SyncOperation;
|
||||
import com.owncloud.android.utils.EncryptionUtils;
|
||||
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||
import com.owncloud.android.utils.FileStorageUtils;
|
||||
import com.owncloud.android.utils.FileUtil;
|
||||
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;
|
||||
|
@ -80,9 +87,11 @@ import java.io.RandomAccessFile;
|
|||
import java.nio.channels.FileChannel;
|
||||
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;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import androidx.annotation.CheckResult;
|
||||
|
@ -90,8 +99,7 @@ import androidx.annotation.Nullable;
|
|||
|
||||
|
||||
/**
|
||||
* Operation performing the update in the ownCloud server
|
||||
* of a file that was modified locally.
|
||||
* Operation performing the update in the ownCloud server of a file that was modified locally.
|
||||
*/
|
||||
public class UploadFileOperation extends SyncOperation {
|
||||
|
||||
|
@ -230,10 +238,10 @@ public class UploadFileOperation extends SyncOperation {
|
|||
mUpload = upload;
|
||||
if (file == null) {
|
||||
mFile = obtainNewOCFileToUpload(
|
||||
upload.getRemotePath(),
|
||||
upload.getLocalPath(),
|
||||
upload.getMimeType()
|
||||
);
|
||||
upload.getRemotePath(),
|
||||
upload.getLocalPath(),
|
||||
upload.getMimeType()
|
||||
);
|
||||
} else {
|
||||
mFile = file;
|
||||
}
|
||||
|
@ -261,7 +269,9 @@ public class UploadFileOperation extends SyncOperation {
|
|||
return mWhileChargingOnly;
|
||||
}
|
||||
|
||||
public boolean isIgnoringPowerSaveMode() { return mIgnoringPowerSaveMode; }
|
||||
public boolean isIgnoringPowerSaveMode() {
|
||||
return mIgnoringPowerSaveMode;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
|
@ -392,7 +402,7 @@ public class UploadFileOperation extends SyncOperation {
|
|||
|
||||
String remoteParentPath = new File(getRemotePath()).getParent();
|
||||
remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
|
||||
remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
|
||||
remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
|
||||
|
||||
OCFile parent = getStorageManager().getFileByPath(remoteParentPath);
|
||||
|
||||
|
@ -444,8 +454,6 @@ public class UploadFileOperation extends SyncOperation {
|
|||
String token = null;
|
||||
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(getContext());
|
||||
|
||||
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
|
||||
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
|
||||
|
||||
try {
|
||||
|
@ -456,32 +464,75 @@ public class UploadFileOperation extends SyncOperation {
|
|||
return result;
|
||||
}
|
||||
/***** E2E *****/
|
||||
// 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()) {
|
||||
token = mFolderUnlockToken;
|
||||
} else {
|
||||
token = EncryptionUtils.lockFolder(parentFile, client);
|
||||
token = EncryptionUtils.lockFolder(parentFile, client, counter);
|
||||
// immediately store it
|
||||
mUpload.setFolderUnlockToken(token);
|
||||
uploadsStorageManager.updateUpload(mUpload);
|
||||
}
|
||||
|
||||
// Update metadata
|
||||
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parentFile,
|
||||
client,
|
||||
privateKey,
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user);
|
||||
EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
|
||||
// kotlin.Pair<Boolean, DecryptedFolderMetadataFile> metadataPair =
|
||||
// encryptionUtilsV2.retrieveMetadata(parentFile,
|
||||
// client,
|
||||
// user,
|
||||
// mContext);
|
||||
|
||||
metadataExists = metadataPair.first;
|
||||
DecryptedFolderMetadata metadata = metadataPair.second;
|
||||
Object object = EncryptionUtils.downloadFolderMetadata(parentFile, client, mContext, user);
|
||||
|
||||
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 {
|
||||
// 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
|
||||
|
||||
// metadataExists = metadataPair.getFirst();
|
||||
// DecryptedFolderMetadataFile metadata = metadataPair.getSecond();
|
||||
|
||||
// TODO E2E: check counter: must be less than our counter, check rest: signature, etc
|
||||
/**** E2E *****/
|
||||
|
||||
// check name collision
|
||||
RemoteOperationResult collisionResult = checkNameCollision(client, metadata, parentFile.isEncrypted());
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
if (object instanceof DecryptedFolderMetadataFileV1 metadata) {
|
||||
for (DecryptedFile file : metadata.getFiles().values()) {
|
||||
fileNames.add(file.getEncrypted().getFilename());
|
||||
}
|
||||
} else {
|
||||
for (com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFile file :
|
||||
((DecryptedFolderMetadataFile) object).getMetadata().getFiles().values()) {
|
||||
fileNames.add(file.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
RemoteOperationResult collisionResult = checkNameCollision(client, fileNames, parentFile.isEncrypted());
|
||||
if (collisionResult != null) {
|
||||
result = collisionResult;
|
||||
return collisionResult;
|
||||
|
@ -509,18 +560,24 @@ public class UploadFileOperation extends SyncOperation {
|
|||
// IV, always generate new one
|
||||
byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
|
||||
|
||||
EncryptionUtils.EncryptedFile encryptedFile = EncryptionUtils.encryptFile(mFile, key, iv);
|
||||
EncryptedFile encryptedFile = EncryptionUtils.encryptFile(mFile, key, iv);
|
||||
|
||||
// new random file name, check if it exists in metadata
|
||||
String encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
String encryptedFileName = EncryptionUtils.generateUid();
|
||||
|
||||
while (metadata.getFiles().get(encryptedFileName) != null) {
|
||||
encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
if (object instanceof DecryptedFolderMetadataFileV1 metadata) {
|
||||
while (metadata.getFiles().get(encryptedFileName) != null) {
|
||||
encryptedFileName = EncryptionUtils.generateUid();
|
||||
}
|
||||
} else {
|
||||
while (((DecryptedFolderMetadataFile) object).getMetadata().getFiles().get(encryptedFileName) != null) {
|
||||
encryptedFileName = EncryptionUtils.generateUid();
|
||||
}
|
||||
}
|
||||
|
||||
File encryptedTempFile = File.createTempFile("encFile", encryptedFileName);
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
|
||||
fileOutputStream.write(encryptedFile.encryptedBytes);
|
||||
fileOutputStream.write(encryptedFile.getEncryptedBytes());
|
||||
fileOutputStream.close();
|
||||
|
||||
/***** E2E *****/
|
||||
|
@ -556,7 +613,6 @@ public class UploadFileOperation extends SyncOperation {
|
|||
size = new File(mFile.getStoragePath()).length();
|
||||
}
|
||||
|
||||
|
||||
updateSize(size);
|
||||
|
||||
/// perform the upload
|
||||
|
@ -605,48 +661,82 @@ public class UploadFileOperation extends SyncOperation {
|
|||
mFile.setDecryptedRemotePath(parentFile.getDecryptedRemotePath() + originalFile.getName());
|
||||
mFile.setRemotePath(parentFile.getRemotePath() + encryptedFileName);
|
||||
|
||||
// update metadata
|
||||
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
|
||||
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
|
||||
data.setFilename(mFile.getDecryptedFileName());
|
||||
data.setMimetype(mFile.getMimeType());
|
||||
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
|
||||
|
||||
decryptedFile.setEncrypted(data);
|
||||
decryptedFile.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
|
||||
decryptedFile.setAuthenticationTag(encryptedFile.authenticationTag);
|
||||
if (object instanceof DecryptedFolderMetadataFileV1 metadata) {
|
||||
// update metadata
|
||||
DecryptedFile decryptedFile = new DecryptedFile();
|
||||
Data data = new Data();
|
||||
data.setFilename(mFile.getDecryptedFileName());
|
||||
data.setMimetype(mFile.getMimeType());
|
||||
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
|
||||
|
||||
metadata.getFiles().put(encryptedFileName, decryptedFile);
|
||||
decryptedFile.setEncrypted(data);
|
||||
decryptedFile.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
|
||||
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
||||
|
||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
parentFile.getLocalId());
|
||||
metadata.getFiles().put(encryptedFileName, decryptedFile);
|
||||
|
||||
String serializedFolderMetadata;
|
||||
EncryptedFolderMetadataFileV1 encryptedFolderMetadata =
|
||||
EncryptionUtils.encryptFolderMetadata(metadata,
|
||||
publicKey,
|
||||
parentFile.getLocalId(),
|
||||
user,
|
||||
arbitraryDataProvider
|
||||
);
|
||||
|
||||
// check if we need metadataKeys
|
||||
if (metadata.getMetadata().getMetadataKey() != null) {
|
||||
serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata, true);
|
||||
String serializedFolderMetadata;
|
||||
|
||||
// check if we need metadataKeys
|
||||
if (metadata.getMetadata().getMetadataKey() != null) {
|
||||
serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata, true);
|
||||
} else {
|
||||
serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
||||
}
|
||||
|
||||
// upload metadata
|
||||
EncryptionUtils.uploadMetadata(parentFile,
|
||||
serializedFolderMetadata,
|
||||
token,
|
||||
client,
|
||||
metadataExists,
|
||||
E2EVersion.V1_2,
|
||||
"",
|
||||
arbitraryDataProvider,
|
||||
user);
|
||||
|
||||
// unlock
|
||||
result = EncryptionUtils.unlockFolderV1(parentFile, client, token);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
token = null;
|
||||
}
|
||||
} else {
|
||||
serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
||||
}
|
||||
DecryptedFolderMetadataFile metadata = (DecryptedFolderMetadataFile) object;
|
||||
encryptionUtilsV2.addFileToMetadata(
|
||||
encryptedFileName,
|
||||
mFile,
|
||||
iv,
|
||||
encryptedFile.getAuthenticationTag(),
|
||||
key,
|
||||
metadata,
|
||||
getStorageManager());
|
||||
|
||||
// upload metadata
|
||||
EncryptionUtils.uploadMetadata(parentFile,
|
||||
serializedFolderMetadata,
|
||||
token,
|
||||
client,
|
||||
metadataExists,
|
||||
arbitraryDataProvider,
|
||||
user);
|
||||
// upload metadata
|
||||
encryptionUtilsV2.serializeAndUploadMetadata(parentFile,
|
||||
metadata,
|
||||
token,
|
||||
client,
|
||||
metadataExists,
|
||||
mContext,
|
||||
user,
|
||||
getStorageManager());
|
||||
|
||||
// unlock
|
||||
result = EncryptionUtils.unlockFolder(parentFile, client, token);
|
||||
// unlock
|
||||
result = EncryptionUtils.unlockFolder(parentFile, client, token);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
token = null;
|
||||
if (result.isSuccess()) {
|
||||
token = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
|
@ -717,24 +807,24 @@ public class UploadFileOperation extends SyncOperation {
|
|||
final BatteryStatus battery = powerManagementService.getBattery();
|
||||
if (mWhileChargingOnly && !battery.isCharging()) {
|
||||
Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath());
|
||||
remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING);
|
||||
remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING);
|
||||
}
|
||||
|
||||
// check that device is not in power save mode
|
||||
if (!mIgnoringPowerSaveMode && powerManagementService.isPowerSavingEnabled()) {
|
||||
Log_OC.d(TAG, "Upload delayed because device is in power save mode: " + getRemotePath());
|
||||
remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_IN_POWER_SAVE_MODE);
|
||||
remoteOperationResult = new RemoteOperationResult(ResultCode.DELAYED_IN_POWER_SAVE_MODE);
|
||||
}
|
||||
|
||||
// check if the file continues existing before schedule the operation
|
||||
if (!originalFile.exists()) {
|
||||
Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore");
|
||||
remoteOperationResult = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
|
||||
remoteOperationResult = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// check that internet is not behind walled garden
|
||||
if (!connectivityService.getConnectivity().isConnected() || connectivityService.isInternetWalled()) {
|
||||
remoteOperationResult = new RemoteOperationResult(ResultCode.NO_NETWORK_CONNECTION);
|
||||
remoteOperationResult = new RemoteOperationResult(ResultCode.NO_NETWORK_CONNECTION);
|
||||
}
|
||||
|
||||
return remoteOperationResult;
|
||||
|
@ -903,7 +993,7 @@ public class UploadFileOperation extends SyncOperation {
|
|||
|
||||
private void updateSize(long size) {
|
||||
OCUpload ocUpload = uploadsStorageManager.getUploadById(getOCUploadId());
|
||||
if(ocUpload != null){
|
||||
if (ocUpload != null) {
|
||||
ocUpload.setFileSize(size);
|
||||
uploadsStorageManager.updateUpload(ocUpload);
|
||||
}
|
||||
|
@ -928,7 +1018,7 @@ public class UploadFileOperation extends SyncOperation {
|
|||
}
|
||||
|
||||
private RemoteOperationResult copyFile(File originalFile, String expectedPath) throws OperationCancelledException,
|
||||
IOException {
|
||||
IOException {
|
||||
if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY && !mOriginalStoragePath.equals(expectedPath)) {
|
||||
String temporalPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mContext) +
|
||||
mFile.getRemotePath();
|
||||
|
@ -947,18 +1037,18 @@ public class UploadFileOperation extends SyncOperation {
|
|||
|
||||
@CheckResult
|
||||
private RemoteOperationResult checkNameCollision(OwnCloudClient client,
|
||||
DecryptedFolderMetadata metadata,
|
||||
List<String> fileNames,
|
||||
boolean encrypted)
|
||||
throws OperationCancelledException {
|
||||
Log_OC.d(TAG, "Checking name collision in server");
|
||||
|
||||
if (existsFile(client, mRemotePath, metadata, encrypted)) {
|
||||
if (existsFile(client, mRemotePath, fileNames, encrypted)) {
|
||||
switch (mNameCollisionPolicy) {
|
||||
case CANCEL:
|
||||
Log_OC.d(TAG, "File exists; canceling");
|
||||
throw new OperationCancelledException();
|
||||
case RENAME:
|
||||
mRemotePath = getNewAvailableRemotePath(client, mRemotePath, metadata, encrypted);
|
||||
mRemotePath = getNewAvailableRemotePath(client, mRemotePath, fileNames, encrypted);
|
||||
mWasRenamed = true;
|
||||
createNewOCFile(mRemotePath);
|
||||
Log_OC.d(TAG, "File renamed as " + mRemotePath);
|
||||
|
@ -1041,15 +1131,14 @@ public class UploadFileOperation extends SyncOperation {
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks the existence of the folder where the current file will be uploaded both
|
||||
* in the remote server and in the local database.
|
||||
* Checks the existence of the folder where the current file will be uploaded both in the remote server and in the
|
||||
* local database.
|
||||
* <p/>
|
||||
* If the upload is set to enforce the creation of the folder, the method tries to
|
||||
* create it both remote and locally.
|
||||
* If the upload is set to enforce the creation of the folder, the method tries to create it both remote and
|
||||
* locally.
|
||||
*
|
||||
* @param pathToGrant Full remote path whose existence will be granted.
|
||||
* @return An {@link OCFile} instance corresponding to the folder where the file
|
||||
* will be uploaded.
|
||||
* @return An {@link OCFile} instance corresponding to the folder where the file will be uploaded.
|
||||
*/
|
||||
private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) {
|
||||
RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, false);
|
||||
|
@ -1075,7 +1164,7 @@ public class UploadFileOperation extends SyncOperation {
|
|||
private OCFile createLocalFolder(String remotePath) {
|
||||
String parentPath = new File(remotePath).getParent();
|
||||
parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ?
|
||||
parentPath : parentPath + OCFile.PATH_SEPARATOR;
|
||||
parentPath : parentPath + OCFile.PATH_SEPARATOR;
|
||||
OCFile parent = getStorageManager().getFileByPath(parentPath);
|
||||
if (parent == null) {
|
||||
parent = createLocalFolder(parentPath);
|
||||
|
@ -1105,8 +1194,8 @@ public class UploadFileOperation extends SyncOperation {
|
|||
newFile.setMimeType(mFile.getMimeType());
|
||||
newFile.setModificationTimestamp(mFile.getModificationTimestamp());
|
||||
newFile.setModificationTimestampAtLastSyncForData(
|
||||
mFile.getModificationTimestampAtLastSyncForData()
|
||||
);
|
||||
mFile.getModificationTimestampAtLastSyncForData()
|
||||
);
|
||||
newFile.setEtag(mFile.getEtag());
|
||||
newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
|
||||
newFile.setLastSyncDateForData(mFile.getLastSyncDateForData());
|
||||
|
@ -1117,15 +1206,16 @@ public class UploadFileOperation extends SyncOperation {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a new and available (does not exists on the server) remotePath.
|
||||
* This adds an incremental suffix.
|
||||
* Returns a new and available (does not exists on the server) remotePath. This adds an incremental suffix.
|
||||
*
|
||||
* @param client OwnCloud client
|
||||
* @param remotePath remote path of the file
|
||||
* @param metadata metadata of encrypted folder
|
||||
* @param fileNames list of decrypted file names
|
||||
* @return new remote path
|
||||
*/
|
||||
private String getNewAvailableRemotePath(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
|
||||
private String getNewAvailableRemotePath(OwnCloudClient client,
|
||||
String remotePath,
|
||||
List<String> fileNames,
|
||||
boolean encrypted) {
|
||||
int extPos = remotePath.lastIndexOf('.');
|
||||
String suffix;
|
||||
|
@ -1142,20 +1232,22 @@ public class UploadFileOperation extends SyncOperation {
|
|||
do {
|
||||
suffix = " (" + count + ")";
|
||||
newPath = extPos >= 0 ? remotePathWithoutExtension + suffix + "." + extension : remotePath + suffix;
|
||||
exists = existsFile(client, newPath, metadata, encrypted);
|
||||
exists = existsFile(client, newPath, fileNames, encrypted);
|
||||
count++;
|
||||
} while (exists);
|
||||
|
||||
return newPath;
|
||||
}
|
||||
|
||||
private boolean existsFile(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
|
||||
private boolean existsFile(OwnCloudClient client,
|
||||
String remotePath,
|
||||
List<String> fileNames,
|
||||
boolean encrypted) {
|
||||
if (encrypted) {
|
||||
String fileName = new File(remotePath).getName();
|
||||
|
||||
for (DecryptedFolderMetadata.DecryptedFile file : metadata.getFiles().values()) {
|
||||
if (file.getEncrypted().getFilename().equalsIgnoreCase(fileName)) {
|
||||
for (String name : fileNames) {
|
||||
if (name.equalsIgnoreCase(fileName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1169,9 +1261,8 @@ public class UploadFileOperation extends SyncOperation {
|
|||
}
|
||||
|
||||
/**
|
||||
* Allows to cancel the actual upload operation. If actual upload operating
|
||||
* is in progress it is cancelled, if upload preparation is being performed
|
||||
* upload will not take place.
|
||||
* Allows to cancel the actual upload operation. If actual upload operating is in progress it is cancelled, if
|
||||
* upload preparation is being performed upload will not take place.
|
||||
*/
|
||||
public void cancel(ResultCode cancellationReason) {
|
||||
if (mUploadOperation == null) {
|
||||
|
@ -1240,7 +1331,7 @@ public class UploadFileOperation extends SyncOperation {
|
|||
int nRead;
|
||||
byte[] buf = new byte[4096];
|
||||
while (!mCancellationRequested.get() &&
|
||||
(nRead = in.read(buf)) > -1) {
|
||||
(nRead = in.read(buf)) > -1) {
|
||||
out.write(buf, 0, nRead);
|
||||
}
|
||||
out.flush();
|
||||
|
@ -1259,7 +1350,7 @@ public class UploadFileOperation extends SyncOperation {
|
|||
}
|
||||
} catch (Exception e) {
|
||||
Log_OC.d(TAG, "Weird exception while closing input stream for " +
|
||||
mOriginalStoragePath + " (ignoring)", e);
|
||||
mOriginalStoragePath + " (ignoring)", e);
|
||||
}
|
||||
try {
|
||||
if (out != null) {
|
||||
|
@ -1267,7 +1358,7 @@ public class UploadFileOperation extends SyncOperation {
|
|||
}
|
||||
} catch (Exception e) {
|
||||
Log_OC.d(TAG, "Weird exception while closing output stream for " +
|
||||
targetFile.getAbsolutePath() + " (ignoring)", e);
|
||||
targetFile.getAbsolutePath() + " (ignoring)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1322,9 +1413,8 @@ public class UploadFileOperation extends SyncOperation {
|
|||
/**
|
||||
* Saves a OC File after a successful upload.
|
||||
* <p>
|
||||
* A PROPFIND is necessary to keep the props in the local database
|
||||
* synchronized with the server, specially the modification time and Etag
|
||||
* (where available)
|
||||
* A PROPFIND is necessary to keep the props in the local database synchronized with the server, specially the
|
||||
* modification time and Etag (where available)
|
||||
*/
|
||||
private void saveUploadedFile(OwnCloudClient client) {
|
||||
OCFile file = mFile;
|
||||
|
@ -1379,7 +1469,7 @@ public class UploadFileOperation extends SyncOperation {
|
|||
|
||||
// generate new Thumbnail
|
||||
final ThumbnailsCacheManager.ThumbnailGenerationTask task =
|
||||
new ThumbnailsCacheManager.ThumbnailGenerationTask(getStorageManager(), user);
|
||||
new ThumbnailsCacheManager.ThumbnailGenerationTask(getStorageManager(), user);
|
||||
task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, file.getRemoteId()));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Álvaro Brey
|
||||
* Copyright (C) 2023 Álvaro Brey
|
||||
* Copyright (C) 2023 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.owncloud.android.providers
|
||||
|
||||
/**
|
||||
* This is a data class that holds the configuration for the user and group searchable.
|
||||
* As we cannot access searchable providers in runtime, injecting a singleton into them is the only way to change their
|
||||
* config.
|
||||
*/
|
||||
data class UsersAndGroupsSearchConfig(var searchOnlyUsers: Boolean = false) {
|
||||
fun reset() {
|
||||
searchOnlyUsers = false
|
||||
}
|
||||
}
|
|
@ -34,6 +34,7 @@ import android.os.Looper;
|
|||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nextcloud.client.account.User;
|
||||
|
@ -116,6 +117,8 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
|||
|
||||
@Inject
|
||||
protected UserAccountManager accountManager;
|
||||
@Inject
|
||||
protected UsersAndGroupsSearchConfig searchConfig;
|
||||
|
||||
private static final Map<String, ShareType> sShareTypes = new HashMap<>();
|
||||
|
||||
|
@ -193,6 +196,10 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
|||
}
|
||||
|
||||
private Cursor searchForUsersOrGroups(Uri uri) {
|
||||
|
||||
// TODO check searchConfig and filter results
|
||||
Log.d(TAG, "searchForUsersOrGroups: searchConfig only users: " + searchConfig.getSearchOnlyUsers());
|
||||
|
||||
String lastPathSegment = uri.getLastPathSegment();
|
||||
|
||||
if (lastPathSegment == null) {
|
||||
|
@ -206,15 +213,14 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
|||
String userQuery = lastPathSegment.toLowerCase(Locale.ROOT);
|
||||
|
||||
// request to the OC server about users and groups matching userQuery
|
||||
GetShareesRemoteOperation searchRequest = new GetShareesRemoteOperation(userQuery, REQUESTED_PAGE,
|
||||
GetShareesRemoteOperation searchRequest = new GetShareesRemoteOperation(userQuery,
|
||||
REQUESTED_PAGE,
|
||||
RESULTS_PER_PAGE);
|
||||
RemoteOperationResult result = searchRequest.execute(user, getContext());
|
||||
RemoteOperationResult<ArrayList<JSONObject>> result = searchRequest.execute(user, getContext());
|
||||
List<JSONObject> names = new ArrayList<>();
|
||||
|
||||
if (result.isSuccess()) {
|
||||
for (Object o : result.getData()) {
|
||||
names.add((JSONObject) o);
|
||||
}
|
||||
names = result.getResultData();
|
||||
} else {
|
||||
showErrorMessage(result);
|
||||
}
|
||||
|
@ -272,6 +278,11 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
|||
status = new Status(StatusType.OFFLINE, "", "", -1);
|
||||
}
|
||||
|
||||
if (searchConfig.getSearchOnlyUsers() && type != ShareType.USER) {
|
||||
// skip all types but users, as E2E secure share is only allowed to users on same server
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case GROUP:
|
||||
displayName = userName;
|
||||
|
|
|
@ -44,6 +44,7 @@ import com.nextcloud.client.account.UserAccountManager;
|
|||
import com.nextcloud.java.util.Optional;
|
||||
import com.nextcloud.utils.extensions.IntentExtensionsKt;
|
||||
import com.owncloud.android.MainApp;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.lib.common.OwnCloudAccount;
|
||||
|
@ -140,6 +141,7 @@ public class OperationsService extends Service {
|
|||
mUndispatchedFinishedOperations = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject UserAccountManager accountManager;
|
||||
@Inject ArbitraryDataProvider arbitraryDataProvider;
|
||||
|
||||
private static class Target {
|
||||
public Uri mServerUrl;
|
||||
|
@ -610,7 +612,10 @@ public class OperationsService extends Service {
|
|||
sharePassword,
|
||||
expirationDateInMillis,
|
||||
hideFileDownload,
|
||||
fileDataStorageManager);
|
||||
fileDataStorageManager,
|
||||
getApplicationContext(),
|
||||
user,
|
||||
arbitraryDataProvider);
|
||||
|
||||
if (operationIntent.hasExtra(EXTRA_SHARE_PUBLIC_LABEL)) {
|
||||
createShareWithShareeOperation.setLabel(operationIntent.getStringExtra(EXTRA_SHARE_PUBLIC_LABEL));
|
||||
|
@ -654,7 +659,11 @@ public class OperationsService extends Service {
|
|||
shareId = operationIntent.getLongExtra(EXTRA_SHARE_ID, -1);
|
||||
|
||||
if (shareId > 0) {
|
||||
operation = new UnshareOperation(remotePath, shareId, fileDataStorageManager);
|
||||
operation = new UnshareOperation(remotePath,
|
||||
shareId,
|
||||
fileDataStorageManager,
|
||||
user,
|
||||
getApplicationContext());
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -81,6 +81,7 @@ import com.owncloud.android.operations.UpdateNoteForShareOperation;
|
|||
import com.owncloud.android.operations.UpdateShareInfoOperation;
|
||||
import com.owncloud.android.operations.UpdateSharePermissionsOperation;
|
||||
import com.owncloud.android.operations.UpdateShareViaLinkOperation;
|
||||
import com.owncloud.android.providers.UsersAndGroupsSearchConfig;
|
||||
import com.owncloud.android.providers.UsersAndGroupsSearchProvider;
|
||||
import com.owncloud.android.services.OperationsService;
|
||||
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
|
||||
|
@ -182,6 +183,12 @@ public abstract class FileActivity extends DrawerActivity
|
|||
@Inject
|
||||
EditorUtils editorUtils;
|
||||
|
||||
@Inject
|
||||
UsersAndGroupsSearchConfig usersAndGroupsSearchConfig;
|
||||
|
||||
@Inject
|
||||
ArbitraryDataProvider arbitraryDataProvider;
|
||||
|
||||
@Override
|
||||
public void showFiles(boolean onDeviceOnly) {
|
||||
// must be specialized in subclasses
|
||||
|
@ -203,6 +210,7 @@ public abstract class FileActivity extends DrawerActivity
|
|||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
usersAndGroupsSearchConfig.reset();
|
||||
mHandler = new Handler();
|
||||
mFileOperationsHelper = new FileOperationsHelper(this, getUserAccountManager(), connectivityService, editorUtils);
|
||||
User user = null;
|
||||
|
@ -907,7 +915,9 @@ public abstract class FileActivity extends DrawerActivity
|
|||
protected void doShareWith(String shareeName, ShareType shareType) {
|
||||
FileDetailFragment fragment = getFileDetailFragment();
|
||||
if (fragment != null) {
|
||||
fragment.initiateSharingProcess(shareeName, shareType);
|
||||
fragment.initiateSharingProcess(shareeName,
|
||||
shareType,
|
||||
usersAndGroupsSearchConfig.getSearchOnlyUsers());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -474,7 +474,7 @@ public class SettingsActivity extends PreferenceActivity
|
|||
}
|
||||
|
||||
private void setupE2EMnemonicPreference(PreferenceCategory preferenceCategoryMore) {
|
||||
String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
|
||||
String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC).trim();
|
||||
|
||||
Preference pMnemonic = findPreference("mnemonic");
|
||||
if (pMnemonic != null) {
|
||||
|
@ -991,7 +991,7 @@ public class SettingsActivity extends PreferenceActivity
|
|||
RequestCredentialsActivity.KEY_CHECK_RESULT_TRUE) {
|
||||
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(this);
|
||||
String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
|
||||
String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC).trim();
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.FallbackTheming_Dialog);
|
||||
AlertDialog alertDialog = builder.setTitle(R.string.prefs_e2e_mnemonic)
|
||||
|
|
|
@ -143,7 +143,8 @@ public class ShareActivity extends FileActivity {
|
|||
getSupportFragmentManager().beginTransaction().replace(R.id.share_fragment_container,
|
||||
FileDetailsSharingProcessFragment.newInstance(getFile(),
|
||||
shareeName,
|
||||
shareType),
|
||||
shareType,
|
||||
false),
|
||||
FileDetailsSharingProcessFragment.TAG)
|
||||
.commit();
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import com.nextcloud.ui.ImageDetailFragment;
|
|||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.ui.fragment.FileDetailActivitiesFragment;
|
||||
import com.owncloud.android.ui.fragment.FileDetailSharingFragment;
|
||||
import com.owncloud.android.utils.EncryptionUtils;
|
||||
import com.owncloud.android.utils.MimeTypeUtil;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -39,15 +38,20 @@ import androidx.fragment.app.FragmentStatePagerAdapter;
|
|||
public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
|
||||
private final OCFile file;
|
||||
private final User user;
|
||||
private final boolean showSharingTab;
|
||||
|
||||
private FileDetailSharingFragment fileDetailSharingFragment;
|
||||
private FileDetailActivitiesFragment fileDetailActivitiesFragment;
|
||||
private ImageDetailFragment imageDetailFragment;
|
||||
|
||||
public FileDetailTabAdapter(FragmentManager fm, OCFile file, User user) {
|
||||
public FileDetailTabAdapter(FragmentManager fm,
|
||||
OCFile file,
|
||||
User user,
|
||||
boolean showSharingTab) {
|
||||
super(fm);
|
||||
this.file = file;
|
||||
this.user = user;
|
||||
this.showSharingTab = showSharingTab;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -81,17 +85,16 @@ public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
|
|||
|
||||
@Override
|
||||
public int getCount() {
|
||||
if (file.isEncrypted()) {
|
||||
if (EncryptionUtils.supportsSecureFiledrop(file, user)) {
|
||||
if (showSharingTab) {
|
||||
if (MimeTypeUtil.isImage(file)) {
|
||||
return 3;
|
||||
}
|
||||
return 2;
|
||||
} else {
|
||||
if (MimeTypeUtil.isImage(file)) {
|
||||
return 2;
|
||||
}
|
||||
// sharing not allowed for encrypted files, thus only show first tab (activities)
|
||||
return 1;
|
||||
}
|
||||
// unencrypted files/folders
|
||||
if (MimeTypeUtil.isImage(file)) {
|
||||
return 3;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,12 +54,13 @@ import com.owncloud.android.databinding.GridItemBinding;
|
|||
import com.owncloud.android.databinding.ListFooterBinding;
|
||||
import com.owncloud.android.databinding.ListHeaderBinding;
|
||||
import com.owncloud.android.databinding.ListItemBinding;
|
||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.SyncedFolderProvider;
|
||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
|
||||
import com.owncloud.android.datamodel.VirtualFolderType;
|
||||
import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
|
||||
import com.owncloud.android.db.ProviderMeta;
|
||||
import com.owncloud.android.lib.common.OwnCloudClientFactory;
|
||||
import com.owncloud.android.lib.common.accounts.AccountUtils;
|
||||
|
@ -285,6 +286,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
|||
for (OCFile file : mFiles) {
|
||||
if (file.getRemoteId().equals(fileId)) {
|
||||
file.setEncrypted(encrypted);
|
||||
file.setE2eCounter(0L);
|
||||
mStorageManager.saveFile(file);
|
||||
|
||||
break;
|
||||
|
@ -294,6 +296,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
|||
for (OCFile file : mFilesAll) {
|
||||
if (file.getRemoteId().equals(fileId)) {
|
||||
file.setEncrypted(encrypted);
|
||||
file.setE2eCounter(0L);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -435,7 +438,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
|||
return;
|
||||
}
|
||||
|
||||
ocFileListDelegate.bindGridViewHolder(gridViewHolder, file, searchType);
|
||||
ocFileListDelegate.bindGridViewHolder(gridViewHolder, file, currentDirectory, searchType);
|
||||
checkVisibilityOfMoreButtons(gridViewHolder);
|
||||
checkVisibilityOfFileFeaturesLayout(gridViewHolder);
|
||||
|
||||
|
@ -890,19 +893,29 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
|||
|
||||
OCFile parentFolder = mStorageManager.getFileById(ocFile.getParentId());
|
||||
if (parentFolder != null && (ocFile.isEncrypted() || parentFolder.isEncrypted())) {
|
||||
DecryptedFolderMetadata metadata = RefreshFolderOperation.getDecryptedFolderMetadata(
|
||||
Object object = RefreshFolderOperation.getDecryptedFolderMetadata(
|
||||
true,
|
||||
parentFolder,
|
||||
OwnCloudClientFactory.createOwnCloudClient(user.toPlatformAccount(), activity),
|
||||
user,
|
||||
activity);
|
||||
|
||||
if (metadata == null) {
|
||||
if (object == null) {
|
||||
throw new IllegalStateException("metadata is null!");
|
||||
}
|
||||
|
||||
// update ocFile
|
||||
RefreshFolderOperation.updateFileNameForEncryptedFile(mStorageManager, metadata, ocFile);
|
||||
if (object instanceof DecryptedFolderMetadataFileV1) {
|
||||
// update ocFile
|
||||
RefreshFolderOperation.updateFileNameForEncryptedFileV1(mStorageManager,
|
||||
(DecryptedFolderMetadataFileV1) object,
|
||||
ocFile);
|
||||
} else {
|
||||
// update ocFile
|
||||
RefreshFolderOperation.updateFileNameForEncryptedFile(mStorageManager,
|
||||
(DecryptedFolderMetadataFile) object,
|
||||
ocFile);
|
||||
}
|
||||
|
||||
ocFile = mStorageManager.saveFileWithParent(ocFile, activity);
|
||||
}
|
||||
|
||||
|
|
|
@ -205,6 +205,7 @@ class OCFileListDelegate(
|
|||
fun bindGridViewHolder(
|
||||
gridViewHolder: ListGridImageViewHolder,
|
||||
file: OCFile,
|
||||
currentDirectory: OCFile?,
|
||||
searchType: SearchType?
|
||||
) {
|
||||
// thumbnail
|
||||
|
@ -250,8 +251,9 @@ class OCFileListDelegate(
|
|||
file.isEncrypted ||
|
||||
file.isEncrypted &&
|
||||
!EncryptionUtils.supportsSecureFiledrop(file, user) ||
|
||||
searchType == SearchType.FAVORITE_SEARCH
|
||||
)
|
||||
searchType == SearchType.FAVORITE_SEARCH ||
|
||||
file.isFolder && currentDirectory?.isEncrypted ?: false
|
||||
) // sharing an encrypted subfolder is not possible
|
||||
if (shouldHideShare) {
|
||||
gridViewHolder.shared.visibility = View.GONE
|
||||
} else {
|
||||
|
|
|
@ -45,12 +45,12 @@ import com.owncloud.android.datamodel.ArbitraryDataProvider
|
|||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
|
||||
import com.owncloud.android.lib.common.accounts.AccountUtils
|
||||
import com.owncloud.android.lib.common.utils.Log_OC
|
||||
import com.owncloud.android.lib.resources.users.DeletePublicKeyOperation
|
||||
import com.owncloud.android.lib.resources.users.GetPrivateKeyOperation
|
||||
import com.owncloud.android.lib.resources.users.GetPublicKeyOperation
|
||||
import com.owncloud.android.lib.resources.users.SendCSROperation
|
||||
import com.owncloud.android.lib.resources.users.StorePrivateKeyOperation
|
||||
import com.owncloud.android.utils.CsrHelper
|
||||
import com.owncloud.android.lib.resources.e2ee.CsrHelper
|
||||
import com.owncloud.android.lib.resources.users.DeletePublicKeyRemoteOperation
|
||||
import com.owncloud.android.lib.resources.users.GetPrivateKeyRemoteOperation
|
||||
import com.owncloud.android.lib.resources.users.GetPublicKeyRemoteOperation
|
||||
import com.owncloud.android.lib.resources.users.SendCSRRemoteOperation
|
||||
import com.owncloud.android.lib.resources.users.StorePrivateKeyRemoteOperation
|
||||
import com.owncloud.android.utils.EncryptionUtils
|
||||
import com.owncloud.android.utils.theme.ViewThemeUtils
|
||||
import java.io.IOException
|
||||
|
@ -175,7 +175,7 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
|||
|
||||
try {
|
||||
val privateKey = task?.get()
|
||||
val mnemonicUnchanged = binding.encryptionPasswordInput.text.toString()
|
||||
val mnemonicUnchanged = binding.encryptionPasswordInput.text.toString().trim()
|
||||
val mnemonic =
|
||||
binding.encryptionPasswordInput.text.toString().replace("\\s".toRegex(), "")
|
||||
.lowercase()
|
||||
|
@ -294,11 +294,11 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
|||
// if available
|
||||
// - store public key
|
||||
// - decrypt private key, store unencrypted private key in database
|
||||
val context = mWeakContext.get()
|
||||
val publicKeyOperation = GetPublicKeyOperation()
|
||||
val context = mWeakContext.get() ?: return null
|
||||
val publicKeyOperation = GetPublicKeyRemoteOperation()
|
||||
val user = user ?: return null
|
||||
|
||||
val publicKeyResult = publicKeyOperation.execute(user, context)
|
||||
val publicKeyResult = publicKeyOperation.executeNextcloudClient(user, context)
|
||||
|
||||
if (publicKeyResult.isSuccess) {
|
||||
Log_OC.d(TAG, "public key successful downloaded for " + user.accountName)
|
||||
|
@ -317,7 +317,7 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
|||
return null
|
||||
}
|
||||
|
||||
val privateKeyResult = GetPrivateKeyOperation().execute(user, context)
|
||||
val privateKeyResult = GetPrivateKeyRemoteOperation().executeNextcloudClient(user, context)
|
||||
if (privateKeyResult.isSuccess) {
|
||||
Log_OC.d(TAG, "private key successful downloaded for " + user!!.accountName)
|
||||
keyResult = KEY_EXISTING_USED
|
||||
|
@ -387,6 +387,11 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
|||
val context = mWeakContext.get()
|
||||
val publicKeyString: String
|
||||
|
||||
if (context == null) {
|
||||
keyResult = KEY_FAILED
|
||||
return ""
|
||||
}
|
||||
|
||||
// Create public/private key pair
|
||||
val keyPair = EncryptionUtils.generateKeyPair()
|
||||
|
||||
|
@ -395,12 +400,12 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
|||
val user = user ?: return ""
|
||||
|
||||
val userId = accountManager.getUserData(user.toPlatformAccount(), AccountUtils.Constants.KEY_USER_ID)
|
||||
val urlEncoded = CsrHelper.generateCsrPemEncodedString(keyPair, userId)
|
||||
val operation = SendCSROperation(urlEncoded)
|
||||
val result = operation.execute(user, context)
|
||||
val urlEncoded = CsrHelper().generateCsrPemEncodedString(keyPair, userId)
|
||||
val operation = SendCSRRemoteOperation(urlEncoded)
|
||||
val result = operation.executeNextcloudClient(user, context)
|
||||
|
||||
if (result.isSuccess) {
|
||||
publicKeyString = result.data[0] as String
|
||||
publicKeyString = result.resultData
|
||||
if (!EncryptionUtils.isMatchingKeys(keyPair, publicKeyString)) {
|
||||
EncryptionUtils.reportE2eError(arbitraryDataProvider, user)
|
||||
throw RuntimeException("Wrong CSR returned")
|
||||
|
@ -420,8 +425,8 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
|||
)
|
||||
|
||||
// upload encryptedPrivateKey
|
||||
val storePrivateKeyOperation = StorePrivateKeyOperation(encryptedPrivateKey)
|
||||
val storePrivateKeyResult = storePrivateKeyOperation.execute(user, context)
|
||||
val storePrivateKeyOperation = StorePrivateKeyRemoteOperation(encryptedPrivateKey)
|
||||
val storePrivateKeyResult = storePrivateKeyOperation.executeNextcloudClient(user, context)
|
||||
if (storePrivateKeyResult.isSuccess) {
|
||||
Log_OC.d(TAG, "private key success")
|
||||
arbitraryDataProvider?.storeOrUpdateKeyValue(
|
||||
|
@ -441,10 +446,10 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
|||
)
|
||||
keyResult = KEY_CREATED
|
||||
|
||||
return storePrivateKeyResult.data[0] as String
|
||||
return storePrivateKeyResult.resultData
|
||||
} else {
|
||||
val deletePublicKeyOperation = DeletePublicKeyOperation()
|
||||
deletePublicKeyOperation.execute(user, context)
|
||||
val deletePublicKeyOperation = DeletePublicKeyRemoteOperation()
|
||||
deletePublicKeyOperation.executeNextcloudClient(user, context)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log_OC.e(TAG, e.message)
|
||||
|
|
|
@ -133,11 +133,11 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
|
|||
* @param user Currently active user
|
||||
* @return New fragment with arguments set
|
||||
*/
|
||||
public static FileDetailFragment newInstance(OCFile fileToDetail, OCFile parentFile, User user) {
|
||||
public static FileDetailFragment newInstance(OCFile fileToDetail, OCFile parentFolder, User user) {
|
||||
FileDetailFragment frag = new FileDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_FILE, fileToDetail);
|
||||
args.putParcelable(ARG_PARENT_FOLDER, parentFile);
|
||||
args.putParcelable(ARG_PARENT_FOLDER, parentFolder);
|
||||
args.putParcelable(ARG_USER, user);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
|
@ -304,7 +304,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
|
|||
binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.drawer_item_activities).setIcon(R.drawable.ic_activity));
|
||||
|
||||
|
||||
if (!getFile().isEncrypted() || EncryptionUtils.supportsSecureFiledrop(getFile(), user)) {
|
||||
if (showSharingTab()) {
|
||||
binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.share_dialog_title).setIcon(R.drawable.shared_via_users));
|
||||
}
|
||||
|
||||
|
@ -314,7 +314,10 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
|
|||
|
||||
viewThemeUtils.material.themeTabLayout(binding.tabLayout);
|
||||
|
||||
final FileDetailTabAdapter adapter = new FileDetailTabAdapter(getFragmentManager(), getFile(), user);
|
||||
final FileDetailTabAdapter adapter = new FileDetailTabAdapter(getFragmentManager(),
|
||||
getFile(),
|
||||
user,
|
||||
showSharingTab());
|
||||
binding.pager.setAdapter(adapter);
|
||||
binding.pager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.tabLayout) {
|
||||
@Override
|
||||
|
@ -733,11 +736,14 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
|
|||
* @param shareeName
|
||||
* @param shareType
|
||||
*/
|
||||
public void initiateSharingProcess(String shareeName, ShareType shareType) {
|
||||
public void initiateSharingProcess(String shareeName,
|
||||
ShareType shareType,
|
||||
boolean secureShare) {
|
||||
requireActivity().getSupportFragmentManager().beginTransaction().add(R.id.sharing_frame_container,
|
||||
FileDetailsSharingProcessFragment.newInstance(getFile(),
|
||||
shareeName,
|
||||
shareType),
|
||||
shareType,
|
||||
secureShare),
|
||||
FileDetailsSharingProcessFragment.TAG)
|
||||
.commit();
|
||||
|
||||
|
@ -801,6 +807,24 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
|
|||
}
|
||||
}
|
||||
|
||||
private boolean showSharingTab() {
|
||||
if (getFile().isEncrypted()) {
|
||||
if (parentFolder == null) {
|
||||
parentFolder = storageManager.getFileById(getFile().getParentId());
|
||||
}
|
||||
if (EncryptionUtils.supportsSecureFiledrop(getFile(), user) && !parentFolder.isEncrypted()) {
|
||||
return true;
|
||||
} else {
|
||||
// sharing not allowed for encrypted files, thus only show first tab (activities)
|
||||
// sharing not allowed for encrypted subfolders
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// unencrypted files/folders
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class responsible for updating the progress bar shown for file downloading.
|
||||
*/
|
||||
|
|
|
@ -59,6 +59,7 @@ import com.owncloud.android.lib.resources.shares.ShareType;
|
|||
import com.owncloud.android.lib.resources.status.NextcloudVersion;
|
||||
import com.owncloud.android.lib.resources.status.OCCapability;
|
||||
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
|
||||
import com.owncloud.android.providers.UsersAndGroupsSearchConfig;
|
||||
import com.owncloud.android.ui.activity.FileActivity;
|
||||
import com.owncloud.android.ui.activity.FileDisplayActivity;
|
||||
import com.owncloud.android.ui.adapter.ShareeListAdapter;
|
||||
|
@ -108,6 +109,7 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
|||
@Inject UserAccountManager accountManager;
|
||||
@Inject ClientFactory clientFactory;
|
||||
@Inject ViewThemeUtils viewThemeUtils;
|
||||
@Inject UsersAndGroupsSearchConfig searchConfig;
|
||||
|
||||
public static FileDetailSharingFragment newInstance(OCFile file, User user) {
|
||||
FileDetailSharingFragment fragment = new FileDetailSharingFragment();
|
||||
|
@ -204,26 +206,52 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
searchConfig.setSearchOnlyUsers(file.isEncrypted());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
searchConfig.reset();
|
||||
}
|
||||
|
||||
private void setupView() {
|
||||
setShareWithYou();
|
||||
|
||||
if (file.isEncrypted()) {
|
||||
binding.searchContainer.setVisibility(View.GONE);
|
||||
} else {
|
||||
FileDetailSharingFragmentHelper.setupSearchView(
|
||||
(SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE),
|
||||
binding.searchView,
|
||||
fileActivity.getComponentName());
|
||||
viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView);
|
||||
OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId());
|
||||
|
||||
if (file.canReshare()) {
|
||||
binding.searchView.setQueryHint(getResources().getString(R.string.share_search));
|
||||
FileDetailSharingFragmentHelper.setupSearchView(
|
||||
(SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE),
|
||||
binding.searchView,
|
||||
fileActivity.getComponentName());
|
||||
viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView);
|
||||
|
||||
|
||||
if (file.canReshare()) {
|
||||
if (file.isEncrypted() || (parentFile != null && parentFile.isEncrypted())) {
|
||||
if (file.getE2eCounter() == -1) {
|
||||
// V1 cannot share
|
||||
binding.searchContainer.setVisibility(View.GONE);
|
||||
} else {
|
||||
binding.searchView.setQueryHint(getResources().getString(R.string.secure_share_search));
|
||||
|
||||
if (file.isSharedViaLink()) {
|
||||
binding.searchView.setQueryHint(getResources().getString(R.string.share_not_allowed_when_file_drop));
|
||||
binding.searchView.setInputType(InputType.TYPE_NULL);
|
||||
disableSearchView(binding.searchView);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.searchView.setQueryHint(getResources().getString(R.string.reshare_not_allowed));
|
||||
binding.searchView.setInputType(InputType.TYPE_NULL);
|
||||
binding.pickContactEmailBtn.setVisibility(View.GONE);
|
||||
disableSearchView(binding.searchView);
|
||||
binding.searchView.setQueryHint(getResources().getString(R.string.share_search));
|
||||
}
|
||||
} else {
|
||||
binding.searchView.setQueryHint(getResources().getString(R.string.reshare_not_allowed));
|
||||
binding.searchView.setInputType(InputType.TYPE_NULL);
|
||||
binding.pickContactEmailBtn.setVisibility(View.GONE);
|
||||
disableSearchView(binding.searchView);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,6 +452,7 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
|||
* before reading database.
|
||||
*/
|
||||
public void refreshSharesFromDB() {
|
||||
file = fileDataStorageManager.getFileById(file.getFileId());
|
||||
ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter();
|
||||
|
||||
if (adapter == null) {
|
||||
|
|
|
@ -72,6 +72,7 @@ class FileDetailsSharingProcessFragment :
|
|||
private const val ARG_SCREEN_TYPE = "arg_screen_type"
|
||||
private const val ARG_RESHARE_SHOWN = "arg_reshare_shown"
|
||||
private const val ARG_EXP_DATE_SHOWN = "arg_exp_date_shown"
|
||||
private const val ARG_SECURE_SHARE = "secure_share"
|
||||
|
||||
// types of screens to be displayed
|
||||
const val SCREEN_TYPE_PERMISSION = 1 // permissions screen
|
||||
|
@ -81,11 +82,17 @@ class FileDetailsSharingProcessFragment :
|
|||
* fragment instance to be called while creating new share for internal and external share
|
||||
*/
|
||||
@JvmStatic
|
||||
fun newInstance(file: OCFile, shareeName: String, shareType: ShareType): FileDetailsSharingProcessFragment {
|
||||
fun newInstance(
|
||||
file: OCFile,
|
||||
shareeName: String,
|
||||
shareType: ShareType,
|
||||
secureShare: Boolean
|
||||
): FileDetailsSharingProcessFragment {
|
||||
val args = Bundle()
|
||||
args.putParcelable(ARG_OCFILE, file)
|
||||
args.putSerializable(ARG_SHARE_TYPE, shareType)
|
||||
args.putString(ARG_SHAREE_NAME, shareeName)
|
||||
args.putBoolean(ARG_SECURE_SHARE, secureShare)
|
||||
val fragment = FileDetailsSharingProcessFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
|
@ -127,6 +134,7 @@ class FileDetailsSharingProcessFragment :
|
|||
private var share: OCShare? = null
|
||||
private var isReShareShown: Boolean = true // show or hide reShare option
|
||||
private var isExpDateShown: Boolean = true // show or hide expiry date option
|
||||
private var isSecureShare: Boolean = false
|
||||
|
||||
private var expirationDatePickerFragment: ExpirationDatePickerDialogFragment? = null
|
||||
|
||||
|
@ -156,6 +164,7 @@ class FileDetailsSharingProcessFragment :
|
|||
shareProcessStep = it.getInt(ARG_SCREEN_TYPE, SCREEN_TYPE_PERMISSION)
|
||||
isReShareShown = it.getBoolean(ARG_RESHARE_SHOWN, true)
|
||||
isExpDateShown = it.getBoolean(ARG_EXP_DATE_SHOWN, true)
|
||||
isSecureShare = it.getBoolean(ARG_SECURE_SHARE, false)
|
||||
}
|
||||
|
||||
fileActivity = activity as FileActivity?
|
||||
|
@ -222,8 +231,22 @@ class FileDetailsSharingProcessFragment :
|
|||
binding.shareProcessEditShareLink.visibility = View.VISIBLE
|
||||
binding.shareProcessGroupTwo.visibility = View.GONE
|
||||
|
||||
if (share != null) setupModificationUI() else setupUpdateUI()
|
||||
binding.shareProcessSetExpDateSwitch.visibility = if (isExpDateShown) View.VISIBLE else View.GONE
|
||||
if (share != null) {
|
||||
setupModificationUI()
|
||||
} else {
|
||||
setupUpdateUI()
|
||||
}
|
||||
|
||||
if (isSecureShare) {
|
||||
binding.shareProcessAdvancePermissionTitle.visibility = View.GONE
|
||||
}
|
||||
|
||||
// show or hide expiry date
|
||||
if (isExpDateShown && !isSecureShare) {
|
||||
binding.shareProcessSetExpDateSwitch.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.shareProcessSetExpDateSwitch.visibility = View.GONE
|
||||
}
|
||||
shareProcessStep = SCREEN_TYPE_PERMISSION
|
||||
}
|
||||
|
||||
|
@ -310,7 +333,11 @@ class FileDetailsSharingProcessFragment :
|
|||
binding.shareProcessChangeNameSwitch.visibility = View.GONE
|
||||
binding.shareProcessChangeNameContainer.visibility = View.GONE
|
||||
binding.shareProcessHideDownloadCheckbox.visibility = View.GONE
|
||||
binding.shareProcessAllowResharingCheckbox.visibility = View.VISIBLE
|
||||
if (isSecureShare) {
|
||||
binding.shareProcessAllowResharingCheckbox.visibility = View.GONE
|
||||
} else {
|
||||
binding.shareProcessAllowResharingCheckbox.visibility = View.VISIBLE
|
||||
}
|
||||
binding.shareProcessSetPasswordSwitch.visibility = View.GONE
|
||||
|
||||
if (share != null) {
|
||||
|
@ -367,6 +394,11 @@ class FileDetailsSharingProcessFragment :
|
|||
binding.shareProcessPermissionUploadEditing.text =
|
||||
requireContext().resources.getString(R.string.link_share_allow_upload_and_editing)
|
||||
binding.shareProcessPermissionFileDrop.visibility = View.VISIBLE
|
||||
if (isSecureShare) {
|
||||
binding.shareProcessPermissionFileDrop.visibility = View.GONE
|
||||
binding.shareProcessAllowResharingCheckbox.visibility = View.GONE
|
||||
binding.shareProcessSetExpDateSwitch.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -569,7 +601,8 @@ class FileDetailsSharingProcessFragment :
|
|||
binding.shareProcessEnterPassword.text.toString().trim(),
|
||||
chosenExpDateInMills,
|
||||
noteText,
|
||||
binding.shareProcessChangeName.text.toString().trim()
|
||||
binding.shareProcessChangeName.text.toString().trim(),
|
||||
true
|
||||
)
|
||||
}
|
||||
removeCurrentFragment()
|
||||
|
|
|
@ -73,12 +73,11 @@ import com.nextcloud.utils.view.FastScrollUtils;
|
|||
import com.owncloud.android.MainApp;
|
||||
import com.owncloud.android.R;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.SyncedFolderProvider;
|
||||
import com.owncloud.android.datamodel.VirtualFolderType;
|
||||
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
|
||||
import com.owncloud.android.lib.common.Creator;
|
||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperation;
|
||||
|
@ -87,6 +86,7 @@ import com.owncloud.android.lib.common.utils.Log_OC;
|
|||
import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.files.ToggleFavoriteRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.status.E2EVersion;
|
||||
import com.owncloud.android.lib.resources.status.OCCapability;
|
||||
import com.owncloud.android.ui.activity.FileActivity;
|
||||
import com.owncloud.android.ui.activity.FileDisplayActivity;
|
||||
|
@ -116,6 +116,7 @@ import com.owncloud.android.ui.preview.PreviewMediaActivity;
|
|||
import com.owncloud.android.ui.preview.PreviewTextFileFragment;
|
||||
import com.owncloud.android.utils.DisplayUtils;
|
||||
import com.owncloud.android.utils.EncryptionUtils;
|
||||
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||
import com.owncloud.android.utils.FileSortOrder;
|
||||
import com.owncloud.android.utils.FileStorageUtils;
|
||||
import com.owncloud.android.utils.MimeTypeUtil;
|
||||
|
@ -1712,13 +1713,15 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
|||
dialog.setTargetFragment(this, SETUP_ENCRYPTION_REQUEST_CODE);
|
||||
dialog.show(getParentFragmentManager(), SETUP_ENCRYPTION_DIALOG_TAG);
|
||||
} else {
|
||||
// TODO E2E: if encryption fails, to not set it as encrypted!
|
||||
encryptFolder(file,
|
||||
event.getLocalId(),
|
||||
event.getRemoteId(),
|
||||
event.getRemotePath(),
|
||||
event.getShouldBeEncrypted(),
|
||||
publicKey,
|
||||
privateKey);
|
||||
privateKey,
|
||||
storageManager);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1727,9 +1730,11 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
|||
String remoteId,
|
||||
String remotePath,
|
||||
boolean shouldBeEncrypted,
|
||||
String publicKey,
|
||||
String privateKey) {
|
||||
String publicKeyString,
|
||||
String privateKeyString,
|
||||
FileDataStorageManager storageManager) {
|
||||
try {
|
||||
Log_OC.d(TAG, "encrypt folder " + folder.getRemoteId());
|
||||
User user = accountManager.getUser();
|
||||
OwnCloudClient client = clientFactory.create(user);
|
||||
RemoteOperationResult remoteOperationResult = new ToggleEncryptionRemoteOperation(localId,
|
||||
|
@ -1741,44 +1746,44 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
|||
// lock folder
|
||||
String token = EncryptionUtils.lockFolder(folder, client);
|
||||
|
||||
// Update metadata
|
||||
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(folder,
|
||||
client,
|
||||
privateKey,
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user);
|
||||
OCCapability ocCapability = mContainerActivity.getStorageManager().getCapability(user.getAccountName());
|
||||
|
||||
boolean metadataExists = metadataPair.first;
|
||||
DecryptedFolderMetadata metadata = metadataPair.second;
|
||||
if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V2_0) {
|
||||
// Update metadata
|
||||
Pair<Boolean, DecryptedFolderMetadataFile> metadataPair = EncryptionUtils.retrieveMetadata(folder,
|
||||
client,
|
||||
privateKeyString,
|
||||
publicKeyString,
|
||||
storageManager,
|
||||
user,
|
||||
requireContext(),
|
||||
arbitraryDataProvider);
|
||||
|
||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folder.getLocalId());
|
||||
boolean metadataExists = metadataPair.first;
|
||||
DecryptedFolderMetadataFile metadata = metadataPair.second;
|
||||
|
||||
String serializedFolderMetadata;
|
||||
new EncryptionUtilsV2().serializeAndUploadMetadata(folder,
|
||||
metadata,
|
||||
token,
|
||||
client,
|
||||
metadataExists,
|
||||
requireContext(),
|
||||
user,
|
||||
storageManager);
|
||||
|
||||
// check if we need metadataKeys
|
||||
if (metadata.getMetadata().getMetadataKey() != null) {
|
||||
serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata, true);
|
||||
} else {
|
||||
serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
||||
// unlock folder
|
||||
EncryptionUtils.unlockFolder(folder, client, token);
|
||||
|
||||
} else if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V1_0 ||
|
||||
ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V1_1 ||
|
||||
ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V1_2
|
||||
) {
|
||||
// unlock folder
|
||||
EncryptionUtils.unlockFolderV1(folder, client, token);
|
||||
} else if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.UNKNOWN) {
|
||||
throw new IllegalArgumentException("Unknown E2E version");
|
||||
}
|
||||
|
||||
// upload metadata
|
||||
EncryptionUtils.uploadMetadata(folder,
|
||||
serializedFolderMetadata,
|
||||
token,
|
||||
client,
|
||||
metadataExists,
|
||||
arbitraryDataProvider,
|
||||
user);
|
||||
|
||||
// unlock folder
|
||||
EncryptionUtils.unlockFolder(folder, client, token);
|
||||
|
||||
mAdapter.setEncryptionAttributeForItemID(remoteId, shouldBeEncrypted);
|
||||
} else if (remoteOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
|
||||
Snackbar.make(getRecyclerView(),
|
||||
|
@ -1790,7 +1795,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
|||
Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
Log_OC.e(TAG, "Error creating encrypted folder", e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -568,13 +568,22 @@ public class FileOperationsHelper {
|
|||
* @param note note message for the receiver. Null or empty for no message
|
||||
* @param label new label
|
||||
*/
|
||||
public void shareFileWithSharee(OCFile file, String shareeName, ShareType shareType, int permissions,
|
||||
boolean hideFileDownload, String password, long expirationTimeInMillis,
|
||||
String note, String label) {
|
||||
public void shareFileWithSharee(OCFile file,
|
||||
String shareeName,
|
||||
ShareType shareType,
|
||||
int permissions,
|
||||
boolean hideFileDownload,
|
||||
String password,
|
||||
long expirationTimeInMillis,
|
||||
String note,
|
||||
String label,
|
||||
boolean showLoadingDialog) {
|
||||
if (file != null) {
|
||||
// TODO check capability?
|
||||
fileActivity.showLoadingDialog(fileActivity.getApplicationContext().
|
||||
getString(R.string.wait_a_moment));
|
||||
if (showLoadingDialog) {
|
||||
fileActivity.showLoadingDialog(fileActivity.getApplicationContext().
|
||||
getString(R.string.wait_a_moment));
|
||||
}
|
||||
|
||||
Intent service = new Intent(fileActivity, OperationsService.class);
|
||||
service.setAction(OperationsService.ACTION_CREATE_SHARE_WITH_SHAREE);
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
package com.owncloud.android.utils;
|
||||
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
import org.bouncycastle.crypto.util.PrivateKeyFactory;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
|
||||
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* copied & modified from:
|
||||
* https://github.com/awslabs/aws-sdk-android-samples/blob/master/CreateIotCertWithCSR/src/com/amazonaws/demo/csrcert/CsrHelper.java
|
||||
* accessed at 31.08.17
|
||||
* Original parts are licensed under the Apache License, Version 2.0: http://aws.amazon.com/apache2.0
|
||||
* Own parts are licensed under GPLv3+.
|
||||
*/
|
||||
|
||||
public final class CsrHelper {
|
||||
|
||||
private CsrHelper() {
|
||||
// utility class -> private constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate CSR with PEM encoding
|
||||
*
|
||||
* @param keyPair the KeyPair with private and public keys
|
||||
* @param userId userId of CSR owner
|
||||
* @return PEM encoded CSR string
|
||||
* @throws IOException thrown if key cannot be created
|
||||
* @throws OperatorCreationException thrown if contentSigner cannot be build
|
||||
*/
|
||||
public static String generateCsrPemEncodedString(KeyPair keyPair, String userId)
|
||||
throws IOException, OperatorCreationException {
|
||||
PKCS10CertificationRequest csr = CsrHelper.generateCSR(keyPair, userId);
|
||||
byte[] derCSR = csr.getEncoded();
|
||||
return "-----BEGIN CERTIFICATE REQUEST-----\n" + android.util.Base64.encodeToString(derCSR,
|
||||
android.util.Base64.NO_WRAP) + "\n-----END CERTIFICATE REQUEST-----";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the certificate signing request (CSR) from private and public keys
|
||||
*
|
||||
* @param keyPair the KeyPair with private and public keys
|
||||
* @param userId userId of CSR owner
|
||||
* @return PKCS10CertificationRequest with the certificate signing request (CSR) data
|
||||
* @throws IOException thrown if key cannot be created
|
||||
* @throws OperatorCreationException thrown if contentSigner cannot be build
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static PKCS10CertificationRequest generateCSR(KeyPair keyPair, String userId) throws IOException,
|
||||
OperatorCreationException {
|
||||
String principal = "CN=" + userId;
|
||||
AsymmetricKeyParameter privateKey = PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
|
||||
AlgorithmIdentifier signatureAlgorithm = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1WITHRSA");
|
||||
AlgorithmIdentifier digestAlgorithm = new DefaultDigestAlgorithmIdentifierFinder().find("SHA-1");
|
||||
ContentSigner signer = new BcRSAContentSignerBuilder(signatureAlgorithm, digestAlgorithm).build(privateKey);
|
||||
|
||||
PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name(principal),
|
||||
keyPair.getPublic());
|
||||
ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator();
|
||||
extensionsGenerator.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
|
||||
csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensionsGenerator.generate());
|
||||
|
||||
return csrBuilder.build(signer);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
1116
app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt
Normal file
1116
app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt
Normal file
File diff suppressed because it is too large
Load diff
|
@ -510,6 +510,7 @@
|
|||
<string name="share_via_link_unset_password">Unset</string>
|
||||
|
||||
<string name="share_search">Name, Federated Cloud ID or email address …</string>
|
||||
<string name="secure_share_search">Secure share …</string>
|
||||
|
||||
<string name="share_group_clarification">%1$s (group)</string>
|
||||
<string name="share_remote_clarification">%1$s (remote)</string>
|
||||
|
@ -1125,4 +1126,6 @@
|
|||
<string name="sub_folder_rule_year">Year</string>
|
||||
<string name="sub_folder_rule_month">Year/Month</string>
|
||||
<string name="sub_folder_rule_day">Year/Month/Day</string>
|
||||
<string name="secure_share_not_set_up">Secure sharing is not set up for this user</string>
|
||||
<string name="share_not_allowed_when_file_drop">Resharing is not allowed during secure file drop</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue