show unknown otr fingerprint warining

This commit is contained in:
Daniel Gultsch 2014-02-16 16:32:15 +01:00
parent 42c4c1789a
commit 9c7cacdbdd
14 changed files with 549 additions and 176 deletions

View file

@ -42,24 +42,24 @@ public final class R {
public static final int section_header=0x7f02000d; public static final int section_header=0x7f02000d;
} }
public static final class id { public static final class id {
public static final int account_confirm_password_desc=0x7f0a0019; public static final int account_confirm_password_desc=0x7f0a001c;
public static final int account_delete=0x7f0a002d; public static final int account_delete=0x7f0a0035;
public static final int account_disable=0x7f0a002e; public static final int account_disable=0x7f0a0036;
public static final int account_enable=0x7f0a002f; public static final int account_enable=0x7f0a0037;
public static final int account_jid=0x7f0a0000; public static final int account_jid=0x7f0a0000;
public static final int account_list=0x7f0a0022; public static final int account_list=0x7f0a0027;
public static final int account_password=0x7f0a0016; public static final int account_password=0x7f0a0019;
public static final int account_password_confirm2=0x7f0a001a; public static final int account_password_confirm2=0x7f0a001d;
public static final int account_status=0x7f0a0002; public static final int account_status=0x7f0a0002;
public static final int account_usetls=0x7f0a0017; public static final int account_usetls=0x7f0a001a;
public static final int action_accounts=0x7f0a002a; public static final int action_accounts=0x7f0a002f;
public static final int action_add=0x7f0a0026; public static final int action_add=0x7f0a002b;
public static final int action_add_account=0x7f0a002c; public static final int action_add_account=0x7f0a0034;
public static final int action_archive=0x7f0a0029; public static final int action_archive=0x7f0a002e;
public static final int action_details=0x7f0a0028; public static final int action_details=0x7f0a002d;
public static final int action_refresh_contacts=0x7f0a0030; public static final int action_refresh_contacts=0x7f0a0038;
public static final int action_security=0x7f0a0027; public static final int action_security=0x7f0a002c;
public static final int action_settings=0x7f0a002b; public static final int action_settings=0x7f0a0030;
public static final int contactList=0x7f0a0006; public static final int contactList=0x7f0a0006;
public static final int contact_display_name=0x7f0a0008; public static final int contact_display_name=0x7f0a0008;
public static final int contact_jid=0x7f0a0009; public static final int contact_jid=0x7f0a0009;
@ -76,21 +76,29 @@ public final class R {
public static final int details_jidbox=0x7f0a000f; public static final int details_jidbox=0x7f0a000f;
public static final int details_receive_presence=0x7f0a0014; public static final int details_receive_presence=0x7f0a0014;
public static final int details_send_presence=0x7f0a0013; public static final int details_send_presence=0x7f0a0013;
public static final int edit_account_register_new=0x7f0a0018; public static final int edit_account_register_new=0x7f0a001b;
public static final int list=0x7f0a0020; public static final int encryption_choice_none=0x7f0a0031;
public static final int message_body=0x7f0a0024; public static final int encryption_choice_otr=0x7f0a0032;
public static final int message_photo=0x7f0a0023; public static final int encryption_choice_pgp=0x7f0a0033;
public static final int message_time=0x7f0a0025; public static final int list=0x7f0a0025;
public static final int messages_view=0x7f0a001e; public static final int message_body=0x7f0a0029;
public static final int message_photo=0x7f0a0028;
public static final int message_time=0x7f0a002a;
public static final int messages_view=0x7f0a0021;
public static final int new_conversation_search=0x7f0a0004; public static final int new_conversation_search=0x7f0a0004;
public static final int new_fingerprint=0x7f0a0022;
public static final int otr_fingerprint=0x7f0a0023;
public static final int progressBar1=0x7f0a0003; public static final int progressBar1=0x7f0a0003;
public static final int selected_conversation=0x7f0a0021; public static final int selected_conversation=0x7f0a0026;
public static final int slidingpanelayout=0x7f0a001f; public static final int slidingpanelayout=0x7f0a0024;
public static final int textSendButton=0x7f0a001d; public static final int textSendButton=0x7f0a0020;
public static final int textView1=0x7f0a0015; public static final int textView1=0x7f0a0018;
public static final int textView2=0x7f0a0001; public static final int textView2=0x7f0a0001;
public static final int textinput=0x7f0a001c; public static final int textinput=0x7f0a001f;
public static final int textsend=0x7f0a001b; public static final int textsend=0x7f0a001e;
public static final int verify_otr_fingerprint=0x7f0a0016;
public static final int verify_otr_jid=0x7f0a0015;
public static final int verify_otr_yourprint=0x7f0a0017;
} }
public static final class layout { public static final class layout {
public static final int account_row=0x7f030000; public static final int account_row=0x7f030000;
@ -98,18 +106,20 @@ public final class R {
public static final int contact=0x7f030002; public static final int contact=0x7f030002;
public static final int conversation_list_row=0x7f030003; public static final int conversation_list_row=0x7f030003;
public static final int dialog_contact_details=0x7f030004; public static final int dialog_contact_details=0x7f030004;
public static final int edit_account_dialog=0x7f030005; public static final int dialog_verify_otr=0x7f030005;
public static final int fragment_conversation=0x7f030006; public static final int edit_account_dialog=0x7f030006;
public static final int fragment_conversations_overview=0x7f030007; public static final int fragment_conversation=0x7f030007;
public static final int manage_accounts=0x7f030008; public static final int fragment_conversations_overview=0x7f030008;
public static final int message_recieved=0x7f030009; public static final int manage_accounts=0x7f030009;
public static final int message_sent=0x7f03000a; public static final int message_recieved=0x7f03000a;
public static final int message_sent=0x7f03000b;
} }
public static final class menu { public static final class menu {
public static final int conversations=0x7f090000; public static final int conversations=0x7f090000;
public static final int manageaccounts=0x7f090001; public static final int encryption_choices=0x7f090001;
public static final int manageaccounts_context=0x7f090002; public static final int manageaccounts=0x7f090002;
public static final int newconversation=0x7f090003; public static final int manageaccounts_context=0x7f090003;
public static final int newconversation=0x7f090004;
} }
public static final class string { public static final class string {
public static final int action_accounts=0x7f070003; public static final int action_accounts=0x7f070003;

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:text="Jabber ID"
android:textColor="#33B5E5"
android:textSize="20sp"/>
<TextView
android:id="@+id/verify_otr_jid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:text="julia@jabber.example.com"
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:text="OTR fingerprint"
android:textColor="#33B5E5"
android:textSize="20sp"/>
<TextView
android:id="@+id/verify_otr_fingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8"
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:text="Your fingerprint"
android:textColor="#33B5E5"
android:textSize="20sp"/>
<TextView
android:id="@+id/verify_otr_yourprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8"
android:textSize="14sp" />
</LinearLayout>

View file

@ -53,5 +53,35 @@
android:transcriptMode="alwaysScroll" android:transcriptMode="alwaysScroll"
android:listSelector="@android:color/transparent"> android:listSelector="@android:color/transparent">
</ListView> </ListView>
<LinearLayout
android:id="@+id/new_fingerprint"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignTop="@+id/messages_view"
android:background="#cc0000"
android:orientation="vertical"
android:visibility="gone"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Unknown OTR Fingerprint"
android:textColor="#eee"
android:textStyle="bold"
android:padding="8dp"
android:textSize="20sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/otr_fingerprint"
android:textColor="#eee"
android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8"
android:paddingLeft="8dp"
android:paddingBottom="8dp"
android:textSize="14sp"/>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<group android:checkableBehavior="single">
<item
android:id="@+id/encryption_choice_none"
android:title="Plain text"
/>
<item
android:id="@+id/encryption_choice_otr"
android:title="OTR"
/>
<item
android:id="@+id/encryption_choice_pgp"
android:title="OpenPGP"/>
</group>
</menu>

View file

@ -124,6 +124,10 @@ public class OtrEngine implements OtrEngineHost {
return null; return null;
} }
public PublicKey getPublicKey() {
return this.keyPair.getPublic();
}
@Override @Override
public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException { public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException {
if (this.keyPair==null) { if (this.keyPair==null) {

View file

@ -1,5 +1,10 @@
package de.gultsch.chat.entities; package de.gultsch.chat.entities;
import java.security.interfaces.DSAPublicKey;
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
import net.java.otr4j.crypto.OtrCryptoException;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -49,6 +54,8 @@ public class Account extends AbstractEntity{
transient OtrEngine otrEngine = null; transient OtrEngine otrEngine = null;
transient XmppConnection xmppConnection = null; transient XmppConnection xmppConnection = null;
private String otrFingerprint;
public Account() { public Account() {
this.uuid = "0"; this.uuid = "0";
} }
@ -177,4 +184,21 @@ public class Account extends AbstractEntity{
public String getFullJid() { public String getFullJid() {
return this.getJid()+"/"+this.resource; return this.getJid()+"/"+this.resource;
} }
public String getOtrFingerprint() {
if (this.otrFingerprint == null) {
try {
DSAPublicKey pubkey = (DSAPublicKey) this.otrEngine.getPublicKey();
StringBuilder builder = new StringBuilder(new OtrCryptoEngineImpl().getFingerprint(pubkey));
builder.insert(8, " ");
builder.insert(17, " ");
builder.insert(26, " ");
builder.insert(35, " ");
this.otrFingerprint = builder.toString();
} catch (OtrCryptoException e) {
}
}
return this.otrFingerprint;
}
} }

View file

@ -1,7 +1,13 @@
package de.gultsch.chat.entities; package de.gultsch.chat.entities;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashSet;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
@ -16,7 +22,7 @@ public class Contact extends AbstractEntity implements Serializable {
public static final String SUBSCRIPTION = "subscription"; public static final String SUBSCRIPTION = "subscription";
public static final String SYSTEMACCOUNT = "systemaccount"; public static final String SYSTEMACCOUNT = "systemaccount";
public static final String PHOTOURI = "photouri"; public static final String PHOTOURI = "photouri";
public static final String OPENPGPKEY = "pgpkey"; public static final String KEYS = "pgpkey";
public static final String PRESENCES = "presences"; public static final String PRESENCES = "presences";
public static final String ACCOUNT = "accountUuid"; public static final String ACCOUNT = "accountUuid";
@ -26,7 +32,7 @@ public class Contact extends AbstractEntity implements Serializable {
protected String subscription; protected String subscription;
protected String systemAccount; protected String systemAccount;
protected String photoUri; protected String photoUri;
protected String openPGPKey; protected JSONObject keys;
protected Presences presences = new Presences(); protected Presences presences = new Presences();
protected Account account; protected Account account;
@ -45,7 +51,7 @@ public class Contact extends AbstractEntity implements Serializable {
public Contact(String uuid, String account, String displayName, String jid, public Contact(String uuid, String account, String displayName, String jid,
String subscription, String photoUri, String systemAccount, String subscription, String photoUri, String systemAccount,
String pgpKey,String presences) { String keys, String presences) {
this.uuid = uuid; this.uuid = uuid;
this.accountUuid = account; this.accountUuid = account;
this.displayName = displayName; this.displayName = displayName;
@ -53,7 +59,14 @@ public class Contact extends AbstractEntity implements Serializable {
this.subscription = subscription; this.subscription = subscription;
this.photoUri = photoUri; this.photoUri = photoUri;
this.systemAccount = systemAccount; this.systemAccount = systemAccount;
this.openPGPKey = pgpKey; if (keys == null) {
keys = "";
}
try {
this.keys = new JSONObject(keys);
} catch (JSONException e) {
this.keys = new JSONObject();
}
this.presences = Presences.fromJsonString(presences); this.presences = Presences.fromJsonString(presences);
} }
@ -84,7 +97,7 @@ public class Contact extends AbstractEntity implements Serializable {
values.put(SUBSCRIPTION, subscription); values.put(SUBSCRIPTION, subscription);
values.put(SYSTEMACCOUNT, systemAccount); values.put(SYSTEMACCOUNT, systemAccount);
values.put(PHOTOURI, photoUri); values.put(PHOTOURI, photoUri);
values.put(OPENPGPKEY, openPGPKey); values.put(KEYS, keys.toString());
values.put(PRESENCES, presences.toJsonString()); values.put(PRESENCES, presences.toJsonString());
return values; return values;
} }
@ -97,7 +110,7 @@ public class Contact extends AbstractEntity implements Serializable {
cursor.getString(cursor.getColumnIndex(SUBSCRIPTION)), cursor.getString(cursor.getColumnIndex(SUBSCRIPTION)),
cursor.getString(cursor.getColumnIndex(PHOTOURI)), cursor.getString(cursor.getColumnIndex(PHOTOURI)),
cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)), cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)),
cursor.getString(cursor.getColumnIndex(OPENPGPKEY)), cursor.getString(cursor.getColumnIndex(KEYS)),
cursor.getString(cursor.getColumnIndex(PRESENCES))); cursor.getString(cursor.getColumnIndex(PRESENCES)));
} }
@ -173,4 +186,36 @@ public class Contact extends AbstractEntity implements Serializable {
public String getSystemAccount() { public String getSystemAccount() {
return systemAccount; return systemAccount;
} }
public Set<String> getOtrFingerprints() {
Set<String> set = new HashSet<String>();
try {
if (this.keys.has("otr_fingerprints")) {
JSONArray fingerprints = this.keys.getJSONArray("otr_fingerprints");
for (int i = 0; i < fingerprints.length(); ++i) {
set.add(fingerprints.getString(i));
}
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return set;
}
public void addOtrFingerprint(String print) {
try {
JSONArray fingerprints;
if (!this.keys.has("otr_fingerprints")) {
fingerprints = new JSONArray();
} else {
fingerprints = this.keys.getJSONArray("otr_fingerprints");
}
fingerprints.put(print);
this.keys.put("otr_fingerprints", fingerprints);
} catch (JSONException e) {
}
}
} }

View file

@ -1,5 +1,6 @@
package de.gultsch.chat.entities; package de.gultsch.chat.entities;
import java.security.interfaces.DSAPublicKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -7,6 +8,8 @@ import de.gultsch.chat.crypto.OtrEngine;
import de.gultsch.chat.xmpp.XmppConnection; import de.gultsch.chat.xmpp.XmppConnection;
import net.java.otr4j.OtrException; import net.java.otr4j.OtrException;
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
import net.java.otr4j.crypto.OtrCryptoException;
import net.java.otr4j.session.SessionID; import net.java.otr4j.session.SessionID;
import net.java.otr4j.session.SessionImpl; import net.java.otr4j.session.SessionImpl;
import net.java.otr4j.session.SessionStatus; import net.java.otr4j.session.SessionStatus;
@ -51,7 +54,10 @@ public class Conversation extends AbstractEntity {
private transient Contact contact; private transient Contact contact;
private transient SessionImpl otrSession; private transient SessionImpl otrSession;
private transient String foreignOtrPresence;
private transient String otrFingerprint = null;
public int nextMessageEncryption = Message.ENCRYPTION_NONE;
public Conversation(String name, Account account, public Conversation(String name, Account account,
String contactJid, int mode) { String contactJid, int mode) {
@ -209,6 +215,11 @@ public class Conversation extends AbstractEntity {
Log.d("xmppService","starting otr session with "+presence); Log.d("xmppService","starting otr session with "+presence);
SessionID sessionId = new SessionID(this.getContactJid(),presence,"xmpp"); SessionID sessionId = new SessionID(this.getContactJid(),presence,"xmpp");
this.otrSession = new SessionImpl(sessionId, getAccount().getOtrEngine(context)); this.otrSession = new SessionImpl(sessionId, getAccount().getOtrEngine(context));
try {
this.otrSession.startSession();
} catch (OtrException e) {
Log.d("xmppServic","couldnt start otr");
}
} }
public SessionImpl getOtrSession() { public SessionImpl getOtrSession() {
@ -225,9 +236,36 @@ public class Conversation extends AbstractEntity {
this.otrSession.endSession(); this.otrSession.endSession();
} }
} }
this.resetOtrSession();
} }
public boolean hasOtrSession() { public boolean hasValidOtrSession() {
return (this.otrSession!=null); if (this.otrSession == null) {
return false;
} else {
String foreignPresence = this.otrSession.getSessionID().getUserID();
if (!getContact().getPresences().containsKey(foreignPresence)) {
this.resetOtrSession();
return false;
}
return true;
}
}
public String getOtrFingerprint() {
if (this.otrFingerprint == null) {
try {
DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey();
StringBuilder builder = new StringBuilder(new OtrCryptoEngineImpl().getFingerprint(remotePubKey));
builder.insert(8, " ");
builder.insert(17, " ");
builder.insert(26, " ");
builder.insert(35, " ");
this.otrFingerprint = builder.toString();
} catch (OtrCryptoException e) {
}
}
return this.otrFingerprint;
} }
} }

View file

@ -132,4 +132,8 @@ public class Message extends AbstractEntity {
this.timeSent = time; this.timeSent = time;
} }
public void setEncryption(int encryption) {
this.encryption = encryption;
}
} }

View file

@ -56,7 +56,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL("create table " + Contact.TABLENAME + "(" + Contact.UUID db.execSQL("create table " + Contact.TABLENAME + "(" + Contact.UUID
+ " TEXT PRIMARY KEY, " + Contact.ACCOUNT + " TEXT, " + " TEXT PRIMARY KEY, " + Contact.ACCOUNT + " TEXT, "
+ Contact.DISPLAYNAME + " TEXT," + Contact.JID + " TEXT," + Contact.DISPLAYNAME + " TEXT," + Contact.JID + " TEXT,"
+ Contact.PRESENCES + " TEXT, " + Contact.OPENPGPKEY + Contact.PRESENCES + " TEXT, " + Contact.KEYS
+ " TEXT," + Contact.PHOTOURI + " TEXT," + Contact.SUBSCRIPTION + " TEXT," + Contact.PHOTOURI + " TEXT," + Contact.SUBSCRIPTION
+ " TEXT," + Contact.SYSTEMACCOUNT + " NUMBER, " + " TEXT," + Contact.SYSTEMACCOUNT + " NUMBER, "
+ "FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES " + "FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
@ -228,10 +228,6 @@ public class DatabaseBackend extends SQLiteOpenHelper {
} }
} }
public void mergeMessageIntoConversation(Message message) {
// select counterpart,body,(timeSent/1000)-180 as min,(timeSent/1000)+180 as max from messages where min<1392035670 and max>1392035670;
}
public List<Contact> getContacts(Account account) { public List<Contact> getContacts(Account account) {
List<Contact> list = new ArrayList<Contact>(); List<Contact> list = new ArrayList<Contact>();
SQLiteDatabase db = this.getReadableDatabase(); SQLiteDatabase db = this.getReadableDatabase();

View file

@ -132,7 +132,7 @@ public class XmppConnectionService extends Service {
} else { } else {
counterPart = fullJid; counterPart = fullJid;
if ((runOtrCheck) && body.startsWith("?OTR")) { if ((runOtrCheck) && body.startsWith("?OTR")) {
if (!conversation.hasOtrSession()) { if (!conversation.hasValidOtrSession()) {
conversation.startOtrSession( conversation.startOtrSession(
getApplicationContext(), fromParts[1]); getApplicationContext(), fromParts[1]);
} }
@ -162,6 +162,9 @@ public class XmppConnectionService extends Service {
if (convChangedListener!=null) { if (convChangedListener!=null) {
convChangedListener.onConversationListChanged(); convChangedListener.onConversationListChanged();
} }
} else if ((before != after) && (after == SessionStatus.FINISHED)) {
conversation.resetOtrSession();
Log.d(LOGTAG,"otr session stoped");
} }
} catch (Exception e) { } catch (Exception e) {
Log.d(LOGTAG, "error receiving otr. resetting"); Log.d(LOGTAG, "error receiving otr. resetting");
@ -327,60 +330,17 @@ public class XmppConnectionService extends Service {
return connection; return connection;
} }
private void startOtrSession(Conversation conv) { public void sendMessage(Account account, Message message, String presence) {
Set<String> presences = conv.getContact().getPresences()
.keySet();
if (presences.size() == 0) {
Log.d(LOGTAG, "counter part isnt online. cant use otr");
return;
} else if (presences.size() == 1) {
conv.startOtrSession(getApplicationContext(),
(String) presences.toArray()[0]);
try {
conv.getOtrSession().startSession();
} catch (OtrException e) {
Log.d(LOGTAG, "couldnt actually start");
}
} else {
String latestCounterpartPresence = null;
List<Message> messages = conv.getMessages();
for (int i = messages.size() - 1; i >= 0; --i) {
if (messages.get(i).getStatus() == Message.STATUS_RECIEVED) {
String[] parts = messages.get(i).getCounterpart()
.split("/");
if (parts.length == 2) {
latestCounterpartPresence = parts[1];
break;
}
}
}
if (presences.contains(latestCounterpartPresence)) {
conv.startOtrSession(getApplicationContext(),
latestCounterpartPresence);
try {
conv.getOtrSession().startSession();
} catch (OtrException e) {
// TODO Auto-generated catch block
Log.d(LOGTAG, "couldnt actually start");
}
} else {
Log.d(LOGTAG,
"could not decide where to send otr connection to");
}
}
}
public void sendMessage(Account account, Message message) {
Conversation conv = message.getConversation(); Conversation conv = message.getConversation();
boolean saveInDb = false; boolean saveInDb = false;
boolean addToConversation = false; boolean addToConversation = false;
if (account.getStatus() == Account.STATUS_ONLINE) { if (account.getStatus() == Account.STATUS_ONLINE) {
MessagePacket packet; MessagePacket packet;
if (message.getEncryption() == Message.ENCRYPTION_OTR) { if (message.getEncryption() == Message.ENCRYPTION_OTR) {
if (!conv.hasOtrSession()) { if (!conv.hasValidOtrSession()) {
//starting otr session. messages will be send later //starting otr session. messages will be send later
startOtrSession(conv); conv.startOtrSession(getApplicationContext(), presence);
} else { } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED){
//otr session aleary exists, creating message packet accordingly //otr session aleary exists, creating message packet accordingly
packet = prepareMessagePacket(account, message, packet = prepareMessagePacket(account, message,
conv.getOtrSession()); conv.getOtrSession());
@ -646,6 +606,7 @@ public class XmppConnectionService extends Service {
conversation.setMode(Conversation.MODE_SINGLE); conversation.setMode(Conversation.MODE_SINGLE);
} }
this.databaseBackend.updateConversation(conversation); this.databaseBackend.updateConversation(conversation);
conversation.setContact(findContact(account, conversation.getContactJid()));
} else { } else {
String conversationName; String conversationName;
Contact contact = findContact(account, jid); Contact contact = findContact(account, jid);
@ -766,9 +727,9 @@ public class XmppConnectionService extends Service {
x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
if (conversation.getMessages().size() != 0) { if (conversation.getMessages().size() != 0) {
Element history = new Element("history"); Element history = new Element("history");
history.setAttribute("seconds", long lastMsgTime = conversation.getLatestMessage().getTimeSent();
(System.currentTimeMillis() - conversation long diff = (System.currentTimeMillis() - lastMsgTime) / 1000;
.getLatestMessage().getTimeSent() / 1000) + ""); history.setAttribute("seconds",diff+"");
x.addChild(history); x.addChild(history);
} }
packet.addChild(x); packet.addChild(x);
@ -806,4 +767,8 @@ public class XmppConnectionService extends Service {
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return mBinder; return mBinder;
} }
public void updateContact(Contact contact) {
databaseBackend.updateContact(contact);
}
} }

View file

@ -32,6 +32,8 @@ import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ListView; import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ImageView; import android.widget.ImageView;
@ -295,6 +297,56 @@ public class ConversationActivity extends XmppActivity {
} else { } else {
Log.d("xmppService","contact was null - means not in roster"); Log.d("xmppService","contact was null - means not in roster");
} }
break;
case R.id.action_security:
final Conversation selConv = getSelectedConversation();
View menuItemView = findViewById(R.id.action_security);
PopupMenu popup = new PopupMenu(this, menuItemView);
final ConversationFragment fragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation");
if (fragment!=null) {
popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.encryption_choice_none:
selConv.nextMessageEncryption = Message.ENCRYPTION_NONE;
item.setChecked(true);
break;
case R.id.encryption_choice_otr:
selConv.nextMessageEncryption = Message.ENCRYPTION_OTR;
item.setChecked(true);
break;
case R.id.encryption_choice_pgp:
selConv.nextMessageEncryption = Message.ENCRYPTION_PGP;
item.setChecked(true);
break;
default:
selConv.nextMessageEncryption = Message.ENCRYPTION_NONE;
break;
}
fragment.updateChatMsgHint();
return true;
}
});
popup.inflate(R.menu.encryption_choices);
switch (selConv.nextMessageEncryption) {
case Message.ENCRYPTION_NONE:
popup.getMenu().findItem(R.id.encryption_choice_none).setChecked(true);
break;
case Message.ENCRYPTION_OTR:
popup.getMenu().findItem(R.id.encryption_choice_otr).setChecked(true);
break;
case Message.ENCRYPTION_PGP:
popup.getMenu().findItem(R.id.encryption_choice_pgp).setChecked(true);
break;
default:
popup.getMenu().findItem(R.id.encryption_choice_none).setChecked(true);
break;
}
popup.show();
}
break; break;
default: default:
break; break;
@ -344,13 +396,6 @@ public class ConversationActivity extends XmppActivity {
@Override @Override
void onBackendConnected() { void onBackendConnected() {
if (contactInserted) {
Log.d("xmppService","merge phone contacts with roster");
contactInserted = false;
xmppConnectionService.mergePhoneContactsWithRoster();
}
xmppConnectionService.setOnConversationListChangedListener(this.onConvChanged); xmppConnectionService.setOnConversationListChangedListener(this.onConvChanged);
if (conversationList.size()==0) { if (conversationList.size()==0) {

View file

@ -1,15 +1,23 @@
package de.gultsch.chat.ui; package de.gultsch.chat.ui;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List; import java.util.List;
import java.util.Set;
import net.java.otr4j.OtrException;
import net.java.otr4j.session.SessionStatus;
import de.gultsch.chat.R; import de.gultsch.chat.R;
import de.gultsch.chat.entities.Contact; import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation; import de.gultsch.chat.entities.Conversation;
import de.gultsch.chat.entities.Message; import de.gultsch.chat.entities.Message;
import de.gultsch.chat.services.XmppConnectionService;
import de.gultsch.chat.utils.PhoneHelper; import de.gultsch.chat.utils.PhoneHelper;
import de.gultsch.chat.utils.UIHelper; import de.gultsch.chat.utils.UIHelper;
import android.app.AlertDialog;
import android.app.Fragment; import android.app.Fragment;
import android.content.DialogInterface;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
@ -21,6 +29,7 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView; import android.widget.ListView;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
@ -36,7 +45,80 @@ public class ConversationFragment extends Fragment {
protected Contact contact; protected Contact contact;
private EditText chatMsg; private EditText chatMsg;
private int nextMessageEncryption = Message.ENCRYPTION_NONE;
private OnClickListener sendMsgListener = new OnClickListener() {
@Override
public void onClick(View v) {
ConversationActivity activity = (ConversationActivity) getActivity();
final XmppConnectionService xmppService = activity.xmppConnectionService;
if (chatMsg.getText().length() < 1)
return;
final Message message = new Message(conversation, chatMsg.getText()
.toString(), conversation.nextMessageEncryption);
if (conversation.nextMessageEncryption == Message.ENCRYPTION_OTR) {
if (conversation.hasValidOtrSession()) {
activity.xmppConnectionService.sendMessage(
conversation.getAccount(), message, null);
chatMsg.setText("");
} else {
Hashtable<String, Integer> presences = conversation
.getContact().getPresences();
if (presences.size() == 0) {
AlertDialog.Builder builder = new AlertDialog.Builder(
getActivity());
builder.setTitle("Contact is offline");
builder.setIconAttribute(android.R.attr.alertDialogIcon);
builder.setMessage("Sending OTR encrypted messages to an offline contact is impossible.");
builder.setPositiveButton("Send plain text",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
message.setEncryption(Message.ENCRYPTION_NONE);
xmppService.sendMessage(
conversation.getAccount(),
message, null);
chatMsg.setText("");
}
});
builder.setNegativeButton("Cancel", null);
builder.create().show();
} else if (presences.size() == 1) {
xmppService.sendMessage(conversation.getAccount(),
message,
(String) presences.keySet().toArray()[0]);
chatMsg.setText("");
}
}
} else {
xmppService.sendMessage(conversation.getAccount(), message,
null);
chatMsg.setText("");
}
}
};
public void updateChatMsgHint() {
if (conversation.getMode() == Conversation.MODE_MULTI) {
chatMsg.setHint("Send message to conference");
} else {
switch (conversation.nextMessageEncryption) {
case Message.ENCRYPTION_NONE:
chatMsg.setHint("Send plain text message");
break;
case Message.ENCRYPTION_OTR:
chatMsg.setHint("Send OTR encrypted message");
break;
case Message.ENCRYPTION_PGP:
chatMsg.setHint("Send openPGP encryted messeage");
default:
break;
}
}
}
@Override @Override
public View onCreateView(final LayoutInflater inflater, public View onCreateView(final LayoutInflater inflater,
@ -47,26 +129,17 @@ public class ConversationFragment extends Fragment {
final View view = inflater.inflate(R.layout.fragment_conversation, final View view = inflater.inflate(R.layout.fragment_conversation,
container, false); container, false);
chatMsg = (EditText) view.findViewById(R.id.textinput); chatMsg = (EditText) view.findViewById(R.id.textinput);
((ImageButton) view.findViewById(R.id.textSendButton)) ImageButton sendButton = (ImageButton) view
.setOnClickListener(new OnClickListener() { .findViewById(R.id.textSendButton);
sendButton.setOnClickListener(this.sendMsgListener);
@Override
public void onClick(View v) {
ConversationActivity activity = (ConversationActivity) getActivity();
if (chatMsg.getText().length() < 1)
return;
Message message = new Message(conversation, chatMsg
.getText().toString(), nextMessageEncryption);
activity.xmppConnectionService.sendMessage(conversation.getAccount(),message);
chatMsg.setText("");
}
});
messagesView = (ListView) view.findViewById(R.id.messages_view); messagesView = (ListView) view.findViewById(R.id.messages_view);
SharedPreferences sharedPref = PreferenceManager SharedPreferences sharedPref = PreferenceManager
.getDefaultSharedPreferences(getActivity().getApplicationContext()); .getDefaultSharedPreferences(getActivity()
boolean showPhoneSelfContactPicture = sharedPref.getBoolean("show_phone_selfcontact_picture",true); .getApplicationContext());
boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
"show_phone_selfcontact_picture", true);
final Uri selfiUri; final Uri selfiUri;
if (showPhoneSelfContactPicture) { if (showPhoneSelfContactPicture) {
@ -76,7 +149,8 @@ public class ConversationFragment extends Fragment {
} }
messageListAdapter = new ArrayAdapter<Message>(this.getActivity() messageListAdapter = new ArrayAdapter<Message>(this.getActivity()
.getApplicationContext(), R.layout.message_sent, this.messageList) { .getApplicationContext(), R.layout.message_sent,
this.messageList) {
private static final int SENT = 0; private static final int SENT = 0;
private static final int RECIEVED = 1; private static final int RECIEVED = 1;
@ -111,32 +185,42 @@ public class ConversationFragment extends Fragment {
break; break;
} }
} }
ImageView imageView = (ImageView) view.findViewById(R.id.message_photo); ImageView imageView = (ImageView) view
.findViewById(R.id.message_photo);
if (type == RECIEVED) { if (type == RECIEVED) {
if(item.getConversation().getMode()==Conversation.MODE_SINGLE) { if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
Uri uri = item.getConversation().getProfilePhotoUri(); Uri uri = item.getConversation().getProfilePhotoUri();
if (uri!=null) { if (uri != null) {
imageView.setImageURI(uri); imageView.setImageURI(uri);
} else { } else {
imageView.setImageBitmap(UIHelper.getUnknownContactPicture(item.getConversation().getName(), 200)); imageView.setImageBitmap(UIHelper
.getUnknownContactPicture(item
.getConversation().getName(), 200));
} }
} else if (item.getConversation().getMode()==Conversation.MODE_MULTI) { } else if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
if (item.getCounterpart()!=null) { if (item.getCounterpart() != null) {
imageView.setImageBitmap(UIHelper.getUnknownContactPicture(item.getCounterpart(), 200)); imageView.setImageBitmap(UIHelper
.getUnknownContactPicture(
item.getCounterpart(), 200));
} else { } else {
imageView.setImageBitmap(UIHelper.getUnknownContactPicture(item.getConversation().getName(), 200)); imageView.setImageBitmap(UIHelper
.getUnknownContactPicture(item
.getConversation().getName(), 200));
} }
} }
} else { } else {
if (selfiUri!=null) { if (selfiUri != null) {
imageView.setImageURI(selfiUri); imageView.setImageURI(selfiUri);
} else { } else {
imageView.setImageBitmap(UIHelper.getUnknownContactPicture(conversation.getAccount().getJid(),200)); imageView.setImageBitmap(UIHelper
.getUnknownContactPicture(conversation
.getAccount().getJid(), 200));
} }
} }
TextView messageBody = (TextView) view.findViewById(R.id.message_body); TextView messageBody = (TextView) view
.findViewById(R.id.message_body);
String body = item.getBody(); String body = item.getBody();
if (body!=null) { if (body != null) {
messageBody.setText(body.trim()); messageBody.setText(body.trim());
} }
TextView time = (TextView) view.findViewById(R.id.message_time); TextView time = (TextView) view.findViewById(R.id.message_time);
@ -144,12 +228,15 @@ public class ConversationFragment extends Fragment {
time.setTypeface(null, Typeface.ITALIC); time.setTypeface(null, Typeface.ITALIC);
time.setText("sending\u2026"); time.setText("sending\u2026");
} else { } else {
time.setTypeface(null,Typeface.NORMAL); time.setTypeface(null, Typeface.NORMAL);
if ((item.getConversation().getMode()==Conversation.MODE_SINGLE)||(type != RECIEVED)) { if ((item.getConversation().getMode() == Conversation.MODE_SINGLE)
|| (type != RECIEVED)) {
time.setText(UIHelper.readableTimeDifference(item time.setText(UIHelper.readableTimeDifference(item
.getTimeSent())); .getTimeSent()));
} else { } else {
time.setText(item.getCounterpart()+" \u00B7 "+UIHelper.readableTimeDifference(item time.setText(item.getCounterpart()
+ " \u00B7 "
+ UIHelper.readableTimeDifference(item
.getTimeSent())); .getTimeSent()));
} }
} }
@ -201,32 +288,50 @@ public class ConversationFragment extends Fragment {
} }
public void updateMessages() { public void updateMessages() {
ConversationActivity activity = (ConversationActivity) getActivity();
this.messageList.clear(); this.messageList.clear();
this.messageList.addAll(this.conversation.getMessages()); this.messageList.addAll(this.conversation.getMessages());
this.messageListAdapter.notifyDataSetChanged(); this.messageListAdapter.notifyDataSetChanged();
if (messageList.size()>=1) { if (messageList.size() >= 1) {
nextMessageEncryption = this.conversation.getLatestMessage().getEncryption(); int latestEncryption = this.conversation.getLatestMessage()
.getEncryption();
conversation.nextMessageEncryption = latestEncryption;
makeFingerprintWarning(latestEncryption);
} }
getActivity().invalidateOptionsMenu(); getActivity().invalidateOptionsMenu();
switch (nextMessageEncryption) { updateChatMsgHint();
case Message.ENCRYPTION_NONE:
chatMsg.setHint("Send plain text message");
break;
case Message.ENCRYPTION_OTR:
chatMsg.setHint("Send OTR encrypted message");
break;
case Message.ENCRYPTION_PGP:
chatMsg.setHint("Send openPGP encryted messeage");
default:
break;
}
int size = this.messageList.size(); int size = this.messageList.size();
if (size >= 1) if (size >= 1)
messagesView.setSelection(size - 1); messagesView.setSelection(size - 1);
ConversationActivity activity = (ConversationActivity) getActivity();
if (!activity.shouldPaneBeOpen()) { if (!activity.shouldPaneBeOpen()) {
conversation.markRead(); conversation.markRead();
activity.updateConversationList(); activity.updateConversationList();
} }
} }
protected void makeFingerprintWarning(int latestEncryption) {
final LinearLayout fingerprintWarning = (LinearLayout) getView()
.findViewById(R.id.new_fingerprint);
Set<String> knownFingerprints = conversation.getContact()
.getOtrFingerprints();
if ((latestEncryption == Message.ENCRYPTION_OTR)
&& (conversation.hasValidOtrSession()
&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
.contains(conversation.getOtrFingerprint())))) {
fingerprintWarning.setVisibility(View.VISIBLE);
TextView fingerprint = (TextView) getView().findViewById(
R.id.otr_fingerprint);
fingerprint.setText(conversation.getOtrFingerprint());
fingerprintWarning.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog dialog = UIHelper.getVerifyFingerprintDialog((ConversationActivity) getActivity(),conversation,fingerprintWarning);
dialog.show();
}
});
} else {
fingerprintWarning.setVisibility(View.GONE);
}
}
} }

