From 98c3599d0b5ab02d749ee5707f3326b82c156eef Mon Sep 17 00:00:00 2001 From: M66B Date: Sun, 16 Oct 2022 23:02:21 +0200 Subject: [PATCH] Proof of concept: protected text --- .../eu/faircode/email/EditTextCompose.java | 87 +++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 2 files changed, 88 insertions(+) diff --git a/app/src/main/java/eu/faircode/email/EditTextCompose.java b/app/src/main/java/eu/faircode/email/EditTextCompose.java index 5f466deb0a..c830d65811 100644 --- a/app/src/main/java/eu/faircode/email/EditTextCompose.java +++ b/app/src/main/java/eu/faircode/email/EditTextCompose.java @@ -37,7 +37,9 @@ import android.text.Spanned; import android.text.TextUtils; import android.text.style.QuoteSpan; import android.text.style.StyleSpan; +import android.text.style.URLSpan; import android.util.AttributeSet; +import android.util.Base64; import android.view.ActionMode; import android.view.KeyEvent; import android.view.Menu; @@ -56,9 +58,16 @@ import androidx.preference.PreferenceManager; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import java.nio.ByteBuffer; +import java.security.SecureRandom; import java.util.List; import java.util.concurrent.ExecutorService; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.PBEKeySpec; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; @@ -113,6 +122,7 @@ public class EditTextCompose extends FixedEditText { int order = 1000; menu.add(Menu.CATEGORY_SECONDARY, R.string.title_insert_brackets, order++, context.getString(R.string.title_insert_brackets)); menu.add(Menu.CATEGORY_SECONDARY, R.string.title_insert_quotes, order++, context.getString(R.string.title_insert_quotes)); + menu.add(Menu.CATEGORY_SECONDARY, R.string.title_protect_text, order++, context.getString(R.string.title_protect_text)); } catch (Throwable ex) { Log.e(ex); } @@ -137,6 +147,8 @@ public class EditTextCompose extends FixedEditText { return surround("(", ")"); else if (id == R.string.title_insert_quotes) return surround("\"", "\""); + else if (id == R.string.title_protect_text) + return protect(); } return false; } @@ -166,6 +178,81 @@ public class EditTextCompose extends FixedEditText { } return selection; } + + private boolean protect() { + Editable edit = getText(); + int start = getSelectionStart(); + int end = getSelectionEnd(); + boolean selection = (edit != null && start >= 0 && start < end); + if (selection) + executor.submit(new Runnable() { + @Override + public void run() { + try { + String password = "FairEmail"; + String text = HtmlHelper.toHtml((Spanned) edit.subSequence(start, end), context); + + SecureRandom random = new SecureRandom(); + + byte[] salt = new byte[16]; // 128 bits + random.nextBytes(salt); + + byte[] iv = new byte[12]; // 96 bites + random.nextBytes(iv); + + // Iterations = 120,000; Keylength = 256 bits = 32 bytes + PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256); + + SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); + SecretKey key = skf.generateSecret(spec); + + // Authentication tag length = 128 bits = 16 bytes + GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); + + final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec); + + byte[] cipherText = cipher.doFinal(text.getBytes()); + + ByteBuffer out = ByteBuffer.allocate(1 + salt.length + iv.length + cipherText.length); + out.put((byte) 1); //version + out.put(salt); + out.put(iv); + out.put(cipherText); + String fragment = Base64.encodeToString(out.array(), Base64.URL_SAFE | Base64.NO_WRAP); + + post(new Runnable() { + @Override + public void run() { + try { + if (getSelectionStart() != start || getSelectionEnd() != end) + return; + String title = context.getString(R.string.title_decrypt); + String url = "https://email.faircode.eu/decrypt/#" + fragment; + edit.delete(start, end); + edit.insert(start, title); + edit.setSpan(new URLSpan(url), start, start + title.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + setSelection(start + title.length()); + } catch (Throwable ex) { + Log.e(ex); + } + } + }); + + } catch (Throwable ex) { + Log.e(ex); + post(new Runnable() { + @Override + public void run() { + ToastEx.makeText(context, ex.toString(), Toast.LENGTH_LONG).show(); + } + }); + } + } + }); + + return selection; + } }); setCustomInsertionActionModeCallback(new ActionMode.Callback() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b17e745446..4e68b48da8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1954,6 +1954,7 @@ Select block Bracket Quote + Protect Add Open with Info