mirror of
https://github.com/nextcloud/android.git
synced 2024-12-19 15:33:00 +03:00
Merge remote-tracking branch 'origin/master' into dev
This commit is contained in:
commit
f4fd390f69
16 changed files with 673 additions and 213 deletions
|
@ -4,7 +4,7 @@
|
|||
|
||||
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
|
||||
- Support multi-page document scanning and exporting to PDF
|
||||
|
|
|
@ -58,7 +58,7 @@ configurations.configureEach {
|
|||
|
||||
// semantic versioning for version code
|
||||
def versionMajor = 3
|
||||
def versionMinor = 25
|
||||
def versionMinor = 26
|
||||
def versionPatch = 0
|
||||
def versionBuild = 0 // 0-50=Alpha / 51-98=RC / 90-99=stable
|
||||
|
||||
|
|
|
@ -28,6 +28,9 @@ import com.google.gson.JsonParser;
|
|||
import com.google.gson.reflect.TypeToken;
|
||||
import com.nextcloud.test.RandomStringGenerator;
|
||||
import com.nextcloud.test.RetryTestRule;
|
||||
import com.owncloud.android.AbstractIT;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||
|
@ -35,7 +38,6 @@ import com.owncloud.android.utils.CsrHelper;
|
|||
import com.owncloud.android.utils.EncryptionUtils;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
@ -44,7 +46,6 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
|
@ -64,7 +65,6 @@ import javax.crypto.BadPaddingException;
|
|||
|
||||
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.decodeStringToBase64Bytes;
|
||||
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.encryptFile;
|
||||
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.generateSHA512;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.isFolderMigrated;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.ivDelimiter;
|
||||
import static com.owncloud.android.utils.EncryptionUtils.ivDelimiterOld;
|
||||
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.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EncryptionTestIT {
|
||||
public class EncryptionTestIT extends AbstractIT {
|
||||
@Rule public RetryTestRule retryTestRule = new RetryTestRule();
|
||||
|
||||
private String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo" +
|
||||
|
@ -104,44 +107,44 @@ public class EncryptionTestIT {
|
|||
"SYk2jjjWVSXRNmex+V6+Y/jBRT2mvAgm8J+7LPwFdatE+lz0aZrMRD2gCWYF6Itpda" +
|
||||
"90OlLkmQPVWWtGTgX2ta2tF5r2iSGzk0IdoL8zw98Q2UzpOcw30KnWtFMxuxWk0mHq" +
|
||||
"pgp00g80cDWg3+RPbWOhdLp5bflQ36fKDfmjq05cGlIk6unnVyC5HXpvh4d4k2EWlX" +
|
||||
"rjGsndVBPCjGkZePlLRgDHxT06r+5XdJ+1CBDZgCsmjGz3M8uOHyCfVW0WhB7ynzDT" +
|
||||
"agVgz0iqpuhAi9sPt6iWWwpAnRw8cQgqEKw9bvKKECgYEA/WPi2PJtL6u/xlysh/H7" +
|
||||
"A717CId6fPHCMDace39ZNtzUzc0nT5BemlcF0wZ74NeJSur3Q395YzB+eBMLs5p8mA" +
|
||||
"95wgGvJhM65/J+HX+k9kt6Z556zLMvtG+j1yo4D0VEwm3xahB4SUUP+1kD7dNvo4+8" +
|
||||
"xeSCyjzNllvYZZC0DrECgYEA7w8pEqhHHn0a+twkPCZJS+gQTB9Rm+FBNGJqB3XpWs" +
|
||||
"TeLUxYRbVGk0iDve+eeeZ41drxcdyWP+WcL34hnrjgI1Fo4mK88saajpwUIYMy6+qM" +
|
||||
"LY+jC2NRSBox56eH7nsVYvQQK9eKqv9wbB+PF9SwOIvuETN7fd8mAY02UnoaaU8CgY" +
|
||||
"BoHRKocXPLkpZJuuppMVQiRUi4SHJbxDo19Tp2w+y0TihiJ1lvp7I3WGpcOt3LlMQk" +
|
||||
"tEbExSvrRZGxZKH6Og/XqwQsYuTEkEIz679F/5yYVosE6GkskrOXQAfh8Mb3/04xVV" +
|
||||
"tMaVgDQw0+CWVD4wyL+BNofGwBDNqsXTCdCsfxAQKBgQCDv2EtbRw0y1HRKv21QIxo" +
|
||||
"ju5cZW4+cDfVPN+eWPdQFOs1H7wOPsc0aGRiiupV2BSEF3O1ApKziEE5U1QH+29bR4" +
|
||||
"R8L1pemeGX8qCNj5bCubKjcWOz5PpouDcEqimZ3q98p3E6GEHN15UHoaTkx0yO/V8o" +
|
||||
"j6zhQ9fYRxDHB5ACtQKBgQCOO7TJUO1IaLTjcrwS4oCfJyRnAdz49L1AbVJkIBK0fh" +
|
||||
"JLecOFu3ZlQl/RStQb69QKb5MNOIMmQhg8WOxZxHcpmIDbkDAm/J/ovJXFSoBdOr5o" +
|
||||
"uQsYsDZhsWW97zvLMzg5pH9/3/1BNz5q3Vu4HgfBSwWGt4E2NENj+XA+QAVmGA==";
|
||||
"rjGsndVBPCjGkZePlLRgDHxT06r+5XdJ+1CBDZgCsmjGz3M8uOHyCfVW0WhB7ynzDT" +
|
||||
"agVgz0iqpuhAi9sPt6iWWwpAnRw8cQgqEKw9bvKKECgYEA/WPi2PJtL6u/xlysh/H7" +
|
||||
"A717CId6fPHCMDace39ZNtzUzc0nT5BemlcF0wZ74NeJSur3Q395YzB+eBMLs5p8mA" +
|
||||
"95wgGvJhM65/J+HX+k9kt6Z556zLMvtG+j1yo4D0VEwm3xahB4SUUP+1kD7dNvo4+8" +
|
||||
"xeSCyjzNllvYZZC0DrECgYEA7w8pEqhHHn0a+twkPCZJS+gQTB9Rm+FBNGJqB3XpWs" +
|
||||
"TeLUxYRbVGk0iDve+eeeZ41drxcdyWP+WcL34hnrjgI1Fo4mK88saajpwUIYMy6+qM" +
|
||||
"LY+jC2NRSBox56eH7nsVYvQQK9eKqv9wbB+PF9SwOIvuETN7fd8mAY02UnoaaU8CgY" +
|
||||
"BoHRKocXPLkpZJuuppMVQiRUi4SHJbxDo19Tp2w+y0TihiJ1lvp7I3WGpcOt3LlMQk" +
|
||||
"tEbExSvrRZGxZKH6Og/XqwQsYuTEkEIz679F/5yYVosE6GkskrOXQAfh8Mb3/04xVV" +
|
||||
"tMaVgDQw0+CWVD4wyL+BNofGwBDNqsXTCdCsfxAQKBgQCDv2EtbRw0y1HRKv21QIxo" +
|
||||
"ju5cZW4+cDfVPN+eWPdQFOs1H7wOPsc0aGRiiupV2BSEF3O1ApKziEE5U1QH+29bR4" +
|
||||
"R8L1pemeGX8qCNj5bCubKjcWOz5PpouDcEqimZ3q98p3E6GEHN15UHoaTkx0yO/V8o" +
|
||||
"j6zhQ9fYRxDHB5ACtQKBgQCOO7TJUO1IaLTjcrwS4oCfJyRnAdz49L1AbVJkIBK0fh" +
|
||||
"JLecOFu3ZlQl/RStQb69QKb5MNOIMmQhg8WOxZxHcpmIDbkDAm/J/ovJXFSoBdOr5o" +
|
||||
"uQsYsDZhsWW97zvLMzg5pH9/3/1BNz5q3Vu4HgfBSwWGt4E2NENj+XA+QAVmGA==";
|
||||
|
||||
private String cert = "-----BEGIN CERTIFICATE-----\n" +
|
||||
"MIIDpzCCAo+gAwIBAgIBADANBgkqhkiG9w0BAQUFADBuMRowGAYDVQQDDBF3d3cu\n" +
|
||||
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
|
||||
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
|
||||
"HhcNMTcwOTI2MTAwNDMwWhcNMzcwOTIxMTAwNDMwWjBuMRowGAYDVQQDDBF3d3cu\n" +
|
||||
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
|
||||
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
|
||||
"ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDsn0JKS/THu328z1IgN0Vz\n" +
|
||||
"YU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzVGzKFvGfZ03fwFrN7Q8P8R2e8\n" +
|
||||
"SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7Y0BJX9i/nW/L0L/VaE8CZT\n" +
|
||||
"AqYBdcSJGgHJjY4UMf892ZPTa9T2Dl3ggdMZ7BQ2kiCiCC3qV99b0igRJGmmLQaG\n" +
|
||||
"iAflhFzuDQPMifUMq75wI8RSRPdxUAtjTfkl68QHu7Umyeyy33OQgdUKaTl5zcS3\n" +
|
||||
"VSQbNjveVCNM4RDH1RlEc+7Wf1BY8APqT6jbiBcROJD2CeoLH2eiIJCi+61ZkSGf\n" +
|
||||
"AgMBAAGjUDBOMB0GA1UdDgQWBBTFrXz2tk1HivD9rQ75qeoyHrAgIjAfBgNVHSME\n" +
|
||||
"GDAWgBTFrXz2tk1HivD9rQ75qeoyHrAgIjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3\n" +
|
||||
"DQEBBQUAA4IBAQARQTX21QKO77gAzBszFJ6xVnjfa23YZF26Z4X1KaM8uV8TGzuN\n" +
|
||||
"JA95XmReeP2iO3r8EWXS9djVCD64m2xx6FOsrUI8HZaw1JErU8mmOaLAe8q9RsOm\n" +
|
||||
"9Eq37e4vFp2YUEInYUqs87ByUcA4/8g3lEYeIUnRsRsWsA45S3wD7wy07t+KAn7j\n" +
|
||||
"yMmfxdma6hFfG9iN/egN6QXUAyIPXvUvlUuZ7/BhWBj/3sHMrF9quy9Q2DOI8F3t\n" +
|
||||
"1wdQrkq4BtStKhciY5AIXz9SqsctFHTv4Lwgtkapoel4izJnO0ZqYTXVe7THwri9\n" +
|
||||
"H/gua6uJDWH9jk2/CiZDWfsyFuNUuXvDSp05\n" +
|
||||
"-----END CERTIFICATE-----";
|
||||
"MIIDpzCCAo+gAwIBAgIBADANBgkqhkiG9w0BAQUFADBuMRowGAYDVQQDDBF3d3cu\n" +
|
||||
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
|
||||
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
|
||||
"HhcNMTcwOTI2MTAwNDMwWhcNMzcwOTIxMTAwNDMwWjBuMRowGAYDVQQDDBF3d3cu\n" +
|
||||
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
|
||||
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
|
||||
"ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDsn0JKS/THu328z1IgN0Vz\n" +
|
||||
"YU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzVGzKFvGfZ03fwFrN7Q8P8R2e8\n" +
|
||||
"SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7Y0BJX9i/nW/L0L/VaE8CZT\n" +
|
||||
"AqYBdcSJGgHJjY4UMf892ZPTa9T2Dl3ggdMZ7BQ2kiCiCC3qV99b0igRJGmmLQaG\n" +
|
||||
"iAflhFzuDQPMifUMq75wI8RSRPdxUAtjTfkl68QHu7Umyeyy33OQgdUKaTl5zcS3\n" +
|
||||
"VSQbNjveVCNM4RDH1RlEc+7Wf1BY8APqT6jbiBcROJD2CeoLH2eiIJCi+61ZkSGf\n" +
|
||||
"AgMBAAGjUDBOMB0GA1UdDgQWBBTFrXz2tk1HivD9rQ75qeoyHrAgIjAfBgNVHSME\n" +
|
||||
"GDAWgBTFrXz2tk1HivD9rQ75qeoyHrAgIjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3\n" +
|
||||
"DQEBBQUAA4IBAQARQTX21QKO77gAzBszFJ6xVnjfa23YZF26Z4X1KaM8uV8TGzuN\n" +
|
||||
"JA95XmReeP2iO3r8EWXS9djVCD64m2xx6FOsrUI8HZaw1JErU8mmOaLAe8q9RsOm\n" +
|
||||
"9Eq37e4vFp2YUEInYUqs87ByUcA4/8g3lEYeIUnRsRsWsA45S3wD7wy07t+KAn7j\n" +
|
||||
"yMmfxdma6hFfG9iN/egN6QXUAyIPXvUvlUuZ7/BhWBj/3sHMrF9quy9Q2DOI8F3t\n" +
|
||||
"1wdQrkq4BtStKhciY5AIXz9SqsctFHTv4Lwgtkapoel4izJnO0ZqYTXVe7THwri9\n" +
|
||||
"H/gua6uJDWH9jk2/CiZDWfsyFuNUuXvDSp05\n" +
|
||||
"-----END CERTIFICATE-----";
|
||||
|
||||
@Test
|
||||
public void encryptStringAsymmetric() throws Exception {
|
||||
|
@ -288,16 +291,22 @@ public class EncryptionTestIT {
|
|||
}
|
||||
|
||||
/**
|
||||
* DecryptedFolderMetadata -> EncryptedFolderMetadata -> JSON -> encrypt
|
||||
* -> decrypt -> JSON -> EncryptedFolderMetadata -> DecryptedFolderMetadata
|
||||
* DecryptedFolderMetadata -> EncryptedFolderMetadata -> JSON -> encrypt -> decrypt -> JSON ->
|
||||
* EncryptedFolderMetadata -> DecryptedFolderMetadata
|
||||
*/
|
||||
@Test
|
||||
public void encryptionMetadata() throws Exception {
|
||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
long folderID = 1;
|
||||
|
||||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1, privateKey);
|
||||
decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
|
||||
// serialize
|
||||
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||
|
@ -305,17 +314,84 @@ public class EncryptionTestIT {
|
|||
// de-serialize
|
||||
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||
new TypeToken<EncryptedFolderMetadata>() {
|
||||
});
|
||||
});
|
||||
|
||||
// decrypt
|
||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||
encryptedFolderMetadata2, privateKey);
|
||||
encryptedFolderMetadata2,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
|
||||
// compare
|
||||
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||
serializeJSON(decryptedFolderMetadata2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangedMetadataKey() throws Exception {
|
||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
long folderID = 1;
|
||||
|
||||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
|
||||
// store metadata key
|
||||
String oldMetadataKey = encryptedFolderMetadata1.getMetadata().getMetadataKey();
|
||||
|
||||
// do it again
|
||||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata2 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
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,
|
||||
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,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
|
||||
String newMetadataKey = encryptedFolderMetadata2.getMetadata().getMetadataKey();
|
||||
|
||||
assertNotEquals(oldMetadataKey, newMetadataKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCryptFileWithoutMetadata() throws Exception {
|
||||
byte[] key = decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg==");
|
||||
|
@ -333,30 +409,36 @@ public class EncryptionTestIT {
|
|||
assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r",
|
||||
"78f42172166f9dc8fd1a7156b1753353",
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
||||
.getEncrypted().getKey()),
|
||||
.getEncrypted().getKey()),
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
||||
.getInitializationVector()),
|
||||
.getInitializationVector()),
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
||||
.getAuthenticationTag())));
|
||||
.getAuthenticationTag())));
|
||||
|
||||
// n9WXAIXO2wRY4R8nXwmo
|
||||
assertTrue(cryptFile("n9WXAIXO2wRY4R8nXwmo",
|
||||
"825143ed1f21ebb0c3b3c3f005b2f5db",
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
||||
.getEncrypted().getKey()),
|
||||
.getEncrypted().getKey()),
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
||||
.getInitializationVector()),
|
||||
.getInitializationVector()),
|
||||
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
||||
.getAuthenticationTag())));
|
||||
.getAuthenticationTag())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bigMetadata() throws Exception {
|
||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
long folderID = 1;
|
||||
|
||||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1, privateKey);
|
||||
decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
|
||||
// serialize
|
||||
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||
|
@ -368,7 +450,11 @@ public class EncryptionTestIT {
|
|||
|
||||
// decrypt
|
||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||
encryptedFolderMetadata2, privateKey);
|
||||
encryptedFolderMetadata2,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
|
||||
// compare
|
||||
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||
|
@ -386,7 +472,11 @@ public class EncryptionTestIT {
|
|||
addFile(decryptedFolderMetadata1, i);
|
||||
|
||||
// encrypt
|
||||
encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1, privateKey);
|
||||
encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1,
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
|
||||
// serialize
|
||||
encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||
|
@ -397,7 +487,11 @@ public class EncryptionTestIT {
|
|||
});
|
||||
|
||||
// decrypt
|
||||
decryptedFolderMetadata2 = decryptFolderMetaData(encryptedFolderMetadata2, privateKey);
|
||||
decryptedFolderMetadata2 = decryptFolderMetaData(encryptedFolderMetadata2,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
|
||||
// compare
|
||||
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||
|
@ -411,6 +505,8 @@ public class EncryptionTestIT {
|
|||
@Test
|
||||
public void filedrop() throws Exception {
|
||||
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
|
||||
long folderID = 1;
|
||||
|
||||
// add filedrop
|
||||
Map<String, DecryptedFolderMetadata.DecryptedFile> filesdrop = new HashMap<>();
|
||||
|
@ -433,8 +529,10 @@ public class EncryptionTestIT {
|
|||
// encrypt
|
||||
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||
decryptedFolderMetadata1,
|
||||
privateKey
|
||||
);
|
||||
cert,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, cert);
|
||||
|
||||
// serialize
|
||||
|
@ -447,7 +545,11 @@ public class EncryptionTestIT {
|
|||
|
||||
// decrypt
|
||||
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||
encryptedFolderMetadata2, privateKey);
|
||||
encryptedFolderMetadata2,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folderID);
|
||||
|
||||
// compare
|
||||
assertFalse(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||
|
@ -532,6 +634,58 @@ public class EncryptionTestIT {
|
|||
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
|
||||
private boolean compareJsonStrings(String expected, String actual) {
|
||||
|
@ -561,15 +715,7 @@ public class EncryptionTestIT {
|
|||
|
||||
DecryptedFolderMetadata.Metadata metadata1 = new DecryptedFolderMetadata.Metadata();
|
||||
metadata1.setMetadataKeys(metadataKeys);
|
||||
metadata1.setVersion(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);
|
||||
metadata1.setVersion(1.1);
|
||||
|
||||
HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
|
||||
|
||||
|
@ -603,7 +749,7 @@ public class EncryptionTestIT {
|
|||
}
|
||||
|
||||
private boolean cryptFile(String fileName, String md5, byte[] key, byte[] iv, byte[] expectedAuthTag)
|
||||
throws Exception {
|
||||
throws Exception {
|
||||
File file = getFile(fileName);
|
||||
assertEquals(md5, getMD5Sum(file));
|
||||
|
||||
|
@ -629,14 +775,6 @@ public class EncryptionTestIT {
|
|||
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) {
|
||||
FileInputStream fileInputStream = null;
|
||||
try {
|
||||
|
|
|
@ -94,22 +94,13 @@ public final class PushUtils {
|
|||
try {
|
||||
messageDigest = MessageDigest.getInstance("SHA-512");
|
||||
messageDigest.update(pushToken.getBytes());
|
||||
return bytesToHex(messageDigest.digest());
|
||||
return EncryptionUtils.bytesToHex(messageDigest.digest());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log_OC.d(TAG, "SHA-512 algorithm not supported");
|
||||
}
|
||||
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() {
|
||||
migratePushKeys();
|
||||
String keyPath = MainApp.getAppContext().getFilesDir().getAbsolutePath() + File.separator +
|
||||
|
|
|
@ -71,9 +71,11 @@ public class DecryptedFolderMetadata {
|
|||
}
|
||||
|
||||
public static class Metadata {
|
||||
private Map<Integer, String> metadataKeys; // each keys is encrypted on its own, decrypt on use
|
||||
private Sharing sharing;
|
||||
private int version;
|
||||
transient
|
||||
private Map<Integer, String> metadataKeys; // outdated with v1.1
|
||||
private String metadataKey;
|
||||
private String checksum;
|
||||
private double version = 1.2;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -84,11 +86,7 @@ public class DecryptedFolderMetadata {
|
|||
return this.metadataKeys;
|
||||
}
|
||||
|
||||
public Sharing getSharing() {
|
||||
return this.sharing;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
public double getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
|
@ -96,12 +94,28 @@ public class DecryptedFolderMetadata {
|
|||
this.metadataKeys = metadataKeys;
|
||||
}
|
||||
|
||||
public void setSharing(Sharing sharing) {
|
||||
this.sharing = sharing;
|
||||
public void setVersion(double version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
public String getMetadataKey() {
|
||||
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 {
|
||||
private Data encrypted;
|
||||
private String initializationVector;
|
||||
private String authenticationTag;
|
||||
private int metadataKey;
|
||||
transient private int metadataKey;
|
||||
|
||||
public Data getEncrypted() {
|
||||
return this.encrypted;
|
||||
|
@ -181,7 +174,7 @@ public class DecryptedFolderMetadata {
|
|||
private String key;
|
||||
private String filename;
|
||||
private String mimetype;
|
||||
private int version;
|
||||
transient private double version;
|
||||
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
|
@ -195,7 +188,7 @@ public class DecryptedFolderMetadata {
|
|||
return this.mimetype;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
public double getVersion() {
|
||||
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 Map<String, EncryptedFile> files;
|
||||
|
||||
private Map<String, EncryptedFile> filedrop;
|
||||
private Map<String, EncryptedFiledrop> filedrop;
|
||||
|
||||
public EncryptedFolderMetadata(DecryptedFolderMetadata.Metadata metadata,
|
||||
Map<String, EncryptedFile> files,
|
||||
Map<String, EncryptedFile> filesdrop) {
|
||||
Map<String, EncryptedFiledrop> filesdrop) {
|
||||
this.metadata = metadata;
|
||||
this.files = files;
|
||||
this.filedrop = filesdrop;
|
||||
|
@ -48,7 +48,7 @@ public class EncryptedFolderMetadata {
|
|||
return files;
|
||||
}
|
||||
|
||||
public Map<String, EncryptedFile> getFiledrop() {
|
||||
public Map<String, EncryptedFiledrop> getFiledrop() {
|
||||
return filedrop;
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ public class EncryptedFolderMetadata {
|
|||
private String encrypted;
|
||||
private String initializationVector;
|
||||
private String authenticationTag;
|
||||
private int metadataKey;
|
||||
transient private int metadataKey;
|
||||
|
||||
public String getEncrypted() {
|
||||
return encrypted;
|
||||
|
@ -93,9 +93,5 @@ public class EncryptedFolderMetadata {
|
|||
public void setAuthenticationTag(String 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) == '.';
|
||||
}
|
||||
|
||||
/**
|
||||
* unique fileId for the file within the instance
|
||||
*/
|
||||
@SuppressFBWarnings("STT")
|
||||
public long getLocalId() {
|
||||
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() {
|
||||
return this.fileId;
|
||||
}
|
||||
|
|
|
@ -126,7 +126,9 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
|||
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent,
|
||||
client,
|
||||
privateKey,
|
||||
publicKey);
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user);
|
||||
|
||||
metadataExists = metadataPair.first;
|
||||
metadata = metadataPair.second;
|
||||
|
@ -150,7 +152,10 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
|
|||
metadata.getFiles().put(encryptedFileName, createDecryptedFile(filename));
|
||||
|
||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
||||
privateKey);
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
parent.getLocalId());
|
||||
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
||||
|
||||
// upload metadata
|
||||
|
|
|
@ -426,8 +426,8 @@ public class RefreshFolderOperation extends RemoteOperation {
|
|||
mStorageManager.removeFolder(
|
||||
mLocalFolder,
|
||||
true,
|
||||
mLocalFolder.isDown() && mLocalFolder.getStoragePath().startsWith(currentSavePath)
|
||||
);
|
||||
mLocalFolder.isDown() && mLocalFolder.getStoragePath().startsWith(currentSavePath)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ import java.io.IOException;
|
|||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
|
@ -99,6 +100,7 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
|||
DecryptedFolderMetadata metadata;
|
||||
|
||||
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
|
||||
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
|
||||
|
||||
try {
|
||||
// Lock folder
|
||||
|
@ -120,10 +122,14 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
|||
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
|
||||
|
||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
|
||||
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
||||
});
|
||||
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
||||
});
|
||||
|
||||
metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
|
||||
metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
parentId);
|
||||
} else {
|
||||
throw new RemoteOperationFailedException("No Metadata found!");
|
||||
}
|
||||
|
@ -140,8 +146,12 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
|||
// remove file from metadata
|
||||
metadata.getFiles().remove(fileName);
|
||||
|
||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
||||
privateKey);
|
||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(
|
||||
metadata,
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
parentId);
|
||||
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
|
||||
|
||||
// upload metadata
|
||||
|
@ -155,14 +165,9 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
|||
|
||||
// return success
|
||||
return result;
|
||||
} catch (NoSuchAlgorithmException |
|
||||
IOException |
|
||||
InvalidKeyException |
|
||||
InvalidAlgorithmParameterException |
|
||||
NoSuchPaddingException |
|
||||
BadPaddingException |
|
||||
IllegalBlockSizeException |
|
||||
InvalidKeySpecException e) {
|
||||
} catch (NoSuchAlgorithmException | IOException | InvalidKeyException | InvalidAlgorithmParameterException |
|
||||
NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeySpecException |
|
||||
CertificateException e) {
|
||||
result = new RemoteOperationResult(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,
|
||||
client,
|
||||
privateKey,
|
||||
publicKey);
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user);
|
||||
|
||||
metadataExists = metadataPair.first;
|
||||
DecryptedFolderMetadata metadata = metadataPair.second;
|
||||
|
@ -617,8 +619,19 @@ public class UploadFileOperation extends SyncOperation {
|
|||
metadata.getFiles().put(encryptedFileName, decryptedFile);
|
||||
|
||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
|
||||
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
|
||||
EncryptionUtils.uploadMetadata(parentFile,
|
||||
|
|
|
@ -33,6 +33,7 @@ import android.os.Handler;
|
|||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
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.client.account.User;
|
||||
import com.nextcloud.client.account.UserAccountManager;
|
||||
import com.nextcloud.client.core.Clock;
|
||||
import com.nextcloud.client.device.DeviceInfo;
|
||||
import com.nextcloud.client.di.Injectable;
|
||||
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.R;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.datamodel.SyncedFolderProvider;
|
||||
|
@ -1700,11 +1702,12 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
|||
String publicKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PUBLIC_KEY);
|
||||
String privateKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PRIVATE_KEY);
|
||||
|
||||
FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
|
||||
OCFile file = storageManager.getFileByRemoteId(event.remoteId);
|
||||
|
||||
if (publicKey.isEmpty() || privateKey.isEmpty()) {
|
||||
Log_OC.d(TAG, "no public key for " + user.getAccountName());
|
||||
|
||||
FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
|
||||
OCFile file = storageManager.getFileByRemoteId(event.remoteId);
|
||||
int position = -1;
|
||||
if (file != null) {
|
||||
position = mAdapter.getItemPosition(file);
|
||||
|
@ -1713,11 +1716,23 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
|||
dialog.setTargetFragment(this, SETUP_ENCRYPTION_REQUEST_CODE);
|
||||
dialog.show(getParentFragmentManager(), SETUP_ENCRYPTION_DIALOG_TAG);
|
||||
} 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 {
|
||||
User user = accountManager.getUser();
|
||||
OwnCloudClient client = clientFactory.create(user);
|
||||
|
@ -1727,6 +1742,45 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
|||
.execute(client);
|
||||
|
||||
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,
|
||||
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);
|
||||
} else if (remoteOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
|
||||
Snackbar.make(getRecyclerView(),
|
||||
|
@ -1738,8 +1792,8 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
|||
Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
} catch (ClientFactory.CreationException e) {
|
||||
Log_OC.e(TAG, "Cannot create client", e);
|
||||
} catch (Exception 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.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.nextcloud.client.account.User;
|
||||
import com.owncloud.android.R;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProvider;
|
||||
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
|
||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.EncryptedFiledrop;
|
||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
||||
|
@ -79,6 +81,7 @@ import java.security.spec.KeySpec;
|
|||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -103,7 +106,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
|||
* Utils for encryption
|
||||
*/
|
||||
public final class EncryptionUtils {
|
||||
private static String TAG = EncryptionUtils.class.getSimpleName();
|
||||
private static final String TAG = EncryptionUtils.class.getSimpleName();
|
||||
|
||||
public static final String PUBLIC_KEY = "PUBLIC_KEY";
|
||||
public static final String PRIVATE_KEY = "PRIVATE_KEY";
|
||||
|
@ -120,6 +123,8 @@ public final class EncryptionUtils {
|
|||
private static final String AES = "AES";
|
||||
private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
|
||||
private static final String RSA = "RSA";
|
||||
@VisibleForTesting
|
||||
public static final String MIGRATED_FOLDER_IDS = "MIGRATED_FOLDER_IDS";
|
||||
|
||||
private EncryptionUtils() {
|
||||
// utility class -> private constructor
|
||||
|
@ -129,12 +134,28 @@ public final class EncryptionUtils {
|
|||
JSON
|
||||
*/
|
||||
|
||||
public static <T> T deserializeJSON(String json, TypeToken<T> type, boolean excludeTransient) {
|
||||
if (excludeTransient) {
|
||||
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 new Gson().fromJson(json, type.getType());
|
||||
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) {
|
||||
return new Gson().toJson(data);
|
||||
return serializeJSON(data, false);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -148,19 +169,32 @@ public final class EncryptionUtils {
|
|||
* @return EncryptedFolderMetadata encrypted folder metadata
|
||||
*/
|
||||
public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
|
||||
String privateKey
|
||||
String publicKey,
|
||||
ArbitraryDataProvider arbitraryDataProvider,
|
||||
User user,
|
||||
long parentId
|
||||
)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException,
|
||||
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
|
||||
IllegalBlockSizeException, InvalidKeySpecException {
|
||||
IllegalBlockSizeException, InvalidKeySpecException, CertificateException {
|
||||
|
||||
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
|
||||
.getMetadata(),
|
||||
files,
|
||||
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"
|
||||
for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
|
||||
.getFiles().entrySet()) {
|
||||
|
@ -169,39 +203,64 @@ public final class EncryptionUtils {
|
|||
|
||||
EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile();
|
||||
encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
|
||||
encryptedFile.setMetadataKey(decryptedFile.getMetadataKey());
|
||||
encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
|
||||
|
||||
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(EncryptionUtils.decryptStringAsymmetric(
|
||||
decryptedFolderMetadata.getMetadata().getMetadataKeys().get(encryptedFile.getMetadataKey()),
|
||||
privateKey));
|
||||
|
||||
// encrypt
|
||||
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
|
||||
encryptedFile.setEncrypted(EncryptionUtils.encryptStringSymmetric(dataJson, decryptedMetadataKey));
|
||||
encryptedFile.setEncrypted(EncryptionUtils.encryptStringSymmetric(dataJson, metadataKeyBytes));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* normally done on server only internal test
|
||||
*/
|
||||
@VisibleForTesting
|
||||
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();
|
||||
public static void encryptFileDropFiles(DecryptedFolderMetadata decryptedFolderMetadata,
|
||||
EncryptedFolderMetadata encryptedFolderMetadata,
|
||||
String cert) throws NoSuchPaddingException, IllegalBlockSizeException, CertificateException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
final Map<String, EncryptedFiledrop> filesdrop = encryptedFolderMetadata.getFiledrop();
|
||||
for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
|
||||
.getFiledrop().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue();
|
||||
|
||||
EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile();
|
||||
encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
|
||||
encryptedFile.setMetadataKey(decryptedFile.getMetadataKey());
|
||||
encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
|
||||
byte[] byt = generateKey();
|
||||
String metadataKey0 = encodeBytesToBase64String(byt);
|
||||
String enc = encryptStringAsymmetric(metadataKey0, cert);
|
||||
|
||||
// encrypt
|
||||
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
|
||||
encryptedFile.setEncrypted(EncryptionUtils.encryptStringAsymmetric(dataJson, cert));
|
||||
|
||||
String encJson = encryptStringSymmetric(dataJson, byt);
|
||||
|
||||
int delimiterPosition = encJson.lastIndexOf(ivDelimiter);
|
||||
String encryptedInitializationVector = encJson.substring(delimiterPosition + ivDelimiter.length());
|
||||
String encodedCryptedBytes = encJson.substring(0, delimiterPosition);
|
||||
|
||||
|
||||
byte[] bytes = decodeStringToBase64Bytes(encodedCryptedBytes);
|
||||
|
||||
// check authentication tag
|
||||
byte[] extractedAuthenticationTag = Arrays.copyOfRange(bytes,
|
||||
bytes.length - (128 / 8),
|
||||
bytes.length);
|
||||
|
||||
String encryptedTag = encodeBytesToBase64String(extractedAuthenticationTag);
|
||||
|
||||
EncryptedFiledrop encryptedFile = new EncryptedFiledrop(encodedCryptedBytes,
|
||||
decryptedFile.getInitializationVector(),
|
||||
decryptedFile.getAuthenticationTag(),
|
||||
enc,
|
||||
encryptedTag,
|
||||
encryptedInitializationVector);
|
||||
|
||||
filesdrop.put(key, encryptedFile);
|
||||
}
|
||||
|
@ -211,7 +270,10 @@ public final class EncryptionUtils {
|
|||
* decrypt folder metaData with private key
|
||||
*/
|
||||
public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata,
|
||||
String privateKey)
|
||||
String privateKey,
|
||||
ArbitraryDataProvider arbitraryDataProvider,
|
||||
User user,
|
||||
long remoteId)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException,
|
||||
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
|
||||
IllegalBlockSizeException, InvalidKeySpecException {
|
||||
|
@ -220,34 +282,18 @@ public final class EncryptionUtils {
|
|||
DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
|
||||
encryptedFolderMetadata.getMetadata(), files);
|
||||
|
||||
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
|
||||
.getFiles().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
|
||||
byte[] decryptedMetadataKey = null;
|
||||
|
||||
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
|
||||
decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
|
||||
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
|
||||
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
||||
String encryptedMetadataKey = decryptedFolderMetadata.getMetadata().getMetadataKey();
|
||||
|
||||
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
|
||||
EncryptionUtils.decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
|
||||
.getMetadataKeys().get(encryptedFile.getMetadataKey()),
|
||||
privateKey));
|
||||
|
||||
// decrypt
|
||||
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
|
||||
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
|
||||
new TypeToken<DecryptedFolderMetadata.Data>() {
|
||||
}));
|
||||
|
||||
files.put(key, decryptedFile);
|
||||
if (encryptedMetadataKey != null) {
|
||||
decryptedMetadataKey = decodeStringToBase64Bytes(
|
||||
decryptStringAsymmetric(encryptedMetadataKey, privateKey));
|
||||
}
|
||||
|
||||
Map<String, EncryptedFolderMetadata.EncryptedFile> fileDrop = encryptedFolderMetadata.getFiledrop();
|
||||
|
||||
if (fileDrop != null) {
|
||||
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : fileDrop.entrySet()) {
|
||||
if (encryptedFolderMetadata.getFiles() != null) {
|
||||
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
|
||||
.getFiles().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
|
||||
|
||||
|
@ -256,14 +302,66 @@ public final class EncryptionUtils {
|
|||
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
|
||||
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
||||
|
||||
// decrypt
|
||||
String dataJson = EncryptionUtils.decryptStringAsymmetric(encryptedFile.getEncrypted(), privateKey);
|
||||
if (decryptedMetadataKey == null) {
|
||||
decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
|
||||
decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
|
||||
.getMetadataKeys().get(encryptedFile.getMetadataKey()),
|
||||
privateKey));
|
||||
}
|
||||
|
||||
// decrypt
|
||||
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
|
||||
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
|
||||
new TypeToken<DecryptedFolderMetadata.Data>() {
|
||||
}));
|
||||
|
||||
files.put(key, decryptedFile);
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
for (Map.Entry<String, EncryptedFiledrop> entry : fileDrop.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
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();
|
||||
decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
|
||||
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
||||
|
||||
|
||||
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(decryptedData,
|
||||
new TypeToken<DecryptedFolderMetadata.Data>() {
|
||||
}));
|
||||
|
||||
files.put(key, decryptedFile);
|
||||
|
||||
// remove from filedrop
|
||||
fileDrop.remove(key);
|
||||
|
@ -294,6 +392,7 @@ public final class EncryptionUtils {
|
|||
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
|
||||
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
|
||||
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
|
||||
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
|
||||
|
||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
|
||||
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
||||
|
@ -306,7 +405,10 @@ public final class EncryptionUtils {
|
|||
}
|
||||
DecryptedFolderMetadata decryptedFolderMetadata = EncryptionUtils.decryptFolderMetaData(
|
||||
encryptedFolderMetadata,
|
||||
privateKey);
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folder.getLocalId());
|
||||
|
||||
boolean transferredFiledrop = filesDropCountBefore > 0 && decryptedFolderMetadata.getFiles().size() ==
|
||||
encryptedFolderMetadata.getFiles().size() + filesDropCountBefore;
|
||||
|
@ -317,7 +419,10 @@ public final class EncryptionUtils {
|
|||
|
||||
// upload metadata
|
||||
EncryptedFolderMetadata encryptedFolderMetadataNew = encryptFolderMetadata(decryptedFolderMetadata,
|
||||
privateKey);
|
||||
publicKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
folder.getLocalId());
|
||||
|
||||
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadataNew);
|
||||
|
||||
|
@ -605,6 +710,36 @@ public final class EncryptionUtils {
|
|||
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
|
||||
|
@ -883,10 +1018,15 @@ public final class EncryptionUtils {
|
|||
public static Pair<Boolean, DecryptedFolderMetadata> retrieveMetadata(OCFile parentFile,
|
||||
OwnCloudClient client,
|
||||
String privateKey,
|
||||
String publicKey) throws UploadException,
|
||||
String publicKey,
|
||||
ArbitraryDataProvider arbitraryDataProvider,
|
||||
User user)
|
||||
throws UploadException,
|
||||
InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
|
||||
IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
|
||||
GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(parentFile.getLocalId());
|
||||
long localId = parentFile.getLocalId();
|
||||
|
||||
GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(localId);
|
||||
RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client);
|
||||
|
||||
DecryptedFolderMetadata metadata;
|
||||
|
@ -900,7 +1040,11 @@ public final class EncryptionUtils {
|
|||
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
||||
});
|
||||
|
||||
return new Pair<>(Boolean.TRUE, EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey));
|
||||
return new Pair<>(Boolean.TRUE, decryptFolderMetaData(encryptedFolderMetadata,
|
||||
privateKey,
|
||||
arbitraryDataProvider,
|
||||
user,
|
||||
localId));
|
||||
|
||||
} else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
|
||||
// new metadata
|
||||
|
@ -909,7 +1053,7 @@ public final class EncryptionUtils {
|
|||
metadata.getMetadata().setMetadataKeys(new HashMap<>());
|
||||
String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
|
||||
String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
|
||||
metadata.getMetadata().getMetadataKeys().put(0, encryptedMetadataKey);
|
||||
metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
|
||||
|
||||
return new Pair<>(Boolean.FALSE, metadata);
|
||||
} else {
|
||||
|
@ -984,4 +1128,83 @@ public final class EncryptionUtils {
|
|||
file.isFolder() &&
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
<string name="conflict_local_file">fichier local</string>
|
||||
<string name="conflict_message_description">Si vous sélectionnez les deux versions, le fichier local aura un numéro ajouté à son nom.</string>
|
||||
<string name="conflict_server_file">Fichier serveur</string>
|
||||
<string name="contact_backup_title">Sauvegarde des contacts</string>
|
||||
<string name="contactlist_item_icon">L\'icône de l\'utilisateur pour la liste des contacts</string>
|
||||
<string name="contactlist_no_permission">Pas de permission donnée, rien n\'a été importé.</string>
|
||||
<string name="contacts">Contacts</string>
|
||||
|
@ -236,6 +237,7 @@ Attention la suppression est irréversible.</string>
|
|||
<string name="drawer_item_all_files">Tous les fichiers</string>
|
||||
<string name="drawer_item_favorites">Favoris</string>
|
||||
<string name="drawer_item_gallery">Médias</string>
|
||||
<string name="drawer_item_groupfolders">Dossiers de groupes</string>
|
||||
<string name="drawer_item_home">Accueil</string>
|
||||
<string name="drawer_item_notifications">Notifications</string>
|
||||
<string name="drawer_item_on_device">Sur l\'appareil</string>
|
||||
|
@ -559,6 +561,7 @@ Attention la suppression est irréversible.</string>
|
|||
<string name="prefs_category_general">Général</string>
|
||||
<string name="prefs_category_more">Plus</string>
|
||||
<string name="prefs_daily_backup_summary">Sauvegarde quotidienne de votre agenda & des contacts</string>
|
||||
<string name="prefs_daily_contact_backup_summary">Sauvegarde quotidienne de vos contacts</string>
|
||||
<string name="prefs_davx5_setup_error">Erreur inattendue lors de la configuration de DAVx5 (anciennement connu sous le nom de DAVdroid)</string>
|
||||
<string name="prefs_e2e_active">Le chiffrement de bout en bout est configuré !</string>
|
||||
<string name="prefs_e2e_mnemonic">Phrase secrète E2E</string>
|
||||
|
@ -798,6 +801,7 @@ Attention la suppression est irréversible.</string>
|
|||
<string name="thumbnail">Miniature</string>
|
||||
<string name="thumbnail_for_existing_file_description">Vignette pour fichier existant</string>
|
||||
<string name="thumbnail_for_new_file_desc">Vignette pour nouveau fichier</string>
|
||||
<string name="timeout_richDocuments">Le chargement prend plus de temps que prévu</string>
|
||||
<string name="today">Aujourd\'hui</string>
|
||||
<string name="trashbin_activity_title">Fichiers supprimés</string>
|
||||
<string name="trashbin_empty_headline">Aucun fichier supprimé</string>
|
||||
|
|
|
@ -800,6 +800,7 @@
|
|||
<string name="thumbnail">Ikon</string>
|
||||
<string name="thumbnail_for_existing_file_description">Miniatyrbild för befintlig fil</string>
|
||||
<string name="thumbnail_for_new_file_desc">Miniatyrbild för ny fil</string>
|
||||
<string name="timeout_richDocuments">Inläsningen tar längre tid än förväntat</string>
|
||||
<string name="today">Idag</string>
|
||||
<string name="trashbin_activity_title">Borttagna filer</string>
|
||||
<string name="trashbin_empty_headline">Inga borttagna filer</string>
|
||||
|
|
Loading…
Reference in a new issue