mirror of
https://github.com/nextcloud/android.git
synced 2024-11-21 20:55:31 +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">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="ktlint" />
|
<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="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
<inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" />
|
<inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
</profile>
|
</profile>
|
||||||
|
|
|
@ -266,7 +266,7 @@ dependencies {
|
||||||
implementation 'org.greenrobot:eventbus:3.3.1'
|
implementation 'org.greenrobot:eventbus:3.3.1'
|
||||||
implementation 'com.googlecode.ez-vcard:ez-vcard:0.12.0'
|
implementation 'com.googlecode.ez-vcard:ez-vcard:0.12.0'
|
||||||
implementation 'org.lukhnos:nnio:0.2'
|
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.google.code.gson:gson:2.10.1'
|
||||||
implementation 'com.github.nextcloud-deps:sectioned-recyclerview:0.6.1'
|
implementation 'com.github.nextcloud-deps:sectioned-recyclerview:0.6.1'
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
<issue id="TrustAllX509TrustManager">
|
<issue id="TrustAllX509TrustManager">
|
||||||
<ignore path="**/bouncycastle/est/jcajce/*.class" />
|
<ignore path="**/bouncycastle/est/jcajce/*.class" />
|
||||||
<ignore path="**/bcpkix-jdk15to18-1.72.jar" />
|
<ignore path="**/bcpkix-jdk15to18-1.72.jar" />
|
||||||
|
<ignore path="**/bcpkix-jdk18on-1.75.jar" />
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue id="RestrictedApi" severity="error">
|
<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.common.NextcloudClient;
|
||||||
import com.nextcloud.java.util.Optional;
|
import com.nextcloud.java.util.Optional;
|
||||||
import com.nextcloud.test.GrantStoragePermissionRule;
|
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.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
import com.owncloud.android.datamodel.OCFile;
|
||||||
import com.owncloud.android.datamodel.UploadsStorageManager;
|
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.OwnCloudClientFactory;
|
||||||
import com.owncloud.android.lib.common.accounts.AccountUtils;
|
import com.owncloud.android.lib.common.accounts.AccountUtils;
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
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.CapabilityBooleanType;
|
||||||
import com.owncloud.android.lib.resources.status.GetCapabilitiesRemoteOperation;
|
import com.owncloud.android.lib.resources.status.GetCapabilitiesRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.status.OCCapability;
|
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.operations.UploadFileOperation;
|
||||||
import com.owncloud.android.utils.FileStorageUtils;
|
import com.owncloud.android.utils.FileStorageUtils;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -100,6 +102,8 @@ public abstract class AbstractIT {
|
||||||
protected FileDataStorageManager fileDataStorageManager =
|
protected FileDataStorageManager fileDataStorageManager =
|
||||||
new FileDataStorageManager(user, targetContext.getContentResolver());
|
new FileDataStorageManager(user, targetContext.getContentResolver());
|
||||||
|
|
||||||
|
protected ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void beforeAll() {
|
public static void beforeAll() {
|
||||||
try {
|
try {
|
||||||
|
@ -118,14 +122,11 @@ public abstract class AbstractIT {
|
||||||
|
|
||||||
client = OwnCloudClientFactory.createOwnCloudClient(account, targetContext);
|
client = OwnCloudClientFactory.createOwnCloudClient(account, targetContext);
|
||||||
nextcloudClient = OwnCloudClientFactory.createNextcloudClient(user, targetContext);
|
nextcloudClient = OwnCloudClientFactory.createNextcloudClient(user, targetContext);
|
||||||
} catch (OperationCanceledException e) {
|
} catch (OperationCanceledException |
|
||||||
e.printStackTrace();
|
IOException |
|
||||||
} catch (AuthenticatorException e) {
|
AccountUtils.AccountNotFoundException |
|
||||||
e.printStackTrace();
|
AuthenticatorException e) {
|
||||||
} catch (IOException e) {
|
throw new RuntimeException("Error setting up clients", e);
|
||||||
e.printStackTrace();
|
|
||||||
} catch (AccountUtils.AccountNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bundle arguments = androidx.test.platform.app.InstrumentationRegistry.getArguments();
|
Bundle arguments = androidx.test.platform.app.InstrumentationRegistry.getArguments();
|
||||||
|
@ -334,11 +335,15 @@ public abstract class AbstractIT {
|
||||||
}
|
}
|
||||||
|
|
||||||
public OCFile createFolder(String remotePath) {
|
public OCFile createFolder(String remotePath) {
|
||||||
TestCase.assertTrue(new CreateFolderOperation(remotePath, user, targetContext, getStorageManager())
|
RemoteOperationResult check = new ExistenceCheckRemoteOperation(remotePath, false).execute(client);
|
||||||
|
|
||||||
|
if (!check.isSuccess()) {
|
||||||
|
assertTrue(new CreateFolderOperation(remotePath, user, targetContext, getStorageManager())
|
||||||
.execute(client)
|
.execute(client)
|
||||||
.isSuccess());
|
.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
return getStorageManager().getFileByDecryptedRemotePath(remotePath);
|
return getStorageManager().getFileByDecryptedRemotePath(remotePath.endsWith("/") ? remotePath : remotePath + "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void uploadFile(File file, String 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);
|
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) {
|
protected static User getUser(Account account) {
|
||||||
Optional<User> optionalUser = UserAccountManagerImpl.fromContext(targetContext).getUser(account.name);
|
Optional<User> optionalUser = UserAccountManagerImpl.fromContext(targetContext).getUser(account.name);
|
||||||
return optionalUser.orElseThrow(IllegalAccessError::new);
|
return optionalUser.orElseThrow(IllegalAccessError::new);
|
||||||
|
|
|
@ -94,21 +94,19 @@ public abstract class AbstractOnServerIT extends AbstractIT {
|
||||||
user = optionalUser.orElseThrow(IllegalAccessError::new);
|
user = optionalUser.orElseThrow(IllegalAccessError::new);
|
||||||
|
|
||||||
client = OwnCloudClientFactory.createOwnCloudClient(account, targetContext);
|
client = OwnCloudClientFactory.createOwnCloudClient(account, targetContext);
|
||||||
|
nextcloudClient = OwnCloudClientFactory.createNextcloudClient(user, targetContext);
|
||||||
|
|
||||||
createDummyFiles();
|
createDummyFiles();
|
||||||
|
|
||||||
waitForServer(client, baseUrl);
|
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) {
|
} catch (OperationCanceledException |
|
||||||
e.printStackTrace();
|
IOException |
|
||||||
} catch (AuthenticatorException e) {
|
AccountUtils.AccountNotFoundException |
|
||||||
e.printStackTrace();
|
AuthenticatorException e) {
|
||||||
} catch (IOException e) {
|
throw new RuntimeException("Error setting up clients", e);
|
||||||
e.printStackTrace();
|
|
||||||
} catch (AccountUtils.AccountNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,11 @@ import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class ArbitraryDataProviderIT : AbstractIT() {
|
class ArbitraryDataProviderIT : AbstractIT() {
|
||||||
private val arbitraryDataProvider = ArbitraryDataProviderImpl(targetContext)
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNull() {
|
fun testEmpty() {
|
||||||
val key = "DUMMY_KEY"
|
val key = "DUMMY_KEY"
|
||||||
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, null)
|
arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, "")
|
||||||
|
|
||||||
assertEquals("", arbitraryDataProvider.getValue(user.accountName, key))
|
assertEquals("", arbitraryDataProvider.getValue(user.accountName, key))
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,7 @@ class FileDetailSharingFragmentIT : AbstractIT() {
|
||||||
remoteId = "00000001"
|
remoteId = "00000001"
|
||||||
parentId = activity.storageManager.getFileByEncryptedRemotePath("/").fileId
|
parentId = activity.storageManager.getFileByEncryptedRemotePath("/").fileId
|
||||||
permissions = OCFile.PERMISSION_CAN_RESHARE
|
permissions = OCFile.PERMISSION_CAN_RESHARE
|
||||||
|
fileDataStorageManager.saveFile(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
folder = OCFile("/test").apply {
|
folder = OCFile("/test").apply {
|
||||||
|
|
|
@ -31,16 +31,20 @@ import com.nextcloud.test.RetryTestRule;
|
||||||
import com.owncloud.android.AbstractIT;
|
import com.owncloud.android.AbstractIT;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
import com.owncloud.android.datamodel.e2e.v1.decrypted.Data;
|
||||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
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.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 com.owncloud.android.utils.EncryptionUtils;
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Hex;
|
import org.apache.commons.codec.binary.Hex;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
@ -63,9 +67,6 @@ import java.util.Set;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
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.decodeStringToBase64Bytes;
|
||||||
import static com.owncloud.android.utils.EncryptionUtils.decryptFile;
|
import static com.owncloud.android.utils.EncryptionUtils.decryptFile;
|
||||||
import static com.owncloud.android.utils.EncryptionUtils.decryptFolderMetaData;
|
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.assertNotEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class EncryptionTestIT extends AbstractIT {
|
public class EncryptionTestIT extends AbstractIT {
|
||||||
@Rule public RetryTestRule retryTestRule = new RetryTestRule();
|
@Rule public RetryTestRule retryTestRule = new RetryTestRule();
|
||||||
|
|
||||||
private String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo" +
|
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||||
|
|
||||||
|
public static final String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo" +
|
||||||
"IBAQDsn0JKS/THu328z1IgN0VzYU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzV" +
|
"IBAQDsn0JKS/THu328z1IgN0VzYU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzV" +
|
||||||
"GzKFvGfZ03fwFrN7Q8P8R2e8SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7" +
|
"GzKFvGfZ03fwFrN7Q8P8R2e8SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7" +
|
||||||
"Y0BJX9i/nW/L0L/VaE8CZTAqYBdcSJGgHJjY4UMf892ZPTa9T2Dl3ggdMZ7BQ2kiCi" +
|
"Y0BJX9i/nW/L0L/VaE8CZTAqYBdcSJGgHJjY4UMf892ZPTa9T2Dl3ggdMZ7BQ2kiCi" +
|
||||||
|
@ -123,7 +125,7 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
"JLecOFu3ZlQl/RStQb69QKb5MNOIMmQhg8WOxZxHcpmIDbkDAm/J/ovJXFSoBdOr5o" +
|
"JLecOFu3ZlQl/RStQb69QKb5MNOIMmQhg8WOxZxHcpmIDbkDAm/J/ovJXFSoBdOr5o" +
|
||||||
"uQsYsDZhsWW97zvLMzg5pH9/3/1BNz5q3Vu4HgfBSwWGt4E2NENj+XA+QAVmGA==";
|
"uQsYsDZhsWW97zvLMzg5pH9/3/1BNz5q3Vu4HgfBSwWGt4E2NENj+XA+QAVmGA==";
|
||||||
|
|
||||||
private String cert = "-----BEGIN CERTIFICATE-----\n" +
|
public static final String publicKey = "-----BEGIN CERTIFICATE-----\n" +
|
||||||
"MIIDpzCCAo+gAwIBAgIBADANBgkqhkiG9w0BAQUFADBuMRowGAYDVQQDDBF3d3cu\n" +
|
"MIIDpzCCAo+gAwIBAgIBADANBgkqhkiG9w0BAQUFADBuMRowGAYDVQQDDBF3d3cu\n" +
|
||||||
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
|
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
|
||||||
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
|
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
|
||||||
|
@ -151,7 +153,7 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
byte[] key1 = generateKey();
|
byte[] key1 = generateKey();
|
||||||
String base64encodedKey = encodeBytesToBase64String(key1);
|
String base64encodedKey = encodeBytesToBase64String(key1);
|
||||||
|
|
||||||
String encryptedString = EncryptionUtils.encryptStringAsymmetric(base64encodedKey, cert);
|
String encryptedString = EncryptionUtils.encryptStringAsymmetric(base64encodedKey, publicKey);
|
||||||
String decryptedString = decryptStringAsymmetric(encryptedString, privateKey);
|
String decryptedString = decryptStringAsymmetric(encryptedString, privateKey);
|
||||||
|
|
||||||
byte[] key2 = decodeStringToBase64Bytes(decryptedString);
|
byte[] key2 = decodeStringToBase64Bytes(decryptedString);
|
||||||
|
@ -207,9 +209,9 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
String encryptedString;
|
String encryptedString;
|
||||||
if (new Random().nextBoolean()) {
|
if (new Random().nextBoolean()) {
|
||||||
encryptedString = EncryptionUtils.encryptStringSymmetric(privateKey, key);
|
encryptedString = EncryptionUtils.encryptStringSymmetricAsString(privateKey, key);
|
||||||
} else {
|
} else {
|
||||||
encryptedString = EncryptionUtils.encryptStringSymmetricOld(privateKey, key);
|
encryptedString = EncryptionUtils.encryptStringSymmetricAsStringOld(privateKey, key);
|
||||||
|
|
||||||
if (encryptedString.indexOf(ivDelimiterOld) != encryptedString.lastIndexOf(ivDelimiterOld)) {
|
if (encryptedString.indexOf(ivDelimiterOld) != encryptedString.lastIndexOf(ivDelimiterOld)) {
|
||||||
Log_OC.d("EncryptionTestIT", "skip due to duplicated iv (old system) -> ignoring");
|
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++) {
|
for (int i = 0; i < max; i++) {
|
||||||
Log_OC.d("EncryptionTestIT", i + " of " + max);
|
Log_OC.d("EncryptionTestIT", i + " of " + max);
|
||||||
|
|
||||||
String encryptedString = EncryptionUtils.encryptStringSymmetric(privateKey, key);
|
String encryptedString = EncryptionUtils.encryptStringSymmetricAsString(privateKey, key);
|
||||||
|
|
||||||
int delimiterPosition = encryptedString.indexOf(ivDelimiter);
|
int delimiterPosition = encryptedString.indexOf(ivDelimiter);
|
||||||
if (delimiterPosition == -1) {
|
if (delimiterPosition == -1) {
|
||||||
|
@ -286,43 +288,42 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
keyGen.initialize(2048, new SecureRandom());
|
keyGen.initialize(2048, new SecureRandom());
|
||||||
KeyPair keyPair = keyGen.generateKeyPair();
|
KeyPair keyPair = keyGen.generateKeyPair();
|
||||||
|
|
||||||
assertFalse(CsrHelper.generateCsrPemEncodedString(keyPair, "").isEmpty());
|
assertFalse(new CsrHelper().generateCsrPemEncodedString(keyPair, "").isEmpty());
|
||||||
assertFalse(encodeBytesToBase64String(keyPair.getPublic().getEncoded()).isEmpty());
|
assertFalse(encodeBytesToBase64String(keyPair.getPublic().getEncoded()).isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DecryptedFolderMetadata -> EncryptedFolderMetadata -> JSON -> encrypt -> decrypt -> JSON ->
|
* DecryptedFolderMetadataFile -> EncryptedFolderMetadataFile -> JSON -> encrypt -> decrypt -> JSON ->
|
||||||
* EncryptedFolderMetadata -> DecryptedFolderMetadata
|
* EncryptedFolderMetadataFile -> DecryptedFolderMetadataFile
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void encryptionMetadata() throws Exception {
|
public void encryptionMetadataV1() throws Exception {
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
DecryptedFolderMetadataFileV1 decryptedFolderMetadata1 = generateFolderMetadataV1_1();
|
||||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
|
||||||
long folderID = 1;
|
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||||
decryptedFolderMetadata1,
|
decryptedFolderMetadata1,
|
||||||
cert,
|
publicKey,
|
||||||
arbitraryDataProvider,
|
1,
|
||||||
user,
|
user,
|
||||||
folderID);
|
arbitraryDataProvider);
|
||||||
|
|
||||||
// serialize
|
// serialize
|
||||||
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||||
|
|
||||||
// de-serialize
|
// de-serialize
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||||
new TypeToken<EncryptedFolderMetadata>() {
|
new TypeToken<>() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// decrypt
|
// decrypt
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
DecryptedFolderMetadataFileV1 decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||||
encryptedFolderMetadata2,
|
encryptedFolderMetadata2,
|
||||||
privateKey,
|
privateKey,
|
||||||
arbitraryDataProvider,
|
arbitraryDataProvider,
|
||||||
user,
|
user,
|
||||||
folderID);
|
1);
|
||||||
|
|
||||||
// compare
|
// compare
|
||||||
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||||
|
@ -331,29 +332,28 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testChangedMetadataKey() throws Exception {
|
public void testChangedMetadataKey() throws Exception {
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
DecryptedFolderMetadataFileV1 decryptedFolderMetadata1 = generateFolderMetadataV1_1();
|
||||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
|
||||||
long folderID = 1;
|
long folderID = 1;
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||||
decryptedFolderMetadata1,
|
decryptedFolderMetadata1,
|
||||||
cert,
|
publicKey,
|
||||||
arbitraryDataProvider,
|
folderID,
|
||||||
user,
|
user,
|
||||||
folderID);
|
arbitraryDataProvider);
|
||||||
|
|
||||||
// store metadata key
|
// store metadata key
|
||||||
String oldMetadataKey = encryptedFolderMetadata1.getMetadata().getMetadataKey();
|
String oldMetadataKey = encryptedFolderMetadata1.getMetadata().getMetadataKey();
|
||||||
|
|
||||||
// do it again
|
// do it again
|
||||||
// encrypt
|
// encrypt
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata2 = encryptFolderMetadata(
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = encryptFolderMetadata(
|
||||||
decryptedFolderMetadata1,
|
decryptedFolderMetadata1,
|
||||||
cert,
|
publicKey,
|
||||||
arbitraryDataProvider,
|
folderID,
|
||||||
user,
|
user,
|
||||||
folderID);
|
arbitraryDataProvider);
|
||||||
|
|
||||||
String newMetadataKey = encryptedFolderMetadata2.getMetadata().getMetadataKey();
|
String newMetadataKey = encryptedFolderMetadata2.getMetadata().getMetadataKey();
|
||||||
|
|
||||||
|
@ -362,17 +362,16 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMigrateMetadataKey() throws Exception {
|
public void testMigrateMetadataKey() throws Exception {
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
DecryptedFolderMetadataFileV1 decryptedFolderMetadata1 = generateFolderMetadataV1_1();
|
||||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
|
||||||
long folderID = 1;
|
long folderID = 1;
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||||
decryptedFolderMetadata1,
|
decryptedFolderMetadata1,
|
||||||
cert,
|
publicKey,
|
||||||
arbitraryDataProvider,
|
folderID,
|
||||||
user,
|
user,
|
||||||
folderID);
|
arbitraryDataProvider);
|
||||||
|
|
||||||
// reset new metadata key, to mimic old version
|
// reset new metadata key, to mimic old version
|
||||||
encryptedFolderMetadata1.getMetadata().setMetadataKey(null);
|
encryptedFolderMetadata1.getMetadata().setMetadataKey(null);
|
||||||
|
@ -380,12 +379,12 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
// do it again
|
// do it again
|
||||||
// encrypt
|
// encrypt
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata2 = encryptFolderMetadata(
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = encryptFolderMetadata(
|
||||||
decryptedFolderMetadata1,
|
decryptedFolderMetadata1,
|
||||||
cert,
|
publicKey,
|
||||||
arbitraryDataProvider,
|
folderID,
|
||||||
user,
|
user,
|
||||||
folderID);
|
arbitraryDataProvider);
|
||||||
|
|
||||||
String newMetadataKey = encryptedFolderMetadata2.getMetadata().getMetadataKey();
|
String newMetadataKey = encryptedFolderMetadata2.getMetadata().getMetadataKey();
|
||||||
|
|
||||||
|
@ -403,7 +402,7 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void cryptFileWithMetadata() throws Exception {
|
public void cryptFileWithMetadata() throws Exception {
|
||||||
DecryptedFolderMetadata metadata = generateFolderMetadata();
|
DecryptedFolderMetadataFileV1 metadata = generateFolderMetadataV1_1();
|
||||||
|
|
||||||
// n9WXAIXO2wRY4R8nXwmo
|
// n9WXAIXO2wRY4R8nXwmo
|
||||||
assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r",
|
assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r",
|
||||||
|
@ -428,28 +427,27 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bigMetadata() throws Exception {
|
public void bigMetadata() throws Exception {
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
DecryptedFolderMetadataFileV1 decryptedFolderMetadata1 = generateFolderMetadataV1_1();
|
||||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
|
||||||
long folderID = 1;
|
long folderID = 1;
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||||
decryptedFolderMetadata1,
|
decryptedFolderMetadata1,
|
||||||
cert,
|
publicKey,
|
||||||
arbitraryDataProvider,
|
folderID,
|
||||||
user,
|
user,
|
||||||
folderID);
|
arbitraryDataProvider);
|
||||||
|
|
||||||
// serialize
|
// serialize
|
||||||
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||||
|
|
||||||
// de-serialize
|
// de-serialize
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||||
new TypeToken<EncryptedFolderMetadata>() {
|
new TypeToken<>() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// decrypt
|
// decrypt
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
DecryptedFolderMetadataFileV1 decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||||
encryptedFolderMetadata2,
|
encryptedFolderMetadata2,
|
||||||
privateKey,
|
privateKey,
|
||||||
arbitraryDataProvider,
|
arbitraryDataProvider,
|
||||||
|
@ -473,17 +471,17 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1,
|
encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1,
|
||||||
cert,
|
publicKey,
|
||||||
arbitraryDataProvider,
|
folderID,
|
||||||
user,
|
user,
|
||||||
folderID);
|
arbitraryDataProvider);
|
||||||
|
|
||||||
// serialize
|
// serialize
|
||||||
encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||||
|
|
||||||
// de-serialize
|
// de-serialize
|
||||||
encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||||
new TypeToken<EncryptedFolderMetadata>() {
|
new TypeToken<>() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// decrypt
|
// 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
|
@Test
|
||||||
public void filedrop() throws Exception {
|
public void filedrop() throws Exception {
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
DecryptedFolderMetadataFileV1 decryptedFolderMetadata1 = generateFolderMetadataV1_1();
|
||||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
|
||||||
long folderID = 1;
|
long folderID = 1;
|
||||||
|
|
||||||
// add filedrop
|
// 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.setKey("9dfzbIYDt28zTyZfbcll+g==");
|
||||||
data.setFilename("test2.txt");
|
data.setFilename("test2.txt");
|
||||||
data.setVersion(1);
|
data.setVersion(1);
|
||||||
|
|
||||||
DecryptedFolderMetadata.DecryptedFile file = new DecryptedFolderMetadata.DecryptedFile();
|
DecryptedFile file = new DecryptedFile();
|
||||||
file.setInitializationVector("hnJLF8uhDvDoFK4ajuvwrg==");
|
file.setInitializationVector("hnJLF8uhDvDoFK4ajuvwrg==");
|
||||||
file.setEncrypted(data);
|
file.setEncrypted(data);
|
||||||
file.setMetadataKey(0);
|
file.setMetadataKey(0);
|
||||||
|
@ -527,24 +601,24 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
decryptedFolderMetadata1.setFiledrop(filesdrop);
|
decryptedFolderMetadata1.setFiledrop(filesdrop);
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||||
decryptedFolderMetadata1,
|
decryptedFolderMetadata1,
|
||||||
cert,
|
publicKey,
|
||||||
arbitraryDataProvider,
|
folderID,
|
||||||
user,
|
user,
|
||||||
folderID);
|
arbitraryDataProvider);
|
||||||
EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, cert);
|
EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, publicKey);
|
||||||
|
|
||||||
// serialize
|
// serialize
|
||||||
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||||
|
|
||||||
// de-serialize
|
// de-serialize
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||||
new TypeToken<EncryptedFolderMetadata>() {
|
new TypeToken<>() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// decrypt
|
// decrypt
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
DecryptedFolderMetadataFileV1 decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||||
encryptedFolderMetadata2,
|
encryptedFolderMetadata2,
|
||||||
privateKey,
|
privateKey,
|
||||||
arbitraryDataProvider,
|
arbitraryDataProvider,
|
||||||
|
@ -562,19 +636,19 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
assertNull(decryptedFolderMetadata2.getFiledrop());
|
assertNull(decryptedFolderMetadata2.getFiledrop());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFile(DecryptedFolderMetadata decryptedFolderMetadata, int counter) {
|
private void addFile(DecryptedFolderMetadataFileV1 decryptedFolderMetadata, int counter) {
|
||||||
// Add new file
|
// Add new file
|
||||||
// Always generate new
|
// Always generate new
|
||||||
byte[] key = generateKey();
|
byte[] key = generateKey();
|
||||||
byte[] iv = randomBytes(ivLength);
|
byte[] iv = randomBytes(ivLength);
|
||||||
byte[] authTag = randomBytes((128 / 8));
|
byte[] authTag = randomBytes((128 / 8));
|
||||||
|
|
||||||
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
|
Data data = new Data();
|
||||||
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
|
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
|
||||||
data.setFilename(counter + ".txt");
|
data.setFilename(counter + ".txt");
|
||||||
data.setVersion(1);
|
data.setVersion(1);
|
||||||
|
|
||||||
DecryptedFolderMetadata.DecryptedFile file = new DecryptedFolderMetadata.DecryptedFile();
|
DecryptedFile file = new DecryptedFile();
|
||||||
file.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
|
file.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
|
||||||
file.setEncrypted(data);
|
file.setEncrypted(data);
|
||||||
file.setMetadataKey(0);
|
file.setMetadataKey(0);
|
||||||
|
@ -636,7 +710,7 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExcludeGSON() throws Exception {
|
public void testExcludeGSON() throws Exception {
|
||||||
DecryptedFolderMetadata metadata = generateFolderMetadata();
|
DecryptedFolderMetadataFileV1 metadata = generateFolderMetadataV1_1();
|
||||||
|
|
||||||
String jsonWithKeys = serializeJSON(metadata);
|
String jsonWithKeys = serializeJSON(metadata);
|
||||||
String jsonWithoutKeys = serializeJSON(metadata, true);
|
String jsonWithoutKeys = serializeJSON(metadata, true);
|
||||||
|
@ -645,13 +719,27 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
assertFalse(jsonWithoutKeys.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
|
@Test
|
||||||
public void testChecksum() throws Exception {
|
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";
|
String mnemonic = "chimney potato joke science ridge trophy result estate spare vapor much room";
|
||||||
|
|
||||||
metadata.getFiles().put("n9WXAIXO2wRY4R8nXwmo", new DecryptedFolderMetadata.DecryptedFile());
|
metadata.getFiles().put("n9WXAIXO2wRY4R8nXwmo", new DecryptedFile());
|
||||||
metadata.getFiles().put("ia7OEEEyXMoRa1QWQk8r", new DecryptedFolderMetadata.DecryptedFile());
|
metadata.getFiles().put("ia7OEEEyXMoRa1QWQk8r", new DecryptedFile());
|
||||||
|
|
||||||
String encryptedMetadataKey = "GuFPAULudgD49S4+VDFck3LiqQ8sx4zmbrBtdpCSGcT+T0W0z4F5gYQYPlzTG6WOkdW5LJZK/";
|
String encryptedMetadataKey = "GuFPAULudgD49S4+VDFck3LiqQ8sx4zmbrBtdpCSGcT+T0W0z4F5gYQYPlzTG6WOkdW5LJZK/";
|
||||||
metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
|
metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
|
||||||
|
@ -667,7 +755,7 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
String newChecksum = generateChecksum(metadata, newMnemonic);
|
String newChecksum = generateChecksum(metadata, newMnemonic);
|
||||||
assertNotEquals(expectedChecksum, newChecksum);
|
assertNotEquals(expectedChecksum, newChecksum);
|
||||||
|
|
||||||
metadata.getFiles().put("aeb34yXMoRa1QWQk8r", new DecryptedFolderMetadata.DecryptedFile());
|
metadata.getFiles().put("aeb34yXMoRa1QWQk8r", new DecryptedFile());
|
||||||
|
|
||||||
newChecksum = generateChecksum(metadata, mnemonic);
|
newChecksum = generateChecksum(metadata, mnemonic);
|
||||||
assertNotEquals(expectedChecksum, newChecksum);
|
assertNotEquals(expectedChecksum, newChecksum);
|
||||||
|
@ -675,8 +763,6 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddIdToMigratedIds() {
|
public void testAddIdToMigratedIds() {
|
||||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
|
||||||
|
|
||||||
// delete ids
|
// delete ids
|
||||||
arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.MIGRATED_FOLDER_IDS);
|
arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.MIGRATED_FOLDER_IDS);
|
||||||
|
|
||||||
|
@ -686,9 +772,21 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
assertTrue(isFolderMigrated(id, user, arbitraryDataProvider));
|
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
|
// Helper
|
||||||
private boolean compareJsonStrings(String expected, String actual) {
|
public static boolean compareJsonStrings(String expected, String actual) {
|
||||||
JsonParser parser = new JsonParser();
|
JsonParser parser = new JsonParser();
|
||||||
JsonElement o1 = parser.parse(expected);
|
JsonElement o1 = parser.parse(expected);
|
||||||
JsonElement o2 = parser.parse(actual);
|
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 metadataKey0 = encodeBytesToBase64String(generateKey());
|
||||||
String metadataKey1 = encodeBytesToBase64String(generateKey());
|
String metadataKey1 = encodeBytesToBase64String(generateKey());
|
||||||
String metadataKey2 = encodeBytesToBase64String(generateKey());
|
String metadataKey2 = encodeBytesToBase64String(generateKey());
|
||||||
HashMap<Integer, String> metadataKeys = new HashMap<>();
|
HashMap<Integer, String> metadataKeys = new HashMap<>();
|
||||||
metadataKeys.put(0, EncryptionUtils.encryptStringAsymmetric(metadataKey0, cert));
|
metadataKeys.put(0, EncryptionUtils.encryptStringAsymmetric(metadataKey0, publicKey));
|
||||||
metadataKeys.put(1, EncryptionUtils.encryptStringAsymmetric(metadataKey1, cert));
|
metadataKeys.put(1, EncryptionUtils.encryptStringAsymmetric(metadataKey1, publicKey));
|
||||||
metadataKeys.put(2, EncryptionUtils.encryptStringAsymmetric(metadataKey2, cert));
|
metadataKeys.put(2, EncryptionUtils.encryptStringAsymmetric(metadataKey2, publicKey));
|
||||||
DecryptedFolderMetadata.Encrypted encrypted = new DecryptedFolderMetadata.Encrypted();
|
Encrypted encrypted = new Encrypted();
|
||||||
encrypted.setMetadataKeys(metadataKeys);
|
encrypted.setMetadataKeys(metadataKeys);
|
||||||
|
|
||||||
DecryptedFolderMetadata.Metadata metadata1 = new DecryptedFolderMetadata.Metadata();
|
DecryptedMetadata metadata1 = new DecryptedMetadata();
|
||||||
metadata1.setMetadataKeys(metadataKeys);
|
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.setKey("WANM0gRv+DhaexIsI0T3Lg==");
|
||||||
data1.setFilename("test.txt");
|
data1.setFilename("test.txt");
|
||||||
data1.setVersion(1);
|
data1.setVersion(1);
|
||||||
|
|
||||||
DecryptedFolderMetadata.DecryptedFile file1 = new DecryptedFolderMetadata.DecryptedFile();
|
DecryptedFile file1 = new DecryptedFile();
|
||||||
file1.setInitializationVector("gKm3n+mJzeY26q4OfuZEqg==");
|
file1.setInitializationVector("gKm3n+mJzeY26q4OfuZEqg==");
|
||||||
file1.setEncrypted(data1);
|
file1.setEncrypted(data1);
|
||||||
file1.setMetadataKey(0);
|
file1.setMetadataKey(0);
|
||||||
|
@ -732,12 +830,12 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
files.put("ia7OEEEyXMoRa1QWQk8r", file1);
|
files.put("ia7OEEEyXMoRa1QWQk8r", file1);
|
||||||
|
|
||||||
DecryptedFolderMetadata.Data data2 = new DecryptedFolderMetadata.Data();
|
Data data2 = new Data();
|
||||||
data2.setKey("9dfzbIYDt28zTyZfbcll+g==");
|
data2.setKey("9dfzbIYDt28zTyZfbcll+g==");
|
||||||
data2.setFilename("test2.txt");
|
data2.setFilename("test2.txt");
|
||||||
data2.setVersion(1);
|
data2.setVersion(1);
|
||||||
|
|
||||||
DecryptedFolderMetadata.DecryptedFile file2 = new DecryptedFolderMetadata.DecryptedFile();
|
DecryptedFile file2 = new DecryptedFile();
|
||||||
file2.setInitializationVector("hnJLF8uhDvDoFK4ajuvwrg==");
|
file2.setInitializationVector("hnJLF8uhDvDoFK4ajuvwrg==");
|
||||||
file2.setEncrypted(data2);
|
file2.setEncrypted(data2);
|
||||||
file2.setMetadataKey(0);
|
file2.setMetadataKey(0);
|
||||||
|
@ -745,9 +843,10 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
files.put("n9WXAIXO2wRY4R8nXwmo", file2);
|
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)
|
private boolean cryptFile(String fileName, String md5, byte[] key, byte[] iv, byte[] expectedAuthTag)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
File file = getFile(fileName);
|
File file = getFile(fileName);
|
||||||
|
@ -757,10 +856,10 @@ public class EncryptionTestIT extends AbstractIT {
|
||||||
|
|
||||||
File encryptedTempFile = File.createTempFile("file", "tmp");
|
File encryptedTempFile = File.createTempFile("file", "tmp");
|
||||||
FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
|
FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
|
||||||
fileOutputStream.write(encryptedFile.encryptedBytes);
|
fileOutputStream.write(encryptedFile.getEncryptedBytes());
|
||||||
fileOutputStream.close();
|
fileOutputStream.close();
|
||||||
|
|
||||||
byte[] authenticationTag = decodeStringToBase64Bytes(encryptedFile.authenticationTag);
|
byte[] authenticationTag = decodeStringToBase64Bytes(encryptedFile.getAuthenticationTag());
|
||||||
|
|
||||||
// verify authentication tag
|
// verify authentication tag
|
||||||
assertTrue(Arrays.equals(expectedAuthTag, authenticationTag));
|
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 = 72, to = 73),
|
||||||
AutoMigration(from = 73, to = 74, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
|
AutoMigration(from = 73, to = 74, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
|
||||||
AutoMigration(from = 74, to = 75),
|
AutoMigration(from = 74, to = 75),
|
||||||
AutoMigration(from = 75, to = 76)
|
AutoMigration(from = 75, to = 76),
|
||||||
|
AutoMigration(from = 76, to = 77)
|
||||||
],
|
],
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
)
|
)
|
||||||
|
|
|
@ -98,6 +98,8 @@ data class CapabilityEntity(
|
||||||
val endToEndEncryption: Int?,
|
val endToEndEncryption: Int?,
|
||||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_KEYS_EXIST)
|
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_KEYS_EXIST)
|
||||||
val endToEndEncryptionKeysExist: Int?,
|
val endToEndEncryptionKeysExist: Int?,
|
||||||
|
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_API_VERSION)
|
||||||
|
val endToEndEncryptionApiVersion: String?,
|
||||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_ACTIVITY)
|
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_ACTIVITY)
|
||||||
val activity: Int?,
|
val activity: Int?,
|
||||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT)
|
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT)
|
||||||
|
|
|
@ -128,5 +128,7 @@ data class FileEntity(
|
||||||
@ColumnInfo(name = ProviderTableMeta.FILE_TAGS)
|
@ColumnInfo(name = ProviderTableMeta.FILE_TAGS)
|
||||||
val tags: String?,
|
val tags: String?,
|
||||||
@ColumnInfo(name = ProviderTableMeta.FILE_METADATA_GPS)
|
@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.notifications.AppNotificationManagerImpl;
|
||||||
import com.nextcloud.client.preferences.AppPreferences;
|
import com.nextcloud.client.preferences.AppPreferences;
|
||||||
import com.nextcloud.client.utils.Throttler;
|
import com.nextcloud.client.utils.Throttler;
|
||||||
|
import com.owncloud.android.providers.UsersAndGroupsSearchConfig;
|
||||||
import com.owncloud.android.authentication.PassCodeManager;
|
import com.owncloud.android.authentication.PassCodeManager;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||||
|
@ -261,4 +262,11 @@ class AppModule {
|
||||||
PassCodeManager passCodeManager(AppPreferences preferences, Clock clock) {
|
PassCodeManager passCodeManager(AppPreferences preferences, Clock clock) {
|
||||||
return new PassCodeManager(preferences, 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 incrementValue(accountName: String, key: String)
|
||||||
fun storeOrUpdateKeyValue(accountName: String, key: String, newValue: Boolean)
|
fun storeOrUpdateKeyValue(accountName: String, key: String, newValue: Boolean)
|
||||||
fun storeOrUpdateKeyValue(accountName: String, key: String, newValue: String)
|
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(accountName: String, key: String): Long
|
||||||
fun getLongValue(user: User, 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 = "DIRECT_EDITING"
|
||||||
const val DIRECT_EDITING_ETAG = "DIRECT_EDITING_ETAG"
|
const val DIRECT_EDITING_ETAG = "DIRECT_EDITING_ETAG"
|
||||||
const val PREDEFINED_STATUS = "PREDEFINED_STATUS"
|
const val PREDEFINED_STATUS = "PREDEFINED_STATUS"
|
||||||
|
const val PUBLIC_KEY = "PUBLIC_KEY_"
|
||||||
const val E2E_ERRORS = "E2E_ERRORS"
|
const val E2E_ERRORS = "E2E_ERRORS"
|
||||||
const val E2E_ERRORS_TIMESTAMP = "E2E_ERRORS_TIMESTAMP"
|
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
|
@Override
|
||||||
public long getLongValue(@NonNull String accountName, @NonNull String key) {
|
public long getLongValue(@NonNull String accountName, @NonNull String key) {
|
||||||
String value = getValue(accountName, key);
|
String value = getValue(accountName, key);
|
||||||
|
|
|
@ -29,18 +29,18 @@ import androidx.annotation.VisibleForTesting;
|
||||||
/**
|
/**
|
||||||
* Decrypted class representation of metadata json of folder metadata.
|
* Decrypted class representation of metadata json of folder metadata.
|
||||||
*/
|
*/
|
||||||
public class DecryptedFolderMetadata {
|
public class DecryptedFolderMetadataOld {
|
||||||
private Metadata metadata;
|
private Metadata metadata;
|
||||||
private Map<String, DecryptedFile> files;
|
private Map<String, DecryptedFile> files;
|
||||||
|
|
||||||
private Map<String, DecryptedFile> filedrop;
|
private Map<String, DecryptedFile> filedrop;
|
||||||
|
|
||||||
public DecryptedFolderMetadata() {
|
public DecryptedFolderMetadataOld() {
|
||||||
this.metadata = new Metadata();
|
this.metadata = new Metadata();
|
||||||
this.files = new HashMap<>();
|
this.files = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DecryptedFolderMetadata(Metadata metadata, Map<String, DecryptedFile> files) {
|
public DecryptedFolderMetadataOld(Metadata metadata, Map<String, DecryptedFile> files) {
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
this.files = files;
|
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.ShareType;
|
||||||
import com.owncloud.android.lib.resources.shares.ShareeUser;
|
import com.owncloud.android.lib.resources.shares.ShareeUser;
|
||||||
import com.owncloud.android.lib.resources.status.CapabilityBooleanType;
|
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.lib.resources.status.OCCapability;
|
||||||
import com.owncloud.android.operations.RemoteOperationFailedException;
|
import com.owncloud.android.operations.RemoteOperationFailedException;
|
||||||
import com.owncloud.android.utils.FileStorageUtils;
|
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_SIZE, gson.toJson(file.getImageDimension()));
|
||||||
cv.put(ProviderTableMeta.FILE_METADATA_GPS, gson.toJson(file.getGeoLocation()));
|
cv.put(ProviderTableMeta.FILE_METADATA_GPS, gson.toJson(file.getGeoLocation()));
|
||||||
cv.put(ProviderTableMeta.FILE_METADATA_LIVE_PHOTO, file.getLinkedFileIdForLivePhoto());
|
cv.put(ProviderTableMeta.FILE_METADATA_LIVE_PHOTO, file.getLinkedFileIdForLivePhoto());
|
||||||
|
cv.put(ProviderTableMeta.FILE_E2E_COUNTER, file.getE2eCounter());
|
||||||
|
|
||||||
return cv;
|
return cv;
|
||||||
}
|
}
|
||||||
|
@ -988,6 +990,7 @@ public class FileDataStorageManager {
|
||||||
ocFile.setLockToken(fileEntity.getLockToken());
|
ocFile.setLockToken(fileEntity.getLockToken());
|
||||||
ocFile.setLivePhoto(fileEntity.getMetadataLivePhoto());
|
ocFile.setLivePhoto(fileEntity.getMetadataLivePhoto());
|
||||||
ocFile.setHidden(nullToZero(fileEntity.getHidden()) == 1);
|
ocFile.setHidden(nullToZero(fileEntity.getHidden()) == 1);
|
||||||
|
ocFile.setE2eCounter(fileEntity.getE2eCounter());
|
||||||
|
|
||||||
String sharees = fileEntity.getSharees();
|
String sharees = fileEntity.getSharees();
|
||||||
// Surprisingly JSON deserialization causes significant overhead.
|
// Surprisingly JSON deserialization causes significant overhead.
|
||||||
|
@ -1974,6 +1977,8 @@ public class FileDataStorageManager {
|
||||||
capability.getEndToEndEncryption().getValue());
|
capability.getEndToEndEncryption().getValue());
|
||||||
contentValues.put(ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_KEYS_EXIST,
|
contentValues.put(ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_KEYS_EXIST,
|
||||||
capability.getEndToEndEncryptionKeysExist().getValue());
|
capability.getEndToEndEncryptionKeysExist().getValue());
|
||||||
|
contentValues.put(ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_API_VERSION,
|
||||||
|
capability.getEndToEndEncryptionApiVersion().getValue());
|
||||||
contentValues.put(ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT,
|
contentValues.put(ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT,
|
||||||
capability.getServerBackgroundDefault().getValue());
|
capability.getServerBackgroundDefault().getValue());
|
||||||
contentValues.put(ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_PLAIN,
|
contentValues.put(ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_PLAIN,
|
||||||
|
@ -2127,6 +2132,16 @@ public class FileDataStorageManager {
|
||||||
getBoolean(cursor,
|
getBoolean(cursor,
|
||||||
ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION_KEYS_EXIST)
|
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(
|
capability.setServerBackgroundDefault(
|
||||||
getBoolean(cursor, ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT));
|
getBoolean(cursor, ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT));
|
||||||
capability.setServerBackgroundPlain(getBoolean(cursor,
|
capability.setServerBackgroundPlain(getBoolean(cursor,
|
||||||
|
|
|
@ -121,6 +121,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
||||||
private String lockToken;
|
private String lockToken;
|
||||||
@Nullable
|
@Nullable
|
||||||
private ImageDimension imageDimension;
|
private ImageDimension imageDimension;
|
||||||
|
private long e2eCounter = -1;
|
||||||
@Nullable
|
@Nullable
|
||||||
private GeoLocation geolocation;
|
private GeoLocation geolocation;
|
||||||
private List<String> tags = new ArrayList<>();
|
private List<String> tags = new ArrayList<>();
|
||||||
|
@ -1056,4 +1057,15 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
||||||
this.tags = tags;
|
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
|
* Nextcloud Android client application
|
||||||
*
|
*
|
||||||
* @author Tobias Kaminsky
|
* @author Tobias Kaminsky
|
||||||
* Copyright (C) 2017 Tobias Kaminsky
|
* Copyright (C) 2023 Tobias Kaminsky
|
||||||
* Copyright (C) 2017 Nextcloud GmbH.
|
* Copyright (C) 2023 Nextcloud GmbH
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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
|
* 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,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
@ -16,23 +17,26 @@
|
||||||
* GNU Affero General Public License for more details.
|
* GNU Affero General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypted class representation of metadata json of folder metadata
|
* Encrypted class representation of metadata json of folder metadata
|
||||||
*/
|
*/
|
||||||
public class EncryptedFolderMetadata {
|
public class EncryptedFolderMetadataFileV1 {
|
||||||
private DecryptedFolderMetadata.Metadata metadata;
|
private DecryptedMetadata metadata;
|
||||||
private Map<String, EncryptedFile> files;
|
private Map<String, EncryptedFile> files;
|
||||||
|
|
||||||
private Map<String, EncryptedFiledrop> filedrop;
|
private Map<String, EncryptedFiledrop> filedrop;
|
||||||
|
|
||||||
public EncryptedFolderMetadata(DecryptedFolderMetadata.Metadata metadata,
|
public EncryptedFolderMetadataFileV1(DecryptedMetadata metadata,
|
||||||
Map<String, EncryptedFile> files,
|
Map<String, EncryptedFile> files,
|
||||||
Map<String, EncryptedFiledrop> filesdrop) {
|
Map<String, EncryptedFiledrop> filesdrop) {
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
|
@ -40,7 +44,7 @@ public class EncryptedFolderMetadata {
|
||||||
this.filedrop = filesdrop;
|
this.filedrop = filesdrop;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DecryptedFolderMetadata.Metadata getMetadata() {
|
public DecryptedMetadata getMetadata() {
|
||||||
return this.metadata;
|
return this.metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +56,7 @@ public class EncryptedFolderMetadata {
|
||||||
return filedrop;
|
return filedrop;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMetadata(DecryptedFolderMetadata.Metadata metadata) {
|
public void setMetadata(DecryptedMetadata metadata) {
|
||||||
this.metadata = 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 class ProviderMeta {
|
||||||
public static final String DB_NAME = "filelist";
|
public static final String DB_NAME = "filelist";
|
||||||
public static final int DB_VERSION = 76;
|
public static final int DB_VERSION = 77;
|
||||||
|
|
||||||
private ProviderMeta() {
|
private ProviderMeta() {
|
||||||
// No instance
|
// No instance
|
||||||
|
@ -129,6 +129,7 @@ public class ProviderMeta {
|
||||||
public static final String FILE_LOCK_TIMEOUT = "lock_timeout";
|
public static final String FILE_LOCK_TIMEOUT = "lock_timeout";
|
||||||
public static final String FILE_LOCK_TOKEN = "lock_token";
|
public static final String FILE_LOCK_TOKEN = "lock_token";
|
||||||
public static final String FILE_TAGS = "tags";
|
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(
|
public static final List<String> FILE_ALL_COLUMNS = Collections.unmodifiableList(Arrays.asList(
|
||||||
_ID,
|
_ID,
|
||||||
|
@ -178,6 +179,7 @@ public class ProviderMeta {
|
||||||
FILE_LOCK_TOKEN,
|
FILE_LOCK_TOKEN,
|
||||||
FILE_METADATA_SIZE,
|
FILE_METADATA_SIZE,
|
||||||
FILE_METADATA_LIVE_PHOTO,
|
FILE_METADATA_LIVE_PHOTO,
|
||||||
|
FILE_E2E_COUNTER,
|
||||||
FILE_TAGS,
|
FILE_TAGS,
|
||||||
FILE_METADATA_GPS));
|
FILE_METADATA_GPS));
|
||||||
public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc";
|
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_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 = "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_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_ACTIVITY = "activity";
|
||||||
public static final String CAPABILITIES_RICHDOCUMENT = "richdocument";
|
public static final String CAPABILITIES_RICHDOCUMENT = "richdocument";
|
||||||
public static final String CAPABILITIES_RICHDOCUMENT_MIMETYPE_LIST = "richdocument_mimetype_list";
|
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) {
|
private void filterShareFile(List<Integer> toHide, OCCapability capability) {
|
||||||
if (!isSingleSelection() || containsEncryptedFile() ||
|
if (!isSingleSelection() || containsEncryptedFile() || hasEncryptedParent() ||
|
||||||
(!isShareViaLinkAllowed() && !isShareWithUsersAllowed()) ||
|
(!isShareViaLinkAllowed() && !isShareWithUsersAllowed()) ||
|
||||||
!isShareApiEnabled(capability) || !files.iterator().next().canReshare()) {
|
!isShareApiEnabled(capability) || !files.iterator().next().canReshare()) {
|
||||||
toHide.add(R.id.action_send_share_file);
|
toHide.add(R.id.action_send_share_file);
|
||||||
|
@ -220,7 +220,11 @@ public class FileMenuFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void filterLock(List<Integer> toHide, boolean fileLockingEnabled) {
|
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);
|
toHide.add(R.id.action_lock_file);
|
||||||
} else {
|
} else {
|
||||||
OCFile file = files.iterator().next();
|
OCFile file = files.iterator().next();
|
||||||
|
@ -340,7 +344,7 @@ public class FileMenuFilter {
|
||||||
|
|
||||||
private void filterRemove(List<Integer> toHide, boolean synchronizing) {
|
private void filterRemove(List<Integer> toHide, boolean synchronizing) {
|
||||||
if (files.isEmpty() || synchronizing || containsLockedFile()
|
if (files.isEmpty() || synchronizing || containsLockedFile()
|
||||||
|| containsEncryptedFolder() || containsEncryptedFile()) {
|
|| containsEncryptedFolder() || isFolderAndContainsEncryptedFile()) {
|
||||||
toHide.add(R.id.action_remove_file);
|
toHide.add(R.id.action_remove_file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -485,6 +489,24 @@ public class FileMenuFilter {
|
||||||
return isSingleSelection() && (MimeTypeUtil.isVideo(file) || MimeTypeUtil.isAudio(file));
|
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() {
|
private boolean containsEncryptedFile() {
|
||||||
for (OCFile file : files) {
|
for (OCFile file : files) {
|
||||||
if (!file.isFolder() && file.isEncrypted()) {
|
if (!file.isFolder() && file.isEncrypted()) {
|
||||||
|
|
|
@ -27,10 +27,13 @@ import android.util.Pair;
|
||||||
import com.nextcloud.client.account.User;
|
import com.nextcloud.client.account.User;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
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.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
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.OwnCloudClient;
|
||||||
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
|
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperation;
|
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.CreateFolderRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
|
import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.files.model.RemoteFile;
|
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.operations.common.SyncOperation;
|
||||||
import com.owncloud.android.utils.EncryptionUtils;
|
import com.owncloud.android.utils.EncryptionUtils;
|
||||||
|
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||||
import com.owncloud.android.utils.FileStorageUtils;
|
import com.owncloud.android.utils.FileStorageUtils;
|
||||||
import com.owncloud.android.utils.MimeType;
|
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;
|
import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Access to remote operation performing the creation of a new folder in the ownCloud server.
|
* Access to remote operation performing the creation of a new folder in the ownCloud server. Save the new folder in
|
||||||
* Save the new folder in Database.
|
* Database.
|
||||||
*/
|
*/
|
||||||
public class CreateFolderOperation extends SyncOperation implements OnRemoteOperationListener {
|
public class CreateFolderOperation extends SyncOperation implements OnRemoteOperationListener {
|
||||||
|
|
||||||
|
@ -100,20 +105,28 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
||||||
boolean encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager());
|
boolean encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager());
|
||||||
|
|
||||||
if (encryptedAncestor) {
|
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 {
|
} else {
|
||||||
return normalCreate(client);
|
return normalCreate(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RemoteOperationResult encryptedCreate(OCFile parent, OwnCloudClient client) {
|
private RemoteOperationResult encryptedCreateV1(OCFile parent, OwnCloudClient client) {
|
||||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
|
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
|
||||||
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
|
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
|
||||||
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
|
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
|
||||||
|
|
||||||
String token = null;
|
String token = null;
|
||||||
Boolean metadataExists;
|
Boolean metadataExists;
|
||||||
DecryptedFolderMetadata metadata;
|
DecryptedFolderMetadataFileV1 metadata;
|
||||||
String encryptedRemotePath = null;
|
String encryptedRemotePath = null;
|
||||||
|
|
||||||
String filename = new File(remotePath).getName();
|
String filename = new File(remotePath).getName();
|
||||||
|
@ -123,12 +136,13 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
||||||
token = EncryptionUtils.lockFolder(parent, client);
|
token = EncryptionUtils.lockFolder(parent, client);
|
||||||
|
|
||||||
// get metadata
|
// get metadata
|
||||||
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent,
|
Pair<Boolean, DecryptedFolderMetadataFileV1> metadataPair = EncryptionUtils.retrieveMetadataV1(parent,
|
||||||
client,
|
client,
|
||||||
privateKey,
|
privateKey,
|
||||||
publicKey,
|
publicKey,
|
||||||
arbitraryDataProvider,
|
arbitraryDataProvider,
|
||||||
user);
|
user
|
||||||
|
);
|
||||||
|
|
||||||
metadataExists = metadataPair.first;
|
metadataExists = metadataPair.first;
|
||||||
metadata = metadataPair.second;
|
metadata = metadataPair.second;
|
||||||
|
@ -142,7 +156,7 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
||||||
String encryptedFileName = createRandomFileName(metadata);
|
String encryptedFileName = createRandomFileName(metadata);
|
||||||
encryptedRemotePath = parent.getRemotePath() + encryptedFileName;
|
encryptedRemotePath = parent.getRemotePath() + encryptedFileName;
|
||||||
|
|
||||||
RemoteOperationResult result = new CreateFolderRemoteOperation(encryptedRemotePath,
|
RemoteOperationResult<String> result = new CreateFolderRemoteOperation(encryptedRemotePath,
|
||||||
true,
|
true,
|
||||||
token)
|
token)
|
||||||
.execute(client);
|
.execute(client);
|
||||||
|
@ -151,11 +165,12 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
||||||
// update metadata
|
// update metadata
|
||||||
metadata.getFiles().put(encryptedFileName, createDecryptedFile(filename));
|
metadata.getFiles().put(encryptedFileName, createDecryptedFile(filename));
|
||||||
|
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
||||||
publicKey,
|
publicKey,
|
||||||
arbitraryDataProvider,
|
parent.getLocalId(),
|
||||||
user,
|
user,
|
||||||
parent.getLocalId());
|
arbitraryDataProvider
|
||||||
|
);
|
||||||
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
||||||
|
|
||||||
// upload metadata
|
// upload metadata
|
||||||
|
@ -164,17 +179,19 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
||||||
token,
|
token,
|
||||||
client,
|
client,
|
||||||
metadataExists,
|
metadataExists,
|
||||||
|
E2EVersion.V1_2,
|
||||||
|
"",
|
||||||
arbitraryDataProvider,
|
arbitraryDataProvider,
|
||||||
user);
|
user);
|
||||||
|
|
||||||
// unlock folder
|
// unlock folder
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token);
|
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolderV1(parent, client, token);
|
||||||
|
|
||||||
if (unlockFolderResult.isSuccess()) {
|
if (unlockFolderResult.isSuccess()) {
|
||||||
token = null;
|
token = null;
|
||||||
} else {
|
} else {
|
||||||
// TODO do better
|
// TODO E2E: do better
|
||||||
throw new RuntimeException("Could not unlock folder!");
|
throw new RuntimeException("Could not unlock folder!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,6 +219,146 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (Exception e) {
|
} 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()) {
|
if (!EncryptionUtils.unlockFolder(parent, client, token).isSuccess()) {
|
||||||
throw new RuntimeException("Could not clean up after failing folder creation!", e);
|
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
|
// remove folder
|
||||||
if (encryptedRemotePath != null) {
|
if (encryptedRemotePath != null) {
|
||||||
RemoteOperationResult removeResult = new RemoveRemoteEncryptedFileOperation(encryptedRemotePath,
|
RemoteOperationResult removeResult = new RemoveRemoteEncryptedFileOperation(encryptedRemotePath,
|
||||||
parent.getLocalId(),
|
|
||||||
user,
|
user,
|
||||||
context,
|
context,
|
||||||
filename).execute(client);
|
filename,
|
||||||
|
parent,
|
||||||
|
true).execute(client);
|
||||||
|
|
||||||
if (!removeResult.isSuccess()) {
|
if (!removeResult.isSuccess()) {
|
||||||
throw new RuntimeException("Could not clean up after failing folder creation!");
|
throw new RuntimeException("Could not clean up after failing folder creation!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO do better
|
// TODO E2E: do better
|
||||||
return new RemoteOperationResult(e);
|
return new RemoteOperationResult(e);
|
||||||
} finally {
|
} finally {
|
||||||
// unlock folder
|
// unlock folder
|
||||||
|
@ -227,21 +385,30 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
||||||
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token);
|
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token);
|
||||||
|
|
||||||
if (!unlockFolderResult.isSuccess()) {
|
if (!unlockFolderResult.isSuccess()) {
|
||||||
// TODO do better
|
// TODO E2E: do better
|
||||||
throw new RuntimeException("Could not unlock folder!");
|
throw new RuntimeException("Could not unlock folder!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFileExisting(DecryptedFolderMetadata metadata, String filename) {
|
private boolean isFileExisting(DecryptedFolderMetadataFileV1 metadata, String filename) {
|
||||||
for (String key : metadata.getFiles().keySet()) {
|
for (com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile file : metadata.getFiles().values()) {
|
||||||
DecryptedFolderMetadata.DecryptedFile file = metadata.getFiles().get(key);
|
if (filename.equalsIgnoreCase(file.getEncrypted().getFilename())) {
|
||||||
|
|
||||||
if (file != null && filename.equalsIgnoreCase(file.getEncrypted().getFilename())) {
|
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,15 +428,16 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private DecryptedFolderMetadata.DecryptedFile createDecryptedFile(String filename) {
|
private com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile createDecryptedFile(String filename) {
|
||||||
// Key, always generate new one
|
// Key, always generate new one
|
||||||
byte[] key = EncryptionUtils.generateKey();
|
byte[] key = EncryptionUtils.generateKey();
|
||||||
|
|
||||||
// IV, always generate new one
|
// IV, always generate new one
|
||||||
byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
|
byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
|
||||||
|
|
||||||
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
|
com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile decryptedFile =
|
||||||
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
|
new com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile();
|
||||||
|
Data data = new Data();
|
||||||
data.setFilename(filename);
|
data.setFilename(filename);
|
||||||
data.setMimetype(MimeType.WEBDAV_FOLDER);
|
data.setMimetype(MimeType.WEBDAV_FOLDER);
|
||||||
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
|
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
|
||||||
|
@ -281,7 +449,32 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@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("-", "");
|
String encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
|
||||||
|
|
||||||
while (metadata.getFiles().get(encryptedFileName) != null) {
|
while (metadata.getFiles().get(encryptedFileName) != null) {
|
||||||
|
|
|
@ -23,17 +23,30 @@
|
||||||
|
|
||||||
package com.owncloud.android.operations;
|
package com.owncloud.android.operations;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.text.TextUtils;
|
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.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
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.OwnCloudClient;
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||||
import com.owncloud.android.lib.resources.files.FileUtils;
|
import com.owncloud.android.lib.resources.files.FileUtils;
|
||||||
import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation;
|
import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.shares.OCShare;
|
import com.owncloud.android.lib.resources.shares.OCShare;
|
||||||
import com.owncloud.android.lib.resources.shares.ShareType;
|
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.operations.common.SyncOperation;
|
||||||
|
import com.owncloud.android.utils.EncryptionUtils;
|
||||||
|
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -44,15 +57,19 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public class CreateShareWithShareeOperation extends SyncOperation {
|
public class CreateShareWithShareeOperation extends SyncOperation {
|
||||||
|
|
||||||
private String path;
|
private final String path;
|
||||||
private String shareeName;
|
private final String shareeName;
|
||||||
private ShareType shareType;
|
private final ShareType shareType;
|
||||||
private int permissions;
|
private final int permissions;
|
||||||
private String noteMessage;
|
private final String noteMessage;
|
||||||
private String sharePassword;
|
private final String sharePassword;
|
||||||
private boolean hideFileDownload;
|
private final boolean hideFileDownload;
|
||||||
private long expirationDateInMillis;
|
private final long expirationDateInMillis;
|
||||||
private String label;
|
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,
|
private static final Set<ShareType> supportedShareTypes = new HashSet<>(Arrays.asList(ShareType.USER,
|
||||||
ShareType.GROUP,
|
ShareType.GROUP,
|
||||||
|
@ -68,35 +85,9 @@ public class CreateShareWithShareeOperation extends SyncOperation {
|
||||||
* @param shareeName User or group name of the target sharee.
|
* @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}
|
* @param shareType Type of share determines type of sharee; {@link ShareType#USER} and {@link ShareType#GROUP}
|
||||||
* are the only valid values for the moment.
|
* are the only valid values for the moment.
|
||||||
* @param permissions Share permissions key as detailed in
|
* @param permissions Share permissions key as detailed in <a
|
||||||
* https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#create-a-new-share
|
* 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,
|
|
||||||
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
|
|
||||||
* .
|
|
||||||
*/
|
*/
|
||||||
public CreateShareWithShareeOperation(String path,
|
public CreateShareWithShareeOperation(String path,
|
||||||
String shareeName,
|
String shareeName,
|
||||||
|
@ -106,7 +97,10 @@ public class CreateShareWithShareeOperation extends SyncOperation {
|
||||||
String sharePassword,
|
String sharePassword,
|
||||||
long expirationDateInMillis,
|
long expirationDateInMillis,
|
||||||
boolean hideFileDownload,
|
boolean hideFileDownload,
|
||||||
FileDataStorageManager storageManager) {
|
FileDataStorageManager storageManager,
|
||||||
|
Context context,
|
||||||
|
User user,
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider) {
|
||||||
super(storageManager);
|
super(storageManager);
|
||||||
|
|
||||||
if (!supportedShareTypes.contains(shareType)) {
|
if (!supportedShareTypes.contains(shareType)) {
|
||||||
|
@ -120,10 +114,52 @@ public class CreateShareWithShareeOperation extends SyncOperation {
|
||||||
this.hideFileDownload = hideFileDownload;
|
this.hideFileDownload = hideFileDownload;
|
||||||
this.noteMessage = noteMessage;
|
this.noteMessage = noteMessage;
|
||||||
this.sharePassword = sharePassword;
|
this.sharePassword = sharePassword;
|
||||||
|
this.context = context;
|
||||||
|
this.user = user;
|
||||||
|
this.arbitraryDataProvider = arbitraryDataProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RemoteOperationResult run(OwnCloudClient client) {
|
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(
|
CreateShareRemoteOperation operation = new CreateShareRemoteOperation(
|
||||||
path,
|
path,
|
||||||
|
@ -135,16 +171,75 @@ public class CreateShareWithShareeOperation extends SyncOperation {
|
||||||
noteMessage
|
noteMessage
|
||||||
);
|
);
|
||||||
operation.setGetShareDetails(true);
|
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) {
|
// E2E: update metadata
|
||||||
OCShare share = (OCShare) result.getData().get(0);
|
if (isEncrypted) {
|
||||||
|
Object object = EncryptionUtils.downloadFolderMetadata(folder,
|
||||||
|
client,
|
||||||
|
context,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
|
||||||
|
if (object instanceof DecryptedFolderMetadataFileV1) {
|
||||||
|
throw new RuntimeException("Trying to share on e2e v1!");
|
||||||
|
}
|
||||||
|
|
||||||
|
DecryptedFolderMetadataFile metadata = (DecryptedFolderMetadataFile) object;
|
||||||
|
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OCShare share = (OCShare) shareResult.getData().get(0);
|
||||||
|
|
||||||
// once creating share link update other information
|
// once creating share link update other information
|
||||||
UpdateShareInfoOperation updateShareInfoOperation = new UpdateShareInfoOperation(share, getStorageManager());
|
UpdateShareInfoOperation updateShareInfoOperation = new UpdateShareInfoOperation(share, getStorageManager());
|
||||||
updateShareInfoOperation.setExpirationDateInMillis(expirationDateInMillis);
|
updateShareInfoOperation.setExpirationDateInMillis(expirationDateInMillis);
|
||||||
updateShareInfoOperation.setHideFileDownload(hideFileDownload);
|
updateShareInfoOperation.setHideFileDownload(hideFileDownload);
|
||||||
|
updateShareInfoOperation.setNote(noteMessage);
|
||||||
updateShareInfoOperation.setLabel(label);
|
updateShareInfoOperation.setLabel(label);
|
||||||
|
|
||||||
//update permissions for external share (will otherwise default to read-only)
|
//update permissions for external share (will otherwise default to read-only)
|
||||||
|
@ -156,9 +251,8 @@ public class CreateShareWithShareeOperation extends SyncOperation {
|
||||||
OCShare shareUpdated = (OCShare) updateShareInfoResult.getData().get(0);
|
OCShare shareUpdated = (OCShare) updateShareInfoResult.getData().get(0);
|
||||||
updateData(shareUpdated);
|
updateData(shareUpdated);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return shareResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateData(OCShare share) {
|
private void updateData(OCShare share) {
|
||||||
|
|
|
@ -28,9 +28,11 @@ import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
import com.nextcloud.client.account.User;
|
import com.nextcloud.client.account.User;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
|
||||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
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.OwnCloudClient;
|
||||||
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
|
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
|
||||||
import com.owncloud.android.lib.common.operations.OperationCancelledException;
|
import com.owncloud.android.lib.common.operations.OperationCancelledException;
|
||||||
|
@ -50,6 +52,8 @@ import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
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
|
* 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());
|
OCFile parent = fileDataStorageManager.getFileByEncryptedRemotePath(file.getParentRemotePath());
|
||||||
|
|
||||||
DecryptedFolderMetadata metadata = EncryptionUtils.downloadFolderMetadata(parent,
|
Object object = EncryptionUtils.downloadFolderMetadata(parent,
|
||||||
client,
|
client,
|
||||||
operationContext,
|
operationContext,
|
||||||
user);
|
user);
|
||||||
|
|
||||||
if (metadata == null) {
|
if (object == null) {
|
||||||
return new RemoteOperationResult(RemoteOperationResult.ResultCode.METADATA_NOT_FOUND);
|
return new RemoteOperationResult(RemoteOperationResult.ResultCode.METADATA_NOT_FOUND);
|
||||||
}
|
}
|
||||||
byte[] key = EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles()
|
|
||||||
.get(file.getEncryptedFileName()).getEncrypted().getKey());
|
String keyString;
|
||||||
byte[] iv = EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles()
|
String nonceString;
|
||||||
.get(file.getEncryptedFileName()).getInitializationVector());
|
String authenticationTagString;
|
||||||
byte[] authenticationTag = EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles()
|
if (object instanceof DecryptedFolderMetadataFile) {
|
||||||
.get(file.getEncryptedFileName()).getAuthenticationTag());
|
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 {
|
try {
|
||||||
byte[] decryptedBytes = EncryptionUtils.decryptFile(tmpFile,
|
byte[] decryptedBytes = EncryptionUtils.decryptFile(tmpFile,
|
||||||
|
|
|
@ -29,9 +29,11 @@ import com.nextcloud.client.account.User;
|
||||||
import com.nextcloud.common.NextcloudClient;
|
import com.nextcloud.common.NextcloudClient;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
|
||||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
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.DirectEditing;
|
||||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||||
import com.owncloud.android.lib.common.OwnCloudClientFactory;
|
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.GetSharesForFileRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.shares.OCShare;
|
import com.owncloud.android.lib.resources.shares.OCShare;
|
||||||
import com.owncloud.android.lib.resources.shares.ShareType;
|
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.GetPredefinedStatusesRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.users.PredefinedStatus;
|
import com.owncloud.android.lib.resources.users.PredefinedStatus;
|
||||||
import com.owncloud.android.syncadapter.FileSyncAdapter;
|
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.FileStorageUtils;
|
||||||
import com.owncloud.android.utils.MimeType;
|
import com.owncloud.android.utils.MimeType;
|
||||||
import com.owncloud.android.utils.MimeTypeUtil;
|
import com.owncloud.android.utils.MimeTypeUtil;
|
||||||
|
import com.owncloud.android.utils.theme.CapabilityUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -236,6 +240,7 @@ public class RefreshFolderOperation extends RemoteOperation {
|
||||||
|
|
||||||
if (result.isSuccess()) {
|
if (result.isSuccess()) {
|
||||||
if (mRemoteFolderChanged) {
|
if (mRemoteFolderChanged) {
|
||||||
|
// TODO catch IllegalStateException, show properly to user
|
||||||
result = fetchAndSyncRemoteFolder(client);
|
result = fetchAndSyncRemoteFolder(client);
|
||||||
} else {
|
} else {
|
||||||
mChildren = mStorageManager.getFolderContent(mLocalFolder, false);
|
mChildren = mStorageManager.getFolderContent(mLocalFolder, false);
|
||||||
|
@ -403,7 +408,8 @@ public class RefreshFolderOperation extends RemoteOperation {
|
||||||
private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {
|
private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {
|
||||||
String remotePath = mLocalFolder.getRemotePath();
|
String remotePath = mLocalFolder.getRemotePath();
|
||||||
RemoteOperationResult result = new ReadFolderRemoteOperation(remotePath).execute(client);
|
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()) {
|
if (result.isSuccess()) {
|
||||||
synchronizeData(result.getData());
|
synchronizeData(result.getData());
|
||||||
|
@ -470,15 +476,38 @@ public class RefreshFolderOperation extends RemoteOperation {
|
||||||
// update size
|
// update size
|
||||||
mLocalFolder.setFileLength(remoteFolder.getFileLength());
|
mLocalFolder.setFileLength(remoteFolder.getFileLength());
|
||||||
|
|
||||||
DecryptedFolderMetadata metadata = getDecryptedFolderMetadata(encryptedAncestor,
|
Object object = null;
|
||||||
|
if (mLocalFolder.isEncrypted()) {
|
||||||
|
object = getDecryptedFolderMetadata(encryptedAncestor,
|
||||||
mLocalFolder,
|
mLocalFolder,
|
||||||
getClient(),
|
getClient(),
|
||||||
user,
|
user,
|
||||||
mContext);
|
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
|
// get current data about local contents of the folder to synchronize
|
||||||
Map<String, OCFile> localFilesMap = prefillLocalFilesMap(metadata,
|
Map<String, OCFile> localFilesMap;
|
||||||
|
E2EVersion e2EVersion;
|
||||||
|
if (object instanceof DecryptedFolderMetadataFileV1) {
|
||||||
|
e2EVersion = E2EVersion.V1_2;
|
||||||
|
localFilesMap = prefillLocalFilesMap((DecryptedFolderMetadataFileV1) object,
|
||||||
mStorageManager.getFolderContent(mLocalFolder, false));
|
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
|
// loop to update every child
|
||||||
OCFile remoteFile;
|
OCFile remoteFile;
|
||||||
|
@ -518,8 +547,17 @@ public class RefreshFolderOperation extends RemoteOperation {
|
||||||
FileStorageUtils.searchForLocalFileInDefaultPath(updatedFile, user.getAccountName());
|
FileStorageUtils.searchForLocalFileInDefaultPath(updatedFile, user.getAccountName());
|
||||||
|
|
||||||
// update file name for encrypted files
|
// update file name for encrypted files
|
||||||
if (metadata != null) {
|
if (e2EVersion == E2EVersion.V1_2) {
|
||||||
updateFileNameForEncryptedFile(mStorageManager, metadata, updatedFile);
|
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
|
// 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
|
// save updated contents in local database
|
||||||
// update file name for encrypted files
|
// update file name for encrypted files
|
||||||
if (metadata != null) {
|
if (e2EVersion == E2EVersion.V1_2) {
|
||||||
updateFileNameForEncryptedFile(mStorageManager, metadata, mLocalFolder);
|
updateFileNameForEncryptedFileV1(mStorageManager,
|
||||||
|
(DecryptedFolderMetadataFileV1) object,
|
||||||
|
mLocalFolder);
|
||||||
|
} else {
|
||||||
|
updateFileNameForEncryptedFile(mStorageManager,
|
||||||
|
(DecryptedFolderMetadataFile) object,
|
||||||
|
mLocalFolder);
|
||||||
}
|
}
|
||||||
mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
|
mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
|
||||||
|
|
||||||
|
@ -540,12 +584,12 @@ public class RefreshFolderOperation extends RemoteOperation {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static DecryptedFolderMetadata getDecryptedFolderMetadata(boolean encryptedAncestor,
|
public static Object getDecryptedFolderMetadata(boolean encryptedAncestor,
|
||||||
OCFile localFolder,
|
OCFile localFolder,
|
||||||
OwnCloudClient client,
|
OwnCloudClient client,
|
||||||
User user,
|
User user,
|
||||||
Context context) {
|
Context context) {
|
||||||
DecryptedFolderMetadata metadata;
|
Object metadata;
|
||||||
if (encryptedAncestor) {
|
if (encryptedAncestor) {
|
||||||
metadata = EncryptionUtils.downloadFolderMetadata(localFolder, client, context, user);
|
metadata = EncryptionUtils.downloadFolderMetadata(localFolder, client, context, user);
|
||||||
} else {
|
} else {
|
||||||
|
@ -554,13 +598,23 @@ public class RefreshFolderOperation extends RemoteOperation {
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateFileNameForEncryptedFile(FileDataStorageManager storageManager,
|
public static void updateFileNameForEncryptedFileV1(FileDataStorageManager storageManager,
|
||||||
@NonNull DecryptedFolderMetadata metadata,
|
@NonNull DecryptedFolderMetadataFileV1 metadata,
|
||||||
OCFile updatedFile) {
|
OCFile updatedFile) {
|
||||||
try {
|
try {
|
||||||
String decryptedFileName = metadata.getFiles().get(updatedFile.getFileName()).getEncrypted()
|
String decryptedFileName;
|
||||||
.getFilename();
|
String mimetype;
|
||||||
String mimetype = metadata.getFiles().get(updatedFile.getFileName()).getEncrypted().getMimetype();
|
|
||||||
|
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());
|
OCFile parentFile = storageManager.getFileById(updatedFile.getParentId());
|
||||||
String decryptedRemotePath = parentFile.getDecryptedRemotePath() + decryptedFileName;
|
String decryptedRemotePath = parentFile.getDecryptedRemotePath() + decryptedFileName;
|
||||||
|
@ -580,7 +634,46 @@ public class RefreshFolderOperation extends RemoteOperation {
|
||||||
updatedFile.setMimeType(mimetype);
|
updatedFile.setMimeType(mimetype);
|
||||||
}
|
}
|
||||||
} catch (NullPointerException e) {
|
} 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
|
@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());
|
Map<String, OCFile> localFilesMap = Maps.newHashMapWithExpectedSize(localFiles.size());
|
||||||
|
|
||||||
for (OCFile file : localFiles) {
|
for (OCFile file : localFiles) {
|
||||||
|
|
|
@ -104,10 +104,11 @@ public class RemoveFileOperation extends SyncOperation {
|
||||||
if (fileToRemove.isEncrypted()) {
|
if (fileToRemove.isEncrypted()) {
|
||||||
OCFile parent = getStorageManager().getFileByPath(fileToRemove.getParentRemotePath());
|
OCFile parent = getStorageManager().getFileByPath(fileToRemove.getParentRemotePath());
|
||||||
operation = new RemoveRemoteEncryptedFileOperation(fileToRemove.getRemotePath(),
|
operation = new RemoveRemoteEncryptedFileOperation(fileToRemove.getRemotePath(),
|
||||||
parent.getLocalId(),
|
|
||||||
user,
|
user,
|
||||||
context,
|
context,
|
||||||
fileToRemove.getEncryptedFileName());
|
fileToRemove.getEncryptedFileName(),
|
||||||
|
parent,
|
||||||
|
fileToRemove.isFolder());
|
||||||
} else {
|
} else {
|
||||||
operation = new RemoveFileRemoteOperation(fileToRemove.getRemotePath());
|
operation = new RemoveFileRemoteOperation(fileToRemove.getRemotePath());
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,68 +23,58 @@ package com.owncloud.android.operations;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import com.nextcloud.client.account.User;
|
import com.nextcloud.client.account.User;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
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.OwnCloudClient;
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperation;
|
import com.owncloud.android.lib.common.operations.RemoteOperation;
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
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.EncryptionUtils;
|
||||||
|
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||||
|
|
||||||
import org.apache.commons.httpclient.HttpStatus;
|
import org.apache.commons.httpclient.HttpStatus;
|
||||||
import org.apache.commons.httpclient.NameValuePair;
|
import org.apache.commons.httpclient.NameValuePair;
|
||||||
import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
|
import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
|
||||||
|
|
||||||
import java.io.IOException;
|
import kotlin.Pair;
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remote operation performing the removal of a remote encrypted file or folder
|
* 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 String TAG = RemoveRemoteEncryptedFileOperation.class.getSimpleName();
|
||||||
|
|
||||||
private static final int REMOVE_READ_TIMEOUT = 30000;
|
private static final int REMOVE_READ_TIMEOUT = 30000;
|
||||||
private static final int REMOVE_CONNECTION_TIMEOUT = 5000;
|
private static final int REMOVE_CONNECTION_TIMEOUT = 5000;
|
||||||
|
|
||||||
private final String remotePath;
|
private final String remotePath;
|
||||||
private final long parentId;
|
private final OCFile parentFolder;
|
||||||
private User user;
|
private final User user;
|
||||||
|
|
||||||
private final ArbitraryDataProvider arbitraryDataProvider;
|
|
||||||
private final String fileName;
|
private final String fileName;
|
||||||
|
private final Context context;
|
||||||
|
private final boolean isFolder;
|
||||||
|
private final ArbitraryDataProvider arbitraryDataProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param remotePath RemotePath of the remote file or folder to remove from the server
|
* @param remotePath RemotePath of the remote file or folder to remove from the server
|
||||||
* @param parentId local id of parent folder
|
* @param parentFolder parent folder
|
||||||
*/
|
*/
|
||||||
RemoveRemoteEncryptedFileOperation(String remotePath,
|
RemoveRemoteEncryptedFileOperation(String remotePath,
|
||||||
long parentId,
|
|
||||||
User user,
|
User user,
|
||||||
Context context,
|
Context context,
|
||||||
String fileName) {
|
String fileName,
|
||||||
|
OCFile parentFolder,
|
||||||
|
boolean isFolder) {
|
||||||
this.remotePath = remotePath;
|
this.remotePath = remotePath;
|
||||||
this.parentId = parentId;
|
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.fileName = fileName;
|
this.fileName = fileName;
|
||||||
|
this.context = context;
|
||||||
|
this.parentFolder = parentFolder;
|
||||||
|
this.isFolder = isFolder;
|
||||||
|
|
||||||
arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
|
arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
|
||||||
}
|
}
|
||||||
|
@ -93,46 +83,19 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
||||||
* Performs the remove operation.
|
* Performs the remove operation.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected RemoteOperationResult run(OwnCloudClient client) {
|
protected RemoteOperationResult<Void> run(OwnCloudClient client) {
|
||||||
RemoteOperationResult result;
|
RemoteOperationResult<Void> result;
|
||||||
DeleteMethod delete = null;
|
DeleteMethod delete = null;
|
||||||
String token = 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 {
|
try {
|
||||||
// Lock folder
|
// Lock folder
|
||||||
RemoteOperationResult lockFileOperationResult = new LockFileRemoteOperation(parentId).execute(client);
|
token = EncryptionUtils.lockFolder(parentFolder, client);
|
||||||
|
|
||||||
if (lockFileOperationResult.isSuccess()) {
|
EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
|
||||||
token = (String) lockFileOperationResult.getData().get(0);
|
Pair<Boolean, DecryptedFolderMetadataFile> pair = encryptionUtilsV2.retrieveMetadata(parentFolder, client, user, context);
|
||||||
} else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
|
boolean metadataExists = pair.getFirst();
|
||||||
throw new RemoteOperationFailedException("Forbidden! Please try again later.)");
|
DecryptedFolderMetadataFile metadata = pair.getSecond();
|
||||||
} 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!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete file remote
|
// delete file remote
|
||||||
delete = new DeleteMethod(client.getFilesDavUri(remotePath));
|
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);
|
int status = client.executeMethod(delete, REMOVE_READ_TIMEOUT, REMOVE_CONNECTION_TIMEOUT);
|
||||||
|
|
||||||
delete.getResponseBodyAsString(); // exhaust the response, although not interesting
|
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());
|
Log_OC.i(TAG, "Remove " + remotePath + ": " + result.getLogMessage());
|
||||||
|
|
||||||
// remove file from metadata
|
if (isFolder) {
|
||||||
metadata.getFiles().remove(fileName);
|
encryptionUtilsV2.removeFolderFromMetadata(fileName, metadata);
|
||||||
|
} else {
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(
|
encryptionUtilsV2.removeFileFromMetadata(fileName, metadata);
|
||||||
metadata,
|
}
|
||||||
publicKey,
|
|
||||||
arbitraryDataProvider,
|
|
||||||
user,
|
|
||||||
parentId);
|
|
||||||
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
|
||||||
|
|
||||||
// upload metadata
|
// upload metadata
|
||||||
RemoteOperationResult uploadMetadataOperationResult =
|
encryptionUtilsV2.serializeAndUploadMetadata(parentFolder,
|
||||||
new UpdateMetadataRemoteOperation(parentId,
|
metadata,
|
||||||
serializedFolderMetadata, token).execute(client);
|
token,
|
||||||
|
client,
|
||||||
if (!uploadMetadataOperationResult.isSuccess()) {
|
metadataExists,
|
||||||
throw new RemoteOperationFailedException("Metadata not uploaded!");
|
context,
|
||||||
}
|
user,
|
||||||
|
new FileDataStorageManager(user, context.getContentResolver()));
|
||||||
|
|
||||||
// return success
|
// return success
|
||||||
return result;
|
return result;
|
||||||
} catch (NoSuchAlgorithmException | IOException | InvalidKeyException | InvalidAlgorithmParameterException |
|
} catch (Exception e) {
|
||||||
NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeySpecException |
|
result = new RemoteOperationResult<>(e);
|
||||||
CertificateException e) {
|
|
||||||
result = new RemoteOperationResult(e);
|
|
||||||
Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e);
|
Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -178,11 +135,12 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
||||||
|
|
||||||
// unlock file
|
// unlock file
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
RemoteOperationResult unlockFileOperationResult = new UnlockFileRemoteOperation(parentId, token)
|
RemoteOperationResult<Void> unlockFileOperationResult = EncryptionUtils.unlockFolder(parentFolder,
|
||||||
.execute(client);
|
client,
|
||||||
|
token);
|
||||||
|
|
||||||
if (!unlockFileOperationResult.isSuccess()) {
|
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.account.User;
|
||||||
import com.nextcloud.client.files.downloader.FileDownloadHelper;
|
import com.nextcloud.client.files.downloader.FileDownloadHelper;
|
||||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
|
||||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
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.OwnCloudClient;
|
||||||
import com.owncloud.android.lib.common.operations.OperationCancelledException;
|
import com.owncloud.android.lib.common.operations.OperationCancelledException;
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
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.ReadFileRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
|
import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.files.model.RemoteFile;
|
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.operations.common.SyncOperation;
|
||||||
import com.owncloud.android.services.OperationsService;
|
import com.owncloud.android.services.OperationsService;
|
||||||
import com.owncloud.android.utils.FileStorageUtils;
|
import com.owncloud.android.utils.FileStorageUtils;
|
||||||
|
@ -49,6 +51,8 @@ import java.util.Map;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remote operation performing the synchronization of the list of files contained
|
* 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);
|
ReadFolderRemoteOperation operation = new ReadFolderRemoteOperation(mRemotePath);
|
||||||
RemoteOperationResult result = operation.execute(client);
|
RemoteOperationResult result = operation.execute(client);
|
||||||
Log_OC.d(TAG, "Synchronizing " + user.getAccountName() + mRemotePath);
|
Log_OC.d(TAG, "Synchronizing " + user.getAccountName() + mRemotePath);
|
||||||
|
Log_OC.d(TAG, "Synchronizing remote id" + mLocalFolder.getRemoteId());
|
||||||
|
|
||||||
if (result.isSuccess()) {
|
if (result.isSuccess()) {
|
||||||
synchronizeData(result.getData());
|
synchronizeData(result.getData());
|
||||||
|
@ -281,16 +286,28 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
||||||
// update richWorkspace
|
// update richWorkspace
|
||||||
mLocalFolder.setRichWorkspace(remoteFolder.getRichWorkspace());
|
mLocalFolder.setRichWorkspace(remoteFolder.getRichWorkspace());
|
||||||
|
|
||||||
DecryptedFolderMetadata metadata = RefreshFolderOperation.getDecryptedFolderMetadata(encryptedAncestor,
|
Object object = RefreshFolderOperation.getDecryptedFolderMetadata(encryptedAncestor,
|
||||||
mLocalFolder,
|
mLocalFolder,
|
||||||
getClient(),
|
getClient(),
|
||||||
user,
|
user,
|
||||||
mContext);
|
mContext);
|
||||||
|
if (mLocalFolder.isEncrypted() && object == null) {
|
||||||
|
throw new IllegalStateException("metadata is null!");
|
||||||
|
}
|
||||||
|
|
||||||
// get current data about local contents of the folder to synchronize
|
// get current data about local contents of the folder to synchronize
|
||||||
Map<String, OCFile> localFilesMap =
|
Map<String, OCFile> localFilesMap;
|
||||||
RefreshFolderOperation.prefillLocalFilesMap(metadata,
|
E2EVersion e2EVersion;
|
||||||
|
|
||||||
|
if (object instanceof DecryptedFolderMetadataFileV1) {
|
||||||
|
e2EVersion = E2EVersion.V1_2;
|
||||||
|
localFilesMap = RefreshFolderOperation.prefillLocalFilesMap((DecryptedFolderMetadataFileV1) object,
|
||||||
storageManager.getFolderContent(mLocalFolder, false));
|
storageManager.getFolderContent(mLocalFolder, false));
|
||||||
|
} else {
|
||||||
|
e2EVersion = E2EVersion.V2_0;
|
||||||
|
localFilesMap = RefreshFolderOperation.prefillLocalFilesMap((DecryptedFolderMetadataFile) object,
|
||||||
|
storageManager.getFolderContent(mLocalFolder, false));
|
||||||
|
}
|
||||||
|
|
||||||
// loop to synchronize every child
|
// loop to synchronize every child
|
||||||
List<OCFile> updatedFiles = new ArrayList<>(folderAndFiles.size() - 1);
|
List<OCFile> updatedFiles = new ArrayList<>(folderAndFiles.size() - 1);
|
||||||
|
@ -323,8 +340,14 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
||||||
FileStorageUtils.searchForLocalFileInDefaultPath(updatedFile, user.getAccountName());
|
FileStorageUtils.searchForLocalFileInDefaultPath(updatedFile, user.getAccountName());
|
||||||
|
|
||||||
// update file name for encrypted files
|
// update file name for encrypted files
|
||||||
if (metadata != null) {
|
if (e2EVersion == E2EVersion.V1_2) {
|
||||||
RefreshFolderOperation.updateFileNameForEncryptedFile(storageManager, metadata, updatedFile);
|
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
|
// 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);
|
updatedFiles.add(updatedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata != null) {
|
// update file name for encrypted files
|
||||||
RefreshFolderOperation.updateFileNameForEncryptedFile(storageManager, metadata, mLocalFolder);
|
if (e2EVersion == E2EVersion.V1_2) {
|
||||||
|
RefreshFolderOperation.updateFileNameForEncryptedFileV1(storageManager,
|
||||||
|
(DecryptedFolderMetadataFileV1) object,
|
||||||
|
mLocalFolder);
|
||||||
|
} else {
|
||||||
|
RefreshFolderOperation.updateFileNameForEncryptedFile(storageManager,
|
||||||
|
(DecryptedFolderMetadataFile) object,
|
||||||
|
mLocalFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// save updated contents in local database
|
// save updated contents in local database
|
||||||
|
@ -391,6 +421,7 @@ public class SynchronizeFolderOperation extends SyncOperation {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressFBWarnings("JLM")
|
||||||
private void prepareOpsFromLocalKnowledge() throws OperationCancelledException {
|
private void prepareOpsFromLocalKnowledge() throws OperationCancelledException {
|
||||||
List<OCFile> children = getStorageManager().getFolderContent(mLocalFolder, false);
|
List<OCFile> children = getStorageManager().getFolderContent(mLocalFolder, false);
|
||||||
for (OCFile child : children) {
|
for (OCFile child : children) {
|
||||||
|
|
|
@ -21,8 +21,13 @@
|
||||||
|
|
||||||
package com.owncloud.android.operations;
|
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.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
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.OwnCloudClient;
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
|
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.RemoveShareRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.shares.ShareType;
|
import com.owncloud.android.lib.resources.shares.ShareType;
|
||||||
import com.owncloud.android.operations.common.SyncOperation;
|
import com.owncloud.android.operations.common.SyncOperation;
|
||||||
|
import com.owncloud.android.utils.EncryptionUtils;
|
||||||
|
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -45,27 +52,89 @@ public class UnshareOperation extends SyncOperation {
|
||||||
|
|
||||||
private final String remotePath;
|
private final String remotePath;
|
||||||
private final long shareId;
|
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);
|
super(storageManager);
|
||||||
|
|
||||||
this.remotePath = remotePath;
|
this.remotePath = remotePath;
|
||||||
this.shareId = shareId;
|
this.shareId = shareId;
|
||||||
|
this.user = user;
|
||||||
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RemoteOperationResult run(OwnCloudClient client) {
|
protected RemoteOperationResult run(OwnCloudClient client) {
|
||||||
RemoteOperationResult result;
|
RemoteOperationResult result;
|
||||||
|
String token = null;
|
||||||
|
|
||||||
// Get Share for a file
|
// Get Share for a file
|
||||||
OCShare share = getStorageManager().getShareById(shareId);
|
OCShare share = getStorageManager().getShareById(shareId);
|
||||||
|
|
||||||
if (share != null) {
|
if (share != null) {
|
||||||
OCFile file = getStorageManager().getFileByEncryptedRemotePath(remotePath);
|
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());
|
RemoveShareRemoteOperation operation = new RemoveShareRemoteOperation(share.getRemoteId());
|
||||||
result = operation.execute(client);
|
result = operation.execute(client);
|
||||||
|
|
||||||
if (result.isSuccess()) {
|
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");
|
Log_OC.d(TAG, "Share id = " + share.getRemoteId() + " deleted");
|
||||||
|
|
||||||
if (ShareType.PUBLIC_LINK == share.getShareType()) {
|
if (ShareType.PUBLIC_LINK == share.getShareType()) {
|
||||||
|
|
|
@ -25,7 +25,6 @@ import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import com.nextcloud.client.account.User;
|
import com.nextcloud.client.account.User;
|
||||||
import com.nextcloud.client.device.BatteryStatus;
|
import com.nextcloud.client.device.BatteryStatus;
|
||||||
|
@ -34,12 +33,17 @@ import com.nextcloud.client.network.Connectivity;
|
||||||
import com.nextcloud.client.network.ConnectivityService;
|
import com.nextcloud.client.network.ConnectivityService;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
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.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
import com.owncloud.android.datamodel.OCFile;
|
||||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
|
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
|
||||||
import com.owncloud.android.datamodel.UploadsStorageManager;
|
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.db.OCUpload;
|
||||||
import com.owncloud.android.files.services.FileUploader;
|
import com.owncloud.android.files.services.FileUploader;
|
||||||
import com.owncloud.android.files.services.NameCollisionPolicy;
|
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.ReadFileRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation;
|
import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.files.model.RemoteFile;
|
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.operations.common.SyncOperation;
|
||||||
import com.owncloud.android.utils.EncryptionUtils;
|
import com.owncloud.android.utils.EncryptionUtils;
|
||||||
|
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||||
import com.owncloud.android.utils.FileStorageUtils;
|
import com.owncloud.android.utils.FileStorageUtils;
|
||||||
import com.owncloud.android.utils.FileUtil;
|
import com.owncloud.android.utils.FileUtil;
|
||||||
import com.owncloud.android.utils.MimeType;
|
import com.owncloud.android.utils.MimeType;
|
||||||
import com.owncloud.android.utils.MimeTypeUtil;
|
import com.owncloud.android.utils.MimeTypeUtil;
|
||||||
import com.owncloud.android.utils.UriUtils;
|
import com.owncloud.android.utils.UriUtils;
|
||||||
|
import com.owncloud.android.utils.theme.CapabilityUtils;
|
||||||
|
|
||||||
import org.apache.commons.httpclient.HttpStatus;
|
import org.apache.commons.httpclient.HttpStatus;
|
||||||
import org.apache.commons.httpclient.methods.RequestEntity;
|
import org.apache.commons.httpclient.methods.RequestEntity;
|
||||||
|
@ -80,9 +87,11 @@ import java.io.RandomAccessFile;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.nio.channels.FileLock;
|
import java.nio.channels.FileLock;
|
||||||
import java.nio.channels.OverlappingFileLockException;
|
import java.nio.channels.OverlappingFileLockException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import androidx.annotation.CheckResult;
|
import androidx.annotation.CheckResult;
|
||||||
|
@ -90,8 +99,7 @@ import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Operation performing the update in the ownCloud server
|
* Operation performing the update in the ownCloud server of a file that was modified locally.
|
||||||
* of a file that was modified locally.
|
|
||||||
*/
|
*/
|
||||||
public class UploadFileOperation extends SyncOperation {
|
public class UploadFileOperation extends SyncOperation {
|
||||||
|
|
||||||
|
@ -261,7 +269,9 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
return mWhileChargingOnly;
|
return mWhileChargingOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIgnoringPowerSaveMode() { return mIgnoringPowerSaveMode; }
|
public boolean isIgnoringPowerSaveMode() {
|
||||||
|
return mIgnoringPowerSaveMode;
|
||||||
|
}
|
||||||
|
|
||||||
public User getUser() {
|
public User getUser() {
|
||||||
return user;
|
return user;
|
||||||
|
@ -444,8 +454,6 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
String token = null;
|
String token = null;
|
||||||
|
|
||||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(getContext());
|
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(getContext());
|
||||||
|
|
||||||
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
|
|
||||||
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
|
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -456,32 +464,75 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
/***** E2E *****/
|
/***** 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
|
// we might have an old token from interrupted upload
|
||||||
if (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()) {
|
if (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()) {
|
||||||
token = mFolderUnlockToken;
|
token = mFolderUnlockToken;
|
||||||
} else {
|
} else {
|
||||||
token = EncryptionUtils.lockFolder(parentFile, client);
|
token = EncryptionUtils.lockFolder(parentFile, client, counter);
|
||||||
// immediately store it
|
// immediately store it
|
||||||
mUpload.setFolderUnlockToken(token);
|
mUpload.setFolderUnlockToken(token);
|
||||||
uploadsStorageManager.updateUpload(mUpload);
|
uploadsStorageManager.updateUpload(mUpload);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update metadata
|
// Update metadata
|
||||||
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parentFile,
|
EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
|
||||||
client,
|
// kotlin.Pair<Boolean, DecryptedFolderMetadataFile> metadataPair =
|
||||||
privateKey,
|
// encryptionUtilsV2.retrieveMetadata(parentFile,
|
||||||
publicKey,
|
// client,
|
||||||
arbitraryDataProvider,
|
// user,
|
||||||
user);
|
// mContext);
|
||||||
|
|
||||||
metadataExists = metadataPair.first;
|
Object object = EncryptionUtils.downloadFolderMetadata(parentFile, client, mContext, user);
|
||||||
DecryptedFolderMetadata metadata = metadataPair.second;
|
|
||||||
|
|
||||||
|
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 *****/
|
/**** E2E *****/
|
||||||
|
|
||||||
// check name collision
|
// 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) {
|
if (collisionResult != null) {
|
||||||
result = collisionResult;
|
result = collisionResult;
|
||||||
return collisionResult;
|
return collisionResult;
|
||||||
|
@ -509,18 +560,24 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
// IV, always generate new one
|
// IV, always generate new one
|
||||||
byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
|
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
|
// new random file name, check if it exists in metadata
|
||||||
String encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
|
String encryptedFileName = EncryptionUtils.generateUid();
|
||||||
|
|
||||||
|
if (object instanceof DecryptedFolderMetadataFileV1 metadata) {
|
||||||
while (metadata.getFiles().get(encryptedFileName) != null) {
|
while (metadata.getFiles().get(encryptedFileName) != null) {
|
||||||
encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
|
encryptedFileName = EncryptionUtils.generateUid();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (((DecryptedFolderMetadataFile) object).getMetadata().getFiles().get(encryptedFileName) != null) {
|
||||||
|
encryptedFileName = EncryptionUtils.generateUid();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File encryptedTempFile = File.createTempFile("encFile", encryptedFileName);
|
File encryptedTempFile = File.createTempFile("encFile", encryptedFileName);
|
||||||
FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
|
FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
|
||||||
fileOutputStream.write(encryptedFile.encryptedBytes);
|
fileOutputStream.write(encryptedFile.getEncryptedBytes());
|
||||||
fileOutputStream.close();
|
fileOutputStream.close();
|
||||||
|
|
||||||
/***** E2E *****/
|
/***** E2E *****/
|
||||||
|
@ -556,7 +613,6 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
size = new File(mFile.getStoragePath()).length();
|
size = new File(mFile.getStoragePath()).length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
updateSize(size);
|
updateSize(size);
|
||||||
|
|
||||||
/// perform the upload
|
/// perform the upload
|
||||||
|
@ -605,24 +661,28 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
mFile.setDecryptedRemotePath(parentFile.getDecryptedRemotePath() + originalFile.getName());
|
mFile.setDecryptedRemotePath(parentFile.getDecryptedRemotePath() + originalFile.getName());
|
||||||
mFile.setRemotePath(parentFile.getRemotePath() + encryptedFileName);
|
mFile.setRemotePath(parentFile.getRemotePath() + encryptedFileName);
|
||||||
|
|
||||||
|
|
||||||
|
if (object instanceof DecryptedFolderMetadataFileV1 metadata) {
|
||||||
// update metadata
|
// update metadata
|
||||||
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
|
DecryptedFile decryptedFile = new DecryptedFile();
|
||||||
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
|
Data data = new Data();
|
||||||
data.setFilename(mFile.getDecryptedFileName());
|
data.setFilename(mFile.getDecryptedFileName());
|
||||||
data.setMimetype(mFile.getMimeType());
|
data.setMimetype(mFile.getMimeType());
|
||||||
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
|
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
|
||||||
|
|
||||||
decryptedFile.setEncrypted(data);
|
decryptedFile.setEncrypted(data);
|
||||||
decryptedFile.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
|
decryptedFile.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
|
||||||
decryptedFile.setAuthenticationTag(encryptedFile.authenticationTag);
|
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
||||||
|
|
||||||
metadata.getFiles().put(encryptedFileName, decryptedFile);
|
metadata.getFiles().put(encryptedFileName, decryptedFile);
|
||||||
|
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
EncryptedFolderMetadataFileV1 encryptedFolderMetadata =
|
||||||
|
EncryptionUtils.encryptFolderMetadata(metadata,
|
||||||
publicKey,
|
publicKey,
|
||||||
arbitraryDataProvider,
|
parentFile.getLocalId(),
|
||||||
user,
|
user,
|
||||||
parentFile.getLocalId());
|
arbitraryDataProvider
|
||||||
|
);
|
||||||
|
|
||||||
String serializedFolderMetadata;
|
String serializedFolderMetadata;
|
||||||
|
|
||||||
|
@ -639,9 +699,38 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
token,
|
token,
|
||||||
client,
|
client,
|
||||||
metadataExists,
|
metadataExists,
|
||||||
|
E2EVersion.V1_2,
|
||||||
|
"",
|
||||||
arbitraryDataProvider,
|
arbitraryDataProvider,
|
||||||
user);
|
user);
|
||||||
|
|
||||||
|
// unlock
|
||||||
|
result = EncryptionUtils.unlockFolderV1(parentFile, client, token);
|
||||||
|
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
token = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DecryptedFolderMetadataFile metadata = (DecryptedFolderMetadataFile) object;
|
||||||
|
encryptionUtilsV2.addFileToMetadata(
|
||||||
|
encryptedFileName,
|
||||||
|
mFile,
|
||||||
|
iv,
|
||||||
|
encryptedFile.getAuthenticationTag(),
|
||||||
|
key,
|
||||||
|
metadata,
|
||||||
|
getStorageManager());
|
||||||
|
|
||||||
|
// upload metadata
|
||||||
|
encryptionUtilsV2.serializeAndUploadMetadata(parentFile,
|
||||||
|
metadata,
|
||||||
|
token,
|
||||||
|
client,
|
||||||
|
metadataExists,
|
||||||
|
mContext,
|
||||||
|
user,
|
||||||
|
getStorageManager());
|
||||||
|
|
||||||
// unlock
|
// unlock
|
||||||
result = EncryptionUtils.unlockFolder(parentFile, client, token);
|
result = EncryptionUtils.unlockFolder(parentFile, client, token);
|
||||||
|
|
||||||
|
@ -649,6 +738,7 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
token = null;
|
token = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
Log_OC.d(TAG, mFile.getStoragePath() + " not exists anymore");
|
Log_OC.d(TAG, mFile.getStoragePath() + " not exists anymore");
|
||||||
result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
|
result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
|
||||||
|
@ -947,18 +1037,18 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
private RemoteOperationResult checkNameCollision(OwnCloudClient client,
|
private RemoteOperationResult checkNameCollision(OwnCloudClient client,
|
||||||
DecryptedFolderMetadata metadata,
|
List<String> fileNames,
|
||||||
boolean encrypted)
|
boolean encrypted)
|
||||||
throws OperationCancelledException {
|
throws OperationCancelledException {
|
||||||
Log_OC.d(TAG, "Checking name collision in server");
|
Log_OC.d(TAG, "Checking name collision in server");
|
||||||
|
|
||||||
if (existsFile(client, mRemotePath, metadata, encrypted)) {
|
if (existsFile(client, mRemotePath, fileNames, encrypted)) {
|
||||||
switch (mNameCollisionPolicy) {
|
switch (mNameCollisionPolicy) {
|
||||||
case CANCEL:
|
case CANCEL:
|
||||||
Log_OC.d(TAG, "File exists; canceling");
|
Log_OC.d(TAG, "File exists; canceling");
|
||||||
throw new OperationCancelledException();
|
throw new OperationCancelledException();
|
||||||
case RENAME:
|
case RENAME:
|
||||||
mRemotePath = getNewAvailableRemotePath(client, mRemotePath, metadata, encrypted);
|
mRemotePath = getNewAvailableRemotePath(client, mRemotePath, fileNames, encrypted);
|
||||||
mWasRenamed = true;
|
mWasRenamed = true;
|
||||||
createNewOCFile(mRemotePath);
|
createNewOCFile(mRemotePath);
|
||||||
Log_OC.d(TAG, "File renamed as " + 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
|
* Checks the existence of the folder where the current file will be uploaded both in the remote server and in the
|
||||||
* in the remote server and in the local database.
|
* local database.
|
||||||
* <p/>
|
* <p/>
|
||||||
* If the upload is set to enforce the creation of the folder, the method tries to
|
* If the upload is set to enforce the creation of the folder, the method tries to create it both remote and
|
||||||
* create it both remote and locally.
|
* locally.
|
||||||
*
|
*
|
||||||
* @param pathToGrant Full remote path whose existence will be granted.
|
* @param pathToGrant Full remote path whose existence will be granted.
|
||||||
* @return An {@link OCFile} instance corresponding to the folder where the file
|
* @return An {@link OCFile} instance corresponding to the folder where the file will be uploaded.
|
||||||
* will be uploaded.
|
|
||||||
*/
|
*/
|
||||||
private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) {
|
private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) {
|
||||||
RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, false);
|
RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, false);
|
||||||
|
@ -1117,15 +1206,16 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new and available (does not exists on the server) remotePath.
|
* Returns a new and available (does not exists on the server) remotePath. This adds an incremental suffix.
|
||||||
* This adds an incremental suffix.
|
|
||||||
*
|
*
|
||||||
* @param client OwnCloud client
|
* @param client OwnCloud client
|
||||||
* @param remotePath remote path of the file
|
* @param remotePath remote path of the file
|
||||||
* @param metadata metadata of encrypted folder
|
* @param fileNames list of decrypted file names
|
||||||
* @return new remote path
|
* @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) {
|
boolean encrypted) {
|
||||||
int extPos = remotePath.lastIndexOf('.');
|
int extPos = remotePath.lastIndexOf('.');
|
||||||
String suffix;
|
String suffix;
|
||||||
|
@ -1142,20 +1232,22 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
do {
|
do {
|
||||||
suffix = " (" + count + ")";
|
suffix = " (" + count + ")";
|
||||||
newPath = extPos >= 0 ? remotePathWithoutExtension + suffix + "." + extension : remotePath + suffix;
|
newPath = extPos >= 0 ? remotePathWithoutExtension + suffix + "." + extension : remotePath + suffix;
|
||||||
exists = existsFile(client, newPath, metadata, encrypted);
|
exists = existsFile(client, newPath, fileNames, encrypted);
|
||||||
count++;
|
count++;
|
||||||
} while (exists);
|
} while (exists);
|
||||||
|
|
||||||
return newPath;
|
return newPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean existsFile(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
|
private boolean existsFile(OwnCloudClient client,
|
||||||
|
String remotePath,
|
||||||
|
List<String> fileNames,
|
||||||
boolean encrypted) {
|
boolean encrypted) {
|
||||||
if (encrypted) {
|
if (encrypted) {
|
||||||
String fileName = new File(remotePath).getName();
|
String fileName = new File(remotePath).getName();
|
||||||
|
|
||||||
for (DecryptedFolderMetadata.DecryptedFile file : metadata.getFiles().values()) {
|
for (String name : fileNames) {
|
||||||
if (file.getEncrypted().getFilename().equalsIgnoreCase(fileName)) {
|
if (name.equalsIgnoreCase(fileName)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1169,9 +1261,8 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to cancel the actual upload operation. If actual upload operating
|
* Allows to cancel the actual upload operation. If actual upload operating is in progress it is cancelled, if
|
||||||
* is in progress it is cancelled, if upload preparation is being performed
|
* upload preparation is being performed upload will not take place.
|
||||||
* upload will not take place.
|
|
||||||
*/
|
*/
|
||||||
public void cancel(ResultCode cancellationReason) {
|
public void cancel(ResultCode cancellationReason) {
|
||||||
if (mUploadOperation == null) {
|
if (mUploadOperation == null) {
|
||||||
|
@ -1322,9 +1413,8 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
/**
|
/**
|
||||||
* Saves a OC File after a successful upload.
|
* Saves a OC File after a successful upload.
|
||||||
* <p>
|
* <p>
|
||||||
* A PROPFIND is necessary to keep the props in the local database
|
* A PROPFIND is necessary to keep the props in the local database synchronized with the server, specially the
|
||||||
* synchronized with the server, specially the modification time and Etag
|
* modification time and Etag (where available)
|
||||||
* (where available)
|
|
||||||
*/
|
*/
|
||||||
private void saveUploadedFile(OwnCloudClient client) {
|
private void saveUploadedFile(OwnCloudClient client) {
|
||||||
OCFile file = mFile;
|
OCFile file = mFile;
|
||||||
|
|
|
@ -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.os.ParcelFileDescriptor;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.nextcloud.client.account.User;
|
import com.nextcloud.client.account.User;
|
||||||
|
@ -116,6 +117,8 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected UserAccountManager accountManager;
|
protected UserAccountManager accountManager;
|
||||||
|
@Inject
|
||||||
|
protected UsersAndGroupsSearchConfig searchConfig;
|
||||||
|
|
||||||
private static final Map<String, ShareType> sShareTypes = new HashMap<>();
|
private static final Map<String, ShareType> sShareTypes = new HashMap<>();
|
||||||
|
|
||||||
|
@ -193,6 +196,10 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cursor searchForUsersOrGroups(Uri uri) {
|
private Cursor searchForUsersOrGroups(Uri uri) {
|
||||||
|
|
||||||
|
// TODO check searchConfig and filter results
|
||||||
|
Log.d(TAG, "searchForUsersOrGroups: searchConfig only users: " + searchConfig.getSearchOnlyUsers());
|
||||||
|
|
||||||
String lastPathSegment = uri.getLastPathSegment();
|
String lastPathSegment = uri.getLastPathSegment();
|
||||||
|
|
||||||
if (lastPathSegment == null) {
|
if (lastPathSegment == null) {
|
||||||
|
@ -206,15 +213,14 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
||||||
String userQuery = lastPathSegment.toLowerCase(Locale.ROOT);
|
String userQuery = lastPathSegment.toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
// request to the OC server about users and groups matching userQuery
|
// 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);
|
RESULTS_PER_PAGE);
|
||||||
RemoteOperationResult result = searchRequest.execute(user, getContext());
|
RemoteOperationResult<ArrayList<JSONObject>> result = searchRequest.execute(user, getContext());
|
||||||
List<JSONObject> names = new ArrayList<>();
|
List<JSONObject> names = new ArrayList<>();
|
||||||
|
|
||||||
if (result.isSuccess()) {
|
if (result.isSuccess()) {
|
||||||
for (Object o : result.getData()) {
|
names = result.getResultData();
|
||||||
names.add((JSONObject) o);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
showErrorMessage(result);
|
showErrorMessage(result);
|
||||||
}
|
}
|
||||||
|
@ -272,6 +278,11 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
|
||||||
status = new Status(StatusType.OFFLINE, "", "", -1);
|
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) {
|
switch (type) {
|
||||||
case GROUP:
|
case GROUP:
|
||||||
displayName = userName;
|
displayName = userName;
|
||||||
|
|
|
@ -44,6 +44,7 @@ import com.nextcloud.client.account.UserAccountManager;
|
||||||
import com.nextcloud.java.util.Optional;
|
import com.nextcloud.java.util.Optional;
|
||||||
import com.nextcloud.utils.extensions.IntentExtensionsKt;
|
import com.nextcloud.utils.extensions.IntentExtensionsKt;
|
||||||
import com.owncloud.android.MainApp;
|
import com.owncloud.android.MainApp;
|
||||||
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
import com.owncloud.android.datamodel.OCFile;
|
||||||
import com.owncloud.android.lib.common.OwnCloudAccount;
|
import com.owncloud.android.lib.common.OwnCloudAccount;
|
||||||
|
@ -140,6 +141,7 @@ public class OperationsService extends Service {
|
||||||
mUndispatchedFinishedOperations = new ConcurrentHashMap<>();
|
mUndispatchedFinishedOperations = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Inject UserAccountManager accountManager;
|
@Inject UserAccountManager accountManager;
|
||||||
|
@Inject ArbitraryDataProvider arbitraryDataProvider;
|
||||||
|
|
||||||
private static class Target {
|
private static class Target {
|
||||||
public Uri mServerUrl;
|
public Uri mServerUrl;
|
||||||
|
@ -610,7 +612,10 @@ public class OperationsService extends Service {
|
||||||
sharePassword,
|
sharePassword,
|
||||||
expirationDateInMillis,
|
expirationDateInMillis,
|
||||||
hideFileDownload,
|
hideFileDownload,
|
||||||
fileDataStorageManager);
|
fileDataStorageManager,
|
||||||
|
getApplicationContext(),
|
||||||
|
user,
|
||||||
|
arbitraryDataProvider);
|
||||||
|
|
||||||
if (operationIntent.hasExtra(EXTRA_SHARE_PUBLIC_LABEL)) {
|
if (operationIntent.hasExtra(EXTRA_SHARE_PUBLIC_LABEL)) {
|
||||||
createShareWithShareeOperation.setLabel(operationIntent.getStringExtra(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);
|
shareId = operationIntent.getLongExtra(EXTRA_SHARE_ID, -1);
|
||||||
|
|
||||||
if (shareId > 0) {
|
if (shareId > 0) {
|
||||||
operation = new UnshareOperation(remotePath, shareId, fileDataStorageManager);
|
operation = new UnshareOperation(remotePath,
|
||||||
|
shareId,
|
||||||
|
fileDataStorageManager,
|
||||||
|
user,
|
||||||
|
getApplicationContext());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ import com.owncloud.android.operations.UpdateNoteForShareOperation;
|
||||||
import com.owncloud.android.operations.UpdateShareInfoOperation;
|
import com.owncloud.android.operations.UpdateShareInfoOperation;
|
||||||
import com.owncloud.android.operations.UpdateSharePermissionsOperation;
|
import com.owncloud.android.operations.UpdateSharePermissionsOperation;
|
||||||
import com.owncloud.android.operations.UpdateShareViaLinkOperation;
|
import com.owncloud.android.operations.UpdateShareViaLinkOperation;
|
||||||
|
import com.owncloud.android.providers.UsersAndGroupsSearchConfig;
|
||||||
import com.owncloud.android.providers.UsersAndGroupsSearchProvider;
|
import com.owncloud.android.providers.UsersAndGroupsSearchProvider;
|
||||||
import com.owncloud.android.services.OperationsService;
|
import com.owncloud.android.services.OperationsService;
|
||||||
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
|
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
|
||||||
|
@ -182,6 +183,12 @@ public abstract class FileActivity extends DrawerActivity
|
||||||
@Inject
|
@Inject
|
||||||
EditorUtils editorUtils;
|
EditorUtils editorUtils;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
UsersAndGroupsSearchConfig usersAndGroupsSearchConfig;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showFiles(boolean onDeviceOnly) {
|
public void showFiles(boolean onDeviceOnly) {
|
||||||
// must be specialized in subclasses
|
// must be specialized in subclasses
|
||||||
|
@ -203,6 +210,7 @@ public abstract class FileActivity extends DrawerActivity
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
usersAndGroupsSearchConfig.reset();
|
||||||
mHandler = new Handler();
|
mHandler = new Handler();
|
||||||
mFileOperationsHelper = new FileOperationsHelper(this, getUserAccountManager(), connectivityService, editorUtils);
|
mFileOperationsHelper = new FileOperationsHelper(this, getUserAccountManager(), connectivityService, editorUtils);
|
||||||
User user = null;
|
User user = null;
|
||||||
|
@ -907,7 +915,9 @@ public abstract class FileActivity extends DrawerActivity
|
||||||
protected void doShareWith(String shareeName, ShareType shareType) {
|
protected void doShareWith(String shareeName, ShareType shareType) {
|
||||||
FileDetailFragment fragment = getFileDetailFragment();
|
FileDetailFragment fragment = getFileDetailFragment();
|
||||||
if (fragment != null) {
|
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) {
|
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");
|
Preference pMnemonic = findPreference("mnemonic");
|
||||||
if (pMnemonic != null) {
|
if (pMnemonic != null) {
|
||||||
|
@ -991,7 +991,7 @@ public class SettingsActivity extends PreferenceActivity
|
||||||
RequestCredentialsActivity.KEY_CHECK_RESULT_TRUE) {
|
RequestCredentialsActivity.KEY_CHECK_RESULT_TRUE) {
|
||||||
|
|
||||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(this);
|
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.Builder builder = new AlertDialog.Builder(this, R.style.FallbackTheming_Dialog);
|
||||||
AlertDialog alertDialog = builder.setTitle(R.string.prefs_e2e_mnemonic)
|
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,
|
getSupportFragmentManager().beginTransaction().replace(R.id.share_fragment_container,
|
||||||
FileDetailsSharingProcessFragment.newInstance(getFile(),
|
FileDetailsSharingProcessFragment.newInstance(getFile(),
|
||||||
shareeName,
|
shareeName,
|
||||||
shareType),
|
shareType,
|
||||||
|
false),
|
||||||
FileDetailsSharingProcessFragment.TAG)
|
FileDetailsSharingProcessFragment.TAG)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import com.nextcloud.ui.ImageDetailFragment;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
import com.owncloud.android.datamodel.OCFile;
|
||||||
import com.owncloud.android.ui.fragment.FileDetailActivitiesFragment;
|
import com.owncloud.android.ui.fragment.FileDetailActivitiesFragment;
|
||||||
import com.owncloud.android.ui.fragment.FileDetailSharingFragment;
|
import com.owncloud.android.ui.fragment.FileDetailSharingFragment;
|
||||||
import com.owncloud.android.utils.EncryptionUtils;
|
|
||||||
import com.owncloud.android.utils.MimeTypeUtil;
|
import com.owncloud.android.utils.MimeTypeUtil;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -39,15 +38,20 @@ import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||||
public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
|
public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
|
||||||
private final OCFile file;
|
private final OCFile file;
|
||||||
private final User user;
|
private final User user;
|
||||||
|
private final boolean showSharingTab;
|
||||||
|
|
||||||
private FileDetailSharingFragment fileDetailSharingFragment;
|
private FileDetailSharingFragment fileDetailSharingFragment;
|
||||||
private FileDetailActivitiesFragment fileDetailActivitiesFragment;
|
private FileDetailActivitiesFragment fileDetailActivitiesFragment;
|
||||||
private ImageDetailFragment imageDetailFragment;
|
private ImageDetailFragment imageDetailFragment;
|
||||||
|
|
||||||
public FileDetailTabAdapter(FragmentManager fm, OCFile file, User user) {
|
public FileDetailTabAdapter(FragmentManager fm,
|
||||||
|
OCFile file,
|
||||||
|
User user,
|
||||||
|
boolean showSharingTab) {
|
||||||
super(fm);
|
super(fm);
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
this.showSharingTab = showSharingTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -81,17 +85,16 @@ public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
if (file.isEncrypted()) {
|
if (showSharingTab) {
|
||||||
if (EncryptionUtils.supportsSecureFiledrop(file, user)) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
// sharing not allowed for encrypted files, thus only show first tab (activities)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// unencrypted files/folders
|
|
||||||
if (MimeTypeUtil.isImage(file)) {
|
if (MimeTypeUtil.isImage(file)) {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
return 2;
|
return 2;
|
||||||
|
} else {
|
||||||
|
if (MimeTypeUtil.isImage(file)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,12 +54,13 @@ import com.owncloud.android.databinding.GridItemBinding;
|
||||||
import com.owncloud.android.databinding.ListFooterBinding;
|
import com.owncloud.android.databinding.ListFooterBinding;
|
||||||
import com.owncloud.android.databinding.ListHeaderBinding;
|
import com.owncloud.android.databinding.ListHeaderBinding;
|
||||||
import com.owncloud.android.databinding.ListItemBinding;
|
import com.owncloud.android.databinding.ListItemBinding;
|
||||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
|
||||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
import com.owncloud.android.datamodel.OCFile;
|
||||||
import com.owncloud.android.datamodel.SyncedFolderProvider;
|
import com.owncloud.android.datamodel.SyncedFolderProvider;
|
||||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
|
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
|
||||||
import com.owncloud.android.datamodel.VirtualFolderType;
|
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.db.ProviderMeta;
|
||||||
import com.owncloud.android.lib.common.OwnCloudClientFactory;
|
import com.owncloud.android.lib.common.OwnCloudClientFactory;
|
||||||
import com.owncloud.android.lib.common.accounts.AccountUtils;
|
import com.owncloud.android.lib.common.accounts.AccountUtils;
|
||||||
|
@ -285,6 +286,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
for (OCFile file : mFiles) {
|
for (OCFile file : mFiles) {
|
||||||
if (file.getRemoteId().equals(fileId)) {
|
if (file.getRemoteId().equals(fileId)) {
|
||||||
file.setEncrypted(encrypted);
|
file.setEncrypted(encrypted);
|
||||||
|
file.setE2eCounter(0L);
|
||||||
mStorageManager.saveFile(file);
|
mStorageManager.saveFile(file);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -294,6 +296,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
for (OCFile file : mFilesAll) {
|
for (OCFile file : mFilesAll) {
|
||||||
if (file.getRemoteId().equals(fileId)) {
|
if (file.getRemoteId().equals(fileId)) {
|
||||||
file.setEncrypted(encrypted);
|
file.setEncrypted(encrypted);
|
||||||
|
file.setE2eCounter(0L);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,7 +438,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ocFileListDelegate.bindGridViewHolder(gridViewHolder, file, searchType);
|
ocFileListDelegate.bindGridViewHolder(gridViewHolder, file, currentDirectory, searchType);
|
||||||
checkVisibilityOfMoreButtons(gridViewHolder);
|
checkVisibilityOfMoreButtons(gridViewHolder);
|
||||||
checkVisibilityOfFileFeaturesLayout(gridViewHolder);
|
checkVisibilityOfFileFeaturesLayout(gridViewHolder);
|
||||||
|
|
||||||
|
@ -890,19 +893,29 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
|
|
||||||
OCFile parentFolder = mStorageManager.getFileById(ocFile.getParentId());
|
OCFile parentFolder = mStorageManager.getFileById(ocFile.getParentId());
|
||||||
if (parentFolder != null && (ocFile.isEncrypted() || parentFolder.isEncrypted())) {
|
if (parentFolder != null && (ocFile.isEncrypted() || parentFolder.isEncrypted())) {
|
||||||
DecryptedFolderMetadata metadata = RefreshFolderOperation.getDecryptedFolderMetadata(
|
Object object = RefreshFolderOperation.getDecryptedFolderMetadata(
|
||||||
true,
|
true,
|
||||||
parentFolder,
|
parentFolder,
|
||||||
OwnCloudClientFactory.createOwnCloudClient(user.toPlatformAccount(), activity),
|
OwnCloudClientFactory.createOwnCloudClient(user.toPlatformAccount(), activity),
|
||||||
user,
|
user,
|
||||||
activity);
|
activity);
|
||||||
|
|
||||||
if (metadata == null) {
|
if (object == null) {
|
||||||
throw new IllegalStateException("metadata is null!");
|
throw new IllegalStateException("metadata is null!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (object instanceof DecryptedFolderMetadataFileV1) {
|
||||||
// update ocFile
|
// update ocFile
|
||||||
RefreshFolderOperation.updateFileNameForEncryptedFile(mStorageManager, metadata, ocFile);
|
RefreshFolderOperation.updateFileNameForEncryptedFileV1(mStorageManager,
|
||||||
|
(DecryptedFolderMetadataFileV1) object,
|
||||||
|
ocFile);
|
||||||
|
} else {
|
||||||
|
// update ocFile
|
||||||
|
RefreshFolderOperation.updateFileNameForEncryptedFile(mStorageManager,
|
||||||
|
(DecryptedFolderMetadataFile) object,
|
||||||
|
ocFile);
|
||||||
|
}
|
||||||
|
|
||||||
ocFile = mStorageManager.saveFileWithParent(ocFile, activity);
|
ocFile = mStorageManager.saveFileWithParent(ocFile, activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -205,6 +205,7 @@ class OCFileListDelegate(
|
||||||
fun bindGridViewHolder(
|
fun bindGridViewHolder(
|
||||||
gridViewHolder: ListGridImageViewHolder,
|
gridViewHolder: ListGridImageViewHolder,
|
||||||
file: OCFile,
|
file: OCFile,
|
||||||
|
currentDirectory: OCFile?,
|
||||||
searchType: SearchType?
|
searchType: SearchType?
|
||||||
) {
|
) {
|
||||||
// thumbnail
|
// thumbnail
|
||||||
|
@ -250,8 +251,9 @@ class OCFileListDelegate(
|
||||||
file.isEncrypted ||
|
file.isEncrypted ||
|
||||||
file.isEncrypted &&
|
file.isEncrypted &&
|
||||||
!EncryptionUtils.supportsSecureFiledrop(file, user) ||
|
!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) {
|
if (shouldHideShare) {
|
||||||
gridViewHolder.shared.visibility = View.GONE
|
gridViewHolder.shared.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -45,12 +45,12 @@ import com.owncloud.android.datamodel.ArbitraryDataProvider
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
|
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
|
||||||
import com.owncloud.android.lib.common.accounts.AccountUtils
|
import com.owncloud.android.lib.common.accounts.AccountUtils
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC
|
import com.owncloud.android.lib.common.utils.Log_OC
|
||||||
import com.owncloud.android.lib.resources.users.DeletePublicKeyOperation
|
import com.owncloud.android.lib.resources.e2ee.CsrHelper
|
||||||
import com.owncloud.android.lib.resources.users.GetPrivateKeyOperation
|
import com.owncloud.android.lib.resources.users.DeletePublicKeyRemoteOperation
|
||||||
import com.owncloud.android.lib.resources.users.GetPublicKeyOperation
|
import com.owncloud.android.lib.resources.users.GetPrivateKeyRemoteOperation
|
||||||
import com.owncloud.android.lib.resources.users.SendCSROperation
|
import com.owncloud.android.lib.resources.users.GetPublicKeyRemoteOperation
|
||||||
import com.owncloud.android.lib.resources.users.StorePrivateKeyOperation
|
import com.owncloud.android.lib.resources.users.SendCSRRemoteOperation
|
||||||
import com.owncloud.android.utils.CsrHelper
|
import com.owncloud.android.lib.resources.users.StorePrivateKeyRemoteOperation
|
||||||
import com.owncloud.android.utils.EncryptionUtils
|
import com.owncloud.android.utils.EncryptionUtils
|
||||||
import com.owncloud.android.utils.theme.ViewThemeUtils
|
import com.owncloud.android.utils.theme.ViewThemeUtils
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -175,7 +175,7 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val privateKey = task?.get()
|
val privateKey = task?.get()
|
||||||
val mnemonicUnchanged = binding.encryptionPasswordInput.text.toString()
|
val mnemonicUnchanged = binding.encryptionPasswordInput.text.toString().trim()
|
||||||
val mnemonic =
|
val mnemonic =
|
||||||
binding.encryptionPasswordInput.text.toString().replace("\\s".toRegex(), "")
|
binding.encryptionPasswordInput.text.toString().replace("\\s".toRegex(), "")
|
||||||
.lowercase()
|
.lowercase()
|
||||||
|
@ -294,11 +294,11 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
||||||
// if available
|
// if available
|
||||||
// - store public key
|
// - store public key
|
||||||
// - decrypt private key, store unencrypted private key in database
|
// - decrypt private key, store unencrypted private key in database
|
||||||
val context = mWeakContext.get()
|
val context = mWeakContext.get() ?: return null
|
||||||
val publicKeyOperation = GetPublicKeyOperation()
|
val publicKeyOperation = GetPublicKeyRemoteOperation()
|
||||||
val user = user ?: return null
|
val user = user ?: return null
|
||||||
|
|
||||||
val publicKeyResult = publicKeyOperation.execute(user, context)
|
val publicKeyResult = publicKeyOperation.executeNextcloudClient(user, context)
|
||||||
|
|
||||||
if (publicKeyResult.isSuccess) {
|
if (publicKeyResult.isSuccess) {
|
||||||
Log_OC.d(TAG, "public key successful downloaded for " + user.accountName)
|
Log_OC.d(TAG, "public key successful downloaded for " + user.accountName)
|
||||||
|
@ -317,7 +317,7 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val privateKeyResult = GetPrivateKeyOperation().execute(user, context)
|
val privateKeyResult = GetPrivateKeyRemoteOperation().executeNextcloudClient(user, context)
|
||||||
if (privateKeyResult.isSuccess) {
|
if (privateKeyResult.isSuccess) {
|
||||||
Log_OC.d(TAG, "private key successful downloaded for " + user!!.accountName)
|
Log_OC.d(TAG, "private key successful downloaded for " + user!!.accountName)
|
||||||
keyResult = KEY_EXISTING_USED
|
keyResult = KEY_EXISTING_USED
|
||||||
|
@ -387,6 +387,11 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
||||||
val context = mWeakContext.get()
|
val context = mWeakContext.get()
|
||||||
val publicKeyString: String
|
val publicKeyString: String
|
||||||
|
|
||||||
|
if (context == null) {
|
||||||
|
keyResult = KEY_FAILED
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// Create public/private key pair
|
// Create public/private key pair
|
||||||
val keyPair = EncryptionUtils.generateKeyPair()
|
val keyPair = EncryptionUtils.generateKeyPair()
|
||||||
|
|
||||||
|
@ -395,12 +400,12 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
||||||
val user = user ?: return ""
|
val user = user ?: return ""
|
||||||
|
|
||||||
val userId = accountManager.getUserData(user.toPlatformAccount(), AccountUtils.Constants.KEY_USER_ID)
|
val userId = accountManager.getUserData(user.toPlatformAccount(), AccountUtils.Constants.KEY_USER_ID)
|
||||||
val urlEncoded = CsrHelper.generateCsrPemEncodedString(keyPair, userId)
|
val urlEncoded = CsrHelper().generateCsrPemEncodedString(keyPair, userId)
|
||||||
val operation = SendCSROperation(urlEncoded)
|
val operation = SendCSRRemoteOperation(urlEncoded)
|
||||||
val result = operation.execute(user, context)
|
val result = operation.executeNextcloudClient(user, context)
|
||||||
|
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
publicKeyString = result.data[0] as String
|
publicKeyString = result.resultData
|
||||||
if (!EncryptionUtils.isMatchingKeys(keyPair, publicKeyString)) {
|
if (!EncryptionUtils.isMatchingKeys(keyPair, publicKeyString)) {
|
||||||
EncryptionUtils.reportE2eError(arbitraryDataProvider, user)
|
EncryptionUtils.reportE2eError(arbitraryDataProvider, user)
|
||||||
throw RuntimeException("Wrong CSR returned")
|
throw RuntimeException("Wrong CSR returned")
|
||||||
|
@ -420,8 +425,8 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
||||||
)
|
)
|
||||||
|
|
||||||
// upload encryptedPrivateKey
|
// upload encryptedPrivateKey
|
||||||
val storePrivateKeyOperation = StorePrivateKeyOperation(encryptedPrivateKey)
|
val storePrivateKeyOperation = StorePrivateKeyRemoteOperation(encryptedPrivateKey)
|
||||||
val storePrivateKeyResult = storePrivateKeyOperation.execute(user, context)
|
val storePrivateKeyResult = storePrivateKeyOperation.executeNextcloudClient(user, context)
|
||||||
if (storePrivateKeyResult.isSuccess) {
|
if (storePrivateKeyResult.isSuccess) {
|
||||||
Log_OC.d(TAG, "private key success")
|
Log_OC.d(TAG, "private key success")
|
||||||
arbitraryDataProvider?.storeOrUpdateKeyValue(
|
arbitraryDataProvider?.storeOrUpdateKeyValue(
|
||||||
|
@ -441,10 +446,10 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
|
||||||
)
|
)
|
||||||
keyResult = KEY_CREATED
|
keyResult = KEY_CREATED
|
||||||
|
|
||||||
return storePrivateKeyResult.data[0] as String
|
return storePrivateKeyResult.resultData
|
||||||
} else {
|
} else {
|
||||||
val deletePublicKeyOperation = DeletePublicKeyOperation()
|
val deletePublicKeyOperation = DeletePublicKeyRemoteOperation()
|
||||||
deletePublicKeyOperation.execute(user, context)
|
deletePublicKeyOperation.executeNextcloudClient(user, context)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log_OC.e(TAG, e.message)
|
Log_OC.e(TAG, e.message)
|
||||||
|
|
|
@ -133,11 +133,11 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
|
||||||
* @param user Currently active user
|
* @param user Currently active user
|
||||||
* @return New fragment with arguments set
|
* @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();
|
FileDetailFragment frag = new FileDetailFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putParcelable(ARG_FILE, fileToDetail);
|
args.putParcelable(ARG_FILE, fileToDetail);
|
||||||
args.putParcelable(ARG_PARENT_FOLDER, parentFile);
|
args.putParcelable(ARG_PARENT_FOLDER, parentFolder);
|
||||||
args.putParcelable(ARG_USER, user);
|
args.putParcelable(ARG_USER, user);
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
return frag;
|
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));
|
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));
|
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);
|
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.setAdapter(adapter);
|
||||||
binding.pager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.tabLayout) {
|
binding.pager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.tabLayout) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -733,11 +736,14 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
|
||||||
* @param shareeName
|
* @param shareeName
|
||||||
* @param shareType
|
* @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,
|
requireActivity().getSupportFragmentManager().beginTransaction().add(R.id.sharing_frame_container,
|
||||||
FileDetailsSharingProcessFragment.newInstance(getFile(),
|
FileDetailsSharingProcessFragment.newInstance(getFile(),
|
||||||
shareeName,
|
shareeName,
|
||||||
shareType),
|
shareType,
|
||||||
|
secureShare),
|
||||||
FileDetailsSharingProcessFragment.TAG)
|
FileDetailsSharingProcessFragment.TAG)
|
||||||
.commit();
|
.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.
|
* 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.NextcloudVersion;
|
||||||
import com.owncloud.android.lib.resources.status.OCCapability;
|
import com.owncloud.android.lib.resources.status.OCCapability;
|
||||||
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
|
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.FileActivity;
|
||||||
import com.owncloud.android.ui.activity.FileDisplayActivity;
|
import com.owncloud.android.ui.activity.FileDisplayActivity;
|
||||||
import com.owncloud.android.ui.adapter.ShareeListAdapter;
|
import com.owncloud.android.ui.adapter.ShareeListAdapter;
|
||||||
|
@ -108,6 +109,7 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
||||||
@Inject UserAccountManager accountManager;
|
@Inject UserAccountManager accountManager;
|
||||||
@Inject ClientFactory clientFactory;
|
@Inject ClientFactory clientFactory;
|
||||||
@Inject ViewThemeUtils viewThemeUtils;
|
@Inject ViewThemeUtils viewThemeUtils;
|
||||||
|
@Inject UsersAndGroupsSearchConfig searchConfig;
|
||||||
|
|
||||||
public static FileDetailSharingFragment newInstance(OCFile file, User user) {
|
public static FileDetailSharingFragment newInstance(OCFile file, User user) {
|
||||||
FileDetailSharingFragment fragment = new FileDetailSharingFragment();
|
FileDetailSharingFragment fragment = new FileDetailSharingFragment();
|
||||||
|
@ -204,20 +206,47 @@ 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() {
|
private void setupView() {
|
||||||
setShareWithYou();
|
setShareWithYou();
|
||||||
|
|
||||||
if (file.isEncrypted()) {
|
OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId());
|
||||||
binding.searchContainer.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
FileDetailSharingFragmentHelper.setupSearchView(
|
FileDetailSharingFragmentHelper.setupSearchView(
|
||||||
(SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE),
|
(SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE),
|
||||||
binding.searchView,
|
binding.searchView,
|
||||||
fileActivity.getComponentName());
|
fileActivity.getComponentName());
|
||||||
viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView);
|
viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView);
|
||||||
|
|
||||||
|
|
||||||
if (file.canReshare()) {
|
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.share_search));
|
binding.searchView.setQueryHint(getResources().getString(R.string.share_search));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.searchView.setQueryHint(getResources().getString(R.string.reshare_not_allowed));
|
binding.searchView.setQueryHint(getResources().getString(R.string.reshare_not_allowed));
|
||||||
binding.searchView.setInputType(InputType.TYPE_NULL);
|
binding.searchView.setInputType(InputType.TYPE_NULL);
|
||||||
|
@ -225,7 +254,6 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
||||||
disableSearchView(binding.searchView);
|
disableSearchView(binding.searchView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void disableSearchView(View view) {
|
private void disableSearchView(View view) {
|
||||||
view.setEnabled(false);
|
view.setEnabled(false);
|
||||||
|
@ -424,6 +452,7 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
||||||
* before reading database.
|
* before reading database.
|
||||||
*/
|
*/
|
||||||
public void refreshSharesFromDB() {
|
public void refreshSharesFromDB() {
|
||||||
|
file = fileDataStorageManager.getFileById(file.getFileId());
|
||||||
ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter();
|
ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter();
|
||||||
|
|
||||||
if (adapter == null) {
|
if (adapter == null) {
|
||||||
|
|
|
@ -72,6 +72,7 @@ class FileDetailsSharingProcessFragment :
|
||||||
private const val ARG_SCREEN_TYPE = "arg_screen_type"
|
private const val ARG_SCREEN_TYPE = "arg_screen_type"
|
||||||
private const val ARG_RESHARE_SHOWN = "arg_reshare_shown"
|
private const val ARG_RESHARE_SHOWN = "arg_reshare_shown"
|
||||||
private const val ARG_EXP_DATE_SHOWN = "arg_exp_date_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
|
// types of screens to be displayed
|
||||||
const val SCREEN_TYPE_PERMISSION = 1 // permissions screen
|
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
|
* fragment instance to be called while creating new share for internal and external share
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newInstance(file: OCFile, shareeName: String, shareType: ShareType): FileDetailsSharingProcessFragment {
|
fun newInstance(
|
||||||
|
file: OCFile,
|
||||||
|
shareeName: String,
|
||||||
|
shareType: ShareType,
|
||||||
|
secureShare: Boolean
|
||||||
|
): FileDetailsSharingProcessFragment {
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
args.putParcelable(ARG_OCFILE, file)
|
args.putParcelable(ARG_OCFILE, file)
|
||||||
args.putSerializable(ARG_SHARE_TYPE, shareType)
|
args.putSerializable(ARG_SHARE_TYPE, shareType)
|
||||||
args.putString(ARG_SHAREE_NAME, shareeName)
|
args.putString(ARG_SHAREE_NAME, shareeName)
|
||||||
|
args.putBoolean(ARG_SECURE_SHARE, secureShare)
|
||||||
val fragment = FileDetailsSharingProcessFragment()
|
val fragment = FileDetailsSharingProcessFragment()
|
||||||
fragment.arguments = args
|
fragment.arguments = args
|
||||||
return fragment
|
return fragment
|
||||||
|
@ -127,6 +134,7 @@ class FileDetailsSharingProcessFragment :
|
||||||
private var share: OCShare? = null
|
private var share: OCShare? = null
|
||||||
private var isReShareShown: Boolean = true // show or hide reShare option
|
private var isReShareShown: Boolean = true // show or hide reShare option
|
||||||
private var isExpDateShown: Boolean = true // show or hide expiry date option
|
private var isExpDateShown: Boolean = true // show or hide expiry date option
|
||||||
|
private var isSecureShare: Boolean = false
|
||||||
|
|
||||||
private var expirationDatePickerFragment: ExpirationDatePickerDialogFragment? = null
|
private var expirationDatePickerFragment: ExpirationDatePickerDialogFragment? = null
|
||||||
|
|
||||||
|
@ -156,6 +164,7 @@ class FileDetailsSharingProcessFragment :
|
||||||
shareProcessStep = it.getInt(ARG_SCREEN_TYPE, SCREEN_TYPE_PERMISSION)
|
shareProcessStep = it.getInt(ARG_SCREEN_TYPE, SCREEN_TYPE_PERMISSION)
|
||||||
isReShareShown = it.getBoolean(ARG_RESHARE_SHOWN, true)
|
isReShareShown = it.getBoolean(ARG_RESHARE_SHOWN, true)
|
||||||
isExpDateShown = it.getBoolean(ARG_EXP_DATE_SHOWN, true)
|
isExpDateShown = it.getBoolean(ARG_EXP_DATE_SHOWN, true)
|
||||||
|
isSecureShare = it.getBoolean(ARG_SECURE_SHARE, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileActivity = activity as FileActivity?
|
fileActivity = activity as FileActivity?
|
||||||
|
@ -222,8 +231,22 @@ class FileDetailsSharingProcessFragment :
|
||||||
binding.shareProcessEditShareLink.visibility = View.VISIBLE
|
binding.shareProcessEditShareLink.visibility = View.VISIBLE
|
||||||
binding.shareProcessGroupTwo.visibility = View.GONE
|
binding.shareProcessGroupTwo.visibility = View.GONE
|
||||||
|
|
||||||
if (share != null) setupModificationUI() else setupUpdateUI()
|
if (share != null) {
|
||||||
binding.shareProcessSetExpDateSwitch.visibility = if (isExpDateShown) View.VISIBLE else View.GONE
|
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
|
shareProcessStep = SCREEN_TYPE_PERMISSION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +333,11 @@ class FileDetailsSharingProcessFragment :
|
||||||
binding.shareProcessChangeNameSwitch.visibility = View.GONE
|
binding.shareProcessChangeNameSwitch.visibility = View.GONE
|
||||||
binding.shareProcessChangeNameContainer.visibility = View.GONE
|
binding.shareProcessChangeNameContainer.visibility = View.GONE
|
||||||
binding.shareProcessHideDownloadCheckbox.visibility = View.GONE
|
binding.shareProcessHideDownloadCheckbox.visibility = View.GONE
|
||||||
|
if (isSecureShare) {
|
||||||
|
binding.shareProcessAllowResharingCheckbox.visibility = View.GONE
|
||||||
|
} else {
|
||||||
binding.shareProcessAllowResharingCheckbox.visibility = View.VISIBLE
|
binding.shareProcessAllowResharingCheckbox.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
binding.shareProcessSetPasswordSwitch.visibility = View.GONE
|
binding.shareProcessSetPasswordSwitch.visibility = View.GONE
|
||||||
|
|
||||||
if (share != null) {
|
if (share != null) {
|
||||||
|
@ -367,6 +394,11 @@ class FileDetailsSharingProcessFragment :
|
||||||
binding.shareProcessPermissionUploadEditing.text =
|
binding.shareProcessPermissionUploadEditing.text =
|
||||||
requireContext().resources.getString(R.string.link_share_allow_upload_and_editing)
|
requireContext().resources.getString(R.string.link_share_allow_upload_and_editing)
|
||||||
binding.shareProcessPermissionFileDrop.visibility = View.VISIBLE
|
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(),
|
binding.shareProcessEnterPassword.text.toString().trim(),
|
||||||
chosenExpDateInMills,
|
chosenExpDateInMills,
|
||||||
noteText,
|
noteText,
|
||||||
binding.shareProcessChangeName.text.toString().trim()
|
binding.shareProcessChangeName.text.toString().trim(),
|
||||||
|
true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
removeCurrentFragment()
|
removeCurrentFragment()
|
||||||
|
|
|
@ -73,12 +73,11 @@ import com.nextcloud.utils.view.FastScrollUtils;
|
||||||
import com.owncloud.android.MainApp;
|
import com.owncloud.android.MainApp;
|
||||||
import com.owncloud.android.R;
|
import com.owncloud.android.R;
|
||||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
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.FileDataStorageManager;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
import com.owncloud.android.datamodel.OCFile;
|
||||||
import com.owncloud.android.datamodel.SyncedFolderProvider;
|
import com.owncloud.android.datamodel.SyncedFolderProvider;
|
||||||
import com.owncloud.android.datamodel.VirtualFolderType;
|
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.Creator;
|
||||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperation;
|
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.e2ee.ToggleEncryptionRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
|
import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.files.ToggleFavoriteRemoteOperation;
|
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.lib.resources.status.OCCapability;
|
||||||
import com.owncloud.android.ui.activity.FileActivity;
|
import com.owncloud.android.ui.activity.FileActivity;
|
||||||
import com.owncloud.android.ui.activity.FileDisplayActivity;
|
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.ui.preview.PreviewTextFileFragment;
|
||||||
import com.owncloud.android.utils.DisplayUtils;
|
import com.owncloud.android.utils.DisplayUtils;
|
||||||
import com.owncloud.android.utils.EncryptionUtils;
|
import com.owncloud.android.utils.EncryptionUtils;
|
||||||
|
import com.owncloud.android.utils.EncryptionUtilsV2;
|
||||||
import com.owncloud.android.utils.FileSortOrder;
|
import com.owncloud.android.utils.FileSortOrder;
|
||||||
import com.owncloud.android.utils.FileStorageUtils;
|
import com.owncloud.android.utils.FileStorageUtils;
|
||||||
import com.owncloud.android.utils.MimeTypeUtil;
|
import com.owncloud.android.utils.MimeTypeUtil;
|
||||||
|
@ -1712,13 +1713,15 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
||||||
dialog.setTargetFragment(this, SETUP_ENCRYPTION_REQUEST_CODE);
|
dialog.setTargetFragment(this, SETUP_ENCRYPTION_REQUEST_CODE);
|
||||||
dialog.show(getParentFragmentManager(), SETUP_ENCRYPTION_DIALOG_TAG);
|
dialog.show(getParentFragmentManager(), SETUP_ENCRYPTION_DIALOG_TAG);
|
||||||
} else {
|
} else {
|
||||||
|
// TODO E2E: if encryption fails, to not set it as encrypted!
|
||||||
encryptFolder(file,
|
encryptFolder(file,
|
||||||
event.getLocalId(),
|
event.getLocalId(),
|
||||||
event.getRemoteId(),
|
event.getRemoteId(),
|
||||||
event.getRemotePath(),
|
event.getRemotePath(),
|
||||||
event.getShouldBeEncrypted(),
|
event.getShouldBeEncrypted(),
|
||||||
publicKey,
|
publicKey,
|
||||||
privateKey);
|
privateKey,
|
||||||
|
storageManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1727,9 +1730,11 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
||||||
String remoteId,
|
String remoteId,
|
||||||
String remotePath,
|
String remotePath,
|
||||||
boolean shouldBeEncrypted,
|
boolean shouldBeEncrypted,
|
||||||
String publicKey,
|
String publicKeyString,
|
||||||
String privateKey) {
|
String privateKeyString,
|
||||||
|
FileDataStorageManager storageManager) {
|
||||||
try {
|
try {
|
||||||
|
Log_OC.d(TAG, "encrypt folder " + folder.getRemoteId());
|
||||||
User user = accountManager.getUser();
|
User user = accountManager.getUser();
|
||||||
OwnCloudClient client = clientFactory.create(user);
|
OwnCloudClient client = clientFactory.create(user);
|
||||||
RemoteOperationResult remoteOperationResult = new ToggleEncryptionRemoteOperation(localId,
|
RemoteOperationResult remoteOperationResult = new ToggleEncryptionRemoteOperation(localId,
|
||||||
|
@ -1741,44 +1746,44 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
||||||
// lock folder
|
// lock folder
|
||||||
String token = EncryptionUtils.lockFolder(folder, client);
|
String token = EncryptionUtils.lockFolder(folder, client);
|
||||||
|
|
||||||
|
OCCapability ocCapability = mContainerActivity.getStorageManager().getCapability(user.getAccountName());
|
||||||
|
|
||||||
|
if (ocCapability.getEndToEndEncryptionApiVersion() == E2EVersion.V2_0) {
|
||||||
// Update metadata
|
// Update metadata
|
||||||
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(folder,
|
Pair<Boolean, DecryptedFolderMetadataFile> metadataPair = EncryptionUtils.retrieveMetadata(folder,
|
||||||
client,
|
client,
|
||||||
privateKey,
|
privateKeyString,
|
||||||
publicKey,
|
publicKeyString,
|
||||||
arbitraryDataProvider,
|
storageManager,
|
||||||
user);
|
user,
|
||||||
|
requireContext(),
|
||||||
|
arbitraryDataProvider);
|
||||||
|
|
||||||
boolean metadataExists = metadataPair.first;
|
boolean metadataExists = metadataPair.first;
|
||||||
DecryptedFolderMetadata metadata = metadataPair.second;
|
DecryptedFolderMetadataFile metadata = metadataPair.second;
|
||||||
|
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
new EncryptionUtilsV2().serializeAndUploadMetadata(folder,
|
||||||
publicKey,
|
metadata,
|
||||||
arbitraryDataProvider,
|
|
||||||
user,
|
|
||||||
folder.getLocalId());
|
|
||||||
|
|
||||||
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(folder,
|
|
||||||
serializedFolderMetadata,
|
|
||||||
token,
|
token,
|
||||||
client,
|
client,
|
||||||
metadataExists,
|
metadataExists,
|
||||||
arbitraryDataProvider,
|
requireContext(),
|
||||||
user);
|
user,
|
||||||
|
storageManager);
|
||||||
|
|
||||||
// unlock folder
|
// unlock folder
|
||||||
EncryptionUtils.unlockFolder(folder, client, token);
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
mAdapter.setEncryptionAttributeForItemID(remoteId, shouldBeEncrypted);
|
mAdapter.setEncryptionAttributeForItemID(remoteId, shouldBeEncrypted);
|
||||||
} else if (remoteOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
|
} else if (remoteOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
|
||||||
Snackbar.make(getRecyclerView(),
|
Snackbar.make(getRecyclerView(),
|
||||||
|
@ -1790,7 +1795,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
||||||
Snackbar.LENGTH_LONG).show();
|
Snackbar.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Throwable e) {
|
||||||
Log_OC.e(TAG, "Error creating encrypted folder", 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 note note message for the receiver. Null or empty for no message
|
||||||
* @param label new label
|
* @param label new label
|
||||||
*/
|
*/
|
||||||
public void shareFileWithSharee(OCFile file, String shareeName, ShareType shareType, int permissions,
|
public void shareFileWithSharee(OCFile file,
|
||||||
boolean hideFileDownload, String password, long expirationTimeInMillis,
|
String shareeName,
|
||||||
String note, String label) {
|
ShareType shareType,
|
||||||
|
int permissions,
|
||||||
|
boolean hideFileDownload,
|
||||||
|
String password,
|
||||||
|
long expirationTimeInMillis,
|
||||||
|
String note,
|
||||||
|
String label,
|
||||||
|
boolean showLoadingDialog) {
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
// TODO check capability?
|
// TODO check capability?
|
||||||
|
if (showLoadingDialog) {
|
||||||
fileActivity.showLoadingDialog(fileActivity.getApplicationContext().
|
fileActivity.showLoadingDialog(fileActivity.getApplicationContext().
|
||||||
getString(R.string.wait_a_moment));
|
getString(R.string.wait_a_moment));
|
||||||
|
}
|
||||||
|
|
||||||
Intent service = new Intent(fileActivity, OperationsService.class);
|
Intent service = new Intent(fileActivity, OperationsService.class);
|
||||||
service.setAction(OperationsService.ACTION_CREATE_SHARE_WITH_SHAREE);
|
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_via_link_unset_password">Unset</string>
|
||||||
|
|
||||||
<string name="share_search">Name, Federated Cloud ID or email address …</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_group_clarification">%1$s (group)</string>
|
||||||
<string name="share_remote_clarification">%1$s (remote)</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_year">Year</string>
|
||||||
<string name="sub_folder_rule_month">Year/Month</string>
|
<string name="sub_folder_rule_month">Year/Month</string>
|
||||||
<string name="sub_folder_rule_day">Year/Month/Day</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>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue