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,12 +90,13 @@ 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" +
|
||||||
"IBAQDsn0JKS/THu328z1IgN0VzYU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzV" +
|
"IBAQDsn0JKS/THu328z1IgN0VzYU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzV" +
|
||||||
"GzKFvGfZ03fwFrN7Q8P8R2e8SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7" +
|
"GzKFvGfZ03fwFrN7Q8P8R2e8SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7" +
|
||||||
|
@ -104,44 +107,44 @@ public class EncryptionTestIT {
|
||||||
"SYk2jjjWVSXRNmex+V6+Y/jBRT2mvAgm8J+7LPwFdatE+lz0aZrMRD2gCWYF6Itpda" +
|
"SYk2jjjWVSXRNmex+V6+Y/jBRT2mvAgm8J+7LPwFdatE+lz0aZrMRD2gCWYF6Itpda" +
|
||||||
"90OlLkmQPVWWtGTgX2ta2tF5r2iSGzk0IdoL8zw98Q2UzpOcw30KnWtFMxuxWk0mHq" +
|
"90OlLkmQPVWWtGTgX2ta2tF5r2iSGzk0IdoL8zw98Q2UzpOcw30KnWtFMxuxWk0mHq" +
|
||||||
"pgp00g80cDWg3+RPbWOhdLp5bflQ36fKDfmjq05cGlIk6unnVyC5HXpvh4d4k2EWlX" +
|
"pgp00g80cDWg3+RPbWOhdLp5bflQ36fKDfmjq05cGlIk6unnVyC5HXpvh4d4k2EWlX" +
|
||||||
"rjGsndVBPCjGkZePlLRgDHxT06r+5XdJ+1CBDZgCsmjGz3M8uOHyCfVW0WhB7ynzDT" +
|
"rjGsndVBPCjGkZePlLRgDHxT06r+5XdJ+1CBDZgCsmjGz3M8uOHyCfVW0WhB7ynzDT" +
|
||||||
"agVgz0iqpuhAi9sPt6iWWwpAnRw8cQgqEKw9bvKKECgYEA/WPi2PJtL6u/xlysh/H7" +
|
"agVgz0iqpuhAi9sPt6iWWwpAnRw8cQgqEKw9bvKKECgYEA/WPi2PJtL6u/xlysh/H7" +
|
||||||
"A717CId6fPHCMDace39ZNtzUzc0nT5BemlcF0wZ74NeJSur3Q395YzB+eBMLs5p8mA" +
|
"A717CId6fPHCMDace39ZNtzUzc0nT5BemlcF0wZ74NeJSur3Q395YzB+eBMLs5p8mA" +
|
||||||
"95wgGvJhM65/J+HX+k9kt6Z556zLMvtG+j1yo4D0VEwm3xahB4SUUP+1kD7dNvo4+8" +
|
"95wgGvJhM65/J+HX+k9kt6Z556zLMvtG+j1yo4D0VEwm3xahB4SUUP+1kD7dNvo4+8" +
|
||||||
"xeSCyjzNllvYZZC0DrECgYEA7w8pEqhHHn0a+twkPCZJS+gQTB9Rm+FBNGJqB3XpWs" +
|
"xeSCyjzNllvYZZC0DrECgYEA7w8pEqhHHn0a+twkPCZJS+gQTB9Rm+FBNGJqB3XpWs" +
|
||||||
"TeLUxYRbVGk0iDve+eeeZ41drxcdyWP+WcL34hnrjgI1Fo4mK88saajpwUIYMy6+qM" +
|
"TeLUxYRbVGk0iDve+eeeZ41drxcdyWP+WcL34hnrjgI1Fo4mK88saajpwUIYMy6+qM" +
|
||||||
"LY+jC2NRSBox56eH7nsVYvQQK9eKqv9wbB+PF9SwOIvuETN7fd8mAY02UnoaaU8CgY" +
|
"LY+jC2NRSBox56eH7nsVYvQQK9eKqv9wbB+PF9SwOIvuETN7fd8mAY02UnoaaU8CgY" +
|
||||||
"BoHRKocXPLkpZJuuppMVQiRUi4SHJbxDo19Tp2w+y0TihiJ1lvp7I3WGpcOt3LlMQk" +
|
"BoHRKocXPLkpZJuuppMVQiRUi4SHJbxDo19Tp2w+y0TihiJ1lvp7I3WGpcOt3LlMQk" +
|
||||||
"tEbExSvrRZGxZKH6Og/XqwQsYuTEkEIz679F/5yYVosE6GkskrOXQAfh8Mb3/04xVV" +
|
"tEbExSvrRZGxZKH6Og/XqwQsYuTEkEIz679F/5yYVosE6GkskrOXQAfh8Mb3/04xVV" +
|
||||||
"tMaVgDQw0+CWVD4wyL+BNofGwBDNqsXTCdCsfxAQKBgQCDv2EtbRw0y1HRKv21QIxo" +
|
"tMaVgDQw0+CWVD4wyL+BNofGwBDNqsXTCdCsfxAQKBgQCDv2EtbRw0y1HRKv21QIxo" +
|
||||||
"ju5cZW4+cDfVPN+eWPdQFOs1H7wOPsc0aGRiiupV2BSEF3O1ApKziEE5U1QH+29bR4" +
|
"ju5cZW4+cDfVPN+eWPdQFOs1H7wOPsc0aGRiiupV2BSEF3O1ApKziEE5U1QH+29bR4" +
|
||||||
"R8L1pemeGX8qCNj5bCubKjcWOz5PpouDcEqimZ3q98p3E6GEHN15UHoaTkx0yO/V8o" +
|
"R8L1pemeGX8qCNj5bCubKjcWOz5PpouDcEqimZ3q98p3E6GEHN15UHoaTkx0yO/V8o" +
|
||||||
"j6zhQ9fYRxDHB5ACtQKBgQCOO7TJUO1IaLTjcrwS4oCfJyRnAdz49L1AbVJkIBK0fh" +
|
"j6zhQ9fYRxDHB5ACtQKBgQCOO7TJUO1IaLTjcrwS4oCfJyRnAdz49L1AbVJkIBK0fh" +
|
||||||
"JLecOFu3ZlQl/RStQb69QKb5MNOIMmQhg8WOxZxHcpmIDbkDAm/J/ovJXFSoBdOr5o" +
|
"JLecOFu3ZlQl/RStQb69QKb5MNOIMmQhg8WOxZxHcpmIDbkDAm/J/ovJXFSoBdOr5o" +
|
||||||
"uQsYsDZhsWW97zvLMzg5pH9/3/1BNz5q3Vu4HgfBSwWGt4E2NENj+XA+QAVmGA==";
|
"uQsYsDZhsWW97zvLMzg5pH9/3/1BNz5q3Vu4HgfBSwWGt4E2NENj+XA+QAVmGA==";
|
||||||
|
|
||||||
private String cert = "-----BEGIN CERTIFICATE-----\n" +
|
private String cert = "-----BEGIN CERTIFICATE-----\n" +
|
||||||
"MIIDpzCCAo+gAwIBAgIBADANBgkqhkiG9w0BAQUFADBuMRowGAYDVQQDDBF3d3cu\n" +
|
"MIIDpzCCAo+gAwIBAgIBADANBgkqhkiG9w0BAQUFADBuMRowGAYDVQQDDBF3d3cu\n" +
|
||||||
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
|
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
|
||||||
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
|
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
|
||||||
"HhcNMTcwOTI2MTAwNDMwWhcNMzcwOTIxMTAwNDMwWjBuMRowGAYDVQQDDBF3d3cu\n" +
|
"HhcNMTcwOTI2MTAwNDMwWhcNMzcwOTIxMTAwNDMwWjBuMRowGAYDVQQDDBF3d3cu\n" +
|
||||||
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
|
"bmV4dGNsb3VkLmNvbTESMBAGA1UECgwJTmV4dGNsb3VkMRIwEAYDVQQHDAlTdHV0\n" +
|
||||||
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
|
"dGdhcnQxGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzELMAkGA1UEBhMCREUw\n" +
|
||||||
"ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDsn0JKS/THu328z1IgN0Vz\n" +
|
"ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDsn0JKS/THu328z1IgN0Vz\n" +
|
||||||
"YU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzVGzKFvGfZ03fwFrN7Q8P8R2e8\n" +
|
"YU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzVGzKFvGfZ03fwFrN7Q8P8R2e8\n" +
|
||||||
"SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7Y0BJX9i/nW/L0L/VaE8CZT\n" +
|
"SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7Y0BJX9i/nW/L0L/VaE8CZT\n" +
|
||||||
"AqYBdcSJGgHJjY4UMf892ZPTa9T2Dl3ggdMZ7BQ2kiCiCC3qV99b0igRJGmmLQaG\n" +
|
"AqYBdcSJGgHJjY4UMf892ZPTa9T2Dl3ggdMZ7BQ2kiCiCC3qV99b0igRJGmmLQaG\n" +
|
||||||
"iAflhFzuDQPMifUMq75wI8RSRPdxUAtjTfkl68QHu7Umyeyy33OQgdUKaTl5zcS3\n" +
|
"iAflhFzuDQPMifUMq75wI8RSRPdxUAtjTfkl68QHu7Umyeyy33OQgdUKaTl5zcS3\n" +
|
||||||
"VSQbNjveVCNM4RDH1RlEc+7Wf1BY8APqT6jbiBcROJD2CeoLH2eiIJCi+61ZkSGf\n" +
|
"VSQbNjveVCNM4RDH1RlEc+7Wf1BY8APqT6jbiBcROJD2CeoLH2eiIJCi+61ZkSGf\n" +
|
||||||
"AgMBAAGjUDBOMB0GA1UdDgQWBBTFrXz2tk1HivD9rQ75qeoyHrAgIjAfBgNVHSME\n" +
|
"AgMBAAGjUDBOMB0GA1UdDgQWBBTFrXz2tk1HivD9rQ75qeoyHrAgIjAfBgNVHSME\n" +
|
||||||
"GDAWgBTFrXz2tk1HivD9rQ75qeoyHrAgIjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3\n" +
|
"GDAWgBTFrXz2tk1HivD9rQ75qeoyHrAgIjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3\n" +
|
||||||
"DQEBBQUAA4IBAQARQTX21QKO77gAzBszFJ6xVnjfa23YZF26Z4X1KaM8uV8TGzuN\n" +
|
"DQEBBQUAA4IBAQARQTX21QKO77gAzBszFJ6xVnjfa23YZF26Z4X1KaM8uV8TGzuN\n" +
|
||||||
"JA95XmReeP2iO3r8EWXS9djVCD64m2xx6FOsrUI8HZaw1JErU8mmOaLAe8q9RsOm\n" +
|
"JA95XmReeP2iO3r8EWXS9djVCD64m2xx6FOsrUI8HZaw1JErU8mmOaLAe8q9RsOm\n" +
|
||||||
"9Eq37e4vFp2YUEInYUqs87ByUcA4/8g3lEYeIUnRsRsWsA45S3wD7wy07t+KAn7j\n" +
|
"9Eq37e4vFp2YUEInYUqs87ByUcA4/8g3lEYeIUnRsRsWsA45S3wD7wy07t+KAn7j\n" +
|
||||||
"yMmfxdma6hFfG9iN/egN6QXUAyIPXvUvlUuZ7/BhWBj/3sHMrF9quy9Q2DOI8F3t\n" +
|
"yMmfxdma6hFfG9iN/egN6QXUAyIPXvUvlUuZ7/BhWBj/3sHMrF9quy9Q2DOI8F3t\n" +
|
||||||
"1wdQrkq4BtStKhciY5AIXz9SqsctFHTv4Lwgtkapoel4izJnO0ZqYTXVe7THwri9\n" +
|
"1wdQrkq4BtStKhciY5AIXz9SqsctFHTv4Lwgtkapoel4izJnO0ZqYTXVe7THwri9\n" +
|
||||||
"H/gua6uJDWH9jk2/CiZDWfsyFuNUuXvDSp05\n" +
|
"H/gua6uJDWH9jk2/CiZDWfsyFuNUuXvDSp05\n" +
|
||||||
"-----END CERTIFICATE-----";
|
"-----END CERTIFICATE-----";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void encryptStringAsymmetric() throws Exception {
|
public void encryptStringAsymmetric() throws Exception {
|
||||||
|
@ -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);
|
||||||
|
@ -305,17 +315,88 @@ public class EncryptionTestIT {
|
||||||
// de-serialize
|
// de-serialize
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||||
new TypeToken<EncryptedFolderMetadata>() {
|
new TypeToken<EncryptedFolderMetadata>() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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==");
|
||||||
|
@ -333,30 +414,37 @@ public class EncryptionTestIT {
|
||||||
assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r",
|
assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r",
|
||||||
"78f42172166f9dc8fd1a7156b1753353",
|
"78f42172166f9dc8fd1a7156b1753353",
|
||||||
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
||||||
.getEncrypted().getKey()),
|
.getEncrypted().getKey()),
|
||||||
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
||||||
.getInitializationVector()),
|
.getInitializationVector()),
|
||||||
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
|
||||||
.getAuthenticationTag())));
|
.getAuthenticationTag())));
|
||||||
|
|
||||||
// n9WXAIXO2wRY4R8nXwmo
|
// n9WXAIXO2wRY4R8nXwmo
|
||||||
assertTrue(cryptFile("n9WXAIXO2wRY4R8nXwmo",
|
assertTrue(cryptFile("n9WXAIXO2wRY4R8nXwmo",
|
||||||
"825143ed1f21ebb0c3b3c3f005b2f5db",
|
"825143ed1f21ebb0c3b3c3f005b2f5db",
|
||||||
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
||||||
.getEncrypted().getKey()),
|
.getEncrypted().getKey()),
|
||||||
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
||||||
.getInitializationVector()),
|
.getInitializationVector()),
|
||||||
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
|
||||||
.getAuthenticationTag())));
|
.getAuthenticationTag())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@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<>();
|
||||||
|
|
||||||
|
@ -603,7 +757,7 @@ public class EncryptionTestIT {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean cryptFile(String fileName, String md5, byte[] key, byte[] iv, byte[] expectedAuthTag)
|
private boolean cryptFile(String fileName, String md5, byte[] key, byte[] iv, byte[] expectedAuthTag)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
File file = getFile(fileName);
|
File file = getFile(fileName);
|
||||||
assertEquals(md5, getMD5Sum(file));
|
assertEquals(md5, getMD5Sum(file));
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
|
@ -426,8 +426,8 @@ public class RefreshFolderOperation extends RemoteOperation {
|
||||||
mStorageManager.removeFolder(
|
mStorageManager.removeFolder(
|
||||||
mLocalFolder,
|
mLocalFolder,
|
||||||
true,
|
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.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
|
||||||
|
@ -120,10 +122,14 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
||||||
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
|
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
|
||||||
|
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
|
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 {
|
} 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, 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) {
|
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) {
|
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,34 +264,18 @@ public final class EncryptionUtils {
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
|
DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
|
||||||
encryptedFolderMetadata.getMetadata(), files);
|
encryptedFolderMetadata.getMetadata(), files);
|
||||||
|
|
||||||
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
|
byte[] decryptedMetadataKey = null;
|
||||||
.getFiles().entrySet()) {
|
|
||||||
String key = entry.getKey();
|
|
||||||
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
|
|
||||||
|
|
||||||
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
|
String encryptedMetadataKey = decryptedFolderMetadata.getMetadata().getMetadataKey();
|
||||||
decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
|
|
||||||
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
|
|
||||||
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
|
||||||
|
|
||||||
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
|
if (encryptedMetadataKey != null) {
|
||||||
EncryptionUtils.decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
|
decryptedMetadataKey = decodeStringToBase64Bytes(
|
||||||
.getMetadataKeys().get(encryptedFile.getMetadataKey()),
|
decryptStringAsymmetric(encryptedMetadataKey, privateKey));
|
||||||
privateKey));
|
|
||||||
|
|
||||||
// decrypt
|
|
||||||
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
|
|
||||||
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
|
|
||||||
new TypeToken<DecryptedFolderMetadata.Data>() {
|
|
||||||
}));
|
|
||||||
|
|
||||||
files.put(key, decryptedFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, EncryptedFolderMetadata.EncryptedFile> fileDrop = encryptedFolderMetadata.getFiledrop();
|
if (encryptedFolderMetadata.getFiles() != null) {
|
||||||
|
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
|
||||||
if (fileDrop != null) {
|
.getFiles().entrySet()) {
|
||||||
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : fileDrop.entrySet()) {
|
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
|
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
|
||||||
|
|
||||||
|
@ -256,14 +284,66 @@ public final class EncryptionUtils {
|
||||||
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
|
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
|
||||||
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
||||||
|
|
||||||
// decrypt
|
if (decryptedMetadataKey == null) {
|
||||||
String dataJson = EncryptionUtils.decryptStringAsymmetric(encryptedFile.getEncrypted(), privateKey);
|
decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
|
||||||
|
decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
|
||||||
|
.getMetadataKeys().get(encryptedFile.getMetadataKey()),
|
||||||
|
privateKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt
|
||||||
|
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
|
||||||
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
|
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
|
||||||
new TypeToken<DecryptedFolderMetadata.Data>() {
|
new TypeToken<DecryptedFolderMetadata.Data>() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
files.put(key, decryptedFile);
|
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
|
// remove from filedrop
|
||||||
fileDrop.remove(key);
|
fileDrop.remove(key);
|
||||||
|
@ -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