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 Minimum: NC 16 Server, Android 6.0 Marshmallow
## 3.24.0 (February 13, 2022) ## 3.24.0 (February 13, 2023)
- Several performance optimizations by @starypatyk - Several performance optimizations by @starypatyk
- Support multi-page document scanning and exporting to PDF - Support multi-page document scanning and exporting to PDF

View file

@ -28,6 +28,9 @@ import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.nextcloud.test.RandomStringGenerator; import com.nextcloud.test.RandomStringGenerator;
import com.nextcloud.test.RetryTestRule; import com.nextcloud.test.RetryTestRule;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.DecryptedFolderMetadata; import com.owncloud.android.datamodel.DecryptedFolderMetadata;
import com.owncloud.android.datamodel.EncryptedFolderMetadata; import com.owncloud.android.datamodel.EncryptedFolderMetadata;
import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.common.utils.Log_OC;
@ -35,7 +38,6 @@ import com.owncloud.android.utils.CsrHelper;
import com.owncloud.android.utils.EncryptionUtils; import com.owncloud.android.utils.EncryptionUtils;
import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.FileUtils;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -44,7 +46,6 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
@ -64,7 +65,6 @@ import javax.crypto.BadPaddingException;
import androidx.test.runner.AndroidJUnit4; import androidx.test.runner.AndroidJUnit4;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.owncloud.android.utils.EncryptionUtils.EncryptedFile; import static com.owncloud.android.utils.EncryptionUtils.EncryptedFile;
import static com.owncloud.android.utils.EncryptionUtils.decodeStringToBase64Bytes; import static com.owncloud.android.utils.EncryptionUtils.decodeStringToBase64Bytes;
import static com.owncloud.android.utils.EncryptionUtils.decryptFile; import static com.owncloud.android.utils.EncryptionUtils.decryptFile;
@ -76,8 +76,10 @@ import static com.owncloud.android.utils.EncryptionUtils.deserializeJSON;
import static com.owncloud.android.utils.EncryptionUtils.encodeBytesToBase64String; import static com.owncloud.android.utils.EncryptionUtils.encodeBytesToBase64String;
import static com.owncloud.android.utils.EncryptionUtils.encryptFile; import static com.owncloud.android.utils.EncryptionUtils.encryptFile;
import static com.owncloud.android.utils.EncryptionUtils.encryptFolderMetadata; import static com.owncloud.android.utils.EncryptionUtils.encryptFolderMetadata;
import static com.owncloud.android.utils.EncryptionUtils.generateChecksum;
import static com.owncloud.android.utils.EncryptionUtils.generateKey; import static com.owncloud.android.utils.EncryptionUtils.generateKey;
import static com.owncloud.android.utils.EncryptionUtils.generateSHA512; import static com.owncloud.android.utils.EncryptionUtils.generateSHA512;
import static com.owncloud.android.utils.EncryptionUtils.isFolderMigrated;
import static com.owncloud.android.utils.EncryptionUtils.ivDelimiter; import static com.owncloud.android.utils.EncryptionUtils.ivDelimiter;
import static com.owncloud.android.utils.EncryptionUtils.ivDelimiterOld; import static com.owncloud.android.utils.EncryptionUtils.ivDelimiterOld;
import static com.owncloud.android.utils.EncryptionUtils.ivLength; import static com.owncloud.android.utils.EncryptionUtils.ivLength;
@ -88,10 +90,11 @@ import static com.owncloud.android.utils.EncryptionUtils.verifySHA512;
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class EncryptionTestIT { public class EncryptionTestIT extends AbstractIT {
@Rule public RetryTestRule retryTestRule = new RetryTestRule(); @Rule public RetryTestRule retryTestRule = new RetryTestRule();
private String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo" + private String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAo" +
@ -288,16 +291,23 @@ public class EncryptionTestIT {
} }
/** /**
* DecryptedFolderMetadata -> EncryptedFolderMetadata -> JSON -> encrypt * DecryptedFolderMetadata -> EncryptedFolderMetadata -> JSON -> encrypt -> decrypt -> JSON ->
* -> decrypt -> JSON -> EncryptedFolderMetadata -> DecryptedFolderMetadata * EncryptedFolderMetadata -> DecryptedFolderMetadata
*/ */
@Test @Test
public void encryptionMetadata() throws Exception { public void encryptionMetadata() throws Exception {
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata(); DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
long folderID = 1;
// encrypt // encrypt
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata( EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
decryptedFolderMetadata1, privateKey); decryptedFolderMetadata1,
privateKey,
cert,
arbitraryDataProvider,
user,
folderID);
// serialize // serialize
String encryptedJson = serializeJSON(encryptedFolderMetadata1); String encryptedJson = serializeJSON(encryptedFolderMetadata1);
@ -309,13 +319,84 @@ public class EncryptionTestIT {
// decrypt // decrypt
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData( DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
encryptedFolderMetadata2, privateKey); encryptedFolderMetadata2,
privateKey,
arbitraryDataProvider,
user,
folderID);
// compare // compare
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1), assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
serializeJSON(decryptedFolderMetadata2))); serializeJSON(decryptedFolderMetadata2)));
} }
@Test
public void testChangedMetadataKey() throws Exception {
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
long folderID = 1;
// encrypt
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
decryptedFolderMetadata1,
privateKey,
cert,
arbitraryDataProvider,
user,
folderID);
// store metadata key
String oldMetadataKey = encryptedFolderMetadata1.getMetadata().getMetadataKey();
// do it again
// encrypt
EncryptedFolderMetadata encryptedFolderMetadata2 = encryptFolderMetadata(
decryptedFolderMetadata1,
privateKey,
cert,
arbitraryDataProvider,
user,
folderID);
String newMetadataKey = encryptedFolderMetadata2.getMetadata().getMetadataKey();
assertNotEquals(oldMetadataKey, newMetadataKey);
}
@Test
public void testMigrateMetadataKey() throws Exception {
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
long folderID = 1;
// encrypt
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
decryptedFolderMetadata1,
privateKey,
cert,
arbitraryDataProvider,
user,
folderID);
// reset new metadata key, to mimic old version
encryptedFolderMetadata1.getMetadata().setMetadataKey(null);
String oldMetadataKey = encryptedFolderMetadata1.getMetadata().getMetadataKey();
// do it again
// encrypt
EncryptedFolderMetadata encryptedFolderMetadata2 = encryptFolderMetadata(
decryptedFolderMetadata1,
privateKey,
cert,
arbitraryDataProvider,
user,
folderID);
String newMetadataKey = encryptedFolderMetadata2.getMetadata().getMetadataKey();
assertNotEquals(oldMetadataKey, newMetadataKey);
}
@Test @Test
public void testCryptFileWithoutMetadata() throws Exception { public void testCryptFileWithoutMetadata() throws Exception {
byte[] key = decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg=="); byte[] key = decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg==");
@ -353,10 +434,17 @@ public class EncryptionTestIT {
@Test @Test
public void bigMetadata() throws Exception { public void bigMetadata() throws Exception {
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata(); DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
long folderID = 1;
// encrypt // encrypt
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata( EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
decryptedFolderMetadata1, privateKey); decryptedFolderMetadata1,
privateKey,
cert,
arbitraryDataProvider,
user,
folderID);
// serialize // serialize
String encryptedJson = serializeJSON(encryptedFolderMetadata1); String encryptedJson = serializeJSON(encryptedFolderMetadata1);
@ -368,7 +456,11 @@ public class EncryptionTestIT {
// decrypt // decrypt
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData( DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
encryptedFolderMetadata2, privateKey); encryptedFolderMetadata2,
privateKey,
arbitraryDataProvider,
user,
folderID);
// compare // compare
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1), assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
@ -386,7 +478,12 @@ public class EncryptionTestIT {
addFile(decryptedFolderMetadata1, i); addFile(decryptedFolderMetadata1, i);
// encrypt // encrypt
encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1, privateKey); encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1,
privateKey,
cert,
arbitraryDataProvider,
user,
folderID);
// serialize // serialize
encryptedJson = serializeJSON(encryptedFolderMetadata1); encryptedJson = serializeJSON(encryptedFolderMetadata1);
@ -397,7 +494,11 @@ public class EncryptionTestIT {
}); });
// decrypt // decrypt
decryptedFolderMetadata2 = decryptFolderMetaData(encryptedFolderMetadata2, privateKey); decryptedFolderMetadata2 = decryptFolderMetaData(encryptedFolderMetadata2,
privateKey,
arbitraryDataProvider,
user,
folderID);
// compare // compare
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1), assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
@ -411,6 +512,8 @@ public class EncryptionTestIT {
@Test @Test
public void filedrop() throws Exception { public void filedrop() throws Exception {
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata(); DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
long folderID = 1;
// add filedrop // add filedrop
Map<String, DecryptedFolderMetadata.DecryptedFile> filesdrop = new HashMap<>(); Map<String, DecryptedFolderMetadata.DecryptedFile> filesdrop = new HashMap<>();
@ -433,8 +536,11 @@ public class EncryptionTestIT {
// encrypt // encrypt
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata( EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
decryptedFolderMetadata1, decryptedFolderMetadata1,
privateKey privateKey,
); cert,
arbitraryDataProvider,
user,
folderID);
EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, cert); EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, cert);
// serialize // serialize
@ -447,7 +553,11 @@ public class EncryptionTestIT {
// decrypt // decrypt
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData( DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
encryptedFolderMetadata2, privateKey); encryptedFolderMetadata2,
privateKey,
arbitraryDataProvider,
user,
folderID);
// compare // compare
assertFalse(compareJsonStrings(serializeJSON(decryptedFolderMetadata1), assertFalse(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
@ -532,6 +642,58 @@ public class EncryptionTestIT {
assertTrue(verifySHA512(hashedToken, token)); assertTrue(verifySHA512(hashedToken, token));
} }
@Test
public void testExcludeGSON() throws Exception {
DecryptedFolderMetadata metadata = generateFolderMetadata();
String jsonWithKeys = serializeJSON(metadata);
String jsonWithoutKeys = serializeJSON(metadata, true);
assertTrue(jsonWithKeys.contains("metadataKeys"));
assertFalse(jsonWithoutKeys.contains("metadataKeys"));
}
@Test
public void testChecksum() throws Exception {
DecryptedFolderMetadata metadata = new DecryptedFolderMetadata();
String mnemonic = "chimney potato joke science ridge trophy result estate spare vapor much room";
metadata.getFiles().put("n9WXAIXO2wRY4R8nXwmo", new DecryptedFolderMetadata.DecryptedFile());
metadata.getFiles().put("ia7OEEEyXMoRa1QWQk8r", new DecryptedFolderMetadata.DecryptedFile());
String encryptedMetadataKey = "GuFPAULudgD49S4+VDFck3LiqQ8sx4zmbrBtdpCSGcT+T0W0z4F5gYQYPlzTG6WOkdW5LJZK/";
metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
String checksum = generateChecksum(metadata, mnemonic);
String expectedChecksum = "002cefa6493f2efb0192247a34bb1b16d391aefee968fd3d4225c4ec3cd56436";
assertEquals(expectedChecksum, checksum);
// change something
String newMnemonic = mnemonic + "1";
String newChecksum = generateChecksum(metadata, newMnemonic);
assertNotEquals(expectedChecksum, newChecksum);
metadata.getFiles().put("aeb34yXMoRa1QWQk8r", new DecryptedFolderMetadata.DecryptedFile());
newChecksum = generateChecksum(metadata, mnemonic);
assertNotEquals(expectedChecksum, newChecksum);
}
@Test
public void testAddIdToMigratedIds() {
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(targetContext);
// delete ids
arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.MIGRATED_FOLDER_IDS);
long id = 1;
EncryptionUtils.addIdToMigratedIds(id, user, arbitraryDataProvider);
assertTrue(isFolderMigrated(id, user, arbitraryDataProvider));
}
// Helper // Helper
private boolean compareJsonStrings(String expected, String actual) { private boolean compareJsonStrings(String expected, String actual) {
@ -561,15 +723,7 @@ public class EncryptionTestIT {
DecryptedFolderMetadata.Metadata metadata1 = new DecryptedFolderMetadata.Metadata(); DecryptedFolderMetadata.Metadata metadata1 = new DecryptedFolderMetadata.Metadata();
metadata1.setMetadataKeys(metadataKeys); metadata1.setMetadataKeys(metadataKeys);
metadata1.setVersion(1); metadata1.setVersion(1.1);
DecryptedFolderMetadata.Sharing sharing = new DecryptedFolderMetadata.Sharing();
sharing.setSignature("HMACOFRECIPIENTANDNEWESTMETADATAKEY");
HashMap<String, String> recipient = new HashMap<>();
recipient.put("blah@schiessle.org", "PUBLIC KEY");
recipient.put("bjoern@schiessle.org", "PUBLIC KEY");
sharing.setRecipient(recipient);
metadata1.setSharing(sharing);
HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>(); HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
@ -629,14 +783,6 @@ public class EncryptionTestIT {
return md5.compareTo(getMD5Sum(decryptedFile)) == 0; return md5.compareTo(getMD5Sum(decryptedFile)) == 0;
} }
private File getFile(String filename) throws IOException {
InputStream inputStream = getInstrumentation().getContext().getAssets().open(filename);
File temp = File.createTempFile("file", "file");
FileUtils.copyInputStreamToFile(inputStream, temp);
return temp;
}
private String getMD5Sum(File file) { private String getMD5Sum(File file) {
FileInputStream fileInputStream = null; FileInputStream fileInputStream = null;
try { try {

View file

@ -94,22 +94,13 @@ public final class PushUtils {
try { try {
messageDigest = MessageDigest.getInstance("SHA-512"); messageDigest = MessageDigest.getInstance("SHA-512");
messageDigest.update(pushToken.getBytes()); messageDigest.update(pushToken.getBytes());
return bytesToHex(messageDigest.digest()); return EncryptionUtils.bytesToHex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
Log_OC.d(TAG, "SHA-512 algorithm not supported"); Log_OC.d(TAG, "SHA-512 algorithm not supported");
} }
return ""; return "";
} }
public static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte individualByte : bytes) {
result.append(Integer.toString((individualByte & 0xff) + 0x100, 16)
.substring(1));
}
return result.toString();
}
private static int generateRsa2048KeyPair() { private static int generateRsa2048KeyPair() {
migratePushKeys(); migratePushKeys();
String keyPath = MainApp.getAppContext().getFilesDir().getAbsolutePath() + File.separator + String keyPath = MainApp.getAppContext().getFilesDir().getAbsolutePath() + File.separator +

View file

@ -71,9 +71,11 @@ public class DecryptedFolderMetadata {
} }
public static class Metadata { public static class Metadata {
private Map<Integer, String> metadataKeys; // each keys is encrypted on its own, decrypt on use transient
private Sharing sharing; private Map<Integer, String> metadataKeys; // outdated with v1.1
private int version; private String metadataKey;
private String checksum;
private double version = 1.2;
@Override @Override
public String toString() { public String toString() {
@ -84,11 +86,7 @@ public class DecryptedFolderMetadata {
return this.metadataKeys; return this.metadataKeys;
} }
public Sharing getSharing() { public double getVersion() {
return this.sharing;
}
public int getVersion() {
return this.version; return this.version;
} }
@ -96,12 +94,28 @@ public class DecryptedFolderMetadata {
this.metadataKeys = metadataKeys; this.metadataKeys = metadataKeys;
} }
public void setSharing(Sharing sharing) { public void setVersion(double version) {
this.sharing = sharing; this.version = version;
} }
public void setVersion(int version) { public String getMetadataKey() {
this.version = version; if (metadataKey == null) {
// fallback to old keys array
return metadataKeys.get(0);
}
return metadataKey;
}
public void setMetadataKey(String metadataKey) {
this.metadataKey = metadataKey;
}
public String getChecksum() {
return checksum;
}
public void setChecksum(String checksum) {
this.checksum = checksum;
} }
} }
@ -117,32 +131,11 @@ public class DecryptedFolderMetadata {
} }
} }
public static class Sharing {
private Map<String, String> recipient;
private String signature;
public Map<String, String> getRecipient() {
return this.recipient;
}
public String getSignature() {
return this.signature;
}
public void setRecipient(Map<String, String> recipient) {
this.recipient = recipient;
}
public void setSignature(String signature) {
this.signature = signature;
}
}
public static class DecryptedFile { public static class DecryptedFile {
private Data encrypted; private Data encrypted;
private String initializationVector; private String initializationVector;
private String authenticationTag; private String authenticationTag;
private int metadataKey; transient private int metadataKey;
public Data getEncrypted() { public Data getEncrypted() {
return this.encrypted; return this.encrypted;
@ -181,7 +174,7 @@ public class DecryptedFolderMetadata {
private String key; private String key;
private String filename; private String filename;
private String mimetype; private String mimetype;
private int version; transient private double version;
public String getKey() { public String getKey() {
return this.key; return this.key;
@ -195,7 +188,7 @@ public class DecryptedFolderMetadata {
return this.mimetype; return this.mimetype;
} }
public int getVersion() { public double getVersion() {
return this.version; return this.version;
} }

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 DecryptedFolderMetadata.Metadata metadata;
private Map<String, EncryptedFile> files; private Map<String, EncryptedFile> files;
private Map<String, EncryptedFile> filedrop; private Map<String, EncryptedFiledrop> filedrop;
public EncryptedFolderMetadata(DecryptedFolderMetadata.Metadata metadata, public EncryptedFolderMetadata(DecryptedFolderMetadata.Metadata metadata,
Map<String, EncryptedFile> files, Map<String, EncryptedFile> files,
Map<String, EncryptedFile> filesdrop) { Map<String, EncryptedFiledrop> filesdrop) {
this.metadata = metadata; this.metadata = metadata;
this.files = files; this.files = files;
this.filedrop = filesdrop; this.filedrop = filesdrop;
@ -48,7 +48,7 @@ public class EncryptedFolderMetadata {
return files; return files;
} }
public Map<String, EncryptedFile> getFiledrop() { public Map<String, EncryptedFiledrop> getFiledrop() {
return filedrop; return filedrop;
} }
@ -64,7 +64,7 @@ public class EncryptedFolderMetadata {
private String encrypted; private String encrypted;
private String initializationVector; private String initializationVector;
private String authenticationTag; private String authenticationTag;
private int metadataKey; transient private int metadataKey;
public String getEncrypted() { public String getEncrypted() {
return encrypted; return encrypted;
@ -93,9 +93,5 @@ public class EncryptedFolderMetadata {
public void setAuthenticationTag(String authenticationTag) { public void setAuthenticationTag(String authenticationTag) {
this.authenticationTag = authenticationTag; this.authenticationTag = authenticationTag;
} }
public void setMetadataKey(int metadataKey) {
this.metadataKey = metadataKey;
}
} }
} }

View file

@ -600,6 +600,9 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
return !TextUtils.isEmpty(getFileName()) && getFileName().charAt(0) == '.'; return !TextUtils.isEmpty(getFileName()) && getFileName().charAt(0) == '.';
} }
/**
* unique fileId for the file within the instance
*/
@SuppressFBWarnings("STT") @SuppressFBWarnings("STT")
public long getLocalId() { public long getLocalId() {
if (localId > 0) { if (localId > 0) {
@ -648,6 +651,9 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
} }
}; };
/**
* Android's internal ID of the file
*/
public long getFileId() { public long getFileId() {
return this.fileId; return this.fileId;
} }

View file

@ -126,7 +126,9 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent, Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent,
client, client,
privateKey, privateKey,
publicKey); publicKey,
arbitraryDataProvider,
user);
metadataExists = metadataPair.first; metadataExists = metadataPair.first;
metadata = metadataPair.second; metadata = metadataPair.second;
@ -150,7 +152,11 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
metadata.getFiles().put(encryptedFileName, createDecryptedFile(filename)); metadata.getFiles().put(encryptedFileName, createDecryptedFile(filename));
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata, EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
privateKey); privateKey,
publicKey,
arbitraryDataProvider,
user,
parent.getLocalId());
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata); String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
// upload metadata // upload metadata

View file

@ -47,6 +47,7 @@ import java.io.IOException;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
@ -99,6 +100,7 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
DecryptedFolderMetadata metadata; DecryptedFolderMetadata metadata;
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY); String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
try { try {
// Lock folder // Lock folder
@ -123,7 +125,11 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() { serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
}); });
metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey); metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata,
privateKey,
arbitraryDataProvider,
user,
parentId);
} else { } else {
throw new RemoteOperationFailedException("No Metadata found!"); throw new RemoteOperationFailedException("No Metadata found!");
} }
@ -140,8 +146,13 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
// remove file from metadata // remove file from metadata
metadata.getFiles().remove(fileName); metadata.getFiles().remove(fileName);
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata, EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(
privateKey); metadata,
privateKey,
publicKey,
arbitraryDataProvider,
user,
parentId);
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata); String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
// upload metadata // upload metadata
@ -155,14 +166,9 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
// return success // return success
return result; return result;
} catch (NoSuchAlgorithmException | } catch (NoSuchAlgorithmException | IOException | InvalidKeyException | InvalidAlgorithmParameterException |
IOException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeySpecException |
InvalidKeyException | CertificateException e) {
InvalidAlgorithmParameterException |
NoSuchPaddingException |
BadPaddingException |
IllegalBlockSizeException |
InvalidKeySpecException e) {
result = new RemoteOperationResult(e); result = new RemoteOperationResult(e);
Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e); Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e);

View file

@ -471,7 +471,9 @@ public class UploadFileOperation extends SyncOperation {
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parentFile, Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parentFile,
client, client,
privateKey, privateKey,
publicKey); publicKey,
arbitraryDataProvider,
user);
metadataExists = metadataPair.first; metadataExists = metadataPair.first;
DecryptedFolderMetadata metadata = metadataPair.second; DecryptedFolderMetadata metadata = metadataPair.second;
@ -617,8 +619,20 @@ public class UploadFileOperation extends SyncOperation {
metadata.getFiles().put(encryptedFileName, decryptedFile); metadata.getFiles().put(encryptedFileName, decryptedFile);
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata, EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
privateKey); privateKey,
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata); publicKey,
arbitraryDataProvider,
user,
parentFile.getLocalId());
String serializedFolderMetadata;
// check if we need metadataKeys
if (metadata.getMetadata().getMetadataKey() != null) {
serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata, true);
} else {
serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
}
// upload metadata // upload metadata
EncryptionUtils.uploadMetadata(parentFile, EncryptionUtils.uploadMetadata(parentFile,

View file

@ -33,6 +33,7 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -51,7 +52,6 @@ import com.nextcloud.android.lib.resources.files.ToggleFileLockRemoteOperation;
import com.nextcloud.android.lib.richWorkspace.RichWorkspaceDirectEditingRemoteOperation; import com.nextcloud.android.lib.richWorkspace.RichWorkspaceDirectEditingRemoteOperation;
import com.nextcloud.client.account.User; import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.DeviceInfo; import com.nextcloud.client.device.DeviceInfo;
import com.nextcloud.client.di.Injectable; import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.documentscan.AppScanOptionalFeature; import com.nextcloud.client.documentscan.AppScanOptionalFeature;
@ -68,6 +68,8 @@ import com.nextcloud.utils.view.FastScrollUtils;
import com.owncloud.android.MainApp; import com.owncloud.android.MainApp;
import com.owncloud.android.R; import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.datamodel.SyncedFolderProvider;
@ -1700,11 +1702,12 @@ public class OCFileListFragment extends ExtendedListFragment implements
String publicKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PUBLIC_KEY); String publicKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PUBLIC_KEY);
String privateKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PRIVATE_KEY); String privateKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PRIVATE_KEY);
FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
OCFile file = storageManager.getFileByRemoteId(event.remoteId);
if (publicKey.isEmpty() || privateKey.isEmpty()) { if (publicKey.isEmpty() || privateKey.isEmpty()) {
Log_OC.d(TAG, "no public key for " + user.getAccountName()); Log_OC.d(TAG, "no public key for " + user.getAccountName());
FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
OCFile file = storageManager.getFileByRemoteId(event.remoteId);
int position = -1; int position = -1;
if (file != null) { if (file != null) {
position = mAdapter.getItemPosition(file); position = mAdapter.getItemPosition(file);
@ -1713,11 +1716,23 @@ public class OCFileListFragment extends ExtendedListFragment implements
dialog.setTargetFragment(this, SETUP_ENCRYPTION_REQUEST_CODE); dialog.setTargetFragment(this, SETUP_ENCRYPTION_REQUEST_CODE);
dialog.show(getParentFragmentManager(), SETUP_ENCRYPTION_DIALOG_TAG); dialog.show(getParentFragmentManager(), SETUP_ENCRYPTION_DIALOG_TAG);
} else { } else {
encryptFolder(event.localId, event.remoteId, event.remotePath, event.shouldBeEncrypted); encryptFolder(file,
event.localId,
event.remoteId,
event.remotePath,
event.shouldBeEncrypted,
publicKey,
privateKey);
} }
} }
private void encryptFolder(long localId, String remoteId, String remotePath, boolean shouldBeEncrypted) { private void encryptFolder(OCFile folder,
long localId,
String remoteId,
String remotePath,
boolean shouldBeEncrypted,
String publicKey,
String privateKey) {
try { try {
User user = accountManager.getUser(); User user = accountManager.getUser();
OwnCloudClient client = clientFactory.create(user); OwnCloudClient client = clientFactory.create(user);
@ -1727,6 +1742,46 @@ public class OCFileListFragment extends ExtendedListFragment implements
.execute(client); .execute(client);
if (remoteOperationResult.isSuccess()) { if (remoteOperationResult.isSuccess()) {
// lock folder
String token = EncryptionUtils.lockFolder(folder, client);
// Update metadata
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(folder,
client,
privateKey,
publicKey,
arbitraryDataProvider,
user);
boolean metadataExists = metadataPair.first;
DecryptedFolderMetadata metadata = metadataPair.second;
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
privateKey,
publicKey,
arbitraryDataProvider,
user,
folder.getLocalId());
String serializedFolderMetadata;
// check if we need metadataKeys
if (metadata.getMetadata().getMetadataKey() != null) {
serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata, true);
} else {
serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
}
// upload metadata
EncryptionUtils.uploadMetadata(folder,
serializedFolderMetadata,
token,
client,
metadataExists);
// unlock folder
EncryptionUtils.unlockFolder(folder, client, token);
mAdapter.setEncryptionAttributeForItemID(remoteId, shouldBeEncrypted); mAdapter.setEncryptionAttributeForItemID(remoteId, shouldBeEncrypted);
} else if (remoteOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) { } else if (remoteOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
Snackbar.make(getRecyclerView(), Snackbar.make(getRecyclerView(),
@ -1738,8 +1793,8 @@ public class OCFileListFragment extends ExtendedListFragment implements
Snackbar.LENGTH_LONG).show(); Snackbar.LENGTH_LONG).show();
} }
} catch (ClientFactory.CreationException e) { } catch (Exception e) {
Log_OC.e(TAG, "Cannot create client", e); Log_OC.e(TAG, "Error creating encrypted folder", e);
} }
} }

View file

@ -28,12 +28,14 @@ import android.util.Pair;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.nextcloud.client.account.User; import com.nextcloud.client.account.User;
import com.owncloud.android.R; import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.DecryptedFolderMetadata; import com.owncloud.android.datamodel.DecryptedFolderMetadata;
import com.owncloud.android.datamodel.EncryptedFiledrop;
import com.owncloud.android.datamodel.EncryptedFolderMetadata; import com.owncloud.android.datamodel.EncryptedFolderMetadata;
import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClient;
@ -79,6 +81,7 @@ import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -120,6 +123,8 @@ public final class EncryptionUtils {
private static final String AES = "AES"; private static final String AES = "AES";
private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
private static final String RSA = "RSA"; private static final String RSA = "RSA";
@VisibleForTesting
public static final String MIGRATED_FOLDER_IDS = "MIGRATED_FOLDER_IDS";
private EncryptionUtils() { private EncryptionUtils() {
// utility class -> private constructor // utility class -> private constructor
@ -129,12 +134,28 @@ public final class EncryptionUtils {
JSON JSON
*/ */
public static <T> T deserializeJSON(String json, TypeToken<T> type) { public static <T> T deserializeJSON(String json, TypeToken<T> type, boolean excludeTransient) {
if (excludeTransient) {
return new Gson().fromJson(json, type.getType()); return new Gson().fromJson(json, type.getType());
} else {
return new GsonBuilder().excludeFieldsWithModifiers(0).create().fromJson(json, type.getType());
}
}
public static <T> T deserializeJSON(String json, TypeToken<T> type) {
return deserializeJSON(json, type, false);
}
public static String serializeJSON(Object data, boolean excludeTransient) {
if (excludeTransient) {
return new Gson().toJson(data);
} else {
return new GsonBuilder().excludeFieldsWithModifiers(0).create().toJson(data);
}
} }
public static String serializeJSON(Object data) { public static String serializeJSON(Object data) {
return new Gson().toJson(data); return serializeJSON(data, false);
} }
/* /*
@ -148,19 +169,33 @@ public final class EncryptionUtils {
* @return EncryptedFolderMetadata encrypted folder metadata * @return EncryptedFolderMetadata encrypted folder metadata
*/ */
public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata, public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
String privateKey String privateKey,
String publicKey,
ArbitraryDataProvider arbitraryDataProvider,
User user,
long parentId
) )
throws NoSuchAlgorithmException, InvalidKeyException, throws NoSuchAlgorithmException, InvalidKeyException,
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
IllegalBlockSizeException, InvalidKeySpecException { IllegalBlockSizeException, InvalidKeySpecException, CertificateException {
HashMap<String, EncryptedFolderMetadata.EncryptedFile> files = new HashMap<>(); HashMap<String, EncryptedFolderMetadata.EncryptedFile> files = new HashMap<>();
HashMap<String, EncryptedFolderMetadata.EncryptedFile> filesdrop = new HashMap<>(); HashMap<String, EncryptedFiledrop> filesdrop = new HashMap<>();
EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata
.getMetadata(), .getMetadata(),
files, files,
filesdrop); filesdrop);
// set new metadata key
byte[] metadataKeyBytes = EncryptionUtils.generateKey();
String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(
EncryptionUtils.encodeBytesToBase64String(metadataKeyBytes),
publicKey);
encryptedFolderMetadata.getMetadata().setMetadataKey(encryptedMetadataKey);
// store that this folder has been migrated
addIdToMigratedIds(parentId, user, arbitraryDataProvider);
// Encrypt each file in "files" // Encrypt each file in "files"
for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
.getFiles().entrySet()) { .getFiles().entrySet()) {
@ -169,39 +204,45 @@ public final class EncryptionUtils {
EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile(); EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile();
encryptedFile.setInitializationVector(decryptedFile.getInitializationVector()); encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
encryptedFile.setMetadataKey(decryptedFile.getMetadataKey());
encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag()); encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(EncryptionUtils.decryptStringAsymmetric(
decryptedFolderMetadata.getMetadata().getMetadataKeys().get(encryptedFile.getMetadataKey()),
privateKey));
// encrypt // encrypt
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted()); String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
encryptedFile.setEncrypted(EncryptionUtils.encryptStringSymmetric(dataJson, decryptedMetadataKey)); encryptedFile.setEncrypted(EncryptionUtils.encryptStringSymmetric(dataJson, metadataKeyBytes));
files.put(key, encryptedFile); files.put(key, encryptedFile);
} }
// set checksum
String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
encryptedFolderMetadata.getMetadata().setChecksum(checksum);
return encryptedFolderMetadata; return encryptedFolderMetadata;
} }
@VisibleForTesting @VisibleForTesting
public static void encryptFileDropFiles(DecryptedFolderMetadata decryptedFolderMetadata, EncryptedFolderMetadata encryptedFolderMetadata, String cert) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, CertificateException { public static void encryptFileDropFiles(DecryptedFolderMetadata decryptedFolderMetadata, EncryptedFolderMetadata encryptedFolderMetadata, String cert) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, CertificateException {
final Map<String, EncryptedFolderMetadata.EncryptedFile> filesdrop = encryptedFolderMetadata.getFiledrop(); final Map<String, EncryptedFiledrop> filesdrop = encryptedFolderMetadata.getFiledrop();
for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
.getFiledrop().entrySet()) { .getFiledrop().entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue(); DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue();
EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile(); // TODO
encryptedFile.setInitializationVector(decryptedFile.getInitializationVector()); String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
encryptedFile.setMetadataKey(decryptedFile.getMetadataKey()); EncryptedFiledrop encryptedFile = new EncryptedFiledrop(dataJson,
encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag()); decryptedFile.getInitializationVector(),
decryptedFile.getAuthenticationTag(),
"123",
"123",
"123");
// encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
// encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
// encrypt // encrypt
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
encryptedFile.setEncrypted(EncryptionUtils.encryptStringAsymmetric(dataJson, cert)); // encryptedFile.setEncrypted(EncryptionUtils.encryptStringAsymmetric(dataJson, cert));
filesdrop.put(key, encryptedFile); filesdrop.put(key, encryptedFile);
} }
@ -211,7 +252,10 @@ public final class EncryptionUtils {
* decrypt folder metaData with private key * decrypt folder metaData with private key
*/ */
public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata, public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata,
String privateKey) String privateKey,
ArbitraryDataProvider arbitraryDataProvider,
User user,
long remoteId)
throws NoSuchAlgorithmException, InvalidKeyException, throws NoSuchAlgorithmException, InvalidKeyException,
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
IllegalBlockSizeException, InvalidKeySpecException { IllegalBlockSizeException, InvalidKeySpecException {
@ -220,6 +264,16 @@ public final class EncryptionUtils {
DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata( DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
encryptedFolderMetadata.getMetadata(), files); encryptedFolderMetadata.getMetadata(), files);
byte[] decryptedMetadataKey = null;
String encryptedMetadataKey = decryptedFolderMetadata.getMetadata().getMetadataKey();
if (encryptedMetadataKey != null) {
decryptedMetadataKey = decodeStringToBase64Bytes(
decryptStringAsymmetric(encryptedMetadataKey, privateKey));
}
if (encryptedFolderMetadata.getFiles() != null) {
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
.getFiles().entrySet()) { .getFiles().entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
@ -230,10 +284,12 @@ public final class EncryptionUtils {
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey()); decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag()); decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes( if (decryptedMetadataKey == null) {
EncryptionUtils.decryptStringAsymmetric(decryptedFolderMetadata.getMetadata() decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
.getMetadataKeys().get(encryptedFile.getMetadataKey()), .getMetadataKeys().get(encryptedFile.getMetadataKey()),
privateKey)); privateKey));
}
// decrypt // decrypt
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey); String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
@ -243,23 +299,47 @@ public final class EncryptionUtils {
files.put(key, decryptedFile); files.put(key, decryptedFile);
} }
}
Map<String, EncryptedFolderMetadata.EncryptedFile> fileDrop = encryptedFolderMetadata.getFiledrop(); // verify checksum
String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
String decryptedFolderChecksum = decryptedFolderMetadata.getMetadata().getChecksum();
if (TextUtils.isEmpty(decryptedFolderChecksum) &&
isFolderMigrated(remoteId, user, arbitraryDataProvider)) {
throw new IllegalStateException("Possible downgrade attack detected!");
}
if (!TextUtils.isEmpty(decryptedFolderChecksum) && !decryptedFolderChecksum.equals(checksum)) {
throw new IllegalStateException("Wrong checksum!");
}
Map<String, EncryptedFiledrop> fileDrop = encryptedFolderMetadata.getFiledrop();
if (fileDrop != null) { if (fileDrop != null) {
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : fileDrop.entrySet()) { for (Map.Entry<String, EncryptedFiledrop> entry : fileDrop.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue(); EncryptedFiledrop encryptedFile = entry.getValue();
// decrypt key
String encryptedKey = decryptStringAsymmetric(encryptedFile.getEncryptedKey(),
privateKey);
// decrypt encrypted blob with key
String decryptedData = decryptStringSymmetric(
encryptedFile.getEncrypted(),
decodeStringToBase64Bytes(encryptedKey),
decodeStringToBase64Bytes(encryptedFile.getEncryptedInitializationVector()),
decodeStringToBase64Bytes(encryptedFile.getEncryptedTag())
);
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile(); DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
decryptedFile.setInitializationVector(encryptedFile.getInitializationVector()); decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag()); decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
// decrypt
String dataJson = EncryptionUtils.decryptStringAsymmetric(encryptedFile.getEncrypted(), privateKey);
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson, decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(decryptedData,
new TypeToken<DecryptedFolderMetadata.Data>() { new TypeToken<DecryptedFolderMetadata.Data>() {
})); }));
@ -294,6 +374,7 @@ public final class EncryptionUtils {
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context); ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0); String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY); String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON( EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() { serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
@ -306,7 +387,10 @@ public final class EncryptionUtils {
} }
DecryptedFolderMetadata decryptedFolderMetadata = EncryptionUtils.decryptFolderMetaData( DecryptedFolderMetadata decryptedFolderMetadata = EncryptionUtils.decryptFolderMetaData(
encryptedFolderMetadata, encryptedFolderMetadata,
privateKey); privateKey,
arbitraryDataProvider,
user,
folder.getLocalId());
boolean transferredFiledrop = filesDropCountBefore > 0 && decryptedFolderMetadata.getFiles().size() == boolean transferredFiledrop = filesDropCountBefore > 0 && decryptedFolderMetadata.getFiles().size() ==
encryptedFolderMetadata.getFiles().size() + filesDropCountBefore; encryptedFolderMetadata.getFiles().size() + filesDropCountBefore;
@ -317,7 +401,11 @@ public final class EncryptionUtils {
// upload metadata // upload metadata
EncryptedFolderMetadata encryptedFolderMetadataNew = encryptFolderMetadata(decryptedFolderMetadata, EncryptedFolderMetadata encryptedFolderMetadataNew = encryptFolderMetadata(decryptedFolderMetadata,
privateKey); privateKey,
publicKey,
arbitraryDataProvider,
user,
folder.getLocalId());
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadataNew); String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadataNew);
@ -605,6 +693,36 @@ public final class EncryptionUtils {
return encodedCryptedBytes + delimiter + encodedIV; return encodedCryptedBytes + delimiter + encodedIV;
} }
public static String decryptStringSymmetric(String string,
byte[] encryptionKeyBytes,
byte[] iv,
byte[] authenticationTag)
throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidAlgorithmParameterException,
InvalidKeyException,
IllegalBlockSizeException,
BadPaddingException {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
Key key = new SecretKeySpec(encryptionKeyBytes, AES);
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
byte[] bytes = decodeStringToBase64Bytes(string);
// check authentication tag
byte[] extractedAuthenticationTag = Arrays.copyOfRange(bytes,
bytes.length - (128 / 8),
bytes.length);
if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
throw new SecurityException("Tag not correct");
}
byte[] encodedBytes = cipher.doFinal(bytes);
return decodeBase64BytesToString(encodedBytes);
}
/** /**
* Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
@ -883,7 +1001,10 @@ public final class EncryptionUtils {
public static Pair<Boolean, DecryptedFolderMetadata> retrieveMetadata(OCFile parentFile, public static Pair<Boolean, DecryptedFolderMetadata> retrieveMetadata(OCFile parentFile,
OwnCloudClient client, OwnCloudClient client,
String privateKey, String privateKey,
String publicKey) throws UploadException, String publicKey,
ArbitraryDataProvider arbitraryDataProvider,
User user)
throws UploadException,
InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException { IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(parentFile.getLocalId()); GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(parentFile.getLocalId());
@ -900,7 +1021,11 @@ public final class EncryptionUtils {
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() { serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
}); });
return new Pair<>(Boolean.TRUE, EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey)); return new Pair<>(Boolean.TRUE, EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata,
privateKey,
arbitraryDataProvider,
user,
parentFile.getLocalId()));
} else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) { } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
// new metadata // new metadata
@ -909,7 +1034,7 @@ public final class EncryptionUtils {
metadata.getMetadata().setMetadataKeys(new HashMap<>()); metadata.getMetadata().setMetadataKeys(new HashMap<>());
String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey()); String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey); String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
metadata.getMetadata().getMetadataKeys().put(0, encryptedMetadataKey); metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
return new Pair<>(Boolean.FALSE, metadata); return new Pair<>(Boolean.FALSE, metadata);
} else { } else {
@ -984,4 +1109,83 @@ public final class EncryptionUtils {
file.isFolder() && file.isFolder() &&
user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_26); user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_26);
} }
public static String generateChecksum(DecryptedFolderMetadata metadata,
String mnemonic) throws NoSuchAlgorithmException {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(mnemonic.replaceAll(" ", ""));
ArrayList<String> keys = new ArrayList<>(metadata.getFiles().keySet());
Collections.sort(keys);
for (String key : keys) {
stringBuilder.append(key);
}
stringBuilder.append(metadata.getMetadata().getMetadataKey());
// sha256 hash-sum
return sha256(stringBuilder.toString());
}
/**
* SHA-256 hash of metadata-key
*/
public static String sha256(String string) throws NoSuchAlgorithmException {
byte[] bytes = MessageDigest
.getInstance("SHA-256")
.digest(string.getBytes(StandardCharsets.UTF_8));
return bytesToHex(bytes);
}
public static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte individualByte : bytes) {
result.append(Integer.toString((individualByte & 0xff) + 0x100, 16)
.substring(1));
}
return result.toString();
}
public static void addIdToMigratedIds(long id,
User user,
ArbitraryDataProvider arbitraryDataProvider) {
Gson gson = new Gson();
String ids = arbitraryDataProvider.getValue(user, MIGRATED_FOLDER_IDS);
ArrayList<Long> arrayList = gson.fromJson(ids, ArrayList.class);
if (arrayList == null) {
arrayList = new ArrayList<>();
}
if (arrayList.contains(id)) {
// nothing to do here
return;
}
arrayList.add(id);
String json = gson.toJson(arrayList);
arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(),
MIGRATED_FOLDER_IDS,
json);
}
public static boolean isFolderMigrated(long id,
User user,
ArbitraryDataProvider arbitraryDataProvider) {
Gson gson = new Gson();
String ids = arbitraryDataProvider.getValue(user, MIGRATED_FOLDER_IDS);
ArrayList<Long> arrayList = gson.fromJson(ids, new TypeToken<List<Long>>() {
}.getType());
if (arrayList == null) {
return false;
}
return arrayList.contains(id);
}
} }