Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
tobiasKaminsky 2023-05-16 09:09:47 +02:00
parent c35e1e6ab3
commit 5492a99e38
No known key found for this signature in database
GPG key ID: 0E00D4D47D0C5AF7
13 changed files with 657 additions and 209 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -47,6 +47,7 @@ import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.BadPaddingException;
@ -99,6 +100,7 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
DecryptedFolderMetadata metadata;
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
try {
// Lock folder
@ -120,10 +122,14 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
});
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
});
metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata,
privateKey,
arbitraryDataProvider,
user,
parentId);
} else {
throw new RemoteOperationFailedException("No Metadata found!");
}
@ -140,8 +146,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);

View file

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

View file

@ -33,6 +33,7 @@ import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
@ -51,7 +52,6 @@ import com.nextcloud.android.lib.resources.files.ToggleFileLockRemoteOperation;
import com.nextcloud.android.lib.richWorkspace.RichWorkspaceDirectEditingRemoteOperation;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.DeviceInfo;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.documentscan.AppScanOptionalFeature;
@ -68,6 +68,8 @@ import com.nextcloud.utils.view.FastScrollUtils;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.SyncedFolderProvider;
@ -1700,11 +1702,12 @@ public class OCFileListFragment extends ExtendedListFragment implements
String publicKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PUBLIC_KEY);
String privateKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PRIVATE_KEY);
FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
OCFile file = storageManager.getFileByRemoteId(event.remoteId);
if (publicKey.isEmpty() || privateKey.isEmpty()) {
Log_OC.d(TAG, "no public key for " + user.getAccountName());
FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
OCFile file = storageManager.getFileByRemoteId(event.remoteId);
int position = -1;
if (file != null) {
position = mAdapter.getItemPosition(file);
@ -1713,11 +1716,23 @@ public class OCFileListFragment extends ExtendedListFragment implements
dialog.setTargetFragment(this, SETUP_ENCRYPTION_REQUEST_CODE);
dialog.show(getParentFragmentManager(), SETUP_ENCRYPTION_DIALOG_TAG);
} else {
encryptFolder(event.localId, event.remoteId, event.remotePath, event.shouldBeEncrypted);
encryptFolder(file,
event.localId,
event.remoteId,
event.remotePath,
event.shouldBeEncrypted,
publicKey,
privateKey);
}
}
private void encryptFolder(long localId, String remoteId, String remotePath, boolean shouldBeEncrypted) {
private void encryptFolder(OCFile folder,
long localId,
String remoteId,
String remotePath,
boolean shouldBeEncrypted,
String publicKey,
String privateKey) {
try {
User user = accountManager.getUser();
OwnCloudClient client = clientFactory.create(user);
@ -1727,6 +1742,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);
}
}

View file

@ -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,34 +264,18 @@ public final class EncryptionUtils {
DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
encryptedFolderMetadata.getMetadata(), files);
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
.getFiles().entrySet()) {
String key = entry.getKey();
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
byte[] decryptedMetadataKey = null;
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
String encryptedMetadataKey = decryptedFolderMetadata.getMetadata().getMetadataKey();
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
EncryptionUtils.decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
.getMetadataKeys().get(encryptedFile.getMetadataKey()),
privateKey));
// decrypt
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
new TypeToken<DecryptedFolderMetadata.Data>() {
}));
files.put(key, decryptedFile);
if (encryptedMetadataKey != null) {
decryptedMetadataKey = decodeStringToBase64Bytes(
decryptStringAsymmetric(encryptedMetadataKey, privateKey));
}
Map<String, EncryptedFolderMetadata.EncryptedFile> fileDrop = encryptedFolderMetadata.getFiledrop();
if (fileDrop != null) {
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : fileDrop.entrySet()) {
if (encryptedFolderMetadata.getFiles() != null) {
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
.getFiles().entrySet()) {
String key = entry.getKey();
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
@ -256,14 +284,66 @@ public final class EncryptionUtils {
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
// decrypt
String dataJson = EncryptionUtils.decryptStringAsymmetric(encryptedFile.getEncrypted(), privateKey);
if (decryptedMetadataKey == null) {
decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
.getMetadataKeys().get(encryptedFile.getMetadataKey()),
privateKey));
}
// decrypt
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
new TypeToken<DecryptedFolderMetadata.Data>() {
}));
files.put(key, decryptedFile);
}
}
// verify checksum
String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
String decryptedFolderChecksum = decryptedFolderMetadata.getMetadata().getChecksum();
if (TextUtils.isEmpty(decryptedFolderChecksum) &&
isFolderMigrated(remoteId, user, arbitraryDataProvider)) {
throw new IllegalStateException("Possible downgrade attack detected!");
}
if (!TextUtils.isEmpty(decryptedFolderChecksum) && !decryptedFolderChecksum.equals(checksum)) {
throw new IllegalStateException("Wrong checksum!");
}
Map<String, EncryptedFiledrop> fileDrop = encryptedFolderMetadata.getFiledrop();
if (fileDrop != null) {
for (Map.Entry<String, EncryptedFiledrop> entry : fileDrop.entrySet()) {
String key = entry.getKey();
EncryptedFiledrop encryptedFile = entry.getValue();
// decrypt key
String encryptedKey = decryptStringAsymmetric(encryptedFile.getEncryptedKey(),
privateKey);
// decrypt encrypted blob with key
String decryptedData = decryptStringSymmetric(
encryptedFile.getEncrypted(),
decodeStringToBase64Bytes(encryptedKey),
decodeStringToBase64Bytes(encryptedFile.getEncryptedInitializationVector()),
decodeStringToBase64Bytes(encryptedFile.getEncryptedTag())
);
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(decryptedData,
new TypeToken<DecryptedFolderMetadata.Data>() {
}));
files.put(key, decryptedFile);
// remove from filedrop
fileDrop.remove(key);
@ -294,6 +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);
}
}