Эх сурвалжийг харах

E2E v1.2

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
tobiasKaminsky 1 жил өмнө
parent
commit
5492a99e38

+ 1 - 1
CHANGELOG.md

@@ -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

+ 223 - 77
app/src/androidTest/java/com/owncloud/android/util/EncryptionTestIT.java

@@ -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,12 +90,13 @@ 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" +
         "IBAQDsn0JKS/THu328z1IgN0VzYU53HjSX03WJIgWkmyTaxbiKpoJaKbksXmfSpgzV" +
         "GzKFvGfZ03fwFrN7Q8P8R2e8SNiell7mh1TDw9/0P7Bt/ER8PJrXORo+GviKHxaLr7" +
@@ -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,23 @@ 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,
+            privateKey,
+            cert,
+            arbitraryDataProvider,
+            user,
+            folderID);
 
         // serialize
         String encryptedJson = serializeJSON(encryptedFolderMetadata1);
@@ -305,17 +315,88 @@ 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,
+            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
     public void testCryptFileWithoutMetadata() throws Exception {
         byte[] key = decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg==");
@@ -333,30 +414,37 @@ 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,
+            privateKey,
+            cert,
+            arbitraryDataProvider,
+            user,
+            folderID);
 
         // serialize
         String encryptedJson = serializeJSON(encryptedFolderMetadata1);
@@ -368,7 +456,11 @@ public class EncryptionTestIT {
 
         // decrypt
         DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
-            encryptedFolderMetadata2, privateKey);
+            encryptedFolderMetadata2,
+            privateKey,
+            arbitraryDataProvider,
+            user,
+            folderID);
 
         // compare
         assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
@@ -386,7 +478,12 @@ public class EncryptionTestIT {
             addFile(decryptedFolderMetadata1, i);
 
             // encrypt
-            encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1, privateKey);
+            encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1,
+                                                             privateKey,
+                                                             cert,
+                                                             arbitraryDataProvider,
+                                                             user,
+                                                             folderID);
 
             // serialize
             encryptedJson = serializeJSON(encryptedFolderMetadata1);
@@ -397,7 +494,11 @@ public class EncryptionTestIT {
                                                        });
 
             // decrypt
-            decryptedFolderMetadata2 = decryptFolderMetaData(encryptedFolderMetadata2, privateKey);
+            decryptedFolderMetadata2 = decryptFolderMetaData(encryptedFolderMetadata2,
+                                                             privateKey,
+                                                             arbitraryDataProvider,
+                                                             user,
+                                                             folderID);
 
             // compare
             assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
@@ -411,6 +512,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 +536,11 @@ public class EncryptionTestIT {
         // encrypt
         EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
             decryptedFolderMetadata1,
-            privateKey
-                                                                                );
+            privateKey,
+            cert,
+            arbitraryDataProvider,
+            user,
+            folderID);
         EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, cert);
 
         // serialize
@@ -447,7 +553,11 @@ public class EncryptionTestIT {
 
         // decrypt
         DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
-            encryptedFolderMetadata2, privateKey);
+            encryptedFolderMetadata2,
+            privateKey,
+            arbitraryDataProvider,
+            user,
+            folderID);
 
         // compare
         assertFalse(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
@@ -532,6 +642,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 +723,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 +757,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 +783,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 {

+ 1 - 10
app/src/gplay/java/com/owncloud/android/utils/PushUtils.java

@@ -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 +

+ 29 - 36
app/src/main/java/com/owncloud/android/datamodel/DecryptedFolderMetadata.java

@@ -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;
         }
 

+ 31 - 0
app/src/main/java/com/owncloud/android/datamodel/EncryptedFiledrop.kt

@@ -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
+)

+ 4 - 8
app/src/main/java/com/owncloud/android/datamodel/EncryptedFolderMetadata.java

@@ -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;
-        }
     }
 }

+ 6 - 0
app/src/main/java/com/owncloud/android/datamodel/OCFile.java

@@ -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;
     }

+ 8 - 2
app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java

@@ -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,11 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
                 metadata.getFiles().put(encryptedFileName, createDecryptedFile(filename));
 
                 EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
-                                                                                                        privateKey);
+                                                                                                        privateKey,
+                                                                                                        publicKey,
+                                                                                                        arbitraryDataProvider,
+                                                                                                        user,
+                                                                                                        parent.getLocalId());
                 String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
 
                 // upload metadata

+ 2 - 2
app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java

@@ -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)
+                                        );
         }
     }
 

+ 20 - 14
app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.java

@@ -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>() {
-                        });
-
-                metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
+                    serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
+                    });
+
+                metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata,
+                                                                 privateKey,
+                                                                 arbitraryDataProvider,
+                                                                 user,
+                                                                 parentId);
             } else {
                 throw new RemoteOperationFailedException("No Metadata found!");
             }
@@ -140,8 +146,13 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
             // remove file from metadata
             metadata.getFiles().remove(fileName);
 
-            EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
-                    privateKey);
+            EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(
+                metadata,
+                privateKey,
+                publicKey,
+                arbitraryDataProvider,
+                user,
+                parentId);
             String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
 
             // upload metadata
@@ -155,14 +166,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);
 

+ 17 - 3
app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java

@@ -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,20 @@ public class UploadFileOperation extends SyncOperation {
                 metadata.getFiles().put(encryptedFileName, decryptedFile);
 
                 EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
-                                                                                                        privateKey);
-                String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
+                                                                                                        privateKey,
+                                                                                                        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,

+ 65 - 10
app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -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);
-        }
-    }
-
-    private void encryptFolder(long localId, String remoteId, String remotePath, boolean shouldBeEncrypted) {
+            encryptFolder(file,
+                          event.localId,
+                          event.remoteId,
+                          event.remotePath,
+                          event.shouldBeEncrypted,
+                          publicKey,
+                          privateKey);
+        }
+    }
+
+    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,46 @@ 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,
+                                                                                                        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);
             } else if (remoteOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
                 Snackbar.make(getRecyclerView(),
@@ -1738,8 +1793,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);
         }
     }
 

+ 253 - 49
app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java

@@ -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;
@@ -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,33 @@ public final class EncryptionUtils {
      * @return EncryptedFolderMetadata encrypted folder metadata
      */
     public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
-                                                                String privateKey
+                                                                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 +204,45 @@ 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;
     }
 
     @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();
+        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());
+            // TODO
+            String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
+            EncryptedFiledrop encryptedFile = new EncryptedFiledrop(dataJson,
+                                                                    decryptedFile.getInitializationVector(),
+                                                                    decryptedFile.getAuthenticationTag(),
+                                                                    "123",
+                                                                    "123",
+                                                                    "123");
+//            encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
+//            encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
 
             // encrypt
-            String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
-            encryptedFile.setEncrypted(EncryptionUtils.encryptStringAsymmetric(dataJson, cert));
+
+//            encryptedFile.setEncrypted(EncryptionUtils.encryptStringAsymmetric(dataJson, cert));
 
             filesdrop.put(key, encryptedFile);
         }
@@ -211,7 +252,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,46 +264,82 @@ 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));
+        if (encryptedMetadataKey != null) {
+            decryptedMetadataKey = decodeStringToBase64Bytes(
+                decryptStringAsymmetric(encryptedMetadataKey, privateKey));
+        }
 
-            // decrypt
-            String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
-            decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
-                                                                       new TypeToken<DecryptedFolderMetadata.Data>() {
-                                                                       }));
+        if (encryptedFolderMetadata.getFiles() != null) {
+            for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
+                .getFiles().entrySet()) {
+                String key = entry.getKey();
+                EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
 
-            files.put(key, decryptedFile);
+                DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
+                decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
+                decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
+                decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
+
+                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);
+            }
         }
 
-        Map<String, EncryptedFolderMetadata.EncryptedFile> fileDrop = encryptedFolderMetadata.getFiledrop();
+        // verify checksum
+        String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
+        String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
+        String decryptedFolderChecksum = decryptedFolderMetadata.getMetadata().getChecksum();
+
+        if (TextUtils.isEmpty(decryptedFolderChecksum) &&
+            isFolderMigrated(remoteId, user, arbitraryDataProvider)) {
+            throw new IllegalStateException("Possible downgrade attack detected!");
+        }
+
+        if (!TextUtils.isEmpty(decryptedFolderChecksum) && !decryptedFolderChecksum.equals(checksum)) {
+            throw new IllegalStateException("Wrong checksum!");
+        }
+
+        Map<String, EncryptedFiledrop> fileDrop = encryptedFolderMetadata.getFiledrop();
 
         if (fileDrop != null) {
-            for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : fileDrop.entrySet()) {
+            for (Map.Entry<String, EncryptedFiledrop> entry : fileDrop.entrySet()) {
                 String key = entry.getKey();
-                EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
+                EncryptedFiledrop encryptedFile = entry.getValue();
+
+                // decrypt key
+                String encryptedKey = decryptStringAsymmetric(encryptedFile.getEncryptedKey(),
+                                                              privateKey);
+
+                // decrypt encrypted blob with key
+                String decryptedData = decryptStringSymmetric(
+                    encryptedFile.getEncrypted(),
+                    decodeStringToBase64Bytes(encryptedKey),
+                    decodeStringToBase64Bytes(encryptedFile.getEncryptedInitializationVector()),
+                    decodeStringToBase64Bytes(encryptedFile.getEncryptedTag())
+                                                             );
 
                 DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
                 decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
-                decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
                 decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
 
-                // decrypt
-                String dataJson = EncryptionUtils.decryptStringAsymmetric(encryptedFile.getEncrypted(), privateKey);
 
-                decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
+                decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(decryptedData,
                                                                            new TypeToken<DecryptedFolderMetadata.Data>() {
                                                                            }));
 
@@ -294,6 +374,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 +387,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 +401,11 @@ public final class EncryptionUtils {
 
                 // upload metadata
                 EncryptedFolderMetadata encryptedFolderMetadataNew = encryptFolderMetadata(decryptedFolderMetadata,
-                                                                                           privateKey);
+                                                                                           privateKey,
+                                                                                           publicKey,
+                                                                                           arbitraryDataProvider,
+                                                                                           user,
+                                                                                           folder.getLocalId());
 
                 String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadataNew);
 
@@ -605,6 +693,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,7 +1001,10 @@ 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());
@@ -900,7 +1021,11 @@ public final class EncryptionUtils {
                 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) {
             // new metadata
@@ -909,7 +1034,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 +1109,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);
+    }
 }