From c9c567d3aeb4e6cff2c051002a267f16fb1fea69 Mon Sep 17 00:00:00 2001 From: sherlock Date: Mon, 26 Feb 2024 17:30:05 +0100 Subject: [PATCH 01/31] - update: fixed issues with E.016 numbers --- .../deku/DefaultSMS/Commons/Helpers.java | 18 ++++++++ .../deku/DefaultSMS/ConversationActivity.java | 41 +++++++++++-------- .../ThreadedConversationsHandler.java | 37 +++++++++++------ .../deku/DefaultSMS/Models/NativeSMSDB.java | 8 ++++ 4 files changed, 74 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Commons/Helpers.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Commons/Helpers.java index 84ce1bf4..d1f74b38 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Commons/Helpers.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Commons/Helpers.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.content.res.Resources; import android.content.res.TypedArray; +import android.database.Cursor; import android.graphics.Color; import android.net.ConnectivityManager; import android.net.Uri; @@ -29,11 +30,14 @@ import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; +import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB; import com.afkanerd.deku.DefaultSMS.R; +import com.google.android.material.navigation.NavigationBarItemView; import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber; +import java.lang.annotation.Native; import java.security.SecureRandom; import java.sql.Date; import java.text.SimpleDateFormat; @@ -133,6 +137,20 @@ public static boolean isShortCode(String address) { return matcher.find(); } + public static String getFormatCompleteNumber(Context context, String address, String defaultRegion) { + try(Cursor cursor = NativeSMSDB.fetchByAddress(context, address)) { + if(cursor.moveToFirst()) { + int recipientIdIndex = cursor.getColumnIndexOrThrow("address"); + address = cursor.getString(recipientIdIndex); + } + cursor.close(); + } catch(Exception e) { + e.printStackTrace(); + } + + return address; + } + public static String getFormatCompleteNumber(String data, String defaultRegion) { data = data.replaceAll("%2B", "+") .replaceAll("-", "") diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java index 04dd7553..31021e1d 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java @@ -17,6 +17,7 @@ import android.telephony.SmsManager; import android.text.Editable; import android.text.TextWatcher; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -150,7 +151,9 @@ public void run() { threadedConversations.getThread_id(), null); conversationsViewModel.updateToRead(getApplicationContext()); threadedConversations.setIs_read(true); - databaseConnector.threadedConversationsDao().update(threadedConversations); + int count = + databaseConnector.threadedConversationsDao().update(threadedConversations); + Log.d(getClass().getName(), "Updating count: " + count); }catch (Exception e) { e.printStackTrace(); } @@ -271,28 +274,31 @@ private void configureActivityDependencies() throws Exception { if(getIntent().hasExtra(Conversation.THREAD_ID)) { ThreadedConversations threadedConversations = new ThreadedConversations(); threadedConversations.setThread_id(getIntent().getStringExtra(Conversation.THREAD_ID)); - this.threadedConversations = ThreadedConversationsHandler.get( + this.threadedConversations = ThreadedConversationsHandler.get(getApplicationContext(), databaseConnector.threadedConversationsDao(), threadedConversations); } else if(getIntent().hasExtra(Conversation.ADDRESS)) { - ThreadedConversations threadedConversations = new ThreadedConversations(); - threadedConversations.setAddress(getIntent().getStringExtra(Conversation.ADDRESS)); this.threadedConversations = ThreadedConversationsHandler.get(getApplicationContext(), getIntent().getStringExtra(Conversation.ADDRESS)); } final String defaultUserCountry = Helpers.getUserCountry(getApplicationContext()); final String address = this.threadedConversations.getAddress(); - this.threadedConversations.setAddress( - Helpers.getFormatCompleteNumber(address, defaultUserCountry)); + Log.d(getClass().getName(), "Address: " + address); +// this.threadedConversations.setAddress( +// Helpers.getFormatCompleteNumber(address, defaultUserCountry)); +// this.threadedConversations.setAddress( +// Helpers.getFormatCompleteNumber(getApplicationContext(), address, defaultUserCountry)); String contactName = Contacts.retrieveContactName(getApplicationContext(), this.threadedConversations.getAddress()); - if(contactName == null) { - this.threadedConversations.setContact_name(Helpers.getFormatNationalNumber(address, - defaultUserCountry )); - } else { - this.threadedConversations.setContact_name(contactName); - } + Log.d(getClass().getName(), "Contact: " + contactName); +// if(contactName == null) { +// this.threadedConversations.setContact_name(Helpers.getFormatNationalNumber(address, +// defaultUserCountry )); +// } else { +// this.threadedConversations.setContact_name(contactName); +// } + this.threadedConversations.setContact_name(contactName); setEncryptionThreadedConversations(this.threadedConversations); isShortCode = Helpers.isShortCode(this.threadedConversations); @@ -546,11 +552,12 @@ private void configureToolbars() { } private String getAbTitle() { - String abTitle = getIntent().getStringExtra(Conversation.ADDRESS); - if(this.threadedConversations == null || this.threadedConversations.getContact_name() == null) { - this.threadedConversations.setContact_name( - Contacts.retrieveContactName(getApplicationContext(), abTitle)); - } +// String abTitle = getIntent().getStringExtra(Conversation.ADDRESS); +// String abTitle = threadedConversations.getAddress(); +// if(this.threadedConversations == null || this.threadedConversations.getContact_name() == null) { +// this.threadedConversations.setContact_name( +// Contacts.retrieveContactName(getApplicationContext(), abTitle)); +// } return (this.threadedConversations.getContact_name() != null && !this.threadedConversations.getContact_name().isEmpty()) ? this.threadedConversations.getContact_name(): this.threadedConversations.getAddress(); diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java index 7aae872f..5488237c 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java @@ -2,10 +2,12 @@ import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.provider.Telephony; import com.afkanerd.deku.DefaultSMS.DAO.ThreadedConversationsDao; +import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB; public class ThreadedConversationsHandler { @@ -17,20 +19,29 @@ public static ThreadedConversations get(Context context, String address) { return threadedConversations; } - public static ThreadedConversations get(ThreadedConversationsDao threadedConversationsDao, + public static ThreadedConversations get(Context context, + ThreadedConversationsDao threadedConversationsDao, ThreadedConversations threadedConversations) throws InterruptedException { - final ThreadedConversations[] threadedConversations1 = {threadedConversations}; - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - threadedConversations1[0] = threadedConversationsDao - .get(threadedConversations.getThread_id()); - } - }); - thread.start(); - thread.join(); - - return threadedConversations1[0]; +// final ThreadedConversations[] threadedConversations1 = {threadedConversations}; +// Thread thread = new Thread(new Runnable() { +// @Override +// public void run() { +// threadedConversations1[0] = threadedConversationsDao +// .get(threadedConversations.getThread_id()); +// } +// }); +// thread.start(); +// thread.join(); + try(Cursor cursor = + NativeSMSDB.fetchByThreadId(context, threadedConversations.getThread_id())) { + if(cursor.moveToFirst()) + threadedConversations = ThreadedConversations.build(cursor); + } catch (Exception e) { + e.printStackTrace(); + } + return threadedConversations; + +// return threadedConversations1[0]; } public static void call(Context context, ThreadedConversations threadedConversations) { diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NativeSMSDB.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NativeSMSDB.java index 4d8ffff5..3c34d189 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NativeSMSDB.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NativeSMSDB.java @@ -55,6 +55,14 @@ public static Cursor fetchByThreadId(Context context, String threadId) { null); } + public static Cursor fetchByAddress(Context context, String address) { + return context.getContentResolver().query(Telephony.Sms.CONTENT_URI, + null, + Telephony.Sms.ADDRESS + "=?", + new String[]{address}, + null); + } + public static Cursor fetchByMessageId(@NonNull Context context, String id) { return context.getContentResolver().query( Telephony.Sms.CONTENT_URI, From a9269640aa56cb0704f4bd6c61269cc899918996 Mon Sep 17 00:00:00 2001 From: sherlock Date: Mon, 26 Feb 2024 17:35:32 +0100 Subject: [PATCH 02/31] - update: fixed issues with E.016 numbers --- .../com/afkanerd/deku/DefaultSMS/ConversationActivity.java | 3 +-- .../Models/Conversations/ThreadedConversationsHandler.java | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java index 31021e1d..4c671f61 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java @@ -275,14 +275,13 @@ private void configureActivityDependencies() throws Exception { ThreadedConversations threadedConversations = new ThreadedConversations(); threadedConversations.setThread_id(getIntent().getStringExtra(Conversation.THREAD_ID)); this.threadedConversations = ThreadedConversationsHandler.get(getApplicationContext(), - databaseConnector.threadedConversationsDao(), threadedConversations); + threadedConversations); } else if(getIntent().hasExtra(Conversation.ADDRESS)) { this.threadedConversations = ThreadedConversationsHandler.get(getApplicationContext(), getIntent().getStringExtra(Conversation.ADDRESS)); } - final String defaultUserCountry = Helpers.getUserCountry(getApplicationContext()); final String address = this.threadedConversations.getAddress(); Log.d(getClass().getName(), "Address: " + address); // this.threadedConversations.setAddress( diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java index 5488237c..c257ca0a 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java @@ -6,21 +6,22 @@ import android.net.Uri; import android.provider.Telephony; +import com.afkanerd.deku.DefaultSMS.Commons.Helpers; import com.afkanerd.deku.DefaultSMS.DAO.ThreadedConversationsDao; import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB; public class ThreadedConversationsHandler { public static ThreadedConversations get(Context context, String address) { + final String defaultUserCountry = Helpers.getUserCountry(context); long threadId = Telephony.Threads.getOrCreateThreadId(context, address); ThreadedConversations threadedConversations = new ThreadedConversations(); - threadedConversations.setAddress(address); + threadedConversations.setAddress(Helpers.getFormatCompleteNumber(address, defaultUserCountry)); threadedConversations.setThread_id(String.valueOf(threadId)); return threadedConversations; } public static ThreadedConversations get(Context context, - ThreadedConversationsDao threadedConversationsDao, ThreadedConversations threadedConversations) throws InterruptedException { // final ThreadedConversations[] threadedConversations1 = {threadedConversations}; // Thread thread = new Thread(new Runnable() { From 6b1ec2303fca4c571e341b7f225e962bf7836f13 Mon Sep 17 00:00:00 2001 From: sherlock Date: Tue, 27 Feb 2024 20:04:11 +0100 Subject: [PATCH 03/31] - update: changed method of retrieving threads. Should work for phones that refused using modern method - if that was indeed the problem --- .../deku/DefaultSMS/DatabaseTest.java | 3 +- .../ConversationsThreadsEncryptionTest.java | 4 - .../ThreadedConversationsViewModel.java | 258 +++++++++++++----- 3 files changed, 186 insertions(+), 79 deletions(-) diff --git a/app/src/androidTest/java/java/com/afkanerd/deku/DefaultSMS/DatabaseTest.java b/app/src/androidTest/java/java/com/afkanerd/deku/DefaultSMS/DatabaseTest.java index a9ead086..a45d3562 100644 --- a/app/src/androidTest/java/java/com/afkanerd/deku/DefaultSMS/DatabaseTest.java +++ b/app/src/androidTest/java/java/com/afkanerd/deku/DefaultSMS/DatabaseTest.java @@ -24,8 +24,9 @@ public DatabaseTest() { } @Test public void testThreads() { + // [snippet, thread_id, msg_count] Cursor cursor = context.getContentResolver().query( - Telephony.Threads.CONTENT_URI, + Telephony.Sms.CONTENT_URI, null, null, null, diff --git a/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java b/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java index eb4a3725..5f2ec1af 100644 --- a/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java +++ b/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java @@ -110,10 +110,6 @@ public void canBeTransmittedAsData() throws Exception { @Test public void canDoubleRatchet() throws Throwable { - ConversationsThreadsEncryption conversationsThreadsEncryption = - new ConversationsThreadsEncryption(); - ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = - conversationsThreadsEncryption.getDaoInstance(context); String aliceAddress = "+237555555555"; String bobAddress = "+237666666666"; diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java index 30300903..c29c2c00 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java @@ -1,5 +1,6 @@ package com.afkanerd.deku.DefaultSMS.AdaptersViewModels; +import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.provider.BlockedNumberContract; @@ -181,89 +182,198 @@ public void delete(Context context, List ids) { NativeSMSDB.deleteThreads(context, ids.toArray(new String[0])); } + public static Cursor getNewestMessages(Context context, List threadIds) { + ContentResolver contentResolver = context.getContentResolver(); + + // Build the selection string using IN operator + StringBuilder selectionBuilder = new StringBuilder(Telephony.Sms.THREAD_ID + " IN ("); + for (int i = 0; i < threadIds.size(); i++) { + selectionBuilder.append("?"); + if (i < threadIds.size() - 1) { + selectionBuilder.append(","); + } + } + selectionBuilder.append(")"); + + // Prepare selection arguments + String[] selectionArgs = threadIds.toArray(new String[0]); + + // Sort by date in descending order to get the newest message first + String sortOrder = Telephony.Sms.DATE + " DESC"; + + // Query the content provider + return contentResolver.query( + Telephony.Sms.CONTENT_URI, + null, // projection: retrieve all columns + selectionBuilder.toString(), + selectionArgs, + sortOrder + ); + } + private void refresh(Context context) { - List newThreadedConversationsList = new ArrayList<>(); - Cursor cursor = context.getContentResolver().query( - Telephony.Threads.CONTENT_URI, +// [snippet, thread_id, msg_count] + List threadIds = new ArrayList<>(); + try(Cursor cursor = context.getContentResolver().query( + Telephony.Sms.Conversations.CONTENT_URI, null, null, null, "date DESC" - ); - - List threadedDraftsList = - databaseConnector.threadedConversationsDao().getThreadedDraftsList( - Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); - - List archivedThreads = databaseConnector.threadedConversationsDao().getArchivedList(); - List threadsIdsInDrafts = new ArrayList<>(); - for(ThreadedConversations threadedConversations : threadedDraftsList) - threadsIdsInDrafts.add(threadedConversations.getThread_id()); - - /* - [date, rr, sub, subject, ct_t, read_status, reply_path_present, body, type, msg_box, - thread_id, sub_cs, resp_st, retr_st, text_only, locked, exp, m_id, retr_txt_cs, st, - date_sent, read, ct_cls, m_size, rpt_a, address, sub_id, pri, tr_id, resp_txt, ct_l, - m_cls, d_rpt, v, person, service_center, error_code, _id, m_type, status] - */ - List threadedConversationsList = - databaseConnector.threadedConversationsDao().getAll(); - if(cursor != null && cursor.moveToFirst()) { - do { - ThreadedConversations threadedConversations = new ThreadedConversations(); - int recipientIdIndex = cursor.getColumnIndex("address"); - int snippetIndex = cursor.getColumnIndex("body"); - int dateIndex = cursor.getColumnIndex("date"); - int threadIdIndex = cursor.getColumnIndex("thread_id"); - int typeIndex = cursor.getColumnIndex("type"); - int readIndex = cursor.getColumnIndex("read"); - - threadedConversations.setIs_read(cursor.getInt(readIndex) == 1); - - threadedConversations.setAddress(cursor.getString(recipientIdIndex)); - if(threadedConversations.getAddress() == null || threadedConversations.getAddress().isEmpty()) - continue; - threadedConversations.setThread_id(cursor.getString(threadIdIndex)); - if(threadsIdsInDrafts.contains(threadedConversations.getThread_id())) { - ThreadedConversations tc = threadedDraftsList.get( - threadsIdsInDrafts.indexOf(threadedConversations.getThread_id())); - threadedConversations.setSnippet(tc.getSnippet()); - threadedConversations.setType(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); - threadedConversations.setDate( - Long.parseLong(tc.getDate()) > - Long.parseLong(cursor.getString(dateIndex)) ? - tc.getDate() : cursor.getString(dateIndex)); - } - else { - threadedConversations.setSnippet(cursor.getString(snippetIndex)); - threadedConversations.setType(cursor.getInt(typeIndex)); - threadedConversations.setDate(cursor.getString(dateIndex)); - } - if(BlockedNumberContract.isBlocked(context, threadedConversations.getAddress())) - threadedConversations.setIs_blocked(true); - - threadedConversations.setIs_archived( - archivedThreads.contains(threadedConversations.getThread_id())); - - String contactName = Contacts.retrieveContactName(context, - threadedConversations.getAddress()); - threadedConversations.setContact_name(contactName); - - /** - * Check things that change first - * - Read status - * - Drafts - */ - if(!threadedConversationsList.contains(threadedConversations)) { - newThreadedConversationsList.add(threadedConversations); + )) { + if(cursor != null && cursor.moveToFirst()) { + do { + try { + int snippetIndex = cursor.getColumnIndexOrThrow(Telephony.Sms.Conversations.SNIPPET); + int threadIndex = cursor.getColumnIndexOrThrow(Telephony.Sms.Conversations.THREAD_ID); + + String snippet = cursor.getString(snippetIndex); + String threadId = cursor.getString(threadIndex); + threadIds.add(threadId); + } catch (Exception e) { + e.printStackTrace(); + } + } while(cursor.moveToNext()); + +// [_id, thread_id, address, person, date, date_sent, protocol, read, status, type, +// reply_path_present, subject, body, service_center, locked, sub_id, error_code, creator, seen] + Cursor conversationsCursor = getNewestMessages(context, threadIds); + if(conversationsCursor != null && conversationsCursor.moveToFirst()) { + List threadedConversationsList = new ArrayList<>(); + do { + try { + int dateIndex = conversationsCursor + .getColumnIndexOrThrow(Telephony.Sms.DATE); + int addressIndex = conversationsCursor + .getColumnIndexOrThrow(Telephony.Sms.ADDRESS); + int typeIndex = conversationsCursor + .getColumnIndexOrThrow(Telephony.Sms.TYPE); + int readIndex = conversationsCursor + .getColumnIndexOrThrow(Telephony.Sms.READ); + int threadIdIndex = conversationsCursor + .getColumnIndexOrThrow(Telephony.Sms.THREAD_ID); + int bodyIndex = conversationsCursor + .getColumnIndexOrThrow(Telephony.Sms.BODY); + + ThreadedConversations threadedConversations = new ThreadedConversations(); + String date = conversationsCursor.getString(dateIndex); + String address = conversationsCursor.getString(addressIndex); + String threadId = conversationsCursor.getString(threadIdIndex); + int type = conversationsCursor.getInt(typeIndex); + boolean read = conversationsCursor.getInt(readIndex) == 1; + + threadedConversations.setAddress(address); + threadedConversations.setDate(date); + threadedConversations.setType(type); + threadedConversations.setIs_read(read); + threadedConversations.setThread_id(threadId); + threadedConversations.setSnippet(conversationsCursor + .getString(bodyIndex)); + + if(BlockedNumberContract.isBlocked(context, threadedConversations.getAddress())) + threadedConversations.setIs_blocked(true); + + String contactName = Contacts.retrieveContactName(context, + threadedConversations.getAddress()); + threadedConversations.setContact_name(contactName); + threadedConversationsList.add(threadedConversations); + } catch (Exception e) { + e.printStackTrace(); + } + } while(conversationsCursor.moveToNext()); + databaseConnector.threadedConversationsDao() + .insertAll(threadedConversationsList); + conversationsCursor.close(); } - } while(cursor.moveToNext()); - cursor.close(); + } + } catch (Exception e) { + e.printStackTrace(); } - databaseConnector.threadedConversationsDao().insertAll(newThreadedConversationsList); - getCount(context); } +// private void refresh(Context context) { +// List newThreadedConversationsList = new ArrayList<>(); +// Cursor cursor = context.getContentResolver().query( +// Telephony.Threads.CONTENT_URI, +// null, +// null, +// null, +// "date DESC" +// ); +// +// List threadedDraftsList = +// databaseConnector.threadedConversationsDao().getThreadedDraftsList( +// Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); +// +// List archivedThreads = databaseConnector.threadedConversationsDao().getArchivedList(); +// List threadsIdsInDrafts = new ArrayList<>(); +// for(ThreadedConversations threadedConversations : threadedDraftsList) +// threadsIdsInDrafts.add(threadedConversations.getThread_id()); +// +// /* +// [date, rr, sub, subject, ct_t, read_status, reply_path_present, body, type, msg_box, +// thread_id, sub_cs, resp_st, retr_st, text_only, locked, exp, m_id, retr_txt_cs, st, +// date_sent, read, ct_cls, m_size, rpt_a, address, sub_id, pri, tr_id, resp_txt, ct_l, +// m_cls, d_rpt, v, person, service_center, error_code, _id, m_type, status] +// */ +// List threadedConversationsList = +// databaseConnector.threadedConversationsDao().getAll(); +// if(cursor != null && cursor.moveToFirst()) { +// do { +// ThreadedConversations threadedConversations = new ThreadedConversations(); +// int recipientIdIndex = cursor.getColumnIndex("address"); +// int snippetIndex = cursor.getColumnIndex("body"); +// int dateIndex = cursor.getColumnIndex("date"); +// int threadIdIndex = cursor.getColumnIndex("thread_id"); +// int typeIndex = cursor.getColumnIndex("type"); +// int readIndex = cursor.getColumnIndex("read"); +// +// threadedConversations.setIs_read(cursor.getInt(readIndex) == 1); +// +// threadedConversations.setAddress(cursor.getString(recipientIdIndex)); +// if(threadedConversations.getAddress() == null || threadedConversations.getAddress().isEmpty()) +// continue; +// threadedConversations.setThread_id(cursor.getString(threadIdIndex)); +// if(threadsIdsInDrafts.contains(threadedConversations.getThread_id())) { +// ThreadedConversations tc = threadedDraftsList.get( +// threadsIdsInDrafts.indexOf(threadedConversations.getThread_id())); +// threadedConversations.setSnippet(tc.getSnippet()); +// threadedConversations.setType(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); +// threadedConversations.setDate( +// Long.parseLong(tc.getDate()) > +// Long.parseLong(cursor.getString(dateIndex)) ? +// tc.getDate() : cursor.getString(dateIndex)); +// } +// else { +// threadedConversations.setSnippet(cursor.getString(snippetIndex)); +// threadedConversations.setType(cursor.getInt(typeIndex)); +// threadedConversations.setDate(cursor.getString(dateIndex)); +// } +// if(BlockedNumberContract.isBlocked(context, threadedConversations.getAddress())) +// threadedConversations.setIs_blocked(true); +// +// threadedConversations.setIs_archived( +// archivedThreads.contains(threadedConversations.getThread_id())); +// +// String contactName = Contacts.retrieveContactName(context, +// threadedConversations.getAddress()); +// threadedConversations.setContact_name(contactName); +// +// /** +// * Check things that change first +// * - Read status +// * - Drafts +// */ +// if(!threadedConversationsList.contains(threadedConversations)) { +// newThreadedConversationsList.add(threadedConversations); +// } +// } while(cursor.moveToNext()); +// cursor.close(); +// } +// databaseConnector.threadedConversationsDao().insertAll(newThreadedConversationsList); +// getCount(context); +// } + public void unarchive(List archiveList) { databaseConnector.threadedConversationsDao().unarchive(archiveList); } From 3a80c4e71283fb962b33525579b40b58ba63c49d Mon Sep 17 00:00:00 2001 From: sherlock Date: Tue, 27 Feb 2024 21:31:25 +0100 Subject: [PATCH 04/31] - update: changed method of retrieving threads. Should work for phones that refused using modern method - if that was indeed the problem --- .../ThreadedConversationsViewModel.java | 246 +++++------------- 1 file changed, 59 insertions(+), 187 deletions(-) diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java index c29c2c00..2847856a 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java @@ -5,6 +5,8 @@ import android.database.Cursor; import android.provider.BlockedNumberContract; import android.provider.Telephony; +import android.text.TextUtils; +import android.util.Log; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -182,197 +184,70 @@ public void delete(Context context, List ids) { NativeSMSDB.deleteThreads(context, ids.toArray(new String[0])); } - public static Cursor getNewestMessages(Context context, List threadIds) { - ContentResolver contentResolver = context.getContentResolver(); - - // Build the selection string using IN operator - StringBuilder selectionBuilder = new StringBuilder(Telephony.Sms.THREAD_ID + " IN ("); - for (int i = 0; i < threadIds.size(); i++) { - selectionBuilder.append("?"); - if (i < threadIds.size() - 1) { - selectionBuilder.append(","); - } - } - selectionBuilder.append(")"); - - // Prepare selection arguments - String[] selectionArgs = threadIds.toArray(new String[0]); - - // Sort by date in descending order to get the newest message first - String sortOrder = Telephony.Sms.DATE + " DESC"; - - // Query the content provider - return contentResolver.query( - Telephony.Sms.CONTENT_URI, - null, // projection: retrieve all columns - selectionBuilder.toString(), - selectionArgs, - sortOrder - ); - } - private void refresh(Context context) { -// [snippet, thread_id, msg_count] - List threadIds = new ArrayList<>(); - try(Cursor cursor = context.getContentResolver().query( - Telephony.Sms.Conversations.CONTENT_URI, - null, - null, - null, - "date DESC" - )) { + try{ + Cursor cursor = context.getContentResolver().query( + Telephony.Threads.CONTENT_URI, + null, + null, + null, + "date DESC" + ); + + /* + [date, rr, sub, subject, ct_t, read_status, reply_path_present, body, type, msg_box, + thread_id, sub_cs, resp_st, retr_st, text_only, locked, exp, m_id, retr_txt_cs, st, + date_sent, read, ct_cls, m_size, rpt_a, address, sub_id, pri, tr_id, resp_txt, ct_l, + m_cls, d_rpt, v, person, service_center, error_code, _id, m_type, status] + */ + List threadedConversationsList = new ArrayList<>(); if(cursor != null && cursor.moveToFirst()) { do { - try { - int snippetIndex = cursor.getColumnIndexOrThrow(Telephony.Sms.Conversations.SNIPPET); - int threadIndex = cursor.getColumnIndexOrThrow(Telephony.Sms.Conversations.THREAD_ID); - - String snippet = cursor.getString(snippetIndex); - String threadId = cursor.getString(threadIndex); - threadIds.add(threadId); - } catch (Exception e) { - e.printStackTrace(); - } + ThreadedConversations threadedConversations = new ThreadedConversations(); + int recipientIdIndex = cursor.getColumnIndex("address"); + int snippetIndex = cursor.getColumnIndex("body"); + int dateIndex = cursor.getColumnIndex("date"); + int threadIdIndex = cursor.getColumnIndex("thread_id"); + int typeIndex = cursor.getColumnIndex("type"); + int readIndex = cursor.getColumnIndex("read"); + + threadedConversations.setIs_read(cursor.getInt(readIndex) == 1); + + threadedConversations.setAddress(cursor.getString(recipientIdIndex)); + if(threadedConversations.getAddress() == null || threadedConversations.getAddress().isEmpty()) + continue; + threadedConversations.setThread_id(cursor.getString(threadIdIndex)); + threadedConversations.setSnippet(cursor.getString(snippetIndex)); + threadedConversations.setType(cursor.getInt(typeIndex)); + threadedConversations.setDate(cursor.getString(dateIndex)); + if(BlockedNumberContract.isBlocked(context, threadedConversations.getAddress())) + threadedConversations.setIs_blocked(true); + + String contactName = Contacts.retrieveContactName(context, + threadedConversations.getAddress()); + threadedConversations.setContact_name(contactName); + threadedConversationsList.add(threadedConversations); } while(cursor.moveToNext()); - -// [_id, thread_id, address, person, date, date_sent, protocol, read, status, type, -// reply_path_present, subject, body, service_center, locked, sub_id, error_code, creator, seen] - Cursor conversationsCursor = getNewestMessages(context, threadIds); - if(conversationsCursor != null && conversationsCursor.moveToFirst()) { - List threadedConversationsList = new ArrayList<>(); - do { - try { - int dateIndex = conversationsCursor - .getColumnIndexOrThrow(Telephony.Sms.DATE); - int addressIndex = conversationsCursor - .getColumnIndexOrThrow(Telephony.Sms.ADDRESS); - int typeIndex = conversationsCursor - .getColumnIndexOrThrow(Telephony.Sms.TYPE); - int readIndex = conversationsCursor - .getColumnIndexOrThrow(Telephony.Sms.READ); - int threadIdIndex = conversationsCursor - .getColumnIndexOrThrow(Telephony.Sms.THREAD_ID); - int bodyIndex = conversationsCursor - .getColumnIndexOrThrow(Telephony.Sms.BODY); - - ThreadedConversations threadedConversations = new ThreadedConversations(); - String date = conversationsCursor.getString(dateIndex); - String address = conversationsCursor.getString(addressIndex); - String threadId = conversationsCursor.getString(threadIdIndex); - int type = conversationsCursor.getInt(typeIndex); - boolean read = conversationsCursor.getInt(readIndex) == 1; - - threadedConversations.setAddress(address); - threadedConversations.setDate(date); - threadedConversations.setType(type); - threadedConversations.setIs_read(read); - threadedConversations.setThread_id(threadId); - threadedConversations.setSnippet(conversationsCursor - .getString(bodyIndex)); - - if(BlockedNumberContract.isBlocked(context, threadedConversations.getAddress())) - threadedConversations.setIs_blocked(true); - - String contactName = Contacts.retrieveContactName(context, - threadedConversations.getAddress()); - threadedConversations.setContact_name(contactName); - threadedConversationsList.add(threadedConversations); - } catch (Exception e) { - e.printStackTrace(); - } - } while(conversationsCursor.moveToNext()); - databaseConnector.threadedConversationsDao() - .insertAll(threadedConversationsList); - conversationsCursor.close(); - } + cursor.close(); } - } catch (Exception e) { + databaseConnector.threadedConversationsDao().insertAll(threadedConversationsList); + getCount(context); + } catch(Exception e) { e.printStackTrace(); + loadNative(context); } + } -// private void refresh(Context context) { -// List newThreadedConversationsList = new ArrayList<>(); -// Cursor cursor = context.getContentResolver().query( -// Telephony.Threads.CONTENT_URI, -// null, -// null, -// null, -// "date DESC" -// ); -// -// List threadedDraftsList = -// databaseConnector.threadedConversationsDao().getThreadedDraftsList( -// Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); -// -// List archivedThreads = databaseConnector.threadedConversationsDao().getArchivedList(); -// List threadsIdsInDrafts = new ArrayList<>(); -// for(ThreadedConversations threadedConversations : threadedDraftsList) -// threadsIdsInDrafts.add(threadedConversations.getThread_id()); -// -// /* -// [date, rr, sub, subject, ct_t, read_status, reply_path_present, body, type, msg_box, -// thread_id, sub_cs, resp_st, retr_st, text_only, locked, exp, m_id, retr_txt_cs, st, -// date_sent, read, ct_cls, m_size, rpt_a, address, sub_id, pri, tr_id, resp_txt, ct_l, -// m_cls, d_rpt, v, person, service_center, error_code, _id, m_type, status] -// */ -// List threadedConversationsList = -// databaseConnector.threadedConversationsDao().getAll(); -// if(cursor != null && cursor.moveToFirst()) { -// do { -// ThreadedConversations threadedConversations = new ThreadedConversations(); -// int recipientIdIndex = cursor.getColumnIndex("address"); -// int snippetIndex = cursor.getColumnIndex("body"); -// int dateIndex = cursor.getColumnIndex("date"); -// int threadIdIndex = cursor.getColumnIndex("thread_id"); -// int typeIndex = cursor.getColumnIndex("type"); -// int readIndex = cursor.getColumnIndex("read"); -// -// threadedConversations.setIs_read(cursor.getInt(readIndex) == 1); -// -// threadedConversations.setAddress(cursor.getString(recipientIdIndex)); -// if(threadedConversations.getAddress() == null || threadedConversations.getAddress().isEmpty()) -// continue; -// threadedConversations.setThread_id(cursor.getString(threadIdIndex)); -// if(threadsIdsInDrafts.contains(threadedConversations.getThread_id())) { -// ThreadedConversations tc = threadedDraftsList.get( -// threadsIdsInDrafts.indexOf(threadedConversations.getThread_id())); -// threadedConversations.setSnippet(tc.getSnippet()); -// threadedConversations.setType(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); -// threadedConversations.setDate( -// Long.parseLong(tc.getDate()) > -// Long.parseLong(cursor.getString(dateIndex)) ? -// tc.getDate() : cursor.getString(dateIndex)); -// } -// else { -// threadedConversations.setSnippet(cursor.getString(snippetIndex)); -// threadedConversations.setType(cursor.getInt(typeIndex)); -// threadedConversations.setDate(cursor.getString(dateIndex)); -// } -// if(BlockedNumberContract.isBlocked(context, threadedConversations.getAddress())) -// threadedConversations.setIs_blocked(true); -// -// threadedConversations.setIs_archived( -// archivedThreads.contains(threadedConversations.getThread_id())); -// -// String contactName = Contacts.retrieveContactName(context, -// threadedConversations.getAddress()); -// threadedConversations.setContact_name(contactName); -// -// /** -// * Check things that change first -// * - Read status -// * - Drafts -// */ -// if(!threadedConversationsList.contains(threadedConversations)) { -// newThreadedConversationsList.add(threadedConversations); -// } -// } while(cursor.moveToNext()); -// cursor.close(); -// } -// databaseConnector.threadedConversationsDao().insertAll(newThreadedConversationsList); -// getCount(context); -// } + private void loadNative(Context context) { + try(Cursor cursor = NativeSMSDB.fetchAll(context)) { + List threadedConversations = + ThreadedConversations.buildRaw(cursor); + databaseConnector.threadedConversationsDao().insertAll(threadedConversations); + } catch (Exception e) { + e.printStackTrace(); + } + } public void unarchive(List archiveList) { databaseConnector.threadedConversationsDao().unarchive(archiveList); @@ -383,15 +258,15 @@ public void unblock(Context context, List threadIds) { databaseConnector.threadedConversationsDao().getList(threadIds); for(ThreadedConversations threadedConversations : threadedConversationsList) { BlockedNumberContract.unblock(context, threadedConversations.getAddress()); + threadedConversations.setIs_blocked(false); + databaseConnector.threadedConversationsDao().update(threadedConversations); } - refresh(context); } public void clearDrafts(Context context) { SMSDatabaseWrapper.deleteAllDraft(context); databaseConnector.threadedConversationsDao() .clearDrafts(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); - refresh(context); } public boolean hasUnread(List ids) { @@ -401,19 +276,16 @@ public boolean hasUnread(List ids) { public void markUnRead(Context context, List threadIds) { NativeSMSDB.Incoming.update_all_read(context, 0, threadIds.toArray(new String[0])); databaseConnector.threadedConversationsDao().updateRead(0, threadIds); - refresh(context); } public void markRead(Context context, List threadIds) { NativeSMSDB.Incoming.update_all_read(context, 1, threadIds.toArray(new String[0])); databaseConnector.threadedConversationsDao().updateRead(1, threadIds); - refresh(context); } public void markAllRead(Context context) { NativeSMSDB.Incoming.update_all_read(context, 1); databaseConnector.threadedConversationsDao().updateRead(1); - refresh(context); } public MutableLiveData> folderMetrics = new MutableLiveData<>(); From 40aaa933efe509b24082a55b6a0576dbbd4393a6 Mon Sep 17 00:00:00 2001 From: sherlock Date: Tue, 27 Feb 2024 21:33:50 +0100 Subject: [PATCH 05/31] - update: changed method of retrieving threads. Should work for phones that refused using modern method - if that was indeed the problem --- .../AdaptersViewModels/ThreadedConversationsViewModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java index 2847856a..d58a155a 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationsViewModel.java @@ -169,7 +169,6 @@ public void reset(Context context) { } databaseConnector.conversationDao().insertAll(conversationList); - databaseConnector.threadedConversationsDao().deleteAll(); refresh(context); } @@ -230,6 +229,7 @@ private void refresh(Context context) { } while(cursor.moveToNext()); cursor.close(); } + databaseConnector.threadedConversationsDao().deleteAll(); databaseConnector.threadedConversationsDao().insertAll(threadedConversationsList); getCount(context); } catch(Exception e) { From 463dbd8ede1ce58648dce0378fab3ca7af0388a1 Mon Sep 17 00:00:00 2001 From: sherlock Date: Wed, 28 Feb 2024 09:04:02 +0100 Subject: [PATCH 06/31] - update: preparing Gateway clients to have the ready counts --- .../GatewayClientProjectListingViewModel.java | 10 +++- .../gateway_client_project_listing_layout.xml | 54 +++++++++---------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingViewModel.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingViewModel.java index 493cdd9b..f5c55b0e 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingViewModel.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingViewModel.java @@ -9,6 +9,7 @@ import androidx.room.Room; import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import java.util.ArrayList; import java.util.HashSet; @@ -18,10 +19,17 @@ public class GatewayClientProjectListingViewModel extends ViewModel { long id; + MutableLiveData> mutableLiveData = new MutableLiveData<>(); public LiveData> get(Datastore databaseConnector, long id) { this.id = id; GatewayClientProjectDao gatewayClientProjectDao = databaseConnector.gatewayClientProjectDao(); - return gatewayClientProjectDao.fetchGatewayClientId(id); + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + mutableLiveData.postValue(gatewayClientProjectDao.fetchGatewayClientIdList(id)); + } + }); + return mutableLiveData; } } diff --git a/app/src/main/res/layout/gateway_client_project_listing_layout.xml b/app/src/main/res/layout/gateway_client_project_listing_layout.xml index c54317dc..78210dc7 100644 --- a/app/src/main/res/layout/gateway_client_project_listing_layout.xml +++ b/app/src/main/res/layout/gateway_client_project_listing_layout.xml @@ -19,38 +19,33 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> - - + android:layout_height="wrap_content"> - - - - - - - - - - - + - - - - - - - - - + + + + + From 66f94f080cd5825ac7090320c9580cf59a5144bd Mon Sep 17 00:00:00 2001 From: sherlock Date: Sun, 3 Mar 2024 22:59:01 +0100 Subject: [PATCH 07/31] - update: began testing self messaging --- .../ConversationsThreadsEncryptionTest.java | 70 +++++++++++++++++++ .../com/afkanerd/deku/E2EE/E2EEHandler.java | 15 ++++ 2 files changed, 85 insertions(+) diff --git a/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java b/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java index 5f2ec1af..1f501379 100644 --- a/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java +++ b/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java @@ -108,6 +108,76 @@ public void canBeTransmittedAsData() throws Exception { assertTrue(transmissionRequest.length < 120); } + @Test + public void canSelfDoubleRatchet() throws Throwable { + String aliceAddress = "+2375555555550"; + String bobAddress = "+2375555555550"; + + // Initial request + String aliceKeystoreAlias = E2EEHandler.deriveKeystoreAlias(bobAddress, 0); + Pair keystorePairAlice = E2EEHandler.buildForEncryptionRequest(context, + bobAddress); +// String aliceKeystoreAlias = keystorePairAlice.first; + byte[] aliceTransmissionKey = keystorePairAlice.second; + + // bob received alice's key + assertTrue(E2EEHandler.isValidDefaultPublicKey(aliceTransmissionKey)); + String bobKeystoreAlias = E2EEHandler.deriveKeystoreAlias(aliceAddress, 0); + byte[] aliceExtractedTransmissionKey = E2EEHandler.extractTransmissionKey(aliceTransmissionKey); + E2EEHandler.insertNewAgreementKeyDefault(context, aliceExtractedTransmissionKey, bobKeystoreAlias); + + // Agreement request + assertTrue(E2EEHandler.hasSameAgreementKey(context, bobKeystoreAlias, + aliceExtractedTransmissionKey)); + Pair keystorePairBob = E2EEHandler.buildForEncryptionRequest(context, aliceAddress); +// String bobKeystoreAlias = keystorePairBob.first; + byte[] bobTransmissionKey = keystorePairBob.second; + + // alice received bob's key + assertTrue(E2EEHandler.isValidDefaultPublicKey(bobTransmissionKey)); + byte[] bobExtractedTransmissionKey = E2EEHandler.extractTransmissionKey(bobTransmissionKey); + E2EEHandler.insertNewAgreementKeyDefault(context, bobExtractedTransmissionKey, aliceKeystoreAlias); + assertFalse(E2EEHandler.hasSameAgreementKey(context, aliceKeystoreAlias, + aliceExtractedTransmissionKey)); + + assertTrue(E2EEHandler.isAvailableInKeystore(aliceKeystoreAlias)); + assertTrue(E2EEHandler.isAvailableInKeystore(bobKeystoreAlias)); + + assertTrue(E2EEHandler.canCommunicateSecurely(context, aliceKeystoreAlias)); + assertTrue(E2EEHandler.canCommunicateSecurely(context, bobKeystoreAlias)); + + // ----> alice sends the message + byte[] aliceText = CryptoHelpers.generateRandomBytes(130); + byte[][] aliceCipherText = E2EEHandler.encrypt(context, aliceKeystoreAlias, aliceText); + String aliceTransmissionText = E2EEHandler.buildTransmissionText(aliceCipherText[0]); + + // <----- bob receives the message + assertTrue(E2EEHandler.isValidDefaultText(aliceTransmissionText)); + byte[] aliceExtractedText = E2EEHandler.extractTransmissionText(aliceTransmissionText); + byte[] alicePlainText = E2EEHandler.decrypt(context, bobKeystoreAlias, aliceExtractedText, + null, null); + assertArrayEquals(aliceText, alicePlainText); + + // <---- bob sends a message + byte[] bobText = CryptoHelpers.generateRandomBytes(130); + byte[][] bobCipherText = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText); + String bobTransmissionText = E2EEHandler.buildTransmissionText(bobCipherText[0]); + + // <----- bob receives the message again - as would be on mobile devices +// aliceExtractedText = E2EEHandler.extractTransmissionText(aliceTransmissionText); +// alicePlainText = E2EEHandler.decrypt(context, bobKeystoreAlias, aliceExtractedText, +// aliceCipherText[1], null); +// assertArrayEquals(aliceText, alicePlainText); + + // <---- alice receives bob's message + assertTrue(E2EEHandler.isValidDefaultText(bobTransmissionText)); + byte[] bobExtractedText = E2EEHandler.extractTransmissionText(bobTransmissionText); + byte[] bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAlias, bobExtractedText, + null, null); + assertArrayEquals(bobText, bobPlainText); + } + + @Test public void canDoubleRatchet() throws Throwable { String aliceAddress = "+237555555555"; diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java index 548a0c8a..673166aa 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java @@ -39,6 +39,7 @@ import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; import java.util.Map; import javax.crypto.BadPaddingException; @@ -93,6 +94,20 @@ public static boolean isAvailableInKeystore(String keystoreAlias) throws Certifi return KeystoreHelpers.isAvailableInKeystore(keystoreAlias); } + public static boolean hasSameAgreementKey(Context context, String keystoreAlias, byte[] publicKey) { + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + ConversationsThreadsEncryption conversationsThreadsEncryption = + Datastore.datastore.conversationsThreadsEncryptionDao().fetch(keystoreAlias); + byte[] currentPubKey = + Base64.decode(conversationsThreadsEncryption.getPublicKey(), Base64.NO_WRAP); + return Arrays.equals(currentPubKey, publicKey); + } + public static boolean canCommunicateSecurely(Context context, String keystoreAlias) throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException { if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { Datastore.datastore = Room.databaseBuilder(context, Datastore.class, From 932e4769241937e0fc5653996e24c40eb7fd40bf Mon Sep 17 00:00:00 2001 From: sherlock Date: Mon, 4 Mar 2024 10:15:01 +0100 Subject: [PATCH 08/31] - update: test passes for self end to end encryption --- .../ConversationsThreadsEncryptionTest.java | 17 +++------ .../com/afkanerd/deku/E2EE/E2EEHandler.java | 38 ++++++++++++++++++- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java b/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java index 1f501379..f43ffad0 100644 --- a/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java +++ b/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java @@ -130,14 +130,13 @@ public void canSelfDoubleRatchet() throws Throwable { assertTrue(E2EEHandler.hasSameAgreementKey(context, bobKeystoreAlias, aliceExtractedTransmissionKey)); Pair keystorePairBob = E2EEHandler.buildForEncryptionRequest(context, aliceAddress); -// String bobKeystoreAlias = keystorePairBob.first; byte[] bobTransmissionKey = keystorePairBob.second; // alice received bob's key assertTrue(E2EEHandler.isValidDefaultPublicKey(bobTransmissionKey)); byte[] bobExtractedTransmissionKey = E2EEHandler.extractTransmissionKey(bobTransmissionKey); E2EEHandler.insertNewAgreementKeyDefault(context, bobExtractedTransmissionKey, aliceKeystoreAlias); - assertFalse(E2EEHandler.hasSameAgreementKey(context, aliceKeystoreAlias, + assertTrue(E2EEHandler.hasSameAgreementKey(context, bobKeystoreAlias, aliceExtractedTransmissionKey)); assertTrue(E2EEHandler.isAvailableInKeystore(aliceKeystoreAlias)); @@ -149,31 +148,27 @@ public void canSelfDoubleRatchet() throws Throwable { // ----> alice sends the message byte[] aliceText = CryptoHelpers.generateRandomBytes(130); byte[][] aliceCipherText = E2EEHandler.encrypt(context, aliceKeystoreAlias, aliceText); + byte[] mk = aliceCipherText[1]; String aliceTransmissionText = E2EEHandler.buildTransmissionText(aliceCipherText[0]); // <----- bob receives the message assertTrue(E2EEHandler.isValidDefaultText(aliceTransmissionText)); byte[] aliceExtractedText = E2EEHandler.extractTransmissionText(aliceTransmissionText); byte[] alicePlainText = E2EEHandler.decrypt(context, bobKeystoreAlias, aliceExtractedText, - null, null); + mk, null); assertArrayEquals(aliceText, alicePlainText); // <---- bob sends a message byte[] bobText = CryptoHelpers.generateRandomBytes(130); byte[][] bobCipherText = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText); + mk = bobCipherText[1]; String bobTransmissionText = E2EEHandler.buildTransmissionText(bobCipherText[0]); - // <----- bob receives the message again - as would be on mobile devices -// aliceExtractedText = E2EEHandler.extractTransmissionText(aliceTransmissionText); -// alicePlainText = E2EEHandler.decrypt(context, bobKeystoreAlias, aliceExtractedText, -// aliceCipherText[1], null); -// assertArrayEquals(aliceText, alicePlainText); - // <---- alice receives bob's message assertTrue(E2EEHandler.isValidDefaultText(bobTransmissionText)); byte[] bobExtractedText = E2EEHandler.extractTransmissionText(bobTransmissionText); - byte[] bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAlias, bobExtractedText, - null, null); + byte[] bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAlias, bobExtractedText, mk, + null); assertArrayEquals(bobText, bobPlainText); } diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java index 673166aa..9d397013 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java @@ -94,6 +94,10 @@ public static boolean isAvailableInKeystore(String keystoreAlias) throws Certifi return KeystoreHelpers.isAvailableInKeystore(keystoreAlias); } + public static boolean isSelf(Context context, String keystoreAlias, byte[] publicKey) { + return hasSameAgreementKey(context, keystoreAlias, publicKey); + } + public static boolean hasSameAgreementKey(Context context, String keystoreAlias, byte[] publicKey) { if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), @@ -254,6 +258,9 @@ public static byte[] extractTransmissionKey(byte[] data) { /** * This uses session = 0, which is the default PublicKey values for. * + * If the PublicKey in Keystore is the same as ConversationsEncryption database for the same alias, + * this infers same person making a request and therefore uses the same public key. + * * @param context * @param address * @return @@ -263,9 +270,15 @@ public static byte[] extractTransmissionKey(byte[] data) { * @throws InterruptedException */ public static Pair buildForEncryptionRequest(Context context, String address) throws Exception { + PublicKey publicKey; int session = 0; + String keystoreAlias = deriveKeystoreAlias(address, session); - PublicKey publicKey = createNewKeyPair(context, keystoreAlias); + KeyPair keyPair = getKeyPairBasedVersioning(context, keystoreAlias); + if(keyPair == null || !isSelf(context, keystoreAlias, keyPair.getPublic().getEncoded())) + publicKey = createNewKeyPair(context, keystoreAlias); + else + publicKey = keyPair.getPublic(); return new Pair<>(keystoreAlias, buildDefaultPublicKey(publicKey.getEncoded())); } @@ -311,6 +324,20 @@ public static ConversationsThreadsEncryption fetchStoredPeerData(Context context return conversationsThreadsEncryptionDao.fetch(keystoreAlias); } + /** + * Get the default KeyPair, used in identifying the peers. This is different from the keypairs + * stored during ratcheting. + * + * @param context + * @param keystoreAlias + * @return + * @throws UnrecoverableEntryException + * @throws CertificateException + * @throws KeyStoreException + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws InterruptedException + */ public static KeyPair getKeyPairBasedVersioning(Context context, String keystoreAlias) throws UnrecoverableEntryException, CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, InterruptedException { final KeyPair[] keyPair = new KeyPair[1]; if(Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { @@ -349,6 +376,15 @@ protected static String getKeystoreForRatchets(String keystoreAlias) { return keystoreAlias + "-ratchet-sessions"; } + /** + * This returns a header, ciphertext and the mk. + * + * @param context + * @param keystoreAlias + * @param data + * @return + * @throws Throwable + */ public static byte[][] encrypt(Context context, final String keystoreAlias, byte[] data) throws Throwable { if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), From 58212cadc9f22200dfd45287cdece32a64189531 Mon Sep 17 00:00:00 2001 From: sherlock Date: Mon, 4 Mar 2024 12:54:13 +0100 Subject: [PATCH 09/31] - update: can work with out of order messages, now to implement the structure --- .../ConversationsThreadsEncryptionTest.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java b/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java index f43ffad0..da73e448 100644 --- a/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java +++ b/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java @@ -164,10 +164,23 @@ public void canSelfDoubleRatchet() throws Throwable { mk = bobCipherText[1]; String bobTransmissionText = E2EEHandler.buildTransmissionText(bobCipherText[0]); + // <---- then bob sends another + byte[] bobText1 = CryptoHelpers.generateRandomBytes(130); + byte[][] bobCipherText1 = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText1); + byte[] mk1 = bobCipherText1[1]; + String bobTransmissionText1 = E2EEHandler.buildTransmissionText(bobCipherText1[0]); + + // <---- alice receives bob's message - this message is out of order + assertTrue(E2EEHandler.isValidDefaultText(bobTransmissionText1)); + byte[] bobExtractedText = E2EEHandler.extractTransmissionText(bobTransmissionText1); + byte[] bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAlias, bobExtractedText, + mk1, null); + assertArrayEquals(bobText1, bobPlainText); + // <---- alice receives bob's message assertTrue(E2EEHandler.isValidDefaultText(bobTransmissionText)); - byte[] bobExtractedText = E2EEHandler.extractTransmissionText(bobTransmissionText); - byte[] bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAlias, bobExtractedText, mk, + bobExtractedText = E2EEHandler.extractTransmissionText(bobTransmissionText); + bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAlias, bobExtractedText, mk, null); assertArrayEquals(bobText, bobPlainText); } From 08948ce1721f8f1174761f1af7ace1cf6b87ebec Mon Sep 17 00:00:00 2001 From: sherlock Date: Tue, 5 Mar 2024 09:07:46 +0100 Subject: [PATCH 10/31] - update: can self encrypt and decrypt, should have some bugs but should be able to identify them later --- .../ConversationsThreadsEncryptionTest.java | 101 +++++++++--------- .../IncomingDataSMSBroadcastReceiver.java | 16 ++- .../IncomingTextSMSBroadcastReceiver.java | 3 +- .../DefaultSMS/CustomAppCompactActivity.java | 35 ++++-- ...readedConversationsTemplateViewHolder.java | 5 +- .../DefaultSMS/Models/SMSDatabaseWrapper.java | 8 ++ .../deku/E2EE/E2EECompactActivity.java | 8 +- .../com/afkanerd/deku/E2EE/E2EEHandler.java | 85 ++++++++++----- 8 files changed, 173 insertions(+), 88 deletions(-) diff --git a/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java b/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java index da73e448..44c6b374 100644 --- a/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java +++ b/app/src/androidTest/java/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java @@ -98,13 +98,15 @@ public void testIsValidAgreementPubKey() throws GeneralSecurityException, IOExce @Test public void testBuildForEncryptionRequest() throws Exception { String address = "+237333333333"; - byte[] transmissionRequest = E2EEHandler.buildForEncryptionRequest(context, address).second; + byte[] transmissionRequest = E2EEHandler + .buildForEncryptionRequest(context, address, null).second; assertTrue(E2EEHandler.isValidDefaultPublicKey(transmissionRequest)); } @Test public void canBeTransmittedAsData() throws Exception { String address = "+237444444444"; - byte[] transmissionRequest = E2EEHandler.buildForEncryptionRequest(context, address).second; + byte[] transmissionRequest = E2EEHandler + .buildForEncryptionRequest(context, address, null).second; assertTrue(transmissionRequest.length < 120); } @@ -114,30 +116,33 @@ public void canSelfDoubleRatchet() throws Throwable { String bobAddress = "+2375555555550"; // Initial request - String aliceKeystoreAlias = E2EEHandler.deriveKeystoreAlias(bobAddress, 0); Pair keystorePairAlice = E2EEHandler.buildForEncryptionRequest(context, - bobAddress); -// String aliceKeystoreAlias = keystorePairAlice.first; + bobAddress, null); + String aliceKeystoreAlias = keystorePairAlice.first; byte[] aliceTransmissionKey = keystorePairAlice.second; - // bob received alice's key + // Bob received Alice's key assertTrue(E2EEHandler.isValidDefaultPublicKey(aliceTransmissionKey)); + byte[] aliceExtractedTransmissionKey = + E2EEHandler.extractTransmissionKey(aliceTransmissionKey); String bobKeystoreAlias = E2EEHandler.deriveKeystoreAlias(aliceAddress, 0); - byte[] aliceExtractedTransmissionKey = E2EEHandler.extractTransmissionKey(aliceTransmissionKey); - E2EEHandler.insertNewAgreementKeyDefault(context, aliceExtractedTransmissionKey, bobKeystoreAlias); + E2EEHandler.insertNewAgreementKeyDefault(context, aliceExtractedTransmissionKey, + bobKeystoreAlias); - // Agreement request - assertTrue(E2EEHandler.hasSameAgreementKey(context, bobKeystoreAlias, - aliceExtractedTransmissionKey)); - Pair keystorePairBob = E2EEHandler.buildForEncryptionRequest(context, aliceAddress); + // assumption == bob initiates a reply to build key, but does not proceed to send + final String bobKeystoreAliasSelf = E2EEHandler.buildForSelf(bobKeystoreAlias); + Pair keystorePairBob = E2EEHandler.buildForEncryptionRequest(context, + aliceAddress, bobKeystoreAliasSelf); byte[] bobTransmissionKey = keystorePairBob.second; - - // alice received bob's key assertTrue(E2EEHandler.isValidDefaultPublicKey(bobTransmissionKey)); - byte[] bobExtractedTransmissionKey = E2EEHandler.extractTransmissionKey(bobTransmissionKey); - E2EEHandler.insertNewAgreementKeyDefault(context, bobExtractedTransmissionKey, aliceKeystoreAlias); - assertTrue(E2EEHandler.hasSameAgreementKey(context, bobKeystoreAlias, - aliceExtractedTransmissionKey)); + byte[] bobExtractedTransmissionKey = + E2EEHandler.extractTransmissionKey(bobTransmissionKey); + E2EEHandler.insertNewAgreementKeyDefault(context, bobExtractedTransmissionKey, + bobKeystoreAliasSelf); + + // assumption == alice exist and self is true + assertTrue(E2EEHandler.isSelf(context, + E2EEHandler.deriveKeystoreAlias(aliceAddress, 0))); assertTrue(E2EEHandler.isAvailableInKeystore(aliceKeystoreAlias)); assertTrue(E2EEHandler.isAvailableInKeystore(bobKeystoreAlias)); @@ -145,43 +150,44 @@ public void canSelfDoubleRatchet() throws Throwable { assertTrue(E2EEHandler.canCommunicateSecurely(context, aliceKeystoreAlias)); assertTrue(E2EEHandler.canCommunicateSecurely(context, bobKeystoreAlias)); + final boolean isSelf = E2EEHandler.isSelf(context, + E2EEHandler.deriveKeystoreAlias(aliceAddress, 0)); + assertTrue(isSelf); // ----> alice sends the message byte[] aliceText = CryptoHelpers.generateRandomBytes(130); - byte[][] aliceCipherText = E2EEHandler.encrypt(context, aliceKeystoreAlias, aliceText); - byte[] mk = aliceCipherText[1]; + byte[][] aliceCipherText = E2EEHandler.encrypt(context, aliceKeystoreAlias, aliceText, true); String aliceTransmissionText = E2EEHandler.buildTransmissionText(aliceCipherText[0]); // <----- bob receives the message assertTrue(E2EEHandler.isValidDefaultText(aliceTransmissionText)); byte[] aliceExtractedText = E2EEHandler.extractTransmissionText(aliceTransmissionText); - byte[] alicePlainText = E2EEHandler.decrypt(context, bobKeystoreAlias, aliceExtractedText, - mk, null); + byte[] alicePlainText = E2EEHandler.decrypt(context, bobKeystoreAliasSelf, + aliceExtractedText, null, null, true); assertArrayEquals(aliceText, alicePlainText); // <---- bob sends a message byte[] bobText = CryptoHelpers.generateRandomBytes(130); - byte[][] bobCipherText = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText); - mk = bobCipherText[1]; + byte[][] bobCipherText = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText, true); String bobTransmissionText = E2EEHandler.buildTransmissionText(bobCipherText[0]); // <---- then bob sends another byte[] bobText1 = CryptoHelpers.generateRandomBytes(130); - byte[][] bobCipherText1 = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText1); - byte[] mk1 = bobCipherText1[1]; + byte[][] bobCipherText1 = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText1, true); String bobTransmissionText1 = E2EEHandler.buildTransmissionText(bobCipherText1[0]); // <---- alice receives bob's message - this message is out of order + final String aliceKeystoreAliasSelf = E2EEHandler.buildForSelf(aliceKeystoreAlias); assertTrue(E2EEHandler.isValidDefaultText(bobTransmissionText1)); - byte[] bobExtractedText = E2EEHandler.extractTransmissionText(bobTransmissionText1); - byte[] bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAlias, bobExtractedText, - mk1, null); - assertArrayEquals(bobText1, bobPlainText); + byte[] bobExtractedText_o3 = E2EEHandler.extractTransmissionText(bobTransmissionText1); + byte[] bobPlainText_o3 = E2EEHandler.decrypt(context, aliceKeystoreAliasSelf, + bobExtractedText_o3, null, null, true); + assertArrayEquals(bobText1, bobPlainText_o3); // <---- alice receives bob's message assertTrue(E2EEHandler.isValidDefaultText(bobTransmissionText)); - bobExtractedText = E2EEHandler.extractTransmissionText(bobTransmissionText); - bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAlias, bobExtractedText, mk, - null); + byte[] bobExtractedText = E2EEHandler.extractTransmissionText(bobTransmissionText); + byte[] bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAliasSelf, bobExtractedText, + null, null, true); assertArrayEquals(bobText, bobPlainText); } @@ -194,8 +200,7 @@ public void canDoubleRatchet() throws Throwable { // Initial request String aliceKeystoreAlias = E2EEHandler.deriveKeystoreAlias(bobAddress, 0); Pair keystorePairAlice = E2EEHandler.buildForEncryptionRequest(context, - bobAddress); -// String aliceKeystoreAlias = keystorePairAlice.first; + bobAddress, null); byte[] aliceTransmissionKey = keystorePairAlice.second; // bob received alice's key @@ -205,8 +210,8 @@ public void canDoubleRatchet() throws Throwable { E2EEHandler.insertNewAgreementKeyDefault(context, aliceExtractedTransmissionKey, bobKeystoreAlias); // Agreement request - Pair keystorePairBob = E2EEHandler.buildForEncryptionRequest(context, aliceAddress); -// String bobKeystoreAlias = keystorePairBob.first; + Pair keystorePairBob = E2EEHandler + .buildForEncryptionRequest(context, aliceAddress, null); byte[] bobTransmissionKey = keystorePairBob.second; // alice received bob's key @@ -222,69 +227,69 @@ public void canDoubleRatchet() throws Throwable { // ----> alice sends the message byte[] aliceText = CryptoHelpers.generateRandomBytes(130); - byte[][] aliceCipherText = E2EEHandler.encrypt(context, aliceKeystoreAlias, aliceText); + byte[][] aliceCipherText = E2EEHandler.encrypt(context, aliceKeystoreAlias, aliceText, false); String aliceTransmissionText = E2EEHandler.buildTransmissionText(aliceCipherText[0]); // <----- bob receives the message assertTrue(E2EEHandler.isValidDefaultText(aliceTransmissionText)); byte[] aliceExtractedText = E2EEHandler.extractTransmissionText(aliceTransmissionText); byte[] alicePlainText = E2EEHandler.decrypt(context, bobKeystoreAlias, aliceExtractedText, - null, null); + null, null, false); assertArrayEquals(aliceText, alicePlainText); // <---- bob sends a message byte[] bobText = CryptoHelpers.generateRandomBytes(130); - byte[][] bobCipherText = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText); + byte[][] bobCipherText = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText, false); String bobTransmissionText = E2EEHandler.buildTransmissionText(bobCipherText[0]); // <----- bob receives the message again - as would be on mobile devices aliceExtractedText = E2EEHandler.extractTransmissionText(aliceTransmissionText); alicePlainText = E2EEHandler.decrypt(context, bobKeystoreAlias, aliceExtractedText, - aliceCipherText[1], null); + aliceCipherText[1], null, false); assertArrayEquals(aliceText, alicePlainText); // <---- alice receives bob's message assertTrue(E2EEHandler.isValidDefaultText(bobTransmissionText)); byte[] bobExtractedText = E2EEHandler.extractTransmissionText(bobTransmissionText); byte[] bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAlias, bobExtractedText, - null, null); + null, null, false); assertArrayEquals(bobText, bobPlainText); // <---- bob sends a message bobText = CryptoHelpers.generateRandomBytes(130); - bobCipherText = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText); + bobCipherText = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText, false); bobTransmissionText = E2EEHandler.buildTransmissionText(bobCipherText[0]); // <---- then bob sends another byte[] bobText1 = CryptoHelpers.generateRandomBytes(130); - byte[][] bobCipherText1 = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText1); + byte[][] bobCipherText1 = E2EEHandler.encrypt(context, bobKeystoreAlias, bobText1, false); String bobTransmissionText1 = E2EEHandler.buildTransmissionText(bobCipherText1[0]); // ----> alice sends the message aliceText = CryptoHelpers.generateRandomBytes(130); - aliceCipherText = E2EEHandler.encrypt(context, aliceKeystoreAlias, aliceText); + aliceCipherText = E2EEHandler.encrypt(context, aliceKeystoreAlias, aliceText, false); aliceTransmissionText = E2EEHandler.buildTransmissionText(aliceCipherText[0]); // <----- bob receives the message assertTrue(E2EEHandler.isValidDefaultText(aliceTransmissionText)); aliceExtractedText = E2EEHandler.extractTransmissionText(aliceTransmissionText); alicePlainText = E2EEHandler.decrypt(context, bobKeystoreAlias, aliceExtractedText, - null, null); + null, null, false); assertArrayEquals(aliceText, alicePlainText); // <---- alice receives bob's message - this message is out of order assertTrue(E2EEHandler.isValidDefaultText(bobTransmissionText1)); bobExtractedText = E2EEHandler.extractTransmissionText(bobTransmissionText1); bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAlias, bobExtractedText, - null, null); + null, null, false); assertArrayEquals(bobText1, bobPlainText); // <---- alice receives bob's message assertTrue(E2EEHandler.isValidDefaultText(bobTransmissionText)); bobExtractedText = E2EEHandler.extractTransmissionText(bobTransmissionText); bobPlainText = E2EEHandler.decrypt(context, aliceKeystoreAlias, bobExtractedText, - null, null); + null, null, false); assertArrayEquals(bobText, bobPlainText); } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java index 32030bcf..7081383d 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java @@ -50,6 +50,15 @@ public class IncomingDataSMSBroadcastReceiver extends BroadcastReceiver { ExecutorService executorService = Executors.newFixedThreadPool(4); Datastore databaseConnector; + + public void insertThreads(Context context, Conversation conversation) { + ThreadedConversations threadedConversations = + ThreadedConversations.build(context, conversation); + String contactName = Contacts.retrieveContactName(context, conversation.getAddress()); + threadedConversations.setContact_name(contactName); + databaseConnector.threadedConversationsDao().insert(threadedConversations); + } + @Override public void onReceive(Context context, Intent intent) { /** @@ -96,6 +105,7 @@ public void onReceive(Context context, Intent intent) { @Override public void run() { databaseConnector.conversationDao().insert(conversation); + insertThreads(context, conversation); if(isValidKey) { try { @@ -126,11 +136,15 @@ public void run() { } } - void processForEncryptionKey(Context context, Conversation conversation) throws NumberParseException, GeneralSecurityException, IOException, InterruptedException, JSONException { + void processForEncryptionKey(Context context, Conversation conversation) throws + NumberParseException, GeneralSecurityException, IOException, InterruptedException, JSONException { byte[] data = Base64.decode(conversation.getData(), Base64.DEFAULT); String keystoreAlias = E2EEHandler.deriveKeystoreAlias(conversation.getAddress(), 0); byte[] extractedTransmissionKey = E2EEHandler.extractTransmissionKey(data); + /* + * This should allow Bob to continue communicating in case (Bob == Alice) = true. + */ E2EEHandler.insertNewAgreementKeyDefault(context, extractedTransmissionKey, keystoreAlias); } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java index e88b2c82..ba9aea31 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java @@ -285,7 +285,8 @@ public String processEncryptedIncoming(Context context, String address, String t if(E2EEHandler.isValidDefaultText(text)) { String keystoreAlias = E2EEHandler.deriveKeystoreAlias(address, 0); byte[] cipherText = E2EEHandler.extractTransmissionText(text); - text = new String(E2EEHandler.decrypt(context, keystoreAlias, cipherText, null, null)); + text = new String(E2EEHandler.decrypt(context, keystoreAlias, cipherText, null, + null, false)); } return text; } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java index b7f1ade8..6fcdf250 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java @@ -31,6 +31,7 @@ import com.google.i18n.phonenumbers.NumberParseException; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; @@ -88,16 +89,32 @@ protected void sendTextMessage(Conversation conversation, threadedConversations, messageId, null); } - protected void sendTextMessage(final String text, int subscriptionId, + protected void sendTextMessage(String text, int subscriptionId, ThreadedConversations threadedConversations, String messageId, byte[] _mk) throws NumberParseException, InterruptedException { if(text != null) { if(messageId == null) messageId = String.valueOf(System.currentTimeMillis()); - final String messageIdFinal = messageId; + Conversation conversation = new Conversation(); + if(_mk != null) { + try { + String keystoreAlias = E2EEHandler.deriveKeystoreAlias( + threadedConversations.getAddress(), 0); + byte[] cipherText = E2EEHandler.extractTransmissionText(text); + String plainText = new String(E2EEHandler.decrypt(getApplicationContext(), + keystoreAlias, cipherText, _mk, null, false), + StandardCharsets.UTF_8); + conversation.setText(plainText); + } catch(Throwable e ) { + e.printStackTrace(); + } + } else { + conversation.setText(text); + } + + final String messageIdFinal = messageId; conversation.setMessage_id(messageId); - conversation.setText(text); conversation.setThread_id(threadedConversations.getThread_id()); conversation.setSubscription_id(subscriptionId); conversation.setType(Telephony.Sms.MESSAGE_TYPE_OUTBOX); @@ -105,8 +122,8 @@ protected void sendTextMessage(final String text, int subscriptionId, conversation.setAddress(threadedConversations.getAddress()); conversation.setStatus(Telephony.Sms.STATUS_PENDING); // TODO: should encrypt this before storing - if(_mk != null) - conversation.set_mk(Base64.encodeToString(_mk, Base64.NO_WRAP)); +// if(_mk != null) +// conversation.set_mk(Base64.encodeToString(_mk, Base64.NO_WRAP)); if(conversationsViewModel != null) { ThreadingPoolExecutor.executorService.execute(new Runnable() { @@ -114,9 +131,11 @@ protected void sendTextMessage(final String text, int subscriptionId, public void run() { try { conversationsViewModel.insert(getApplicationContext(), conversation); - SMSDatabaseWrapper.send_text(getApplicationContext(), conversation, null); -// conversationsViewModel.updateThreadId(conversation.getThread_id(), -// _messageId, id); + if(_mk == null) + SMSDatabaseWrapper.send_text(getApplicationContext(), conversation, null); + else + SMSDatabaseWrapper.send_text(getApplicationContext(), conversation, + text, null); } catch (Exception e) { e.printStackTrace(); NativeSMSDB.Outgoing.register_failed(getApplicationContext(), messageIdFinal, 1); diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java index 06665847..77d1d778 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java @@ -24,6 +24,7 @@ import com.afkanerd.deku.DefaultSMS.Models.Contacts; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; import com.afkanerd.deku.DefaultSMS.R; +import com.afkanerd.deku.E2EE.E2EEHandler; import com.google.android.material.card.MaterialCardView; import java.util.List; @@ -91,7 +92,9 @@ public void bind(ThreadedConversations conversation, View.OnClickListener onClic } else this.address.setText(conversation.getAddress()); - this.snippet.setText(conversation.getSnippet()); + String text = conversation.getSnippet(); + + this.snippet.setText(text); String date = Helpers.formatDate(itemView.getContext(), Long.parseLong(conversation.getDate())); this.date.setText(date); diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SMSDatabaseWrapper.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SMSDatabaseWrapper.java index 0901525a..9434091d 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SMSDatabaseWrapper.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SMSDatabaseWrapper.java @@ -29,6 +29,14 @@ public static void send_text(Context context, Conversation conversation, Bundle // conversation.setThread_id(nativeOutputs[NativeSMSDB.THREAD_ID]); } + public static void send_text(Context context, Conversation conversation, String text, Bundle bundle) throws Exception { + String transmissionAddress = Helpers.getFormatForTransmission(conversation.getAddress(), + Helpers.getUserCountry(context)); + String[] nativeOutputs = NativeSMSDB.Outgoing._send_text(context, conversation.getMessage_id(), + transmissionAddress, text, conversation.getSubscription_id(), bundle); + + } + public static void saveDraft(Context context, Conversation conversation) { Log.d(SMSDatabaseWrapper.class.getName(), "Saving draft: " + conversation.getText()); String[] outputs = NativeSMSDB.Outgoing.register_drafts(context, conversation.getMessage_id(), diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java index 9d8c02eb..2eb07da8 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java @@ -61,10 +61,10 @@ public void sendTextMessage(final String text, int subscriptionId, public void run() { try { byte[][] cipherText = E2EEHandler.encrypt(getApplicationContext(), - keystoreAlias, text.getBytes(StandardCharsets.UTF_8)); - String text = E2EEHandler.buildTransmissionText(cipherText[0]); + keystoreAlias, text.getBytes(StandardCharsets.UTF_8), false); + String encryptedText = E2EEHandler.buildTransmissionText(cipherText[0]); isEncrypted = true; - sendTextMessage(text, subscriptionId, threadedConversations, messageId, + sendTextMessage(encryptedText, subscriptionId, threadedConversations, messageId, cipherText[1]); } catch (Throwable e) { e.printStackTrace(); @@ -102,7 +102,7 @@ public void run() { try { Pair transmissionRequestKeyPair = E2EEHandler.buildForEncryptionRequest(getApplicationContext(), - threadedConversations.getAddress()); + threadedConversations.getAddress(), null); final String messageId = String.valueOf(System.currentTimeMillis()); Conversation conversation = new Conversation(); diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java index 9d397013..28e99b2f 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java @@ -94,22 +94,41 @@ public static boolean isAvailableInKeystore(String keystoreAlias) throws Certifi return KeystoreHelpers.isAvailableInKeystore(keystoreAlias); } - public static boolean isSelf(Context context, String keystoreAlias, byte[] publicKey) { - return hasSameAgreementKey(context, keystoreAlias, publicKey); + public static boolean samePublicKey(Context context, String keystoreAlias, byte[] publicKey) throws UnrecoverableEntryException, CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, InterruptedException { + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + ConversationsThreadsEncryption conversationsThreadsEncryption = + Datastore.datastore.conversationsThreadsEncryptionDao().fetch(keystoreAlias); + if(conversationsThreadsEncryption == null) + return false; + + byte[] currentPubKey = + Base64.decode(conversationsThreadsEncryption.getPublicKey(), Base64.NO_WRAP); + return Arrays.equals(currentPubKey, publicKey); } - public static boolean hasSameAgreementKey(Context context, String keystoreAlias, byte[] publicKey) { + public static boolean isSelf(Context context, String keystoreAlias) throws UnrecoverableEntryException, CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, InterruptedException { if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), Datastore.class, Datastore.databaseName) .enableMultiInstanceInvalidation() .build(); } + ConversationsThreadsEncryption conversationsThreadsEncryption = Datastore.datastore.conversationsThreadsEncryptionDao().fetch(keystoreAlias); + + if(conversationsThreadsEncryption == null) + return false; + + KeyPair keyPair = getKeyPairBasedVersioning(context, keystoreAlias); byte[] currentPubKey = Base64.decode(conversationsThreadsEncryption.getPublicKey(), Base64.NO_WRAP); - return Arrays.equals(currentPubKey, publicKey); + return Arrays.equals(currentPubKey, keyPair.getPublic().getEncoded()); } public static boolean canCommunicateSecurely(Context context, String keystoreAlias) throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException { @@ -119,9 +138,12 @@ public static boolean canCommunicateSecurely(Context context, String keystoreAli .enableMultiInstanceInvalidation() .build(); } - return isAvailableInKeystore(keystoreAlias) && + return (isAvailableInKeystore(keystoreAlias) && Datastore.datastore.conversationsThreadsEncryptionDao() - .findByKeystoreAlias(keystoreAlias) != null; + .findByKeystoreAlias(keystoreAlias) != null || + isAvailableInKeystore(keystoreAlias) && + Datastore.datastore.conversationsThreadsEncryptionDao() + .findByKeystoreAlias(buildForSelf(keystoreAlias)) != null); } public static PublicKey createNewKeyPair(Context context, String keystoreAlias) @@ -269,19 +291,24 @@ public static byte[] extractTransmissionKey(byte[] data) { * @throws IOException * @throws InterruptedException */ - public static Pair buildForEncryptionRequest(Context context, String address) throws Exception { - PublicKey publicKey; + public static Pair buildForEncryptionRequest(Context context, String address, + String keystoreAlias) throws Exception { int session = 0; - String keystoreAlias = deriveKeystoreAlias(address, session); - KeyPair keyPair = getKeyPairBasedVersioning(context, keystoreAlias); - if(keyPair == null || !isSelf(context, keystoreAlias, keyPair.getPublic().getEncoded())) - publicKey = createNewKeyPair(context, keystoreAlias); - else - publicKey = keyPair.getPublic(); + if(keystoreAlias == null) + keystoreAlias = deriveKeystoreAlias(address, session); + PublicKey publicKey = createNewKeyPair(context, keystoreAlias); return new Pair<>(keystoreAlias, buildDefaultPublicKey(publicKey.getEncoded())); } + public static String buildForSelf(String keystoreAlias) { + return keystoreAlias + "_self"; + } + + public static String buildForOriginal(String keystoreAlias) { + return keystoreAlias.split("_")[0]; + } + /** * Inserts the peer public key which would be used as the primary key for everything this peer. * @param context @@ -385,17 +412,18 @@ protected static String getKeystoreForRatchets(String keystoreAlias) { * @return * @throws Throwable */ - public static byte[][] encrypt(Context context, final String keystoreAlias, byte[] data) throws Throwable { + public static byte[][] encrypt(Context context, final String keystoreAlias, byte[] data, + boolean isSelf) throws Throwable { if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), Datastore.class, Datastore.databaseName) .enableMultiInstanceInvalidation() .build(); } - ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = Datastore.datastore.conversationsThreadsEncryptionDao(); - ConversationsThreadsEncryption conversationsThreadsEncryption = + ConversationsThreadsEncryption conversationsThreadsEncryption = isSelf ? + conversationsThreadsEncryptionDao.findByKeystoreAlias(buildForSelf(keystoreAlias)): conversationsThreadsEncryptionDao.findByKeystoreAlias(keystoreAlias); States states; @@ -407,7 +435,7 @@ public static byte[][] encrypt(Context context, final String keystoreAlias, byte * You are Alice, so act like it */ PublicKey publicKey = SecurityECDH.buildPublicKey( - Base64.decode(conversationsThreadsEncryption.getPublicKey(), Base64.DEFAULT)); + Base64.decode(conversationsThreadsEncryption.getPublicKey(), Base64.NO_WRAP)); keyPair = getKeyPairBasedVersioning(context, keystoreAlias); final byte[] SK = SecurityECDH.generateSecretKey(keyPair, publicKey); @@ -431,25 +459,30 @@ public static byte[][] encrypt(Context context, final String keystoreAlias, byte cipherPair.second[1]}; } - public static byte[] decrypt(Context context, final String keystoreAlias, - final byte[] cipherText, byte[] mk, byte[] _AD) throws Throwable { + public static byte[] decrypt(Context context, final String keystoreAlias, final byte[] cipherText, + byte[] mk, byte[] _AD, boolean isSelf) throws Throwable { if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), Datastore.class, Datastore.databaseName) .enableMultiInstanceInvalidation() .build(); } + if(isSelf && !keystoreAlias.endsWith("_self")) + throw new Exception("Expected " + keystoreAlias + "_self but got " + keystoreAlias); + ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = Datastore.datastore.conversationsThreadsEncryptionDao(); ConversationsThreadsEncryption conversationsThreadsEncryption = - conversationsThreadsEncryptionDao.findByKeystoreAlias(keystoreAlias); + conversationsThreadsEncryptionDao.findByKeystoreAlias(isSelf ? + buildForOriginal(keystoreAlias) : + keystoreAlias); - Headers header = new Headers(); - byte[] outputCipherText = header.deSerializeHeader(cipherText); + String keystoreAliasRatchet = getKeystoreForRatchets(keystoreAlias); States states; - final String keystoreAliasRatchet = getKeystoreForRatchets(keystoreAlias); + Headers header = new Headers(); + byte[] outputCipherText = header.deSerializeHeader(cipherText); KeyPair keyPair = getKeyPairBasedVersioning(context, keystoreAliasRatchet); if(keyPair == null) { /** @@ -457,12 +490,14 @@ public static byte[] decrypt(Context context, final String keystoreAlias, */ keyPair = getKeyPairBasedVersioning(context, keystoreAlias); PublicKey publicKey = SecurityECDH.buildPublicKey( - Base64.decode(conversationsThreadsEncryption.getPublicKey(), Base64.DEFAULT)); + Base64.decode(conversationsThreadsEncryption.getPublicKey(), Base64.NO_WRAP)); final byte[] SK = SecurityECDH.generateSecretKey(keyPair, publicKey); states = Ratchets.ratchetInitBob(new States(), SK, keyPair); } else { + Log.d(E2EEHandler.class.getName(), "Yep not null no more..."); states = new States(keyPair, conversationsThreadsEncryption.getStates()); + Log.d(E2EEHandler.class.getName(), states.getSerializedStates()); } byte[] AD = _AD == null ? From f37484de532d7a3657f53124aecb8799d3588fe3 Mon Sep 17 00:00:00 2001 From: sherlock Date: Tue, 5 Mar 2024 09:12:13 +0100 Subject: [PATCH 11/31] - update: can self encrypt and decrypt, should have some bugs but should be able to identify them later --- fastlane/metadata/android/en-US/changelogs/0.42.0.txt | 1 + smswithoutborders_libsignal-doubleratchet | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/0.42.0.txt diff --git a/fastlane/metadata/android/en-US/changelogs/0.42.0.txt b/fastlane/metadata/android/en-US/changelogs/0.42.0.txt new file mode 100644 index 00000000..3a946363 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/0.42.0.txt @@ -0,0 +1 @@ +- update: diff --git a/smswithoutborders_libsignal-doubleratchet b/smswithoutborders_libsignal-doubleratchet index b24b1587..e1896377 160000 --- a/smswithoutborders_libsignal-doubleratchet +++ b/smswithoutborders_libsignal-doubleratchet @@ -1 +1 @@ -Subproject commit b24b15876bf44dba51f376ea8545e6dfd90ee1c1 +Subproject commit e18963779a530e2bedb72715ef867cc07ff210a5 From eee93fb98e2ceedcd202abffbefbf0ab10c7b86b Mon Sep 17 00:00:00 2001 From: sherlock Date: Tue, 5 Mar 2024 18:40:20 +0100 Subject: [PATCH 12/31] - update: can now encrypt and talk to self in a single ratchet. Should update to changing the ratchet soon --- .../IncomingDataSMSBroadcastReceiver.java | 20 +++++++++------- .../IncomingTextSMSBroadcastReceiver.java | 7 ++++-- .../deku/DefaultSMS/ConversationActivity.java | 2 +- .../DefaultSMS/CustomAppCompactActivity.java | 8 ++++--- .../deku/E2EE/E2EECompactActivity.java | 23 ++++++++++++------- 5 files changed, 38 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java index 7081383d..47d3e67c 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java @@ -7,6 +7,7 @@ import android.provider.Telephony; import android.util.Base64; import android.util.Log; +import android.util.Pair; import androidx.room.Room; @@ -110,8 +111,7 @@ public void run() { if(isValidKey) { try { processForEncryptionKey(context, conversation); - } catch (NumberParseException | IOException | InterruptedException | - GeneralSecurityException | JSONException e) { + } catch (Exception e) { e.printStackTrace(); } } @@ -137,14 +137,18 @@ public void run() { } void processForEncryptionKey(Context context, Conversation conversation) throws - NumberParseException, GeneralSecurityException, IOException, InterruptedException, JSONException { + Exception { byte[] data = Base64.decode(conversation.getData(), Base64.DEFAULT); - String keystoreAlias = E2EEHandler.deriveKeystoreAlias(conversation.getAddress(), 0); + final String keystoreAlias = E2EEHandler.deriveKeystoreAlias(conversation.getAddress(), 0); byte[] extractedTransmissionKey = E2EEHandler.extractTransmissionKey(data); - - /* - * This should allow Bob to continue communicating in case (Bob == Alice) = true. - */ E2EEHandler.insertNewAgreementKeyDefault(context, extractedTransmissionKey, keystoreAlias); + + if(E2EEHandler.isSelf(context, keystoreAlias)) { + final String keystoreAliasSelf = E2EEHandler.buildForSelf(keystoreAlias); + Pair keystorePair = E2EEHandler.buildForEncryptionRequest(context, + conversation.getAddress(), keystoreAliasSelf); + byte[] transmissionKey = E2EEHandler.extractTransmissionKey(keystorePair.second); + E2EEHandler.insertNewAgreementKeyDefault(context, transmissionKey, keystoreAliasSelf); + } } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java index ba9aea31..43b22fff 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java @@ -285,8 +285,11 @@ public String processEncryptedIncoming(Context context, String address, String t if(E2EEHandler.isValidDefaultText(text)) { String keystoreAlias = E2EEHandler.deriveKeystoreAlias(address, 0); byte[] cipherText = E2EEHandler.extractTransmissionText(text); - text = new String(E2EEHandler.decrypt(context, keystoreAlias, cipherText, null, - null, false)); + boolean isSelf = E2EEHandler.isSelf(context, keystoreAlias); + Log.d(getClass().getName(), "Decrypting incoming: " + text); + text = new String(E2EEHandler.decrypt(context, isSelf ? + E2EEHandler.buildForSelf(keystoreAlias) :keystoreAlias, + cipherText, null, null, isSelf)); } return text; } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java index 4c671f61..8a25316e 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java @@ -661,7 +661,7 @@ public void onClick(View v) { final String text = smsTextView.getText().toString(); sendTextMessage(text, defaultSubscriptionId.getValue(), threadedConversations, String.valueOf(System.currentTimeMillis()), - null); + null, isSelf); smsTextView.setText(null); } } catch (Exception e) { diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java index 6fcdf250..e8b55cbd 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java @@ -86,12 +86,12 @@ protected void sendTextMessage(Conversation conversation, ThreadedConversations threadedConversations, String messageId) throws NumberParseException, InterruptedException { sendTextMessage(conversation.getText(), conversation.getSubscription_id(), - threadedConversations, messageId, null); + threadedConversations, messageId, null, false); } protected void sendTextMessage(String text, int subscriptionId, ThreadedConversations threadedConversations, String messageId, - byte[] _mk) throws NumberParseException, InterruptedException { + byte[] _mk, boolean isSelf) throws NumberParseException, InterruptedException { if(text != null) { if(messageId == null) messageId = String.valueOf(System.currentTimeMillis()); @@ -101,9 +101,11 @@ protected void sendTextMessage(String text, int subscriptionId, try { String keystoreAlias = E2EEHandler.deriveKeystoreAlias( threadedConversations.getAddress(), 0); + if(isSelf) + keystoreAlias = E2EEHandler.buildForSelf(keystoreAlias); byte[] cipherText = E2EEHandler.extractTransmissionText(text); String plainText = new String(E2EEHandler.decrypt(getApplicationContext(), - keystoreAlias, cipherText, _mk, null, false), + keystoreAlias, cipherText, _mk, null, isSelf), StandardCharsets.UTF_8); conversation.setText(plainText); } catch(Throwable e ) { diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java index 2eb07da8..27a7e926 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java @@ -34,6 +34,8 @@ public class E2EECompactActivity extends CustomAppCompactActivity { protected String keystoreAlias; + protected boolean isSelf = false; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -54,18 +56,18 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { @Override public void sendTextMessage(final String text, int subscriptionId, ThreadedConversations threadedConversations, String messageId, - final byte[] _mk) throws NumberParseException, InterruptedException { + final byte[] _mk, boolean _isSelf) throws NumberParseException, InterruptedException { if(threadedConversations.is_secured && !isEncrypted) { ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { try { byte[][] cipherText = E2EEHandler.encrypt(getApplicationContext(), - keystoreAlias, text.getBytes(StandardCharsets.UTF_8), false); + keystoreAlias, text.getBytes(StandardCharsets.UTF_8), isSelf); String encryptedText = E2EEHandler.buildTransmissionText(cipherText[0]); isEncrypted = true; - sendTextMessage(encryptedText, subscriptionId, threadedConversations, messageId, - cipherText[1]); + sendTextMessage(encryptedText, subscriptionId, threadedConversations, + messageId, cipherText[1], isSelf); } catch (Throwable e) { e.printStackTrace(); } @@ -74,7 +76,8 @@ public void run() { } else { isEncrypted = false; - super.sendTextMessage(text, subscriptionId, threadedConversations, messageId, _mk); + super.sendTextMessage(text, subscriptionId, threadedConversations, messageId, _mk, + _isSelf); } } @@ -205,10 +208,13 @@ protected void onResume() { @Override public void run() { try { - keystoreAlias = E2EEHandler.deriveKeystoreAlias(threadedConversations.getAddress(), 0); + keystoreAlias = E2EEHandler.deriveKeystoreAlias( + threadedConversations.getAddress(), 0); threadedConversations.is_secured = - E2EEHandler.canCommunicateSecurely(getApplicationContext(), keystoreAlias); + E2EEHandler.canCommunicateSecurely(getApplicationContext(), + keystoreAlias); if(threadedConversations.is_secured) { + isSelf = E2EEHandler.isSelf(getApplicationContext(), keystoreAlias); runOnUiThread(new Runnable() { @Override public void run() { @@ -217,7 +223,8 @@ public void run() { } }); } - } catch (IOException | GeneralSecurityException | NumberParseException e) { + } catch (IOException | GeneralSecurityException | NumberParseException | + InterruptedException e) { e.printStackTrace(); } } From ae70a1d8e9b6c886fa0ff52c701d2a9e7e2b3067 Mon Sep 17 00:00:00 2001 From: sherlock Date: Tue, 5 Mar 2024 18:48:43 +0100 Subject: [PATCH 13/31] - update: can now encrypt and talk to self in a single ratchet. Should update to changing the ratchet soon --- fastlane/metadata/android/en-US/changelogs/0.42.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/en-US/changelogs/0.42.0.txt b/fastlane/metadata/android/en-US/changelogs/0.42.0.txt index 3a946363..3c5092b9 100644 --- a/fastlane/metadata/android/en-US/changelogs/0.42.0.txt +++ b/fastlane/metadata/android/en-US/changelogs/0.42.0.txt @@ -1 +1 @@ -- update: +- update: can send encrypted messages to self using ratchets (but single ratchet chain at this time) From 1765511ffed6eba6223de15f1ac83d45eb1578c1 Mon Sep 17 00:00:00 2001 From: sherlock Date: Tue, 5 Mar 2024 22:18:12 +0100 Subject: [PATCH 14/31] - update: migrated database to 13, added some params to database but they don't work yet --- .../13.json | 591 ++++++++++++++++++ .../IncomingDataSMSBroadcastReceiver.java | 20 +- .../deku/DefaultSMS/ConversationActivity.java | 25 +- .../DefaultSMS/CustomAppCompactActivity.java | 8 +- .../DAO/ThreadedConversationsDao.java | 6 +- .../Conversations/ThreadedConversations.java | 11 + ...readedConversationsTemplateViewHolder.java | 83 +-- .../DefaultSMS/Models/Database/Datastore.java | 2 +- .../deku/E2EE/E2EECompactActivity.java | 47 +- .../com/afkanerd/deku/E2EE/E2EEHandler.java | 4 + 10 files changed, 703 insertions(+), 94 deletions(-) create mode 100644 app/schemas/com.afkanerd.deku.DefaultSMS.Models.Database.Datastore/13.json diff --git a/app/schemas/com.afkanerd.deku.DefaultSMS.Models.Database.Datastore/13.json b/app/schemas/com.afkanerd.deku.DefaultSMS.Models.Database.Datastore/13.json new file mode 100644 index 00000000..daa1e87a --- /dev/null +++ b/app/schemas/com.afkanerd.deku.DefaultSMS.Models.Database.Datastore/13.json @@ -0,0 +1,591 @@ +{ + "formatVersion": 1, + "database": { + "version": 13, + "identityHash": "1a131ad98d1f91a978b77d49221ec1c4", + "entities": [ + { + "tableName": "ThreadedConversations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`isSelf` INTEGER NOT NULL DEFAULT 0, `is_secured` INTEGER NOT NULL DEFAULT 0, `thread_id` TEXT NOT NULL, `address` TEXT, `msg_count` INTEGER NOT NULL, `type` INTEGER NOT NULL, `date` TEXT, `is_archived` INTEGER NOT NULL, `is_blocked` INTEGER NOT NULL, `is_shortcode` INTEGER NOT NULL, `is_read` INTEGER NOT NULL, `snippet` TEXT, `contact_name` TEXT, `formatted_datetime` TEXT, `is_mute` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`thread_id`))", + "fields": [ + { + "fieldPath": "isSelf", + "columnName": "isSelf", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "is_secured", + "columnName": "is_secured", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "thread_id", + "columnName": "thread_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "msg_count", + "columnName": "msg_count", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "is_archived", + "columnName": "is_archived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_blocked", + "columnName": "is_blocked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_shortcode", + "columnName": "is_shortcode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_read", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "snippet", + "columnName": "snippet", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contact_name", + "columnName": "contact_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "formatted_datetime", + "columnName": "formatted_datetime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "is_mute", + "columnName": "is_mute", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "thread_id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CustomKeyStore", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER NOT NULL, `keystoreAlias` TEXT, `publicKey` TEXT, `privateKey` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keystoreAlias", + "columnName": "keystoreAlias", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "publicKey", + "columnName": "publicKey", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "privateKey", + "columnName": "privateKey", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_CustomKeyStore_keystoreAlias", + "unique": true, + "columnNames": [ + "keystoreAlias" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_CustomKeyStore_keystoreAlias` ON `${TABLE_NAME}` (`keystoreAlias`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Archive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`thread_id` TEXT NOT NULL, `is_archived` INTEGER NOT NULL, PRIMARY KEY(`thread_id`))", + "fields": [ + { + "fieldPath": "thread_id", + "columnName": "thread_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "is_archived", + "columnName": "is_archived", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "thread_id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GatewayServer", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`URL` TEXT, `protocol` TEXT, `tag` TEXT, `format` TEXT, `date` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "URL", + "columnName": "URL", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "protocol", + "columnName": "protocol", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GatewayClientProjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `gatewayClientId` INTEGER NOT NULL, `name` TEXT, `binding1Name` TEXT, `binding2Name` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "gatewayClientId", + "columnName": "gatewayClientId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "binding1Name", + "columnName": "binding1Name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "binding2Name", + "columnName": "binding2Name", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ConversationsThreadsEncryption", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `keystoreAlias` TEXT, `publicKey` TEXT, `states` TEXT, `exchangeDate` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keystoreAlias", + "columnName": "keystoreAlias", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "publicKey", + "columnName": "publicKey", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "states", + "columnName": "states", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "exchangeDate", + "columnName": "exchangeDate", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_ConversationsThreadsEncryption_keystoreAlias", + "unique": true, + "columnNames": [ + "keystoreAlias" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ConversationsThreadsEncryption_keystoreAlias` ON `${TABLE_NAME}` (`keystoreAlias`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Conversation", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `message_id` TEXT, `thread_id` TEXT, `date` TEXT, `date_sent` TEXT, `type` INTEGER NOT NULL, `num_segments` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, `status` INTEGER NOT NULL, `error_code` INTEGER NOT NULL, `read` INTEGER NOT NULL, `is_encrypted` INTEGER NOT NULL, `is_key` INTEGER NOT NULL, `is_image` INTEGER NOT NULL, `formatted_date` TEXT, `address` TEXT, `text` TEXT, `data` TEXT, `_mk` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message_id", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thread_id", + "columnName": "thread_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "date_sent", + "columnName": "date_sent", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "num_segments", + "columnName": "num_segments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscription_id", + "columnName": "subscription_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "error_code", + "columnName": "error_code", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "read", + "columnName": "read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_encrypted", + "columnName": "is_encrypted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_key", + "columnName": "is_key", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_image", + "columnName": "is_image", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "formatted_date", + "columnName": "formatted_date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "_mk", + "columnName": "_mk", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Conversation_message_id", + "unique": true, + "columnNames": [ + "message_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Conversation_message_id` ON `${TABLE_NAME}` (`message_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "GatewayClient", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER NOT NULL, `hostUrl` TEXT, `username` TEXT, `password` TEXT, `port` INTEGER NOT NULL, `friendlyConnectionName` TEXT, `virtualHost` TEXT, `connectionTimeout` INTEGER NOT NULL, `prefetch_count` INTEGER NOT NULL, `heartbeat` INTEGER NOT NULL, `protocol` TEXT, `projectName` TEXT, `projectBinding` TEXT, `projectBinding2` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hostUrl", + "columnName": "hostUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "friendlyConnectionName", + "columnName": "friendlyConnectionName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "virtualHost", + "columnName": "virtualHost", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connectionTimeout", + "columnName": "connectionTimeout", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "prefetch_count", + "columnName": "prefetch_count", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "heartbeat", + "columnName": "heartbeat", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "protocol", + "columnName": "protocol", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "projectName", + "columnName": "projectName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "projectBinding", + "columnName": "projectBinding", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "projectBinding2", + "columnName": "projectBinding2", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1a131ad98d1f91a978b77d49221ec1c4')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java index 47d3e67c..cbf80488 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java @@ -52,12 +52,16 @@ public class IncomingDataSMSBroadcastReceiver extends BroadcastReceiver { Datastore databaseConnector; - public void insertThreads(Context context, Conversation conversation) { + public ThreadedConversations insertThreads(Context context, Conversation conversation, boolean isSecure, boolean isSelf) { ThreadedConversations threadedConversations = ThreadedConversations.build(context, conversation); String contactName = Contacts.retrieveContactName(context, conversation.getAddress()); threadedConversations.setContact_name(contactName); + threadedConversations.setIs_secured(isSecure); + threadedConversations.setSelf(isSelf); databaseConnector.threadedConversationsDao().insert(threadedConversations); + + return threadedConversations; } @Override @@ -106,23 +110,25 @@ public void onReceive(Context context, Intent intent) { @Override public void run() { databaseConnector.conversationDao().insert(conversation); - insertThreads(context, conversation); + boolean isSelf = false; + boolean isSecured = false; if(isValidKey) { try { - processForEncryptionKey(context, conversation); + isSelf = processForEncryptionKey(context, conversation); + isSecured = true; } catch (Exception e) { e.printStackTrace(); } } + ThreadedConversations threadedConversations = + insertThreads(context, conversation, isSecured, isSelf); Intent broadcastIntent = new Intent(DATA_DELIVER_ACTION); broadcastIntent.putExtra(Conversation.ID, messageId); broadcastIntent.putExtra(Conversation.THREAD_ID, threadId); context.sendBroadcast(broadcastIntent); - ThreadedConversations threadedConversations = - databaseConnector.threadedConversationsDao().get(threadId); if(!threadedConversations.isIs_mute()) NotificationsHandler.sendIncomingTextMessageNotification(context, conversation); @@ -136,7 +142,7 @@ public void run() { } } - void processForEncryptionKey(Context context, Conversation conversation) throws + boolean processForEncryptionKey(Context context, Conversation conversation) throws Exception { byte[] data = Base64.decode(conversation.getData(), Base64.DEFAULT); final String keystoreAlias = E2EEHandler.deriveKeystoreAlias(conversation.getAddress(), 0); @@ -149,6 +155,8 @@ void processForEncryptionKey(Context context, Conversation conversation) throws conversation.getAddress(), keystoreAliasSelf); byte[] transmissionKey = E2EEHandler.extractTransmissionKey(keystorePair.second); E2EEHandler.insertNewAgreementKeyDefault(context, transmissionKey, keystoreAliasSelf); + return true; } + return false; } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java index 8a25316e..03cbc47d 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java @@ -272,10 +272,19 @@ private void configureActivityDependencies() throws Exception { throw new Exception("No threadId nor Address supplied for activity"); } if(getIntent().hasExtra(Conversation.THREAD_ID)) { - ThreadedConversations threadedConversations = new ThreadedConversations(); - threadedConversations.setThread_id(getIntent().getStringExtra(Conversation.THREAD_ID)); - this.threadedConversations = ThreadedConversationsHandler.get(getApplicationContext(), - threadedConversations); +// ThreadedConversations threadedConversations = new ThreadedConversations(); +// threadedConversations.setThread_id(getIntent().getStringExtra(Conversation.THREAD_ID)); +// this.threadedConversations = ThreadedConversationsHandler.get(getApplicationContext(), +// threadedConversations); + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + threadedConversations = databaseConnector.threadedConversationsDao() + .get(getIntent().getStringExtra(Conversation.THREAD_ID)); + } + }); + thread.start(); + thread.join(); } else if(getIntent().hasExtra(Conversation.ADDRESS)) { this.threadedConversations = ThreadedConversationsHandler.get(getApplicationContext(), @@ -437,6 +446,7 @@ public void onChanged(PagingData conversationPagingData) { @Override public void onReceive(Context context, Intent intent) { final String messageId = intent.getStringExtra(Conversation.ID); + final String threadId = intent.getStringExtra(Conversation.THREAD_ID); ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { @@ -444,6 +454,11 @@ public void run() { .getMessage(messageId); conversation.setRead(true); conversationsViewModel.update(conversation); + threadedConversations = databaseConnector.threadedConversationsDao().get(threadId); + if(threadedConversations.isIs_secured()) { + TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); + layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); + } } }); } @@ -661,7 +676,7 @@ public void onClick(View v) { final String text = smsTextView.getText().toString(); sendTextMessage(text, defaultSubscriptionId.getValue(), threadedConversations, String.valueOf(System.currentTimeMillis()), - null, isSelf); + null); smsTextView.setText(null); } } catch (Exception e) { diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java index e8b55cbd..2807b913 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java @@ -86,12 +86,12 @@ protected void sendTextMessage(Conversation conversation, ThreadedConversations threadedConversations, String messageId) throws NumberParseException, InterruptedException { sendTextMessage(conversation.getText(), conversation.getSubscription_id(), - threadedConversations, messageId, null, false); + threadedConversations, messageId, null); } protected void sendTextMessage(String text, int subscriptionId, ThreadedConversations threadedConversations, String messageId, - byte[] _mk, boolean isSelf) throws NumberParseException, InterruptedException { + byte[] _mk) throws NumberParseException, InterruptedException { if(text != null) { if(messageId == null) messageId = String.valueOf(System.currentTimeMillis()); @@ -101,11 +101,11 @@ protected void sendTextMessage(String text, int subscriptionId, try { String keystoreAlias = E2EEHandler.deriveKeystoreAlias( threadedConversations.getAddress(), 0); - if(isSelf) + if(threadedConversations.isSelf()) keystoreAlias = E2EEHandler.buildForSelf(keystoreAlias); byte[] cipherText = E2EEHandler.extractTransmissionText(text); String plainText = new String(E2EEHandler.decrypt(getApplicationContext(), - keystoreAlias, cipherText, _mk, null, isSelf), + keystoreAlias, cipherText, _mk, null, threadedConversations.isSelf()), StandardCharsets.UTF_8); conversation.setText(plainText); } catch(Throwable e ) { diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ThreadedConversationsDao.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ThreadedConversationsDao.java index 89abf8ca..5e6aa2ba 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ThreadedConversationsDao.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ThreadedConversationsDao.java @@ -75,7 +75,8 @@ public interface ThreadedConversationsDao { "ThreadedConversations.msg_count, ThreadedConversations.is_archived, " + "ThreadedConversations.is_blocked, ThreadedConversations.is_read, " + "ThreadedConversations.is_shortcode, ThreadedConversations.contact_name, " + - "ThreadedConversations.is_mute, ThreadedConversations.is_secured " + + "ThreadedConversations.is_mute, ThreadedConversations.is_secured, " + + "ThreadedConversations.isSelf " + "FROM Conversation, ThreadedConversations WHERE " + "Conversation.type = :type AND ThreadedConversations.thread_id = Conversation.thread_id " + "ORDER BY Conversation.date DESC") @@ -87,7 +88,8 @@ public interface ThreadedConversationsDao { "Conversation.date, Conversation.type, Conversation.read, " + "0 as msg_count, ThreadedConversations.is_archived, ThreadedConversations.is_blocked, " + "ThreadedConversations.is_read, ThreadedConversations.is_shortcode, " + - "ThreadedConversations.is_mute, ThreadedConversations.is_secured " + + "ThreadedConversations.is_mute, ThreadedConversations.is_secured, " + + "ThreadedConversations.isSelf " + "FROM Conversation, ThreadedConversations WHERE " + "Conversation.type = :type AND ThreadedConversations.thread_id = Conversation.thread_id " + "ORDER BY Conversation.date DESC") diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java index 2415364f..b2b49487 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java @@ -31,6 +31,17 @@ public void setIs_secured(boolean is_secured) { this.is_secured = is_secured; } + public boolean isSelf() { + return isSelf; + } + + public void setSelf(boolean self) { + isSelf = self; + } + + @ColumnInfo(defaultValue = "0") + private boolean isSelf = false; + @ColumnInfo(defaultValue = "0") public boolean is_secured = false; @NonNull diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java index 77d1d778..9a59a34e 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java @@ -12,6 +12,7 @@ import android.graphics.drawable.Drawable; import android.provider.Telephony; import android.util.Log; +import android.util.Pair; import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -66,48 +67,50 @@ public ThreadedConversationsTemplateViewHolder(@NonNull View itemView) { public void bind(ThreadedConversations conversation, View.OnClickListener onClickListener, View.OnLongClickListener onLongClickListener, String defaultRegion) { - this.id = String.valueOf(conversation.getThread_id()); - - int contactColor = Helpers.getColor(itemView.getContext(), id); - if(conversation.getContact_name() != null && !conversation.getContact_name().isEmpty()) { - this.contactAvatar.setVisibility(View.GONE); - this.contactInitials.setVisibility(View.VISIBLE); - this.contactInitials.setAvatarInitials(conversation.getContact_name().contains(" ") ? - conversation.getContact_name() : conversation.getContact_name().substring(0, 1)); - this.contactInitials.setAvatarInitialsBackgroundColor(contactColor); - } - else { - this.contactAvatar.setVisibility(View.VISIBLE); - this.contactInitials.setVisibility(View.GONE); - Drawable drawable = contactAvatar.getDrawable(); - if (drawable == null) { - drawable = itemView.getContext().getDrawable(R.drawable.baseline_account_circle_24); + try { + this.id = String.valueOf(conversation.getThread_id()); + + int contactColor = Helpers.getColor(itemView.getContext(), id); + if (conversation.getContact_name() != null && !conversation.getContact_name().isEmpty()) { + this.contactAvatar.setVisibility(View.GONE); + this.contactInitials.setVisibility(View.VISIBLE); + this.contactInitials.setAvatarInitials(conversation.getContact_name().contains(" ") ? + conversation.getContact_name() : conversation.getContact_name().substring(0, 1)); + this.contactInitials.setAvatarInitialsBackgroundColor(contactColor); + } else { + this.contactAvatar.setVisibility(View.VISIBLE); + this.contactInitials.setVisibility(View.GONE); + Drawable drawable = contactAvatar.getDrawable(); + if (drawable == null) { + drawable = itemView.getContext().getDrawable(R.drawable.baseline_account_circle_24); + } + if (drawable != null) + drawable.setColorFilter(contactColor, PorterDuff.Mode.SRC_IN); + contactAvatar.setImageDrawable(drawable); } - if(drawable != null) - drawable.setColorFilter(contactColor, PorterDuff.Mode.SRC_IN); - contactAvatar.setImageDrawable(drawable); + if (conversation.getContact_name() != null) { + this.address.setText(conversation.getContact_name()); + } else this.address.setText(conversation.getAddress()); + + String text = conversation.getSnippet(); + + this.snippet.setText(text); + String date = Helpers.formatDate(itemView.getContext(), + Long.parseLong(conversation.getDate())); + this.date.setText(date); + this.materialCardView.setOnClickListener(onClickListener); + this.materialCardView.setOnLongClickListener(onLongClickListener); + + if (conversation.isIs_mute()) + this.muteAvatar.setVisibility(View.VISIBLE); + else + this.muteAvatar.setVisibility(View.GONE); + + // TODO: investigate new Avatar first before anything else + // this.contactInitials.setPlaceholder(itemView.getContext().getDrawable(R.drawable.round_person_24)); + } catch (Exception e) { + e.printStackTrace(); } - if(conversation.getContact_name() != null) { - this.address.setText(conversation.getContact_name()); - } - else this.address.setText(conversation.getAddress()); - - String text = conversation.getSnippet(); - - this.snippet.setText(text); - String date = Helpers.formatDate(itemView.getContext(), - Long.parseLong(conversation.getDate())); - this.date.setText(date); - this.materialCardView.setOnClickListener(onClickListener); - this.materialCardView.setOnLongClickListener(onLongClickListener); - - if(conversation.isIs_mute()) - this.muteAvatar.setVisibility(View.VISIBLE); - else - this.muteAvatar.setVisibility(View.GONE); - - // TODO: investigate new Avatar first before anything else -// this.contactInitials.setPlaceholder(itemView.getContext().getDrawable(R.drawable.round_person_24)); } public static int getViewType(int position, List items) { diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Datastore.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Datastore.java index aa1de2ad..6774336d 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Datastore.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Datastore.java @@ -43,7 +43,7 @@ ConversationsThreadsEncryption.class, Conversation.class, GatewayClient.class}, - version = 12, autoMigrations = {@AutoMigration(from = 11, to = 12)}) + version = 13, autoMigrations = {@AutoMigration(from = 12, to = 13)}) public abstract class Datastore extends RoomDatabase { public static Datastore datastore; diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java index 27a7e926..3d42d247 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java @@ -32,10 +32,6 @@ public class E2EECompactActivity extends CustomAppCompactActivity { protected ThreadedConversations threadedConversations; View securePopUpRequest; - protected String keystoreAlias; - - protected boolean isSelf = false; - @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -56,18 +52,22 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { @Override public void sendTextMessage(final String text, int subscriptionId, ThreadedConversations threadedConversations, String messageId, - final byte[] _mk, boolean _isSelf) throws NumberParseException, InterruptedException { + final byte[] _mk) throws NumberParseException, InterruptedException { if(threadedConversations.is_secured && !isEncrypted) { ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { try { + String keystoreAlias = + E2EEHandler.deriveKeystoreAlias( + threadedConversations.getAddress(), 0); byte[][] cipherText = E2EEHandler.encrypt(getApplicationContext(), - keystoreAlias, text.getBytes(StandardCharsets.UTF_8), isSelf); + keystoreAlias, text.getBytes(StandardCharsets.UTF_8), + threadedConversations.isSelf()); String encryptedText = E2EEHandler.buildTransmissionText(cipherText[0]); isEncrypted = true; sendTextMessage(encryptedText, subscriptionId, threadedConversations, - messageId, cipherText[1], isSelf); + messageId, cipherText[1]); } catch (Throwable e) { e.printStackTrace(); } @@ -76,8 +76,7 @@ public void run() { } else { isEncrypted = false; - super.sendTextMessage(text, subscriptionId, threadedConversations, messageId, _mk, - _isSelf); + super.sendTextMessage(text, subscriptionId, threadedConversations, messageId, _mk); } } @@ -86,7 +85,6 @@ public void informSecured(boolean secured) { runOnUiThread(new Runnable() { @Override public void run() { - threadedConversations.is_secured = secured; if(secured && securePopUpRequest != null) { securePopUpRequest.setVisibility(View.GONE); TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); @@ -203,32 +201,9 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { @Override protected void onResume() { super.onResume(); - if(threadedConversations != null) { - ThreadingPoolExecutor.executorService.execute(new Runnable() { - @Override - public void run() { - try { - keystoreAlias = E2EEHandler.deriveKeystoreAlias( - threadedConversations.getAddress(), 0); - threadedConversations.is_secured = - E2EEHandler.canCommunicateSecurely(getApplicationContext(), - keystoreAlias); - if(threadedConversations.is_secured) { - isSelf = E2EEHandler.isSelf(getApplicationContext(), keystoreAlias); - runOnUiThread(new Runnable() { - @Override - public void run() { - TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); - layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); - } - }); - } - } catch (IOException | GeneralSecurityException | NumberParseException | - InterruptedException e) { - e.printStackTrace(); - } - } - }); + if(threadedConversations.is_secured) { + TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); + layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); } } } diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java index 28e99b2f..7b379eb7 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java @@ -470,6 +470,7 @@ public static byte[] decrypt(Context context, final String keystoreAlias, final if(isSelf && !keystoreAlias.endsWith("_self")) throw new Exception("Expected " + keystoreAlias + "_self but got " + keystoreAlias); + Log.d(E2EEHandler.class.getName(), "Is self: " + isSelf + ":" + keystoreAlias); ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = Datastore.datastore.conversationsThreadsEncryptionDao(); ConversationsThreadsEncryption conversationsThreadsEncryption = @@ -562,10 +563,13 @@ public static void clear(Context context, String keystoreAlias) throws Certifica .build(); } removeFromKeystore(context, keystoreAlias); + removeFromKeystore(context, buildForSelf(keystoreAlias)); removeFromKeystore(context, getKeystoreForRatchets(keystoreAlias)); + removeFromKeystore(context, getKeystoreForRatchets(buildForSelf(keystoreAlias))); ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = Datastore.datastore.conversationsThreadsEncryptionDao(); conversationsThreadsEncryptionDao.delete(keystoreAlias); + conversationsThreadsEncryptionDao.delete(buildForSelf(keystoreAlias)); conversationsThreadsEncryptionDao.delete(getKeystoreForRatchets(keystoreAlias)); } From 3ac6234b7dc50cba57aa6823edb9ae30de798f73 Mon Sep 17 00:00:00 2001 From: sherlock Date: Tue, 5 Mar 2024 23:04:50 +0100 Subject: [PATCH 15/31] - update: self encrypted messaging works better now --- .../deku/DefaultSMS/ConversationActivity.java | 20 +++++++++++-------- .../Conversations/ThreadedConversations.java | 2 ++ .../deku/E2EE/E2EECompactActivity.java | 10 +++++----- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java index 03cbc47d..502da886 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java @@ -140,9 +140,6 @@ protected void onResume() { TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); layout.requestFocus(); - if(threadedConversations.is_secured) - layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); - ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { @@ -454,11 +451,6 @@ public void run() { .getMessage(messageId); conversation.setRead(true); conversationsViewModel.update(conversation); - threadedConversations = databaseConnector.threadedConversationsDao().get(threadId); - if(threadedConversations.isIs_secured()) { - TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); - layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); - } } }); } @@ -482,6 +474,18 @@ else if(this.threadedConversations.getThread_id()!= null && @Override public void onChanged(PagingData smsList) { conversationsRecyclerAdapter.submitData(getLifecycle(), smsList); + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + ThreadedConversations tc = + databaseConnector.threadedConversationsDao() + .get(threadedConversations.getThread_id()); + if(tc != null && tc.isIs_secured()) { + informSecured(tc.isIs_secured()); + threadedConversations = tc; + } + } + }); } }); } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java index b2b49487..3e8178de 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java @@ -309,6 +309,8 @@ public boolean equals(@Nullable Object obj) { threadedConversations.type == this.type && threadedConversations.msg_count == this.msg_count && threadedConversations.is_mute == this.is_mute && + threadedConversations.is_secured == this.is_secured && + threadedConversations.isSelf == this.isSelf && Objects.equals(threadedConversations.date, this.date) && Objects.equals(threadedConversations.address, this.address) && Objects.equals(threadedConversations.contact_name, this.contact_name) && diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java index 3d42d247..758077d4 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java @@ -82,12 +82,12 @@ public void run() { @Override public void informSecured(boolean secured) { + TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); runOnUiThread(new Runnable() { @Override public void run() { if(secured && securePopUpRequest != null) { securePopUpRequest.setVisibility(View.GONE); - TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); } @@ -201,9 +201,9 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { @Override protected void onResume() { super.onResume(); - if(threadedConversations.is_secured) { - TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); - layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); - } +// if(threadedConversations.is_secured) { +// TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); +// layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); +// } } } From cee650ab03e6cbf3a74c54ee690487fe99153d21 Mon Sep 17 00:00:00 2001 From: sherlock Date: Wed, 6 Mar 2024 09:35:50 +0100 Subject: [PATCH 16/31] - update: fixed first messaging thing --- .../IncomingDataSMSBroadcastReceiver.java | 18 +++-- .../deku/DefaultSMS/ConversationActivity.java | 30 +------- .../ConversationsThreadsEncryptionDao.java | 4 ++ .../deku/E2EE/E2EECompactActivity.java | 69 ++++++++++++++----- 4 files changed, 68 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java index cbf80488..c84f83cc 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java @@ -52,14 +52,22 @@ public class IncomingDataSMSBroadcastReceiver extends BroadcastReceiver { Datastore databaseConnector; - public ThreadedConversations insertThreads(Context context, Conversation conversation, boolean isSecure, boolean isSelf) { + public ThreadedConversations insertThreads(Context context, Conversation conversation, + boolean isSecure, boolean isSelf) { ThreadedConversations threadedConversations = - ThreadedConversations.build(context, conversation); - String contactName = Contacts.retrieveContactName(context, conversation.getAddress()); - threadedConversations.setContact_name(contactName); + databaseConnector.threadedConversationsDao().get(conversation.getThread_id()); + boolean available = threadedConversations != null; + if(!available) { + threadedConversations = ThreadedConversations.build(context, conversation); + String contactName = Contacts.retrieveContactName(context, conversation.getAddress()); + threadedConversations.setContact_name(contactName); + } threadedConversations.setIs_secured(isSecure); threadedConversations.setSelf(isSelf); - databaseConnector.threadedConversationsDao().insert(threadedConversations); + if(available) + databaseConnector.threadedConversationsDao().update(threadedConversations); + else + databaseConnector.threadedConversationsDao().insert(threadedConversations); return threadedConversations; } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java index 502da886..f1f5aa33 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java @@ -269,10 +269,6 @@ private void configureActivityDependencies() throws Exception { throw new Exception("No threadId nor Address supplied for activity"); } if(getIntent().hasExtra(Conversation.THREAD_ID)) { -// ThreadedConversations threadedConversations = new ThreadedConversations(); -// threadedConversations.setThread_id(getIntent().getStringExtra(Conversation.THREAD_ID)); -// this.threadedConversations = ThreadedConversationsHandler.get(getApplicationContext(), -// threadedConversations); Thread thread = new Thread(new Runnable() { @Override public void run() { @@ -288,25 +284,13 @@ else if(getIntent().hasExtra(Conversation.ADDRESS)) { getIntent().getStringExtra(Conversation.ADDRESS)); } - final String address = this.threadedConversations.getAddress(); - Log.d(getClass().getName(), "Address: " + address); -// this.threadedConversations.setAddress( -// Helpers.getFormatCompleteNumber(address, defaultUserCountry)); -// this.threadedConversations.setAddress( -// Helpers.getFormatCompleteNumber(getApplicationContext(), address, defaultUserCountry)); String contactName = Contacts.retrieveContactName(getApplicationContext(), this.threadedConversations.getAddress()); - Log.d(getClass().getName(), "Contact: " + contactName); -// if(contactName == null) { -// this.threadedConversations.setContact_name(Helpers.getFormatNationalNumber(address, -// defaultUserCountry )); -// } else { -// this.threadedConversations.setContact_name(contactName); -// } this.threadedConversations.setContact_name(contactName); setEncryptionThreadedConversations(this.threadedConversations); isShortCode = Helpers.isShortCode(this.threadedConversations); + attachObservers(); } int searchPointerPosition; @@ -474,18 +458,6 @@ else if(this.threadedConversations.getThread_id()!= null && @Override public void onChanged(PagingData smsList) { conversationsRecyclerAdapter.submitData(getLifecycle(), smsList); - ThreadingPoolExecutor.executorService.execute(new Runnable() { - @Override - public void run() { - ThreadedConversations tc = - databaseConnector.threadedConversationsDao() - .get(threadedConversations.getThread_id()); - if(tc != null && tc.isIs_secured()) { - informSecured(tc.isIs_secured()); - threadedConversations = tc; - } - } - }); } }); } diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionDao.java b/app/src/main/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionDao.java index f92145f8..1e6fc047 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionDao.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionDao.java @@ -1,6 +1,7 @@ package com.afkanerd.deku.E2EE; +import androidx.lifecycle.LiveData; import androidx.room.Dao; import androidx.room.Insert; import androidx.room.OnConflictStrategy; @@ -12,6 +13,9 @@ @Dao public interface ConversationsThreadsEncryptionDao { + @Query("SELECT * FROM ConversationsThreadsEncryption WHERE keystoreAlias = :keystoreAlias") + LiveData fetchLiveData(String keystoreAlias); + @Insert(onConflict = OnConflictStrategy.REPLACE) long insert(ConversationsThreadsEncryption conversationsThreadsEncryption); diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java index 758077d4..476678e3 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java @@ -3,6 +3,7 @@ import android.os.Bundle; import android.provider.Telephony; import android.util.Base64; +import android.util.Log; import android.util.Pair; import android.view.MenuItem; import android.view.View; @@ -12,10 +13,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.lifecycle.Observer; import com.afkanerd.deku.DefaultSMS.CustomAppCompactActivity; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; import com.afkanerd.deku.DefaultSMS.Models.SIMHandler; import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper; import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; @@ -26,18 +29,60 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableEntryException; +import java.security.cert.CertificateException; public class E2EECompactActivity extends CustomAppCompactActivity { protected ThreadedConversations threadedConversations; View securePopUpRequest; + boolean isEncrypted = false; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } - boolean isEncrypted = false; + protected void attachObservers() { + try { + String keystoreAlias = + E2EEHandler.deriveKeystoreAlias(threadedConversations.getAddress(), 0); + databaseConnector.conversationsThreadsEncryptionDao().fetchLiveData(keystoreAlias) + .observe(this, new Observer() { + @Override + public void onChanged(ConversationsThreadsEncryption conversationsThreadsEncryption) { + Log.d(getClass().getName(), "Encryption changed!"); + if(conversationsThreadsEncryption != null) { + threadedConversations.setIs_secured(true); + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + try { + threadedConversations.setSelf( + E2EEHandler.isSelf(getApplicationContext(), keystoreAlias)); + runOnUiThread(new Runnable() { + @Override + public void run() { + informSecured(true); + } + }); + } catch (UnrecoverableEntryException | CertificateException | + KeyStoreException | IOException | + NoSuchAlgorithmException | InterruptedException e) { + e.printStackTrace(); + } + } + }); + } + } + }); + } catch (NumberParseException e) { + e.printStackTrace(); + } + } + /** * @@ -83,16 +128,10 @@ public void run() { @Override public void informSecured(boolean secured) { TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); - runOnUiThread(new Runnable() { - @Override - public void run() { - if(secured && securePopUpRequest != null) { - securePopUpRequest.setVisibility(View.GONE); - layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); - } - - } - }); + if(secured && securePopUpRequest != null) { + securePopUpRequest.setVisibility(View.GONE); + layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); + } } protected void sendDataMessage(ThreadedConversations threadedConversations) { @@ -198,12 +237,4 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { return super.onOptionsItemSelected(item); } - @Override - protected void onResume() { - super.onResume(); -// if(threadedConversations.is_secured) { -// TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); -// layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); -// } - } } From 7515fbf72a3055de565cdccb33bb7811c477443b Mon Sep 17 00:00:00 2001 From: sherlock Date: Thu, 7 Mar 2024 20:00:22 +0100 Subject: [PATCH 17/31] - update: improved UX for key exchange --- .../IncomingTextSMSBroadcastReceiver.java | 4 +- .../deku/DefaultSMS/ConversationActivity.java | 6 ++ .../Fragments/ModalSheetFragment.kt | 38 +++++++++ .../deku/E2EE/E2EECompactActivity.java | 24 +++++- .../com/afkanerd/deku/E2EE/E2EEHandler.java | 3 +- .../res/layout/activity_conversations.xml | 6 +- .../main/res/layout/fragment_modalsheet.xml | 12 +++ .../res/layout/secure_model_sheet_layout.xml | 78 +++++++++++++++++++ app/src/main/res/values-fr/strings.xml | 4 + app/src/main/res/values/strings.xml | 7 ++ app/src/main/res/values/styles.xml | 3 +- .../java/com/afkanerd/deku/TemplateTest.kt | 13 ++++ 12 files changed, 188 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/ModalSheetFragment.kt create mode 100644 app/src/main/res/layout/fragment_modalsheet.xml create mode 100644 app/src/main/res/layout/secure_model_sheet_layout.xml create mode 100644 app/src/test/java/com/afkanerd/deku/TemplateTest.kt diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java index 43b22fff..6523c222 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java @@ -79,7 +79,8 @@ public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Telephony.Sms.Intents.SMS_DELIVER_ACTION)) { if (getResultCode() == Activity.RESULT_OK) { try { - final String[] regIncomingOutput = NativeSMSDB.Incoming.register_incoming_text(context, intent); + final String[] regIncomingOutput = + NativeSMSDB.Incoming.register_incoming_text(context, intent); if(regIncomingOutput != null) { final String messageId = regIncomingOutput[NativeSMSDB.MESSAGE_ID]; final String body = regIncomingOutput[NativeSMSDB.BODY]; @@ -237,7 +238,6 @@ public void insertThreads(Context context, Conversation conversation) { public void insertConversation(Context context, String address, String messageId, String threadId, String body, int subscriptionId, String date, String dateSent) { - Conversation conversation = new Conversation(); conversation.setMessage_id(messageId); conversation.setThread_id(threadId); diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java index f1f5aa33..c4842f2e 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java @@ -101,6 +101,7 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_conversations); // test(); + toolbar = (Toolbar) findViewById(R.id.conversation_toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -118,6 +119,11 @@ protected void onCreate(Bundle savedInstanceState) { } } + @Override + protected void onStart() { + super.onStart(); + } + public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { // Always call the superclass so it can restore the view hierarchy. super.onRestoreInstanceState(savedInstanceState); diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/ModalSheetFragment.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/ModalSheetFragment.kt new file mode 100644 index 00000000..4260544f --- /dev/null +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/ModalSheetFragment.kt @@ -0,0 +1,38 @@ +package com.afkanerd.deku.DefaultSMS.Fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.afkanerd.deku.DefaultSMS.R +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialogFragment + +class ModalSheetFragment : BottomSheetDialogFragment() { + + lateinit var bottomSheetBehavior: BottomSheetBehavior + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_modalsheet, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val bottomSheet = view.findViewById(R.id.conversations_bottom_sheet_view_id) + + // Get the BottomSheetBehavior instance + bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet) + bottomSheetBehavior.isFitToContents = true + bottomSheetBehavior.isDraggable = true + bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED + } + + companion object { + const val TAG = "ModalBottomSheet" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java index 476678e3..d9e1983e 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java @@ -1,5 +1,10 @@ package com.afkanerd.deku.E2EE; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.provider.Telephony; import android.util.Base64; @@ -8,14 +13,17 @@ import android.view.MenuItem; import android.view.View; import android.widget.Button; +import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.lifecycle.Observer; import com.afkanerd.deku.DefaultSMS.CustomAppCompactActivity; +import com.afkanerd.deku.DefaultSMS.Fragments.ModalSheetFragment; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; @@ -23,11 +31,14 @@ import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper; import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import com.afkanerd.deku.DefaultSMS.R; +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.textfield.TextInputLayout; import com.google.i18n.phonenumbers.NumberParseException; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitOption; import java.security.GeneralSecurityException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -40,9 +51,12 @@ public class E2EECompactActivity extends CustomAppCompactActivity { View securePopUpRequest; boolean isEncrypted = false; + ModalSheetFragment modalSheetFragment; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + modalSheetFragment = new ModalSheetFragment(); + modalSheetFragment.show(getSupportFragmentManager(), ModalSheetFragment.TAG); } protected void attachObservers() { @@ -53,7 +67,6 @@ protected void attachObservers() { .observe(this, new Observer() { @Override public void onChanged(ConversationsThreadsEncryption conversationsThreadsEncryption) { - Log.d(getClass().getName(), "Encryption changed!"); if(conversationsThreadsEncryption != null) { threadedConversations.setIs_secured(true); ThreadingPoolExecutor.executorService.execute(new Runnable() { @@ -221,8 +234,6 @@ protected void onStart() { super.onStart(); securePopUpRequest = findViewById(R.id.conversations_request_secure_pop_layout); setSecurePopUpRequest(); -// if(!SettingsHandler.alertNotEncryptedCommunicationDisabled(getApplicationContext())) -// securePopUpRequest.setVisibility(View.VISIBLE); } @Override @@ -237,4 +248,11 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { return super.onOptionsItemSelected(item); } + + public void clickPrivacyPolicy(View view) { + String url = getString(R.string.conversations_secure_conversation_request_information_deku_encryption_read_more); + Intent shareIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + startActivity(shareIntent); + } + } diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java index 7b379eb7..045c9be3 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java @@ -81,6 +81,7 @@ public static String deriveKeystoreAlias(String address, int mode) throws Number } public static String getAddressFromKeystore(String keystoreAlias) { + keystoreAlias = buildForOriginal(keystoreAlias); String decodedAlias = new String(Base64.decode(keystoreAlias, Base64.NO_WRAP), StandardCharsets.UTF_8); return "+" + decodedAlias.split("_")[0]; @@ -306,7 +307,7 @@ public static String buildForSelf(String keystoreAlias) { } public static String buildForOriginal(String keystoreAlias) { - return keystoreAlias.split("_")[0]; + return keystoreAlias.endsWith("_self") ? keystoreAlias.split("_")[0] : keystoreAlias; } /** diff --git a/app/src/main/res/layout/activity_conversations.xml b/app/src/main/res/layout/activity_conversations.xml index 0be010a4..ef71b414 100644 --- a/app/src/main/res/layout/activity_conversations.xml +++ b/app/src/main/res/layout/activity_conversations.xml @@ -74,9 +74,9 @@ + app:layout_constraintStart_toStartOf="parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_modalsheet.xml b/app/src/main/res/layout/fragment_modalsheet.xml new file mode 100644 index 00000000..a0bceba8 --- /dev/null +++ b/app/src/main/res/layout/fragment_modalsheet.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/secure_model_sheet_layout.xml b/app/src/main/res/layout/secure_model_sheet_layout.xml new file mode 100644 index 00000000..ae724bd5 --- /dev/null +++ b/app/src/main/res/layout/secure_model_sheet_layout.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + +