mirror of
https://github.com/nextcloud/android.git
synced 2024-11-25 06:35:48 +03:00
E2E v1.2
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
parent
c35e1e6ab3
commit
5492a99e38
13 changed files with 657 additions and 209 deletions
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
Minimum: NC 16 Server, Android 6.0 Marshmallow
|
Minimum: NC 16 Server, Android 6.0 Marshmallow
|
||||||
|
|
||||||
## 3.24.0 (February 13, 2022)
|
## 3.24.0 (February 13, 2023)
|
||||||
|
|
||||||
- Several performance optimizations by @starypatyk
|
- Several performance optimizations by @starypatyk
|
||||||
- Support multi-page document scanning and exporting to PDF
|
- Support multi-page document scanning and exporting to PDF
|
||||||
|
|
|
@ -28,6 +28,9 @@ import com.google.gson.JsonParser;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.nextcloud.test.RandomStringGenerator;
|
import com.nextcloud.test.RandomStringGenerator;
|
||||||
import com.nextcloud.test.RetryTestRule;
|
import com.nextcloud.test.RetryTestRule;
|
||||||
|
import com.owncloud.android.AbstractIT;
|
||||||
|
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||||
|
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||||
|
@ -35,7 +38,6 @@ import com.owncloud.android.utils.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.apache.commons.io.FileUtils;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -44,7 +46,6 @@ import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
|
@ -64,7 +65,6 @@ import javax.crypto.BadPaddingException;
|
||||||
|
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import static androidx.test.InstrumentationRegistry.getInstrumentation;
|
|
||||||
import static com.owncloud.android.utils.EncryptionUtils.EncryptedFile;
|
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;
|
||||||
|
@ -76,8 +76,10 @@ import static com.owncloud.android.utils.EncryptionUtils.deserializeJSON;
|
||||||
import static com.owncloud.android.utils.EncryptionUtils.encodeBytesToBase64String;
|
import static com.owncloud.android.utils.EncryptionUtils.encodeBytesToBase64String;
|
||||||
import static com.owncloud.android.utils.EncryptionUtils.encryptFile;
|
import static com.owncloud.android.utils.EncryptionUtils.encryptFile;
|
||||||
import static com.owncloud.android.utils.EncryptionUtils.encryptFolderMetadata;
|
import static com.owncloud.android.utils.EncryptionUtils.encryptFolderMetadata;
|
||||||
|
import static com.owncloud.android.utils.EncryptionUtils.generateChecksum;
|
||||||
import static com.owncloud.android.utils.EncryptionUtils.generateKey;
|
import static com.owncloud.android.utils.EncryptionUtils.generateKey;
|
||||||
import static com.owncloud.android.utils.EncryptionUtils.generateSHA512;
|
import static com.owncloud.android.utils.EncryptionUtils.generateSHA512;
|
||||||
|
import static com.owncloud.android.utils.EncryptionUtils.isFolderMigrated;
|
||||||
import static com.owncloud.android.utils.EncryptionUtils.ivDelimiter;
|
import static com.owncloud.android.utils.EncryptionUtils.ivDelimiter;
|
||||||
import static com.owncloud.android.utils.EncryptionUtils.ivDelimiterOld;
|
import static com.owncloud.android.utils.EncryptionUtils.ivDelimiterOld;
|
||||||
import static com.owncloud.android.utils.EncryptionUtils.ivLength;
|
import static com.owncloud.android.utils.EncryptionUtils.ivLength;
|
||||||
|
@ -88,10 +90,11 @@ import static com.owncloud.android.utils.EncryptionUtils.verifySHA512;
|
||||||
import static junit.framework.Assert.assertFalse;
|
import static junit.framework.Assert.assertFalse;
|
||||||
import static junit.framework.Assert.assertTrue;
|
import static junit.framework.Assert.assertTrue;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class EncryptionTestIT {
|
public class EncryptionTestIT extends AbstractIT {
|
||||||
@Rule public RetryTestRule retryTestRule = new RetryTestRule();
|
@Rule public RetryTestRule retryTestRule = new RetryTestRule();
|
||||||
|
|
||||||
private String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo" +
|
private String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo" +
|
||||||
|
@ -288,16 +291,23 @@ public class EncryptionTestIT {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DecryptedFolderMetadata -> EncryptedFolderMetadata -> JSON -> encrypt
|
* DecryptedFolderMetadata -> EncryptedFolderMetadata -> JSON -> encrypt -> decrypt -> JSON ->
|
||||||
* -> decrypt -> JSON -> EncryptedFolderMetadata -> DecryptedFolderMetadata
|
* EncryptedFolderMetadata -> DecryptedFolderMetadata
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void encryptionMetadata() throws Exception {
|
public void encryptionMetadata() throws Exception {
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||||
|
long folderID = 1;
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||||
decryptedFolderMetadata1, privateKey);
|
decryptedFolderMetadata1,
|
||||||
|
privateKey,
|
||||||
|
cert,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
|
|
||||||
// serialize
|
// serialize
|
||||||
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||||
|
@ -309,13 +319,84 @@ public class EncryptionTestIT {
|
||||||
|
|
||||||
// decrypt
|
// decrypt
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||||
encryptedFolderMetadata2, privateKey);
|
encryptedFolderMetadata2,
|
||||||
|
privateKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
|
|
||||||
// compare
|
// compare
|
||||||
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||||
serializeJSON(decryptedFolderMetadata2)));
|
serializeJSON(decryptedFolderMetadata2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChangedMetadataKey() throws Exception {
|
||||||
|
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||||
|
long folderID = 1;
|
||||||
|
|
||||||
|
// encrypt
|
||||||
|
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||||
|
decryptedFolderMetadata1,
|
||||||
|
privateKey,
|
||||||
|
cert,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
|
|
||||||
|
// store metadata key
|
||||||
|
String oldMetadataKey = encryptedFolderMetadata1.getMetadata().getMetadataKey();
|
||||||
|
|
||||||
|
// do it again
|
||||||
|
// encrypt
|
||||||
|
EncryptedFolderMetadata encryptedFolderMetadata2 = encryptFolderMetadata(
|
||||||
|
decryptedFolderMetadata1,
|
||||||
|
privateKey,
|
||||||
|
cert,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
|
|
||||||
|
String newMetadataKey = encryptedFolderMetadata2.getMetadata().getMetadataKey();
|
||||||
|
|
||||||
|
assertNotEquals(oldMetadataKey, newMetadataKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMigrateMetadataKey() throws Exception {
|
||||||
|
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||||
|
long folderID = 1;
|
||||||
|
|
||||||
|
// encrypt
|
||||||
|
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||||
|
decryptedFolderMetadata1,
|
||||||
|
privateKey,
|
||||||
|
cert,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
|
|
||||||
|
// reset new metadata key, to mimic old version
|
||||||
|
encryptedFolderMetadata1.getMetadata().setMetadataKey(null);
|
||||||
|
String oldMetadataKey = encryptedFolderMetadata1.getMetadata().getMetadataKey();
|
||||||
|
|
||||||
|
// do it again
|
||||||
|
// encrypt
|
||||||
|
EncryptedFolderMetadata encryptedFolderMetadata2 = encryptFolderMetadata(
|
||||||
|
decryptedFolderMetadata1,
|
||||||
|
privateKey,
|
||||||
|
cert,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
|
|
||||||
|
String newMetadataKey = encryptedFolderMetadata2.getMetadata().getMetadataKey();
|
||||||
|
|
||||||
|
assertNotEquals(oldMetadataKey, newMetadataKey);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCryptFileWithoutMetadata() throws Exception {
|
public void testCryptFileWithoutMetadata() throws Exception {
|
||||||
byte[] key = decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg==");
|
byte[] key = decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg==");
|
||||||
|
@ -353,10 +434,17 @@ public class EncryptionTestIT {
|
||||||
@Test
|
@Test
|
||||||
public void bigMetadata() throws Exception {
|
public void bigMetadata() throws Exception {
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||||
|
long folderID = 1;
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||||
decryptedFolderMetadata1, privateKey);
|
decryptedFolderMetadata1,
|
||||||
|
privateKey,
|
||||||
|
cert,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
|
|
||||||
// serialize
|
// serialize
|
||||||
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||||
|
@ -368,7 +456,11 @@ public class EncryptionTestIT {
|
||||||
|
|
||||||
// decrypt
|
// decrypt
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||||
encryptedFolderMetadata2, privateKey);
|
encryptedFolderMetadata2,
|
||||||
|
privateKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
|
|
||||||
// compare
|
// compare
|
||||||
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||||
|
@ -386,7 +478,12 @@ public class EncryptionTestIT {
|
||||||
addFile(decryptedFolderMetadata1, i);
|
addFile(decryptedFolderMetadata1, i);
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1, privateKey);
|
encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1,
|
||||||
|
privateKey,
|
||||||
|
cert,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
|
|
||||||
// serialize
|
// serialize
|
||||||
encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||||
|
@ -397,7 +494,11 @@ public class EncryptionTestIT {
|
||||||
});
|
});
|
||||||
|
|
||||||
// decrypt
|
// decrypt
|
||||||
decryptedFolderMetadata2 = decryptFolderMetaData(encryptedFolderMetadata2, privateKey);
|
decryptedFolderMetadata2 = decryptFolderMetaData(encryptedFolderMetadata2,
|
||||||
|
privateKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
|
|
||||||
// compare
|
// compare
|
||||||
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||||
|
@ -411,6 +512,8 @@ public class EncryptionTestIT {
|
||||||
@Test
|
@Test
|
||||||
public void filedrop() throws Exception {
|
public void filedrop() throws Exception {
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||||
|
long folderID = 1;
|
||||||
|
|
||||||
// add filedrop
|
// add filedrop
|
||||||
Map<String, DecryptedFolderMetadata.DecryptedFile> filesdrop = new HashMap<>();
|
Map<String, DecryptedFolderMetadata.DecryptedFile> filesdrop = new HashMap<>();
|
||||||
|
@ -433,8 +536,11 @@ public class EncryptionTestIT {
|
||||||
// encrypt
|
// encrypt
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||||
decryptedFolderMetadata1,
|
decryptedFolderMetadata1,
|
||||||
privateKey
|
privateKey,
|
||||||
);
|
cert,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, cert);
|
EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, cert);
|
||||||
|
|
||||||
// serialize
|
// serialize
|
||||||
|
@ -447,7 +553,11 @@ public class EncryptionTestIT {
|
||||||
|
|
||||||
// decrypt
|
// decrypt
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||||
encryptedFolderMetadata2, privateKey);
|
encryptedFolderMetadata2,
|
||||||
|
privateKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folderID);
|
||||||
|
|
||||||
// compare
|
// compare
|
||||||
assertFalse(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
assertFalse(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||||
|
@ -532,6 +642,58 @@ public class EncryptionTestIT {
|
||||||
assertTrue(verifySHA512(hashedToken, token));
|
assertTrue(verifySHA512(hashedToken, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExcludeGSON() throws Exception {
|
||||||
|
DecryptedFolderMetadata metadata = generateFolderMetadata();
|
||||||
|
|
||||||
|
String jsonWithKeys = serializeJSON(metadata);
|
||||||
|
String jsonWithoutKeys = serializeJSON(metadata, true);
|
||||||
|
|
||||||
|
assertTrue(jsonWithKeys.contains("metadataKeys"));
|
||||||
|
assertFalse(jsonWithoutKeys.contains("metadataKeys"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChecksum() throws Exception {
|
||||||
|
DecryptedFolderMetadata metadata = new DecryptedFolderMetadata();
|
||||||
|
String mnemonic = "chimney potato joke science ridge trophy result estate spare vapor much room";
|
||||||
|
|
||||||
|
metadata.getFiles().put("n9WXAIXO2wRY4R8nXwmo", new DecryptedFolderMetadata.DecryptedFile());
|
||||||
|
metadata.getFiles().put("ia7OEEEyXMoRa1QWQk8r", new DecryptedFolderMetadata.DecryptedFile());
|
||||||
|
|
||||||
|
String encryptedMetadataKey = "GuFPAULudgD49S4+VDFck3LiqQ8sx4zmbrBtdpCSGcT+T0W0z4F5gYQYPlzTG6WOkdW5LJZK/";
|
||||||
|
metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
|
||||||
|
|
||||||
|
String checksum = generateChecksum(metadata, mnemonic);
|
||||||
|
|
||||||
|
String expectedChecksum = "002cefa6493f2efb0192247a34bb1b16d391aefee968fd3d4225c4ec3cd56436";
|
||||||
|
assertEquals(expectedChecksum, checksum);
|
||||||
|
|
||||||
|
// change something
|
||||||
|
String newMnemonic = mnemonic + "1";
|
||||||
|
|
||||||
|
String newChecksum = generateChecksum(metadata, newMnemonic);
|
||||||
|
assertNotEquals(expectedChecksum, newChecksum);
|
||||||
|
|
||||||
|
metadata.getFiles().put("aeb34yXMoRa1QWQk8r", new DecryptedFolderMetadata.DecryptedFile());
|
||||||
|
|
||||||
|
newChecksum = generateChecksum(metadata, mnemonic);
|
||||||
|
assertNotEquals(expectedChecksum, newChecksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddIdToMigratedIds() {
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||||
|
|
||||||
|
// delete ids
|
||||||
|
arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.MIGRATED_FOLDER_IDS);
|
||||||
|
|
||||||
|
long id = 1;
|
||||||
|
EncryptionUtils.addIdToMigratedIds(id, user, arbitraryDataProvider);
|
||||||
|
|
||||||
|
assertTrue(isFolderMigrated(id, user, arbitraryDataProvider));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Helper
|
// Helper
|
||||||
private boolean compareJsonStrings(String expected, String actual) {
|
private boolean compareJsonStrings(String expected, String actual) {
|
||||||
|
@ -561,15 +723,7 @@ public class EncryptionTestIT {
|
||||||
|
|
||||||
DecryptedFolderMetadata.Metadata metadata1 = new DecryptedFolderMetadata.Metadata();
|
DecryptedFolderMetadata.Metadata metadata1 = new DecryptedFolderMetadata.Metadata();
|
||||||
metadata1.setMetadataKeys(metadataKeys);
|
metadata1.setMetadataKeys(metadataKeys);
|
||||||
metadata1.setVersion(1);
|
metadata1.setVersion(1.1);
|
||||||
|
|
||||||
DecryptedFolderMetadata.Sharing sharing = new DecryptedFolderMetadata.Sharing();
|
|
||||||
sharing.setSignature("HMACOFRECIPIENTANDNEWESTMETADATAKEY");
|
|
||||||
HashMap<String, String> recipient = new HashMap<>();
|
|
||||||
recipient.put("blah@schiessle.org", "PUBLIC KEY");
|
|
||||||
recipient.put("bjoern@schiessle.org", "PUBLIC KEY");
|
|
||||||
sharing.setRecipient(recipient);
|
|
||||||
metadata1.setSharing(sharing);
|
|
||||||
|
|
||||||
HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
|
HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
|
||||||
|
|
||||||
|
@ -629,14 +783,6 @@ public class EncryptionTestIT {
|
||||||
return md5.compareTo(getMD5Sum(decryptedFile)) == 0;
|
return md5.compareTo(getMD5Sum(decryptedFile)) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getFile(String filename) throws IOException {
|
|
||||||
InputStream inputStream = getInstrumentation().getContext().getAssets().open(filename);
|
|
||||||
File temp = File.createTempFile("file", "file");
|
|
||||||
FileUtils.copyInputStreamToFile(inputStream, temp);
|
|
||||||
|
|
||||||
return temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getMD5Sum(File file) {
|
private String getMD5Sum(File file) {
|
||||||
FileInputStream fileInputStream = null;
|
FileInputStream fileInputStream = null;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -94,22 +94,13 @@ public final class PushUtils {
|
||||||
try {
|
try {
|
||||||
messageDigest = MessageDigest.getInstance("SHA-512");
|
messageDigest = MessageDigest.getInstance("SHA-512");
|
||||||
messageDigest.update(pushToken.getBytes());
|
messageDigest.update(pushToken.getBytes());
|
||||||
return bytesToHex(messageDigest.digest());
|
return EncryptionUtils.bytesToHex(messageDigest.digest());
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
Log_OC.d(TAG, "SHA-512 algorithm not supported");
|
Log_OC.d(TAG, "SHA-512 algorithm not supported");
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String bytesToHex(byte[] bytes) {
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
for (byte individualByte : bytes) {
|
|
||||||
result.append(Integer.toString((individualByte & 0xff) + 0x100, 16)
|
|
||||||
.substring(1));
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int generateRsa2048KeyPair() {
|
private static int generateRsa2048KeyPair() {
|
||||||
migratePushKeys();
|
migratePushKeys();
|
||||||
String keyPath = MainApp.getAppContext().getFilesDir().getAbsolutePath() + File.separator +
|
String keyPath = MainApp.getAppContext().getFilesDir().getAbsolutePath() + File.separator +
|
||||||
|
|
|
@ -71,9 +71,11 @@ public class DecryptedFolderMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Metadata {
|
public static class Metadata {
|
||||||
private Map<Integer, String> metadataKeys; // each keys is encrypted on its own, decrypt on use
|
transient
|
||||||
private Sharing sharing;
|
private Map<Integer, String> metadataKeys; // outdated with v1.1
|
||||||
private int version;
|
private String metadataKey;
|
||||||
|
private String checksum;
|
||||||
|
private double version = 1.2;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@ -84,11 +86,7 @@ public class DecryptedFolderMetadata {
|
||||||
return this.metadataKeys;
|
return this.metadataKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sharing getSharing() {
|
public double getVersion() {
|
||||||
return this.sharing;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVersion() {
|
|
||||||
return this.version;
|
return this.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,12 +94,28 @@ public class DecryptedFolderMetadata {
|
||||||
this.metadataKeys = metadataKeys;
|
this.metadataKeys = metadataKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSharing(Sharing sharing) {
|
public void setVersion(double version) {
|
||||||
this.sharing = sharing;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVersion(int version) {
|
public String getMetadataKey() {
|
||||||
this.version = version;
|
if (metadataKey == null) {
|
||||||
|
// fallback to old keys array
|
||||||
|
return metadataKeys.get(0);
|
||||||
|
}
|
||||||
|
return metadataKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetadataKey(String metadataKey) {
|
||||||
|
this.metadataKey = metadataKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChecksum() {
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChecksum(String checksum) {
|
||||||
|
this.checksum = checksum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,32 +131,11 @@ public class DecryptedFolderMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DecryptedFile {
|
public static class DecryptedFile {
|
||||||
private Data encrypted;
|
private Data encrypted;
|
||||||
private String initializationVector;
|
private String initializationVector;
|
||||||
private String authenticationTag;
|
private String authenticationTag;
|
||||||
private int metadataKey;
|
transient private int metadataKey;
|
||||||
|
|
||||||
public Data getEncrypted() {
|
public Data getEncrypted() {
|
||||||
return this.encrypted;
|
return this.encrypted;
|
||||||
|
@ -181,7 +174,7 @@ public class DecryptedFolderMetadata {
|
||||||
private String key;
|
private String key;
|
||||||
private String filename;
|
private String filename;
|
||||||
private String mimetype;
|
private String mimetype;
|
||||||
private int version;
|
transient private double version;
|
||||||
|
|
||||||
public String getKey() {
|
public String getKey() {
|
||||||
return this.key;
|
return this.key;
|
||||||
|
@ -195,7 +188,7 @@ public class DecryptedFolderMetadata {
|
||||||
return this.mimetype;
|
return this.mimetype;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVersion() {
|
public double getVersion() {
|
||||||
return this.version;
|
return this.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
data class EncryptedFiledrop(
|
||||||
|
val encrypted: String,
|
||||||
|
val initializationVector: String,
|
||||||
|
val authenticationTag: String,
|
||||||
|
val encryptedKey: String,
|
||||||
|
val encryptedTag: String,
|
||||||
|
val encryptedInitializationVector: String
|
||||||
|
)
|
|
@ -30,11 +30,11 @@ public class EncryptedFolderMetadata {
|
||||||
private DecryptedFolderMetadata.Metadata metadata;
|
private DecryptedFolderMetadata.Metadata metadata;
|
||||||
private Map<String, EncryptedFile> files;
|
private Map<String, EncryptedFile> files;
|
||||||
|
|
||||||
private Map<String, EncryptedFile> filedrop;
|
private Map<String, EncryptedFiledrop> filedrop;
|
||||||
|
|
||||||
public EncryptedFolderMetadata(DecryptedFolderMetadata.Metadata metadata,
|
public EncryptedFolderMetadata(DecryptedFolderMetadata.Metadata metadata,
|
||||||
Map<String, EncryptedFile> files,
|
Map<String, EncryptedFile> files,
|
||||||
Map<String, EncryptedFile> filesdrop) {
|
Map<String, EncryptedFiledrop> filesdrop) {
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
this.files = files;
|
this.files = files;
|
||||||
this.filedrop = filesdrop;
|
this.filedrop = filesdrop;
|
||||||
|
@ -48,7 +48,7 @@ public class EncryptedFolderMetadata {
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, EncryptedFile> getFiledrop() {
|
public Map<String, EncryptedFiledrop> getFiledrop() {
|
||||||
return filedrop;
|
return filedrop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ public class EncryptedFolderMetadata {
|
||||||
private String encrypted;
|
private String encrypted;
|
||||||
private String initializationVector;
|
private String initializationVector;
|
||||||
private String authenticationTag;
|
private String authenticationTag;
|
||||||
private int metadataKey;
|
transient private int metadataKey;
|
||||||
|
|
||||||
public String getEncrypted() {
|
public String getEncrypted() {
|
||||||
return encrypted;
|
return encrypted;
|
||||||
|
@ -93,9 +93,5 @@ public class EncryptedFolderMetadata {
|
||||||
public void setAuthenticationTag(String authenticationTag) {
|
public void setAuthenticationTag(String authenticationTag) {
|
||||||
this.authenticationTag = authenticationTag;
|
this.authenticationTag = authenticationTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMetadataKey(int metadataKey) {
|
|
||||||
this.metadataKey = metadataKey;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -600,6 +600,9 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
||||||
return !TextUtils.isEmpty(getFileName()) && getFileName().charAt(0) == '.';
|
return !TextUtils.isEmpty(getFileName()) && getFileName().charAt(0) == '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unique fileId for the file within the instance
|
||||||
|
*/
|
||||||
@SuppressFBWarnings("STT")
|
@SuppressFBWarnings("STT")
|
||||||
public long getLocalId() {
|
public long getLocalId() {
|
||||||
if (localId > 0) {
|
if (localId > 0) {
|
||||||
|
@ -648,6 +651,9 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android's internal ID of the file
|
||||||
|
*/
|
||||||
public long getFileId() {
|
public long getFileId() {
|
||||||
return this.fileId;
|
return this.fileId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,9 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
||||||
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent,
|
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent,
|
||||||
client,
|
client,
|
||||||
privateKey,
|
privateKey,
|
||||||
publicKey);
|
publicKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user);
|
||||||
|
|
||||||
metadataExists = metadataPair.first;
|
metadataExists = metadataPair.first;
|
||||||
metadata = metadataPair.second;
|
metadata = metadataPair.second;
|
||||||
|
@ -150,7 +152,11 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
||||||
metadata.getFiles().put(encryptedFileName, createDecryptedFile(filename));
|
metadata.getFiles().put(encryptedFileName, createDecryptedFile(filename));
|
||||||
|
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
||||||
privateKey);
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
parent.getLocalId());
|
||||||
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
||||||
|
|
||||||
// upload metadata
|
// upload metadata
|
||||||
|
|
|
@ -47,6 +47,7 @@ import java.io.IOException;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
|
@ -99,6 +100,7 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
||||||
DecryptedFolderMetadata metadata;
|
DecryptedFolderMetadata metadata;
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Lock folder
|
// Lock folder
|
||||||
|
@ -123,7 +125,11 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
||||||
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
||||||
});
|
});
|
||||||
|
|
||||||
metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
|
metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata,
|
||||||
|
privateKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
parentId);
|
||||||
} else {
|
} else {
|
||||||
throw new RemoteOperationFailedException("No Metadata found!");
|
throw new RemoteOperationFailedException("No Metadata found!");
|
||||||
}
|
}
|
||||||
|
@ -140,8 +146,13 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
||||||
// remove file from metadata
|
// remove file from metadata
|
||||||
metadata.getFiles().remove(fileName);
|
metadata.getFiles().remove(fileName);
|
||||||
|
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(
|
||||||
privateKey);
|
metadata,
|
||||||
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
parentId);
|
||||||
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
||||||
|
|
||||||
// upload metadata
|
// upload metadata
|
||||||
|
@ -155,14 +166,9 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
||||||
|
|
||||||
// return success
|
// return success
|
||||||
return result;
|
return result;
|
||||||
} catch (NoSuchAlgorithmException |
|
} catch (NoSuchAlgorithmException | IOException | InvalidKeyException | InvalidAlgorithmParameterException |
|
||||||
IOException |
|
NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeySpecException |
|
||||||
InvalidKeyException |
|
CertificateException e) {
|
||||||
InvalidAlgorithmParameterException |
|
|
||||||
NoSuchPaddingException |
|
|
||||||
BadPaddingException |
|
|
||||||
IllegalBlockSizeException |
|
|
||||||
InvalidKeySpecException e) {
|
|
||||||
result = new RemoteOperationResult(e);
|
result = new RemoteOperationResult(e);
|
||||||
Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e);
|
Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e);
|
||||||
|
|
||||||
|
|
|
@ -471,7 +471,9 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parentFile,
|
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parentFile,
|
||||||
client,
|
client,
|
||||||
privateKey,
|
privateKey,
|
||||||
publicKey);
|
publicKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user);
|
||||||
|
|
||||||
metadataExists = metadataPair.first;
|
metadataExists = metadataPair.first;
|
||||||
DecryptedFolderMetadata metadata = metadataPair.second;
|
DecryptedFolderMetadata metadata = metadataPair.second;
|
||||||
|
@ -617,8 +619,20 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
metadata.getFiles().put(encryptedFileName, decryptedFile);
|
metadata.getFiles().put(encryptedFileName, decryptedFile);
|
||||||
|
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
||||||
privateKey);
|
privateKey,
|
||||||
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
publicKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
parentFile.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
|
// upload metadata
|
||||||
EncryptionUtils.uploadMetadata(parentFile,
|
EncryptionUtils.uploadMetadata(parentFile,
|
||||||
|
|
|
@ -33,6 +33,7 @@ import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.ActionMode;
|
import android.view.ActionMode;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
@ -51,7 +52,6 @@ import com.nextcloud.android.lib.resources.files.ToggleFileLockRemoteOperation;
|
||||||
import com.nextcloud.android.lib.richWorkspace.RichWorkspaceDirectEditingRemoteOperation;
|
import com.nextcloud.android.lib.richWorkspace.RichWorkspaceDirectEditingRemoteOperation;
|
||||||
import com.nextcloud.client.account.User;
|
import com.nextcloud.client.account.User;
|
||||||
import com.nextcloud.client.account.UserAccountManager;
|
import com.nextcloud.client.account.UserAccountManager;
|
||||||
import com.nextcloud.client.core.Clock;
|
|
||||||
import com.nextcloud.client.device.DeviceInfo;
|
import com.nextcloud.client.device.DeviceInfo;
|
||||||
import com.nextcloud.client.di.Injectable;
|
import com.nextcloud.client.di.Injectable;
|
||||||
import com.nextcloud.client.documentscan.AppScanOptionalFeature;
|
import com.nextcloud.client.documentscan.AppScanOptionalFeature;
|
||||||
|
@ -68,6 +68,8 @@ 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;
|
||||||
|
@ -1700,11 +1702,12 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
||||||
String publicKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PUBLIC_KEY);
|
String publicKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PUBLIC_KEY);
|
||||||
String privateKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PRIVATE_KEY);
|
String privateKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PRIVATE_KEY);
|
||||||
|
|
||||||
|
FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
|
||||||
|
OCFile file = storageManager.getFileByRemoteId(event.remoteId);
|
||||||
|
|
||||||
if (publicKey.isEmpty() || privateKey.isEmpty()) {
|
if (publicKey.isEmpty() || privateKey.isEmpty()) {
|
||||||
Log_OC.d(TAG, "no public key for " + user.getAccountName());
|
Log_OC.d(TAG, "no public key for " + user.getAccountName());
|
||||||
|
|
||||||
FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
|
|
||||||
OCFile file = storageManager.getFileByRemoteId(event.remoteId);
|
|
||||||
int position = -1;
|
int position = -1;
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
position = mAdapter.getItemPosition(file);
|
position = mAdapter.getItemPosition(file);
|
||||||
|
@ -1713,11 +1716,23 @@ 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 {
|
||||||
encryptFolder(event.localId, event.remoteId, event.remotePath, event.shouldBeEncrypted);
|
encryptFolder(file,
|
||||||
|
event.localId,
|
||||||
|
event.remoteId,
|
||||||
|
event.remotePath,
|
||||||
|
event.shouldBeEncrypted,
|
||||||
|
publicKey,
|
||||||
|
privateKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encryptFolder(long localId, String remoteId, String remotePath, boolean shouldBeEncrypted) {
|
private void encryptFolder(OCFile folder,
|
||||||
|
long localId,
|
||||||
|
String remoteId,
|
||||||
|
String remotePath,
|
||||||
|
boolean shouldBeEncrypted,
|
||||||
|
String publicKey,
|
||||||
|
String privateKey) {
|
||||||
try {
|
try {
|
||||||
User user = accountManager.getUser();
|
User user = accountManager.getUser();
|
||||||
OwnCloudClient client = clientFactory.create(user);
|
OwnCloudClient client = clientFactory.create(user);
|
||||||
|
@ -1727,6 +1742,46 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
||||||
.execute(client);
|
.execute(client);
|
||||||
|
|
||||||
if (remoteOperationResult.isSuccess()) {
|
if (remoteOperationResult.isSuccess()) {
|
||||||
|
// lock folder
|
||||||
|
String token = EncryptionUtils.lockFolder(folder, client);
|
||||||
|
|
||||||
|
// Update metadata
|
||||||
|
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(folder,
|
||||||
|
client,
|
||||||
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user);
|
||||||
|
|
||||||
|
boolean metadataExists = metadataPair.first;
|
||||||
|
DecryptedFolderMetadata metadata = metadataPair.second;
|
||||||
|
|
||||||
|
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
||||||
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
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,
|
||||||
|
client,
|
||||||
|
metadataExists);
|
||||||
|
|
||||||
|
// unlock folder
|
||||||
|
EncryptionUtils.unlockFolder(folder, client, token);
|
||||||
|
|
||||||
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(),
|
||||||
|
@ -1738,8 +1793,8 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
||||||
Snackbar.LENGTH_LONG).show();
|
Snackbar.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (ClientFactory.CreationException e) {
|
} catch (Exception e) {
|
||||||
Log_OC.e(TAG, "Cannot create client", e);
|
Log_OC.e(TAG, "Error creating encrypted folder", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,12 +28,14 @@ import android.util.Pair;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.nextcloud.client.account.User;
|
import com.nextcloud.client.account.User;
|
||||||
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.ArbitraryDataProviderImpl;
|
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||||
|
import com.owncloud.android.datamodel.EncryptedFiledrop;
|
||||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
import com.owncloud.android.datamodel.OCFile;
|
||||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||||
|
@ -79,6 +81,7 @@ import java.security.spec.KeySpec;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -120,6 +123,8 @@ public final class EncryptionUtils {
|
||||||
private static final String AES = "AES";
|
private static final String AES = "AES";
|
||||||
private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
|
private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
|
||||||
private static final String RSA = "RSA";
|
private static final String RSA = "RSA";
|
||||||
|
@VisibleForTesting
|
||||||
|
public static final String MIGRATED_FOLDER_IDS = "MIGRATED_FOLDER_IDS";
|
||||||
|
|
||||||
private EncryptionUtils() {
|
private EncryptionUtils() {
|
||||||
// utility class -> private constructor
|
// utility class -> private constructor
|
||||||
|
@ -129,12 +134,28 @@ public final class EncryptionUtils {
|
||||||
JSON
|
JSON
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static <T> T deserializeJSON(String json, TypeToken<T> type) {
|
public static <T> T deserializeJSON(String json, TypeToken<T> type, boolean excludeTransient) {
|
||||||
|
if (excludeTransient) {
|
||||||
return new Gson().fromJson(json, type.getType());
|
return new Gson().fromJson(json, type.getType());
|
||||||
|
} else {
|
||||||
|
return new GsonBuilder().excludeFieldsWithModifiers(0).create().fromJson(json, type.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T deserializeJSON(String json, TypeToken<T> type) {
|
||||||
|
return deserializeJSON(json, type, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String serializeJSON(Object data, boolean excludeTransient) {
|
||||||
|
if (excludeTransient) {
|
||||||
|
return new Gson().toJson(data);
|
||||||
|
} else {
|
||||||
|
return new GsonBuilder().excludeFieldsWithModifiers(0).create().toJson(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String serializeJSON(Object data) {
|
public static String serializeJSON(Object data) {
|
||||||
return new Gson().toJson(data);
|
return serializeJSON(data, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -148,19 +169,33 @@ public final class EncryptionUtils {
|
||||||
* @return EncryptedFolderMetadata encrypted folder metadata
|
* @return EncryptedFolderMetadata encrypted folder metadata
|
||||||
*/
|
*/
|
||||||
public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
|
public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
|
||||||
String privateKey
|
String privateKey,
|
||||||
|
String publicKey,
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider,
|
||||||
|
User user,
|
||||||
|
long parentId
|
||||||
)
|
)
|
||||||
throws NoSuchAlgorithmException, InvalidKeyException,
|
throws NoSuchAlgorithmException, InvalidKeyException,
|
||||||
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
|
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
|
||||||
IllegalBlockSizeException, InvalidKeySpecException {
|
IllegalBlockSizeException, InvalidKeySpecException, CertificateException {
|
||||||
|
|
||||||
HashMap<String, EncryptedFolderMetadata.EncryptedFile> files = new HashMap<>();
|
HashMap<String, EncryptedFolderMetadata.EncryptedFile> files = new HashMap<>();
|
||||||
HashMap<String, EncryptedFolderMetadata.EncryptedFile> filesdrop = new HashMap<>();
|
HashMap<String, EncryptedFiledrop> filesdrop = new HashMap<>();
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata
|
EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata
|
||||||
.getMetadata(),
|
.getMetadata(),
|
||||||
files,
|
files,
|
||||||
filesdrop);
|
filesdrop);
|
||||||
|
|
||||||
|
// set new metadata key
|
||||||
|
byte[] metadataKeyBytes = EncryptionUtils.generateKey();
|
||||||
|
String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(
|
||||||
|
EncryptionUtils.encodeBytesToBase64String(metadataKeyBytes),
|
||||||
|
publicKey);
|
||||||
|
encryptedFolderMetadata.getMetadata().setMetadataKey(encryptedMetadataKey);
|
||||||
|
|
||||||
|
// store that this folder has been migrated
|
||||||
|
addIdToMigratedIds(parentId, user, arbitraryDataProvider);
|
||||||
|
|
||||||
// Encrypt each file in "files"
|
// Encrypt each file in "files"
|
||||||
for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
|
for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
|
||||||
.getFiles().entrySet()) {
|
.getFiles().entrySet()) {
|
||||||
|
@ -169,39 +204,45 @@ public final class EncryptionUtils {
|
||||||
|
|
||||||
EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile();
|
EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile();
|
||||||
encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
|
encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
|
||||||
encryptedFile.setMetadataKey(decryptedFile.getMetadataKey());
|
|
||||||
encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
|
encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
|
||||||
|
|
||||||
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(EncryptionUtils.decryptStringAsymmetric(
|
|
||||||
decryptedFolderMetadata.getMetadata().getMetadataKeys().get(encryptedFile.getMetadataKey()),
|
|
||||||
privateKey));
|
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
|
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
|
||||||
encryptedFile.setEncrypted(EncryptionUtils.encryptStringSymmetric(dataJson, decryptedMetadataKey));
|
encryptedFile.setEncrypted(EncryptionUtils.encryptStringSymmetric(dataJson, metadataKeyBytes));
|
||||||
|
|
||||||
files.put(key, encryptedFile);
|
files.put(key, encryptedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set checksum
|
||||||
|
String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
|
||||||
|
String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
|
||||||
|
encryptedFolderMetadata.getMetadata().setChecksum(checksum);
|
||||||
|
|
||||||
return encryptedFolderMetadata;
|
return encryptedFolderMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public static void encryptFileDropFiles(DecryptedFolderMetadata decryptedFolderMetadata, EncryptedFolderMetadata encryptedFolderMetadata, String cert) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, CertificateException {
|
public static void encryptFileDropFiles(DecryptedFolderMetadata decryptedFolderMetadata, EncryptedFolderMetadata encryptedFolderMetadata, String cert) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, CertificateException {
|
||||||
final Map<String, EncryptedFolderMetadata.EncryptedFile> filesdrop = encryptedFolderMetadata.getFiledrop();
|
final Map<String, EncryptedFiledrop> filesdrop = encryptedFolderMetadata.getFiledrop();
|
||||||
for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
|
for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
|
||||||
.getFiledrop().entrySet()) {
|
.getFiledrop().entrySet()) {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue();
|
DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue();
|
||||||
|
|
||||||
EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile();
|
// TODO
|
||||||
encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
|
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
|
||||||
encryptedFile.setMetadataKey(decryptedFile.getMetadataKey());
|
EncryptedFiledrop encryptedFile = new EncryptedFiledrop(dataJson,
|
||||||
encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
|
decryptedFile.getInitializationVector(),
|
||||||
|
decryptedFile.getAuthenticationTag(),
|
||||||
|
"123",
|
||||||
|
"123",
|
||||||
|
"123");
|
||||||
|
// encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
|
||||||
|
// encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
|
|
||||||
encryptedFile.setEncrypted(EncryptionUtils.encryptStringAsymmetric(dataJson, cert));
|
// encryptedFile.setEncrypted(EncryptionUtils.encryptStringAsymmetric(dataJson, cert));
|
||||||
|
|
||||||
filesdrop.put(key, encryptedFile);
|
filesdrop.put(key, encryptedFile);
|
||||||
}
|
}
|
||||||
|
@ -211,7 +252,10 @@ public final class EncryptionUtils {
|
||||||
* decrypt folder metaData with private key
|
* decrypt folder metaData with private key
|
||||||
*/
|
*/
|
||||||
public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata,
|
public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata,
|
||||||
String privateKey)
|
String privateKey,
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider,
|
||||||
|
User user,
|
||||||
|
long remoteId)
|
||||||
throws NoSuchAlgorithmException, InvalidKeyException,
|
throws NoSuchAlgorithmException, InvalidKeyException,
|
||||||
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
|
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
|
||||||
IllegalBlockSizeException, InvalidKeySpecException {
|
IllegalBlockSizeException, InvalidKeySpecException {
|
||||||
|
@ -220,6 +264,16 @@ public final class EncryptionUtils {
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
|
DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
|
||||||
encryptedFolderMetadata.getMetadata(), files);
|
encryptedFolderMetadata.getMetadata(), files);
|
||||||
|
|
||||||
|
byte[] decryptedMetadataKey = null;
|
||||||
|
|
||||||
|
String encryptedMetadataKey = decryptedFolderMetadata.getMetadata().getMetadataKey();
|
||||||
|
|
||||||
|
if (encryptedMetadataKey != null) {
|
||||||
|
decryptedMetadataKey = decodeStringToBase64Bytes(
|
||||||
|
decryptStringAsymmetric(encryptedMetadataKey, privateKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encryptedFolderMetadata.getFiles() != null) {
|
||||||
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
|
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
|
||||||
.getFiles().entrySet()) {
|
.getFiles().entrySet()) {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
|
@ -230,10 +284,12 @@ public final class EncryptionUtils {
|
||||||
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
|
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
|
||||||
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
||||||
|
|
||||||
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
|
if (decryptedMetadataKey == null) {
|
||||||
EncryptionUtils.decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
|
decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
|
||||||
|
decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
|
||||||
.getMetadataKeys().get(encryptedFile.getMetadataKey()),
|
.getMetadataKeys().get(encryptedFile.getMetadataKey()),
|
||||||
privateKey));
|
privateKey));
|
||||||
|
}
|
||||||
|
|
||||||
// decrypt
|
// decrypt
|
||||||
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
|
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
|
||||||
|
@ -243,23 +299,47 @@ public final class EncryptionUtils {
|
||||||
|
|
||||||
files.put(key, decryptedFile);
|
files.put(key, decryptedFile);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, EncryptedFolderMetadata.EncryptedFile> fileDrop = encryptedFolderMetadata.getFiledrop();
|
// verify checksum
|
||||||
|
String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
|
||||||
|
String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
|
||||||
|
String decryptedFolderChecksum = decryptedFolderMetadata.getMetadata().getChecksum();
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(decryptedFolderChecksum) &&
|
||||||
|
isFolderMigrated(remoteId, user, arbitraryDataProvider)) {
|
||||||
|
throw new IllegalStateException("Possible downgrade attack detected!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(decryptedFolderChecksum) && !decryptedFolderChecksum.equals(checksum)) {
|
||||||
|
throw new IllegalStateException("Wrong checksum!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, EncryptedFiledrop> fileDrop = encryptedFolderMetadata.getFiledrop();
|
||||||
|
|
||||||
if (fileDrop != null) {
|
if (fileDrop != null) {
|
||||||
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : fileDrop.entrySet()) {
|
for (Map.Entry<String, EncryptedFiledrop> entry : fileDrop.entrySet()) {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
|
EncryptedFiledrop encryptedFile = entry.getValue();
|
||||||
|
|
||||||
|
// decrypt key
|
||||||
|
String encryptedKey = decryptStringAsymmetric(encryptedFile.getEncryptedKey(),
|
||||||
|
privateKey);
|
||||||
|
|
||||||
|
// decrypt encrypted blob with key
|
||||||
|
String decryptedData = decryptStringSymmetric(
|
||||||
|
encryptedFile.getEncrypted(),
|
||||||
|
decodeStringToBase64Bytes(encryptedKey),
|
||||||
|
decodeStringToBase64Bytes(encryptedFile.getEncryptedInitializationVector()),
|
||||||
|
decodeStringToBase64Bytes(encryptedFile.getEncryptedTag())
|
||||||
|
);
|
||||||
|
|
||||||
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
|
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
|
||||||
decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
|
decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
|
||||||
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
|
|
||||||
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
||||||
|
|
||||||
// decrypt
|
|
||||||
String dataJson = EncryptionUtils.decryptStringAsymmetric(encryptedFile.getEncrypted(), privateKey);
|
|
||||||
|
|
||||||
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
|
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(decryptedData,
|
||||||
new TypeToken<DecryptedFolderMetadata.Data>() {
|
new TypeToken<DecryptedFolderMetadata.Data>() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -294,6 +374,7 @@ public final class EncryptionUtils {
|
||||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
|
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
|
||||||
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
|
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
|
||||||
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);
|
||||||
|
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
|
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
|
||||||
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
||||||
|
@ -306,7 +387,10 @@ public final class EncryptionUtils {
|
||||||
}
|
}
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata = EncryptionUtils.decryptFolderMetaData(
|
DecryptedFolderMetadata decryptedFolderMetadata = EncryptionUtils.decryptFolderMetaData(
|
||||||
encryptedFolderMetadata,
|
encryptedFolderMetadata,
|
||||||
privateKey);
|
privateKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folder.getLocalId());
|
||||||
|
|
||||||
boolean transferredFiledrop = filesDropCountBefore > 0 && decryptedFolderMetadata.getFiles().size() ==
|
boolean transferredFiledrop = filesDropCountBefore > 0 && decryptedFolderMetadata.getFiles().size() ==
|
||||||
encryptedFolderMetadata.getFiles().size() + filesDropCountBefore;
|
encryptedFolderMetadata.getFiles().size() + filesDropCountBefore;
|
||||||
|
@ -317,7 +401,11 @@ public final class EncryptionUtils {
|
||||||
|
|
||||||
// upload metadata
|
// upload metadata
|
||||||
EncryptedFolderMetadata encryptedFolderMetadataNew = encryptFolderMetadata(decryptedFolderMetadata,
|
EncryptedFolderMetadata encryptedFolderMetadataNew = encryptFolderMetadata(decryptedFolderMetadata,
|
||||||
privateKey);
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
folder.getLocalId());
|
||||||
|
|
||||||
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadataNew);
|
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadataNew);
|
||||||
|
|
||||||
|
@ -605,6 +693,36 @@ public final class EncryptionUtils {
|
||||||
return encodedCryptedBytes + delimiter + encodedIV;
|
return encodedCryptedBytes + delimiter + encodedIV;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String decryptStringSymmetric(String string,
|
||||||
|
byte[] encryptionKeyBytes,
|
||||||
|
byte[] iv,
|
||||||
|
byte[] authenticationTag)
|
||||||
|
throws NoSuchPaddingException,
|
||||||
|
NoSuchAlgorithmException,
|
||||||
|
InvalidAlgorithmParameterException,
|
||||||
|
InvalidKeyException,
|
||||||
|
IllegalBlockSizeException,
|
||||||
|
BadPaddingException {
|
||||||
|
Cipher cipher = Cipher.getInstance(AES_CIPHER);
|
||||||
|
Key key = new SecretKeySpec(encryptionKeyBytes, AES);
|
||||||
|
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||||
|
|
||||||
|
byte[] bytes = decodeStringToBase64Bytes(string);
|
||||||
|
|
||||||
|
// check authentication tag
|
||||||
|
byte[] extractedAuthenticationTag = Arrays.copyOfRange(bytes,
|
||||||
|
bytes.length - (128 / 8),
|
||||||
|
bytes.length);
|
||||||
|
|
||||||
|
if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
|
||||||
|
throw new SecurityException("Tag not correct");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] encodedBytes = cipher.doFinal(bytes);
|
||||||
|
|
||||||
|
return decodeBase64BytesToString(encodedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
|
* Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
|
||||||
|
@ -883,7 +1001,10 @@ public final class EncryptionUtils {
|
||||||
public static Pair<Boolean, DecryptedFolderMetadata> retrieveMetadata(OCFile parentFile,
|
public static Pair<Boolean, DecryptedFolderMetadata> retrieveMetadata(OCFile parentFile,
|
||||||
OwnCloudClient client,
|
OwnCloudClient client,
|
||||||
String privateKey,
|
String privateKey,
|
||||||
String publicKey) throws UploadException,
|
String publicKey,
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider,
|
||||||
|
User user)
|
||||||
|
throws UploadException,
|
||||||
InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
|
InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
|
||||||
IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
|
IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
|
||||||
GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(parentFile.getLocalId());
|
GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(parentFile.getLocalId());
|
||||||
|
@ -900,7 +1021,11 @@ public final class EncryptionUtils {
|
||||||
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Pair<>(Boolean.TRUE, EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey));
|
return new Pair<>(Boolean.TRUE, EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata,
|
||||||
|
privateKey,
|
||||||
|
arbitraryDataProvider,
|
||||||
|
user,
|
||||||
|
parentFile.getLocalId()));
|
||||||
|
|
||||||
} else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
|
} else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
|
||||||
// new metadata
|
// new metadata
|
||||||
|
@ -909,7 +1034,7 @@ public final class EncryptionUtils {
|
||||||
metadata.getMetadata().setMetadataKeys(new HashMap<>());
|
metadata.getMetadata().setMetadataKeys(new HashMap<>());
|
||||||
String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
|
String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
|
||||||
String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
|
String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
|
||||||
metadata.getMetadata().getMetadataKeys().put(0, encryptedMetadataKey);
|
metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
|
||||||
|
|
||||||
return new Pair<>(Boolean.FALSE, metadata);
|
return new Pair<>(Boolean.FALSE, metadata);
|
||||||
} else {
|
} else {
|
||||||
|
@ -984,4 +1109,83 @@ public final class EncryptionUtils {
|
||||||
file.isFolder() &&
|
file.isFolder() &&
|
||||||
user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_26);
|
user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_26);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String generateChecksum(DecryptedFolderMetadata metadata,
|
||||||
|
String mnemonic) throws NoSuchAlgorithmException {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
stringBuilder.append(mnemonic.replaceAll(" ", ""));
|
||||||
|
|
||||||
|
ArrayList<String> keys = new ArrayList<>(metadata.getFiles().keySet());
|
||||||
|
Collections.sort(keys);
|
||||||
|
|
||||||
|
for (String key : keys) {
|
||||||
|
stringBuilder.append(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
stringBuilder.append(metadata.getMetadata().getMetadataKey());
|
||||||
|
|
||||||
|
// sha256 hash-sum
|
||||||
|
return sha256(stringBuilder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SHA-256 hash of metadata-key
|
||||||
|
*/
|
||||||
|
public static String sha256(String string) throws NoSuchAlgorithmException {
|
||||||
|
byte[] bytes = MessageDigest
|
||||||
|
.getInstance("SHA-256")
|
||||||
|
.digest(string.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
return bytesToHex(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String bytesToHex(byte[] bytes) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
for (byte individualByte : bytes) {
|
||||||
|
result.append(Integer.toString((individualByte & 0xff) + 0x100, 16)
|
||||||
|
.substring(1));
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addIdToMigratedIds(long id,
|
||||||
|
User user,
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
String ids = arbitraryDataProvider.getValue(user, MIGRATED_FOLDER_IDS);
|
||||||
|
|
||||||
|
ArrayList<Long> arrayList = gson.fromJson(ids, ArrayList.class);
|
||||||
|
|
||||||
|
if (arrayList == null) {
|
||||||
|
arrayList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arrayList.contains(id)) {
|
||||||
|
// nothing to do here
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayList.add(id);
|
||||||
|
|
||||||
|
String json = gson.toJson(arrayList);
|
||||||
|
arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(),
|
||||||
|
MIGRATED_FOLDER_IDS,
|
||||||
|
json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isFolderMigrated(long id,
|
||||||
|
User user,
|
||||||
|
ArbitraryDataProvider arbitraryDataProvider) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
String ids = arbitraryDataProvider.getValue(user, MIGRATED_FOLDER_IDS);
|
||||||
|
|
||||||
|
ArrayList<Long> arrayList = gson.fromJson(ids, new TypeToken<List<Long>>() {
|
||||||
|
}.getType());
|
||||||
|
|
||||||
|
if (arrayList == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return arrayList.contains(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue