First release of E2E

This commit is contained in:
Jan-Christoph Borchardt 2017-08-23 15:52:14 +02:00 committed by tobiasKaminsky
parent e87bf739cf
commit 34f198cc48
No known key found for this signature in database
GPG key ID: 0E00D4D47D0C5AF7
9 changed files with 1169 additions and 11 deletions

View file

@ -20,8 +20,8 @@
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.owncloud.android" package="com.owncloud.android"
android:versionCode="20000009" android:versionCode="20000052"
android:versionName="2.0.0RC9"> android:versionName="2.0.0-e2e-02">
<!-- GET_ACCOUNTS is needed for API <= 22. <!-- GET_ACCOUNTS is needed for API <= 22.
For API >= 23 results in the addition of CONTACTS group to the list of permissions that may be For API >= 23 results in the addition of CONTACTS group to the list of permissions that may be

View file

@ -64,6 +64,7 @@ import com.owncloud.android.utils.AnalyticsUtils;
import com.owncloud.android.utils.FilesSyncHelper; import com.owncloud.android.utils.FilesSyncHelper;
import com.owncloud.android.utils.PermissionUtil; import com.owncloud.android.utils.PermissionUtil;
import com.owncloud.android.utils.ReceiversHelper; import com.owncloud.android.utils.ReceiversHelper;
import com.owncloud.android.utils.EncryptionUtils;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
@ -71,8 +72,13 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.crypto.Cipher;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import static com.owncloud.android.utils.EncryptionUtils.generateIV;
import static com.owncloud.android.utils.EncryptionUtils.generateKey;
/** /**
* Main Application of the project * Main Application of the project

View file

@ -1055,8 +1055,19 @@ public class FileUploader extends Service
mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton(). mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
getClientFor(ocAccount, this); getClientFor(ocAccount, this);
/// perform the upload
uploadResult = mCurrentUpload.execute(mUploadClient, mStorageManager); // // If parent folder is encrypted, upload file encrypted
// OCFile parent = mStorageManager.getFileByPath(mCurrentUpload.getFile().getParentRemotePath());
// if (parent.isEncrypted()) {
// UploadEncryptedFileOperation uploadEncryptedFileOperation =
// new UploadEncryptedFileOperation(parent, mCurrentUpload);
//
// uploadResult = uploadEncryptedFileOperation.execute(mUploadClient, mStorageManager);
// } else {
/// perform the regular upload
uploadResult = mCurrentUpload.execute(mUploadClient, mStorageManager);
// }
} catch (Exception e) { } catch (Exception e) {

View file

@ -0,0 +1,880 @@
/*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2017 Tobias Kaminsky
* Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.operations;
import android.accounts.Account;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import com.google.gson.reflect.TypeToken;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.network.ProgressiveDataTransferer;
import com.owncloud.android.lib.common.operations.OperationCancelledException;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.files.ChunkedUploadRemoteFileOperation;
import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
import com.owncloud.android.lib.resources.files.GetMetadataOperation;
import com.owncloud.android.lib.resources.files.LockFileOperation;
import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
import com.owncloud.android.lib.resources.files.RemoteFile;
import com.owncloud.android.lib.resources.files.StoreMetadataOperation;
import com.owncloud.android.lib.resources.files.UnlockFileOperation;
import com.owncloud.android.lib.resources.files.UpdateMetadataOperation;
import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation;
import com.owncloud.android.operations.common.SyncOperation;
import com.owncloud.android.utils.EncryptionUtils;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.MimeType;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.RequestEntity;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.owncloud.android.utils.EncryptionUtils.encodeStringToBase64Bytes;
/**
* Operation performing the update in the ownCloud server
* of a file that was modified locally.
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public class UploadEncryptedFileOperation extends SyncOperation {
private static final String TAG = UploadEncryptedFileOperation.class.getSimpleName();
public static final int CREATED_BY_USER = 0;
public static final int CREATED_AS_INSTANT_PICTURE = 1;
public static final int CREATED_AS_INSTANT_VIDEO = 2;
private OCFile parentFile;
/**
* OCFile which is to be uploaded.
*/
private OCFile ocFile;
private int localBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
private final String originalStoragePath;
private boolean chunked = false;
private boolean mRemoteFolderToBeCreated = false;
private int mCreatedBy = CREATED_BY_USER;
private long mOCUploadId = -1;
/**
* Local path to file which is to be uploaded (before any possible renaming or moving).
*/
private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
private final AtomicBoolean mUploadStarted = new AtomicBoolean(false);
private Context context;
private UploadRemoteFileOperation mUploadOperation;
protected RequestEntity mEntity = null;
private Account account;
private ArbitraryDataProvider arbitraryDataProvider;
public UploadEncryptedFileOperation(OCFile parent, UploadFileOperation uploadFileOperation) {
parentFile = parent;
ocFile = uploadFileOperation.getFile();
account = uploadFileOperation.getAccount();
chunked = uploadFileOperation.isChunkedUploadSupported();
context = uploadFileOperation.getContext();
localBehaviour = uploadFileOperation.getLocalBehaviour();
originalStoragePath = uploadFileOperation.getStoragePath();
arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
}
public Account getAccount() {
return account;
}
public String getFileName() {
return (ocFile != null) ? ocFile.getFileName() : null;
}
public OCFile getFile() {
return ocFile;
}
public String getStoragePath() {
return ocFile.getStoragePath();
}
public String getRemotePath() {
return ocFile.getParentRemotePath() + ocFile.getEncryptedFileName();
}
public String getMimeType() {
return ocFile.getMimetype();
}
public void setRemoteFolderToBeCreated() {
mRemoteFolderToBeCreated = true;
}
public void setCreatedBy(int createdBy) {
mCreatedBy = createdBy;
if (createdBy < CREATED_BY_USER || CREATED_AS_INSTANT_VIDEO < createdBy) {
mCreatedBy = CREATED_BY_USER;
}
}
public int getCreatedBy() {
return mCreatedBy;
}
public boolean isInstantPicture() {
return mCreatedBy == CREATED_AS_INSTANT_PICTURE;
}
public boolean isInstantVideo() {
return mCreatedBy == CREATED_AS_INSTANT_VIDEO;
}
public void setOCUploadId(long id) {
mOCUploadId = id;
}
public long getOCUploadId() {
return mOCUploadId;
}
public Set<OnDatatransferProgressListener> getDataTransferListeners() {
return mDataTransferListeners;
}
public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.add(listener);
}
if (mEntity != null) {
((ProgressiveDataTransferer) mEntity).addDatatransferProgressListener(listener);
}
if (mUploadOperation != null) {
mUploadOperation.addDatatransferProgressListener(listener);
}
}
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.remove(listener);
}
if (mEntity != null) {
((ProgressiveDataTransferer) mEntity).removeDatatransferProgressListener(listener);
}
if (mUploadOperation != null) {
mUploadOperation.removeDatatransferProgressListener(listener);
}
}
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result = null;
boolean metadataExists = false;
String token = null;
mCancellationRequested.set(false);
mUploadStarted.set(true);
File temporalFile = null;
File originalFile = new File(ocFile.getStoragePath());
File expectedFile = null;
FileLock fileLock = null;
String privateKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PRIVATE_KEY);
String publicKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PUBLIC_KEY);
try {
/// check if the file continues existing before schedule the operation
if (!originalFile.exists()) {
Log_OC.d(TAG, ocFile.getStoragePath() + " not exists anymore");
throw new FileNotFoundException();
}
/// check the existence of the parent folder for the file to upload
String remoteParentPath = new File(getRemotePath()).getParent();
remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
result = grantFolderExistence(remoteParentPath, client);
if (!result.isSuccess()) {
return result;
}
// TODO automatic rename? UploadFileOperation:365
/// set parent local id in uploading file
// OCFile parent = getStorageManager().getFileByPath(remoteParentPath);
// ocFile.setParentId(parent.getFileId());
// if (mCancellationRequested.get()) {
// throw new OperationCancelledException();
// }
// Get the last modification date of the file from the file system
Long timeStampLong = originalFile.lastModified() / 1000;
String timeStamp = timeStampLong.toString();
// Lock folder
LockFileOperation lockFileOperation = new LockFileOperation(parentFile.getLocalId());
RemoteOperationResult lockFileOperationResult = lockFileOperation.execute(client);
if (lockFileOperationResult.isSuccess()) {
token = (String) lockFileOperationResult.getData().get(0);
} else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
throw new Exception("Forbidden! Please try again later.)");
} else {
throw new Exception("Unknown error!");
}
// Update metadata
GetMetadataOperation getMetadataOperation = new GetMetadataOperation(parentFile.getLocalId());
RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client);
DecryptedFolderMetadata metadata;
if (getMetadataOperationResult.isSuccess()) {
metadataExists = true;
// decrypt metadata
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
});
metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
} else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
// new metadata
metadata = new DecryptedFolderMetadata();
metadata.metadata = new DecryptedFolderMetadata.Metadata();
metadata.metadata.metadataKeys = new HashMap<>();
String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
metadata.metadata.metadataKeys.put(0, encryptedMetadataKey);
} else {
// TODO error
throw new Exception("something wrong");
}
// Key
byte[] key = null;
try {
// TODO change key if file has changed, e.g. when file is updated
key = encodeStringToBase64Bytes(metadata.files.get(ocFile.getFileName()).encrypted.key);
} catch (Exception e) {
// no key found
}
if (key == null || key.length == 0) {
key = EncryptionUtils.generateKey();
}
// IV
byte[] iv = null;
try {
iv = encodeStringToBase64Bytes(metadata.files.get(ocFile.getFileName()).initializationVector);
} catch (Exception e) {
// no iv found
}
if (iv == null || iv.length == 0) {
iv = EncryptionUtils.generateIV();
}
EncryptionUtils.EncryptedFile encryptedFile = EncryptionUtils.encryptFile(ocFile, key, iv);
// new random file name, check if it exists in metadata
String encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
while (metadata.files.get(encryptedFileName) != null) {
encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
}
ocFile.setEncryptedFileName(encryptedFileName);
File encryptedTempFile = File.createTempFile("encFile", encryptedFileName);
FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
fileOutputStream.write(encryptedFile.encryptedBytes);
fileOutputStream.close();
/// perform the upload
if (chunked &&
(new File(ocFile.getStoragePath())).length() >
ChunkedUploadRemoteFileOperation.CHUNK_SIZE) {
mUploadOperation = new ChunkedUploadRemoteFileOperation(context, encryptedTempFile.getAbsolutePath(),
ocFile.getParentRemotePath() + encryptedFileName, ocFile.getMimetype(),
ocFile.getEtagInConflict(), timeStamp);
} else {
mUploadOperation = new UploadRemoteFileOperation(encryptedTempFile.getAbsolutePath(),
ocFile.getParentRemotePath() + encryptedFileName, ocFile.getMimetype(),
ocFile.getEtagInConflict(), timeStamp);
}
Iterator<OnDatatransferProgressListener> listener = mDataTransferListeners.iterator();
while (listener.hasNext()) {
mUploadOperation.addDatatransferProgressListener(listener.next());
}
if (mCancellationRequested.get()) {
throw new OperationCancelledException();
}
// FileChannel channel = null;
// try {
// channel = new RandomAccessFile(ocFile.getStoragePath(), "rw").getChannel();
// fileLock = channel.tryLock();
// } catch (FileNotFoundException e) {
// if (temporalFile == null) {
// String temporalPath = FileStorageUtils.getTemporalPath(account.name) + ocFile.getRemotePath();
// ocFile.setStoragePath(temporalPath);
// temporalFile = new File(temporalPath);
//
// result = copy(originalFile, temporalFile);
//
// if (result != null) {
// return result;
// } else {
// if (temporalFile.length() == originalFile.length()) {
// channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
// fileLock = channel.tryLock();
// } else {
// while (temporalFile.length() != originalFile.length()) {
// Files.deleteIfExists(Paths.get(temporalPath));
// result = copy(originalFile, temporalFile);
//
// if (result != null) {
// return result;
// } else {
// channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").
// getChannel();
// fileLock = channel.tryLock();
// }
// }
// }
// }
// } else {
// channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
// fileLock = channel.tryLock();
// }
// }
result = mUploadOperation.execute(client);
/// move local temporal file or original file to its corresponding
// location in the ownCloud local folder
if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) {
result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
}
if (result.isSuccess()) {
// upload metadata
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
data.filename = ocFile.getFileName();
data.mimetype = ocFile.getMimetype();
data.key = EncryptionUtils.encodeBytesToBase64String(key);
decryptedFile.encrypted = data;
decryptedFile.initializationVector = EncryptionUtils.encodeBytesToBase64String(iv);
decryptedFile.authenticationTag = encryptedFile.authenticationTag;
metadata.files.put(encryptedFileName, decryptedFile);
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
privateKey);
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
// upload metadata
RemoteOperationResult uploadMetadataOperationResult;
if (metadataExists) {
// update metadata
UpdateMetadataOperation storeMetadataOperation = new UpdateMetadataOperation(parentFile.getLocalId(),
serializedFolderMetadata, token);
uploadMetadataOperationResult = storeMetadataOperation.execute(client);
} else {
// store metadata
StoreMetadataOperation storeMetadataOperation = new StoreMetadataOperation(parentFile.getLocalId(),
serializedFolderMetadata);
uploadMetadataOperationResult = storeMetadataOperation.execute(client);
}
if (!uploadMetadataOperationResult.isSuccess()) {
throw new Exception();
}
}
} catch (FileNotFoundException e) {
Log_OC.d(TAG, ocFile.getStoragePath() + " not exists anymore");
result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
} catch (OverlappingFileLockException e) {
Log_OC.d(TAG, "Overlapping file lock exception");
result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
} catch (Exception e) {
result = new RemoteOperationResult(e);
} finally {
mUploadStarted.set(false);
// unlock file
if (token != null) {
UnlockFileOperation unlockFileOperation = new UnlockFileOperation(parentFile.getLocalId(), token);
RemoteOperationResult unlockFileOperationResult = unlockFileOperation.execute(client);
if (!unlockFileOperationResult.isSuccess()) {
Log_OC.e(TAG, "Failed to unlock " + parentFile.getLocalId());
}
}
if (fileLock != null) {
try {
fileLock.release();
} catch (IOException e) {
Log_OC.e(TAG, "Failed to unlock file with path " + ocFile.getStoragePath());
}
}
if (temporalFile != null && !originalFile.equals(temporalFile)) {
temporalFile.delete();
}
if (result == null) {
result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
}
if (result.isSuccess()) {
Log_OC.i(TAG, "Upload of " + ocFile.getStoragePath() + " to " + ocFile.getRemotePath() + ": " +
result.getLogMessage());
} else {
if (result.getException() != null) {
if (result.isCancelled()) {
Log_OC.w(TAG, "Upload of " + ocFile.getStoragePath() + " to " + ocFile.getRemotePath() +
": " + result.getLogMessage());
} else {
Log_OC.e(TAG, "Upload of " + ocFile.getStoragePath() + " to " + ocFile.getRemotePath() +
": " + result.getLogMessage(), result.getException());
}
} else {
Log_OC.e(TAG, "Upload of " + ocFile.getStoragePath() + " to " + ocFile.getRemotePath() +
": " + result.getLogMessage());
}
}
}
switch (localBehaviour) {
case FileUploader.LOCAL_BEHAVIOUR_FORGET:
String temporalPath = FileStorageUtils.getTemporalPath(account.name) + ocFile.getRemotePath();
if (originalStoragePath.equals(temporalPath)) {
// delete local file is was pre-copied in temporary folder (see .ui.helpers.UriUploader)
temporalFile = new File(temporalPath);
temporalFile.delete();
}
ocFile.setStoragePath("");
saveUploadedFile(client);
break;
case FileUploader.LOCAL_BEHAVIOUR_DELETE:
Log_OC.d(TAG, "Delete source file");
originalFile.delete();
getStorageManager().deleteFileInMediaScan(originalFile.getAbsolutePath());
saveUploadedFile(client);
break;
case FileUploader.LOCAL_BEHAVIOUR_COPY:
if (temporalFile != null) {
try {
move(temporalFile, expectedFile);
} catch (IOException e) {
e.printStackTrace();
}
}
ocFile.setStoragePath(expectedFile.getAbsolutePath());
saveUploadedFile(client);
FileDataStorageManager.triggerMediaScan(expectedFile.getAbsolutePath());
break;
case FileUploader.LOCAL_BEHAVIOUR_MOVE:
String expectedPath = FileStorageUtils.getDefaultSavePathFor(account.name, ocFile);
expectedFile = new File(expectedPath);
try {
move(originalFile, expectedFile);
} catch (IOException e) {
e.printStackTrace();
}
getStorageManager().deleteFileInMediaScan(originalFile.getAbsolutePath());
ocFile.setStoragePath(expectedFile.getAbsolutePath());
saveUploadedFile(client);
FileDataStorageManager.triggerMediaScan(expectedFile.getAbsolutePath());
break;
}
return result;
}
/**
* Checks the existence of the folder where the current file will be uploaded both
* in the remote server and in the local database.
* <p/>
* If the upload is set to enforce the creation of the folder, the method tries to
* create it both remote and locally.
*
* @param pathToGrant Full remote path whose existence will be granted.
* @return An {@link OCFile} instance corresponding to the folder where the file
* will be uploaded.
*/
private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) {
RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, context, false);
RemoteOperationResult result = operation.execute(client);
if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mRemoteFolderToBeCreated) {
SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true);
result = syncOp.execute(client, getStorageManager());
}
if (result.isSuccess()) {
OCFile parentDir = getStorageManager().getFileByPath(pathToGrant);
if (parentDir == null) {
parentDir = createLocalFolder(pathToGrant);
}
if (parentDir != null) {
result = new RemoteOperationResult(ResultCode.OK);
} else {
result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
}
}
return result;
}
private OCFile createLocalFolder(String remotePath) {
String parentPath = new File(remotePath).getParent();
parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ?
parentPath : parentPath + OCFile.PATH_SEPARATOR;
OCFile parent = getStorageManager().getFileByPath(parentPath);
if (parent == null) {
parent = createLocalFolder(parentPath);
}
if (parent != null) {
OCFile createdFolder = new OCFile(remotePath);
createdFolder.setMimetype(MimeType.DIRECTORY);
createdFolder.setParentId(parent.getFileId());
getStorageManager().saveFile(createdFolder);
return createdFolder;
}
return null;
}
/**
* Checks if remotePath does not exist in the server and returns it, or adds
* a suffix to it in order to avoid the server file is overwritten.
*
* @param wc
* @param remotePath
* @return
*/
private String getAvailableRemotePath(OwnCloudClient wc, String remotePath) {
boolean check = existsFile(wc, remotePath);
if (!check) {
return remotePath;
}
int pos = remotePath.lastIndexOf('.');
String suffix = "";
String extension = "";
if (pos >= 0) {
extension = remotePath.substring(pos + 1);
remotePath = remotePath.substring(0, pos);
}
int count = 2;
do {
suffix = " (" + count + ")";
if (pos >= 0) {
check = existsFile(wc, remotePath + suffix + "." + extension);
} else {
check = existsFile(wc, remotePath + suffix);
}
count++;
} while (check);
if (pos >= 0) {
return remotePath + suffix + "." + extension;
} else {
return remotePath + suffix;
}
}
private boolean existsFile(OwnCloudClient client, String remotePath) {
ExistenceCheckRemoteOperation existsOperation =
new ExistenceCheckRemoteOperation(remotePath, context, false);
RemoteOperationResult result = existsOperation.execute(client);
return result.isSuccess();
}
/**
* Allows to cancel the actual upload operation. If actual upload operating
* is in progress it is cancelled, if upload preparation is being performed
* upload will not take place.
*/
public void cancel() {
if (mUploadOperation == null) {
if (mUploadStarted.get()) {
Log_OC.d(TAG, "Cancelling upload during upload preparations.");
mCancellationRequested.set(true);
} else {
Log_OC.e(TAG, "No upload in progress. This should not happen.");
}
} else {
Log_OC.d(TAG, "Cancelling upload during actual upload operation.");
mUploadOperation.cancel();
}
}
/**
* As soon as this method return true, upload can be cancel via cancel().
*/
public boolean isUploadInProgress() {
return mUploadStarted.get();
}
// /**
// * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
// * TODO use Exceptions instead
// *
// * @param sourceFile Source file to copy.
// * @param targetFile Target location to copy the file.
// * @return {@link RemoteOperationResult}
// * @throws IOException
// */
// private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOException {
// Log_OC.d(TAG, "Copying local file");
//
// RemoteOperationResult result = null;
//
// if (FileStorageUtils.getUsableSpace(account.name) < sourceFile.length()) {
// result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
// return result; // error condition when the file should be copied
//
// } else {
// Log_OC.d(TAG, "Creating temporal folder");
// File temporalParent = targetFile.getParentFile();
// temporalParent.mkdirs();
// if (!temporalParent.isDirectory()) {
// throw new IOException(
// "Unexpected error: parent directory could not be created");
// }
// Log_OC.d(TAG, "Creating temporal file");
// targetFile.createNewFile();
// if (!targetFile.isFile()) {
// throw new IOException(
// "Unexpected error: target file could not be created");
// }
//
// Log_OC.d(TAG, "Copying file contents");
// InputStream in = null;
// OutputStream out = null;
//
// try {
// if (!mOriginalStoragePath.equals(targetFile.getAbsolutePath())) {
// // In case document provider schema as 'content://'
// if (mOriginalStoragePath.startsWith(UriUtils.URI_CONTENT_SCHEME)) {
// Uri uri = Uri.parse(mOriginalStoragePath);
// in = context.getContentResolver().openInputStream(uri);
// } else {
// in = new FileInputStream(sourceFile);
// }
// out = new FileOutputStream(targetFile);
// int nRead;
// byte[] buf = new byte[4096];
// while (!mCancellationRequested.get() &&
// (nRead = in.read(buf)) > -1) {
// out.write(buf, 0, nRead);
// }
// out.flush();
//
// } // else: weird but possible situation, nothing to copy
//
// if (mCancellationRequested.get()) {
// result = new RemoteOperationResult(new OperationCancelledException());
// return result;
// }
//
// } catch (Exception e) {
// result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
// return result;
//
// } finally {
// try {
// if (in != null) {
// in.close();
// }
// } catch (Exception e) {
// Log_OC.d(TAG, "Weird exception while closing input stream for " +
// mOriginalStoragePath + " (ignoring)", e);
// }
// try {
// if (out != null) {
// out.close();
// }
// } catch (Exception e) {
// Log_OC.d(TAG, "Weird exception while closing output stream for " +
// targetFile.getAbsolutePath() + " (ignoring)", e);
// }
// }
// }
// return result;
// }
/**
* TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
* TODO use Exceptions instead
* <p>
* TODO refactor both this and 'copy' in a single method
*
* @param sourceFile Source file to move.
* @param targetFile Target location to move the file.
* @return {@link RemoteOperationResult}
* @throws IOException
*/
private void move(File sourceFile, File targetFile) throws IOException {
if (!targetFile.equals(sourceFile)) {
File expectedFolder = targetFile.getParentFile();
expectedFolder.mkdirs();
if (expectedFolder.isDirectory()) {
if (!sourceFile.renameTo(targetFile)) {
// try to copy and then delete
targetFile.createNewFile();
FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
FileChannel outChannel = new FileOutputStream(targetFile).getChannel();
try {
inChannel.transferTo(0, inChannel.size(), outChannel);
sourceFile.delete();
} catch (Exception e) {
ocFile.setStoragePath(""); // forget the local file
// by now, treat this as a success; the file was uploaded
// the best option could be show a warning message
} finally {
if (inChannel != null) {
inChannel.close();
}
if (outChannel != null) {
outChannel.close();
}
}
}
} else {
ocFile.setStoragePath("");
}
}
}
/**
* Saves a OC File after a successful upload.
* <p/>
* A PROPFIND is necessary to keep the props in the local database
* synchronized with the server, specially the modification time and Etag
* (where available)
* <p/>
*/
private void saveUploadedFile(OwnCloudClient client) {
OCFile file = ocFile;
if (file.fileExists()) {
file = getStorageManager().getFileById(file.getFileId());
}
long syncDate = System.currentTimeMillis();
file.setLastSyncDateForData(syncDate);
// new PROPFIND to keep data consistent with server
// in theory, should return the same we already have
// TODO from the appropriate OC server version, get data from last PUT response headers, instead
// TODO of a new PROPFIND; the latter may fail, specially for chunked uploads
ReadRemoteFileOperation operation = new ReadRemoteFileOperation(getRemotePath());
RemoteOperationResult result = operation.execute(client);
if (result.isSuccess()) {
updateOCFile(file, (RemoteFile) result.getData().get(0));
file.setLastSyncDateForProperties(syncDate);
} else {
Log_OC.e(TAG, "Error reading properties of file after successful upload; this is gonna hurt...");
}
file.setEncrypted(true);
file.setStoragePath("");
file.setParentId(parentFile.getFileId());
getStorageManager().saveFile(file);
getStorageManager().saveConflict(file, null);
FileDataStorageManager.triggerMediaScan(file.getStoragePath());
}
private void updateOCFile(OCFile file, RemoteFile remoteFile) {
file.setCreationTimestamp(remoteFile.getCreationTimestamp());
file.setFileLength(remoteFile.getLength());
// file.setMimetype(file.getMimetype());
file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
file.setEtag(remoteFile.getEtag());
file.setRemoteId(remoteFile.getRemoteId());
}
public interface OnRenameListener {
void onRenameUpload();
}
}

View file

@ -87,6 +87,8 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import static com.owncloud.android.utils.EncryptionUtils.encodeStringToBase64Bytes;
/** /**
* Operation performing the update in the ownCloud server * Operation performing the update in the ownCloud server
@ -387,6 +389,50 @@ public class UploadFileOperation extends SyncOperation {
} }
} }
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile parentFile) {
RemoteOperationResult result = null;
File temporalFile = null;
File originalFile = new File(mOriginalStoragePath);
File expectedFile = null;
FileLock fileLock = null;
long size = 0;
boolean metadataExists = false;
String token = null;
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContext().getContentResolver());
String privateKey = arbitraryDataProvider.getValue(getAccount().name, EncryptionUtils.PRIVATE_KEY);
String publicKey = arbitraryDataProvider.getValue(getAccount().name, EncryptionUtils.PUBLIC_KEY);
/// check the existence of the parent folder for the file to upload
String remoteParentPath = new File(getRemotePath()).getParent();
remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
RemoteOperationResult result = grantFolderExistence(remoteParentPath, client);
if (!result.isSuccess()) {
return result;
}
OCFile parent = getStorageManager().getFileByPath(remoteParentPath);
mFile.setParentId(parent.getFileId());
if (parent.isEncrypted()) {
Log_OC.d(TAG, "encrypted upload");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return encryptedUpload(client, parent);
} else {
Log_OC.e(TAG, "Encrypted upload on old Android API");
return new RemoteOperationResult(ResultCode.OLD_ANDROID_API);
}
} else {
Log_OC.d(TAG, "normal upload");
return normalUpload(client);
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT) @RequiresApi(api = Build.VERSION_CODES.KITKAT)
private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile parentFile) { private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile parentFile) {
RemoteOperationResult result = null; RemoteOperationResult result = null;
@ -522,11 +568,33 @@ public class UploadFileOperation extends SyncOperation {
String timeStamp = timeStampLong.toString(); String timeStamp = timeStampLong.toString();
// Key, always generate new one // Key
byte[] key = EncryptionUtils.generateKey(); byte[] key = null;
try {
// TODO change key if file has changed, e.g. when file is updated
key = encodeStringToBase64Bytes(metadata.files.get(mFile.getFileName()).encrypted.key);
} catch (Exception e) {
// no key found
}
if (key == null || key.length == 0) {
key = EncryptionUtils.generateKey();
}
// IV
byte[] iv = null;
try {
iv = encodeStringToBase64Bytes(metadata.files.get(mFile.getFileName()).initializationVector);
} catch (Exception e) {
// no iv found
}
if (iv == null || iv.length == 0) {
iv = EncryptionUtils.generateIV();
}
// IV, always generate new one
byte[] iv = EncryptionUtils.generateIV();
EncryptionUtils.EncryptedFile encryptedFile = EncryptionUtils.encryptFile(mFile, key, iv); EncryptionUtils.EncryptedFile encryptedFile = EncryptionUtils.encryptFile(mFile, key, iv);
@ -566,6 +634,45 @@ public class UploadFileOperation extends SyncOperation {
throw new OperationCancelledException(); throw new OperationCancelledException();
} }
// FileChannel channel = null;
// try {
// channel = new RandomAccessFile(ocFile.getStoragePath(), "rw").getChannel();
// fileLock = channel.tryLock();
// } catch (FileNotFoundException e) {
// if (temporalFile == null) {
// String temporalPath = FileStorageUtils.getTemporalPath(account.name) + ocFile.getRemotePath();
// ocFile.setStoragePath(temporalPath);
// temporalFile = new File(temporalPath);
//
// result = copy(originalFile, temporalFile);
//
// if (result != null) {
// return result;
// } else {
// if (temporalFile.length() == originalFile.length()) {
// channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
// fileLock = channel.tryLock();
// } else {
// while (temporalFile.length() != originalFile.length()) {
// Files.deleteIfExists(Paths.get(temporalPath));
// result = copy(originalFile, temporalFile);
//
// if (result != null) {
// return result;
// } else {
// channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").
// getChannel();
// fileLock = channel.tryLock();
// }
// }
// }
// }
// } else {
// channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
// fileLock = channel.tryLock();
// }
// }
result = mUploadOperation.execute(client); result = mUploadOperation.execute(client);
/// move local temporal file or original file to its corresponding /// move local temporal file or original file to its corresponding
@ -668,7 +775,6 @@ public class UploadFileOperation extends SyncOperation {
switch (mLocalBehaviour) { switch (mLocalBehaviour) {
case FileUploader.LOCAL_BEHAVIOUR_FORGET: case FileUploader.LOCAL_BEHAVIOUR_FORGET:
default:
String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath(); String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
if (mOriginalStoragePath.equals(temporalPath)) { if (mOriginalStoragePath.equals(temporalPath)) {
// delete local file is was pre-copied in temporary folder (see .ui.helpers.UriUploader) // delete local file is was pre-copied in temporary folder (see .ui.helpers.UriUploader)

View file

@ -52,6 +52,7 @@ import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.widget.SearchView; import android.support.v7.widget.SearchView;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;

View file

@ -270,6 +270,8 @@
<string name="favorite_switch">Available offline</string> <string name="favorite_switch">Available offline</string>
<string name="encrypted">Set as encrypted</string> <string name="encrypted">Set as encrypted</string>
<string name="unset_encrypted">Unset encryption</string> <string name="unset_encrypted">Unset encryption</string>
<string name="encrypted">Set as encrypted</string>
<string name="unset_encrypted">Unset encryption</string>
<string name="common_rename">Rename</string> <string name="common_rename">Rename</string>
<string name="common_remove">Delete</string> <string name="common_remove">Delete</string>
<string name="confirmation_remove_file_alert">Do you really want to delete %1$s?</string> <string name="confirmation_remove_file_alert">Do you really want to delete %1$s?</string>

View file

@ -19,8 +19,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.owncloud.android" package="com.owncloud.android"
android:versionCode="20000009" android:versionCode="20000052"
android:versionName="2.0.0RC9"> android:versionName="2.0.0-e2e-02">
<application <application
android:name=".MainApp" android:name=".MainApp"

View file

@ -0,0 +1,152 @@
/*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2017 Tobias Kaminsky
* Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.utils;
import android.os.Build;
import android.support.annotation.RequiresApi;
import com.google.gson.reflect.TypeToken;
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
import org.junit.Test;
import java.util.HashMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public class EncryptionTest {
private String encryptedString = "{\"metadata\":{\"encrypted\":\"np1sIwoAFCb/vRqV/jWOIe1UtyVO02EJhvPoh3VwcZuiSbDjwQO5QHUWtKXpHLyX6wfbkRX6nr8mSG0+HhLRud1t126UMxQK5BNINu99WlzPMa5PaKhTXlpuRUf3tR6PTQ\\u003d\\u003d\",\"initializationVector\":" +
"\"kahzfT4u86Knc+e3\",\"sharing\":{\"recipient\":{\"blah@schiessle.org\":\"PUBLIC KEY\"," +
"\"bjoern@schiessle.org\":\"PUBLIC KEY\"},\"signature\":\"HMACOFRECIPIENTANDNEWESTMETADATAKEY\"}," +
"\"version\":1},\"files\":{\"ia7OEEEyXMoRa1QWQk8r\":{\"encrypted\":\"yl52TIccvo62LezCaFjQFJs7a1Q281pOuj59oNXMX7ti+7+h1SjK1AAk1HuwT+CI7BT64+R0ZLgyR/vBPjWvAQuxi9JWgsCjFMX91Mv2m2zI/bNQCarczOfnmf4FZ3Nv6yPLSjShmfQzemQ99Z3g7UHyrZ6pKT18m17IueJHF3V5kOhd9vcH\",\"metadataKey\":0," +
"\"initializationVector\":\"+mHu52HyZq+pAAIN\"}," +
"\"n9WXAIXO2wRY4R8nXwmo\":{\"encrypted\":\"Z9YTAgY/0YqKQlDwiqENcZRRupjgmJ1f0bTy0hOHP2/mHxFtoHCftT4STvt21OJMx8wF6V3cquQIGJ976WxkhA4SQxaQNpznhm1W9e8y+B5x8QnxSasYnOSFtZ/xVgQq6IZRjvYdPo7rvZk49hnqkwnUyvqtCj14aCE42qoxVZCd9M6XaZEBTA\\u003d\\u003d\",\"metadataKey\":0,\"initializationVector\":" +
"\"sOFd17hCKWIv0gyB\"}}}";
private String decryptedString = "{\"metadata\":{\"encrypted\":{\"metadataKeys\":{\"0\":" +
"\"s4k4LPDpxoO53TKwem3Lo1\",\"2\":\"\",\"3\":\"NEWESTMETADATAKEY\"}},\"initializationVector\":" +
"\"kahzfT4u86Knc+e3\",\"sharing\":{\"recipient\":{\"blah@schiessle.org\":\"PUBLIC KEY\"," +
"\"bjoern@schiessle.org\":\"PUBLIC KEY\"},\"signature\":\"HMACOFRECIPIENTANDNEWESTMETADATAKEY\"}," +
"\"version\":1},\"files\":{\"ia7OEEEyXMoRa1QWQk8r\":{\"encrypted\":{\"key\":" +
"\"jtboLmgGR1OQf2uneqCVHpklQLlIwWL5TXAQ0keK\",\"filename\":\"test.txt\",\"authenticationTag\":" +
"\"HMAC of file\",\"version\":1},\"metadataKey\":0,\"initializationVector\":\"+mHu52HyZq+pAAIN\"}," +
"\"n9WXAIXO2wRY4R8nXwmo\":{\"encrypted\":{\"key\":\"s4k4LPDpxoO53TKwem3Lo1yJnbNUYH2KLrSFT8Ea\"," +
"\"filename\":\"test2.txt\",\"authenticationTag\":\"HMAC of file\",\"version\":1}," +
"\"metadataKey\":0,\"initializationVector\":\"sOFd17hCKWIv0gyB\"}}}";
private String privateKey = "TUlJRXZRSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2N3Z2dTakFnRUFBb0lCQVFEV0FKMDNuNlBLVDNJWlZTT1paSzgyd3dqRTVWSW4vclZ5L0VLbmsyMVgzQ1dPbkdsb0ZrUXdiSWFsYjlkVWNRcmQzM250NVFoMmhJeXM5TkdsMjdMUmhreU1qemYzaFdzbnljSVhZQURVSExweGJZbWpJRzRjbWlNQnI4ckhjVFEvM0dUTlBKWG00ZzUwQlZEOXZlRThoMkQrTXdIQXlTSkxZcG5FaDBKWExES0pvVnpQYldxdVZVdHZNMmdmWVFHNFgwbS9kamR5VzJ4ZGN2UkpZeUtpWDZ3UmY3U3ViaWJkR1V1MHgxckhRdGhnM1dtaXdLd09NR2pQQ3oxRDAwMC9vWjZnckRHcW03TG5XMVZaN0VNTWtWWjVjVlc4NlpGQ2lrcXNtcDd1MVg1YjdCTzBiaThyV2Z0Zm8zZnJaOUptdmkvZHpmeXo2RFRFOWIrZU9CNzlBZ01CQUFFQ2dnRUJBSURYYzlCR1k5VnRDWFBwQjNyVjNJdXExcis4bFQ4UklldHRweSsvR2dqWXVSL29XYW5hSmduRmZUZGpZNUFxVXZHTUY1dTcxZUdOSWlrTGFLRmo2WUF1VEM0Z0dBRUZLYU9WM0M0NGxhY2UrTDFMeHA4WTZsSjhGbkZ3aGpTWG1tNk1ZWUFUWnVqUDF3WFJJWmJ6V0FVYU9MSXl3VzV4YWgxYTZ0c2cyRGNrZ2h3YVoxMURiRTVkNEpNNXl0Q1J6YmZzWTA3cElrUmU4WlFLcm1sMXU0THlsWXVHVHBDZGMwSnNXdUx3d1NWbm8vK056RURiVVNKZThIRmpnM1k3L2JkajgzNE9INzBtQWdBVUdEQTJqQ1BqY1hzNHJnN25udkhuQm5WcVFaemFvVWhHYzFOak9pZzMwNitUV25OL1JUNWc0TThucDNuMXZETWF2dVBiM0lFQ2dZRUE2NVpQV243UlYxZVVVS20zcVRUN1BSa3NuLzZDVWdFaFQ1WDloc0c3cXJXMUVlalhuODE2S1A0R3VsaDc1S0dhQ3hUVmdGZDhHdHF1QVU3MGdaKzB5MHVGd2hTdGtWN1A0K2xlUXlYSmxtNDNWNU9zT3Jxb25ocTZyOCthZ3lGbnNuOEhGd3lVcG1VM0FqR2tXVUNzYWFNdWM4SEt0ajU4RkdGRlh6K3ZlcDBDZ1lFQTZJdUZVZmljLytpUXpsVjczTDdWSS9kRjJNcFRPeHVKb1c4MmM1OE1tdEhGMFFQSktlYU5YVGJuYzluUXZ0aFBWM3c1eEZWc3FkN3pKOEM4ZkJ5S21Xc2ZLOU1abGQvY3RZTk9EMTNLWlM2WTNvWXpGVU5ZRzFPSEhpamNZT3RSWUsvY3k0cS9SRnExQ0pZVCtPaEVTOXlmeGFqZlZXdzFPdGxNZGNuNlYrRUNnWUFwNlN5bTBjYldQZnRodWorMU4zcTJyT0xXZDhXaFp4Z1ErNE1GMVROWXRFakpMZDRtVEx5OXpDdFFQV3VWQ2ZiSW4rVTNsdGk2UWtzUWFvWnZCUVY1NFM2amoyQXRhMnVhaFNyQzBWY2lqdXNEaG43dVY4U2xrK1hBWHpPQ3ZvK2ZIcUFaUnFDdlZYUkt6S0FMVE1rZlpldGVwb3cwamJzdk9QckpiaC8rdFFLQmdIQUFpd1R4RmtVWGNXOC9zdm1lSERCSGI2ZTd3eHlyNWIwUVFJeXRwVGVJSTV2SkZBR1BYclR2dGNpUnR6M0VGMnJPbFZBZnlNZUViMTdOTUxzaVVBc1draHZjZiswMHRpdmlneDFaa2hycnQ0c3QzYnEzQmQrYmVtK25SSVdWc1VzOVNMM3NKTFU2Yndra3A1ZngzcnNmRndEdmxpbWhoWDNEblZUNkpBNWhBb0dBZUtSZGRBVFAxZmVDL1pIZWE0VWh1MEVIencrY2xraDBrdjFDQkNHNnhjM002Y245SzFUNlIwMHRZYlFjS1JvZHJ1ZnBZNUNOL3V2Zyt0VWRucTUxRFhwTU5tbDVMdGpRYnV3MktuZ1F1ZG9NQ1NFMHMzT0dnaGJqMzZtbEVsRERjUGxLaE0rb1hyaVRYdXUvWmljclZqcGxTcWxuSlN2aDh6STJGNFBLaDlnPQ==";
private String publicKey = "-----BEGIN CERTIFICATE-----\n" +
"MIIDpzCCAo+gAwIBAgIBADANBgkqhkiG9w0BAQUFADBuMRowGAYDVQQDDBF3d3cu\n" +
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
"HhcNMTcwOTA0MTEwNTQyWhcNMzcwODMwMTEwNTQyWjBuMRowGAYDVQQDDBF3d3cu\n" +
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
"ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWAJ03n6PKT3IZVSOZZK82\n" +
"wwjE5VIn/rVy/EKnk21X3CWOnGloFkQwbIalb9dUcQrd33nt5Qh2hIys9NGl27LR\n" +
"hkyMjzf3hWsnycIXYADUHLpxbYmjIG4cmiMBr8rHcTQ/3GTNPJXm4g50BVD9veE8\n" +
"h2D+MwHAySJLYpnEh0JXLDKJoVzPbWquVUtvM2gfYQG4X0m/djdyW2xdcvRJYyKi\n" +
"X6wRf7SubibdGUu0x1rHQthg3WmiwKwOMGjPCz1D000/oZ6grDGqm7LnW1VZ7EMM\n" +
"kVZ5cVW86ZFCikqsmp7u1X5b7BO0bi8rWftfo3frZ9Jmvi/dzfyz6DTE9b+eOB79\n" +
"AgMBAAGjUDBOMB0GA1UdDgQWBBS3zNF86LEZFT/KDdscr4ZJEisXqDAfBgNVHSME\n" +
"GDAWgBS3zNF86LEZFT/KDdscr4ZJEisXqDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3\n" +
"DQEBBQUAA4IBAQCPMu5AKNlh0gTr/k9Vc7RJ01uF07D+lTPGIErfW7+qXO21kXyw\n" +
"+w8sxw+e1B/gah/bxotfO7ZuOhs49d8QRUzPy/miBFaZXfjfiqs7UXSDQ6oUbX3a\n" +
"X9eTFMHDcsSUbyqhwn2cghmPJEhE10mtH2DJNPqDYvdpekJ6sEUVaqx63CD3nxcl\n" +
"7fXh0IfmvDQOrSBszRqPY8pvnZJIEwqaENPk9Vgbzs8oXVstKl6wCqM0B36tmhBl\n" +
"f6Dz/EhriF3Rq9w5RrWZOpS6XAWwRpyHPN+lKPa321dF6EEsnvhX8G3UbLbr0uEg\n" +
"dR8lPhuKejU/Ds0ARwQGmFXFzidFNZL5ymos\n" +
"-----END CERTIFICATE-----";
@Test
public void deserializeJSON() {
String file = "ia7OEEEyXMoRa1QWQk8r";
DecryptedFolderMetadata metadata = EncryptionUtils.deserializeJSON(decryptedString,
new TypeToken<DecryptedFolderMetadata>() {});
assertEquals("jtboLmgGR1OQf2uneqCVHpklQLlIwWL5TXAQ0keK", metadata.files.get(file).encrypted.key);
assertEquals("+mHu52HyZq+pAAIN", metadata.files.get(file).initializationVector);
}
@Test
public void serializeJSON() {
try {
HashMap<Integer, String> metadataKeys = new HashMap<>();
metadataKeys.put(0, EncryptionUtils.encryptStringAsymmetric("s4k4LPDpxoO53TKwem3Lo1", publicKey));
metadataKeys.put(1, EncryptionUtils.encryptStringAsymmetric("Q3ZobVJHbTlkK1VHT0g3ME", publicKey));
metadataKeys.put(2, EncryptionUtils.encryptStringAsymmetric("lkK1VHT0g3ME3TKwem3Lo1", publicKey));
DecryptedFolderMetadata.Encrypted encrypted = new DecryptedFolderMetadata.Encrypted();
encrypted.metadataKeys = metadataKeys;
DecryptedFolderMetadata.Metadata metadata1 = new DecryptedFolderMetadata.Metadata();
metadata1.metadataKeys = metadataKeys;
metadata1.version = 1;
DecryptedFolderMetadata.Sharing sharing = new DecryptedFolderMetadata.Sharing();
sharing.signature = "HMACOFRECIPIENTANDNEWESTMETADATAKEY";
HashMap<String, String> recipient = new HashMap<>();
recipient.put("blah@schiessle.org", "PUBLIC KEY");
recipient.put("bjoern@schiessle.org", "PUBLIC KEY");
sharing.recipient = recipient;
metadata1.sharing = sharing;
HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
DecryptedFolderMetadata.Data data1 = new DecryptedFolderMetadata.Data();
data1.key = "jtboLmgGR1OQf2uneqCVHpklQLlIwWL5TXAQ0keK";
data1.filename = "test.txt";
data1.version = 1;
DecryptedFolderMetadata.DecryptedFile file1 = new DecryptedFolderMetadata.DecryptedFile();
file1.initializationVector = "+mHu52HyZq+pAAIN";
file1.encrypted = data1;
file1.metadataKey = 0;
file1.authenticationTag = "HMAC of file";
files.put("ia7OEEEyXMoRa1QWQk8r", file1);
DecryptedFolderMetadata.Data data2 = new DecryptedFolderMetadata.Data();
data2.key = "s4k4LPDpxoO53TKwem3Lo1yJnbNUYH2KLrSFT8Ea";
data2.filename = "test2.txt";
data2.version = 1;
DecryptedFolderMetadata.DecryptedFile file2 = new DecryptedFolderMetadata.DecryptedFile();
file2.initializationVector = "sOFd17hCKWIv0gyB";
file2.encrypted = data2;
file2.metadataKey = 0;
file2.authenticationTag = "HMAC of file";
files.put("n9WXAIXO2wRY4R8nXwmo", file2);
DecryptedFolderMetadata metadata = new DecryptedFolderMetadata(metadata1, files);
// serialize
assertEquals(decryptedString, EncryptionUtils.serializeJSON(metadata));
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
}