parse otr-fingerprint in qr codes and nfc. include otr fingerprint in shareable uri where ever possible

iNPUTmice created

Change summary

src/main/java/eu/siacs/conversations/entities/Account.java               | 45 
src/main/java/eu/siacs/conversations/entities/Contact.java               | 35 
src/main/java/eu/siacs/conversations/entities/Conversation.java          |  6 
src/main/java/eu/siacs/conversations/parser/MessageParser.java           |  6 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 15 
src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java      |  6 
src/main/java/eu/siacs/conversations/ui/ConversationActivity.java        |  2 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java         |  8 
src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java   | 51 
src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java           |  5 
src/main/java/eu/siacs/conversations/utils/CryptoHelper.java             |  9 
11 files changed, 119 insertions(+), 69 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Account.java 🔗

@@ -280,10 +280,11 @@ public class Account extends AbstractEntity {
 		return values;
 	}
 
-	public OtrEngine getOtrEngine(XmppConnectionService context) {
-		if (otrEngine == null) {
-			otrEngine = new OtrEngine(context, this);
-		}
+	public void initOtrEngine(XmppConnectionService context) {
+		this.otrEngine = new OtrEngine(context, this);
+	}
+
+	public OtrEngine getOtrEngine() {
 		return this.otrEngine;
 	}
 
@@ -298,23 +299,21 @@ public class Account extends AbstractEntity {
 	public String getOtrFingerprint() {
 		if (this.otrFingerprint == null) {
 			try {
-				DSAPublicKey pubkey = (DSAPublicKey) this.otrEngine
-					.getPublicKey();
-				if (pubkey == null) {
+				if (this.otrEngine == null) {
 					return null;
 				}
-				StringBuilder builder = new StringBuilder(
-						new OtrCryptoEngineImpl().getFingerprint(pubkey));
-				builder.insert(8, " ");
-				builder.insert(17, " ");
-				builder.insert(26, " ");
-				builder.insert(35, " ");
-				this.otrFingerprint = builder.toString();
+				DSAPublicKey publicKey = (DSAPublicKey) this.otrEngine.getPublicKey();
+				if (publicKey == null) {
+					return null;
+				}
+				this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey);
+				return this.otrFingerprint;
 			} catch (final OtrCryptoException ignored) {
-
+				return null;
 			}
+		} else {
+			return this.otrFingerprint;
 		}
-		return this.otrFingerprint;
 	}
 
 	public String getRosterVersion() {
@@ -329,11 +328,6 @@ public class Account extends AbstractEntity {
 		this.rosterVersion = version;
 	}
 
-	public String getOtrFingerprint(XmppConnectionService service) {
-		this.getOtrEngine(service);
-		return this.getOtrFingerprint();
-	}
-
 	public void updatePresence(String resource, int status) {
 		this.presences.updatePresence(resource, status);
 	}
@@ -411,4 +405,13 @@ public class Account extends AbstractEntity {
 	public boolean inGracePeriod() {
 		return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
 	}
+
+	public String getShareableUri() {
+		String fingerprint = this.getOtrFingerprint();
+		if (fingerprint != null) {
+			return "xmpp:" + this.getJid().toBareJid().toString() + "?otr-fingerprint="+fingerprint;
+		} else {
+			return "xmpp:" + this.getJid().toBareJid().toString();
+		}
+	}
 }

src/main/java/eu/siacs/conversations/entities/Contact.java 🔗

@@ -7,6 +7,7 @@ import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -206,24 +207,26 @@ public class Contact implements ListItem {
 		return systemAccount;
 	}
 
-	public Set<String> getOtrFingerprints() {
-		Set<String> set = new HashSet<>();
+	public ArrayList<String> getOtrFingerprints() {
+		ArrayList<String> fingerprints = new ArrayList<String>();
 		try {
 			if (this.keys.has("otr_fingerprints")) {
-				JSONArray fingerprints = this.keys
+				JSONArray prints = this.keys
 						.getJSONArray("otr_fingerprints");
-				for (int i = 0; i < fingerprints.length(); ++i) {
-					set.add(fingerprints.getString(i));
+				for (int i = 0; i < prints.length(); ++i) {
+					fingerprints.add(prints.getString(i));
 				}
 			}
-		} catch (JSONException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
+		} catch (final JSONException ignored) {
+
 		}
-		return set;
+		return fingerprints;
 	}
 
-	public void addOtrFingerprint(String print) {
+	public boolean addOtrFingerprint(String print) {
+		if (getOtrFingerprints().contains(print)) {
+			return false;
+		}
 		try {
 			JSONArray fingerprints;
 			if (!this.keys.has("otr_fingerprints")) {
@@ -234,8 +237,9 @@ public class Contact implements ListItem {
 			}
 			fingerprints.put(print);
 			this.keys.put("otr_fingerprints", fingerprints);
+			return true;
 		} catch (final JSONException ignored) {
-
+			return false;
 		}
 	}
 
@@ -396,4 +400,13 @@ public class Contact implements ListItem {
 	public boolean trusted() {
 		return getOption(Options.FROM) && getOption(Options.TO);
 	}
+
+	public String getShareableUri() {
+		if (getOtrFingerprints().size() >= 1) {
+			String otr = getOtrFingerprints().get(0);
+			return "xmpp:"+getJid().toBareJid().toString()+"?otr-fingerprint="+otr.replace(" ","");
+		} else {
+			return "xmpp:"+getJid().toBareJid().toString();
+		}
+	}
 }

src/main/java/eu/siacs/conversations/entities/Conversation.java 🔗

@@ -240,16 +240,14 @@ public class Conversation extends AbstractEntity {
 		this.mode = mode;
 	}
 
-	public SessionImpl startOtrSession(XmppConnectionService service,
-			String presence, boolean sendStart) {
+	public SessionImpl startOtrSession(String presence, boolean sendStart) {
 		if (this.otrSession != null) {
 			return this.otrSession;
 		} else {
             final SessionID sessionId = new SessionID(this.getContactJid().toBareJid().toString(),
                     presence,
                     "xmpp");
-			this.otrSession = new SessionImpl(sessionId, getAccount()
-					.getOtrEngine(service));
+			this.otrSession = new SessionImpl(sessionId, getAccount().getOtrEngine());
 			try {
 				if (sendStart) {
 					this.otrSession.startSession();

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -76,8 +76,7 @@ public class MessageParser extends AbstractParser implements
 		}
 		if (!conversation.hasValidOtrSession()) {
 			if (properlyAddressed) {
-				conversation.startOtrSession(mXmppConnectionService, presence,
-						false);
+				conversation.startOtrSession(presence,false);
 			} else {
 				return null;
 			}
@@ -87,8 +86,7 @@ public class MessageParser extends AbstractParser implements
 			if (!foreignPresence.equals(presence)) {
 				conversation.endOtrIfNeeded();
 				if (properlyAddressed) {
-					conversation.startOtrSession(mXmppConnectionService,
-							presence, false);
+					conversation.startOtrSession(presence, false);
 				} else {
 					return null;
 				}

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -491,6 +491,7 @@ public class XmppConnectionService extends Service {
 		this.accounts = databaseBackend.getAccounts();
 
 		for (Account account : this.accounts) {
+			account.initOtrEngine(this);
 			this.databaseBackend.readRoster(account.getRoster());
 		}
 		this.mergePhoneContactsWithRoster();
@@ -608,8 +609,7 @@ public class XmppConnectionService extends Service {
 				if (message.getCounterpart() != null) {
 					if (message.getEncryption() == Message.ENCRYPTION_OTR) {
 						if (!conv.hasValidOtrSession()) {
-							conv.startOtrSession(this, message.getCounterpart().getResourcepart(),
-									true);
+							conv.startOtrSession(message.getCounterpart().getResourcepart(),true);
 							message.setStatus(Message.STATUS_WAITING);
 						} else if (conv.hasValidOtrSession()
 								&& conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
@@ -628,7 +628,7 @@ public class XmppConnectionService extends Service {
 			} else {
 				if (message.getEncryption() == Message.ENCRYPTION_OTR) {
 					if (!conv.hasValidOtrSession() && (message.getCounterpart() != null)) {
-						conv.startOtrSession(this, message.getCounterpart().getResourcepart(), true);
+						conv.startOtrSession(message.getCounterpart().getResourcepart(), true);
 						message.setStatus(Message.STATUS_WAITING);
 					} else if (conv.hasValidOtrSession()) {
 						if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
@@ -672,7 +672,7 @@ public class XmppConnectionService extends Service {
 				} else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
 					if (!conv.hasValidOtrSession()
 							&& message.getCounterpart() != null) {
-						conv.startOtrSession(this, message.getCounterpart().getResourcepart(), false);
+						conv.startOtrSession(message.getCounterpart().getResourcepart(), false);
 							}
 				}
 			}
@@ -709,13 +709,11 @@ public class XmppConnectionService extends Service {
 			if (!message.getConversation().hasValidOtrSession()) {
 				if ((message.getCounterpart() != null)
 						&& (presences.has(message.getCounterpart().getResourcepart()))) {
-					message.getConversation().startOtrSession(this,
-							message.getCounterpart().getResourcepart(), true);
+					message.getConversation().startOtrSession(message.getCounterpart().getResourcepart(), true);
 				} else {
 					if (presences.size() == 1) {
 						String presence = presences.asStringArray()[0];
-						message.getConversation().startOtrSession(this,
-								presence, true);
+						message.getConversation().startOtrSession(presence, true);
 					}
 				}
 			} else {
@@ -1061,6 +1059,7 @@ public class XmppConnectionService extends Service {
 	}
 
 	public void createAccount(Account account) {
+		account.initOtrEngine(this);
 		databaseBackend.createAccount(account);
 		this.accounts.add(account);
 		this.reconnectAccount(account, false);

src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java 🔗

@@ -154,7 +154,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
 	@Override
 	protected String getShareableUri() {
 		if (contact != null) {
-			return "xmpp:" + contact.getJid();
+			return contact.getShareableUri();
 		} else {
 			return "";
 		}
@@ -326,10 +326,8 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
 		keys.removeAllViews();
 		boolean hasKeys = false;
 		LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-		for (Iterator<String> iterator = contact.getOtrFingerprints()
-				.iterator(); iterator.hasNext(); ) {
+		for(final String otrFingerprint : contact.getOtrFingerprints()) {
 			hasKeys = true;
-			final String otrFingerprint = iterator.next();
 			View view = inflater.inflate(R.layout.contact_key, keys, false);
 			TextView key = (TextView) view.findViewById(R.id.key);
 			TextView keyType = (TextView) view.findViewById(R.id.key_type);

src/main/java/eu/siacs/conversations/ui/ConversationActivity.java 🔗

@@ -108,7 +108,7 @@ public class ConversationActivity extends XmppActivity implements
 	protected String getShareableUri() {
 		Conversation conversation = getSelectedConversation();
 		if (conversation != null) {
-			return "xmpp:" + conversation.getAccount().getJid().toBareJid();
+			return conversation.getAccount().getShareableUri();
 		} else {
 			return "";
 		}

src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java 🔗

@@ -26,6 +26,7 @@ import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
 import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
+import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.UIHelper;
 import eu.siacs.conversations.utils.Validator;
 import eu.siacs.conversations.xmpp.XmppConnection.Features;
@@ -270,7 +271,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 	@Override
 	protected String getShareableUri() {
 		if (mAccount!=null) {
-			return "xmpp:"+mAccount.getJid().toBareJid();
+			return mAccount.getShareableUri();
 		} else {
 			return "";
 		}
@@ -402,11 +403,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			} else {
 				this.mServerInfoPep.setText(R.string.server_info_unavailable);
 			}
-			final String fingerprint = this.mAccount
-					.getOtrFingerprint(xmppConnectionService);
+			final String fingerprint = this.mAccount.getOtrFingerprint();
 			if (fingerprint != null) {
 				this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
-				this.mOtrFingerprint.setText(fingerprint);
+				this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(fingerprint));
 				this.mOtrFingerprintToClipboardButton
 						.setVisibility(View.VISIBLE);
 				this.mOtrFingerprintToClipboardButton

src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java 🔗

@@ -62,6 +62,7 @@ import eu.siacs.conversations.entities.ListItem;
 import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
 import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
 import eu.siacs.conversations.ui.adapter.ListItemAdapter;
+import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.Validator;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 import eu.siacs.conversations.xmpp.jid.Jid;
@@ -316,7 +317,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
 	}
 
 	@SuppressLint("InflateParams")
-	protected void showCreateContactDialog(String prefilledJid) {
+	protected void showCreateContactDialog(final String prefilledJid, final String fingerprint) {
 		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 		builder.setTitle(R.string.create_contact);
 		View dialogView = getLayoutInflater().inflate(
@@ -328,6 +329,12 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
 				android.R.layout.simple_list_item_1, mKnownHosts));
 		if (prefilledJid != null) {
 			jid.append(prefilledJid);
+			if (fingerprint!=null) {
+				jid.setFocusable(false);
+				jid.setFocusableInTouchMode(false);
+				jid.setClickable(false);
+				jid.setCursorVisible(false);
+			}
 		}
 		populateAccountSpinner(spinner);
 		builder.setView(dialogView);
@@ -367,6 +374,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
 							if (contact.showInRoster()) {
 								jid.setError(getString(R.string.contact_already_exists));
 							} else {
+								contact.addOtrFingerprint(fingerprint);
 								xmppConnectionService.createContact(contact);
 								dialog.dismiss();
 								switchToConversation(contact);
@@ -511,7 +519,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
 	public boolean onOptionsItemSelected(MenuItem item) {
 		switch (item.getItemId()) {
 			case R.id.action_create_contact:
-				showCreateContactDialog(null);
+				showCreateContactDialog(null,null);
 				return true;
 			case R.id.action_join_conference:
 				showJoinConferenceDialog(null);
@@ -615,22 +623,29 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
 		return false;
 	}
 
-	private boolean handleJid(String jid) {
-		List<Contact> contacts = xmppConnectionService.findContacts(jid);
+	private boolean handleJid(Invite invite) {
+		List<Contact> contacts = xmppConnectionService.findContacts(invite.jid);
 		if (contacts.size() == 0) {
-			showCreateContactDialog(jid);
+			showCreateContactDialog(invite.jid,invite.fingerprint);
 			return false;
 		} else if (contacts.size() == 1) {
-			switchToConversation(contacts.get(0));
+			Contact contact = contacts.get(0);
+			if (invite.fingerprint != null) {
+				if (contact.addOtrFingerprint(invite.fingerprint)) {
+					Log.d(Config.LOGTAG,"added new fingerprint");
+					xmppConnectionService.syncRosterToDisk(contact.getAccount());
+				}
+			}
+			switchToConversation(contact);
 			return true;
 		} else {
 			if (mMenuSearchView != null) {
 				mMenuSearchView.expandActionView();
 				mSearchEditText.setText("");
-				mSearchEditText.append(jid);
-				filter(jid);
+				mSearchEditText.append(invite.jid);
+				filter(invite.jid);
 			} else {
-				mInitialJid = jid;
+				mInitialJid = invite.jid;
 			}
 			return true;
 		}
@@ -743,6 +758,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
 	private class Invite {
 		private String jid;
 		private boolean muc;
+		private String fingerprint;
 
 		Invite(Uri uri) {
 			parse(uri);
@@ -761,7 +777,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
 				if (muc) {
 					showJoinConferenceDialog(jid);
 				} else {
-					return handleJid(jid);
+					return handleJid(this);
 				}
 			}
 			return false;
@@ -777,6 +793,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
 				} else {
 					jid = uri.getSchemeSpecificPart().split("\\?")[0];
 				}
+				fingerprint = parseFingerprint(uri.getQuery());
 			} else if ("imto".equals(scheme)) {
 				// sample: imto://xmpp/jid@foo.com
 				try {
@@ -785,5 +802,19 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
 				}
 			}
 		}
+
+		String parseFingerprint(String query) {
+			if (query == null) {
+				return null;
+			} else {
+				final String NEEDLE = "otr-fingerprint=";
+				int index = query.indexOf(NEEDLE);
+				if (index >= 0 && query.length() >= (NEEDLE.length() + index + 40)) {
+					return CryptoHelper.prettifyFingerprint(query.substring(index + NEEDLE.length(), index + NEEDLE.length() + 40));
+				} else {
+					return null;
+				}
+			}
+		}
 	}
 }

src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java 🔗

@@ -16,6 +16,7 @@ import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 import eu.siacs.conversations.xmpp.jid.Jid;
 
@@ -184,7 +185,7 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer
 			this.mVerificationAreaOne.setVisibility(View.VISIBLE);
 			this.mVerificationAreaTwo.setVisibility(View.VISIBLE);
 			this.mErrorNoSession.setVisibility(View.GONE);
-			this.mYourFingerprint.setText(this.mAccount.getOtrFingerprint(xmppConnectionService));
+			this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint()));
 			this.mRemoteFingerprint.setText(this.mConversation.getOtrFingerprint());
 			this.mRemoteJid.setText(this.mConversation.getContact().getJid().toBareJid().toString());
 			Conversation.Smp smp = mConversation.smp();
@@ -279,7 +280,7 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer
 	@Override
 	protected String getShareableUri() {
 		if (mAccount!=null) {
-			return "xmpp:"+mAccount.getJid().toBareJid();
+			return mAccount.getShareableUri();
 		} else {
 			return "";
 		}

src/main/java/eu/siacs/conversations/utils/CryptoHelper.java 🔗

@@ -82,4 +82,13 @@ public class CryptoHelper {
 	public static String saslPrep(final String s) {
 		return saslEscape(Normalizer.normalize(s, Normalizer.Form.NFKC));
 	}
+
+	public static String prettifyFingerprint(String fingerprint) {
+		StringBuilder builder = new StringBuilder(fingerprint);
+		builder.insert(8, " ");
+		builder.insert(17, " ");
+		builder.insert(26, " ");
+		builder.insert(35, " ");
+		return builder.toString();
+	}
 }