View file

@ -7,15 +7,19 @@ import java.util.Date;
import java.util.List; import java.util.List;
import de.gultsch.chat.R; import de.gultsch.chat.R;
import de.gultsch.chat.entities.Account;
import de.gultsch.chat.entities.Contact; import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation; import de.gultsch.chat.entities.Conversation;
import de.gultsch.chat.entities.Message; import de.gultsch.chat.entities.Message;
import de.gultsch.chat.ui.ConversationActivity; import de.gultsch.chat.ui.ConversationActivity;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification; import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Resources; import android.content.res.Resources;
@ -29,7 +33,11 @@ import android.provider.ContactsContract.Contacts;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder; import android.support.v4.app.TaskStackBuilder;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.QuickContactBadge; import android.widget.QuickContactBadge;
import android.widget.TextView;
public class UIHelper { public class UIHelper {
public static String readableTimeDifference(long time) { public static String readableTimeDifference(long time) {
@ -153,4 +161,33 @@ public class UIHelper {
} }
} }
public static AlertDialog getVerifyFingerprintDialog(final ConversationActivity activity,final Conversation conversation, final LinearLayout msg) {
final Contact contact = conversation.getContact();
final Account account = conversation.getAccount();
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("Verify fingerprint");
LayoutInflater inflater = activity.getLayoutInflater();
View view = inflater.inflate(R.layout.dialog_verify_otr, null);
TextView jid = (TextView) view.findViewById(R.id.verify_otr_jid);
TextView fingerprint = (TextView) view.findViewById(R.id.verify_otr_fingerprint);
TextView yourprint = (TextView) view.findViewById(R.id.verify_otr_yourprint);
jid.setText(contact.getJid());
fingerprint.setText(conversation.getOtrFingerprint());
yourprint.setText(account.getOtrFingerprint());
builder.setNegativeButton("Cancel", null);
builder.setPositiveButton("Verify", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
contact.addOtrFingerprint(conversation.getOtrFingerprint());
msg.setVisibility(View.GONE);
activity.xmppConnectionService.updateContact(contact);
}
});
builder.setView(view);
return builder.create();
}
} }