From b93b192762b8d6a5fa3b3b4b8a5189b057c02755 Mon Sep 17 00:00:00 2001 From: M66B Date: Sat, 14 Jan 2023 22:48:53 +0100 Subject: [PATCH] Cloud sync improvements --- .../java/eu/faircode/email/CloudSync.java | 35 ++++++----------- .../faircode/email/FragmentOptionsBackup.java | 39 +++++++++---------- 2 files changed, 30 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/CloudSync.java b/app/src/main/java/eu/faircode/email/CloudSync.java index 79650522c1..532c7972a7 100644 --- a/app/src/main/java/eu/faircode/email/CloudSync.java +++ b/app/src/main/java/eu/faircode/email/CloudSync.java @@ -29,18 +29,15 @@ import org.json.JSONObject; import java.io.IOException; import java.net.URL; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Arrays; -import javax.crypto.BadPaddingException; import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; @@ -52,10 +49,7 @@ public class CloudSync { private static final int CLOUD_TIMEOUT = 10 * 1000; // timeout public static JSONObject perform(Context context, String user, String password, JSONObject jrequest) - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, - InvalidKeySpecException, InvalidKeyException, - IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, - JSONException, IOException { + throws GeneralSecurityException, JSONException, IOException { byte[] salt = MessageDigest.getInstance("SHA256").digest(user.getBytes()); byte[] huser = MessageDigest.getInstance("SHA256").digest(salt); byte[] userid = Arrays.copyOfRange(huser, 0, 8); @@ -69,24 +63,18 @@ public class CloudSync { jrequest.put("password", cloudPassword); jrequest.put("debug", BuildConfig.DEBUG); - if (jrequest.has("keys")) { - JSONArray jkeys = jrequest.getJSONArray("keys"); - for (int i = 0; i < jkeys.length(); i++) { - jkeys.put(i, transform(jkeys.getString(i), key.second, true)); - } - } - if (jrequest.has("items")) { JSONArray jitems = jrequest.getJSONArray("items"); for (int i = 0; i < jitems.length(); i++) { JSONObject jitem = jitems.getJSONObject(i); + int revision = jitem.getInt("revision"); String k = jitem.getString("key"); - jitem.put("key", transform(k, key.second, true)); + jitem.put("key", transform(k, key.second, null, true)); if (jitem.has("value") && !jitem.isNull("value")) { String v = jitem.getString("value"); - jitem.put("value", transform(v, key.second, true)); + jitem.put("value", transform(v, key.second, revision, true)); } } } @@ -130,13 +118,14 @@ public class CloudSync { JSONArray jitems = jresponse.getJSONArray("items"); for (int i = 0; i < jitems.length(); i++) { JSONObject jitem = jitems.getJSONObject(i); + int revision = jitem.getInt("revision"); String ekey = jitem.getString("key"); - jitem.put("key", transform(ekey, key.second, false)); + jitem.put("key", transform(ekey, key.second, null, false)); if (jitem.has("value") && !jitem.isNull("value")) { String evalue = jitem.getString("value"); - jitem.put("value", transform(evalue, key.second, false)); + jitem.put("value", transform(evalue, key.second, revision, false)); } } } @@ -161,13 +150,13 @@ public class CloudSync { Arrays.copyOfRange(encoded, half, half + half)); } - private static String transform(String value, byte[] key, boolean encrypt) - throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { + private static String transform(String value, byte[] key, Integer revision, boolean encrypt) throws GeneralSecurityException { SecretKeySpec secret = new SecretKeySpec(key, "AES"); Cipher cipher = Cipher.getInstance("AES/GCM-SIV/NoPadding"); IvParameterSpec ivSpec = new IvParameterSpec(new byte[12]); cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secret, ivSpec); - //cipher.updateAAD(ByteBuffer.allocate(4).putInt(0).array()); + if (revision != null) + cipher.updateAAD(ByteBuffer.allocate(4).putInt(revision).array()); if (encrypt) { byte[] encrypted = cipher.doFinal(value.getBytes()); return Base64.encodeToString(encrypted, Base64.NO_PADDING | Base64.NO_WRAP); diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java b/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java index 40d90b8e75..665f8537ff 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java @@ -1532,32 +1532,29 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere JSONObject jrequest = new JSONObject(); jrequest.put("command", wipe ? "wipe" : "login"); - if (false) { - JSONArray jwrite = new JSONArray(); + if (true) { + boolean sync = true; + + JSONArray jitems = new JSONArray(); JSONObject jkv0 = new JSONObject(); jkv0.put("key", "key0"); - jkv0.put("timestamp", 1000); - jkv0.put("value", "value0"); - jwrite.put(jkv0); + jkv0.put("revision", 1000); + if (!sync) + jkv0.put("value", "value0"); + jitems.put(jkv0); JSONObject jkv1 = new JSONObject(); jkv1.put("key", "key1"); - jkv1.put("timestamp", 1001); - jkv1.put("value", "value1"); - jwrite.put(jkv1); + jkv1.put("revision", 1001); + if (!sync) + jkv1.put("value", "value1"); + jitems.put(jkv1); - jrequest.put("command", "write"); - jrequest.put("stage", "ack"); - jrequest.put("items", jwrite); - } - - if (true) { - JSONArray jread = new JSONArray(); - jread.put("key1"); - jrequest.put("command", "read"); - jrequest.put("stage", "sync"); - jrequest.put("keys", jread); + jrequest.put("command", sync ? "read" : "write"); + if (sync) + jrequest.put("compare", true); + jrequest.put("items", jitems); } JSONObject jresponse = CloudSync.perform(context, user, password, jrequest); @@ -1567,9 +1564,9 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere for (int i = 0; i < jitems.length(); i++) { JSONObject jitem = jitems.getJSONObject(i); String key = jitem.getString("key"); - long timestamp = jitem.getLong("timestamp"); + long revision = jitem.getLong("revision"); String value = (jitem.has("value") ? jitem.getString("value") : null); - Log.i("Cloud item " + key + "=" + value + " @" + timestamp); + Log.i("Cloud item " + key + "=" + value + " @" + revision); } } return jresponse.optString("status");