From b8048a5538293b3855c2b2eae55d35645e614f11 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 29 May 2015 11:17:26 +0200 Subject: CryptoNext persistance layer mockup Initial sketch of the peripheral storage infrastructure for the new axolotl-based encryption scheme. --- .../crypto/axolotl/AxolotlService.java | 440 +++++++++++++++++++++ .../crypto/axolotl/XmppAxolotlMessage.java | 4 + .../eu/siacs/conversations/entities/Account.java | 4 + .../eu/siacs/conversations/entities/Contact.java | 182 ++++++--- .../eu/siacs/conversations/entities/Message.java | 13 + .../conversations/persistance/DatabaseBackend.java | 263 +++++++++++- 6 files changed, 841 insertions(+), 65 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java create mode 100644 src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java new file mode 100644 index 000000000..8e3002487 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -0,0 +1,440 @@ +package eu.siacs.conversations.crypto.axolotl; + +import android.util.Log; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.ecc.Curve; +import org.whispersystems.libaxolotl.ecc.ECKeyPair; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SessionRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; +import org.whispersystems.libaxolotl.util.KeyHelper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class AxolotlService { + + private Account account; + private XmppConnectionService mXmppConnectionService; + private SQLiteAxolotlStore axolotlStore; + private Map sessions; + + public static class SQLiteAxolotlStore implements AxolotlStore { + + public static final String PREKEY_TABLENAME = "prekeys"; + public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys"; + public static final String SESSION_TABLENAME = "signed_prekeys"; + public static final String NAME = "name"; + public static final String DEVICE_ID = "device_id"; + public static final String ID = "id"; + public static final String KEY = "key"; + public static final String ACCOUNT = "account"; + + public static final String JSONKEY_IDENTITY_KEY_PAIR = "axolotl_key"; + public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; + + private final Account account; + private final XmppConnectionService mXmppConnectionService; + + private final IdentityKeyPair identityKeyPair; + private final int localRegistrationId; + + + private static IdentityKeyPair generateIdentityKeyPair() { + Log.d(Config.LOGTAG, "Generating axolotl IdentityKeyPair..."); + ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); + IdentityKeyPair ownKey = new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), + identityKeyPairKeys.getPrivateKey()); + return ownKey; + } + + private static int generateRegistrationId() { + Log.d(Config.LOGTAG, "Generating axolotl registration ID..."); + int reg_id = KeyHelper.generateRegistrationId(false); + return reg_id; + } + + public SQLiteAxolotlStore(Account account, XmppConnectionService service) { + this.account = account; + this.mXmppConnectionService = service; + this.identityKeyPair = loadIdentityKeyPair(); + this.localRegistrationId = loadRegistrationId(); + } + + // -------------------------------------- + // IdentityKeyStore + // -------------------------------------- + + private IdentityKeyPair loadIdentityKeyPair() { + String serializedKey = this.account.getKey(JSONKEY_IDENTITY_KEY_PAIR); + IdentityKeyPair ownKey; + if( serializedKey != null ) { + try { + ownKey = new IdentityKeyPair(serializedKey.getBytes()); + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, "Invalid key stored for account " + account.getJid() + ": " + e.getMessage()); + return null; + } + } else { + Log.d(Config.LOGTAG, "Could not retrieve axolotl key for account " + account.getJid()); + ownKey = generateIdentityKeyPair(); + boolean success = this.account.setKey(JSONKEY_IDENTITY_KEY_PAIR, new String(ownKey.serialize())); + if(success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, "Failed to write new key to the database!"); + } + } + return ownKey; + } + + private int loadRegistrationId() { + String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); + int reg_id; + if (regIdString != null) { + reg_id = Integer.valueOf(regIdString); + } else { + Log.d(Config.LOGTAG, "Could not retrieve axolotl registration id for account " + account.getJid()); + reg_id = generateRegistrationId(); + boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID,""+reg_id); + if(success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, "Failed to write new key to the database!"); + } + } + return reg_id; + } + + /** + * Get the local client's identity key pair. + * + * @return The local client's persistent identity key pair. + */ + @Override + public IdentityKeyPair getIdentityKeyPair() { + return identityKeyPair; + } + + /** + * Return the local client's registration ID. + *

+ * Clients should maintain a registration ID, a random number + * between 1 and 16380 that's generated once at install time. + * + * @return the local client's registration ID. + */ + @Override + public int getLocalRegistrationId() { + return localRegistrationId; + } + + /** + * Save a remote client's identity key + *

+ * Store a remote client's identity key as trusted. + * + * @param name The name of the remote client. + * @param identityKey The remote client's identity key. + */ + @Override + public void saveIdentity(String name, IdentityKey identityKey) { + try { + Jid contactJid = Jid.fromString(name); + Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid); + if (conversation != null) { + conversation.getContact().addAxolotlIdentityKey(identityKey, false); + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.syncRosterToDisk(conversation.getAccount()); + } + } catch (final InvalidJidException e) { + Log.e(Config.LOGTAG, "Failed to save identityKey for contact name " + name + ": " + e.toString()); + } + } + + /** + * Verify a remote client's identity key. + *

+ * Determine whether a remote client's identity is trusted. Convention is + * that the TextSecure protocol is 'trust on first use.' This means that + * an identity key is considered 'trusted' if there is no entry for the recipient + * in the local store, or if it matches the saved key for a recipient in the local + * store. Only if it mismatches an entry in the local store is it considered + * 'untrusted.' + * + * @param name The name of the remote client. + * @param identityKey The identity key to verify. + * @return true if trusted, false if untrusted. + */ + @Override + public boolean isTrustedIdentity(String name, IdentityKey identityKey) { + try { + Jid contactJid = Jid.fromString(name); + Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid); + if (conversation != null) { + List trustedKeys = conversation.getContact().getTrustedAxolotlIdentityKeys(); + return trustedKeys.contains(identityKey); + } else { + return false; + } + } catch (final InvalidJidException e) { + Log.e(Config.LOGTAG, "Failed to save identityKey for contact name" + name + ": " + e.toString()); + return false; + } + } + + // -------------------------------------- + // SessionStore + // -------------------------------------- + + /** + * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, + * or a new SessionRecord if one does not currently exist. + *

+ * It is important that implementations return a copy of the current durable information. The + * returned SessionRecord may be modified, but those changes should not have an effect on the + * durable session state (what is returned by subsequent calls to this method) without the + * store method being called here first. + * + * @param address The name and device ID of the remote client. + * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or + * a new SessionRecord if one does not currently exist. + */ + @Override + public SessionRecord loadSession(AxolotlAddress address) { + SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address); + return (session!=null)?session:new SessionRecord(); + } + + /** + * Returns all known devices with active sessions for a recipient + * + * @param name the name of the client. + * @return all known sub-devices with active sessions. + */ + @Override + public List getSubDeviceSessions(String name) { + return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account, + new AxolotlAddress(name,0)); + } + + /** + * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @param record the current SessionRecord for the remote client. + */ + @Override + public void storeSession(AxolotlAddress address, SessionRecord record) { + mXmppConnectionService.databaseBackend.storeSession(account, address, record); + } + + /** + * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @return true if a {@link SessionRecord} exists, false otherwise. + */ + @Override + public boolean containsSession(AxolotlAddress address) { + return mXmppConnectionService.databaseBackend.containsSession(account, address); + } + + /** + * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + */ + @Override + public void deleteSession(AxolotlAddress address) { + mXmppConnectionService.databaseBackend.deleteSession(account, address); + } + + /** + * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. + * + * @param name the name of the remote client. + */ + @Override + public void deleteAllSessions(String name) { + mXmppConnectionService.databaseBackend.deleteAllSessions(account, + new AxolotlAddress(name,0)); + } + + // -------------------------------------- + // PreKeyStore + // -------------------------------------- + + /** + * Load a local PreKeyRecord. + * + * @param preKeyId the ID of the local PreKeyRecord. + * @return the corresponding PreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord. + */ + @Override + public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { + PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId); + if(record == null) { + throw new InvalidKeyIdException("No such PreKeyRecord!"); + } + return record; + } + + /** + * Store a local PreKeyRecord. + * + * @param preKeyId the ID of the PreKeyRecord to store. + * @param record the PreKeyRecord. + */ + @Override + public void storePreKey(int preKeyId, PreKeyRecord record) { + mXmppConnectionService.databaseBackend.storePreKey(account, record); + } + + /** + * @param preKeyId A PreKeyRecord ID. + * @return true if the store has a record for the preKeyId, otherwise false. + */ + @Override + public boolean containsPreKey(int preKeyId) { + return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId); + } + + /** + * Delete a PreKeyRecord from local storage. + * + * @param preKeyId The ID of the PreKeyRecord to remove. + */ + @Override + public void removePreKey(int preKeyId) { + mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId); + } + + // -------------------------------------- + // SignedPreKeyStore + // -------------------------------------- + + /** + * Load a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the local SignedPreKeyRecord. + * @return the corresponding SignedPreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord. + */ + @Override + public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { + SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId); + if(record == null) { + throw new InvalidKeyIdException("No such PreKeyRecord!"); + } + return record; + } + + /** + * Load all local SignedPreKeyRecords. + * + * @return All stored SignedPreKeyRecords. + */ + @Override + public List loadSignedPreKeys() { + return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account); + } + + /** + * Store a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. + * @param record the SignedPreKeyRecord. + */ + @Override + public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { + mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record); + } + + /** + * @param signedPreKeyId A SignedPreKeyRecord ID. + * @return true if the store has a record for the signedPreKeyId, otherwise false. + */ + @Override + public boolean containsSignedPreKey(int signedPreKeyId) { + return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId); + } + + /** + * Delete a SignedPreKeyRecord from local storage. + * + * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. + */ + @Override + public void removeSignedPreKey(int signedPreKeyId) { + mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId); + } + } + + private static class XmppAxolotlSession { + private List untrustedMessages; + private AxolotlStore axolotlStore; + + public XmppAxolotlSession(SQLiteAxolotlStore axolotlStore) { + this.untrustedMessages = new ArrayList<>(); + this.axolotlStore = axolotlStore; + } + + public void trust() { + for (Message message : this.untrustedMessages) { + message.trust(); + } + this.untrustedMessages = null; + } + + public boolean isTrusted() { + return (this.untrustedMessages == null); + } + + public String processReceiving(XmppAxolotlMessage incomingMessage) { + return null; + } + + public XmppAxolotlMessage processSending(String outgoingMessage) { + return null; + } + } + + public AxolotlService(Account account, XmppConnectionService connectionService) { + this.mXmppConnectionService = connectionService; + this.account = account; + this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); + this.sessions = new HashMap<>(); + } + + public void trustSession(Jid counterpart) { + XmppAxolotlSession session = sessions.get(counterpart); + if(session != null) { + session.trust(); + } + } + + public boolean isTrustedSession(Jid counterpart) { + XmppAxolotlSession session = sessions.get(counterpart); + return session != null && session.isTrusted(); + } + + +} diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java new file mode 100644 index 000000000..b11670e4a --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -0,0 +1,4 @@ +package eu.siacs.conversations.crypto.axolotl; + +public class XmppAxolotlMessage { +} diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index f472361f2..383125667 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -254,6 +254,10 @@ public class Account extends AbstractEntity { return keys; } + public String getKey(final String name) { + return this.keys.optString(name, null); + } + public boolean setKey(final String keyName, final String keyValue) { try { this.keys.put(keyName, keyValue); diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index e546f2149..2f9b375df 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -2,15 +2,19 @@ package eu.siacs.conversations.entities; import android.content.ContentValues; import android.database.Cursor; +import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.InvalidKeyException; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.InvalidJidException; @@ -183,20 +187,22 @@ public class Contact implements ListItem, Blockable { } public ContentValues getContentValues() { - final ContentValues values = new ContentValues(); - values.put(ACCOUNT, accountUuid); - values.put(SYSTEMNAME, systemName); - values.put(SERVERNAME, serverName); - values.put(JID, jid.toString()); - values.put(OPTIONS, subscription); - values.put(SYSTEMACCOUNT, systemAccount); - values.put(PHOTOURI, photoUri); - values.put(KEYS, keys.toString()); - values.put(AVATAR, avatar == null ? null : avatar.getFilename()); - values.put(LAST_PRESENCE, lastseen.presence); - values.put(LAST_TIME, lastseen.time); - values.put(GROUPS, groups.toString()); - return values; + synchronized (this.keys) { + final ContentValues values = new ContentValues(); + values.put(ACCOUNT, accountUuid); + values.put(SYSTEMNAME, systemName); + values.put(SERVERNAME, serverName); + values.put(JID, jid.toString()); + values.put(OPTIONS, subscription); + values.put(SYSTEMACCOUNT, systemAccount); + values.put(PHOTOURI, photoUri); + values.put(KEYS, keys.toString()); + values.put(AVATAR, avatar == null ? null : avatar.getFilename()); + values.put(LAST_PRESENCE, lastseen.presence); + values.put(LAST_TIME, lastseen.time); + values.put(GROUPS, groups.toString()); + return values; + } } public int getSubscription() { @@ -281,63 +287,109 @@ public class Contact implements ListItem, Blockable { } public ArrayList getOtrFingerprints() { - final ArrayList fingerprints = new ArrayList(); - try { - if (this.keys.has("otr_fingerprints")) { - final JSONArray prints = this.keys.getJSONArray("otr_fingerprints"); - for (int i = 0; i < prints.length(); ++i) { - final String print = prints.isNull(i) ? null : prints.getString(i); - if (print != null && !print.isEmpty()) { - fingerprints.add(prints.getString(i)); + synchronized (this.keys) { + final ArrayList fingerprints = new ArrayList(); + try { + if (this.keys.has("otr_fingerprints")) { + final JSONArray prints = this.keys.getJSONArray("otr_fingerprints"); + for (int i = 0; i < prints.length(); ++i) { + final String print = prints.isNull(i) ? null : prints.getString(i); + if (print != null && !print.isEmpty()) { + fingerprints.add(prints.getString(i)); + } } } - } - } catch (final JSONException ignored) { + } catch (final JSONException ignored) { + } + return fingerprints; } - return fingerprints; } - public boolean addOtrFingerprint(String print) { - if (getOtrFingerprints().contains(print)) { - return false; + synchronized (this.keys) { + if (getOtrFingerprints().contains(print)) { + return false; + } + 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); + return true; + } catch (final JSONException ignored) { + return false; + } } - try { - JSONArray fingerprints; - if (!this.keys.has("otr_fingerprints")) { - fingerprints = new JSONArray(); + } + public long getPgpKeyId() { + synchronized (this.keys) { + if (this.keys.has("pgp_keyid")) { + try { + return this.keys.getLong("pgp_keyid"); + } catch (JSONException e) { + return 0; + } } else { - fingerprints = this.keys.getJSONArray("otr_fingerprints"); + return 0; } - fingerprints.put(print); - this.keys.put("otr_fingerprints", fingerprints); - return true; - } catch (final JSONException ignored) { - return false; } } - public long getPgpKeyId() { - if (this.keys.has("pgp_keyid")) { + public void setPgpKeyId(long keyId) { + synchronized (this.keys) { try { - return this.keys.getLong("pgp_keyid"); - } catch (JSONException e) { - return 0; + this.keys.put("pgp_keyid", keyId); + } catch (final JSONException ignored) { } - } else { - return 0; } } - public void setPgpKeyId(long keyId) { - try { - this.keys.put("pgp_keyid", keyId); - } catch (final JSONException ignored) { + public List getTrustedAxolotlIdentityKeys() { + synchronized (this.keys) { + JSONArray serializedKeyItems = this.keys.optJSONArray("axolotl_identity_key"); + List identityKeys = new ArrayList<>(); + if(serializedKeyItems != null) { + for(int i = 0; i getSubDeviceSessions(Account account, AxolotlAddress contact) { + List devices = new ArrayList<>(); + final SQLiteDatabase db = this.getReadableDatabase(); + String[] columns = {AxolotlService.SQLiteAxolotlStore.DEVICE_ID}; + String[] selectionArgs = {account.getUuid(), + contact.getName()}; + Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, + columns, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND ", + selectionArgs, + null, null, null); + + while(cursor.moveToNext()) { + devices.add(cursor.getInt( + cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.DEVICE_ID))); + } + + cursor.close(); + return devices; + } + + public boolean containsSession(Account account, AxolotlAddress contact) { + Cursor cursor = getCursorForSession(account, contact); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(AxolotlService.SQLiteAxolotlStore.NAME, contact.getName()); + values.put(AxolotlService.SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); + values.put(AxolotlService.SQLiteAxolotlStore.KEY, session.serialize()); + values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values); + } + + public void deleteSession(Account account, AxolotlAddress contact) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), + contact.getName(), + Integer.toString(contact.getDeviceId())}; + db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " + + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ", + args); + } + + public void deleteAllSessions(Account account, AxolotlAddress contact) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), contact.getName()}; + db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + + AxolotlService.SQLiteAxolotlStore.NAME + " = ?", + args); + } + + private Cursor getCursorForPreKey(Account account, int preKeyId) { + SQLiteDatabase db = this.getReadableDatabase(); + String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)}; + Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, + columns, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + + AxolotlService.SQLiteAxolotlStore.ID + "=?", + selectionArgs, + null, null, null); + + return cursor; + } + + public PreKeyRecord loadPreKey(Account account, int preKeyId) { + PreKeyRecord record = null; + Cursor cursor = getCursorForPreKey(account, preKeyId); + if(cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + record = new PreKeyRecord(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)).getBytes()); + } catch (IOException e ) { + throw new AssertionError(e); + } + } + cursor.close(); + return record; + } + + public boolean containsPreKey(Account account, int preKeyId) { + Cursor cursor = getCursorForPreKey(account, preKeyId); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storePreKey(Account account, PreKeyRecord record) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId()); + values.put(AxolotlService.SQLiteAxolotlStore.KEY, record.serialize()); + values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, null, values); + } + + public void deletePreKey(Account account, int preKeyId) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), Integer.toString(preKeyId)}; + db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + + AxolotlService.SQLiteAxolotlStore.ID + "=?", + args); + } + + private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) { + SQLiteDatabase db = this.getReadableDatabase(); + String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)}; + Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + columns, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + AxolotlService.SQLiteAxolotlStore.ID + "=?", + selectionArgs, + null, null, null); + + return cursor; + } + + public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) { + SignedPreKeyRecord record = null; + Cursor cursor = getCursorForPreKey(account, signedPreKeyId); + if(cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + record = new SignedPreKeyRecord(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)).getBytes()); + } catch (IOException e ) { + throw new AssertionError(e); + } + } + cursor.close(); + return record; + } + + public List loadSignedPreKeys(Account account) { + List prekeys = new ArrayList<>(); + SQLiteDatabase db = this.getReadableDatabase(); + String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid()}; + Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + columns, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=?", + selectionArgs, + null, null, null); + + while(cursor.moveToNext()) { + try { + prekeys.add(new SignedPreKeyRecord(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)).getBytes())); + } catch (IOException ignored) { + } + } + return prekeys; + } + + public boolean containsSignedPreKey(Account account, int signedPreKeyId) { + Cursor cursor = getCursorForPreKey(account, signedPreKeyId); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storeSignedPreKey(Account account, SignedPreKeyRecord record) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId()); + values.put(AxolotlService.SQLiteAxolotlStore.KEY, record.serialize()); + values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values); + } + + public void deleteSignedPreKey(Account account, int signedPreKeyId) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)}; + db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + + AxolotlService.SQLiteAxolotlStore.ID + "=?", + args); + } } -- cgit v1.2.3 From f73aa1a2006beb741bc39026bfd10e6166d7951a Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Thu, 25 Jun 2015 16:56:34 +0200 Subject: Reworked axolotl protocol layer Numerous fixes --- .../crypto/axolotl/AxolotlService.java | 310 ++++++++++++++++++--- .../crypto/axolotl/NoSessionsCreatedException.java | 4 + .../crypto/axolotl/XmppAxolotlMessage.java | 180 ++++++++++++ .../eu/siacs/conversations/entities/Account.java | 7 + .../eu/siacs/conversations/entities/Contact.java | 62 +++-- .../eu/siacs/conversations/entities/Message.java | 14 +- .../conversations/persistance/DatabaseBackend.java | 90 ++++-- .../services/XmppConnectionService.java | 7 +- 8 files changed, 579 insertions(+), 95 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/crypto/axolotl/NoSessionsCreatedException.java (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 8e3002487..eae7a9abc 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -1,15 +1,29 @@ package eu.siacs.conversations.crypto.axolotl; +import android.util.Base64; import android.util.Log; import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.DuplicateMessageException; import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.IdentityKeyPair; import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.libaxolotl.InvalidVersionException; +import org.whispersystems.libaxolotl.LegacyMessageException; +import org.whispersystems.libaxolotl.NoSessionException; +import org.whispersystems.libaxolotl.SessionBuilder; +import org.whispersystems.libaxolotl.SessionCipher; +import org.whispersystems.libaxolotl.UntrustedIdentityException; import org.whispersystems.libaxolotl.ecc.Curve; import org.whispersystems.libaxolotl.ecc.ECKeyPair; +import org.whispersystems.libaxolotl.ecc.ECPublicKey; +import org.whispersystems.libaxolotl.protocol.CiphertextMessage; +import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; +import org.whispersystems.libaxolotl.protocol.WhisperMessage; import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.libaxolotl.state.PreKeyBundle; import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; @@ -17,43 +31,50 @@ import org.whispersystems.libaxolotl.util.KeyHelper; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Random; +import java.util.Set; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; public class AxolotlService { - private Account account; - private XmppConnectionService mXmppConnectionService; - private SQLiteAxolotlStore axolotlStore; - private Map sessions; + private final Account account; + private final XmppConnectionService mXmppConnectionService; + private final SQLiteAxolotlStore axolotlStore; + private final SessionMap sessions; + private int ownDeviceId; public static class SQLiteAxolotlStore implements AxolotlStore { public static final String PREKEY_TABLENAME = "prekeys"; public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys"; - public static final String SESSION_TABLENAME = "signed_prekeys"; - public static final String NAME = "name"; + public static final String SESSION_TABLENAME = "sessions"; + public static final String ACCOUNT = "account"; public static final String DEVICE_ID = "device_id"; public static final String ID = "id"; public static final String KEY = "key"; - public static final String ACCOUNT = "account"; + public static final String NAME = "name"; + public static final String TRUSTED = "trusted"; public static final String JSONKEY_IDENTITY_KEY_PAIR = "axolotl_key"; public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; + public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id"; private final Account account; private final XmppConnectionService mXmppConnectionService; private final IdentityKeyPair identityKeyPair; private final int localRegistrationId; + private int currentPreKeyId = 0; private static IdentityKeyPair generateIdentityKeyPair() { @@ -75,6 +96,14 @@ public class AxolotlService { this.mXmppConnectionService = service; this.identityKeyPair = loadIdentityKeyPair(); this.localRegistrationId = loadRegistrationId(); + this.currentPreKeyId = loadCurrentPreKeyId(); + for( SignedPreKeyRecord record:loadSignedPreKeys()) { + Log.d(Config.LOGTAG, "Got Axolotl signed prekey record:" + record.getId()); + } + } + + public int getCurrentPreKeyId() { + return currentPreKeyId; } // -------------------------------------- @@ -86,21 +115,22 @@ public class AxolotlService { IdentityKeyPair ownKey; if( serializedKey != null ) { try { - ownKey = new IdentityKeyPair(serializedKey.getBytes()); + ownKey = new IdentityKeyPair(Base64.decode(serializedKey,Base64.DEFAULT)); + return ownKey; } catch (InvalidKeyException e) { Log.d(Config.LOGTAG, "Invalid key stored for account " + account.getJid() + ": " + e.getMessage()); - return null; +// return null; } - } else { + } //else { Log.d(Config.LOGTAG, "Could not retrieve axolotl key for account " + account.getJid()); ownKey = generateIdentityKeyPair(); - boolean success = this.account.setKey(JSONKEY_IDENTITY_KEY_PAIR, new String(ownKey.serialize())); + boolean success = this.account.setKey(JSONKEY_IDENTITY_KEY_PAIR, Base64.encodeToString(ownKey.serialize(), Base64.DEFAULT)); if(success) { mXmppConnectionService.databaseBackend.updateAccount(account); } else { Log.e(Config.LOGTAG, "Failed to write new key to the database!"); } - } + //} return ownKey; } @@ -122,6 +152,19 @@ public class AxolotlService { return reg_id; } + private int loadCurrentPreKeyId() { + String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID); + int reg_id; + if (regIdString != null) { + reg_id = Integer.valueOf(regIdString); + } else { + Log.d(Config.LOGTAG, "Could not retrieve current prekey id for account " + account.getJid()); + reg_id = 0; + } + return reg_id; + } + + /** * Get the local client's identity key pair. * @@ -159,7 +202,7 @@ public class AxolotlService { Jid contactJid = Jid.fromString(name); Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid); if (conversation != null) { - conversation.getContact().addAxolotlIdentityKey(identityKey, false); + conversation.getContact().addAxolotlIdentityKey(identityKey); mXmppConnectionService.updateConversationUi(); mXmppConnectionService.syncRosterToDisk(conversation.getAccount()); } @@ -188,8 +231,8 @@ public class AxolotlService { Jid contactJid = Jid.fromString(name); Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid); if (conversation != null) { - List trustedKeys = conversation.getContact().getTrustedAxolotlIdentityKeys(); - return trustedKeys.contains(identityKey); + List trustedKeys = conversation.getContact().getAxolotlIdentityKeys(); + return trustedKeys.isEmpty() || trustedKeys.contains(identityKey); } else { return false; } @@ -274,7 +317,15 @@ public class AxolotlService { @Override public void deleteAllSessions(String name) { mXmppConnectionService.databaseBackend.deleteAllSessions(account, - new AxolotlAddress(name,0)); + new AxolotlAddress(name, 0)); + } + + public boolean isTrustedSession(AxolotlAddress address) { + return mXmppConnectionService.databaseBackend.isTrustedSession(this.account, address); + } + + public void setTrustedSession(AxolotlAddress address, boolean trusted) { + mXmppConnectionService.databaseBackend.setTrustedSession(this.account, address,trusted); } // -------------------------------------- @@ -292,7 +343,7 @@ public class AxolotlService { public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId); if(record == null) { - throw new InvalidKeyIdException("No such PreKeyRecord!"); + throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId); } return record; } @@ -306,6 +357,13 @@ public class AxolotlService { @Override public void storePreKey(int preKeyId, PreKeyRecord record) { mXmppConnectionService.databaseBackend.storePreKey(account, record); + currentPreKeyId = preKeyId; + boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID,Integer.toString(preKeyId)); + if(success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, "Failed to write new prekey id to the database!"); + } } /** @@ -342,7 +400,7 @@ public class AxolotlService { public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId); if(record == null) { - throw new InvalidKeyIdException("No such PreKeyRecord!"); + throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId); } return record; } @@ -388,53 +446,229 @@ public class AxolotlService { } } - private static class XmppAxolotlSession { - private List untrustedMessages; - private AxolotlStore axolotlStore; - - public XmppAxolotlSession(SQLiteAxolotlStore axolotlStore) { - this.untrustedMessages = new ArrayList<>(); - this.axolotlStore = axolotlStore; + public static class XmppAxolotlSession { + private SessionCipher cipher; + private boolean isTrusted = false; + private SQLiteAxolotlStore sqLiteAxolotlStore; + private AxolotlAddress remoteAddress; + + public XmppAxolotlSession(SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { + this.cipher = new SessionCipher(store, remoteAddress); + this.remoteAddress = remoteAddress; + this.sqLiteAxolotlStore = store; + this.isTrusted = sqLiteAxolotlStore.isTrustedSession(remoteAddress); } public void trust() { - for (Message message : this.untrustedMessages) { - message.trust(); - } - this.untrustedMessages = null; + sqLiteAxolotlStore.setTrustedSession(remoteAddress, true); + this.isTrusted = true; } public boolean isTrusted() { - return (this.untrustedMessages == null); + return this.isTrusted; } - public String processReceiving(XmppAxolotlMessage incomingMessage) { - return null; + public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { + byte[] plaintext = null; + try { + try { + PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); + Log.d(Config.LOGTAG,"PreKeyWhisperMessage ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); + plaintext = cipher.decrypt(message); + } catch (InvalidMessageException|InvalidVersionException e) { + WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); + plaintext = cipher.decrypt(message); + } catch (InvalidKeyException|InvalidKeyIdException| UntrustedIdentityException e) { + Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); + } + } catch (LegacyMessageException|InvalidMessageException e) { + Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); + } catch (DuplicateMessageException|NoSessionException e) { + Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); + } + return plaintext; } - public XmppAxolotlMessage processSending(String outgoingMessage) { - return null; + public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(byte[] outgoingMessage) { + CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); + XmppAxolotlMessage.XmppAxolotlMessageHeader header = + new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), + ciphertextMessage.serialize()); + return header; } } + private static class AxolotlAddressMap { + protected Map> map; + protected final Object MAP_LOCK = new Object(); + + public AxolotlAddressMap() { + this.map = new HashMap<>(); + } + + public void put(AxolotlAddress address, T value) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + if (devices == null) { + devices = new HashMap<>(); + map.put(address.getName(), devices); + } + devices.put(address.getDeviceId(), value); + } + } + + public T get(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + if(devices == null) { + return null; + } + return devices.get(address.getDeviceId()); + } + } + + public Map getAll(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + if(devices == null) { + return new HashMap<>(); + } + return devices; + } + } + + public boolean hasAny(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + return devices != null && !devices.isEmpty(); + } + } + + + } + + private static class SessionMap extends AxolotlAddressMap { + + public SessionMap(SQLiteAxolotlStore store, Account account) { + super(); + this.fillMap(store, account); + } + + private void fillMap(SQLiteAxolotlStore store, Account account) { + for(Contact contact:account.getRoster().getContacts()){ + Jid bareJid = contact.getJid().toBareJid(); + if(bareJid == null) { + continue; // FIXME: handle this? + } + String address = bareJid.toString(); + List deviceIDs = store.getSubDeviceSessions(address); + for(Integer deviceId:deviceIDs) { + AxolotlAddress axolotlAddress = new AxolotlAddress(address, deviceId); + this.put(axolotlAddress, new XmppAxolotlSession(store, axolotlAddress)); + } + } + } + + } + + } + public AxolotlService(Account account, XmppConnectionService connectionService) { this.mXmppConnectionService = connectionService; this.account = account; this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); - this.sessions = new HashMap<>(); + this.sessions = new SessionMap(axolotlStore, account); + this.ownDeviceId = axolotlStore.getLocalRegistrationId(); } - public void trustSession(Jid counterpart) { + public void trustSession(AxolotlAddress counterpart) { XmppAxolotlSession session = sessions.get(counterpart); if(session != null) { session.trust(); } } - public boolean isTrustedSession(Jid counterpart) { + public boolean isTrustedSession(AxolotlAddress counterpart) { XmppAxolotlSession session = sessions.get(counterpart); return session != null && session.isTrusted(); } + private AxolotlAddress getAddressForJid(Jid jid) { + return new AxolotlAddress(jid.toString(), 0); + } + + private Set findOwnSessions() { + AxolotlAddress ownAddress = getAddressForJid(account.getJid()); + Set ownDeviceSessions = new HashSet<>(this.sessions.getAll(ownAddress).values()); + return ownDeviceSessions; + } + private Set findSessionsforContact(Contact contact) { + AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); + Set sessions = new HashSet<>(this.sessions.getAll(contactAddress).values()); + return sessions; + } + + private boolean hasAny(Contact contact) { + AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); + return sessions.hasAny(contactAddress); + } + + public int getOwnDeviceId() { + return ownDeviceId; + } + + private void createSessionsIfNeeded(Contact contact) throws NoSessionsCreatedException { + } + + public XmppAxolotlMessage processSending(Contact contact, String outgoingMessage) throws NoSessionsCreatedException { + XmppAxolotlMessage message = new XmppAxolotlMessage(contact, ownDeviceId, outgoingMessage); + createSessionsIfNeeded(contact); + Log.d(Config.LOGTAG, "Building axolotl foreign headers..."); + + for(XmppAxolotlSession session : findSessionsforContact(contact)) { +// if(!session.isTrusted()) { + // TODO: handle this properly + // continue; + // } + message.addHeader(session.processSending(message.getInnerKey())); + } + Log.d(Config.LOGTAG, "Building axolotl own headers..."); + for(XmppAxolotlSession session : findOwnSessions()) { + // if(!session.isTrusted()) { + // TODO: handle this properly + // continue; + // } + message.addHeader(session.processSending(message.getInnerKey())); + } + + return message; + } + + public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; + AxolotlAddress senderAddress = new AxolotlAddress(message.getContact().getJid().toBareJid().toString(), + message.getSenderDeviceId()); + + XmppAxolotlSession session = sessions.get(senderAddress); + if (session == null) { + Log.d(Config.LOGTAG, "No axolotl session found while parsing received message " + message); + // TODO: handle this properly + session = new XmppAxolotlSession(axolotlStore, senderAddress); + + } + + for(XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { + if (header.getRecipientDeviceId() == ownDeviceId) { + Log.d(Config.LOGTAG, "Found axolotl header matching own device ID, processing..."); + byte[] payloadKey = session.processReceiving(header); + if (payloadKey != null) { + Log.d(Config.LOGTAG, "Got payload key from axolotl header. Decrypting message..."); + plaintextMessage = message.decrypt(session, payloadKey); + } + } + } + + return plaintextMessage; + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/NoSessionsCreatedException.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/NoSessionsCreatedException.java new file mode 100644 index 000000000..663b42b58 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/NoSessionsCreatedException.java @@ -0,0 +1,4 @@ +package eu.siacs.conversations.crypto.axolotl; + +public class NoSessionsCreatedException extends Throwable{ +} diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index b11670e4a..4b87fc5cb 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -1,4 +1,184 @@ package eu.siacs.conversations.crypto.axolotl; +import android.util.Base64; + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.InvalidKeyException; +import java.util.HashSet; +import java.util.Set; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.xml.Element; + public class XmppAxolotlMessage { + private byte[] innerKey; + private byte[] ciphertext; + private byte[] iv; + private final Set headers; + private final Contact contact; + private final int sourceDeviceId; + + public static class XmppAxolotlMessageHeader { + private final int recipientDeviceId; + private final byte[] content; + + public XmppAxolotlMessageHeader(int deviceId, byte[] content) { + this.recipientDeviceId = deviceId; + this.content = content; + } + + public XmppAxolotlMessageHeader(Element header) { + if("header".equals(header.getName())) { + this.recipientDeviceId = Integer.parseInt(header.getAttribute("rid")); + this.content = Base64.decode(header.getContent(),Base64.DEFAULT); + } else { + throw new IllegalArgumentException("Argument not a

Element!"); + } + } + + public int getRecipientDeviceId() { + return recipientDeviceId; + } + + public byte[] getContents() { + return content; + } + + public Element toXml() { + Element headerElement = new Element("header"); + // TODO: generate XML + headerElement.setAttribute("rid", getRecipientDeviceId()); + headerElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); + return headerElement; + } + } + + public static class XmppAxolotlPlaintextMessage { + private final AxolotlService.XmppAxolotlSession session; + private final String plaintext; + + public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext) { + this.session = session; + this.plaintext = plaintext; + } + + public String getPlaintext() { + return plaintext; + } + } + + public XmppAxolotlMessage(Contact contact, Element axolotlMessage) { + this.contact = contact; + this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id")); + this.headers = new HashSet<>(); + for(Element child:axolotlMessage.getChildren()) { + switch(child.getName()) { + case "header": + headers.add(new XmppAxolotlMessageHeader(child)); + break; + case "message": + iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT); + ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT); + break; + default: + break; + } + } + } + + public XmppAxolotlMessage(Contact contact, int sourceDeviceId, String plaintext) { + this.contact = contact; + this.sourceDeviceId = sourceDeviceId; + this.headers = new HashSet<>(); + this.encrypt(plaintext); + } + + private void encrypt(String plaintext) { + try { + KeyGenerator generator = KeyGenerator.getInstance("AES"); + generator.init(128); + SecretKey secretKey = generator.generateKey(); + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + this.innerKey = secretKey.getEncoded(); + this.iv = cipher.getIV(); + this.ciphertext = cipher.doFinal(plaintext.getBytes()); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | IllegalBlockSizeException | BadPaddingException e) { + + } + } + + public Contact getContact() { + return this.contact; + } + + public int getSenderDeviceId() { + return sourceDeviceId; + } + + public byte[] getCiphertext() { + return ciphertext; + } + + public Set getHeaders() { + return headers; + } + + public void addHeader(XmppAxolotlMessageHeader header) { + headers.add(header); + } + + public byte[] getInnerKey(){ + return innerKey; + } + + public byte[] getIV() { + return this.iv; + } + + public Element toXml() { + // TODO: generate outer XML, add in header XML + Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX); + message.setAttribute("id", sourceDeviceId); + for(XmppAxolotlMessageHeader header: headers) { + message.addChild(header.toXml()); + } + Element payload = message.addChild("message"); + payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT)); + payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT)); + return message; + } + + + public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key) { + XmppAxolotlPlaintextMessage plaintextMessage = null; + try { + + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + + String plaintext = new String(cipher.doFinal(ciphertext)); + plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext); + + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | InvalidAlgorithmParameterException | IllegalBlockSizeException + | BadPaddingException e) { + throw new AssertionError(e); + } + return plaintextMessage; + } } diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 383125667..7a2dc3f76 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -20,6 +20,7 @@ import java.util.concurrent.CopyOnWriteArraySet; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.OtrService; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jid.InvalidJidException; @@ -122,6 +123,7 @@ public class Account extends AbstractEntity { protected String avatar; protected boolean online = false; private OtrService mOtrService = null; + private AxolotlService axolotlService = null; private XmppConnection xmppConnection = null; private long mEndGracePeriod = 0L; private String otrFingerprint; @@ -281,8 +283,13 @@ public class Account extends AbstractEntity { return values; } + public AxolotlService getAxolotlService() { + return axolotlService; + } + public void initAccountServices(final XmppConnectionService context) { this.mOtrService = new OtrService(context, this); + this.axolotlService = new AxolotlService(this, context); } public OtrService getOtrService() { diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 2f9b375df..45b55e49d 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.entities; import android.content.ContentValues; import android.database.Cursor; +import android.util.Base64; import android.util.Log; import org.json.JSONArray; @@ -349,20 +350,38 @@ public class Contact implements ListItem, Blockable { } } - public List getTrustedAxolotlIdentityKeys() { + public List getAxolotlIdentityKeys() { synchronized (this.keys) { JSONArray serializedKeyItems = this.keys.optJSONArray("axolotl_identity_key"); List identityKeys = new ArrayList<>(); + List toDelete = new ArrayList<>(); if(serializedKeyItems != null) { for(int i = 0; i= 15) { + db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME); + db.execSQL(CREATE_SESSIONS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME); + db.execSQL(CREATE_PREKEYS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); + db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); + } } public static synchronized DatabaseBackend getInstance(Context context) { @@ -547,7 +561,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if(cursor.getCount() != 0) { cursor.moveToFirst(); try { - session = new SessionRecord(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)).getBytes()); + session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (IOException e) { throw new AssertionError(e); } @@ -590,7 +604,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { ContentValues values = new ContentValues(); values.put(AxolotlService.SQLiteAxolotlStore.NAME, contact.getName()); values.put(AxolotlService.SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); - values.put(AxolotlService.SQLiteAxolotlStore.KEY, session.serialize()); + values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT)); values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values); } @@ -616,6 +630,28 @@ public class DatabaseBackend extends SQLiteOpenHelper { args); } + public boolean isTrustedSession(Account account, AxolotlAddress contact) { + boolean trusted = false; + Cursor cursor = getCursorForSession(account, contact); + if(cursor.getCount() != 0) { + cursor.moveToFirst(); + trusted = cursor.getInt(cursor.getColumnIndex( + AxolotlService.SQLiteAxolotlStore.TRUSTED)) > 0; + } + cursor.close(); + return trusted; + } + + public void setTrustedSession(Account account, AxolotlAddress contact, boolean trusted) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(AxolotlService.SQLiteAxolotlStore.NAME, contact.getName()); + values.put(AxolotlService.SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); + values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted?1:0); + db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values); + } + private Cursor getCursorForPreKey(Account account, int preKeyId) { SQLiteDatabase db = this.getReadableDatabase(); String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; @@ -636,7 +672,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if(cursor.getCount() != 0) { cursor.moveToFirst(); try { - record = new PreKeyRecord(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)).getBytes()); + record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (IOException e ) { throw new AssertionError(e); } @@ -656,7 +692,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId()); - values.put(AxolotlService.SQLiteAxolotlStore.KEY, record.serialize()); + values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); db.insert(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, null, values); } @@ -685,11 +721,11 @@ public class DatabaseBackend extends SQLiteOpenHelper { public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) { SignedPreKeyRecord record = null; - Cursor cursor = getCursorForPreKey(account, signedPreKeyId); + Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId); if(cursor.getCount() != 0) { cursor.moveToFirst(); try { - record = new SignedPreKeyRecord(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)).getBytes()); + record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (IOException e ) { throw new AssertionError(e); } @@ -711,7 +747,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { while(cursor.moveToNext()) { try { - prekeys.add(new SignedPreKeyRecord(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)).getBytes())); + prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)), Base64.DEFAULT))); } catch (IOException ignored) { } } @@ -729,7 +765,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId()); - values.put(AxolotlService.SQLiteAxolotlStore.KEY, record.serialize()); + values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); db.insert(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index f5c54adf5..bed9267bb 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -52,6 +52,7 @@ import de.duenndns.ssl.MemorizingTrustManager; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.crypto.axolotl.NoSessionsCreatedException; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Bookmark; @@ -273,7 +274,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } syncDirtyContacts(account); - scheduleWakeUpCall(Config.PING_MAX_INTERVAL,account.getUuid().hashCode()); + account.getAxolotlService().publishOwnDeviceIdIfNeeded(); + account.getAxolotlService().publishBundleIfNeeded(); + account.getAxolotlService().publishPreKeysIfNeeded(); + + scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); } else if (account.getStatus() == Account.State.OFFLINE) { resetSendingToWaiting(account); if (!account.isOptionSet(Account.OPTION_DISABLED)) { -- cgit v1.2.3 From 77619b55e47aafbe6b5a530b33ce8b6a77a615dd Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Thu, 25 Jun 2015 16:58:24 +0200 Subject: Added PEP and message protocol layers Can now fetch/retrieve from PEP, as well as encode/decode messages --- .../crypto/axolotl/AxolotlService.java | 208 +++++++++++++++++++++ .../conversations/generator/AbstractGenerator.java | 4 +- .../siacs/conversations/generator/IqGenerator.java | 70 +++++++ .../conversations/generator/MessageGenerator.java | 21 +++ .../eu/siacs/conversations/parser/IqParser.java | 158 +++++++++++++++- .../siacs/conversations/parser/MessageParser.java | 46 ++++- .../java/eu/siacs/conversations/xml/Element.java | 5 + .../conversations/xmpp/stanzas/MessagePacket.java | 5 + 8 files changed, 509 insertions(+), 8 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index eae7a9abc..865f903ae 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -41,16 +41,26 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class AxolotlService { + public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl"; + public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist"; + public static final String PEP_PREKEYS = PEP_PREFIX + ".prekeys"; + public static final String PEP_BUNDLE = PEP_PREFIX + ".bundle"; + private final Account account; private final XmppConnectionService mXmppConnectionService; private final SQLiteAxolotlStore axolotlStore; private final SessionMap sessions; + private final BundleMap bundleCache; private int ownDeviceId; public static class SQLiteAxolotlStore implements AxolotlStore { @@ -571,6 +581,8 @@ public class AxolotlService { } + private static class BundleMap extends AxolotlAddressMap { + } public AxolotlService(Account account, XmppConnectionService connectionService) { @@ -578,6 +590,7 @@ public class AxolotlService { this.account = account; this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); this.sessions = new SessionMap(axolotlStore, account); + this.bundleCache = new BundleMap(); this.ownDeviceId = axolotlStore.getLocalRegistrationId(); } @@ -618,7 +631,202 @@ public class AxolotlService { return ownDeviceId; } + public void fetchBundleIfNeeded(final Contact contact, final Integer deviceId) { + final AxolotlAddress address = new AxolotlAddress(contact.getJid().toString(), deviceId); + if (sessions.get(address) != null) { + return; + } + + synchronized (bundleCache) { + PreKeyBundle bundle = bundleCache.get(address); + if (bundle == null) { + bundle = new PreKeyBundle(0, deviceId, 0, null, 0, null, null, null); + bundleCache.put(address, bundle); + } + + if(bundle.getPreKey() == null) { + Log.d(Config.LOGTAG, "No preKey in cache, fetching..."); + IqPacket prekeysPacket = mXmppConnectionService.getIqGenerator().retrievePreKeysForDevice(contact.getJid(), deviceId); + mXmppConnectionService.sendIqPacket(account, prekeysPacket, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + synchronized (bundleCache) { + Log.d(Config.LOGTAG, "Received preKey IQ packet, processing..."); + final IqParser parser = mXmppConnectionService.getIqParser(); + final PreKeyBundle bundle = bundleCache.get(address); + final List preKeyBundleList = parser.preKeys(packet); + if (preKeyBundleList.isEmpty()) { + Log.d(Config.LOGTAG, "preKey IQ packet invalid: " + packet); + return; + } + Random random = new Random(); + final PreKeyBundle newBundle = preKeyBundleList.get(random.nextInt(preKeyBundleList.size())); + if (bundle == null || newBundle == null) { + //should never happen + return; + } + + final PreKeyBundle mergedBundle = new PreKeyBundle(bundle.getRegistrationId(), + bundle.getDeviceId(), newBundle.getPreKeyId(), newBundle.getPreKey(), + bundle.getSignedPreKeyId(), bundle.getSignedPreKey(), + bundle.getSignedPreKeySignature(), bundle.getIdentityKey()); + + bundleCache.put(address, mergedBundle); + } + } + }); + } + if(bundle.getIdentityKey() == null) { + Log.d(Config.LOGTAG, "No bundle in cache, fetching..."); + IqPacket bundlePacket = mXmppConnectionService.getIqGenerator().retrieveBundleForDevice(contact.getJid(), deviceId); + mXmppConnectionService.sendIqPacket(account, bundlePacket, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + synchronized (bundleCache) { + Log.d(Config.LOGTAG, "Received bundle IQ packet, processing..."); + final IqParser parser = mXmppConnectionService.getIqParser(); + final PreKeyBundle bundle = bundleCache.get(address); + final PreKeyBundle newBundle = parser.bundle(packet); + if( bundle == null || newBundle == null ) { + Log.d(Config.LOGTAG, "bundle IQ packet invalid: " + packet); + //should never happen + return; + } + + final PreKeyBundle mergedBundle = new PreKeyBundle(bundle.getRegistrationId(), + bundle.getDeviceId(), bundle.getPreKeyId(), bundle.getPreKey(), + newBundle.getSignedPreKeyId(), newBundle.getSignedPreKey(), + newBundle.getSignedPreKeySignature(), newBundle.getIdentityKey()); + + axolotlStore.saveIdentity(contact.getJid().toBareJid().toString(), newBundle.getIdentityKey()); + bundleCache.put(address, mergedBundle); + } + } + }); + } + } + } + + public void publishOwnDeviceIdIfNeeded() { + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Element item = mXmppConnectionService.getIqParser().getItem(packet); + List deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + if(deviceIds == null) { + deviceIds = new ArrayList<>(); + } + if(!deviceIds.contains(getOwnDeviceId())) { + Log.d(Config.LOGTAG, "Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing..."); + deviceIds.add(getOwnDeviceId()); + IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + // TODO: implement this! + } + }); + } + } + }); + } + + public void publishBundleIfNeeded() { + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundleForDevice(account.getJid().toBareJid(), ownDeviceId); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); + if(bundle == null) { + Log.d(Config.LOGTAG, "Bundle " + getOwnDeviceId() + " not in PEP. Publishing..."); + int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size(); + try { + SignedPreKeyRecord signedPreKeyRecord = KeyHelper.generateSignedPreKey( + axolotlStore.getIdentityKeyPair(), numSignedPreKeys + 1); + axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundle( + signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), + ownDeviceId); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + // TODO: implement this! + Log.d(Config.LOGTAG, "Published bundle, got: " + packet); + } + }); + } catch (InvalidKeyException e) { + Log.e(Config.LOGTAG, "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); + } + } + } + }); + } + + public void publishPreKeysIfNeeded() { + IqPacket packet = mXmppConnectionService.getIqGenerator().retrievePreKeysForDevice(account.getJid().toBareJid(), ownDeviceId); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); + if(keys == null || keys.isEmpty()) { + Log.d(Config.LOGTAG, "Prekeys " + getOwnDeviceId() + " not in PEP. Publishing..."); + List preKeyRecords = KeyHelper.generatePreKeys( + axolotlStore.getCurrentPreKeyId(), 100); + for(PreKeyRecord record : preKeyRecords) { + axolotlStore.storePreKey(record.getId(), record); + } + IqPacket publish = mXmppConnectionService.getIqGenerator().publishPreKeys( + preKeyRecords, ownDeviceId); + + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Log.d(Config.LOGTAG, "Published prekeys, got: " + packet); + // TODO: implement this! + } + }); + } + } + }); + } + + + public boolean isContactAxolotlCapable(Contact contact) { + AxolotlAddress address = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0); + return sessions.hasAny(address) || bundleCache.hasAny(address); + } + + public void initiateSynchronousSession(Contact contact) { + + } + private void createSessionsIfNeeded(Contact contact) throws NoSessionsCreatedException { + Log.d(Config.LOGTAG, "Creating axolotl sessions if needed..."); + AxolotlAddress address = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0); + for(Integer deviceId: bundleCache.getAll(address).keySet()) { + Log.d(Config.LOGTAG, "Processing device ID: " + deviceId); + AxolotlAddress remoteAddress = new AxolotlAddress(contact.getJid().toBareJid().toString(), deviceId); + if(sessions.get(remoteAddress) == null) { + Log.d(Config.LOGTAG, "Building new sesstion for " + deviceId); + SessionBuilder builder = new SessionBuilder(this.axolotlStore, remoteAddress); + try { + builder.process(bundleCache.get(remoteAddress)); + XmppAxolotlSession session = new XmppAxolotlSession(this.axolotlStore, remoteAddress); + sessions.put(remoteAddress, session); + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, "Error building session for " + deviceId+ ": InvalidKeyException, " +e.getMessage()); + } catch (UntrustedIdentityException e) { + Log.d(Config.LOGTAG, "Error building session for " + deviceId+ ": UntrustedIdentityException, " +e.getMessage()); + } + } else { + Log.d(Config.LOGTAG, "Already have session for " + deviceId); + } + } + if(!this.hasAny(contact)) { + Log.e(Config.LOGTAG, "No Axolotl sessions available!"); + throw new NoSessionsCreatedException(); // FIXME: proper error handling + } } public XmppAxolotlMessage processSending(Contact contact, String outgoingMessage) throws NoSessionsCreatedException { diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index 79626511a..69bb18036 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.PhoneHelper; @@ -28,7 +29,8 @@ public abstract class AbstractGenerator { "urn:xmpp:avatar:metadata+notify", "urn:xmpp:ping", "jabber:iq:version", - "http://jabber.org/protocol/chatstates"}; + "http://jabber.org/protocol/chatstates", + AxolotlService.PEP_DEVICE_LIST+"+notify"}; private final String[] MESSAGE_CONFIRMATION_FEATURES = { "urn:xmpp:chat-markers:0", "urn:xmpp:receipts" diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 47915e3fb..6daadc2af 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -1,9 +1,17 @@ package eu.siacs.conversations.generator; +import android.util.Base64; + +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.ecc.ECPublicKey; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; + import java.util.ArrayList; import java.util.List; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.DownloadableFile; @@ -115,6 +123,68 @@ public class IqGenerator extends AbstractGenerator { return packet; } + public IqPacket retrieveDeviceIds(final Jid to) { + final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null); + if(to != null) { + packet.setTo(to); + } + return packet; + } + + public IqPacket retrieveBundleForDevice(final Jid to, final int deviceid) { + final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLE+":"+deviceid, null); + if(to != null) { + packet.setTo(to); + } + return packet; + } + + public IqPacket retrievePreKeysForDevice(final Jid to, final int deviceId) { + final IqPacket packet = retrieve(AxolotlService.PEP_PREKEYS+":"+deviceId, null); + if(to != null) { + packet.setTo(to); + } + return packet; + } + + public IqPacket publishDeviceIds(final List ids) { + final Element item = new Element("item"); + final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); + for(Integer id:ids) { + final Element device = new Element("device"); + device.setAttribute("id", id); + list.addChild(device); + } + return publish(AxolotlService.PEP_DEVICE_LIST, item); + } + + public IqPacket publishBundle(final SignedPreKeyRecord signedPreKeyRecord, IdentityKey identityKey, final int deviceId) { + final Element item = new Element("item"); + final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX); + final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic"); + signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId()); + ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey(); + signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(),Base64.DEFAULT)); + final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature"); + signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(),Base64.DEFAULT)); + final Element identityKeyElement = bundle.addChild("identityKey"); + identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); + + return publish(AxolotlService.PEP_BUNDLE+":"+deviceId, item); + } + + public IqPacket publishPreKeys(final List prekeyList, final int deviceId) { + final Element item = new Element("item"); + final Element prekeys = item.addChild("prekeys", AxolotlService.PEP_PREFIX); + for(PreKeyRecord preKeyRecord:prekeyList) { + final Element prekey = prekeys.addChild("preKeyPublic"); + prekey.setAttribute("preKeyId", preKeyRecord.getId()); + prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT)); + } + + return publish(AxolotlService.PEP_PREKEYS+":"+deviceId, item); + } + public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Element query = packet.query("urn:xmpp:mam:0"); diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index bc1148d9a..e6032e0cb 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.generator; +import android.util.Log; + import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; @@ -7,6 +9,11 @@ import java.util.TimeZone; import net.java.otr4j.OtrException; import net.java.otr4j.session.Session; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.NoSessionsCreatedException; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; @@ -59,6 +66,20 @@ public class MessageGenerator extends AbstractGenerator { delay.setAttribute("stamp", mDateFormat.format(date)); } + public MessagePacket generateAxolotlChat(Message message) throws NoSessionsCreatedException{ + return generateAxolotlChat(message, false); + } + + public MessagePacket generateAxolotlChat(Message message, boolean addDelay) throws NoSessionsCreatedException{ + MessagePacket packet = preparePacket(message, addDelay); + AxolotlService service = message.getConversation().getAccount().getAxolotlService(); + Log.d(Config.LOGTAG, "Submitting message to axolotl service for send processing..."); + XmppAxolotlMessage axolotlMessage = service.processSending(message.getContact(), + message.getBody()); + packet.setAxolotlMessage(axolotlMessage.toXml()); + return packet; + } + public MessagePacket generateOtrChat(Message message) { return generateOtrChat(message, false); } diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 6039d3956..00f968858 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -1,9 +1,19 @@ package eu.siacs.conversations.parser; +import android.util.Base64; import android.util.Log; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.ecc.Curve; +import org.whispersystems.libaxolotl.ecc.ECPublicKey; +import org.whispersystems.libaxolotl.state.PreKeyBundle; + import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; @@ -60,7 +70,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { public String avatarData(final IqPacket packet) { final Element pubsub = packet.findChild("pubsub", - "http://jabber.org/protocol/pubsub"); + "http://jabber.org/protocol/pubsub"); if (pubsub == null) { return null; } @@ -71,6 +81,152 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { return super.avatarData(items); } + public Element getItem(final IqPacket packet) { + final Element pubsub = packet.findChild("pubsub", + "http://jabber.org/protocol/pubsub"); + if (pubsub == null) { + return null; + } + final Element items = pubsub.findChild("items"); + if (items == null) { + return null; + } + return items.findChild("item"); + } + + public List deviceIds(final Element item) { + List deviceIds = new ArrayList<>(); + if (item == null) { + return null; + } + final Element list = item.findChild("list"); + if(list == null) { + return null; + } + for(Element device : list.getChildren()) { + if(!device.getName().equals("device")) { + continue; + } + try { + Integer id = Integer.valueOf(device.getAttribute("id")); + deviceIds.add(id); + } catch (NumberFormatException e) { + Log.e(Config.LOGTAG, "Encountered nvalid node in PEP:" + device.toString() + + ", skipping..."); + continue; + } + } + return deviceIds; + } + + public Integer signedPreKeyId(final Element bundle) { + final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); + if(signedPreKeyPublic == null) { + return null; + } + return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId")); + } + + public ECPublicKey signedPreKeyPublic(final Element bundle) { + ECPublicKey publicKey = null; + final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); + if(signedPreKeyPublic == null) { + return null; + } + try { + publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0); + } catch (InvalidKeyException e) { + Log.e(Config.LOGTAG, "Invalid signedPreKeyPublic in PEP: " + e.getMessage()); + } + return publicKey; + } + + public byte[] signedPreKeySignature(final Element bundle) { + final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature"); + if(signedPreKeySignature == null) { + return null; + } + return Base64.decode(signedPreKeySignature.getContent(),Base64.DEFAULT); + } + + public IdentityKey identityKey(final Element bundle) { + IdentityKey identityKey = null; + final Element identityKeyElement = bundle.findChild("identityKey"); + if(identityKeyElement == null) { + return null; + } + try { + identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0); + } catch (InvalidKeyException e) { + Log.e(Config.LOGTAG,"Invalid identityKey in PEP: "+e.getMessage()); + } + return identityKey; + } + + public Map preKeyPublics(final IqPacket packet) { + Map preKeyRecords = new HashMap<>(); + Element prekeysItem = getItem(packet); + if (prekeysItem == null) { + Log.d(Config.LOGTAG, "Couldn't find in preKeyPublic IQ packet: " + packet); + return null; + } + final Element prekeysElement = prekeysItem.findChild("prekeys"); + if(prekeysElement == null) { + Log.d(Config.LOGTAG, "Couldn't find in preKeyPublic IQ packet: " + packet); + return null; + } + for(Element preKeyPublicElement : prekeysElement.getChildren()) { + if(!preKeyPublicElement.getName().equals("preKeyPublic")){ + Log.d(Config.LOGTAG, "Encountered unexpected tag in prekeys list: " + preKeyPublicElement); + continue; + } + Integer preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId")); + try { + ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0); + preKeyRecords.put(preKeyId, preKeyPublic); + } catch (InvalidKeyException e) { + Log.e(Config.LOGTAG, "Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping..."); + continue; + } + } + return preKeyRecords; + } + + public PreKeyBundle bundle(final IqPacket bundle) { + Element bundleItem = getItem(bundle); + if(bundleItem == null) { + return null; + } + final Element bundleElement = bundleItem.findChild("bundle"); + if(bundleElement == null) { + return null; + } + ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement); + Integer signedPreKeyId = signedPreKeyId(bundleElement); + byte[] signedPreKeySignature = signedPreKeySignature(bundleElement); + IdentityKey identityKey = identityKey(bundleElement); + if(signedPreKeyPublic == null || identityKey == null) { + return null; + } + + return new PreKeyBundle(0, 0, 0, null, + signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); + } + + public List preKeys(final IqPacket preKeys) { + List bundles = new ArrayList<>(); + Map preKeyPublics = preKeyPublics(preKeys); + if ( preKeyPublics != null) { + for (Integer preKeyId : preKeyPublics.keySet()) { + ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId); + bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, + 0, null, null, null)); + } + } + + return bundles; + } + @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index d46ff1950..bf4f3ad4b 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -6,7 +6,11 @@ import android.util.Pair; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; +import java.util.List; + import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -94,6 +98,18 @@ public class MessageParser extends AbstractParser implements } } + private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation) { + Message finishedMessage = null; + AxolotlService service = conversation.getAccount().getAxolotlService(); + XmppAxolotlMessage xmppAxolotlMessage = new XmppAxolotlMessage(conversation.getContact(), axolotlMessage); + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage); + if(plaintextMessage != null) { + finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, Message.STATUS_RECEIVED); + } + + return finishedMessage; + } + private class Invite { Jid jid; String password; @@ -170,6 +186,18 @@ public class MessageParser extends AbstractParser implements mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateAccountUi(); } + } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) { + Log.d(Config.LOGTAG, "Received PEP device list update from "+ from + ", processing..."); + Element item = items.findChild("item"); + List deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + AxolotlService axolotlService = account.getAxolotlService(); + if(account.getJid().toBareJid().equals(from)) { + } else { + Contact contact = account.getRoster().getContact(from); + for (Integer deviceId : deviceIds) { + axolotlService.fetchBundleIfNeeded(contact, deviceId); + } + } } } @@ -232,8 +260,9 @@ public class MessageParser extends AbstractParser implements timestamp = AbstractParser.getTimestamp(packet, System.currentTimeMillis()); } final String body = packet.getBody(); - final String encrypted = packet.findChildContent("x", "jabber:x:encrypted"); final Element mucUserElement = packet.findChild("x","http://jabber.org/protocol/muc#user"); + final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); + final Element axolotlEncrypted = packet.findChild("axolotl_message", AxolotlService.PEP_PREFIX); int status; final Jid counterpart; final Jid to = packet.getTo(); @@ -261,11 +290,11 @@ public class MessageParser extends AbstractParser implements return; } - if (extractChatState(mXmppConnectionService.find(account,from), packet)) { + if (extractChatState(mXmppConnectionService.find(account, from), packet)) { mXmppConnectionService.updateConversationUi(); } - if ((body != null || encrypted != null) && !isMucStatusMessage) { + if ((body != null || pgpEncrypted != null || axolotlEncrypted != null) && !isMucStatusMessage) { Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat); if (isTypeGroupChat) { if (counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) { @@ -294,9 +323,14 @@ public class MessageParser extends AbstractParser implements } else { message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); } - } else if (encrypted != null) { - message = new Message(conversation, encrypted, Message.ENCRYPTION_PGP, status); - } else { + } else if (pgpEncrypted != null) { + message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status); + } else if (axolotlEncrypted != null) { + message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation); + if (message == null) { + return; + } + } else { message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); } message.setCounterpart(counterpart); diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index 32657c66c..dc5a68f6c 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -21,6 +21,11 @@ public class Element { this.name = name; } + public Element(String name, String xmlns) { + this.name = name; + this.setAttribute("xmlns", xmlns); + } + public Element addChild(Element child) { this.content = null; children.add(child); diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java index e32811afe..628f0d934 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java @@ -29,6 +29,11 @@ public class MessagePacket extends AbstractStanza { this.children.add(0, body); } + public void setAxolotlMessage(Element axolotlMessage) { + this.children.remove(findChild("body")); + this.children.add(0, axolotlMessage); + } + public void setType(int type) { switch (type) { case TYPE_CHAT: -- cgit v1.2.3 From 065519d3f39f0a7b925157b50e4435957f63d8ae Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Thu, 25 Jun 2015 17:01:42 +0200 Subject: Added axolotl activation code to UI --- .../services/XmppConnectionService.java | 9 +++++++++ .../conversations/ui/ContactDetailsActivity.java | 20 +++++++++++++++++++ .../conversations/ui/ConversationActivity.java | 18 +++++++++++++++++ .../conversations/ui/ConversationFragment.java | 10 ++++++++++ .../eu/siacs/conversations/ui/XmppActivity.java | 23 ++++++++++++++++++++++ 5 files changed, 80 insertions(+) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index bed9267bb..1ebe3a033 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -758,6 +758,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } break; + case Message.ENCRYPTION_AXOLOTL: + try { + packet = mMessageGenerator.generateAxolotlChat(message); + Log.d(Config.LOGTAG, "Succeeded generating axolotl chat message!"); + } catch (NoSessionsCreatedException e) { + message.setStatus(Message.STATUS_WAITING); + } + break; + } if (packet != null) { if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) { diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index c190caede..fb04946a8 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -29,6 +29,7 @@ import android.widget.QuickContactBadge; import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpUtils; +import org.whispersystems.libaxolotl.IdentityKey; import java.util.List; @@ -376,6 +377,25 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } }); } + for(final IdentityKey identityKey:contact.getAxolotlIdentityKeys()) { + hasKeys = true; + 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); + ImageButton remove = (ImageButton) view + .findViewById(R.id.button_remove); + remove.setVisibility(View.VISIBLE); + keyType.setText("Axolotl Fingerprint"); + key.setText(identityKey.getFingerprint()); + keys.addView(view); + remove.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + //confirmToDeleteFingerprint(otrFingerprint); + } + }); + } if (contact.getPgpKeyId() != 0) { hasKeys = true; View view = inflater.inflate(R.layout.contact_key, keys, false); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 96abf65b0..ff1355c35 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -16,6 +16,7 @@ import android.os.Bundle; import android.provider.MediaStore; import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -34,6 +35,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; @@ -749,6 +751,17 @@ public class ConversationActivity extends XmppActivity showInstallPgpDialog(); } break; + case R.id.encryption_choice_axolotl: + Log.d(Config.LOGTAG, "Trying to enable axolotl..."); + if(conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) { + Log.d(Config.LOGTAG, "Enabled axolotl for Contact " + conversation.getContact().getJid() ); + conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); + item.setChecked(true); + } else { + Log.d(Config.LOGTAG, "Contact " + conversation.getContact().getJid() + " not axolotl capable!"); + showAxolotlNoSessionsDialog(); + } + break; default: conversation.setNextEncryption(Message.ENCRYPTION_NONE); break; @@ -780,6 +793,11 @@ public class ConversationActivity extends XmppActivity case Message.ENCRYPTION_PGP: pgp.setChecked(true); break; + case Message.ENCRYPTION_AXOLOTL: + Log.d(Config.LOGTAG, "Axolotl confirmed. Setting menu item checked!"); + popup.getMenu().findItem(R.id.encryption_choice_axolotl) + .setChecked(true); + break; default: none.setChecked(true); break; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index d254ece7b..ec50ea548 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -303,6 +303,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa sendOtrMessage(message); } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) { sendPgpMessage(message); + } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_AXOLOTL) { + sendAxolotlMessage(message); } else { sendPlainTextMessage(message); } @@ -1120,6 +1122,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa builder.create().show(); } + protected void sendAxolotlMessage(final Message message) { + final ConversationActivity activity = (ConversationActivity) getActivity(); + final XmppConnectionService xmppService = activity.xmppConnectionService; + //message.setCounterpart(conversation.getNextCounterpart()); + xmppService.sendMessage(message); + messageSent(); + } + protected void sendOtrMessage(final Message message) { final ConversationActivity activity = (ConversationActivity) getActivity(); final XmppConnectionService xmppService = activity.xmppConnectionService; diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 7c994c31a..eebeb040a 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -266,6 +266,29 @@ public abstract class XmppActivity extends Activity { builder.create().show(); } + public void showAxolotlNoSessionsDialog() { + Builder builder = new AlertDialog.Builder(this); + builder.setTitle("No Sessions"); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage("Your contact is not Axolotl-capable!"); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setNeutralButton("Foo", + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + builder.setPositiveButton("Bar", + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + builder.create().show(); + } + abstract void onBackendConnected(); protected void registerListeners() { -- cgit v1.2.3 From 299bbdf27f0144e6eed99e70a3b2e46f9a3aa301 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 26 Jun 2015 15:41:02 +0200 Subject: Reformat code to use tabs This really sucks to do it like this. Sorry. :( --- .../crypto/axolotl/AxolotlService.java | 1650 ++++++++++---------- .../crypto/axolotl/XmppAxolotlMessage.java | 320 ++-- .../eu/siacs/conversations/entities/Contact.java | 20 +- .../siacs/conversations/generator/IqGenerator.java | 36 +- .../conversations/generator/MessageGenerator.java | 22 +- .../eu/siacs/conversations/parser/IqParser.java | 76 +- .../siacs/conversations/parser/MessageParser.java | 56 +- .../conversations/persistance/DatabaseBackend.java | 34 +- .../services/XmppConnectionService.java | 6 +- .../conversations/ui/ConversationActivity.java | 10 +- 10 files changed, 1115 insertions(+), 1115 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 865f903ae..5c4b34a46 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -51,832 +51,832 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class AxolotlService { - public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl"; - public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist"; - public static final String PEP_PREKEYS = PEP_PREFIX + ".prekeys"; - public static final String PEP_BUNDLE = PEP_PREFIX + ".bundle"; - - private final Account account; - private final XmppConnectionService mXmppConnectionService; - private final SQLiteAxolotlStore axolotlStore; - private final SessionMap sessions; - private final BundleMap bundleCache; - private int ownDeviceId; - - public static class SQLiteAxolotlStore implements AxolotlStore { - - public static final String PREKEY_TABLENAME = "prekeys"; - public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys"; - public static final String SESSION_TABLENAME = "sessions"; - public static final String ACCOUNT = "account"; - public static final String DEVICE_ID = "device_id"; - public static final String ID = "id"; - public static final String KEY = "key"; - public static final String NAME = "name"; - public static final String TRUSTED = "trusted"; - - public static final String JSONKEY_IDENTITY_KEY_PAIR = "axolotl_key"; - public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; - public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id"; - - private final Account account; - private final XmppConnectionService mXmppConnectionService; - - private final IdentityKeyPair identityKeyPair; - private final int localRegistrationId; - private int currentPreKeyId = 0; - - - private static IdentityKeyPair generateIdentityKeyPair() { - Log.d(Config.LOGTAG, "Generating axolotl IdentityKeyPair..."); - ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); - IdentityKeyPair ownKey = new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), - identityKeyPairKeys.getPrivateKey()); - return ownKey; - } - - private static int generateRegistrationId() { - Log.d(Config.LOGTAG, "Generating axolotl registration ID..."); - int reg_id = KeyHelper.generateRegistrationId(false); - return reg_id; - } - - public SQLiteAxolotlStore(Account account, XmppConnectionService service) { - this.account = account; - this.mXmppConnectionService = service; - this.identityKeyPair = loadIdentityKeyPair(); - this.localRegistrationId = loadRegistrationId(); - this.currentPreKeyId = loadCurrentPreKeyId(); - for( SignedPreKeyRecord record:loadSignedPreKeys()) { - Log.d(Config.LOGTAG, "Got Axolotl signed prekey record:" + record.getId()); - } - } - - public int getCurrentPreKeyId() { - return currentPreKeyId; - } - - // -------------------------------------- - // IdentityKeyStore - // -------------------------------------- - - private IdentityKeyPair loadIdentityKeyPair() { - String serializedKey = this.account.getKey(JSONKEY_IDENTITY_KEY_PAIR); - IdentityKeyPair ownKey; - if( serializedKey != null ) { - try { - ownKey = new IdentityKeyPair(Base64.decode(serializedKey,Base64.DEFAULT)); - return ownKey; - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "Invalid key stored for account " + account.getJid() + ": " + e.getMessage()); + public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl"; + public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist"; + public static final String PEP_PREKEYS = PEP_PREFIX + ".prekeys"; + public static final String PEP_BUNDLE = PEP_PREFIX + ".bundle"; + + private final Account account; + private final XmppConnectionService mXmppConnectionService; + private final SQLiteAxolotlStore axolotlStore; + private final SessionMap sessions; + private final BundleMap bundleCache; + private int ownDeviceId; + + public static class SQLiteAxolotlStore implements AxolotlStore { + + public static final String PREKEY_TABLENAME = "prekeys"; + public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys"; + public static final String SESSION_TABLENAME = "sessions"; + public static final String ACCOUNT = "account"; + public static final String DEVICE_ID = "device_id"; + public static final String ID = "id"; + public static final String KEY = "key"; + public static final String NAME = "name"; + public static final String TRUSTED = "trusted"; + + public static final String JSONKEY_IDENTITY_KEY_PAIR = "axolotl_key"; + public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; + public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id"; + + private final Account account; + private final XmppConnectionService mXmppConnectionService; + + private final IdentityKeyPair identityKeyPair; + private final int localRegistrationId; + private int currentPreKeyId = 0; + + + private static IdentityKeyPair generateIdentityKeyPair() { + Log.d(Config.LOGTAG, "Generating axolotl IdentityKeyPair..."); + ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); + IdentityKeyPair ownKey = new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), + identityKeyPairKeys.getPrivateKey()); + return ownKey; + } + + private static int generateRegistrationId() { + Log.d(Config.LOGTAG, "Generating axolotl registration ID..."); + int reg_id = KeyHelper.generateRegistrationId(false); + return reg_id; + } + + public SQLiteAxolotlStore(Account account, XmppConnectionService service) { + this.account = account; + this.mXmppConnectionService = service; + this.identityKeyPair = loadIdentityKeyPair(); + this.localRegistrationId = loadRegistrationId(); + this.currentPreKeyId = loadCurrentPreKeyId(); + for( SignedPreKeyRecord record:loadSignedPreKeys()) { + Log.d(Config.LOGTAG, "Got Axolotl signed prekey record:" + record.getId()); + } + } + + public int getCurrentPreKeyId() { + return currentPreKeyId; + } + + // -------------------------------------- + // IdentityKeyStore + // -------------------------------------- + + private IdentityKeyPair loadIdentityKeyPair() { + String serializedKey = this.account.getKey(JSONKEY_IDENTITY_KEY_PAIR); + IdentityKeyPair ownKey; + if( serializedKey != null ) { + try { + ownKey = new IdentityKeyPair(Base64.decode(serializedKey,Base64.DEFAULT)); + return ownKey; + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, "Invalid key stored for account " + account.getJid() + ": " + e.getMessage()); // return null; - } - } //else { - Log.d(Config.LOGTAG, "Could not retrieve axolotl key for account " + account.getJid()); - ownKey = generateIdentityKeyPair(); - boolean success = this.account.setKey(JSONKEY_IDENTITY_KEY_PAIR, Base64.encodeToString(ownKey.serialize(), Base64.DEFAULT)); - if(success) { - mXmppConnectionService.databaseBackend.updateAccount(account); - } else { - Log.e(Config.LOGTAG, "Failed to write new key to the database!"); - } - //} - return ownKey; - } - - private int loadRegistrationId() { - String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); - int reg_id; - if (regIdString != null) { - reg_id = Integer.valueOf(regIdString); - } else { - Log.d(Config.LOGTAG, "Could not retrieve axolotl registration id for account " + account.getJid()); - reg_id = generateRegistrationId(); - boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID,""+reg_id); - if(success) { - mXmppConnectionService.databaseBackend.updateAccount(account); - } else { - Log.e(Config.LOGTAG, "Failed to write new key to the database!"); - } - } - return reg_id; - } - - private int loadCurrentPreKeyId() { - String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID); - int reg_id; - if (regIdString != null) { - reg_id = Integer.valueOf(regIdString); - } else { - Log.d(Config.LOGTAG, "Could not retrieve current prekey id for account " + account.getJid()); - reg_id = 0; - } - return reg_id; - } - - - /** - * Get the local client's identity key pair. - * - * @return The local client's persistent identity key pair. - */ - @Override - public IdentityKeyPair getIdentityKeyPair() { - return identityKeyPair; - } - - /** - * Return the local client's registration ID. - *

- * Clients should maintain a registration ID, a random number - * between 1 and 16380 that's generated once at install time. - * - * @return the local client's registration ID. - */ - @Override - public int getLocalRegistrationId() { - return localRegistrationId; - } - - /** - * Save a remote client's identity key - *

- * Store a remote client's identity key as trusted. - * - * @param name The name of the remote client. - * @param identityKey The remote client's identity key. - */ - @Override - public void saveIdentity(String name, IdentityKey identityKey) { - try { - Jid contactJid = Jid.fromString(name); - Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid); - if (conversation != null) { - conversation.getContact().addAxolotlIdentityKey(identityKey); - mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.syncRosterToDisk(conversation.getAccount()); - } - } catch (final InvalidJidException e) { - Log.e(Config.LOGTAG, "Failed to save identityKey for contact name " + name + ": " + e.toString()); - } - } - - /** - * Verify a remote client's identity key. - *

- * Determine whether a remote client's identity is trusted. Convention is - * that the TextSecure protocol is 'trust on first use.' This means that - * an identity key is considered 'trusted' if there is no entry for the recipient - * in the local store, or if it matches the saved key for a recipient in the local - * store. Only if it mismatches an entry in the local store is it considered - * 'untrusted.' - * - * @param name The name of the remote client. - * @param identityKey The identity key to verify. - * @return true if trusted, false if untrusted. - */ - @Override - public boolean isTrustedIdentity(String name, IdentityKey identityKey) { - try { - Jid contactJid = Jid.fromString(name); - Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid); - if (conversation != null) { - List trustedKeys = conversation.getContact().getAxolotlIdentityKeys(); - return trustedKeys.isEmpty() || trustedKeys.contains(identityKey); - } else { - return false; - } - } catch (final InvalidJidException e) { - Log.e(Config.LOGTAG, "Failed to save identityKey for contact name" + name + ": " + e.toString()); - return false; - } - } - - // -------------------------------------- - // SessionStore - // -------------------------------------- - - /** - * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, - * or a new SessionRecord if one does not currently exist. - *

- * It is important that implementations return a copy of the current durable information. The - * returned SessionRecord may be modified, but those changes should not have an effect on the - * durable session state (what is returned by subsequent calls to this method) without the - * store method being called here first. - * - * @param address The name and device ID of the remote client. - * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or - * a new SessionRecord if one does not currently exist. - */ - @Override - public SessionRecord loadSession(AxolotlAddress address) { - SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address); - return (session!=null)?session:new SessionRecord(); - } - - /** - * Returns all known devices with active sessions for a recipient - * - * @param name the name of the client. - * @return all known sub-devices with active sessions. - */ - @Override - public List getSubDeviceSessions(String name) { - return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account, - new AxolotlAddress(name,0)); - } - - /** - * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. - * - * @param address the address of the remote client. - * @param record the current SessionRecord for the remote client. - */ - @Override - public void storeSession(AxolotlAddress address, SessionRecord record) { - mXmppConnectionService.databaseBackend.storeSession(account, address, record); - } - - /** - * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. - * - * @param address the address of the remote client. - * @return true if a {@link SessionRecord} exists, false otherwise. - */ - @Override - public boolean containsSession(AxolotlAddress address) { - return mXmppConnectionService.databaseBackend.containsSession(account, address); - } - - /** - * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. - * - * @param address the address of the remote client. - */ - @Override - public void deleteSession(AxolotlAddress address) { - mXmppConnectionService.databaseBackend.deleteSession(account, address); - } - - /** - * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. - * - * @param name the name of the remote client. - */ - @Override - public void deleteAllSessions(String name) { - mXmppConnectionService.databaseBackend.deleteAllSessions(account, - new AxolotlAddress(name, 0)); - } - - public boolean isTrustedSession(AxolotlAddress address) { - return mXmppConnectionService.databaseBackend.isTrustedSession(this.account, address); - } - - public void setTrustedSession(AxolotlAddress address, boolean trusted) { - mXmppConnectionService.databaseBackend.setTrustedSession(this.account, address,trusted); - } - - // -------------------------------------- - // PreKeyStore - // -------------------------------------- - - /** - * Load a local PreKeyRecord. - * - * @param preKeyId the ID of the local PreKeyRecord. - * @return the corresponding PreKeyRecord. - * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord. - */ - @Override - public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { - PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId); - if(record == null) { - throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId); - } - return record; - } - - /** - * Store a local PreKeyRecord. - * - * @param preKeyId the ID of the PreKeyRecord to store. - * @param record the PreKeyRecord. - */ - @Override - public void storePreKey(int preKeyId, PreKeyRecord record) { - mXmppConnectionService.databaseBackend.storePreKey(account, record); - currentPreKeyId = preKeyId; - boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID,Integer.toString(preKeyId)); - if(success) { - mXmppConnectionService.databaseBackend.updateAccount(account); - } else { - Log.e(Config.LOGTAG, "Failed to write new prekey id to the database!"); - } - } - - /** - * @param preKeyId A PreKeyRecord ID. - * @return true if the store has a record for the preKeyId, otherwise false. - */ - @Override - public boolean containsPreKey(int preKeyId) { - return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId); - } - - /** - * Delete a PreKeyRecord from local storage. - * - * @param preKeyId The ID of the PreKeyRecord to remove. - */ - @Override - public void removePreKey(int preKeyId) { - mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId); - } - - // -------------------------------------- - // SignedPreKeyStore - // -------------------------------------- - - /** - * Load a local SignedPreKeyRecord. - * - * @param signedPreKeyId the ID of the local SignedPreKeyRecord. - * @return the corresponding SignedPreKeyRecord. - * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord. - */ - @Override - public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { - SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId); - if(record == null) { - throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId); - } - return record; - } - - /** - * Load all local SignedPreKeyRecords. - * - * @return All stored SignedPreKeyRecords. - */ - @Override - public List loadSignedPreKeys() { - return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account); - } - - /** - * Store a local SignedPreKeyRecord. - * - * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. - * @param record the SignedPreKeyRecord. - */ - @Override - public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { - mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record); - } - - /** - * @param signedPreKeyId A SignedPreKeyRecord ID. - * @return true if the store has a record for the signedPreKeyId, otherwise false. - */ - @Override - public boolean containsSignedPreKey(int signedPreKeyId) { - return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId); - } - - /** - * Delete a SignedPreKeyRecord from local storage. - * - * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. - */ - @Override - public void removeSignedPreKey(int signedPreKeyId) { - mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId); - } - } - - public static class XmppAxolotlSession { - private SessionCipher cipher; - private boolean isTrusted = false; - private SQLiteAxolotlStore sqLiteAxolotlStore; - private AxolotlAddress remoteAddress; - - public XmppAxolotlSession(SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { - this.cipher = new SessionCipher(store, remoteAddress); - this.remoteAddress = remoteAddress; - this.sqLiteAxolotlStore = store; - this.isTrusted = sqLiteAxolotlStore.isTrustedSession(remoteAddress); - } - - public void trust() { - sqLiteAxolotlStore.setTrustedSession(remoteAddress, true); - this.isTrusted = true; - } - - public boolean isTrusted() { - return this.isTrusted; - } - - public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { - byte[] plaintext = null; - try { - try { - PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); - Log.d(Config.LOGTAG,"PreKeyWhisperMessage ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); - plaintext = cipher.decrypt(message); - } catch (InvalidMessageException|InvalidVersionException e) { - WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); - plaintext = cipher.decrypt(message); - } catch (InvalidKeyException|InvalidKeyIdException| UntrustedIdentityException e) { - Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); - } - } catch (LegacyMessageException|InvalidMessageException e) { - Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); - } catch (DuplicateMessageException|NoSessionException e) { - Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); - } - return plaintext; - } - - public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(byte[] outgoingMessage) { - CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); - XmppAxolotlMessage.XmppAxolotlMessageHeader header = - new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), - ciphertextMessage.serialize()); - return header; - } - } - - private static class AxolotlAddressMap { - protected Map> map; - protected final Object MAP_LOCK = new Object(); - - public AxolotlAddressMap() { - this.map = new HashMap<>(); - } - - public void put(AxolotlAddress address, T value) { - synchronized (MAP_LOCK) { - Map devices = map.get(address.getName()); - if (devices == null) { - devices = new HashMap<>(); - map.put(address.getName(), devices); - } - devices.put(address.getDeviceId(), value); - } - } - - public T get(AxolotlAddress address) { - synchronized (MAP_LOCK) { - Map devices = map.get(address.getName()); - if(devices == null) { - return null; - } - return devices.get(address.getDeviceId()); - } - } - - public Map getAll(AxolotlAddress address) { - synchronized (MAP_LOCK) { - Map devices = map.get(address.getName()); - if(devices == null) { - return new HashMap<>(); - } - return devices; - } - } - - public boolean hasAny(AxolotlAddress address) { - synchronized (MAP_LOCK) { - Map devices = map.get(address.getName()); - return devices != null && !devices.isEmpty(); - } - } - - - } - - private static class SessionMap extends AxolotlAddressMap { - - public SessionMap(SQLiteAxolotlStore store, Account account) { - super(); - this.fillMap(store, account); - } - - private void fillMap(SQLiteAxolotlStore store, Account account) { - for(Contact contact:account.getRoster().getContacts()){ - Jid bareJid = contact.getJid().toBareJid(); - if(bareJid == null) { - continue; // FIXME: handle this? - } - String address = bareJid.toString(); - List deviceIDs = store.getSubDeviceSessions(address); - for(Integer deviceId:deviceIDs) { - AxolotlAddress axolotlAddress = new AxolotlAddress(address, deviceId); - this.put(axolotlAddress, new XmppAxolotlSession(store, axolotlAddress)); - } - } - } - - } - - private static class BundleMap extends AxolotlAddressMap { - - } - - public AxolotlService(Account account, XmppConnectionService connectionService) { - this.mXmppConnectionService = connectionService; - this.account = account; - this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); - this.sessions = new SessionMap(axolotlStore, account); - this.bundleCache = new BundleMap(); - this.ownDeviceId = axolotlStore.getLocalRegistrationId(); - } - - public void trustSession(AxolotlAddress counterpart) { - XmppAxolotlSession session = sessions.get(counterpart); - if(session != null) { - session.trust(); - } - } - - public boolean isTrustedSession(AxolotlAddress counterpart) { - XmppAxolotlSession session = sessions.get(counterpart); - return session != null && session.isTrusted(); - } - - private AxolotlAddress getAddressForJid(Jid jid) { - return new AxolotlAddress(jid.toString(), 0); - } - - private Set findOwnSessions() { - AxolotlAddress ownAddress = getAddressForJid(account.getJid()); - Set ownDeviceSessions = new HashSet<>(this.sessions.getAll(ownAddress).values()); - return ownDeviceSessions; - } - - private Set findSessionsforContact(Contact contact) { - AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); - Set sessions = new HashSet<>(this.sessions.getAll(contactAddress).values()); - return sessions; - } - - private boolean hasAny(Contact contact) { - AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); - return sessions.hasAny(contactAddress); - } - - public int getOwnDeviceId() { - return ownDeviceId; - } - - public void fetchBundleIfNeeded(final Contact contact, final Integer deviceId) { - final AxolotlAddress address = new AxolotlAddress(contact.getJid().toString(), deviceId); - if (sessions.get(address) != null) { - return; - } - - synchronized (bundleCache) { - PreKeyBundle bundle = bundleCache.get(address); - if (bundle == null) { - bundle = new PreKeyBundle(0, deviceId, 0, null, 0, null, null, null); - bundleCache.put(address, bundle); - } - - if(bundle.getPreKey() == null) { - Log.d(Config.LOGTAG, "No preKey in cache, fetching..."); - IqPacket prekeysPacket = mXmppConnectionService.getIqGenerator().retrievePreKeysForDevice(contact.getJid(), deviceId); - mXmppConnectionService.sendIqPacket(account, prekeysPacket, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - synchronized (bundleCache) { - Log.d(Config.LOGTAG, "Received preKey IQ packet, processing..."); - final IqParser parser = mXmppConnectionService.getIqParser(); - final PreKeyBundle bundle = bundleCache.get(address); - final List preKeyBundleList = parser.preKeys(packet); - if (preKeyBundleList.isEmpty()) { - Log.d(Config.LOGTAG, "preKey IQ packet invalid: " + packet); - return; - } - Random random = new Random(); - final PreKeyBundle newBundle = preKeyBundleList.get(random.nextInt(preKeyBundleList.size())); - if (bundle == null || newBundle == null) { - //should never happen - return; - } - - final PreKeyBundle mergedBundle = new PreKeyBundle(bundle.getRegistrationId(), - bundle.getDeviceId(), newBundle.getPreKeyId(), newBundle.getPreKey(), - bundle.getSignedPreKeyId(), bundle.getSignedPreKey(), - bundle.getSignedPreKeySignature(), bundle.getIdentityKey()); - - bundleCache.put(address, mergedBundle); - } - } - }); - } - if(bundle.getIdentityKey() == null) { - Log.d(Config.LOGTAG, "No bundle in cache, fetching..."); - IqPacket bundlePacket = mXmppConnectionService.getIqGenerator().retrieveBundleForDevice(contact.getJid(), deviceId); - mXmppConnectionService.sendIqPacket(account, bundlePacket, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - synchronized (bundleCache) { - Log.d(Config.LOGTAG, "Received bundle IQ packet, processing..."); - final IqParser parser = mXmppConnectionService.getIqParser(); - final PreKeyBundle bundle = bundleCache.get(address); - final PreKeyBundle newBundle = parser.bundle(packet); - if( bundle == null || newBundle == null ) { - Log.d(Config.LOGTAG, "bundle IQ packet invalid: " + packet); - //should never happen - return; - } - - final PreKeyBundle mergedBundle = new PreKeyBundle(bundle.getRegistrationId(), - bundle.getDeviceId(), bundle.getPreKeyId(), bundle.getPreKey(), - newBundle.getSignedPreKeyId(), newBundle.getSignedPreKey(), - newBundle.getSignedPreKeySignature(), newBundle.getIdentityKey()); - - axolotlStore.saveIdentity(contact.getJid().toBareJid().toString(), newBundle.getIdentityKey()); - bundleCache.put(address, mergedBundle); - } - } - }); - } - } - } - - public void publishOwnDeviceIdIfNeeded() { - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Element item = mXmppConnectionService.getIqParser().getItem(packet); - List deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); - if(deviceIds == null) { - deviceIds = new ArrayList<>(); - } - if(!deviceIds.contains(getOwnDeviceId())) { - Log.d(Config.LOGTAG, "Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing..."); - deviceIds.add(getOwnDeviceId()); - IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - // TODO: implement this! - } - }); - } - } - }); - } - - public void publishBundleIfNeeded() { - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundleForDevice(account.getJid().toBareJid(), ownDeviceId); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); - if(bundle == null) { - Log.d(Config.LOGTAG, "Bundle " + getOwnDeviceId() + " not in PEP. Publishing..."); - int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size(); - try { - SignedPreKeyRecord signedPreKeyRecord = KeyHelper.generateSignedPreKey( - axolotlStore.getIdentityKeyPair(), numSignedPreKeys + 1); - axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); - IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundle( - signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), - ownDeviceId); - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - // TODO: implement this! - Log.d(Config.LOGTAG, "Published bundle, got: " + packet); - } - }); - } catch (InvalidKeyException e) { - Log.e(Config.LOGTAG, "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); - } - } - } - }); - } - - public void publishPreKeysIfNeeded() { - IqPacket packet = mXmppConnectionService.getIqGenerator().retrievePreKeysForDevice(account.getJid().toBareJid(), ownDeviceId); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); - if(keys == null || keys.isEmpty()) { - Log.d(Config.LOGTAG, "Prekeys " + getOwnDeviceId() + " not in PEP. Publishing..."); - List preKeyRecords = KeyHelper.generatePreKeys( - axolotlStore.getCurrentPreKeyId(), 100); - for(PreKeyRecord record : preKeyRecords) { - axolotlStore.storePreKey(record.getId(), record); - } - IqPacket publish = mXmppConnectionService.getIqGenerator().publishPreKeys( - preKeyRecords, ownDeviceId); - - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Log.d(Config.LOGTAG, "Published prekeys, got: " + packet); - // TODO: implement this! - } - }); - } - } - }); - } - - - public boolean isContactAxolotlCapable(Contact contact) { - AxolotlAddress address = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0); - return sessions.hasAny(address) || bundleCache.hasAny(address); - } - - public void initiateSynchronousSession(Contact contact) { - - } - - private void createSessionsIfNeeded(Contact contact) throws NoSessionsCreatedException { - Log.d(Config.LOGTAG, "Creating axolotl sessions if needed..."); - AxolotlAddress address = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0); - for(Integer deviceId: bundleCache.getAll(address).keySet()) { - Log.d(Config.LOGTAG, "Processing device ID: " + deviceId); - AxolotlAddress remoteAddress = new AxolotlAddress(contact.getJid().toBareJid().toString(), deviceId); - if(sessions.get(remoteAddress) == null) { - Log.d(Config.LOGTAG, "Building new sesstion for " + deviceId); - SessionBuilder builder = new SessionBuilder(this.axolotlStore, remoteAddress); - try { - builder.process(bundleCache.get(remoteAddress)); - XmppAxolotlSession session = new XmppAxolotlSession(this.axolotlStore, remoteAddress); - sessions.put(remoteAddress, session); - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "Error building session for " + deviceId+ ": InvalidKeyException, " +e.getMessage()); - } catch (UntrustedIdentityException e) { - Log.d(Config.LOGTAG, "Error building session for " + deviceId+ ": UntrustedIdentityException, " +e.getMessage()); - } - } else { - Log.d(Config.LOGTAG, "Already have session for " + deviceId); - } - } - if(!this.hasAny(contact)) { - Log.e(Config.LOGTAG, "No Axolotl sessions available!"); - throw new NoSessionsCreatedException(); // FIXME: proper error handling - } - } - - public XmppAxolotlMessage processSending(Contact contact, String outgoingMessage) throws NoSessionsCreatedException { - XmppAxolotlMessage message = new XmppAxolotlMessage(contact, ownDeviceId, outgoingMessage); - createSessionsIfNeeded(contact); - Log.d(Config.LOGTAG, "Building axolotl foreign headers..."); - - for(XmppAxolotlSession session : findSessionsforContact(contact)) { + } + } //else { + Log.d(Config.LOGTAG, "Could not retrieve axolotl key for account " + account.getJid()); + ownKey = generateIdentityKeyPair(); + boolean success = this.account.setKey(JSONKEY_IDENTITY_KEY_PAIR, Base64.encodeToString(ownKey.serialize(), Base64.DEFAULT)); + if(success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, "Failed to write new key to the database!"); + } + //} + return ownKey; + } + + private int loadRegistrationId() { + String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); + int reg_id; + if (regIdString != null) { + reg_id = Integer.valueOf(regIdString); + } else { + Log.d(Config.LOGTAG, "Could not retrieve axolotl registration id for account " + account.getJid()); + reg_id = generateRegistrationId(); + boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID,""+reg_id); + if(success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, "Failed to write new key to the database!"); + } + } + return reg_id; + } + + private int loadCurrentPreKeyId() { + String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID); + int reg_id; + if (regIdString != null) { + reg_id = Integer.valueOf(regIdString); + } else { + Log.d(Config.LOGTAG, "Could not retrieve current prekey id for account " + account.getJid()); + reg_id = 0; + } + return reg_id; + } + + + /** + * Get the local client's identity key pair. + * + * @return The local client's persistent identity key pair. + */ + @Override + public IdentityKeyPair getIdentityKeyPair() { + return identityKeyPair; + } + + /** + * Return the local client's registration ID. + *

+ * Clients should maintain a registration ID, a random number + * between 1 and 16380 that's generated once at install time. + * + * @return the local client's registration ID. + */ + @Override + public int getLocalRegistrationId() { + return localRegistrationId; + } + + /** + * Save a remote client's identity key + *

+ * Store a remote client's identity key as trusted. + * + * @param name The name of the remote client. + * @param identityKey The remote client's identity key. + */ + @Override + public void saveIdentity(String name, IdentityKey identityKey) { + try { + Jid contactJid = Jid.fromString(name); + Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid); + if (conversation != null) { + conversation.getContact().addAxolotlIdentityKey(identityKey); + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.syncRosterToDisk(conversation.getAccount()); + } + } catch (final InvalidJidException e) { + Log.e(Config.LOGTAG, "Failed to save identityKey for contact name " + name + ": " + e.toString()); + } + } + + /** + * Verify a remote client's identity key. + *

+ * Determine whether a remote client's identity is trusted. Convention is + * that the TextSecure protocol is 'trust on first use.' This means that + * an identity key is considered 'trusted' if there is no entry for the recipient + * in the local store, or if it matches the saved key for a recipient in the local + * store. Only if it mismatches an entry in the local store is it considered + * 'untrusted.' + * + * @param name The name of the remote client. + * @param identityKey The identity key to verify. + * @return true if trusted, false if untrusted. + */ + @Override + public boolean isTrustedIdentity(String name, IdentityKey identityKey) { + try { + Jid contactJid = Jid.fromString(name); + Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid); + if (conversation != null) { + List trustedKeys = conversation.getContact().getAxolotlIdentityKeys(); + return trustedKeys.isEmpty() || trustedKeys.contains(identityKey); + } else { + return false; + } + } catch (final InvalidJidException e) { + Log.e(Config.LOGTAG, "Failed to save identityKey for contact name" + name + ": " + e.toString()); + return false; + } + } + + // -------------------------------------- + // SessionStore + // -------------------------------------- + + /** + * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, + * or a new SessionRecord if one does not currently exist. + *

+ * It is important that implementations return a copy of the current durable information. The + * returned SessionRecord may be modified, but those changes should not have an effect on the + * durable session state (what is returned by subsequent calls to this method) without the + * store method being called here first. + * + * @param address The name and device ID of the remote client. + * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or + * a new SessionRecord if one does not currently exist. + */ + @Override + public SessionRecord loadSession(AxolotlAddress address) { + SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address); + return (session!=null)?session:new SessionRecord(); + } + + /** + * Returns all known devices with active sessions for a recipient + * + * @param name the name of the client. + * @return all known sub-devices with active sessions. + */ + @Override + public List getSubDeviceSessions(String name) { + return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account, + new AxolotlAddress(name,0)); + } + + /** + * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @param record the current SessionRecord for the remote client. + */ + @Override + public void storeSession(AxolotlAddress address, SessionRecord record) { + mXmppConnectionService.databaseBackend.storeSession(account, address, record); + } + + /** + * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @return true if a {@link SessionRecord} exists, false otherwise. + */ + @Override + public boolean containsSession(AxolotlAddress address) { + return mXmppConnectionService.databaseBackend.containsSession(account, address); + } + + /** + * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + */ + @Override + public void deleteSession(AxolotlAddress address) { + mXmppConnectionService.databaseBackend.deleteSession(account, address); + } + + /** + * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. + * + * @param name the name of the remote client. + */ + @Override + public void deleteAllSessions(String name) { + mXmppConnectionService.databaseBackend.deleteAllSessions(account, + new AxolotlAddress(name, 0)); + } + + public boolean isTrustedSession(AxolotlAddress address) { + return mXmppConnectionService.databaseBackend.isTrustedSession(this.account, address); + } + + public void setTrustedSession(AxolotlAddress address, boolean trusted) { + mXmppConnectionService.databaseBackend.setTrustedSession(this.account, address,trusted); + } + + // -------------------------------------- + // PreKeyStore + // -------------------------------------- + + /** + * Load a local PreKeyRecord. + * + * @param preKeyId the ID of the local PreKeyRecord. + * @return the corresponding PreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord. + */ + @Override + public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { + PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId); + if(record == null) { + throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId); + } + return record; + } + + /** + * Store a local PreKeyRecord. + * + * @param preKeyId the ID of the PreKeyRecord to store. + * @param record the PreKeyRecord. + */ + @Override + public void storePreKey(int preKeyId, PreKeyRecord record) { + mXmppConnectionService.databaseBackend.storePreKey(account, record); + currentPreKeyId = preKeyId; + boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID,Integer.toString(preKeyId)); + if(success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, "Failed to write new prekey id to the database!"); + } + } + + /** + * @param preKeyId A PreKeyRecord ID. + * @return true if the store has a record for the preKeyId, otherwise false. + */ + @Override + public boolean containsPreKey(int preKeyId) { + return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId); + } + + /** + * Delete a PreKeyRecord from local storage. + * + * @param preKeyId The ID of the PreKeyRecord to remove. + */ + @Override + public void removePreKey(int preKeyId) { + mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId); + } + + // -------------------------------------- + // SignedPreKeyStore + // -------------------------------------- + + /** + * Load a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the local SignedPreKeyRecord. + * @return the corresponding SignedPreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord. + */ + @Override + public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { + SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId); + if(record == null) { + throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId); + } + return record; + } + + /** + * Load all local SignedPreKeyRecords. + * + * @return All stored SignedPreKeyRecords. + */ + @Override + public List loadSignedPreKeys() { + return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account); + } + + /** + * Store a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. + * @param record the SignedPreKeyRecord. + */ + @Override + public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { + mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record); + } + + /** + * @param signedPreKeyId A SignedPreKeyRecord ID. + * @return true if the store has a record for the signedPreKeyId, otherwise false. + */ + @Override + public boolean containsSignedPreKey(int signedPreKeyId) { + return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId); + } + + /** + * Delete a SignedPreKeyRecord from local storage. + * + * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. + */ + @Override + public void removeSignedPreKey(int signedPreKeyId) { + mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId); + } + } + + public static class XmppAxolotlSession { + private SessionCipher cipher; + private boolean isTrusted = false; + private SQLiteAxolotlStore sqLiteAxolotlStore; + private AxolotlAddress remoteAddress; + + public XmppAxolotlSession(SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { + this.cipher = new SessionCipher(store, remoteAddress); + this.remoteAddress = remoteAddress; + this.sqLiteAxolotlStore = store; + this.isTrusted = sqLiteAxolotlStore.isTrustedSession(remoteAddress); + } + + public void trust() { + sqLiteAxolotlStore.setTrustedSession(remoteAddress, true); + this.isTrusted = true; + } + + public boolean isTrusted() { + return this.isTrusted; + } + + public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { + byte[] plaintext = null; + try { + try { + PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); + Log.d(Config.LOGTAG,"PreKeyWhisperMessage ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); + plaintext = cipher.decrypt(message); + } catch (InvalidMessageException|InvalidVersionException e) { + WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); + plaintext = cipher.decrypt(message); + } catch (InvalidKeyException|InvalidKeyIdException| UntrustedIdentityException e) { + Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); + } + } catch (LegacyMessageException|InvalidMessageException e) { + Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); + } catch (DuplicateMessageException|NoSessionException e) { + Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); + } + return plaintext; + } + + public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(byte[] outgoingMessage) { + CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); + XmppAxolotlMessage.XmppAxolotlMessageHeader header = + new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), + ciphertextMessage.serialize()); + return header; + } + } + + private static class AxolotlAddressMap { + protected Map> map; + protected final Object MAP_LOCK = new Object(); + + public AxolotlAddressMap() { + this.map = new HashMap<>(); + } + + public void put(AxolotlAddress address, T value) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + if (devices == null) { + devices = new HashMap<>(); + map.put(address.getName(), devices); + } + devices.put(address.getDeviceId(), value); + } + } + + public T get(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + if(devices == null) { + return null; + } + return devices.get(address.getDeviceId()); + } + } + + public Map getAll(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + if(devices == null) { + return new HashMap<>(); + } + return devices; + } + } + + public boolean hasAny(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map devices = map.get(address.getName()); + return devices != null && !devices.isEmpty(); + } + } + + + } + + private static class SessionMap extends AxolotlAddressMap { + + public SessionMap(SQLiteAxolotlStore store, Account account) { + super(); + this.fillMap(store, account); + } + + private void fillMap(SQLiteAxolotlStore store, Account account) { + for(Contact contact:account.getRoster().getContacts()){ + Jid bareJid = contact.getJid().toBareJid(); + if(bareJid == null) { + continue; // FIXME: handle this? + } + String address = bareJid.toString(); + List deviceIDs = store.getSubDeviceSessions(address); + for(Integer deviceId:deviceIDs) { + AxolotlAddress axolotlAddress = new AxolotlAddress(address, deviceId); + this.put(axolotlAddress, new XmppAxolotlSession(store, axolotlAddress)); + } + } + } + + } + + private static class BundleMap extends AxolotlAddressMap { + + } + + public AxolotlService(Account account, XmppConnectionService connectionService) { + this.mXmppConnectionService = connectionService; + this.account = account; + this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); + this.sessions = new SessionMap(axolotlStore, account); + this.bundleCache = new BundleMap(); + this.ownDeviceId = axolotlStore.getLocalRegistrationId(); + } + + public void trustSession(AxolotlAddress counterpart) { + XmppAxolotlSession session = sessions.get(counterpart); + if(session != null) { + session.trust(); + } + } + + public boolean isTrustedSession(AxolotlAddress counterpart) { + XmppAxolotlSession session = sessions.get(counterpart); + return session != null && session.isTrusted(); + } + + private AxolotlAddress getAddressForJid(Jid jid) { + return new AxolotlAddress(jid.toString(), 0); + } + + private Set findOwnSessions() { + AxolotlAddress ownAddress = getAddressForJid(account.getJid()); + Set ownDeviceSessions = new HashSet<>(this.sessions.getAll(ownAddress).values()); + return ownDeviceSessions; + } + + private Set findSessionsforContact(Contact contact) { + AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); + Set sessions = new HashSet<>(this.sessions.getAll(contactAddress).values()); + return sessions; + } + + private boolean hasAny(Contact contact) { + AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); + return sessions.hasAny(contactAddress); + } + + public int getOwnDeviceId() { + return ownDeviceId; + } + + public void fetchBundleIfNeeded(final Contact contact, final Integer deviceId) { + final AxolotlAddress address = new AxolotlAddress(contact.getJid().toString(), deviceId); + if (sessions.get(address) != null) { + return; + } + + synchronized (bundleCache) { + PreKeyBundle bundle = bundleCache.get(address); + if (bundle == null) { + bundle = new PreKeyBundle(0, deviceId, 0, null, 0, null, null, null); + bundleCache.put(address, bundle); + } + + if(bundle.getPreKey() == null) { + Log.d(Config.LOGTAG, "No preKey in cache, fetching..."); + IqPacket prekeysPacket = mXmppConnectionService.getIqGenerator().retrievePreKeysForDevice(contact.getJid(), deviceId); + mXmppConnectionService.sendIqPacket(account, prekeysPacket, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + synchronized (bundleCache) { + Log.d(Config.LOGTAG, "Received preKey IQ packet, processing..."); + final IqParser parser = mXmppConnectionService.getIqParser(); + final PreKeyBundle bundle = bundleCache.get(address); + final List preKeyBundleList = parser.preKeys(packet); + if (preKeyBundleList.isEmpty()) { + Log.d(Config.LOGTAG, "preKey IQ packet invalid: " + packet); + return; + } + Random random = new Random(); + final PreKeyBundle newBundle = preKeyBundleList.get(random.nextInt(preKeyBundleList.size())); + if (bundle == null || newBundle == null) { + //should never happen + return; + } + + final PreKeyBundle mergedBundle = new PreKeyBundle(bundle.getRegistrationId(), + bundle.getDeviceId(), newBundle.getPreKeyId(), newBundle.getPreKey(), + bundle.getSignedPreKeyId(), bundle.getSignedPreKey(), + bundle.getSignedPreKeySignature(), bundle.getIdentityKey()); + + bundleCache.put(address, mergedBundle); + } + } + }); + } + if(bundle.getIdentityKey() == null) { + Log.d(Config.LOGTAG, "No bundle in cache, fetching..."); + IqPacket bundlePacket = mXmppConnectionService.getIqGenerator().retrieveBundleForDevice(contact.getJid(), deviceId); + mXmppConnectionService.sendIqPacket(account, bundlePacket, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + synchronized (bundleCache) { + Log.d(Config.LOGTAG, "Received bundle IQ packet, processing..."); + final IqParser parser = mXmppConnectionService.getIqParser(); + final PreKeyBundle bundle = bundleCache.get(address); + final PreKeyBundle newBundle = parser.bundle(packet); + if( bundle == null || newBundle == null ) { + Log.d(Config.LOGTAG, "bundle IQ packet invalid: " + packet); + //should never happen + return; + } + + final PreKeyBundle mergedBundle = new PreKeyBundle(bundle.getRegistrationId(), + bundle.getDeviceId(), bundle.getPreKeyId(), bundle.getPreKey(), + newBundle.getSignedPreKeyId(), newBundle.getSignedPreKey(), + newBundle.getSignedPreKeySignature(), newBundle.getIdentityKey()); + + axolotlStore.saveIdentity(contact.getJid().toBareJid().toString(), newBundle.getIdentityKey()); + bundleCache.put(address, mergedBundle); + } + } + }); + } + } + } + + public void publishOwnDeviceIdIfNeeded() { + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Element item = mXmppConnectionService.getIqParser().getItem(packet); + List deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + if(deviceIds == null) { + deviceIds = new ArrayList<>(); + } + if(!deviceIds.contains(getOwnDeviceId())) { + Log.d(Config.LOGTAG, "Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing..."); + deviceIds.add(getOwnDeviceId()); + IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + // TODO: implement this! + } + }); + } + } + }); + } + + public void publishBundleIfNeeded() { + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundleForDevice(account.getJid().toBareJid(), ownDeviceId); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); + if(bundle == null) { + Log.d(Config.LOGTAG, "Bundle " + getOwnDeviceId() + " not in PEP. Publishing..."); + int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size(); + try { + SignedPreKeyRecord signedPreKeyRecord = KeyHelper.generateSignedPreKey( + axolotlStore.getIdentityKeyPair(), numSignedPreKeys + 1); + axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundle( + signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), + ownDeviceId); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + // TODO: implement this! + Log.d(Config.LOGTAG, "Published bundle, got: " + packet); + } + }); + } catch (InvalidKeyException e) { + Log.e(Config.LOGTAG, "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); + } + } + } + }); + } + + public void publishPreKeysIfNeeded() { + IqPacket packet = mXmppConnectionService.getIqGenerator().retrievePreKeysForDevice(account.getJid().toBareJid(), ownDeviceId); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); + if(keys == null || keys.isEmpty()) { + Log.d(Config.LOGTAG, "Prekeys " + getOwnDeviceId() + " not in PEP. Publishing..."); + List preKeyRecords = KeyHelper.generatePreKeys( + axolotlStore.getCurrentPreKeyId(), 100); + for(PreKeyRecord record : preKeyRecords) { + axolotlStore.storePreKey(record.getId(), record); + } + IqPacket publish = mXmppConnectionService.getIqGenerator().publishPreKeys( + preKeyRecords, ownDeviceId); + + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Log.d(Config.LOGTAG, "Published prekeys, got: " + packet); + // TODO: implement this! + } + }); + } + } + }); + } + + + public boolean isContactAxolotlCapable(Contact contact) { + AxolotlAddress address = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0); + return sessions.hasAny(address) || bundleCache.hasAny(address); + } + + public void initiateSynchronousSession(Contact contact) { + + } + + private void createSessionsIfNeeded(Contact contact) throws NoSessionsCreatedException { + Log.d(Config.LOGTAG, "Creating axolotl sessions if needed..."); + AxolotlAddress address = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0); + for(Integer deviceId: bundleCache.getAll(address).keySet()) { + Log.d(Config.LOGTAG, "Processing device ID: " + deviceId); + AxolotlAddress remoteAddress = new AxolotlAddress(contact.getJid().toBareJid().toString(), deviceId); + if(sessions.get(remoteAddress) == null) { + Log.d(Config.LOGTAG, "Building new sesstion for " + deviceId); + SessionBuilder builder = new SessionBuilder(this.axolotlStore, remoteAddress); + try { + builder.process(bundleCache.get(remoteAddress)); + XmppAxolotlSession session = new XmppAxolotlSession(this.axolotlStore, remoteAddress); + sessions.put(remoteAddress, session); + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, "Error building session for " + deviceId+ ": InvalidKeyException, " +e.getMessage()); + } catch (UntrustedIdentityException e) { + Log.d(Config.LOGTAG, "Error building session for " + deviceId+ ": UntrustedIdentityException, " +e.getMessage()); + } + } else { + Log.d(Config.LOGTAG, "Already have session for " + deviceId); + } + } + if(!this.hasAny(contact)) { + Log.e(Config.LOGTAG, "No Axolotl sessions available!"); + throw new NoSessionsCreatedException(); // FIXME: proper error handling + } + } + + public XmppAxolotlMessage processSending(Contact contact, String outgoingMessage) throws NoSessionsCreatedException { + XmppAxolotlMessage message = new XmppAxolotlMessage(contact, ownDeviceId, outgoingMessage); + createSessionsIfNeeded(contact); + Log.d(Config.LOGTAG, "Building axolotl foreign headers..."); + + for(XmppAxolotlSession session : findSessionsforContact(contact)) { // if(!session.isTrusted()) { - // TODO: handle this properly + // TODO: handle this properly // continue; - // } - message.addHeader(session.processSending(message.getInnerKey())); - } - Log.d(Config.LOGTAG, "Building axolotl own headers..."); - for(XmppAxolotlSession session : findOwnSessions()) { - // if(!session.isTrusted()) { - // TODO: handle this properly - // continue; - // } - message.addHeader(session.processSending(message.getInnerKey())); - } - - return message; - } - - public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) { - XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; - AxolotlAddress senderAddress = new AxolotlAddress(message.getContact().getJid().toBareJid().toString(), - message.getSenderDeviceId()); - - XmppAxolotlSession session = sessions.get(senderAddress); - if (session == null) { - Log.d(Config.LOGTAG, "No axolotl session found while parsing received message " + message); - // TODO: handle this properly - session = new XmppAxolotlSession(axolotlStore, senderAddress); - - } - - for(XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { - if (header.getRecipientDeviceId() == ownDeviceId) { - Log.d(Config.LOGTAG, "Found axolotl header matching own device ID, processing..."); - byte[] payloadKey = session.processReceiving(header); - if (payloadKey != null) { - Log.d(Config.LOGTAG, "Got payload key from axolotl header. Decrypting message..."); - plaintextMessage = message.decrypt(session, payloadKey); - } - } - } - - return plaintextMessage; - } + // } + message.addHeader(session.processSending(message.getInnerKey())); + } + Log.d(Config.LOGTAG, "Building axolotl own headers..."); + for(XmppAxolotlSession session : findOwnSessions()) { + // if(!session.isTrusted()) { + // TODO: handle this properly + // continue; + // } + message.addHeader(session.processSending(message.getInnerKey())); + } + + return message; + } + + public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; + AxolotlAddress senderAddress = new AxolotlAddress(message.getContact().getJid().toBareJid().toString(), + message.getSenderDeviceId()); + + XmppAxolotlSession session = sessions.get(senderAddress); + if (session == null) { + Log.d(Config.LOGTAG, "No axolotl session found while parsing received message " + message); + // TODO: handle this properly + session = new XmppAxolotlSession(axolotlStore, senderAddress); + + } + + for(XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { + if (header.getRecipientDeviceId() == ownDeviceId) { + Log.d(Config.LOGTAG, "Found axolotl header matching own device ID, processing..."); + byte[] payloadKey = session.processReceiving(header); + if (payloadKey != null) { + Log.d(Config.LOGTAG, "Got payload key from axolotl header. Decrypting message..."); + plaintextMessage = message.decrypt(session, payloadKey); + } + } + } + + return plaintextMessage; + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index 4b87fc5cb..e1b956507 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -21,164 +21,164 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.xml.Element; public class XmppAxolotlMessage { - private byte[] innerKey; - private byte[] ciphertext; - private byte[] iv; - private final Set headers; - private final Contact contact; - private final int sourceDeviceId; - - public static class XmppAxolotlMessageHeader { - private final int recipientDeviceId; - private final byte[] content; - - public XmppAxolotlMessageHeader(int deviceId, byte[] content) { - this.recipientDeviceId = deviceId; - this.content = content; - } - - public XmppAxolotlMessageHeader(Element header) { - if("header".equals(header.getName())) { - this.recipientDeviceId = Integer.parseInt(header.getAttribute("rid")); - this.content = Base64.decode(header.getContent(),Base64.DEFAULT); - } else { - throw new IllegalArgumentException("Argument not a

Element!"); - } - } - - public int getRecipientDeviceId() { - return recipientDeviceId; - } - - public byte[] getContents() { - return content; - } - - public Element toXml() { - Element headerElement = new Element("header"); - // TODO: generate XML - headerElement.setAttribute("rid", getRecipientDeviceId()); - headerElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); - return headerElement; - } - } - - public static class XmppAxolotlPlaintextMessage { - private final AxolotlService.XmppAxolotlSession session; - private final String plaintext; - - public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext) { - this.session = session; - this.plaintext = plaintext; - } - - public String getPlaintext() { - return plaintext; - } - } - - public XmppAxolotlMessage(Contact contact, Element axolotlMessage) { - this.contact = contact; - this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id")); - this.headers = new HashSet<>(); - for(Element child:axolotlMessage.getChildren()) { - switch(child.getName()) { - case "header": - headers.add(new XmppAxolotlMessageHeader(child)); - break; - case "message": - iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT); - ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT); - break; - default: - break; - } - } - } - - public XmppAxolotlMessage(Contact contact, int sourceDeviceId, String plaintext) { - this.contact = contact; - this.sourceDeviceId = sourceDeviceId; - this.headers = new HashSet<>(); - this.encrypt(plaintext); - } - - private void encrypt(String plaintext) { - try { - KeyGenerator generator = KeyGenerator.getInstance("AES"); - generator.init(128); - SecretKey secretKey = generator.generateKey(); - Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - this.innerKey = secretKey.getEncoded(); - this.iv = cipher.getIV(); - this.ciphertext = cipher.doFinal(plaintext.getBytes()); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | IllegalBlockSizeException | BadPaddingException e) { - - } - } - - public Contact getContact() { - return this.contact; - } - - public int getSenderDeviceId() { - return sourceDeviceId; - } - - public byte[] getCiphertext() { - return ciphertext; - } - - public Set getHeaders() { - return headers; - } - - public void addHeader(XmppAxolotlMessageHeader header) { - headers.add(header); - } - - public byte[] getInnerKey(){ - return innerKey; - } - - public byte[] getIV() { - return this.iv; - } - - public Element toXml() { - // TODO: generate outer XML, add in header XML - Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX); - message.setAttribute("id", sourceDeviceId); - for(XmppAxolotlMessageHeader header: headers) { - message.addChild(header.toXml()); - } - Element payload = message.addChild("message"); - payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT)); - payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT)); - return message; - } - - - public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key) { - XmppAxolotlPlaintextMessage plaintextMessage = null; - try { - - Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); - SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); - IvParameterSpec ivSpec = new IvParameterSpec(iv); - - cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); - - String plaintext = new String(cipher.doFinal(ciphertext)); - plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext); - - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | InvalidAlgorithmParameterException | IllegalBlockSizeException - | BadPaddingException e) { - throw new AssertionError(e); - } - return plaintextMessage; - } + private byte[] innerKey; + private byte[] ciphertext; + private byte[] iv; + private final Set headers; + private final Contact contact; + private final int sourceDeviceId; + + public static class XmppAxolotlMessageHeader { + private final int recipientDeviceId; + private final byte[] content; + + public XmppAxolotlMessageHeader(int deviceId, byte[] content) { + this.recipientDeviceId = deviceId; + this.content = content; + } + + public XmppAxolotlMessageHeader(Element header) { + if("header".equals(header.getName())) { + this.recipientDeviceId = Integer.parseInt(header.getAttribute("rid")); + this.content = Base64.decode(header.getContent(),Base64.DEFAULT); + } else { + throw new IllegalArgumentException("Argument not a
Element!"); + } + } + + public int getRecipientDeviceId() { + return recipientDeviceId; + } + + public byte[] getContents() { + return content; + } + + public Element toXml() { + Element headerElement = new Element("header"); + // TODO: generate XML + headerElement.setAttribute("rid", getRecipientDeviceId()); + headerElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); + return headerElement; + } + } + + public static class XmppAxolotlPlaintextMessage { + private final AxolotlService.XmppAxolotlSession session; + private final String plaintext; + + public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext) { + this.session = session; + this.plaintext = plaintext; + } + + public String getPlaintext() { + return plaintext; + } + } + + public XmppAxolotlMessage(Contact contact, Element axolotlMessage) { + this.contact = contact; + this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id")); + this.headers = new HashSet<>(); + for(Element child:axolotlMessage.getChildren()) { + switch(child.getName()) { + case "header": + headers.add(new XmppAxolotlMessageHeader(child)); + break; + case "message": + iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT); + ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT); + break; + default: + break; + } + } + } + + public XmppAxolotlMessage(Contact contact, int sourceDeviceId, String plaintext) { + this.contact = contact; + this.sourceDeviceId = sourceDeviceId; + this.headers = new HashSet<>(); + this.encrypt(plaintext); + } + + private void encrypt(String plaintext) { + try { + KeyGenerator generator = KeyGenerator.getInstance("AES"); + generator.init(128); + SecretKey secretKey = generator.generateKey(); + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + this.innerKey = secretKey.getEncoded(); + this.iv = cipher.getIV(); + this.ciphertext = cipher.doFinal(plaintext.getBytes()); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | IllegalBlockSizeException | BadPaddingException e) { + + } + } + + public Contact getContact() { + return this.contact; + } + + public int getSenderDeviceId() { + return sourceDeviceId; + } + + public byte[] getCiphertext() { + return ciphertext; + } + + public Set getHeaders() { + return headers; + } + + public void addHeader(XmppAxolotlMessageHeader header) { + headers.add(header); + } + + public byte[] getInnerKey(){ + return innerKey; + } + + public byte[] getIV() { + return this.iv; + } + + public Element toXml() { + // TODO: generate outer XML, add in header XML + Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX); + message.setAttribute("id", sourceDeviceId); + for(XmppAxolotlMessageHeader header: headers) { + message.addChild(header.toXml()); + } + Element payload = message.addChild("message"); + payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT)); + payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT)); + return message; + } + + + public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key) { + XmppAxolotlPlaintextMessage plaintextMessage = null; + try { + + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + + String plaintext = new String(cipher.doFinal(ciphertext)); + plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext); + + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | InvalidAlgorithmParameterException | IllegalBlockSizeException + | BadPaddingException e) { + throw new AssertionError(e); + } + return plaintextMessage; + } } diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 45b55e49d..240d5223c 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -310,7 +310,7 @@ public class Contact implements ListItem, Blockable { synchronized (this.keys) { if (getOtrFingerprints().contains(print)) { return false; - } + } try { JSONArray fingerprints; if (!this.keys.has("otr_fingerprints")) { @@ -392,12 +392,12 @@ public class Contact implements ListItem, Blockable { public boolean addAxolotlIdentityKey(IdentityKey identityKey) { synchronized (this.keys) { if(!getAxolotlIdentityKeys().contains(identityKey)) { - JSONArray keysList; - try { - keysList = this.keys.getJSONArray("axolotl_identity_key"); - } catch (JSONException e) { - keysList = new JSONArray(); - } + JSONArray keysList; + try { + keysList = this.keys.getJSONArray("axolotl_identity_key"); + } catch (JSONException e) { + keysList = new JSONArray(); + } keysList.put(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); try { @@ -406,10 +406,10 @@ public class Contact implements ListItem, Blockable { Log.e(Config.LOGTAG, "Error adding Identity Key to Contact " + this.getJid() + ": " + e.getMessage()); return false; } - return true; + return true; } else { - return false; - } + return false; + } } } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 6daadc2af..5b3bde6a5 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -147,16 +147,16 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket publishDeviceIds(final List ids) { - final Element item = new Element("item"); - final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); - for(Integer id:ids) { - final Element device = new Element("device"); - device.setAttribute("id", id); - list.addChild(device); - } - return publish(AxolotlService.PEP_DEVICE_LIST, item); - } + public IqPacket publishDeviceIds(final List ids) { + final Element item = new Element("item"); + final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); + for(Integer id:ids) { + final Element device = new Element("device"); + device.setAttribute("id", id); + list.addChild(device); + } + return publish(AxolotlService.PEP_DEVICE_LIST, item); + } public IqPacket publishBundle(final SignedPreKeyRecord signedPreKeyRecord, IdentityKey identityKey, final int deviceId) { final Element item = new Element("item"); @@ -173,17 +173,17 @@ public class IqGenerator extends AbstractGenerator { return publish(AxolotlService.PEP_BUNDLE+":"+deviceId, item); } - public IqPacket publishPreKeys(final List prekeyList, final int deviceId) { - final Element item = new Element("item"); - final Element prekeys = item.addChild("prekeys", AxolotlService.PEP_PREFIX); - for(PreKeyRecord preKeyRecord:prekeyList) { - final Element prekey = prekeys.addChild("preKeyPublic"); + public IqPacket publishPreKeys(final List prekeyList, final int deviceId) { + final Element item = new Element("item"); + final Element prekeys = item.addChild("prekeys", AxolotlService.PEP_PREFIX); + for(PreKeyRecord preKeyRecord:prekeyList) { + final Element prekey = prekeys.addChild("preKeyPublic"); prekey.setAttribute("preKeyId", preKeyRecord.getId()); - prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT)); - } + prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT)); + } return publish(AxolotlService.PEP_PREKEYS+":"+deviceId, item); - } + } public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index e6032e0cb..0b6a7c614 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -66,19 +66,19 @@ public class MessageGenerator extends AbstractGenerator { delay.setAttribute("stamp", mDateFormat.format(date)); } - public MessagePacket generateAxolotlChat(Message message) throws NoSessionsCreatedException{ - return generateAxolotlChat(message, false); - } + public MessagePacket generateAxolotlChat(Message message) throws NoSessionsCreatedException{ + return generateAxolotlChat(message, false); + } public MessagePacket generateAxolotlChat(Message message, boolean addDelay) throws NoSessionsCreatedException{ - MessagePacket packet = preparePacket(message, addDelay); - AxolotlService service = message.getConversation().getAccount().getAxolotlService(); - Log.d(Config.LOGTAG, "Submitting message to axolotl service for send processing..."); - XmppAxolotlMessage axolotlMessage = service.processSending(message.getContact(), - message.getBody()); - packet.setAxolotlMessage(axolotlMessage.toXml()); - return packet; - } + MessagePacket packet = preparePacket(message, addDelay); + AxolotlService service = message.getConversation().getAccount().getAxolotlService(); + Log.d(Config.LOGTAG, "Submitting message to axolotl service for send processing..."); + XmppAxolotlMessage axolotlMessage = service.processSending(message.getContact(), + message.getBody()); + packet.setAxolotlMessage(axolotlMessage.toXml()); + return packet; + } public MessagePacket generateOtrChat(Message message) { return generateOtrChat(message, false); diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 00f968858..df143a411 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -70,7 +70,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { public String avatarData(final IqPacket packet) { final Element pubsub = packet.findChild("pubsub", - "http://jabber.org/protocol/pubsub"); + "http://jabber.org/protocol/pubsub"); if (pubsub == null) { return null; } @@ -165,19 +165,19 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { public Map preKeyPublics(final IqPacket packet) { Map preKeyRecords = new HashMap<>(); - Element prekeysItem = getItem(packet); - if (prekeysItem == null) { - Log.d(Config.LOGTAG, "Couldn't find in preKeyPublic IQ packet: " + packet); - return null; - } - final Element prekeysElement = prekeysItem.findChild("prekeys"); - if(prekeysElement == null) { - Log.d(Config.LOGTAG, "Couldn't find in preKeyPublic IQ packet: " + packet); - return null; - } + Element prekeysItem = getItem(packet); + if (prekeysItem == null) { + Log.d(Config.LOGTAG, "Couldn't find in preKeyPublic IQ packet: " + packet); + return null; + } + final Element prekeysElement = prekeysItem.findChild("prekeys"); + if(prekeysElement == null) { + Log.d(Config.LOGTAG, "Couldn't find in preKeyPublic IQ packet: " + packet); + return null; + } for(Element preKeyPublicElement : prekeysElement.getChildren()) { if(!preKeyPublicElement.getName().equals("preKeyPublic")){ - Log.d(Config.LOGTAG, "Encountered unexpected tag in prekeys list: " + preKeyPublicElement); + Log.d(Config.LOGTAG, "Encountered unexpected tag in prekeys list: " + preKeyPublicElement); continue; } Integer preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId")); @@ -192,37 +192,37 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { return preKeyRecords; } - public PreKeyBundle bundle(final IqPacket bundle) { - Element bundleItem = getItem(bundle); - if(bundleItem == null) { - return null; - } - final Element bundleElement = bundleItem.findChild("bundle"); - if(bundleElement == null) { - return null; - } - ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement); - Integer signedPreKeyId = signedPreKeyId(bundleElement); - byte[] signedPreKeySignature = signedPreKeySignature(bundleElement); - IdentityKey identityKey = identityKey(bundleElement); - if(signedPreKeyPublic == null || identityKey == null) { - return null; - } + public PreKeyBundle bundle(final IqPacket bundle) { + Element bundleItem = getItem(bundle); + if(bundleItem == null) { + return null; + } + final Element bundleElement = bundleItem.findChild("bundle"); + if(bundleElement == null) { + return null; + } + ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement); + Integer signedPreKeyId = signedPreKeyId(bundleElement); + byte[] signedPreKeySignature = signedPreKeySignature(bundleElement); + IdentityKey identityKey = identityKey(bundleElement); + if(signedPreKeyPublic == null || identityKey == null) { + return null; + } - return new PreKeyBundle(0, 0, 0, null, - signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); - } + return new PreKeyBundle(0, 0, 0, null, + signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); + } public List preKeys(final IqPacket preKeys) { List bundles = new ArrayList<>(); Map preKeyPublics = preKeyPublics(preKeys); - if ( preKeyPublics != null) { - for (Integer preKeyId : preKeyPublics.keySet()) { - ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId); - bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, - 0, null, null, null)); - } - } + if ( preKeyPublics != null) { + for (Integer preKeyId : preKeyPublics.keySet()) { + ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId); + bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, + 0, null, null, null)); + } + } return bundles; } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index bf4f3ad4b..de7057308 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -98,17 +98,17 @@ public class MessageParser extends AbstractParser implements } } - private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation) { - Message finishedMessage = null; - AxolotlService service = conversation.getAccount().getAxolotlService(); - XmppAxolotlMessage xmppAxolotlMessage = new XmppAxolotlMessage(conversation.getContact(), axolotlMessage); - XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage); - if(plaintextMessage != null) { - finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, Message.STATUS_RECEIVED); - } + private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation) { + Message finishedMessage = null; + AxolotlService service = conversation.getAccount().getAxolotlService(); + XmppAxolotlMessage xmppAxolotlMessage = new XmppAxolotlMessage(conversation.getContact(), axolotlMessage); + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage); + if(plaintextMessage != null) { + finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, Message.STATUS_RECEIVED); + } - return finishedMessage; - } + return finishedMessage; + } private class Invite { Jid jid; @@ -187,17 +187,17 @@ public class MessageParser extends AbstractParser implements mXmppConnectionService.updateAccountUi(); } } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) { - Log.d(Config.LOGTAG, "Received PEP device list update from "+ from + ", processing..."); - Element item = items.findChild("item"); + Log.d(Config.LOGTAG, "Received PEP device list update from "+ from + ", processing..."); + Element item = items.findChild("item"); List deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); - AxolotlService axolotlService = account.getAxolotlService(); - if(account.getJid().toBareJid().equals(from)) { - } else { - Contact contact = account.getRoster().getContact(from); - for (Integer deviceId : deviceIds) { - axolotlService.fetchBundleIfNeeded(contact, deviceId); - } - } + AxolotlService axolotlService = account.getAxolotlService(); + if(account.getJid().toBareJid().equals(from)) { + } else { + Contact contact = account.getRoster().getContact(from); + for (Integer deviceId : deviceIds) { + axolotlService.fetchBundleIfNeeded(contact, deviceId); + } + } } } @@ -262,7 +262,7 @@ public class MessageParser extends AbstractParser implements final String body = packet.getBody(); final Element mucUserElement = packet.findChild("x","http://jabber.org/protocol/muc#user"); final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); - final Element axolotlEncrypted = packet.findChild("axolotl_message", AxolotlService.PEP_PREFIX); + final Element axolotlEncrypted = packet.findChild("axolotl_message", AxolotlService.PEP_PREFIX); int status; final Jid counterpart; final Jid to = packet.getTo(); @@ -324,13 +324,13 @@ public class MessageParser extends AbstractParser implements message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); } } else if (pgpEncrypted != null) { - message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status); - } else if (axolotlEncrypted != null) { - message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation); - if (message == null) { - return; - } - } else { + message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status); + } else if (axolotlEncrypted != null) { + message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation); + if (message == null) { + return; + } + } else { message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); } message.setCounterpart(counterpart); diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index af0e2fa88..ee6c76368 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -42,7 +42,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Contact.JID + " TEXT," + Contact.KEYS + " TEXT," + Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER," + Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, " - + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, " + + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, " + Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", " @@ -75,14 +75,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME + "(" + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " INTEGER, " + + AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, " + + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " INTEGER, " + AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, " - + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " - + AxolotlService.SQLiteAxolotlStore.NAME + ", " + + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " + + AxolotlService.SQLiteAxolotlStore.NAME + ", " + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + ") ON CONFLICT REPLACE" +");"; @@ -157,12 +157,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN " + Conversation.ATTRIBUTES + " TEXT"); } - if (oldVersion < 9 && newVersion >= 9) { - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.LAST_TIME + " NUMBER"); - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.LAST_PRESENCE + " TEXT"); - } + if (oldVersion < 9 && newVersion >= 9) { + db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + + Contact.LAST_TIME + " NUMBER"); + db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + + Contact.LAST_PRESENCE + " TEXT"); + } if (oldVersion < 10 && newVersion >= 10) { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.RELATIVE_FILE_PATH + " TEXT"); @@ -557,7 +557,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public SessionRecord loadSession(Account account, AxolotlAddress contact) { SessionRecord session = null; - Cursor cursor = getCursorForSession(account, contact); + Cursor cursor = getCursorForSession(account, contact); if(cursor.getCount() != 0) { cursor.moveToFirst(); try { @@ -565,8 +565,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { } catch (IOException e) { throw new AssertionError(e); } - } - cursor.close(); + } + cursor.close(); return session; } @@ -635,7 +635,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { Cursor cursor = getCursorForSession(account, contact); if(cursor.getCount() != 0) { cursor.moveToFirst(); - trusted = cursor.getInt(cursor.getColumnIndex( + trusted = cursor.getInt(cursor.getColumnIndex( AxolotlService.SQLiteAxolotlStore.TRUSTED)) > 0; } cursor.close(); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 1ebe3a033..9a4cc276f 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -274,9 +274,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } syncDirtyContacts(account); - account.getAxolotlService().publishOwnDeviceIdIfNeeded(); - account.getAxolotlService().publishBundleIfNeeded(); - account.getAxolotlService().publishPreKeysIfNeeded(); + account.getAxolotlService().publishOwnDeviceIdIfNeeded(); + account.getAxolotlService().publishBundleIfNeeded(); + account.getAxolotlService().publishPreKeysIfNeeded(); scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); } else if (account.getStatus() == Account.State.OFFLINE) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index ff1355c35..e4ce4a0f1 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -752,16 +752,16 @@ public class ConversationActivity extends XmppActivity } break; case R.id.encryption_choice_axolotl: - Log.d(Config.LOGTAG, "Trying to enable axolotl..."); + Log.d(Config.LOGTAG, "Trying to enable axolotl..."); if(conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) { - Log.d(Config.LOGTAG, "Enabled axolotl for Contact " + conversation.getContact().getJid() ); + Log.d(Config.LOGTAG, "Enabled axolotl for Contact " + conversation.getContact().getJid() ); conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); item.setChecked(true); } else { - Log.d(Config.LOGTAG, "Contact " + conversation.getContact().getJid() + " not axolotl capable!"); + Log.d(Config.LOGTAG, "Contact " + conversation.getContact().getJid() + " not axolotl capable!"); showAxolotlNoSessionsDialog(); } - break; + break; default: conversation.setNextEncryption(Message.ENCRYPTION_NONE); break; @@ -794,7 +794,7 @@ public class ConversationActivity extends XmppActivity pgp.setChecked(true); break; case Message.ENCRYPTION_AXOLOTL: - Log.d(Config.LOGTAG, "Axolotl confirmed. Setting menu item checked!"); + Log.d(Config.LOGTAG, "Axolotl confirmed. Setting menu item checked!"); popup.getMenu().findItem(R.id.encryption_choice_axolotl) .setChecked(true); break; -- cgit v1.2.3 From 9e07fc5651015dc60e54bcb2f796d0f932d5f925 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 29 Jun 2015 13:40:56 +0200 Subject: DatabaseBackend bugfixes Don't leak cursors, initially create tables --- .../java/eu/siacs/conversations/persistance/DatabaseBackend.java | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index ee6c76368..966734bae 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -123,6 +123,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { + ") ON DELETE CASCADE);"); db.execSQL(CREATE_CONTATCS_STATEMENT); + db.execSQL(CREATE_SESSIONS_STATEMENT); + db.execSQL(CREATE_PREKEYS_STATEMENT); + db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); } @Override @@ -563,6 +566,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { try { session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (IOException e) { + cursor.close(); throw new AssertionError(e); } } @@ -751,6 +755,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } catch (IOException ignored) { } } + cursor.close(); return prekeys; } -- cgit v1.2.3 From 74026b742bd7e1d4b79fd839f887a0beb940b0dc Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 29 Jun 2015 13:53:39 +0200 Subject: Save IdentityKeys in database --- .../crypto/axolotl/AxolotlService.java | 70 ++++++------------ .../conversations/persistance/DatabaseBackend.java | 85 ++++++++++++++++++++++ 2 files changed, 108 insertions(+), 47 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 5c4b34a46..d788ed066 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -68,12 +68,14 @@ public class AxolotlService { public static final String PREKEY_TABLENAME = "prekeys"; public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys"; public static final String SESSION_TABLENAME = "sessions"; + public static final String IDENTITIES_TABLENAME = "identities"; public static final String ACCOUNT = "account"; public static final String DEVICE_ID = "device_id"; public static final String ID = "id"; public static final String KEY = "key"; public static final String NAME = "name"; public static final String TRUSTED = "trusted"; + public static final String OWN = "ownkey"; public static final String JSONKEY_IDENTITY_KEY_PAIR = "axolotl_key"; public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; @@ -82,7 +84,7 @@ public class AxolotlService { private final Account account; private final XmppConnectionService mXmppConnectionService; - private final IdentityKeyPair identityKeyPair; + private IdentityKeyPair identityKeyPair; private final int localRegistrationId; private int currentPreKeyId = 0; @@ -104,10 +106,9 @@ public class AxolotlService { public SQLiteAxolotlStore(Account account, XmppConnectionService service) { this.account = account; this.mXmppConnectionService = service; - this.identityKeyPair = loadIdentityKeyPair(); this.localRegistrationId = loadRegistrationId(); this.currentPreKeyId = loadCurrentPreKeyId(); - for( SignedPreKeyRecord record:loadSignedPreKeys()) { + for (SignedPreKeyRecord record : loadSignedPreKeys()) { Log.d(Config.LOGTAG, "Got Axolotl signed prekey record:" + record.getId()); } } @@ -121,26 +122,17 @@ public class AxolotlService { // -------------------------------------- private IdentityKeyPair loadIdentityKeyPair() { - String serializedKey = this.account.getKey(JSONKEY_IDENTITY_KEY_PAIR); - IdentityKeyPair ownKey; - if( serializedKey != null ) { - try { - ownKey = new IdentityKeyPair(Base64.decode(serializedKey,Base64.DEFAULT)); - return ownKey; - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "Invalid key stored for account " + account.getJid() + ": " + e.getMessage()); -// return null; - } - } //else { - Log.d(Config.LOGTAG, "Could not retrieve axolotl key for account " + account.getJid()); + String ownName = account.getJid().toBareJid().toString(); + IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account, + ownName); + + if (ownKey != null) { + return ownKey; + } else { + Log.d(Config.LOGTAG, "Could not retrieve axolotl key for account " + ownName); ownKey = generateIdentityKeyPair(); - boolean success = this.account.setKey(JSONKEY_IDENTITY_KEY_PAIR, Base64.encodeToString(ownKey.serialize(), Base64.DEFAULT)); - if(success) { - mXmppConnectionService.databaseBackend.updateAccount(account); - } else { - Log.e(Config.LOGTAG, "Failed to write new key to the database!"); - } - //} + mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey); + } return ownKey; } @@ -152,8 +144,8 @@ public class AxolotlService { } else { Log.d(Config.LOGTAG, "Could not retrieve axolotl registration id for account " + account.getJid()); reg_id = generateRegistrationId(); - boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID,""+reg_id); - if(success) { + boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id)); + if (success) { mXmppConnectionService.databaseBackend.updateAccount(account); } else { Log.e(Config.LOGTAG, "Failed to write new key to the database!"); @@ -182,6 +174,9 @@ public class AxolotlService { */ @Override public IdentityKeyPair getIdentityKeyPair() { + if(identityKeyPair == null) { + identityKeyPair = loadIdentityKeyPair(); + } return identityKeyPair; } @@ -208,16 +203,8 @@ public class AxolotlService { */ @Override public void saveIdentity(String name, IdentityKey identityKey) { - try { - Jid contactJid = Jid.fromString(name); - Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid); - if (conversation != null) { - conversation.getContact().addAxolotlIdentityKey(identityKey); - mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.syncRosterToDisk(conversation.getAccount()); - } - } catch (final InvalidJidException e) { - Log.e(Config.LOGTAG, "Failed to save identityKey for contact name " + name + ": " + e.toString()); + if(!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) { + mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey); } } @@ -237,19 +224,8 @@ public class AxolotlService { */ @Override public boolean isTrustedIdentity(String name, IdentityKey identityKey) { - try { - Jid contactJid = Jid.fromString(name); - Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid); - if (conversation != null) { - List trustedKeys = conversation.getContact().getAxolotlIdentityKeys(); - return trustedKeys.isEmpty() || trustedKeys.contains(identityKey); - } else { - return false; - } - } catch (final InvalidJidException e) { - Log.e(Config.LOGTAG, "Failed to save identityKey for contact name" + name + ": " + e.toString()); - return false; - } + Set trustedKeys = mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name); + return trustedKeys.isEmpty() || trustedKeys.contains(identityKey); } // -------------------------------------- diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 966734bae..c70ffad2c 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -10,13 +10,18 @@ import android.util.Base64; import android.util.Log; import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import eu.siacs.conversations.Config; @@ -87,6 +92,16 @@ public class DatabaseBackend extends SQLiteOpenHelper { + ") ON CONFLICT REPLACE" +");"; + private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE " + + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME + "(" + + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, " + + AxolotlService.SQLiteAxolotlStore.OWN + " INTEGER, " + + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + AxolotlService.SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE " + +");"; + private DatabaseBackend(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @@ -126,6 +141,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_SESSIONS_STATEMENT); db.execSQL(CREATE_PREKEYS_STATEMENT); db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); + db.execSQL(CREATE_IDENTITIES_STATEMENT); } @Override @@ -273,6 +289,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_PREKEYS_STATEMENT); db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME); + db.execSQL(CREATE_IDENTITIES_STATEMENT); } } @@ -783,4 +801,71 @@ public class DatabaseBackend extends SQLiteOpenHelper { + AxolotlService.SQLiteAxolotlStore.ID + "=?", args); } + + private Cursor getIdentityKeyCursor(Account account, String name, boolean own) { + final SQLiteDatabase db = this.getReadableDatabase(); + String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid(), + name, + own?"1":"0"}; + Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, + columns, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " + + AxolotlService.SQLiteAxolotlStore.OWN + " = ? ", + selectionArgs, + null, null, null); + + return cursor; + } + + public IdentityKeyPair loadOwnIdentityKeyPair(Account account, String name) { + IdentityKeyPair identityKeyPair = null; + Cursor cursor = getIdentityKeyCursor(account, name, true); + if(cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); + } + } + cursor.close(); + + return identityKeyPair; + } + + public Set loadIdentityKeys(Account account, String name) { + Set identityKeys = new HashSet<>(); + Cursor cursor = getIdentityKeyCursor(account, name, false); + + while(cursor.moveToNext()) { + try { + identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0)); + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, "Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name); + } + } + cursor.close(); + + return identityKeys; + } + + private void storeIdentityKey(Account account, String name, boolean own, String base64Serialized) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + values.put(AxolotlService.SQLiteAxolotlStore.NAME, name); + values.put(AxolotlService.SQLiteAxolotlStore.OWN, own?1:0); + values.put(AxolotlService.SQLiteAxolotlStore.KEY, base64Serialized); + db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); + } + + public void storeIdentityKey(Account account, String name, IdentityKey identityKey) { + storeIdentityKey(account, name, false, Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); + } + + public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) { + storeIdentityKey(account, name, true, Base64.encodeToString(identityKeyPair.serialize(),Base64.DEFAULT)); + } } -- cgit v1.2.3 From 6492801b89cea496b41fe77e6b2a9be18abb2081 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 29 Jun 2015 13:55:45 +0200 Subject: Formatting fixes --- .../crypto/axolotl/AxolotlService.java | 44 +++++++++++----------- 1 file changed, 22 insertions(+), 22 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index d788ed066..8879a0fe1 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -248,7 +248,7 @@ public class AxolotlService { @Override public SessionRecord loadSession(AxolotlAddress address) { SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address); - return (session!=null)?session:new SessionRecord(); + return (session != null) ? session : new SessionRecord(); } /** @@ -260,7 +260,7 @@ public class AxolotlService { @Override public List getSubDeviceSessions(String name) { return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account, - new AxolotlAddress(name,0)); + new AxolotlAddress(name, 0)); } /** @@ -311,7 +311,7 @@ public class AxolotlService { } public void setTrustedSession(AxolotlAddress address, boolean trusted) { - mXmppConnectionService.databaseBackend.setTrustedSession(this.account, address,trusted); + mXmppConnectionService.databaseBackend.setTrustedSession(this.account, address, trusted); } // -------------------------------------- @@ -328,7 +328,7 @@ public class AxolotlService { @Override public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId); - if(record == null) { + if (record == null) { throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId); } return record; @@ -344,8 +344,8 @@ public class AxolotlService { public void storePreKey(int preKeyId, PreKeyRecord record) { mXmppConnectionService.databaseBackend.storePreKey(account, record); currentPreKeyId = preKeyId; - boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID,Integer.toString(preKeyId)); - if(success) { + boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId)); + if (success) { mXmppConnectionService.databaseBackend.updateAccount(account); } else { Log.e(Config.LOGTAG, "Failed to write new prekey id to the database!"); @@ -385,7 +385,7 @@ public class AxolotlService { @Override public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId); - if(record == null) { + if (record == null) { throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId); } return record; @@ -459,18 +459,18 @@ public class AxolotlService { try { try { PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); - Log.d(Config.LOGTAG,"PreKeyWhisperMessage ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); + Log.d(Config.LOGTAG, "PreKeyWhisperMessage ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); plaintext = cipher.decrypt(message); - } catch (InvalidMessageException|InvalidVersionException e) { + } catch (InvalidMessageException | InvalidVersionException e) { WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); plaintext = cipher.decrypt(message); - } catch (InvalidKeyException|InvalidKeyIdException| UntrustedIdentityException e) { - Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); + } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { + Log.d(Config.LOGTAG, "Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); } - } catch (LegacyMessageException|InvalidMessageException e) { - Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); - } catch (DuplicateMessageException|NoSessionException e) { - Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage()); + } catch (LegacyMessageException | InvalidMessageException e) { + Log.d(Config.LOGTAG, "Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); + } catch (DuplicateMessageException | NoSessionException e) { + Log.d(Config.LOGTAG, "Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); } return plaintext; } @@ -485,7 +485,7 @@ public class AxolotlService { } private static class AxolotlAddressMap { - protected Map> map; + protected Map> map; protected final Object MAP_LOCK = new Object(); public AxolotlAddressMap() { @@ -506,7 +506,7 @@ public class AxolotlService { public T get(AxolotlAddress address) { synchronized (MAP_LOCK) { Map devices = map.get(address.getName()); - if(devices == null) { + if (devices == null) { return null; } return devices.get(address.getDeviceId()); @@ -516,7 +516,7 @@ public class AxolotlService { public Map getAll(AxolotlAddress address) { synchronized (MAP_LOCK) { Map devices = map.get(address.getName()); - if(devices == null) { + if (devices == null) { return new HashMap<>(); } return devices; @@ -541,14 +541,14 @@ public class AxolotlService { } private void fillMap(SQLiteAxolotlStore store, Account account) { - for(Contact contact:account.getRoster().getContacts()){ + for (Contact contact : account.getRoster().getContacts()) { Jid bareJid = contact.getJid().toBareJid(); - if(bareJid == null) { + if (bareJid == null) { continue; // FIXME: handle this? } String address = bareJid.toString(); List deviceIDs = store.getSubDeviceSessions(address); - for(Integer deviceId:deviceIDs) { + for (Integer deviceId : deviceIDs) { AxolotlAddress axolotlAddress = new AxolotlAddress(address, deviceId); this.put(axolotlAddress, new XmppAxolotlSession(store, axolotlAddress)); } @@ -572,7 +572,7 @@ public class AxolotlService { public void trustSession(AxolotlAddress counterpart) { XmppAxolotlSession session = sessions.get(counterpart); - if(session != null) { + if (session != null) { session.trust(); } } -- cgit v1.2.3 From c1d23b2395bfc9570aed07ba3413f988f08d84f5 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 29 Jun 2015 14:18:11 +0200 Subject: Migrate to new PEP layout Merge prekeys into bundle node --- .../crypto/axolotl/AxolotlService.java | 252 +++++++++++---------- .../siacs/conversations/generator/IqGenerator.java | 29 +-- .../eu/siacs/conversations/parser/IqParser.java | 20 +- .../services/XmppConnectionService.java | 3 +- 4 files changed, 157 insertions(+), 147 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 8879a0fe1..22e959eb8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -30,6 +30,7 @@ import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.util.KeyHelper; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -53,14 +54,16 @@ public class AxolotlService { public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl"; public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist"; - public static final String PEP_PREKEYS = PEP_PREFIX + ".prekeys"; - public static final String PEP_BUNDLE = PEP_PREFIX + ".bundle"; + public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles"; + + public static final int NUM_KEYS_TO_PUBLISH = 10; private final Account account; private final XmppConnectionService mXmppConnectionService; private final SQLiteAxolotlStore axolotlStore; private final SessionMap sessions; private final BundleMap bundleCache; + private final Map> deviceIds; private int ownDeviceId; public static class SQLiteAxolotlStore implements AxolotlStore { @@ -565,6 +568,7 @@ public class AxolotlService { this.mXmppConnectionService = connectionService; this.account = account; this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); + this.deviceIds = new HashMap<>(); this.sessions = new SessionMap(axolotlStore, account); this.bundleCache = new BundleMap(); this.ownDeviceId = axolotlStore.getLocalRegistrationId(); @@ -607,80 +611,11 @@ public class AxolotlService { return ownDeviceId; } - public void fetchBundleIfNeeded(final Contact contact, final Integer deviceId) { - final AxolotlAddress address = new AxolotlAddress(contact.getJid().toString(), deviceId); - if (sessions.get(address) != null) { - return; - } - - synchronized (bundleCache) { - PreKeyBundle bundle = bundleCache.get(address); - if (bundle == null) { - bundle = new PreKeyBundle(0, deviceId, 0, null, 0, null, null, null); - bundleCache.put(address, bundle); - } - - if(bundle.getPreKey() == null) { - Log.d(Config.LOGTAG, "No preKey in cache, fetching..."); - IqPacket prekeysPacket = mXmppConnectionService.getIqGenerator().retrievePreKeysForDevice(contact.getJid(), deviceId); - mXmppConnectionService.sendIqPacket(account, prekeysPacket, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - synchronized (bundleCache) { - Log.d(Config.LOGTAG, "Received preKey IQ packet, processing..."); - final IqParser parser = mXmppConnectionService.getIqParser(); - final PreKeyBundle bundle = bundleCache.get(address); - final List preKeyBundleList = parser.preKeys(packet); - if (preKeyBundleList.isEmpty()) { - Log.d(Config.LOGTAG, "preKey IQ packet invalid: " + packet); - return; - } - Random random = new Random(); - final PreKeyBundle newBundle = preKeyBundleList.get(random.nextInt(preKeyBundleList.size())); - if (bundle == null || newBundle == null) { - //should never happen - return; - } - - final PreKeyBundle mergedBundle = new PreKeyBundle(bundle.getRegistrationId(), - bundle.getDeviceId(), newBundle.getPreKeyId(), newBundle.getPreKey(), - bundle.getSignedPreKeyId(), bundle.getSignedPreKey(), - bundle.getSignedPreKeySignature(), bundle.getIdentityKey()); - - bundleCache.put(address, mergedBundle); - } - } - }); - } - if(bundle.getIdentityKey() == null) { - Log.d(Config.LOGTAG, "No bundle in cache, fetching..."); - IqPacket bundlePacket = mXmppConnectionService.getIqGenerator().retrieveBundleForDevice(contact.getJid(), deviceId); - mXmppConnectionService.sendIqPacket(account, bundlePacket, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - synchronized (bundleCache) { - Log.d(Config.LOGTAG, "Received bundle IQ packet, processing..."); - final IqParser parser = mXmppConnectionService.getIqParser(); - final PreKeyBundle bundle = bundleCache.get(address); - final PreKeyBundle newBundle = parser.bundle(packet); - if( bundle == null || newBundle == null ) { - Log.d(Config.LOGTAG, "bundle IQ packet invalid: " + packet); - //should never happen - return; - } - - final PreKeyBundle mergedBundle = new PreKeyBundle(bundle.getRegistrationId(), - bundle.getDeviceId(), bundle.getPreKeyId(), bundle.getPreKey(), - newBundle.getSignedPreKeyId(), newBundle.getSignedPreKey(), - newBundle.getSignedPreKeySignature(), newBundle.getIdentityKey()); - - axolotlStore.saveIdentity(contact.getJid().toBareJid().toString(), newBundle.getIdentityKey()); - bundleCache.put(address, mergedBundle); - } - } - }); - } + public void registerDevices(final Jid jid, final Set deviceIds) { + for(Integer i:deviceIds) { + Log.d(Config.LOGTAG, "Adding Device ID:"+ jid + ":"+i); } + this.deviceIds.put(jid, deviceIds); } public void publishOwnDeviceIdIfNeeded() { @@ -689,14 +624,14 @@ public class AxolotlService { @Override public void onIqPacketReceived(Account account, IqPacket packet) { Element item = mXmppConnectionService.getIqParser().getItem(packet); - List deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); - if(deviceIds == null) { - deviceIds = new ArrayList<>(); + Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + if (deviceIds == null) { + deviceIds = new HashSet(); } - if(!deviceIds.contains(getOwnDeviceId())) { - Log.d(Config.LOGTAG, "Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing..."); + if (!deviceIds.contains(getOwnDeviceId())) { deviceIds.add(getOwnDeviceId()); IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); + Log.d(Config.LOGTAG, "Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing: " + publish); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -708,22 +643,68 @@ public class AxolotlService { }); } - public void publishBundleIfNeeded() { - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundleForDevice(account.getJid().toBareJid(), ownDeviceId); + private boolean validateBundle(PreKeyBundle bundle) { + if (bundle == null || bundle.getIdentityKey() == null + || bundle.getSignedPreKey() == null || bundle.getSignedPreKeySignature() == null) { + return false; + } + + try { + SignedPreKeyRecord signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); + IdentityKey identityKey = axolotlStore.getIdentityKeyPair().getPublicKey(); + Log.d(Config.LOGTAG,"own identity key:"+identityKey.getFingerprint()+", foreign: "+bundle.getIdentityKey().getFingerprint()); + Log.d(Config.LOGTAG,"bundle: "+Boolean.toString(bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())) + +" " + Boolean.toString(Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) + +" " + Boolean.toString( bundle.getIdentityKey().equals(identityKey))); + return bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) + && Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature()) + && bundle.getIdentityKey().equals(identityKey); + } catch (InvalidKeyIdException ignored) { + return false; + } + } + + private boolean validatePreKeys(Map keys) { + if(keys == null) { return false; } + for(Integer id:keys.keySet()) { + try { + PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id); + if(!preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) { + return false; + } + } catch (InvalidKeyIdException ignored) { + return false; + } + } + return true; + } + + public void publishBundlesIfNeeded() { + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), ownDeviceId); mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); - if(bundle == null) { - Log.d(Config.LOGTAG, "Bundle " + getOwnDeviceId() + " not in PEP. Publishing..."); + Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); + SignedPreKeyRecord signedPreKeyRecord; + List preKeyRecords; + if (!validateBundle(bundle) || keys.isEmpty() || !validatePreKeys(keys)) { int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size(); try { - SignedPreKeyRecord signedPreKeyRecord = KeyHelper.generateSignedPreKey( + signedPreKeyRecord = KeyHelper.generateSignedPreKey( axolotlStore.getIdentityKeyPair(), numSignedPreKeys + 1); axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); - IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundle( + + preKeyRecords = KeyHelper.generatePreKeys( + axolotlStore.getCurrentPreKeyId(), NUM_KEYS_TO_PUBLISH); + for (PreKeyRecord record : preKeyRecords) { + axolotlStore.storePreKey(record.getId(), record); + } + + IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), - ownDeviceId); + preKeyRecords, ownDeviceId); + Log.d(Config.LOGTAG, "Bundle " + getOwnDeviceId() + " not in PEP. Publishing: " + publish); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -733,48 +714,83 @@ public class AxolotlService { }); } catch (InvalidKeyException e) { Log.e(Config.LOGTAG, "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); + return; } } } }); } - public void publishPreKeysIfNeeded() { - IqPacket packet = mXmppConnectionService.getIqGenerator().retrievePreKeysForDevice(account.getJid().toBareJid(), ownDeviceId); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); - if(keys == null || keys.isEmpty()) { - Log.d(Config.LOGTAG, "Prekeys " + getOwnDeviceId() + " not in PEP. Publishing..."); - List preKeyRecords = KeyHelper.generatePreKeys( - axolotlStore.getCurrentPreKeyId(), 100); - for(PreKeyRecord record : preKeyRecords) { - axolotlStore.storePreKey(record.getId(), record); - } - IqPacket publish = mXmppConnectionService.getIqGenerator().publishPreKeys( - preKeyRecords, ownDeviceId); - - mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Log.d(Config.LOGTAG, "Published prekeys, got: " + packet); - // TODO: implement this! - } - }); - } - } - }); + public boolean isContactAxolotlCapable(Contact contact) { + Jid jid = contact.getJid().toBareJid(); + AxolotlAddress address = new AxolotlAddress(jid.toString(), 0); + return sessions.hasAny(address) || + ( deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty()); } + private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address) { + Log.d(Config.LOGTAG, "Building new sesstion for " + address.getDeviceId()); + + try { + IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice( + Jid.fromString(address.getName()), address.getDeviceId()); + Log.d(Config.LOGTAG, "Retrieving bundle: " + bundlesPacket); + mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Log.d(Config.LOGTAG, "Received preKey IQ packet, processing..."); + final IqParser parser = mXmppConnectionService.getIqParser(); + final List preKeyBundleList = parser.preKeys(packet); + final PreKeyBundle bundle = parser.bundle(packet); + if (preKeyBundleList.isEmpty() || bundle == null) { + Log.d(Config.LOGTAG, "preKey IQ packet invalid: " + packet); + fetchStatusMap.put(address, FetchStatus.ERROR); + return; + } + Random random = new Random(); + final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size())); + if (preKey == null) { + //should never happen + fetchStatusMap.put(address, FetchStatus.ERROR); + return; + } - public boolean isContactAxolotlCapable(Contact contact) { - AxolotlAddress address = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0); - return sessions.hasAny(address) || bundleCache.hasAny(address); - } + final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(), + preKey.getPreKeyId(), preKey.getPreKey(), + bundle.getSignedPreKeyId(), bundle.getSignedPreKey(), + bundle.getSignedPreKeySignature(), bundle.getIdentityKey()); + + axolotlStore.saveIdentity(address.getName(), bundle.getIdentityKey()); - public void initiateSynchronousSession(Contact contact) { + try { + SessionBuilder builder = new SessionBuilder(axolotlStore, address); + builder.process(preKeyBundle); + XmppAxolotlSession session = new XmppAxolotlSession(axolotlStore, address); + sessions.put(address, session); + fetchStatusMap.put(address, FetchStatus.SUCCESS); + } catch (UntrustedIdentityException|InvalidKeyException e) { + Log.d(Config.LOGTAG, "Error building session for " + address + ": " + + e.getClass().getName() + ", " + e.getMessage()); + fetchStatusMap.put(address, FetchStatus.ERROR); + } + AxolotlAddress ownAddress = new AxolotlAddress(conversation.getAccount().getJid().toBareJid().toString(),0); + AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0); + if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) + && !fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) { + conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_AXOLOTL, + new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + processSending(message); + } + }); + } + } + }); + } catch (InvalidJidException e) { + Log.e(Config.LOGTAG,"Got address with invalid jid: " + address.getName()); + } } private void createSessionsIfNeeded(Contact contact) throws NoSessionsCreatedException { diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 5b3bde6a5..0bef88532 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -10,6 +10,7 @@ import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import java.util.ArrayList; import java.util.List; +import java.util.Set; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; @@ -131,23 +132,15 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket retrieveBundleForDevice(final Jid to, final int deviceid) { - final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLE+":"+deviceid, null); + public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) { + final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES+":"+deviceid, null); if(to != null) { packet.setTo(to); } return packet; } - public IqPacket retrievePreKeysForDevice(final Jid to, final int deviceId) { - final IqPacket packet = retrieve(AxolotlService.PEP_PREKEYS+":"+deviceId, null); - if(to != null) { - packet.setTo(to); - } - return packet; - } - - public IqPacket publishDeviceIds(final List ids) { + public IqPacket publishDeviceIds(final Set ids) { final Element item = new Element("item"); final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); for(Integer id:ids) { @@ -158,7 +151,8 @@ public class IqGenerator extends AbstractGenerator { return publish(AxolotlService.PEP_DEVICE_LIST, item); } - public IqPacket publishBundle(final SignedPreKeyRecord signedPreKeyRecord, IdentityKey identityKey, final int deviceId) { + public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, + final List preKeyRecords, final int deviceId) { final Element item = new Element("item"); final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX); final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic"); @@ -170,19 +164,14 @@ public class IqGenerator extends AbstractGenerator { final Element identityKeyElement = bundle.addChild("identityKey"); identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); - return publish(AxolotlService.PEP_BUNDLE+":"+deviceId, item); - } - - public IqPacket publishPreKeys(final List prekeyList, final int deviceId) { - final Element item = new Element("item"); - final Element prekeys = item.addChild("prekeys", AxolotlService.PEP_PREFIX); - for(PreKeyRecord preKeyRecord:prekeyList) { + final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX); + for(PreKeyRecord preKeyRecord:preKeyRecords) { final Element prekey = prekeys.addChild("preKeyPublic"); prekey.setAttribute("preKeyId", preKeyRecord.getId()); prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT)); } - return publish(AxolotlService.PEP_PREKEYS+":"+deviceId, item); + return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item); } public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index df143a411..935517877 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -12,8 +12,10 @@ import org.whispersystems.libaxolotl.state.PreKeyBundle; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; @@ -94,8 +96,8 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { return items.findChild("item"); } - public List deviceIds(final Element item) { - List deviceIds = new ArrayList<>(); + public Set deviceIds(final Element item) { + Set deviceIds = new HashSet<>(); if (item == null) { return null; } @@ -165,14 +167,18 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { public Map preKeyPublics(final IqPacket packet) { Map preKeyRecords = new HashMap<>(); - Element prekeysItem = getItem(packet); - if (prekeysItem == null) { - Log.d(Config.LOGTAG, "Couldn't find in preKeyPublic IQ packet: " + packet); + Element item = getItem(packet); + if (item == null) { + Log.d(Config.LOGTAG, "Couldn't find in bundle IQ packet: " + packet); + return null; + } + final Element bundleElement = item.findChild("bundle"); + if(bundleElement == null) { return null; } - final Element prekeysElement = prekeysItem.findChild("prekeys"); + final Element prekeysElement = bundleElement.findChild("prekeys"); if(prekeysElement == null) { - Log.d(Config.LOGTAG, "Couldn't find in preKeyPublic IQ packet: " + packet); + Log.d(Config.LOGTAG, "Couldn't find in bundle IQ packet: " + packet); return null; } for(Element preKeyPublicElement : prekeysElement.getChildren()) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 9a4cc276f..f96e5d7eb 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -275,8 +275,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } syncDirtyContacts(account); account.getAxolotlService().publishOwnDeviceIdIfNeeded(); - account.getAxolotlService().publishBundleIfNeeded(); - account.getAxolotlService().publishPreKeysIfNeeded(); + account.getAxolotlService().publishBundlesIfNeeded(); scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); } else if (account.getStatus() == Account.State.OFFLINE) { -- cgit v1.2.3 From cb7980c65ed5d91296e3ad571298dbed434707c0 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 29 Jun 2015 14:19:17 +0200 Subject: Use bareJid for own session retrieval --- src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 22e959eb8..e4c0480ae 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -591,7 +591,7 @@ public class AxolotlService { } private Set findOwnSessions() { - AxolotlAddress ownAddress = getAddressForJid(account.getJid()); + AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid()); Set ownDeviceSessions = new HashSet<>(this.sessions.getAll(ownAddress).values()); return ownDeviceSessions; } -- cgit v1.2.3 From 3815d4efa378846c8aef840ad659268a0bef1536 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 29 Jun 2015 14:22:26 +0200 Subject: Fetch bundles on-demand, encrypt in background Bundles are now fetched on demand when a session needs to be established. This should lessen the chance of changes to the bundles occuring before they're used, as well as lessen the load of fetching bundles. Also, the message encryption is now done in a background thread, as this can be somewhat costly if many sessions are present. This is probably not going to be an issue in real use, but it's good practice anyway. --- .../crypto/axolotl/AxolotlService.java | 133 ++++++++++++++------- .../siacs/conversations/entities/Conversation.java | 6 +- .../conversations/generator/MessageGenerator.java | 10 +- .../siacs/conversations/parser/MessageParser.java | 12 +- .../services/XmppConnectionService.java | 13 +- 5 files changed, 109 insertions(+), 65 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index e4c0480ae..54394dd28 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.crypto.axolotl; +import android.support.annotation.Nullable; import android.util.Base64; import android.util.Log; @@ -42,13 +43,16 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.SerialSingleThreadExecutor; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.stanzas.IqPacket; +import eu.siacs.conversations.xmpp.stanzas.MessagePacket; public class AxolotlService { @@ -62,8 +66,9 @@ public class AxolotlService { private final XmppConnectionService mXmppConnectionService; private final SQLiteAxolotlStore axolotlStore; private final SessionMap sessions; - private final BundleMap bundleCache; private final Map> deviceIds; + private final FetchStatusMap fetchStatusMap; + private final SerialSingleThreadExecutor executor; private int ownDeviceId; public static class SQLiteAxolotlStore implements AxolotlStore { @@ -560,7 +565,13 @@ public class AxolotlService { } - private static class BundleMap extends AxolotlAddressMap { + private static enum FetchStatus { + PENDING, + SUCCESS, + ERROR + } + + private static class FetchStatusMap extends AxolotlAddressMap { } @@ -570,7 +581,8 @@ public class AxolotlService { this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); this.deviceIds = new HashMap<>(); this.sessions = new SessionMap(axolotlStore, account); - this.bundleCache = new BundleMap(); + this.fetchStatusMap = new FetchStatusMap(); + this.executor = new SerialSingleThreadExecutor(); this.ownDeviceId = axolotlStore.getLocalRegistrationId(); } @@ -793,56 +805,93 @@ public class AxolotlService { } } - private void createSessionsIfNeeded(Contact contact) throws NoSessionsCreatedException { + private boolean createSessionsIfNeeded(Conversation conversation) { + boolean newSessions = false; Log.d(Config.LOGTAG, "Creating axolotl sessions if needed..."); - AxolotlAddress address = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0); - for(Integer deviceId: bundleCache.getAll(address).keySet()) { - Log.d(Config.LOGTAG, "Processing device ID: " + deviceId); - AxolotlAddress remoteAddress = new AxolotlAddress(contact.getJid().toBareJid().toString(), deviceId); - if(sessions.get(remoteAddress) == null) { - Log.d(Config.LOGTAG, "Building new sesstion for " + deviceId); - SessionBuilder builder = new SessionBuilder(this.axolotlStore, remoteAddress); - try { - builder.process(bundleCache.get(remoteAddress)); - XmppAxolotlSession session = new XmppAxolotlSession(this.axolotlStore, remoteAddress); - sessions.put(remoteAddress, session); - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "Error building session for " + deviceId+ ": InvalidKeyException, " +e.getMessage()); - } catch (UntrustedIdentityException e) { - Log.d(Config.LOGTAG, "Error building session for " + deviceId+ ": UntrustedIdentityException, " +e.getMessage()); - } - } else { - Log.d(Config.LOGTAG, "Already have session for " + deviceId); + Jid contactJid = conversation.getContact().getJid().toBareJid(); + Set addresses = new HashSet<>(); + if(deviceIds.get(contactJid) != null) { + for(Integer foreignId:this.deviceIds.get(contactJid)) { + Log.d(Config.LOGTAG, "Found device "+account.getJid().toBareJid()+":"+foreignId); + addresses.add(new AxolotlAddress(contactJid.toString(), foreignId)); + } + } else { + Log.e(Config.LOGTAG, "Have no target devices in PEP!"); + } + Log.d(Config.LOGTAG, "Checking own account "+account.getJid().toBareJid()); + if(deviceIds.get(account.getJid().toBareJid()) != null) { + for(Integer ownId:this.deviceIds.get(account.getJid().toBareJid())) { + Log.d(Config.LOGTAG, "Found device "+account.getJid().toBareJid()+":"+ownId); + addresses.add(new AxolotlAddress(account.getJid().toBareJid().toString(), ownId)); } } - if(!this.hasAny(contact)) { - Log.e(Config.LOGTAG, "No Axolotl sessions available!"); - throw new NoSessionsCreatedException(); // FIXME: proper error handling + for (AxolotlAddress address : addresses) { + Log.d(Config.LOGTAG, "Processing device: " + address.toString()); + FetchStatus status = fetchStatusMap.get(address); + XmppAxolotlSession session = sessions.get(address); + if ( session == null && ( status == null || status == FetchStatus.ERROR) ) { + fetchStatusMap.put(address, FetchStatus.PENDING); + this.buildSessionFromPEP(conversation, address); + newSessions = true; + } else { + Log.d(Config.LOGTAG, "Already have session for " + address.toString()); + } } + return newSessions; } - public XmppAxolotlMessage processSending(Contact contact, String outgoingMessage) throws NoSessionsCreatedException { - XmppAxolotlMessage message = new XmppAxolotlMessage(contact, ownDeviceId, outgoingMessage); - createSessionsIfNeeded(contact); - Log.d(Config.LOGTAG, "Building axolotl foreign headers..."); + @Nullable + public XmppAxolotlMessage encrypt(Message message ){ + final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(message.getContact(), + ownDeviceId, message.getBody()); - for(XmppAxolotlSession session : findSessionsforContact(contact)) { -// if(!session.isTrusted()) { - // TODO: handle this properly - // continue; - // } - message.addHeader(session.processSending(message.getInnerKey())); + if(findSessionsforContact(axolotlMessage.getContact()).isEmpty()) { + return null; + } + Log.d(Config.LOGTAG, "Building axolotl foreign headers..."); + for (XmppAxolotlSession session : findSessionsforContact(axolotlMessage.getContact())) { + Log.d(Config.LOGTAG, session.remoteAddress.toString()); + //if(!session.isTrusted()) { + // TODO: handle this properly + // continue; + // } + axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey())); } Log.d(Config.LOGTAG, "Building axolotl own headers..."); - for(XmppAxolotlSession session : findOwnSessions()) { - // if(!session.isTrusted()) { - // TODO: handle this properly - // continue; - // } - message.addHeader(session.processSending(message.getInnerKey())); + for (XmppAxolotlSession session : findOwnSessions()) { + Log.d(Config.LOGTAG, session.remoteAddress.toString()); + // if(!session.isTrusted()) { + // TODO: handle this properly + // continue; + // } + axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey())); } - return message; + return axolotlMessage; + } + + private void processSending(final Message message) { + executor.execute(new Runnable() { + @Override + public void run() { + MessagePacket packet = mXmppConnectionService.getMessageGenerator() + .generateAxolotlChat(message); + if (packet == null) { + mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + } else { + mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); + mXmppConnectionService.sendMessagePacket(account, packet); + } + } + }); + } + + public void sendMessage(Message message) { + boolean newSessions = createSessionsIfNeeded(message.getConversation()); + + if (!newSessions) { + this.processSending(message); + } } public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) { diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 289ed4ea5..2efd8a290 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -179,13 +179,13 @@ public class Conversation extends AbstractEntity implements Blockable { } } - public void findUnsentMessagesWithOtrEncryption(OnMessageFound onMessageFound) { + public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) { synchronized (this.messages) { for (Message message : this.messages) { if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING) - && (message.getEncryption() == Message.ENCRYPTION_OTR)) { + && (message.getEncryption() == encryptionType)) { onMessageFound.onMessageFound(message); - } + } } } } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 0b6a7c614..b0727690b 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -66,16 +66,18 @@ public class MessageGenerator extends AbstractGenerator { delay.setAttribute("stamp", mDateFormat.format(date)); } - public MessagePacket generateAxolotlChat(Message message) throws NoSessionsCreatedException{ + public MessagePacket generateAxolotlChat(Message message) { return generateAxolotlChat(message, false); } - public MessagePacket generateAxolotlChat(Message message, boolean addDelay) throws NoSessionsCreatedException{ + public MessagePacket generateAxolotlChat(Message message, boolean addDelay) { MessagePacket packet = preparePacket(message, addDelay); AxolotlService service = message.getConversation().getAccount().getAxolotlService(); Log.d(Config.LOGTAG, "Submitting message to axolotl service for send processing..."); - XmppAxolotlMessage axolotlMessage = service.processSending(message.getContact(), - message.getBody()); + XmppAxolotlMessage axolotlMessage = service.encrypt(message); + if (axolotlMessage == null) { + return null; + } packet.setAxolotlMessage(axolotlMessage.toXml()); return packet; } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index de7057308..31f70b97b 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -7,6 +7,7 @@ import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; import java.util.List; +import java.util.Set; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -105,6 +106,7 @@ public class MessageParser extends AbstractParser implements XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage); if(plaintextMessage != null) { finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, Message.STATUS_RECEIVED); + finishedMessage.setAxolotlSession(plaintextMessage.getSession()); } return finishedMessage; @@ -189,15 +191,9 @@ public class MessageParser extends AbstractParser implements } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) { Log.d(Config.LOGTAG, "Received PEP device list update from "+ from + ", processing..."); Element item = items.findChild("item"); - List deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); AxolotlService axolotlService = account.getAxolotlService(); - if(account.getJid().toBareJid().equals(from)) { - } else { - Contact contact = account.getRoster().getContact(from); - for (Integer deviceId : deviceIds) { - axolotlService.fetchBundleIfNeeded(contact, deviceId); - } - } + axolotlService.registerDevices(from, deviceIds); } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index f96e5d7eb..8b7161210 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -703,7 +703,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) { message.getConversation().endOtrIfNeeded(); - message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { + message.getConversation().findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, + new Conversation.OnMessageFound() { @Override public void onMessageFound(Message message) { markMessage(message,Message.STATUS_SEND_FAILED); @@ -758,12 +759,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } break; case Message.ENCRYPTION_AXOLOTL: - try { - packet = mMessageGenerator.generateAxolotlChat(message); - Log.d(Config.LOGTAG, "Succeeded generating axolotl chat message!"); - } catch (NoSessionsCreatedException e) { - message.setStatus(Message.STATUS_WAITING); - } + message.setStatus(Message.STATUS_WAITING); + account.getAxolotlService().sendMessage(message); break; } @@ -1797,7 +1794,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa account.getJid().toBareJid() + " otr session established with " + conversation.getJid() + "/" + otrSession.getSessionID().getUserID()); - conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { + conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() { @Override public void onMessageFound(Message message) { -- cgit v1.2.3 From 1b0596d57473f7aafa633ed2d5f3b3610a653a51 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 29 Jun 2015 14:25:23 +0200 Subject: Tag messages with originating session This can be used later in order to display trust status of messages, as well as for potential resending of messages in case of preKey conflicts. --- .../eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java | 5 +++++ src/main/java/eu/siacs/conversations/entities/Message.java | 4 ++++ 2 files changed, 9 insertions(+) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index e1b956507..01b06c226 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -75,6 +75,11 @@ public class XmppAxolotlMessage { public String getPlaintext() { return plaintext; } + + public AxolotlService.XmppAxolotlSession getSession() { + return session; + } + } public XmppAxolotlMessage(Contact contact, Element axolotlMessage) { diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 336af9721..b1dffc820 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -670,4 +670,8 @@ public class Message extends AbstractEntity { public boolean isTrusted() { return this.axolotlSession != null && this.axolotlSession.isTrusted(); } + + public void setAxolotlSession(AxolotlService.XmppAxolotlSession session) { + this.axolotlSession = session; + } } -- cgit v1.2.3 From 992cf5652e37f075bfcd9ed4f862fe407f3404dc Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 29 Jun 2015 14:30:23 +0200 Subject: When receiving, add mock session if none exists We need a session object in order to build a session from a PreKeyWhisperMessage, so add an empty one when none exists on receiving a message. Warning: this will break right now if the session can not be constructed from the received message.There will be an invalid session which will break if we try to send using it. --- src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 54394dd28..6a493e548 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -904,7 +904,7 @@ public class AxolotlService { Log.d(Config.LOGTAG, "No axolotl session found while parsing received message " + message); // TODO: handle this properly session = new XmppAxolotlSession(axolotlStore, senderAddress); - + sessions.put(senderAddress,session); } for(XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { -- cgit v1.2.3 From 9a0232f7e7271209afb04395091c6ca7016fed09 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 29 Jun 2015 14:33:43 +0200 Subject: Formatting fixes --- .../java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 6a493e548..1b591f781 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -901,13 +901,13 @@ public class AxolotlService { XmppAxolotlSession session = sessions.get(senderAddress); if (session == null) { - Log.d(Config.LOGTAG, "No axolotl session found while parsing received message " + message); + Log.d(Config.LOGTAG, "Account: "+account.getJid()+" No axolotl session found while parsing received message " + message); // TODO: handle this properly session = new XmppAxolotlSession(axolotlStore, senderAddress); sessions.put(senderAddress,session); } - for(XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { + for (XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { if (header.getRecipientDeviceId() == ownDeviceId) { Log.d(Config.LOGTAG, "Found axolotl header matching own device ID, processing..."); byte[] payloadKey = session.processReceiving(header); -- cgit v1.2.3 From 18c1e15d002f415c4449afe06e6dc80aef5aeade Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 3 Jul 2015 13:20:27 +0200 Subject: Rework PEP content verification Now checks which part(s) are out of sync w/ local storage, and updates only those, rather than assuming the entire node corrupt and overwriting it all (especially relevant for preKey list) --- .../crypto/axolotl/AxolotlService.java | 108 ++++++++++++--------- .../siacs/conversations/generator/IqGenerator.java | 2 +- 2 files changed, 62 insertions(+), 48 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 1b591f781..470c52067 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -655,42 +655,6 @@ public class AxolotlService { }); } - private boolean validateBundle(PreKeyBundle bundle) { - if (bundle == null || bundle.getIdentityKey() == null - || bundle.getSignedPreKey() == null || bundle.getSignedPreKeySignature() == null) { - return false; - } - - try { - SignedPreKeyRecord signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); - IdentityKey identityKey = axolotlStore.getIdentityKeyPair().getPublicKey(); - Log.d(Config.LOGTAG,"own identity key:"+identityKey.getFingerprint()+", foreign: "+bundle.getIdentityKey().getFingerprint()); - Log.d(Config.LOGTAG,"bundle: "+Boolean.toString(bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())) - +" " + Boolean.toString(Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) - +" " + Boolean.toString( bundle.getIdentityKey().equals(identityKey))); - return bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) - && Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature()) - && bundle.getIdentityKey().equals(identityKey); - } catch (InvalidKeyIdException ignored) { - return false; - } - } - - private boolean validatePreKeys(Map keys) { - if(keys == null) { return false; } - for(Integer id:keys.keySet()) { - try { - PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id); - if(!preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) { - return false; - } - } catch (InvalidKeyIdException ignored) { - return false; - } - } - return true; - } - public void publishBundlesIfNeeded() { IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), ownDeviceId); mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { @@ -698,25 +662,75 @@ public class AxolotlService { public void onIqPacketReceived(Account account, IqPacket packet) { PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); - SignedPreKeyRecord signedPreKeyRecord; - List preKeyRecords; - if (!validateBundle(bundle) || keys.isEmpty() || !validatePreKeys(keys)) { + boolean flush = false; + if (bundle == null) { + Log.e(Config.LOGTAG, "Received invalid bundle:" + packet); + bundle = new PreKeyBundle(-1, -1, -1 , null, -1, null, null, null); + flush = true; + } + if (keys == null) { + Log.e(Config.LOGTAG, "Received invalid prekeys:" + packet); + } + try { + boolean changed = false; + // Validate IdentityKey + IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair(); + if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) { + Log.d(Config.LOGTAG, "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); + changed = true; + } + + // Validate signedPreKeyRecord + ID + SignedPreKeyRecord signedPreKeyRecord; int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size(); try { - signedPreKeyRecord = KeyHelper.generateSignedPreKey( - axolotlStore.getIdentityKeyPair(), numSignedPreKeys + 1); + signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); + if ( flush + ||!bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) + || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { + Log.d(Config.LOGTAG, "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); + axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + changed = true; + } + } catch (InvalidKeyIdException e) { + Log.d(Config.LOGTAG, "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + changed = true; + } - preKeyRecords = KeyHelper.generatePreKeys( - axolotlStore.getCurrentPreKeyId(), NUM_KEYS_TO_PUBLISH); - for (PreKeyRecord record : preKeyRecords) { + // Validate PreKeys + Set preKeyRecords = new HashSet<>(); + if (keys != null) { + for (Integer id : keys.keySet()) { + try { + PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id); + if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) { + preKeyRecords.add(preKeyRecord); + } + } catch (InvalidKeyIdException ignored) { + } + } + } + int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size(); + if (newKeys > 0) { + List newRecords = KeyHelper.generatePreKeys( + axolotlStore.getCurrentPreKeyId()+1, newKeys); + preKeyRecords.addAll(newRecords); + for (PreKeyRecord record : newRecords) { axolotlStore.storePreKey(record.getId(), record); } + changed = true; + Log.d(Config.LOGTAG, "Adding " + newKeys + " new preKeys to PEP."); + } + + if(changed) { IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), preKeyRecords, ownDeviceId); - Log.d(Config.LOGTAG, "Bundle " + getOwnDeviceId() + " not in PEP. Publishing: " + publish); + Log.d(Config.LOGTAG, "Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -724,10 +738,10 @@ public class AxolotlService { Log.d(Config.LOGTAG, "Published bundle, got: " + packet); } }); - } catch (InvalidKeyException e) { + } + } catch (InvalidKeyException e) { Log.e(Config.LOGTAG, "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); return; - } } } }); diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 0bef88532..19c1d4f73 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -152,7 +152,7 @@ public class IqGenerator extends AbstractGenerator { } public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, - final List preKeyRecords, final int deviceId) { + final Set preKeyRecords, final int deviceId) { final Element item = new Element("item"); final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX); final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic"); -- cgit v1.2.3 From ec6870307e0ecee8184ddfef73444290e9d15828 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 3 Jul 2015 13:27:35 +0200 Subject: Properly track message sender Previously, the sender was assumed to be the conversation counterpart. This broke carboned own-device messages. We now track the sender properly, and also set the status (sent by one of the own devices vs received from the counterpart) accordingly. --- .../conversations/crypto/axolotl/AxolotlService.java | 8 ++++---- .../conversations/crypto/axolotl/XmppAxolotlMessage.java | 15 ++++++++------- .../java/eu/siacs/conversations/parser/MessageParser.java | 8 ++++---- 3 files changed, 16 insertions(+), 15 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 470c52067..d4089b202 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -856,14 +856,14 @@ public class AxolotlService { @Nullable public XmppAxolotlMessage encrypt(Message message ){ - final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(message.getContact(), + final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(message.getContact().getJid().toBareJid(), ownDeviceId, message.getBody()); - if(findSessionsforContact(axolotlMessage.getContact()).isEmpty()) { + if(findSessionsforContact(message.getContact()).isEmpty()) { return null; } Log.d(Config.LOGTAG, "Building axolotl foreign headers..."); - for (XmppAxolotlSession session : findSessionsforContact(axolotlMessage.getContact())) { + for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { Log.d(Config.LOGTAG, session.remoteAddress.toString()); //if(!session.isTrusted()) { // TODO: handle this properly @@ -910,7 +910,7 @@ public class AxolotlService { public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) { XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; - AxolotlAddress senderAddress = new AxolotlAddress(message.getContact().getJid().toBareJid().toString(), + AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(), message.getSenderDeviceId()); XmppAxolotlSession session = sessions.get(senderAddress); diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index 01b06c226..06dd2cdaf 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -19,13 +19,14 @@ import javax.crypto.spec.SecretKeySpec; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.jid.Jid; public class XmppAxolotlMessage { private byte[] innerKey; private byte[] ciphertext; private byte[] iv; private final Set headers; - private final Contact contact; + private final Jid from; private final int sourceDeviceId; public static class XmppAxolotlMessageHeader { @@ -82,8 +83,8 @@ public class XmppAxolotlMessage { } - public XmppAxolotlMessage(Contact contact, Element axolotlMessage) { - this.contact = contact; + public XmppAxolotlMessage(Jid from, Element axolotlMessage) { + this.from = from; this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id")); this.headers = new HashSet<>(); for(Element child:axolotlMessage.getChildren()) { @@ -101,8 +102,8 @@ public class XmppAxolotlMessage { } } - public XmppAxolotlMessage(Contact contact, int sourceDeviceId, String plaintext) { - this.contact = contact; + public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) { + this.from = from; this.sourceDeviceId = sourceDeviceId; this.headers = new HashSet<>(); this.encrypt(plaintext); @@ -124,8 +125,8 @@ public class XmppAxolotlMessage { } } - public Contact getContact() { - return this.contact; + public Jid getFrom() { + return this.from; } public int getSenderDeviceId() { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 31f70b97b..cc878e7fb 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -99,13 +99,13 @@ public class MessageParser extends AbstractParser implements } } - private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation) { + private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation, int status) { Message finishedMessage = null; AxolotlService service = conversation.getAccount().getAxolotlService(); - XmppAxolotlMessage xmppAxolotlMessage = new XmppAxolotlMessage(conversation.getContact(), axolotlMessage); + XmppAxolotlMessage xmppAxolotlMessage = new XmppAxolotlMessage(from.toBareJid(), axolotlMessage); XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage); if(plaintextMessage != null) { - finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, Message.STATUS_RECEIVED); + finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); finishedMessage.setAxolotlSession(plaintextMessage.getSession()); } @@ -322,7 +322,7 @@ public class MessageParser extends AbstractParser implements } else if (pgpEncrypted != null) { message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status); } else if (axolotlEncrypted != null) { - message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation); + message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation, status); if (message == null) { return; } -- cgit v1.2.3 From 69600502d217334031732cef2e80ebd4461e522d Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 3 Jul 2015 13:31:14 +0200 Subject: Fix asynchronous axolotl message sending XmppConnectionService.sendMessage() now dispatches messages to the AxolotlService, where they only are prepared for sending and cached. AxolotlService now triggers a XmppConnectionService.resendMessage(), which then handles sending the cached message packet. This transparently fixes, e.g., handling of messages sent while we are offline. --- .../crypto/axolotl/AxolotlService.java | 29 +++++++++++++++++----- .../services/XmppConnectionService.java | 6 +++-- 2 files changed, 27 insertions(+), 8 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index d4089b202..d79fa1bf8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -67,6 +67,7 @@ public class AxolotlService { private final SQLiteAxolotlStore axolotlStore; private final SessionMap sessions; private final Map> deviceIds; + private final Map messageCache; private final FetchStatusMap fetchStatusMap; private final SerialSingleThreadExecutor executor; private int ownDeviceId; @@ -580,6 +581,7 @@ public class AxolotlService { this.account = account; this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); this.deviceIds = new HashMap<>(); + this.messageCache = new HashMap<>(); this.sessions = new SessionMap(axolotlStore, account); this.fetchStatusMap = new FetchStatusMap(); this.executor = new SerialSingleThreadExecutor(); @@ -892,20 +894,35 @@ public class AxolotlService { .generateAxolotlChat(message); if (packet == null) { mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + //mXmppConnectionService.updateConversationUi(); } else { - mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); - mXmppConnectionService.sendMessagePacket(account, packet); + Log.d(Config.LOGTAG, "Generated message, caching: " + message.getUuid()); + messageCache.put(message.getUuid(), packet); + mXmppConnectionService.resendMessage(message); } } }); } - public void sendMessage(Message message) { - boolean newSessions = createSessionsIfNeeded(message.getConversation()); + public void prepareMessage(Message message) { + if (!messageCache.containsKey(message.getUuid())) { + boolean newSessions = createSessionsIfNeeded(message.getConversation()); - if (!newSessions) { - this.processSending(message); + if (!newSessions) { + this.processSending(message); + } + } + } + + public MessagePacket fetchPacketFromCache(Message message) { + MessagePacket packet = messageCache.get(message.getUuid()); + if (packet != null) { + Log.d(Config.LOGTAG, "Cache hit: " + message.getUuid()); + messageCache.remove(message.getUuid()); + } else { + Log.d(Config.LOGTAG, "Cache miss: " + message.getUuid()); } + return packet; } public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 8b7161210..3e443d75f 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -52,7 +52,6 @@ import de.duenndns.ssl.MemorizingTrustManager; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; -import eu.siacs.conversations.crypto.axolotl.NoSessionsCreatedException; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Bookmark; @@ -760,7 +759,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa break; case Message.ENCRYPTION_AXOLOTL: message.setStatus(Message.STATUS_WAITING); - account.getAxolotlService().sendMessage(message); + packet = account.getAxolotlService().fetchPacketFromCache(message); + if (packet == null && account.isOnlineAndConnected()) { + account.getAxolotlService().prepareMessage(message); + } break; } -- cgit v1.2.3 From bf4185ac08a42e9d16bf1e2fc0126bff467a55be Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 3 Jul 2015 13:34:34 +0200 Subject: Refresh PEP on session establish We now track preKeys used to establish incoming sessions with us. On each new established session, we remove the used prekey from PEP. We have to do this because libaxolotl-java internally clears the used preKey from its storage, so we will not be able to establish any future sessions using that key. --- .../conversations/crypto/axolotl/AxolotlService.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index d79fa1bf8..420c75b5b 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -444,6 +444,7 @@ public class AxolotlService { public static class XmppAxolotlSession { private SessionCipher cipher; private boolean isTrusted = false; + private Integer preKeyId = null; private SQLiteAxolotlStore sqLiteAxolotlStore; private AxolotlAddress remoteAddress; @@ -463,6 +464,14 @@ public class AxolotlService { return this.isTrusted; } + public Integer getPreKeyId() { + return preKeyId; + } + + public void resetPreKeyId() { + preKeyId = null; + } + public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { byte[] plaintext = null; try { @@ -470,6 +479,9 @@ public class AxolotlService { PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); Log.d(Config.LOGTAG, "PreKeyWhisperMessage ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); plaintext = cipher.decrypt(message); + if (message.getPreKeyId().isPresent()) { + preKeyId = message.getPreKeyId().get(); + } } catch (InvalidMessageException | InvalidVersionException e) { WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); plaintext = cipher.decrypt(message); @@ -946,6 +958,12 @@ public class AxolotlService { Log.d(Config.LOGTAG, "Got payload key from axolotl header. Decrypting message..."); plaintextMessage = message.decrypt(session, payloadKey); } + Integer preKeyId = session.getPreKeyId(); + if (preKeyId != null) { + publishBundlesIfNeeded(); + session.resetPreKeyId(); + } + break; } } -- cgit v1.2.3 From 25450bf6d365e4bb71addd38275296575f3a5658 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Sun, 5 Jul 2015 22:10:43 +0200 Subject: Trust all IdentityKeys The trust-on-first-use policy leads to problems when receiving messages from two different devices of a contact before sending a message to them (as their IdentityKeys will not have been added yet). Since session trust will be managed externally anyway, this change is not a security problem, and will allow us to decrypt messages from yet-untrusted sessions. --- .../java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 420c75b5b..cdd8d85d9 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -233,8 +233,9 @@ public class AxolotlService { */ @Override public boolean isTrustedIdentity(String name, IdentityKey identityKey) { - Set trustedKeys = mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name); - return trustedKeys.isEmpty() || trustedKeys.contains(identityKey); + //Set trustedKeys = mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name); + //return trustedKeys.isEmpty() || trustedKeys.contains(identityKey); + return true; } // -------------------------------------- -- cgit v1.2.3 From 6867b5c3abeeb5116a2542c56a706b733fd9cbf0 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Sun, 5 Jul 2015 22:53:34 +0200 Subject: Return empty set on invalid PEP devicelist --- .../crypto/axolotl/AxolotlService.java | 3 +- .../eu/siacs/conversations/parser/IqParser.java | 36 +++++++++++----------- 2 files changed, 20 insertions(+), 19 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index cdd8d85d9..d37879c3d 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.crypto.axolotl; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Base64; import android.util.Log; @@ -638,7 +639,7 @@ public class AxolotlService { return ownDeviceId; } - public void registerDevices(final Jid jid, final Set deviceIds) { + public void registerDevices(final Jid jid, @NonNull final Set deviceIds) { for(Integer i:deviceIds) { Log.d(Config.LOGTAG, "Adding Device ID:"+ jid + ":"+i); } diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 935517877..c147978e0 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.parser; +import android.support.annotation.NonNull; import android.util.Base64; import android.util.Log; @@ -96,26 +97,25 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { return items.findChild("item"); } + @NonNull public Set deviceIds(final Element item) { Set deviceIds = new HashSet<>(); - if (item == null) { - return null; - } - final Element list = item.findChild("list"); - if(list == null) { - return null; - } - for(Element device : list.getChildren()) { - if(!device.getName().equals("device")) { - continue; - } - try { - Integer id = Integer.valueOf(device.getAttribute("id")); - deviceIds.add(id); - } catch (NumberFormatException e) { - Log.e(Config.LOGTAG, "Encountered nvalid node in PEP:" + device.toString() - + ", skipping..."); - continue; + if (item != null) { + final Element list = item.findChild("list"); + if (list != null) { + for (Element device : list.getChildren()) { + if (!device.getName().equals("device")) { + continue; + } + try { + Integer id = Integer.valueOf(device.getAttribute("id")); + deviceIds.add(id); + } catch (NumberFormatException e) { + Log.e(Config.LOGTAG, "Encountered nvalid node in PEP:" + device.toString() + + ", skipping..."); + continue; + } + } } } return deviceIds; -- cgit v1.2.3 From 0cf64857cfa8d42b8759ca2934af91d6060c55a5 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Sun, 5 Jul 2015 22:54:28 +0200 Subject: Only cache session if successfully established When receiving a message, only remember the XmppAxolotlSession wrapper if the prospective session was actually established. This prevents us from erroneously adding empty sessions that are never established using received PreKeyWhisperMessages, which would lead to errors if we try to use them for sending. --- .../java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index d37879c3d..faa0e5add 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -944,12 +944,13 @@ public class AxolotlService { AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(), message.getSenderDeviceId()); + boolean newSession = false; XmppAxolotlSession session = sessions.get(senderAddress); if (session == null) { Log.d(Config.LOGTAG, "Account: "+account.getJid()+" No axolotl session found while parsing received message " + message); // TODO: handle this properly session = new XmppAxolotlSession(axolotlStore, senderAddress); - sessions.put(senderAddress,session); + newSession = true; } for (XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { @@ -969,6 +970,10 @@ public class AxolotlService { } } + if (newSession && plaintextMessage != null) { + sessions.put(senderAddress,session); + } + return plaintextMessage; } } -- cgit v1.2.3 From 491f623708437f418497ecace2876e9c81708e72 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 7 Jul 2015 19:27:12 +0200 Subject: Fix displaying Contact IdentityKeys Migrate ContactDetailsActivity to use new SQL IdentityKeys storage, remove dead code from Contact class. --- .../eu/siacs/conversations/entities/Contact.java | 64 ---------------------- .../conversations/ui/ContactDetailsActivity.java | 3 +- 2 files changed, 2 insertions(+), 65 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 240d5223c..fdb5f932a 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -350,70 +350,6 @@ public class Contact implements ListItem, Blockable { } } - public List getAxolotlIdentityKeys() { - synchronized (this.keys) { - JSONArray serializedKeyItems = this.keys.optJSONArray("axolotl_identity_key"); - List identityKeys = new ArrayList<>(); - List toDelete = new ArrayList<>(); - if(serializedKeyItems != null) { - for(int i = 0; i Date: Tue, 7 Jul 2015 19:28:35 +0200 Subject: Adapt prettifyFingerprint() to axolotl FP sizes --- .../java/eu/siacs/conversations/ui/ContactDetailsActivity.java | 2 +- src/main/java/eu/siacs/conversations/utils/CryptoHelper.java | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 2777b8148..33861f828 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -387,7 +387,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd .findViewById(R.id.button_remove); remove.setVisibility(View.VISIBLE); keyType.setText("Axolotl Fingerprint"); - key.setText(identityKey.getFingerprint()); + key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint())); keys.addView(view); remove.setOnClickListener(new OnClickListener() { diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java index 2dec203d9..c7c9ac423 100644 --- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java @@ -96,11 +96,10 @@ public final class CryptoHelper { } else if (fingerprint.length() < 40) { return fingerprint; } - StringBuilder builder = new StringBuilder(fingerprint); - builder.insert(8, " "); - builder.insert(17, " "); - builder.insert(26, " "); - builder.insert(35, " "); + StringBuilder builder = new StringBuilder(fingerprint.replaceAll("\\s","")); + for(int i=8;i Date: Tue, 7 Jul 2015 19:30:08 +0200 Subject: Refactor axolotl database recreation --- .../conversations/persistance/DatabaseBackend.java | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index c70ffad2c..a3868a1d9 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -283,14 +283,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { cursor.close(); } if (oldVersion < 15 && newVersion >= 15) { - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME); - db.execSQL(CREATE_SESSIONS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME); - db.execSQL(CREATE_PREKEYS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); - db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME); - db.execSQL(CREATE_IDENTITIES_STATEMENT); + recreateAxolotlDb(); } } @@ -868,4 +861,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) { storeIdentityKey(account, name, true, Base64.encodeToString(identityKeyPair.serialize(),Base64.DEFAULT)); } + + public void recreateAxolotlDb() { + Log.d(Config.LOGTAG, ">>> (RE)CREATING AXOLOTL DATABASE <<<"); + SQLiteDatabase db = this.getWritableDatabase(); + db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME); + db.execSQL(CREATE_SESSIONS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME); + db.execSQL(CREATE_PREKEYS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); + db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME); + db.execSQL(CREATE_IDENTITIES_STATEMENT); + } } -- cgit v1.2.3 From 968410ae33f7ed341868847f3fedbd03ebd54f2b Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 7 Jul 2015 19:32:52 +0200 Subject: Fix devicelist update handling No longer store own device ID (so that we don't encrypt messages for ourselves), verify that own device ID is present in update list (otherwise republish), reflect update in UI. --- .../java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 5 +++++ src/main/java/eu/siacs/conversations/parser/MessageParser.java | 1 + 2 files changed, 6 insertions(+) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index faa0e5add..2b0954c6d 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -640,10 +640,15 @@ public class AxolotlService { } public void registerDevices(final Jid jid, @NonNull final Set deviceIds) { + if(deviceIds.contains(getOwnDeviceId())) { + Log.d(Config.LOGTAG, "Skipping own Device ID:"+ jid + ":"+getOwnDeviceId()); + deviceIds.remove(getOwnDeviceId()); + } for(Integer i:deviceIds) { Log.d(Config.LOGTAG, "Adding Device ID:"+ jid + ":"+i); } this.deviceIds.put(jid, deviceIds); + publishOwnDeviceIdIfNeeded(); } public void publishOwnDeviceIdIfNeeded() { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index cc878e7fb..0dc8d5215 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -194,6 +194,7 @@ public class MessageParser extends AbstractParser implements Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); AxolotlService axolotlService = account.getAxolotlService(); axolotlService.registerDevices(from, deviceIds); + mXmppConnectionService.updateAccountUi(); } } -- cgit v1.2.3 From 7049904c326e6dabc02138fa436d209bf724e0bc Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 7 Jul 2015 19:36:22 +0200 Subject: Add basic PEP managemend UI to EditAccountActivity EditAccountActivity now show own fingerprint, and gives an option to regenerate local keying material (and wipe all sessions associated with the old keys in the process). It also now displays a list of other own devices, and gives an option to remove all but the current device. --- .../crypto/axolotl/AxolotlService.java | 33 +++++++ .../conversations/persistance/DatabaseBackend.java | 21 ++++ .../conversations/ui/EditAccountActivity.java | 109 ++++++++++++++++++++- 3 files changed, 159 insertions(+), 4 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 2b0954c6d..ec4eb7c57 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -176,6 +176,13 @@ public class AxolotlService { return reg_id; } + public void regenerate() { + mXmppConnectionService.databaseBackend.wipeAxolotlDb(account); + account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0)); + identityKeyPair = loadIdentityKeyPair(); + currentPreKeyId = 0; + mXmppConnectionService.updateAccountUi(); + } /** * Get the local client's identity key pair. @@ -602,6 +609,10 @@ public class AxolotlService { this.ownDeviceId = axolotlStore.getLocalRegistrationId(); } + public IdentityKey getOwnPublicKey() { + return axolotlStore.getIdentityKeyPair().getPublicKey(); + } + public void trustSession(AxolotlAddress counterpart) { XmppAxolotlSession session = sessions.get(counterpart); if (session != null) { @@ -635,10 +646,19 @@ public class AxolotlService { return sessions.hasAny(contactAddress); } + public void regenerateKeys() { + axolotlStore.regenerate(); + publishBundlesIfNeeded(); + } + public int getOwnDeviceId() { return ownDeviceId; } + public Set getOwnDeviceIds() { + return this.deviceIds.get(account.getJid().toBareJid()); + } + public void registerDevices(final Jid jid, @NonNull final Set deviceIds) { if(deviceIds.contains(getOwnDeviceId())) { Log.d(Config.LOGTAG, "Skipping own Device ID:"+ jid + ":"+getOwnDeviceId()); @@ -651,6 +671,19 @@ public class AxolotlService { publishOwnDeviceIdIfNeeded(); } + public void wipeOtherPepDevices() { + Set deviceIds = new HashSet<>(); + deviceIds.add(getOwnDeviceId()); + IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); + Log.d(Config.LOGTAG, "Wiping all other devices from Pep:" + publish); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + // TODO: implement this! + } + }); + } + public void publishOwnDeviceIdIfNeeded() { IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid()); mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index a3868a1d9..1aa2bade9 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -874,4 +874,25 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME); db.execSQL(CREATE_IDENTITIES_STATEMENT); } + + public void wipeAxolotlDb(Account account) { + String accountName = account.getUuid(); + Log.d(Config.LOGTAG, ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<"); + SQLiteDatabase db = this.getWritableDatabase(); + String[] deleteArgs= { + accountName + }; + db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 908c29d21..ab4dc0592 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -1,9 +1,13 @@ package eu.siacs.conversations.ui; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; import android.app.PendingIntent; +import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.text.Editable; +import android.text.TextUtils; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; @@ -23,6 +27,8 @@ import android.widget.TableLayout; import android.widget.TextView; import android.widget.Toast; +import java.util.Set; + import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; @@ -54,9 +60,16 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private TextView mServerInfoPep; private TextView mSessionEst; private TextView mOtrFingerprint; + private TextView mAxolotlFingerprint; + private TextView mAxolotlDevicelist; private ImageView mAvatar; private RelativeLayout mOtrFingerprintBox; + private RelativeLayout mAxolotlFingerprintBox; + private RelativeLayout mAxolotlDevicelistBox; private ImageButton mOtrFingerprintToClipboardButton; + private ImageButton mAxolotlFingerprintToClipboardButton; + private ImageButton mWipeAxolotlPepButton; + private ImageButton mRegenerateAxolotlKeyButton; private Jid jidToEdit; private Account mAccount; @@ -310,6 +323,13 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint); this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box); this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard); + this.mAxolotlFingerprint = (TextView) findViewById(R.id.axolotl_fingerprint); + this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box); + this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard); + this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key); + this.mAxolotlDevicelist = (TextView) findViewById(R.id.axolotl_devicelist); + this.mAxolotlDevicelistBox = (RelativeLayout) findViewById(R.id.axolotl_devices_box); + this.mWipeAxolotlPepButton = (ImageButton) findViewById(R.id.action_wipe_axolotl_pep); this.mSaveButton = (Button) findViewById(R.id.save_button); this.mCancelButton = (Button) findViewById(R.id.cancel_button); this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); @@ -477,10 +497,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } else { this.mServerInfoPep.setText(R.string.server_info_unavailable); } - final String fingerprint = this.mAccount.getOtrFingerprint(); - if (fingerprint != null) { + final String otrFingerprint = this.mAccount.getOtrFingerprint(); + if (otrFingerprint != null) { this.mOtrFingerprintBox.setVisibility(View.VISIBLE); - this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(fingerprint)); + this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); this.mOtrFingerprintToClipboardButton .setVisibility(View.VISIBLE); this.mOtrFingerprintToClipboardButton @@ -489,7 +509,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void onClick(final View v) { - if (copyTextToClipboard(fingerprint, R.string.otr_fingerprint)) { + if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) { Toast.makeText( EditAccountActivity.this, R.string.toast_message_otr_fingerprint, @@ -500,6 +520,55 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } else { this.mOtrFingerprintBox.setVisibility(View.GONE); } + final Set ownDevices = this.mAccount.getAxolotlService().getOwnDeviceIds(); + if (ownDevices != null && !ownDevices.isEmpty()) { + this.mAxolotlDevicelistBox.setVisibility(View.VISIBLE); + this.mAxolotlDevicelist.setText(TextUtils.join(", ", ownDevices)); + this.mWipeAxolotlPepButton + .setVisibility(View.VISIBLE); + this.mWipeAxolotlPepButton + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final View v) { + showWipePepDialog(); + } + }); + } else { + this.mAxolotlDevicelistBox.setVisibility(View.GONE); + } + final String axolotlFingerprint = this.mAccount.getAxolotlService().getOwnPublicKey().getFingerprint(); + if (axolotlFingerprint != null) { + this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE); + this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(axolotlFingerprint)); + this.mAxolotlFingerprintToClipboardButton + .setVisibility(View.VISIBLE); + this.mAxolotlFingerprintToClipboardButton + .setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(final View v) { + + if (copyTextToClipboard(axolotlFingerprint, R.string.axolotl_fingerprint)) { + Toast.makeText( + EditAccountActivity.this, + R.string.toast_message_axolotl_fingerprint, + Toast.LENGTH_SHORT).show(); + } + } + }); + this.mRegenerateAxolotlKeyButton + .setVisibility(View.VISIBLE); + this.mRegenerateAxolotlKeyButton + .setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(final View v) { + showRegenerateAxolotlKeyDialog(); + } + }); + } else { + this.mAxolotlFingerprintBox.setVisibility(View.GONE); + } } else { if (this.mAccount.errorStatus()) { this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId())); @@ -512,4 +581,36 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mStats.setVisibility(View.GONE); } } + + public void showRegenerateAxolotlKeyDialog() { + Builder builder = new Builder(this); + builder.setTitle("Regenerate Key"); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage("Are you sure you want to regenerate your Identity Key? (This will also wipe all established sessions and contact Identity Keys)"); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton("Yes", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAccount.getAxolotlService().regenerateKeys(); + } + }); + builder.create().show(); + } + + public void showWipePepDialog() { + Builder builder = new Builder(this); + builder.setTitle("Wipe PEP"); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage("Are you sure you want to wipe all other devices from the PEP device ID list?"); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton("Yes", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAccount.getAxolotlService().wipeOtherPepDevices(); + } + }); + builder.create().show(); + } } -- cgit v1.2.3 From 3458f5bb9182ca99dd800e4cde21168323260da4 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 8 Jul 2015 17:44:24 +0200 Subject: Clean up logging Add a fixed prefix to axolotl-related log messages, set log levels sensibly. --- .../crypto/axolotl/AxolotlService.java | 115 +++++++++++---------- .../conversations/generator/MessageGenerator.java | 2 +- .../eu/siacs/conversations/parser/IqParser.java | 15 +-- .../siacs/conversations/parser/MessageParser.java | 2 +- .../conversations/persistance/DatabaseBackend.java | 8 +- 5 files changed, 75 insertions(+), 67 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index ec4eb7c57..8ac69d37a 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -61,6 +61,8 @@ public class AxolotlService { public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist"; public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles"; + public static final String LOGPREFIX = "AxolotlService"; + public static final int NUM_KEYS_TO_PUBLISH = 10; private final Account account; @@ -100,7 +102,7 @@ public class AxolotlService { private static IdentityKeyPair generateIdentityKeyPair() { - Log.d(Config.LOGTAG, "Generating axolotl IdentityKeyPair..."); + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl IdentityKeyPair..."); ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); IdentityKeyPair ownKey = new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), identityKeyPairKeys.getPrivateKey()); @@ -108,7 +110,7 @@ public class AxolotlService { } private static int generateRegistrationId() { - Log.d(Config.LOGTAG, "Generating axolotl registration ID..."); + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl registration ID..."); int reg_id = KeyHelper.generateRegistrationId(false); return reg_id; } @@ -119,7 +121,7 @@ public class AxolotlService { this.localRegistrationId = loadRegistrationId(); this.currentPreKeyId = loadCurrentPreKeyId(); for (SignedPreKeyRecord record : loadSignedPreKeys()) { - Log.d(Config.LOGTAG, "Got Axolotl signed prekey record:" + record.getId()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got Axolotl signed prekey record:" + record.getId()); } } @@ -139,7 +141,7 @@ public class AxolotlService { if (ownKey != null) { return ownKey; } else { - Log.d(Config.LOGTAG, "Could not retrieve axolotl key for account " + ownName); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve axolotl key for account " + ownName); ownKey = generateIdentityKeyPair(); mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey); } @@ -152,13 +154,13 @@ public class AxolotlService { if (regIdString != null) { reg_id = Integer.valueOf(regIdString); } else { - Log.d(Config.LOGTAG, "Could not retrieve axolotl registration id for account " + account.getJid()); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve axolotl registration id for account " + account.getJid()); reg_id = generateRegistrationId(); boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id)); if (success) { mXmppConnectionService.databaseBackend.updateAccount(account); } else { - Log.e(Config.LOGTAG, "Failed to write new key to the database!"); + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to write new key to the database!"); } } return reg_id; @@ -170,7 +172,7 @@ public class AxolotlService { if (regIdString != null) { reg_id = Integer.valueOf(regIdString); } else { - Log.d(Config.LOGTAG, "Could not retrieve current prekey id for account " + account.getJid()); + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve current prekey id for account " + account.getJid()); reg_id = 0; } return reg_id; @@ -366,7 +368,7 @@ public class AxolotlService { if (success) { mXmppConnectionService.databaseBackend.updateAccount(account); } else { - Log.e(Config.LOGTAG, "Failed to write new prekey id to the database!"); + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to write new prekey id to the database!"); } } @@ -456,11 +458,13 @@ public class AxolotlService { private Integer preKeyId = null; private SQLiteAxolotlStore sqLiteAxolotlStore; private AxolotlAddress remoteAddress; + private final Account account; - public XmppAxolotlSession(SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { + public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { this.cipher = new SessionCipher(store, remoteAddress); this.remoteAddress = remoteAddress; this.sqLiteAxolotlStore = store; + this.account = account; this.isTrusted = sqLiteAxolotlStore.isTrustedSession(remoteAddress); } @@ -486,21 +490,20 @@ public class AxolotlService { try { try { PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); - Log.d(Config.LOGTAG, "PreKeyWhisperMessage ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); plaintext = cipher.decrypt(message); if (message.getPreKeyId().isPresent()) { preKeyId = message.getPreKeyId().get(); } } catch (InvalidMessageException | InvalidVersionException e) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"WhisperMessage received"); WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); plaintext = cipher.decrypt(message); } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { - Log.d(Config.LOGTAG, "Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); } - } catch (LegacyMessageException | InvalidMessageException e) { - Log.d(Config.LOGTAG, "Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); - } catch (DuplicateMessageException | NoSessionException e) { - Log.d(Config.LOGTAG, "Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); + } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); } return plaintext; } @@ -580,7 +583,7 @@ public class AxolotlService { List deviceIDs = store.getSubDeviceSessions(address); for (Integer deviceId : deviceIDs) { AxolotlAddress axolotlAddress = new AxolotlAddress(address, deviceId); - this.put(axolotlAddress, new XmppAxolotlSession(store, axolotlAddress)); + this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress)); } } } @@ -596,6 +599,10 @@ public class AxolotlService { private static class FetchStatusMap extends AxolotlAddressMap { } + + public static String getLogprefix(Account account) { + return LOGPREFIX+" ("+account.getJid().toBareJid().toString()+"): "; + } public AxolotlService(Account account, XmppConnectionService connectionService) { this.mXmppConnectionService = connectionService; @@ -661,11 +668,11 @@ public class AxolotlService { public void registerDevices(final Jid jid, @NonNull final Set deviceIds) { if(deviceIds.contains(getOwnDeviceId())) { - Log.d(Config.LOGTAG, "Skipping own Device ID:"+ jid + ":"+getOwnDeviceId()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Skipping own Device ID:"+ jid + ":"+getOwnDeviceId()); deviceIds.remove(getOwnDeviceId()); } for(Integer i:deviceIds) { - Log.d(Config.LOGTAG, "Adding Device ID:"+ jid + ":"+i); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding Device ID:"+ jid + ":"+i); } this.deviceIds.put(jid, deviceIds); publishOwnDeviceIdIfNeeded(); @@ -675,7 +682,7 @@ public class AxolotlService { Set deviceIds = new HashSet<>(); deviceIds.add(getOwnDeviceId()); IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); - Log.d(Config.LOGTAG, "Wiping all other devices from Pep:" + publish); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Wiping all other devices from Pep:" + publish); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -697,7 +704,7 @@ public class AxolotlService { if (!deviceIds.contains(getOwnDeviceId())) { deviceIds.add(getOwnDeviceId()); IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); - Log.d(Config.LOGTAG, "Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing: " + publish); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing: " + publish); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -718,19 +725,19 @@ public class AxolotlService { Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); boolean flush = false; if (bundle == null) { - Log.e(Config.LOGTAG, "Received invalid bundle:" + packet); + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received invalid bundle:" + packet); bundle = new PreKeyBundle(-1, -1, -1 , null, -1, null, null, null); flush = true; } if (keys == null) { - Log.e(Config.LOGTAG, "Received invalid prekeys:" + packet); + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received invalid prekeys:" + packet); } try { boolean changed = false; // Validate IdentityKey IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair(); if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) { - Log.d(Config.LOGTAG, "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); changed = true; } @@ -742,13 +749,13 @@ public class AxolotlService { if ( flush ||!bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { - Log.d(Config.LOGTAG, "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); changed = true; } } catch (InvalidKeyIdException e) { - Log.d(Config.LOGTAG, "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); changed = true; @@ -776,7 +783,7 @@ public class AxolotlService { axolotlStore.storePreKey(record.getId(), record); } changed = true; - Log.d(Config.LOGTAG, "Adding " + newKeys + " new preKeys to PEP."); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding " + newKeys + " new preKeys to PEP."); } @@ -784,17 +791,17 @@ public class AxolotlService { IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), preKeyRecords, ownDeviceId); - Log.d(Config.LOGTAG, "Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+ ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { // TODO: implement this! - Log.d(Config.LOGTAG, "Published bundle, got: " + packet); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Published bundle, got: " + packet); } }); } } catch (InvalidKeyException e) { - Log.e(Config.LOGTAG, "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); return; } } @@ -809,21 +816,21 @@ public class AxolotlService { } private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address) { - Log.d(Config.LOGTAG, "Building new sesstion for " + address.getDeviceId()); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building new sesstion for " + address.getDeviceId()); try { IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice( Jid.fromString(address.getName()), address.getDeviceId()); - Log.d(Config.LOGTAG, "Retrieving bundle: " + bundlesPacket); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Retrieving bundle: " + bundlesPacket); mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - Log.d(Config.LOGTAG, "Received preKey IQ packet, processing..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received preKey IQ packet, processing..."); final IqParser parser = mXmppConnectionService.getIqParser(); final List preKeyBundleList = parser.preKeys(packet); final PreKeyBundle bundle = parser.bundle(packet); if (preKeyBundleList.isEmpty() || bundle == null) { - Log.d(Config.LOGTAG, "preKey IQ packet invalid: " + packet); + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"preKey IQ packet invalid: " + packet); fetchStatusMap.put(address, FetchStatus.ERROR); return; } @@ -845,11 +852,11 @@ public class AxolotlService { try { SessionBuilder builder = new SessionBuilder(axolotlStore, address); builder.process(preKeyBundle); - XmppAxolotlSession session = new XmppAxolotlSession(axolotlStore, address); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address); sessions.put(address, session); fetchStatusMap.put(address, FetchStatus.SUCCESS); } catch (UntrustedIdentityException|InvalidKeyException e) { - Log.d(Config.LOGTAG, "Error building session for " + address + ": " + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error building session for " + address + ": " + e.getClass().getName() + ", " + e.getMessage()); fetchStatusMap.put(address, FetchStatus.ERROR); } @@ -869,32 +876,32 @@ public class AxolotlService { } }); } catch (InvalidJidException e) { - Log.e(Config.LOGTAG,"Got address with invalid jid: " + address.getName()); + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got address with invalid jid: " + address.getName()); } } private boolean createSessionsIfNeeded(Conversation conversation) { boolean newSessions = false; - Log.d(Config.LOGTAG, "Creating axolotl sessions if needed..."); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Creating axolotl sessions if needed..."); Jid contactJid = conversation.getContact().getJid().toBareJid(); Set addresses = new HashSet<>(); if(deviceIds.get(contactJid) != null) { for(Integer foreignId:this.deviceIds.get(contactJid)) { - Log.d(Config.LOGTAG, "Found device "+account.getJid().toBareJid()+":"+foreignId); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found device "+account.getJid().toBareJid()+":"+foreignId); addresses.add(new AxolotlAddress(contactJid.toString(), foreignId)); } } else { - Log.e(Config.LOGTAG, "Have no target devices in PEP!"); + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Have no target devices in PEP!"); } - Log.d(Config.LOGTAG, "Checking own account "+account.getJid().toBareJid()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Checking own account "+account.getJid().toBareJid()); if(deviceIds.get(account.getJid().toBareJid()) != null) { for(Integer ownId:this.deviceIds.get(account.getJid().toBareJid())) { - Log.d(Config.LOGTAG, "Found device "+account.getJid().toBareJid()+":"+ownId); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found device "+account.getJid().toBareJid()+":"+ownId); addresses.add(new AxolotlAddress(account.getJid().toBareJid().toString(), ownId)); } } for (AxolotlAddress address : addresses) { - Log.d(Config.LOGTAG, "Processing device: " + address.toString()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Processing device: " + address.toString()); FetchStatus status = fetchStatusMap.get(address); XmppAxolotlSession session = sessions.get(address); if ( session == null && ( status == null || status == FetchStatus.ERROR) ) { @@ -902,7 +909,7 @@ public class AxolotlService { this.buildSessionFromPEP(conversation, address); newSessions = true; } else { - Log.d(Config.LOGTAG, "Already have session for " + address.toString()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString()); } } return newSessions; @@ -916,18 +923,18 @@ public class AxolotlService { if(findSessionsforContact(message.getContact()).isEmpty()) { return null; } - Log.d(Config.LOGTAG, "Building axolotl foreign headers..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign headers..."); for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { - Log.d(Config.LOGTAG, session.remoteAddress.toString()); + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString()); //if(!session.isTrusted()) { // TODO: handle this properly // continue; // } axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey())); } - Log.d(Config.LOGTAG, "Building axolotl own headers..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own headers..."); for (XmppAxolotlSession session : findOwnSessions()) { - Log.d(Config.LOGTAG, session.remoteAddress.toString()); + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString()); // if(!session.isTrusted()) { // TODO: handle this properly // continue; @@ -948,7 +955,7 @@ public class AxolotlService { mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); //mXmppConnectionService.updateConversationUi(); } else { - Log.d(Config.LOGTAG, "Generated message, caching: " + message.getUuid()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Generated message, caching: " + message.getUuid()); messageCache.put(message.getUuid(), packet); mXmppConnectionService.resendMessage(message); } @@ -969,10 +976,10 @@ public class AxolotlService { public MessagePacket fetchPacketFromCache(Message message) { MessagePacket packet = messageCache.get(message.getUuid()); if (packet != null) { - Log.d(Config.LOGTAG, "Cache hit: " + message.getUuid()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache hit: " + message.getUuid()); messageCache.remove(message.getUuid()); } else { - Log.d(Config.LOGTAG, "Cache miss: " + message.getUuid()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache miss: " + message.getUuid()); } return packet; } @@ -985,18 +992,18 @@ public class AxolotlService { boolean newSession = false; XmppAxolotlSession session = sessions.get(senderAddress); if (session == null) { - Log.d(Config.LOGTAG, "Account: "+account.getJid()+" No axolotl session found while parsing received message " + message); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Account: "+account.getJid()+" No axolotl session found while parsing received message " + message); // TODO: handle this properly - session = new XmppAxolotlSession(axolotlStore, senderAddress); + session = new XmppAxolotlSession(account, axolotlStore, senderAddress); newSession = true; } for (XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { if (header.getRecipientDeviceId() == ownDeviceId) { - Log.d(Config.LOGTAG, "Found axolotl header matching own device ID, processing..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl header matching own device ID, processing..."); byte[] payloadKey = session.processReceiving(header); if (payloadKey != null) { - Log.d(Config.LOGTAG, "Got payload key from axolotl header. Decrypting message..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl header. Decrypting message..."); plaintextMessage = message.decrypt(session, payloadKey); } Integer preKeyId = session.getPreKeyId(); diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index b0727690b..1dd21541b 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -73,7 +73,7 @@ public class MessageGenerator extends AbstractGenerator { public MessagePacket generateAxolotlChat(Message message, boolean addDelay) { MessagePacket packet = preparePacket(message, addDelay); AxolotlService service = message.getConversation().getAccount().getAxolotlService(); - Log.d(Config.LOGTAG, "Submitting message to axolotl service for send processing..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(message.getConversation().getAccount())+"Submitting message to axolotl service for send processing..."); XmppAxolotlMessage axolotlMessage = service.encrypt(message); if (axolotlMessage == null) { return null; diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index c147978e0..e74cb65c0 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Set; import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.services.XmppConnectionService; @@ -111,7 +112,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { Integer id = Integer.valueOf(device.getAttribute("id")); deviceIds.add(id); } catch (NumberFormatException e) { - Log.e(Config.LOGTAG, "Encountered nvalid node in PEP:" + device.toString() + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered nvalid node in PEP:" + device.toString() + ", skipping..."); continue; } @@ -138,7 +139,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { try { publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0); } catch (InvalidKeyException e) { - Log.e(Config.LOGTAG, "Invalid signedPreKeyPublic in PEP: " + e.getMessage()); + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid signedPreKeyPublic in PEP: " + e.getMessage()); } return publicKey; } @@ -160,7 +161,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { try { identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0); } catch (InvalidKeyException e) { - Log.e(Config.LOGTAG,"Invalid identityKey in PEP: "+e.getMessage()); + Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage()); } return identityKey; } @@ -169,7 +170,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { Map preKeyRecords = new HashMap<>(); Element item = getItem(packet); if (item == null) { - Log.d(Config.LOGTAG, "Couldn't find in bundle IQ packet: " + packet); + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find in bundle IQ packet: " + packet); return null; } final Element bundleElement = item.findChild("bundle"); @@ -178,12 +179,12 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } final Element prekeysElement = bundleElement.findChild("prekeys"); if(prekeysElement == null) { - Log.d(Config.LOGTAG, "Couldn't find in bundle IQ packet: " + packet); + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find in bundle IQ packet: " + packet); return null; } for(Element preKeyPublicElement : prekeysElement.getChildren()) { if(!preKeyPublicElement.getName().equals("preKeyPublic")){ - Log.d(Config.LOGTAG, "Encountered unexpected tag in prekeys list: " + preKeyPublicElement); + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered unexpected tag in prekeys list: " + preKeyPublicElement); continue; } Integer preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId")); @@ -191,7 +192,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0); preKeyRecords.put(preKeyId, preKeyPublic); } catch (InvalidKeyException e) { - Log.e(Config.LOGTAG, "Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping..."); + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping..."); continue; } } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 0dc8d5215..8b5f97af6 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -189,7 +189,7 @@ public class MessageParser extends AbstractParser implements mXmppConnectionService.updateAccountUi(); } } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) { - Log.d(Config.LOGTAG, "Received PEP device list update from "+ from + ", processing..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received PEP device list update from "+ from + ", processing..."); Element item = items.findChild("item"); Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); AxolotlService axolotlService = account.getAxolotlService(); diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 1aa2bade9..e408fa7ff 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -820,7 +820,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { try { identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); } } cursor.close(); @@ -836,7 +836,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { try { identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0)); } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name); } } cursor.close(); @@ -863,7 +863,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void recreateAxolotlDb() { - Log.d(Config.LOGTAG, ">>> (RE)CREATING AXOLOTL DATABASE <<<"); + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+">>> (RE)CREATING AXOLOTL DATABASE <<<"); SQLiteDatabase db = this.getWritableDatabase(); db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME); db.execSQL(CREATE_SESSIONS_STATEMENT); @@ -877,7 +877,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void wipeAxolotlDb(Account account) { String accountName = account.getUuid(); - Log.d(Config.LOGTAG, ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<"); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<"); SQLiteDatabase db = this.getWritableDatabase(); String[] deleteArgs= { accountName -- cgit v1.2.3 From bd29653a20768039778f3cbf75609772fb16de4d Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 8 Jul 2015 17:45:37 +0200 Subject: Make some fields final --- .../java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 8ac69d37a..664a248f8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -453,11 +453,11 @@ public class AxolotlService { } public static class XmppAxolotlSession { - private SessionCipher cipher; + private final SessionCipher cipher; private boolean isTrusted = false; private Integer preKeyId = null; - private SQLiteAxolotlStore sqLiteAxolotlStore; - private AxolotlAddress remoteAddress; + private final SQLiteAxolotlStore sqLiteAxolotlStore; + private final AxolotlAddress remoteAddress; private final Account account; public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { -- cgit v1.2.3 From 540faeb54b1fb230e624573840284daabaf4919b Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 8 Jul 2015 17:46:03 +0200 Subject: Clean up unused constant --- src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 1 - 1 file changed, 1 deletion(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 664a248f8..3c9584d3d 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -89,7 +89,6 @@ public class AxolotlService { public static final String TRUSTED = "trusted"; public static final String OWN = "ownkey"; - public static final String JSONKEY_IDENTITY_KEY_PAIR = "axolotl_key"; public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id"; -- cgit v1.2.3 From f1d73b9d4e64150cda347223afdeaee2ddf595eb Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 8 Jul 2015 18:13:49 +0200 Subject: Use full int range for device IDs --- src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 3c9584d3d..ef7c523df 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -110,7 +110,7 @@ public class AxolotlService { private static int generateRegistrationId() { Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl registration ID..."); - int reg_id = KeyHelper.generateRegistrationId(false); + int reg_id = KeyHelper.generateRegistrationId(true); return reg_id; } -- cgit v1.2.3 From 2628662a7f374dc7cbb01869afb6f5f8fdf5f619 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 8 Jul 2015 18:14:28 +0200 Subject: Display axolotl chat message hint --- src/main/java/eu/siacs/conversations/ui/ConversationFragment.java | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index ec50ea548..026c74adf 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -325,6 +325,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa case Message.ENCRYPTION_OTR: mEditMessage.setHint(getString(R.string.send_otr_message)); break; + case Message.ENCRYPTION_AXOLOTL: + mEditMessage.setHint(getString(R.string.send_axolotl_message)); + break; case Message.ENCRYPTION_PGP: mEditMessage.setHint(getString(R.string.send_pgp_message)); break; -- cgit v1.2.3 From 03614a0262639d1f16b0fd13ec2f4ee0767205c9 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Thu, 9 Jul 2015 14:15:59 +0200 Subject: Fix getSubDeviceSessions SQL query --- src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index e408fa7ff..4fb019423 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -594,7 +594,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, columns, AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND ", + + AxolotlService.SQLiteAxolotlStore.NAME + " = ?", selectionArgs, null, null, null); -- cgit v1.2.3 From 7f918542c845fc1746676143334a5c0eca0a1e76 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Thu, 9 Jul 2015 14:18:54 +0200 Subject: Postpone initAccountService until roster loaded The AxolotlService depends on the roster being loaded when it is initialized so that it can fill its in-memory SessionMap. --- .../java/eu/siacs/conversations/services/XmppConnectionService.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 3e443d75f..ca62b7db6 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -594,9 +594,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext()); this.accounts = databaseBackend.getAccounts(); - for (final Account account : this.accounts) { - account.initAccountServices(this); - } restoreFromDatabase(); getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver); @@ -955,6 +952,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa Log.d(Config.LOGTAG,"restoring roster"); for(Account account : accounts) { databaseBackend.readRoster(account.getRoster()); + account.initAccountServices(XmppConnectionService.this); } getBitmapCache().evictAll(); Looper.prepare(); -- cgit v1.2.3 From d173913ebabbf8de2725dd296ae6428defd4b3b3 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Thu, 9 Jul 2015 14:23:17 +0200 Subject: Overhauled Message tagging Messages are now tagged with the IdentityKey fingerprint of the originating session. IdentityKeys have one of three trust states: undecided (default), trusted, and untrusted/not yet trusted. --- .../crypto/axolotl/AxolotlService.java | 100 ++++++++++++--------- .../crypto/axolotl/XmppAxolotlMessage.java | 11 ++- .../eu/siacs/conversations/entities/Message.java | 19 ++-- .../siacs/conversations/parser/MessageParser.java | 3 +- .../conversations/persistance/DatabaseBackend.java | 93 +++++++++++-------- 5 files changed, 138 insertions(+), 88 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index ef7c523df..9c5c82ffa 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -85,6 +85,7 @@ public class AxolotlService { public static final String DEVICE_ID = "device_id"; public static final String ID = "id"; public static final String KEY = "key"; + public static final String FINGERPRINT = "fingerprint"; public static final String NAME = "name"; public static final String TRUSTED = "trusted"; public static final String OWN = "ownkey"; @@ -99,6 +100,23 @@ public class AxolotlService { private final int localRegistrationId; private int currentPreKeyId = 0; + public enum Trust { + UNDECIDED, // 0 + TRUSTED, + UNTRUSTED; + + public String toString() { + switch(this){ + case UNDECIDED: + return "Trust undecided"; + case TRUSTED: + return "Trusted"; + case UNTRUSTED: + default: + return "Untrusted"; + } + } + }; private static IdentityKeyPair generateIdentityKeyPair() { Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl IdentityKeyPair..."); @@ -242,11 +260,17 @@ public class AxolotlService { */ @Override public boolean isTrustedIdentity(String name, IdentityKey identityKey) { - //Set trustedKeys = mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name); - //return trustedKeys.isEmpty() || trustedKeys.contains(identityKey); return true; } + public Trust getFingerprintTrust(String name, String fingerprint) { + return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, name, fingerprint); + } + + public void setFingerprintTrust(String name, String fingerprint, Trust trust) { + mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, name, fingerprint, trust); + } + // -------------------------------------- // SessionStore // -------------------------------------- @@ -325,14 +349,6 @@ public class AxolotlService { new AxolotlAddress(name, 0)); } - public boolean isTrustedSession(AxolotlAddress address) { - return mXmppConnectionService.databaseBackend.isTrustedSession(this.account, address); - } - - public void setTrustedSession(AxolotlAddress address, boolean trusted) { - mXmppConnectionService.databaseBackend.setTrustedSession(this.account, address, trusted); - } - // -------------------------------------- // PreKeyStore // -------------------------------------- @@ -453,27 +469,22 @@ public class AxolotlService { public static class XmppAxolotlSession { private final SessionCipher cipher; - private boolean isTrusted = false; private Integer preKeyId = null; private final SQLiteAxolotlStore sqLiteAxolotlStore; private final AxolotlAddress remoteAddress; private final Account account; + private String fingerprint = null; + + public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) { + this(account, store, remoteAddress); + this.fingerprint = fingerprint; + } public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { this.cipher = new SessionCipher(store, remoteAddress); this.remoteAddress = remoteAddress; this.sqLiteAxolotlStore = store; this.account = account; - this.isTrusted = sqLiteAxolotlStore.isTrustedSession(remoteAddress); - } - - public void trust() { - sqLiteAxolotlStore.setTrustedSession(remoteAddress, true); - this.isTrusted = true; - } - - public boolean isTrusted() { - return this.isTrusted; } public Integer getPreKeyId() { @@ -481,18 +492,29 @@ public class AxolotlService { } public void resetPreKeyId() { + preKeyId = null; } + public String getFingerprint() { + return fingerprint; + } + public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { byte[] plaintext = null; try { try { PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); - plaintext = cipher.decrypt(message); - if (message.getPreKeyId().isPresent()) { - preKeyId = message.getPreKeyId().get(); + String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", ""); + if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Had session with fingerprint "+ this.fingerprint+", received message with fingerprint "+fingerprint); + } else { + this.fingerprint = fingerprint; + plaintext = cipher.decrypt(message); + if (message.getPreKeyId().isPresent()) { + preKeyId = message.getPreKeyId().get(); + } } } catch (InvalidMessageException | InvalidVersionException e) { Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"WhisperMessage received"); @@ -582,7 +604,9 @@ public class AxolotlService { List deviceIDs = store.getSubDeviceSessions(address); for (Integer deviceId : deviceIDs) { AxolotlAddress axolotlAddress = new AxolotlAddress(address, deviceId); - this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress)); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building session for remote address: "+axolotlAddress.toString()); + String fingerprint = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey().getFingerprint().replaceAll("\\s", ""); + this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, fingerprint)); } } } @@ -619,18 +643,6 @@ public class AxolotlService { return axolotlStore.getIdentityKeyPair().getPublicKey(); } - public void trustSession(AxolotlAddress counterpart) { - XmppAxolotlSession session = sessions.get(counterpart); - if (session != null) { - session.trust(); - } - } - - public boolean isTrustedSession(AxolotlAddress counterpart) { - XmppAxolotlSession session = sessions.get(counterpart); - return session != null && session.isTrusted(); - } - private AxolotlAddress getAddressForJid(Jid jid) { return new AxolotlAddress(jid.toString(), 0); } @@ -808,11 +820,19 @@ public class AxolotlService { } public boolean isContactAxolotlCapable(Contact contact) { + Jid jid = contact.getJid().toBareJid(); AxolotlAddress address = new AxolotlAddress(jid.toString(), 0); return sessions.hasAny(address) || ( deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty()); } + public SQLiteAxolotlStore.Trust getFingerprintTrust(String name, String fingerprint) { + return axolotlStore.getFingerprintTrust(name, fingerprint); + } + + public void setFingerprintTrust(String name, String fingerprint, SQLiteAxolotlStore.Trust trust) { + axolotlStore.setFingerprintTrust(name, fingerprint, trust); + } private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address) { Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building new sesstion for " + address.getDeviceId()); @@ -851,7 +871,7 @@ public class AxolotlService { try { SessionBuilder builder = new SessionBuilder(axolotlStore, address); builder.process(preKeyBundle); - XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey().getFingerprint().replaceAll("\\s", "")); sessions.put(address, session); fetchStatusMap.put(address, FetchStatus.SUCCESS); } catch (UntrustedIdentityException|InvalidKeyException e) { @@ -890,7 +910,7 @@ public class AxolotlService { addresses.add(new AxolotlAddress(contactJid.toString(), foreignId)); } } else { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Have no target devices in PEP!"); + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!"); } Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Checking own account "+account.getJid().toBareJid()); if(deviceIds.get(account.getJid().toBareJid()) != null) { @@ -1003,7 +1023,7 @@ public class AxolotlService { byte[] payloadKey = session.processReceiving(header); if (payloadKey != null) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl header. Decrypting message..."); - plaintextMessage = message.decrypt(session, payloadKey); + plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint()); } Integer preKeyId = session.getPreKeyId(); if (preKeyId != null) { diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index 06dd2cdaf..459952281 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -67,10 +67,12 @@ public class XmppAxolotlMessage { public static class XmppAxolotlPlaintextMessage { private final AxolotlService.XmppAxolotlSession session; private final String plaintext; + private final String fingerprint; - public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext) { + public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext, String fingerprint) { this.session = session; this.plaintext = plaintext; + this.fingerprint = fingerprint; } public String getPlaintext() { @@ -81,6 +83,9 @@ public class XmppAxolotlMessage { return session; } + public String getFingerprint() { + return fingerprint; + } } public XmppAxolotlMessage(Jid from, Element axolotlMessage) { @@ -167,7 +172,7 @@ public class XmppAxolotlMessage { } - public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key) { + public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) { XmppAxolotlPlaintextMessage plaintextMessage = null; try { @@ -178,7 +183,7 @@ public class XmppAxolotlMessage { cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); String plaintext = new String(cipher.doFinal(ciphertext)); - plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext); + plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext, fingerprint); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index b1dffc820..9695e6fa9 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -54,6 +54,7 @@ public class Message extends AbstractEntity { public static final String REMOTE_MSG_ID = "remoteMsgId"; public static final String SERVER_MSG_ID = "serverMsgId"; public static final String RELATIVE_FILE_PATH = "relativeFilePath"; + public static final String FINGERPRINT = "axolotl_fingerprint"; public static final String ME_COMMAND = "/me "; @@ -67,7 +68,6 @@ public class Message extends AbstractEntity { protected int encryption; protected int status; protected int type; - private AxolotlService.XmppAxolotlSession axolotlSession = null; protected String relativeFilePath; protected boolean read = true; protected String remoteMsgId = null; @@ -76,6 +76,7 @@ public class Message extends AbstractEntity { protected Transferable transferable = null; private Message mNextMessage = null; private Message mPreviousMessage = null; + private String axolotlFingerprint = null; private Message() { @@ -97,6 +98,7 @@ public class Message extends AbstractEntity { TYPE_TEXT, null, null, + null, null); this.conversation = conversation; } @@ -104,7 +106,7 @@ public class Message extends AbstractEntity { private Message(final String uuid, final String conversationUUid, final Jid counterpart, final Jid trueCounterpart, final String body, final long timeSent, final int encryption, final int status, final int type, final String remoteMsgId, - final String relativeFilePath, final String serverMsgId) { + final String relativeFilePath, final String serverMsgId, final String fingerprint) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; @@ -117,6 +119,7 @@ public class Message extends AbstractEntity { this.remoteMsgId = remoteMsgId; this.relativeFilePath = relativeFilePath; this.serverMsgId = serverMsgId; + this.axolotlFingerprint = fingerprint; } public static Message fromCursor(Cursor cursor) { @@ -153,7 +156,8 @@ public class Message extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(TYPE)), cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)), cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)), - cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID))); + cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)), + cursor.getString(cursor.getColumnIndex(FINGERPRINT))); } public static Message createStatusMessage(Conversation conversation, String body) { @@ -187,6 +191,7 @@ public class Message extends AbstractEntity { values.put(REMOTE_MSG_ID, remoteMsgId); values.put(RELATIVE_FILE_PATH, relativeFilePath); values.put(SERVER_MSG_ID, serverMsgId); + values.put(FINGERPRINT, axolotlFingerprint); return values; } @@ -667,11 +672,7 @@ public class Message extends AbstractEntity { public int height = 0; } - public boolean isTrusted() { - return this.axolotlSession != null && this.axolotlSession.isTrusted(); - } - - public void setAxolotlSession(AxolotlService.XmppAxolotlSession session) { - this.axolotlSession = session; + public void setAxolotlFingerprint(String fingerprint) { + this.axolotlFingerprint = fingerprint; } } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 8b5f97af6..9cbe5b460 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -106,7 +106,8 @@ public class MessageParser extends AbstractParser implements XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage); if(plaintextMessage != null) { finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); - finishedMessage.setAxolotlSession(plaintextMessage.getSession()); + finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint()); } return finishedMessage; diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 4fb019423..0da52010b 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -82,7 +82,6 @@ public class DatabaseBackend extends SQLiteOpenHelper { + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " + AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, " + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " INTEGER, " - + AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, " + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " @@ -97,6 +96,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " + AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, " + AxolotlService.SQLiteAxolotlStore.OWN + " INTEGER, " + + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " TEXT PRIMARY KEY ON CONFLICT IGNORE, " + + AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, " + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE " @@ -132,6 +133,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, " + Message.RELATIVE_FILE_PATH + " TEXT, " + Message.SERVER_MSG_ID + " TEXT, " + + Message.FINGERPRINT + " TEXT, " + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + Message.CONVERSATION + ") REFERENCES " + Conversation.TABLENAME + "(" + Conversation.UUID @@ -284,6 +286,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { } if (oldVersion < 15 && newVersion >= 15) { recreateAxolotlDb(); + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.FINGERPRINT + " TEXT"); } } @@ -645,28 +649,6 @@ public class DatabaseBackend extends SQLiteOpenHelper { args); } - public boolean isTrustedSession(Account account, AxolotlAddress contact) { - boolean trusted = false; - Cursor cursor = getCursorForSession(account, contact); - if(cursor.getCount() != 0) { - cursor.moveToFirst(); - trusted = cursor.getInt(cursor.getColumnIndex( - AxolotlService.SQLiteAxolotlStore.TRUSTED)) > 0; - } - cursor.close(); - return trusted; - } - - public void setTrustedSession(Account account, AxolotlAddress contact, boolean trusted) { - SQLiteDatabase db = this.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put(AxolotlService.SQLiteAxolotlStore.NAME, contact.getName()); - values.put(AxolotlService.SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); - values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted?1:0); - db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values); - } - private Cursor getCursorForPreKey(Account account, int preKeyId) { SQLiteDatabase db = this.getReadableDatabase(); String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; @@ -796,17 +778,28 @@ public class DatabaseBackend extends SQLiteOpenHelper { } private Cursor getIdentityKeyCursor(Account account, String name, boolean own) { + return getIdentityKeyCursor(account, name, own, null); + } + + private Cursor getIdentityKeyCursor(Account account, String name, boolean own, String fingerprint) { final SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; - String[] selectionArgs = {account.getUuid(), - name, - own?"1":"0"}; + String[] columns = {AxolotlService.SQLiteAxolotlStore.TRUSTED, + AxolotlService.SQLiteAxolotlStore.KEY}; + ArrayList selectionArgs = new ArrayList<>(4); + selectionArgs.add(account.getUuid()); + selectionArgs.add(name); + selectionArgs.add(own?"1":"0"); + String selectionString = AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " + + AxolotlService.SQLiteAxolotlStore.OWN + " = ? "; + if (fingerprint != null){ + selectionArgs.add(fingerprint); + selectionString += "AND " +AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? "; + } Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, columns, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.OWN + " = ? ", - selectionArgs, + selectionString, + selectionArgs.toArray(new String[selectionArgs.size()]), null, null, null); return cursor; @@ -844,22 +837,52 @@ public class DatabaseBackend extends SQLiteOpenHelper { return identityKeys; } - private void storeIdentityKey(Account account, String name, boolean own, String base64Serialized) { + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); values.put(AxolotlService.SQLiteAxolotlStore.NAME, name); - values.put(AxolotlService.SQLiteAxolotlStore.OWN, own?1:0); + values.put(AxolotlService.SQLiteAxolotlStore.OWN, own ? 1 : 0); + values.put(AxolotlService.SQLiteAxolotlStore.FINGERPRINT, fingerprint); values.put(AxolotlService.SQLiteAxolotlStore.KEY, base64Serialized); db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); } + public AxolotlService.SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String name, String fingerprint) { + Cursor cursor = getIdentityKeyCursor(account, name, false, fingerprint); + AxolotlService.SQLiteAxolotlStore.Trust trust = null; + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int trustValue = cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED)); + trust = AxolotlService.SQLiteAxolotlStore.Trust.values()[trustValue]; + } + cursor.close(); + return trust; + } + + public boolean setIdentityKeyTrust(Account account, String name, String fingerprint, AxolotlService.SQLiteAxolotlStore.Trust trust) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] selectionArgs = { + account.getUuid(), + name, + fingerprint + }; + ContentValues values = new ContentValues(); + values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.ordinal()); + int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? " + + AxolotlService.SQLiteAxolotlStore.NAME + " = ? " + + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ", + selectionArgs); + return rows == 1; + } + public void storeIdentityKey(Account account, String name, IdentityKey identityKey) { - storeIdentityKey(account, name, false, Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); + storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); } public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) { - storeIdentityKey(account, name, true, Base64.encodeToString(identityKeyPair.serialize(),Base64.DEFAULT)); + storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT)); } public void recreateAxolotlDb() { -- cgit v1.2.3 From 23a4e1e6fada4eb5309db35efcf80d6f1f6f4d34 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Thu, 9 Jul 2015 14:26:19 +0200 Subject: Display trust status in ContactDetailsActivity --- src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 33861f828..4ff47f9ec 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -383,11 +383,14 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd 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); + TextView keyTrust = (TextView) view.findViewById(R.id.key_trust); ImageButton remove = (ImageButton) view .findViewById(R.id.button_remove); remove.setVisibility(View.VISIBLE); + keyTrust.setVisibility(View.VISIBLE); keyType.setText("Axolotl Fingerprint"); key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint())); + keyTrust.setText(contact.getAccount().getAxolotlService().getFingerprintTrust(contact.getJid().toBareJid().toString(), identityKey.getFingerprint().replaceAll("\\s","")).toString()); keys.addView(view); remove.setOnClickListener(new OnClickListener() { -- cgit v1.2.3 From 461d0446f747edae3f7b8227e23577ab15cf1f9f Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 10 Jul 2015 02:18:01 +0200 Subject: Fix and expand key regeneration function Wipe session cache to prevent stale sessions being used. Wipe fetch status cache to enable recreation of sessions. Regenerate deviceId, so that foreign devices will talk to us again. --- .../crypto/axolotl/AxolotlService.java | 27 ++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 9c5c82ffa..6b6c37505 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -73,7 +73,6 @@ public class AxolotlService { private final Map messageCache; private final FetchStatusMap fetchStatusMap; private final SerialSingleThreadExecutor executor; - private int ownDeviceId; public static class SQLiteAxolotlStore implements AxolotlStore { @@ -97,7 +96,7 @@ public class AxolotlService { private final XmppConnectionService mXmppConnectionService; private IdentityKeyPair identityKeyPair; - private final int localRegistrationId; + private int localRegistrationId; private int currentPreKeyId = 0; public enum Trust { @@ -166,9 +165,13 @@ public class AxolotlService { } private int loadRegistrationId() { + return loadRegistrationId(false); + } + + private int loadRegistrationId(boolean regenerate) { String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); int reg_id; - if (regIdString != null) { + if (!regenerate && regIdString != null) { reg_id = Integer.valueOf(regIdString); } else { Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve axolotl registration id for account " + account.getJid()); @@ -199,6 +202,7 @@ public class AxolotlService { mXmppConnectionService.databaseBackend.wipeAxolotlDb(account); account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0)); identityKeyPair = loadIdentityKeyPair(); + localRegistrationId = loadRegistrationId(true); currentPreKeyId = 0; mXmppConnectionService.updateAccountUi(); } @@ -584,6 +588,9 @@ public class AxolotlService { } } + public void clear() { + map.clear(); + } } @@ -636,7 +643,6 @@ public class AxolotlService { this.sessions = new SessionMap(axolotlStore, account); this.fetchStatusMap = new FetchStatusMap(); this.executor = new SerialSingleThreadExecutor(); - this.ownDeviceId = axolotlStore.getLocalRegistrationId(); } public IdentityKey getOwnPublicKey() { @@ -666,11 +672,14 @@ public class AxolotlService { public void regenerateKeys() { axolotlStore.regenerate(); + sessions.clear(); + fetchStatusMap.clear(); publishBundlesIfNeeded(); + publishOwnDeviceIdIfNeeded(); } public int getOwnDeviceId() { - return ownDeviceId; + return axolotlStore.loadRegistrationId(); } public Set getOwnDeviceIds() { @@ -728,7 +737,7 @@ public class AxolotlService { } public void publishBundlesIfNeeded() { - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), ownDeviceId); + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId()); mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -801,7 +810,7 @@ public class AxolotlService { if(changed) { IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), - preKeyRecords, ownDeviceId); + preKeyRecords, getOwnDeviceId()); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+ ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override @@ -937,7 +946,7 @@ public class AxolotlService { @Nullable public XmppAxolotlMessage encrypt(Message message ){ final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(message.getContact().getJid().toBareJid(), - ownDeviceId, message.getBody()); + getOwnDeviceId(), message.getBody()); if(findSessionsforContact(message.getContact()).isEmpty()) { return null; @@ -1018,7 +1027,7 @@ public class AxolotlService { } for (XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { - if (header.getRecipientDeviceId() == ownDeviceId) { + if (header.getRecipientDeviceId() == getOwnDeviceId()) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl header matching own device ID, processing..."); byte[] payloadKey = session.processReceiving(header); if (payloadKey != null) { -- cgit v1.2.3 From 160e4017dfbd458325b8b63cf527f190b3f5bc2d Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 10 Jul 2015 02:24:33 +0200 Subject: Fix IdentityKey storage model Added proper UNIQUE statement --- .../java/eu/siacs/conversations/persistance/DatabaseBackend.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 0da52010b..0ba2876af 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -96,11 +96,15 @@ public class DatabaseBackend extends SQLiteOpenHelper { + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " + AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, " + AxolotlService.SQLiteAxolotlStore.OWN + " INTEGER, " - + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " TEXT PRIMARY KEY ON CONFLICT IGNORE, " + + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " TEXT, " + AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, " + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + AxolotlService.SQLiteAxolotlStore.ACCOUNT - + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE " + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " + + AxolotlService.SQLiteAxolotlStore.NAME + ", " + + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + + ") ON CONFLICT IGNORE" +");"; private DatabaseBackend(Context context) { -- cgit v1.2.3 From 31d375c2c374dc8a36539a78582f44accc13592e Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 10 Jul 2015 02:25:28 +0200 Subject: Fix setIdentityKeyTrust update statement --- src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 0ba2876af..4c6bf2215 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -874,8 +874,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { ContentValues values = new ContentValues(); values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.ordinal()); int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? " - + AxolotlService.SQLiteAxolotlStore.NAME + " = ? " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ", selectionArgs); return rows == 1; -- cgit v1.2.3 From 35714d3d08d287c5ded125c356835fb70ab342b7 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 10 Jul 2015 02:36:29 +0200 Subject: Ensure that available sessions are always used Any time a new session is established, call syncRosterToDisk() to ensure that on subsequent restoreFromDatabase() calls, the roster is actually available. This is important so that initAccountServices() can properly initialize the SessionMap. This prevents a race condition where after adding a new account and initiating sessions with it, if the app is killed (e.g. by reinstall) before triggering a syncRosterToDisk(), subsequent restores will not have the roster available, leading to missing XmppAxolotlSessions in the SessionMap cache. As a result of this, a new session was initiated when sending a new message, and received messages could not be tagged with the originating session's fingerprint. As an added sanity check, go to the database to confirm no records are present before creating fresh XmppAxolotlSession objects (both in the sending and receiving case). --- .../crypto/axolotl/AxolotlService.java | 39 +++++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 6b6c37505..23adbad84 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -595,13 +595,17 @@ public class AxolotlService { } private static class SessionMap extends AxolotlAddressMap { + private final XmppConnectionService xmppConnectionService; + private final Account account; - public SessionMap(SQLiteAxolotlStore store, Account account) { + public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) { super(); - this.fillMap(store, account); + this.xmppConnectionService = service; + this.account = account; + this.fillMap(store); } - private void fillMap(SQLiteAxolotlStore store, Account account) { + private void fillMap(SQLiteAxolotlStore store) { for (Contact contact : account.getRoster().getContacts()) { Jid bareJid = contact.getJid().toBareJid(); if (bareJid == null) { @@ -618,6 +622,11 @@ public class AxolotlService { } } + @Override + public void put(AxolotlAddress address, XmppAxolotlSession value) { + super.put(address, value); + xmppConnectionService.syncRosterToDisk(account); + } } private static enum FetchStatus { @@ -640,7 +649,7 @@ public class AxolotlService { this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); this.deviceIds = new HashMap<>(); this.messageCache = new HashMap<>(); - this.sessions = new SessionMap(axolotlStore, account); + this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account); this.fetchStatusMap = new FetchStatusMap(); this.executor = new SerialSingleThreadExecutor(); } @@ -933,9 +942,16 @@ public class AxolotlService { FetchStatus status = fetchStatusMap.get(address); XmppAxolotlSession session = sessions.get(address); if ( session == null && ( status == null || status == FetchStatus.ERROR) ) { - fetchStatusMap.put(address, FetchStatus.PENDING); - this.buildSessionFromPEP(conversation, address); - newSessions = true; + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + if ( identityKey != null ) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache..."); + session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", "")); + sessions.put(address, session); + } else { + fetchStatusMap.put(address, FetchStatus.PENDING); + this.buildSessionFromPEP(conversation, address); + newSessions = true; + } } else { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString()); } @@ -1022,7 +1038,12 @@ public class AxolotlService { if (session == null) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Account: "+account.getJid()+" No axolotl session found while parsing received message " + message); // TODO: handle this properly - session = new XmppAxolotlSession(account, axolotlStore, senderAddress); + IdentityKey identityKey = axolotlStore.loadSession(senderAddress).getSessionState().getRemoteIdentityKey(); + if ( identityKey != null ) { + session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", "")); + } else { + session = new XmppAxolotlSession(account, axolotlStore, senderAddress); + } newSession = true; } @@ -1044,7 +1065,7 @@ public class AxolotlService { } if (newSession && plaintextMessage != null) { - sessions.put(senderAddress,session); + sessions.put(senderAddress, session); } return plaintextMessage; -- cgit v1.2.3 From 3d339460889644c859d932eb3f2e324bd5696707 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 10 Jul 2015 02:56:44 +0200 Subject: Add key trust toggle to ContactDetailsActivity Can now toggle IdentityKey trust --- .../conversations/ui/ContactDetailsActivity.java | 49 +++++++++++++++++----- 1 file changed, 39 insertions(+), 10 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 4ff47f9ec..ebc1ae839 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -35,6 +35,7 @@ import java.util.List; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.ListItem; @@ -363,13 +364,13 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd 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); - ImageButton remove = (ImageButton) view + ImageButton removeButton = (ImageButton) view .findViewById(R.id.button_remove); - remove.setVisibility(View.VISIBLE); + removeButton.setVisibility(View.VISIBLE); keyType.setText("OTR Fingerprint"); key.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); keys.addView(view); - remove.setOnClickListener(new OnClickListener() { + removeButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -384,19 +385,47 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd TextView key = (TextView) view.findViewById(R.id.key); TextView keyType = (TextView) view.findViewById(R.id.key_type); TextView keyTrust = (TextView) view.findViewById(R.id.key_trust); - ImageButton remove = (ImageButton) view + ImageButton removeButton = (ImageButton) view .findViewById(R.id.button_remove); - remove.setVisibility(View.VISIBLE); - keyTrust.setVisibility(View.VISIBLE); + ImageButton trustButton = (ImageButton) view + .findViewById(R.id.button_trust); + final AxolotlService axolotlService = contact.getAccount().getAxolotlService(); + final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); + final Jid bareJid = contactJid.toBareJid(); + AxolotlService.SQLiteAxolotlStore.Trust trust = contact.getAccount().getAxolotlService() + .getFingerprintTrust(bareJid.toString(), fingerprint); + switch (trust) { + case TRUSTED: + removeButton.setVisibility(View.VISIBLE); + //Log.d(Config.LOGTAG, AxolotlService.getLogprefix(contact.getAccount()) + "Setting remove button visible!"); + break; + case UNDECIDED: + case UNTRUSTED: + //Log.d(Config.LOGTAG, AxolotlService.getLogprefix(contact.getAccount()) + "Setting trust button visible!"); + trustButton.setVisibility(View.VISIBLE); + break; + } keyType.setText("Axolotl Fingerprint"); key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint())); - keyTrust.setText(contact.getAccount().getAxolotlService().getFingerprintTrust(contact.getJid().toBareJid().toString(), identityKey.getFingerprint().replaceAll("\\s","")).toString()); + keyTrust.setText(trust.toString()); + keyTrust.setVisibility(View.VISIBLE); keys.addView(view); - remove.setOnClickListener(new OnClickListener() { - + removeButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + axolotlService.setFingerprintTrust(bareJid.toString(), fingerprint, + AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED); + refreshUi(); + xmppConnectionService.updateConversationUi(); + } + }); + trustButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - //confirmToDeleteFingerprint(otrFingerprint); + axolotlService.setFingerprintTrust(bareJid.toString(), fingerprint, + AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED); + refreshUi(); + xmppConnectionService.updateConversationUi(); } }); } -- cgit v1.2.3 From 9e8d9a64012531be8f6f72835c6f879d2a4451a1 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 10 Jul 2015 03:00:40 +0200 Subject: Show trust status of messages' originating session Shade lock icon red if message was received in a session that has not been marked trusted by the user or fingerprint is unknown --- src/main/java/eu/siacs/conversations/entities/Message.java | 4 ++++ .../eu/siacs/conversations/ui/adapter/MessageAdapter.java | 13 +++++++++++++ 2 files changed, 17 insertions(+) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 9695e6fa9..ac6c66250 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -675,4 +675,8 @@ public class Message extends AbstractEntity { public void setAxolotlFingerprint(String fingerprint) { this.axolotlFingerprint = fingerprint; } + + public String getAxolotlFingerprint() { + return axolotlFingerprint; + } } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 167f3f02f..d2c75a5ee 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.ui.adapter; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.graphics.Color; import android.graphics.Typeface; import android.net.Uri; import android.text.Spannable; @@ -26,6 +27,7 @@ import android.widget.Toast; import java.util.List; import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -154,6 +156,17 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.indicator.setVisibility(View.GONE); } else { viewHolder.indicator.setVisibility(View.VISIBLE); + if (message.getMergedStatus() == Message.STATUS_RECEIVED + && message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + AxolotlService.SQLiteAxolotlStore.Trust trust = message.getConversation() + .getAccount().getAxolotlService().getFingerprintTrust( + message.getContact().getJid().toBareJid().toString(), + message.getAxolotlFingerprint()); + + if (trust == null || trust != AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED) { + viewHolder.indicator.setColorFilter(Color.RED); + } + } } String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), -- cgit v1.2.3 From 6c38e531284b76ee71ef33e7f76ba1d619b25cc2 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 10 Jul 2015 03:02:49 +0200 Subject: Disable Axolotl option if not usable In MUCs or if contact is not axolotl capable, disable axolotl menu option --- .../conversations/ui/ConversationActivity.java | 20 +++++++++---------- .../eu/siacs/conversations/ui/XmppActivity.java | 23 ---------------------- 2 files changed, 10 insertions(+), 33 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index e4ce4a0f1..2e50af3bf 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -37,6 +37,7 @@ import java.util.List; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Contact; @@ -752,15 +753,10 @@ public class ConversationActivity extends XmppActivity } break; case R.id.encryption_choice_axolotl: - Log.d(Config.LOGTAG, "Trying to enable axolotl..."); - if(conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) { - Log.d(Config.LOGTAG, "Enabled axolotl for Contact " + conversation.getContact().getJid() ); - conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); - item.setChecked(true); - } else { - Log.d(Config.LOGTAG, "Contact " + conversation.getContact().getJid() + " not axolotl capable!"); - showAxolotlNoSessionsDialog(); - } + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount()) + + "Enabled axolotl for Contact " + conversation.getContact().getJid()); + conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); + item.setChecked(true); break; default: conversation.setNextEncryption(Message.ENCRYPTION_NONE); @@ -776,13 +772,18 @@ public class ConversationActivity extends XmppActivity MenuItem otr = popup.getMenu().findItem(R.id.encryption_choice_otr); MenuItem none = popup.getMenu().findItem(R.id.encryption_choice_none); MenuItem pgp = popup.getMenu().findItem(R.id.encryption_choice_pgp); + MenuItem axolotl = popup.getMenu().findItem(R.id.encryption_choice_axolotl); if (conversation.getMode() == Conversation.MODE_MULTI) { otr.setEnabled(false); + axolotl.setEnabled(false); } else { if (forceEncryption()) { none.setVisible(false); } } + if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) { + axolotl.setEnabled(false); + } switch (conversation.getNextEncryption(forceEncryption())) { case Message.ENCRYPTION_NONE: none.setChecked(true); @@ -794,7 +795,6 @@ public class ConversationActivity extends XmppActivity pgp.setChecked(true); break; case Message.ENCRYPTION_AXOLOTL: - Log.d(Config.LOGTAG, "Axolotl confirmed. Setting menu item checked!"); popup.getMenu().findItem(R.id.encryption_choice_axolotl) .setChecked(true); break; diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index eebeb040a..7c994c31a 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -266,29 +266,6 @@ public abstract class XmppActivity extends Activity { builder.create().show(); } - public void showAxolotlNoSessionsDialog() { - Builder builder = new AlertDialog.Builder(this); - builder.setTitle("No Sessions"); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setMessage("Your contact is not Axolotl-capable!"); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setNeutralButton("Foo", - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - } - }); - builder.setPositiveButton("Bar", - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - } - }); - builder.create().show(); - } - abstract void onBackendConnected(); protected void registerListeners() { -- cgit v1.2.3 From 3ab59c93a6d26362ab8ade6b88dadc099cc6cb07 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Thu, 9 Jul 2015 22:37:40 -0500 Subject: Add refresh icon to v21 theme --- src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java | 1 - 1 file changed, 1 deletion(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index ab4dc0592..b33e909f8 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.ui; -import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.PendingIntent; import android.content.DialogInterface; -- cgit v1.2.3 From fca0c367576e4cd1f218c663ad2da6a1d4bfb392 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Thu, 9 Jul 2015 23:08:44 -0500 Subject: Fix copying of axolotl keys to clipboard --- src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index b33e909f8..e3a43b8b3 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -324,7 +324,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard); this.mAxolotlFingerprint = (TextView) findViewById(R.id.axolotl_fingerprint); this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box); - this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard); + this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_axolotl_to_clipboard); this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key); this.mAxolotlDevicelist = (TextView) findViewById(R.id.axolotl_devicelist); this.mAxolotlDevicelistBox = (RelativeLayout) findViewById(R.id.axolotl_devices_box); -- cgit v1.2.3 From e8ec2ee628076aa43132c3214b786ddde64f93a6 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 15 Jul 2015 15:45:12 +0200 Subject: Don't merge messages with different trust statuses --- src/main/java/eu/siacs/conversations/entities/Message.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index ac6c66250..698775c8a 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -399,7 +399,8 @@ public class Message extends AbstractEntity { !message.getBody().startsWith(ME_COMMAND) && !this.getBody().startsWith(ME_COMMAND) && !this.bodyIsHeart() && - !message.bodyIsHeart() + !message.bodyIsHeart() && + this.isTrusted() == message.isTrusted() ); } @@ -679,4 +680,9 @@ public class Message extends AbstractEntity { public String getAxolotlFingerprint() { return axolotlFingerprint; } + + public boolean isTrusted() { + return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint) + == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED; + } } -- cgit v1.2.3 From 4038af2f4764f1d35d82822514d65d17ab45b302 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 15 Jul 2015 16:32:42 +0200 Subject: Fix trust status for outgoing messages Tag sent messages with own fingerprint, set own fingerprint as always trusted, include own fingerprint in database trust search, explicitly reset trust colorfilter --- .../crypto/axolotl/AxolotlService.java | 16 +++++----- .../conversations/persistance/DatabaseBackend.java | 37 ++++++++++++++-------- .../services/XmppConnectionService.java | 4 +++ .../conversations/ui/ContactDetailsActivity.java | 6 ++-- .../conversations/ui/adapter/MessageAdapter.java | 8 ++--- 5 files changed, 43 insertions(+), 28 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 23adbad84..57e57f7f3 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -267,12 +267,12 @@ public class AxolotlService { return true; } - public Trust getFingerprintTrust(String name, String fingerprint) { - return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, name, fingerprint); + public Trust getFingerprintTrust(String fingerprint) { + return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint); } - public void setFingerprintTrust(String name, String fingerprint, Trust trust) { - mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, name, fingerprint, trust); + public void setFingerprintTrust(String fingerprint, Trust trust) { + mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); } // -------------------------------------- @@ -844,12 +844,12 @@ public class AxolotlService { return sessions.hasAny(address) || ( deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty()); } - public SQLiteAxolotlStore.Trust getFingerprintTrust(String name, String fingerprint) { - return axolotlStore.getFingerprintTrust(name, fingerprint); + public SQLiteAxolotlStore.Trust getFingerprintTrust(String fingerprint) { + return axolotlStore.getFingerprintTrust(fingerprint); } - public void setFingerprintTrust(String name, String fingerprint, SQLiteAxolotlStore.Trust trust) { - axolotlStore.setFingerprintTrust(name, fingerprint, trust); + public void setFingerprintTrust(String fingerprint, SQLiteAxolotlStore.Trust trust) { + axolotlStore.setFingerprintTrust(fingerprint, trust); } private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address) { diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 4c6bf2215..39ef5d366 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -785,20 +785,28 @@ public class DatabaseBackend extends SQLiteOpenHelper { return getIdentityKeyCursor(account, name, own, null); } - private Cursor getIdentityKeyCursor(Account account, String name, boolean own, String fingerprint) { + private Cursor getIdentityKeyCursor(Account account, String fingerprint) { + return getIdentityKeyCursor(account, null, null, fingerprint); + } + + private Cursor getIdentityKeyCursor(Account account, String name, Boolean own, String fingerprint) { final SQLiteDatabase db = this.getReadableDatabase(); String[] columns = {AxolotlService.SQLiteAxolotlStore.TRUSTED, AxolotlService.SQLiteAxolotlStore.KEY}; ArrayList selectionArgs = new ArrayList<>(4); selectionArgs.add(account.getUuid()); - selectionArgs.add(name); - selectionArgs.add(own?"1":"0"); - String selectionString = AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.OWN + " = ? "; + String selectionString = AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?"; + if (name != null){ + selectionArgs.add(name); + selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.NAME + " = ?"; + } if (fingerprint != null){ selectionArgs.add(fingerprint); - selectionString += "AND " +AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? "; + selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ?"; + } + if (own != null){ + selectionArgs.add(own?"1":"0"); + selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.OWN + " = ?"; } Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, columns, @@ -842,6 +850,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { } private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) { + storeIdentityKey(account, name, own, fingerprint, base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust.UNDECIDED); + } + + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust trusted) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); @@ -849,11 +861,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { values.put(AxolotlService.SQLiteAxolotlStore.OWN, own ? 1 : 0); values.put(AxolotlService.SQLiteAxolotlStore.FINGERPRINT, fingerprint); values.put(AxolotlService.SQLiteAxolotlStore.KEY, base64Serialized); + values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted.ordinal()); db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); } - public AxolotlService.SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String name, String fingerprint) { - Cursor cursor = getIdentityKeyCursor(account, name, false, fingerprint); + public AxolotlService.SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) { + Cursor cursor = getIdentityKeyCursor(account, fingerprint); AxolotlService.SQLiteAxolotlStore.Trust trust = null; if (cursor.getCount() > 0) { cursor.moveToFirst(); @@ -864,18 +877,16 @@ public class DatabaseBackend extends SQLiteOpenHelper { return trust; } - public boolean setIdentityKeyTrust(Account account, String name, String fingerprint, AxolotlService.SQLiteAxolotlStore.Trust trust) { + public boolean setIdentityKeyTrust(Account account, String fingerprint, AxolotlService.SQLiteAxolotlStore.Trust trust) { SQLiteDatabase db = this.getWritableDatabase(); String[] selectionArgs = { account.getUuid(), - name, fingerprint }; ContentValues values = new ContentValues(); values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.ordinal()); int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ", selectionArgs); return rows == 1; @@ -886,7 +897,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) { - storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT)); + storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED); } public void recreateAxolotlDb() { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index ca62b7db6..92e4cf35e 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -759,6 +759,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa packet = account.getAxolotlService().fetchPacketFromCache(message); if (packet == null && account.isOnlineAndConnected()) { account.getAxolotlService().prepareMessage(message); + message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); } break; @@ -789,6 +790,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa conversation.startOtrSession(message.getCounterpart().getResourcepart(), false); } break; + case Message.ENCRYPTION_AXOLOTL: + message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); + break; } } diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index ebc1ae839..ef99b4915 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -393,7 +393,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); final Jid bareJid = contactJid.toBareJid(); AxolotlService.SQLiteAxolotlStore.Trust trust = contact.getAccount().getAxolotlService() - .getFingerprintTrust(bareJid.toString(), fingerprint); + .getFingerprintTrust(fingerprint); switch (trust) { case TRUSTED: removeButton.setVisibility(View.VISIBLE); @@ -413,7 +413,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd removeButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - axolotlService.setFingerprintTrust(bareJid.toString(), fingerprint, + axolotlService.setFingerprintTrust(fingerprint, AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED); refreshUi(); xmppConnectionService.updateConversationUi(); @@ -422,7 +422,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd trustButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - axolotlService.setFingerprintTrust(bareJid.toString(), fingerprint, + axolotlService.setFingerprintTrust(fingerprint, AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED); refreshUi(); xmppConnectionService.updateConversationUi(); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index d2c75a5ee..df3a391b1 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -156,15 +156,15 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.indicator.setVisibility(View.GONE); } else { viewHolder.indicator.setVisibility(View.VISIBLE); - if (message.getMergedStatus() == Message.STATUS_RECEIVED - && message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { AxolotlService.SQLiteAxolotlStore.Trust trust = message.getConversation() .getAccount().getAxolotlService().getFingerprintTrust( - message.getContact().getJid().toBareJid().toString(), message.getAxolotlFingerprint()); - if (trust == null || trust != AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED) { + if(trust == null || trust != AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED) { viewHolder.indicator.setColorFilter(Color.RED); + } else { + viewHolder.indicator.clearColorFilter(); } } } -- cgit v1.2.3 From 43703870e87bfda94993439f57c7d2692aaf7783 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 17 Jul 2015 18:40:27 +0200 Subject: Remove unneccessary code --- .../java/eu/siacs/conversations/services/XmppConnectionService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 92e4cf35e..60815f5e0 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -755,9 +755,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } break; case Message.ENCRYPTION_AXOLOTL: - message.setStatus(Message.STATUS_WAITING); packet = account.getAxolotlService().fetchPacketFromCache(message); - if (packet == null && account.isOnlineAndConnected()) { + if (packet == null) { account.getAxolotlService().prepareMessage(message); message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); } -- cgit v1.2.3 From 2045a7126242ef7656a38e445b636ae87d4b763e Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 17 Jul 2015 19:44:05 +0200 Subject: Handle file transmission properly in axolotl --- .../conversations/services/XmppConnectionService.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 60815f5e0..08c0b3fa5 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -755,10 +755,18 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } break; case Message.ENCRYPTION_AXOLOTL: - packet = account.getAxolotlService().fetchPacketFromCache(message); - if (packet == null) { - account.getAxolotlService().prepareMessage(message); - message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); + if (message.needsUploading()) { + if (account.httpUploadAvailable() || message.fixCounterpart()) { + this.sendFileMessage(message); + } else { + break; + } + } else { + packet = account.getAxolotlService().fetchPacketFromCache(message); + if (packet == null) { + account.getAxolotlService().prepareMessage(message); + message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); + } } break; -- cgit v1.2.3 From 9c4d55f82ce50391ac09b4f7d7a0f3576c014e56 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 17 Jul 2015 19:44:45 +0200 Subject: Send correct body for HTTP files When using HTTP upload to send files, take care to transmit only the URL rather than the entire body, which contains metadata. --- .../eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 57e57f7f3..8358125d2 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -961,8 +961,14 @@ public class AxolotlService { @Nullable public XmppAxolotlMessage encrypt(Message message ){ + final String content; + if (message.hasFileOnRemoteHost()) { + content = message.getFileParams().url.toString(); + } else { + content = message.getBody(); + } final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(message.getContact().getJid().toBareJid(), - getOwnDeviceId(), message.getBody()); + getOwnDeviceId(), content); if(findSessionsforContact(message.getContact()).isEmpty()) { return null; -- cgit v1.2.3 From 6f67469bda0ffe97cb8cd8d400affed5a17c34c5 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Sun, 19 Jul 2015 14:09:49 +0200 Subject: Refactor trust key ui and show in account details Refactored the trust key row UI element so it can be used in multiple places. It now also uses a slider to toggle the trust state, and the redundant trust state description was removed. EditAccountActivity now shows the keys of other devices associated with that account. --- .../conversations/ui/ContactDetailsActivity.java | 49 +------------- .../conversations/ui/EditAccountActivity.java | 22 ++++++ .../eu/siacs/conversations/ui/XmppActivity.java | 79 ++++++++++++++++++++++ 3 files changed, 102 insertions(+), 48 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index ef99b4915..e7a8ffb75 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -35,7 +35,6 @@ import java.util.List; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; -import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.ListItem; @@ -381,53 +380,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys( contact.getAccount(), contact.getJid().toBareJid().toString())) { hasKeys = true; - 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); - TextView keyTrust = (TextView) view.findViewById(R.id.key_trust); - ImageButton removeButton = (ImageButton) view - .findViewById(R.id.button_remove); - ImageButton trustButton = (ImageButton) view - .findViewById(R.id.button_trust); - final AxolotlService axolotlService = contact.getAccount().getAxolotlService(); - final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); - final Jid bareJid = contactJid.toBareJid(); - AxolotlService.SQLiteAxolotlStore.Trust trust = contact.getAccount().getAxolotlService() - .getFingerprintTrust(fingerprint); - switch (trust) { - case TRUSTED: - removeButton.setVisibility(View.VISIBLE); - //Log.d(Config.LOGTAG, AxolotlService.getLogprefix(contact.getAccount()) + "Setting remove button visible!"); - break; - case UNDECIDED: - case UNTRUSTED: - //Log.d(Config.LOGTAG, AxolotlService.getLogprefix(contact.getAccount()) + "Setting trust button visible!"); - trustButton.setVisibility(View.VISIBLE); - break; - } - keyType.setText("Axolotl Fingerprint"); - key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint())); - keyTrust.setText(trust.toString()); - keyTrust.setVisibility(View.VISIBLE); - keys.addView(view); - removeButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - axolotlService.setFingerprintTrust(fingerprint, - AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED); - refreshUi(); - xmppConnectionService.updateConversationUi(); - } - }); - trustButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - axolotlService.setFingerprintTrust(fingerprint, - AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED); - refreshUi(); - xmppConnectionService.updateConversationUi(); - } - }); + addFingerprintRow(keys, contact.getAccount(), identityKey); } if (contact.getPgpKeyId() != 0) { hasKeys = true; diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index e3a43b8b3..77ca2a679 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -26,6 +26,8 @@ import android.widget.TableLayout; import android.widget.TextView; import android.widget.Toast; +import org.whispersystems.libaxolotl.IdentityKey; + import java.util.Set; import eu.siacs.conversations.R; @@ -69,6 +71,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private ImageButton mAxolotlFingerprintToClipboardButton; private ImageButton mWipeAxolotlPepButton; private ImageButton mRegenerateAxolotlKeyButton; + private LinearLayout keys; + private LinearLayout keysCard; private Jid jidToEdit; private Account mAccount; @@ -329,6 +333,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mAxolotlDevicelist = (TextView) findViewById(R.id.axolotl_devicelist); this.mAxolotlDevicelistBox = (RelativeLayout) findViewById(R.id.axolotl_devices_box); this.mWipeAxolotlPepButton = (ImageButton) findViewById(R.id.action_wipe_axolotl_pep); + this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card); + this.keys = (LinearLayout) findViewById(R.id.other_device_keys); this.mSaveButton = (Button) findViewById(R.id.save_button); this.mCancelButton = (Button) findViewById(R.id.cancel_button); this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); @@ -568,6 +574,22 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } else { this.mAxolotlFingerprintBox.setVisibility(View.GONE); } + final IdentityKey ownKey = mAccount.getAxolotlService().getOwnPublicKey(); + boolean hasKeys = false; + keys.removeAllViews(); + for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys( + mAccount, mAccount.getJid().toBareJid().toString())) { + if(ownKey.equals(identityKey)) { + continue; + } + hasKeys = true; + addFingerprintRow(keys, mAccount, identityKey); + } + if (hasKeys) { + keysCard.setVisibility(View.VISIBLE); + } else { + keysCard.setVisibility(View.GONE); + } } else { if (this.mAccount.errorStatus()) { this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId())); diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 7c994c31a..9dfece2f5 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -43,8 +43,11 @@ import android.util.Log; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; +import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; import com.google.zxing.BarcodeFormat; @@ -53,9 +56,12 @@ import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.kyleduo.switchbutton.SwitchButton; import net.java.otr4j.session.SessionID; +import org.whispersystems.libaxolotl.IdentityKey; + import java.io.FileNotFoundException; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -65,6 +71,7 @@ import java.util.concurrent.RejectedExecutionException; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -74,6 +81,8 @@ import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder; +import eu.siacs.conversations.ui.widget.Switch; +import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.jid.InvalidJidException; @@ -588,6 +597,76 @@ public abstract class XmppActivity extends Activity { builder.create().show(); } + protected void addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) { + final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); + final AxolotlService.SQLiteAxolotlStore.Trust trust = account.getAxolotlService() + .getFingerprintTrust(fingerprint); + addFingerprintRowWithListeners(keys, account, identityKey, trust, true, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked != (trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED)) { + account.getAxolotlService().setFingerprintTrust(fingerprint, + (isChecked) ? AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED : + AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED); + } + refreshUi(); + xmppConnectionService.updateAccountUi(); + xmppConnectionService.updateConversationUi(); + } + }, + new View.OnClickListener() { + @Override + public void onClick(View v) { + account.getAxolotlService().setFingerprintTrust(fingerprint, + AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED); + refreshUi(); + xmppConnectionService.updateAccountUi(); + xmppConnectionService.updateConversationUi(); + } + } + + ); + } + + protected void addFingerprintRowWithListeners(LinearLayout keys, final Account account, + IdentityKey identityKey, + AxolotlService.SQLiteAxolotlStore.Trust trust, + boolean showTag, + CompoundButton.OnCheckedChangeListener + onCheckedChangeListener, + View.OnClickListener onClickListener) { + View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false); + TextView key = (TextView) view.findViewById(R.id.key); + TextView keyType = (TextView) view.findViewById(R.id.key_type); + Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust); + trustToggle.setVisibility(View.VISIBLE); + trustToggle.setOnCheckedChangeListener(onCheckedChangeListener); + trustToggle.setOnClickListener(onClickListener); + + switch (trust) { + case UNTRUSTED: + case TRUSTED: + trustToggle.setChecked(trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED, false); + trustToggle.setEnabled(true); + break; + case UNDECIDED: + trustToggle.setChecked(false, false); + trustToggle.setEnabled(false); + break; + } + + if (showTag) { + keyType.setText(getString(R.string.axolotl_fingerprint)); + } else { + keyType.setVisibility(View.GONE); + } + + key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint())); + keys.addView(view); + + } + public void selectPresence(final Conversation conversation, final OnPresenceSelected listener) { final Contact contact = conversation.getContact(); -- cgit v1.2.3 From ec0aff4ed7982cc6db43cb6337f828f732429fd2 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Sun, 19 Jul 2015 14:17:25 +0200 Subject: Encrypt files for HTTP upload in encrypted chats --- .../java/eu/siacs/conversations/http/HttpDownloadConnection.java | 6 ++++-- src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 62fe4191d..a9c106ab1 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -70,7 +70,8 @@ public class HttpDownloadConnection implements Transferable { String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null; if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) { this.message.setEncryption(Message.ENCRYPTION_PGP); - } else if (message.getEncryption() != Message.ENCRYPTION_OTR) { + } else if (message.getEncryption() != Message.ENCRYPTION_OTR + && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) { this.message.setEncryption(Message.ENCRYPTION_NONE); } String extension; @@ -86,7 +87,8 @@ public class HttpDownloadConnection implements Transferable { this.file.setKey(CryptoHelper.hexToBytes(reference)); } - if (this.message.getEncryption() == Message.ENCRYPTION_OTR + if ((this.message.getEncryption() == Message.ENCRYPTION_OTR + || this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL) && this.file.getKey() == null) { this.message.setEncryption(Message.ENCRYPTION_NONE); } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index a3ab8daba..c0d4455a1 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -88,7 +88,9 @@ public class HttpUploadConnection implements Transferable { this.file = mXmppConnectionService.getFileBackend().getFile(message, false); this.file.setExpectedSize(this.file.getSize()); - if (Config.ENCRYPT_ON_HTTP_UPLOADED) { + if (Config.ENCRYPT_ON_HTTP_UPLOADED + || message.getEncryption() == Message.ENCRYPTION_AXOLOTL + || message.getEncryption() == Message.ENCRYPTION_OTR) { this.key = new byte[48]; mXmppConnectionService.getRNG().nextBytes(this.key); this.file.setKey(this.key); -- cgit v1.2.3 From 14010bf5a6198e4e53ba3f86328d061cf20b8da1 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Sun, 19 Jul 2015 18:36:28 +0200 Subject: Ask for key trust when sending messages If the contact (or the own account) has keys that have UNDECIDED trust, we now drop the user into the new TrustKeysActivity, where they have to decide for each new key whether it should be TRUSTED or UNTRUSTED. --- .../crypto/axolotl/AxolotlService.java | 125 +++++++---- .../conversations/persistance/DatabaseBackend.java | 9 + .../services/XmppConnectionService.java | 46 +++- .../conversations/ui/ConversationActivity.java | 56 ++++- .../conversations/ui/ConversationFragment.java | 23 +- .../siacs/conversations/ui/TrustKeysActivity.java | 237 +++++++++++++++++++++ .../eu/siacs/conversations/ui/XmppActivity.java | 9 +- .../conversations/xmpp/OnNewKeysAvailable.java | 5 + 8 files changed, 453 insertions(+), 57 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java create mode 100644 src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 8358125d2..b05112a34 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -115,6 +115,10 @@ public class AxolotlService { return "Untrusted"; } } + + public static Trust fromBoolean(Boolean trusted) { + return trusted?TRUSTED:UNTRUSTED; + } }; private static IdentityKeyPair generateIdentityKeyPair() { @@ -275,6 +279,10 @@ public class AxolotlService { mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); } + public Set getContactUndecidedKeys(String bareJid) { + return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, Trust.UNDECIDED); + } + // -------------------------------------- // SessionStore // -------------------------------------- @@ -658,6 +666,14 @@ public class AxolotlService { return axolotlStore.getIdentityKeyPair().getPublicKey(); } + public Set getPendingKeys() { + return axolotlStore.getContactUndecidedKeys(account.getJid().toBareJid().toString()); + } + + public Set getPendingKeys(Contact contact) { + return axolotlStore.getContactUndecidedKeys(contact.getJid().toBareJid().toString()); + } + private AxolotlAddress getAddressForJid(Jid jid) { return new AxolotlAddress(jid.toString(), 0); } @@ -852,14 +868,32 @@ public class AxolotlService { axolotlStore.setFingerprintTrust(fingerprint, trust); } - private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building new sesstion for " + address.getDeviceId()); + private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address, final boolean flushWaitingQueueAfterFetch) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.getDeviceId()); try { IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice( Jid.fromString(address.getName()), address.getDeviceId()); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Retrieving bundle: " + bundlesPacket); mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() { + private void finish() { + AxolotlAddress ownAddress = new AxolotlAddress(conversation.getAccount().getJid().toBareJid().toString(),0); + AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0); + if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) + && !fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) { + if (flushWaitingQueueAfterFetch) { + conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_AXOLOTL, + new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + processSending(message); + } + }); + } + mXmppConnectionService.newKeysAvailable(); + } + } + @Override public void onIqPacketReceived(Account account, IqPacket packet) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received preKey IQ packet, processing..."); @@ -869,6 +903,7 @@ public class AxolotlService { if (preKeyBundleList.isEmpty() || bundle == null) { Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"preKey IQ packet invalid: " + packet); fetchStatusMap.put(address, FetchStatus.ERROR); + finish(); return; } Random random = new Random(); @@ -876,6 +911,7 @@ public class AxolotlService { if (preKey == null) { //should never happen fetchStatusMap.put(address, FetchStatus.ERROR); + finish(); return; } @@ -898,18 +934,7 @@ public class AxolotlService { fetchStatusMap.put(address, FetchStatus.ERROR); } - AxolotlAddress ownAddress = new AxolotlAddress(conversation.getAccount().getJid().toBareJid().toString(),0); - AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0); - if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) - && !fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) { - conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_AXOLOTL, - new Conversation.OnMessageFound() { - @Override - public void onMessageFound(Message message) { - processSending(message); - } - }); - } + finish(); } }); } catch (InvalidJidException e) { @@ -917,48 +942,75 @@ public class AxolotlService { } } - private boolean createSessionsIfNeeded(Conversation conversation) { - boolean newSessions = false; - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Creating axolotl sessions if needed..."); + public Set findDevicesWithoutSession(final Conversation conversation) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + conversation.getContact().getJid().toBareJid()); Jid contactJid = conversation.getContact().getJid().toBareJid(); Set addresses = new HashSet<>(); if(deviceIds.get(contactJid) != null) { for(Integer foreignId:this.deviceIds.get(contactJid)) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found device "+account.getJid().toBareJid()+":"+foreignId); - addresses.add(new AxolotlAddress(contactJid.toString(), foreignId)); + AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId); + if(sessions.get(address) == null) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + if ( identityKey != null ) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache..."); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", "")); + sessions.put(address, session); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + foreignId); + addresses.add(new AxolotlAddress(contactJid.toString(), foreignId)); + } + } } } else { Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!"); } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Checking own account "+account.getJid().toBareJid()); if(deviceIds.get(account.getJid().toBareJid()) != null) { for(Integer ownId:this.deviceIds.get(account.getJid().toBareJid())) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found device "+account.getJid().toBareJid()+":"+ownId); - addresses.add(new AxolotlAddress(account.getJid().toBareJid().toString(), ownId)); + AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId); + if(sessions.get(address) == null) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + if ( identityKey != null ) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache..."); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", "")); + sessions.put(address, session); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId); + addresses.add(new AxolotlAddress(account.getJid().toBareJid().toString(), ownId)); + } + } } } + + return addresses; + } + + public boolean createSessionsIfNeeded(final Conversation conversation, final boolean flushWaitingQueueAfterFetch) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed..."); + boolean newSessions = false; + Set addresses = findDevicesWithoutSession(conversation); for (AxolotlAddress address : addresses) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Processing device: " + address.toString()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString()); FetchStatus status = fetchStatusMap.get(address); - XmppAxolotlSession session = sessions.get(address); - if ( session == null && ( status == null || status == FetchStatus.ERROR) ) { - IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); - if ( identityKey != null ) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache..."); - session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", "")); - sessions.put(address, session); - } else { + if ( status == null || status == FetchStatus.ERROR ) { fetchStatusMap.put(address, FetchStatus.PENDING); - this.buildSessionFromPEP(conversation, address); + this.buildSessionFromPEP(conversation, address, flushWaitingQueueAfterFetch); newSessions = true; - } } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already fetching bundle for " + address.toString()); } } + return newSessions; } + public boolean hasPendingKeyFetches(Conversation conversation) { + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(),0); + AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0); + return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) + ||fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING); + + } + @Nullable public XmppAxolotlMessage encrypt(Message message ){ final String content; @@ -1013,10 +1065,9 @@ public class AxolotlService { }); } - public void prepareMessage(Message message) { + public void prepareMessage(final Message message) { if (!messageCache.containsKey(message.getUuid())) { - boolean newSessions = createSessionsIfNeeded(message.getConversation()); - + boolean newSessions = createSessionsIfNeeded(message.getConversation(), true); if (!newSessions) { this.processSending(message); } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 39ef5d366..a2c62a8c5 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -834,10 +834,19 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public Set loadIdentityKeys(Account account, String name) { + return loadIdentityKeys(account, name, null); + } + + public Set loadIdentityKeys(Account account, String name, AxolotlService.SQLiteAxolotlStore.Trust trust) { Set identityKeys = new HashSet<>(); Cursor cursor = getIdentityKeyCursor(account, name, false); while(cursor.moveToNext()) { + if ( trust != null && + cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED)) + != trust.ordinal()) { + continue; + } try { identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0)); } catch (InvalidKeyException e) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 08c0b3fa5..cc113cefa 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -85,6 +85,7 @@ import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnMessageAcknowledged; import eu.siacs.conversations.xmpp.OnMessagePacketReceived; +import eu.siacs.conversations.xmpp.OnNewKeysAvailable; import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.OnStatusChanged; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; @@ -307,6 +308,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private int rosterChangedListenerCount = 0; private OnMucRosterUpdate mOnMucRosterUpdate = null; private int mucRosterChangedListenerCount = 0; + private OnNewKeysAvailable mOnNewKeysAvailable = null; + private int newKeysAvailableListenerCount = 0; private SecureRandom mRandom; private OpenPgpServiceConnection pgpServiceConnection; private PgpEngine mPgpEngine = null; @@ -1344,17 +1347,17 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa switchToForeground(); } this.mOnUpdateBlocklist = listener; - if (this.updateBlocklistListenerCount < 2) { - this.updateBlocklistListenerCount++; + if (this.newKeysAvailableListenerCount < 2) { + this.newKeysAvailableListenerCount++; } } } public void removeOnUpdateBlocklistListener() { synchronized (this) { - this.updateBlocklistListenerCount--; - if (this.updateBlocklistListenerCount <= 0) { - this.updateBlocklistListenerCount = 0; + this.newKeysAvailableListenerCount--; + if (this.newKeysAvailableListenerCount <= 0) { + this.newKeysAvailableListenerCount = 0; this.mOnUpdateBlocklist = null; if (checkListeners()) { switchToBackground(); @@ -1363,6 +1366,30 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void setOnNewKeysAvailableListener(final OnNewKeysAvailable listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnNewKeysAvailable = listener; + if (this.newKeysAvailableListenerCount < 2) { + this.newKeysAvailableListenerCount++; + } + } + } + + public void removeOnNewKeysAvailableListener() { + synchronized (this) { + this.newKeysAvailableListenerCount--; + if (this.newKeysAvailableListenerCount <= 0) { + this.newKeysAvailableListenerCount = 0; + this.mOnNewKeysAvailable = null; + if (checkListeners()) { + switchToBackground(); + } + } + } + } public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) { synchronized (this) { if (checkListeners()) { @@ -1393,7 +1420,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa && this.mOnConversationUpdate == null && this.mOnRosterUpdate == null && this.mOnUpdateBlocklist == null - && this.mOnShowErrorToast == null); + && this.mOnShowErrorToast == null + && this.mOnNewKeysAvailable == null); } private void switchToForeground() { @@ -2281,6 +2309,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void newKeysAvailable() { + if(mOnNewKeysAvailable != null) { + mOnNewKeysAvailable.onNewKeysAvailable(); + } + } + public Account findAccountByJid(final Jid accountJid) { for (Account account : this.accounts) { if (account.getJid().toBareJid().equals(accountJid.toBareJid())) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 2e50af3bf..a6cd0431f 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -65,11 +65,14 @@ public class ConversationActivity extends XmppActivity public static final int REQUEST_SEND_MESSAGE = 0x0201; public static final int REQUEST_DECRYPT_PGP = 0x0202; public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; + public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208; + public static final int REQUEST_TRUST_KEYS_MENU = 0x0209; public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; + public static final int ATTACHMENT_CHOICE_INVALID = 0x0306; private static final String STATE_OPEN_CONVERSATION = "state_open_conversation"; private static final String STATE_PANEL_OPEN = "state_panel_open"; private static final String STATE_PENDING_URI = "state_pending_uri"; @@ -79,6 +82,7 @@ public class ConversationActivity extends XmppActivity final private List mPendingImageUris = new ArrayList<>(); final private List mPendingFileUris = new ArrayList<>(); private Uri mPendingGeoUri = null; + private boolean forbidProcessingPendings = false; private View mContentView; @@ -401,7 +405,7 @@ public class ConversationActivity extends XmppActivity return true; } - private void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { + protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { final Conversation conversation = getSelectedConversation(); final Account account = conversation.getAccount(); final OnPresenceSelected callback = new OnPresenceSelected() { @@ -537,7 +541,9 @@ public class ConversationActivity extends XmppActivity showInstallPgpDialog(); } } else { - selectPresenceToAttachFile(attachmentChoice,encryption); + if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) { + selectPresenceToAttachFile(attachmentChoice, encryption); + } } } @@ -962,18 +968,23 @@ public class ConversationActivity extends XmppActivity this.mConversationFragment.reInit(getSelectedConversation()); } - for(Iterator i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { - attachImageToConversation(getSelectedConversation(),i.next()); - } + if(!forbidProcessingPendings) { + for (Iterator i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { + Uri foo = i.next(); + attachImageToConversation(getSelectedConversation(), foo); + } - for(Iterator i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { - attachFileToConversation(getSelectedConversation(),i.next()); - } + for (Iterator i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { + attachFileToConversation(getSelectedConversation(), i.next()); + } - if (mPendingGeoUri != null) { - attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); - mPendingGeoUri = null; + if (mPendingGeoUri != null) { + attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); + mPendingGeoUri = null; + } } + forbidProcessingPendings = false; + ExceptionHelper.checkForCrash(this, this.xmppConnectionService); setIntent(new Intent()); } @@ -1083,6 +1094,9 @@ public class ConversationActivity extends XmppActivity attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); this.mPendingGeoUri = null; } + } else if (requestCode == REQUEST_TRUST_KEYS_TEXT || requestCode == REQUEST_TRUST_KEYS_MENU) { + this.forbidProcessingPendings = !xmppConnectionServiceBound; + mConversationFragment.onActivityResult(requestCode, resultCode, data); } } else { mPendingImageUris.clear(); @@ -1235,6 +1249,26 @@ public class ConversationActivity extends XmppActivity return getPreferences().getBoolean("indicate_received", false); } + protected boolean trustKeysIfNeeded(int requestCode) { + return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID); + } + + protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { + AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService(); + if(!axolotlService.getPendingKeys(mSelectedConversation.getContact()).isEmpty() + || !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty()) { + axolotlService.createSessionsIfNeeded(mSelectedConversation, false); + Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class); + intent.putExtra("contact", mSelectedConversation.getContact().getJid().toBareJid().toString()); + intent.putExtra("account", mSelectedConversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra("choice", attachmentChoice); + startActivityForResult(intent, requestCode); + return true; + } else { + return false; + } + } + @Override protected void refreshUiReal() { updateConversationList(); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 026c74adf..15491deab 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.ui; +import android.app.Activity; import android.app.AlertDialog; import android.app.Fragment; import android.app.PendingIntent; @@ -11,6 +12,7 @@ import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.os.Bundle; import android.text.InputType; +import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; @@ -43,6 +45,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -304,7 +307,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) { sendPgpMessage(message); } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_AXOLOTL) { - sendAxolotlMessage(message); + if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) { + sendAxolotlMessage(message); + } } else { sendPlainTextMessage(message); } @@ -1128,7 +1133,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa protected void sendAxolotlMessage(final Message message) { final ConversationActivity activity = (ConversationActivity) getActivity(); final XmppConnectionService xmppService = activity.xmppConnectionService; - //message.setCounterpart(conversation.getNextCounterpart()); xmppService.sendMessage(message); messageSent(); } @@ -1195,4 +1199,19 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa updateSendButton(); } + @Override + public void onActivityResult(int requestCode, int resultCode, + final Intent data) { + if (resultCode == Activity.RESULT_OK) { + if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) { + final String body = mEditMessage.getText().toString(); + Message message = new Message(conversation, body, conversation.getNextEncryption(activity.forceEncryption())); + sendAxolotlMessage(message); + } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) { + int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID); + activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption(activity.forceEncryption())); + } + } + } + } diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java new file mode 100644 index 000000000..4efa4f6cf --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -0,0 +1,237 @@ +package eu.siacs.conversations.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.whispersystems.libaxolotl.IdentityKey; + + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.xmpp.OnNewKeysAvailable; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailable { + private Jid accountJid; + private Jid contactJid; + + private Contact contact; + private TextView ownKeysTitle; + private LinearLayout ownKeys; + private LinearLayout ownKeysCard; + private TextView foreignKeysTitle; + private LinearLayout foreignKeys; + private LinearLayout foreignKeysCard; + private Button mSaveButton; + private Button mCancelButton; + + private final Map ownKeysToTrust = new HashMap<>(); + private final Map foreignKeysToTrust = new HashMap<>(); + + private final OnClickListener mSaveButtonListener = new OnClickListener() { + @Override + public void onClick(View v) { + commitTrusts(); + Intent data = new Intent(); + data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID)); + setResult(RESULT_OK, data); + finish(); + } + }; + + private final OnClickListener mCancelButtonListener = new OnClickListener() { + @Override + public void onClick(View v) { + setResult(RESULT_CANCELED); + finish(); + } + }; + + @Override + protected void refreshUiReal() { + invalidateOptionsMenu(); + populateView(); + } + + @Override + protected String getShareableUri() { + if (contact != null) { + return contact.getShareableUri(); + } else { + return ""; + } + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_trust_keys); + try { + this.accountJid = Jid.fromString(getIntent().getExtras().getString("account")); + } catch (final InvalidJidException ignored) { + } + try { + this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact")); + } catch (final InvalidJidException ignored) { + } + + ownKeysTitle = (TextView) findViewById(R.id.own_keys_title); + ownKeys = (LinearLayout) findViewById(R.id.own_keys_details); + ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card); + foreignKeysTitle = (TextView) findViewById(R.id.foreign_keys_title); + foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys_details); + foreignKeysCard = (LinearLayout) findViewById(R.id.foreign_keys_card); + mCancelButton = (Button) findViewById(R.id.cancel_button); + mCancelButton.setOnClickListener(mCancelButtonListener); + mSaveButton = (Button) findViewById(R.id.save_button); + mSaveButton.setOnClickListener(mSaveButtonListener); + + + if (getActionBar() != null) { + getActionBar().setHomeButtonEnabled(true); + getActionBar().setDisplayHomeAsUpEnabled(true); + } + } + + private void populateView() { + setTitle(getString(R.string.trust_keys)); + ownKeys.removeAllViews(); + foreignKeys.removeAllViews(); + boolean hasOwnKeys = false; + boolean hasForeignKeys = false; + for(final IdentityKey identityKey : ownKeysToTrust.keySet()) { + hasOwnKeys = true; + addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey, + Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + ownKeysToTrust.put(identityKey, isChecked); + refreshUi(); + xmppConnectionService.updateAccountUi(); + xmppConnectionService.updateConversationUi(); + } + }, + null + ); + } + for(final IdentityKey identityKey : foreignKeysToTrust.keySet()) { + hasForeignKeys = true; + addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey, + Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + foreignKeysToTrust.put(identityKey, isChecked); + refreshUi(); + xmppConnectionService.updateAccountUi(); + xmppConnectionService.updateConversationUi(); + } + }, + null + ); + } + + if(hasOwnKeys) { + ownKeysTitle.setText(accountJid.toString()); + ownKeysCard.setVisibility(View.VISIBLE); + } + if(hasForeignKeys) { + foreignKeysTitle.setText(contactJid.toString()); + foreignKeysCard.setVisibility(View.VISIBLE); + } + } + + private void getFingerprints(final Account account) { + Set ownKeysSet = account.getAxolotlService().getPendingKeys(); + for(final IdentityKey identityKey : ownKeysSet) { + if(!ownKeysToTrust.containsKey(identityKey)) { + ownKeysToTrust.put(identityKey, false); + } + } + Set foreignKeysSet = account.getAxolotlService().getPendingKeys(contact); + for(final IdentityKey identityKey : foreignKeysSet) { + if(!foreignKeysToTrust.containsKey(identityKey)) { + foreignKeysToTrust.put(identityKey, false); + } + } + } + + @Override + public void onBackendConnected() { + if ((accountJid != null) && (contactJid != null)) { + final Account account = xmppConnectionService + .findAccountByJid(accountJid); + if (account == null) { + return; + } + this.contact = account.getRoster().getContact(contactJid); + ownKeysToTrust.clear(); + foreignKeysToTrust.clear(); + getFingerprints(account); + + Conversation conversation = xmppConnectionService.findOrCreateConversation(account, contactJid, false); + if(account.getAxolotlService().hasPendingKeyFetches(conversation)) { + lock(); + } + + populateView(); + } + } + + @Override + public void onNewKeysAvailable() { + runOnUiThread(new Runnable() { + @Override + public void run() { + final Account account = xmppConnectionService + .findAccountByJid(accountJid); + unlock(); + getFingerprints(account); + refreshUi(); + } + }); + } + + private void commitTrusts() { + for(IdentityKey identityKey:ownKeysToTrust.keySet()) { + contact.getAccount().getAxolotlService().setFingerprintTrust( + identityKey.getFingerprint().replaceAll("\\s", ""), + Trust.fromBoolean(ownKeysToTrust.get(identityKey))); + } + for(IdentityKey identityKey:foreignKeysToTrust.keySet()) { + contact.getAccount().getAxolotlService().setFingerprintTrust( + identityKey.getFingerprint().replaceAll("\\s", ""), + Trust.fromBoolean(foreignKeysToTrust.get(identityKey))); + } + } + + private void unlock() { + mSaveButton.setEnabled(true); + mSaveButton.setText(getString(R.string.done)); + mSaveButton.setTextColor(getPrimaryTextColor()); + } + + private void lock() { + mSaveButton.setEnabled(false); + mSaveButton.setText(getString(R.string.fetching_keys)); + mSaveButton.setTextColor(getSecondaryTextColor()); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 9dfece2f5..00322452c 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -84,6 +84,7 @@ import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinde import eu.siacs.conversations.ui.widget.Switch; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExceptionHelper; +import eu.siacs.conversations.xmpp.OnNewKeysAvailable; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -296,6 +297,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnShowErrorToast) { this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this); } + if (this instanceof OnNewKeysAvailable) { + this.xmppConnectionService.setOnNewKeysAvailableListener((OnNewKeysAvailable) this); + } } protected void unregisterListeners() { @@ -317,6 +321,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnShowErrorToast) { this.xmppConnectionService.removeOnShowErrorToastListener(); } + if (this instanceof OnNewKeysAvailable) { + this.xmppConnectionService.removeOnNewKeysAvailableListener(); + } } @Override @@ -452,7 +459,7 @@ public abstract class XmppActivity extends Activity { @Override public void userInputRequried(PendingIntent pi, - Account account) { + Account account) { try { startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java b/src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java new file mode 100644 index 000000000..59dc1c1ea --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java @@ -0,0 +1,5 @@ +package eu.siacs.conversations.xmpp; + +public interface OnNewKeysAvailable { + public void onNewKeysAvailable(); +} -- cgit v1.2.3 From 480b1cde810e0ed2afc6ea424fb36fd973ae24b9 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Sun, 19 Jul 2015 23:35:03 +0200 Subject: Add clear devices to overflow menu in EditAccount --- .../eu/siacs/conversations/ui/EditAccountActivity.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 77ca2a679..1f5b76a3c 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -363,6 +363,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list); final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more); final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server); + final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices); if (mAccount != null && mAccount.isOnlineAndConnected()) { if (!mAccount.getXmppConnection().getFeatures().blocking()) { showBlocklist.setVisible(false); @@ -370,6 +371,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate if (!mAccount.getXmppConnection().getFeatures().register()) { changePassword.setVisible(false); } + Set otherDevices = mAccount.getAxolotlService().getOwnDeviceIds(); + if (otherDevices == null || otherDevices.isEmpty()) { + clearDevices.setVisible(false); + } } else { showQrCode.setVisible(false); showBlocklist.setVisible(false); @@ -440,6 +445,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate changePasswordIntent.putExtra("account", mAccount.getJid().toString()); startActivity(changePasswordIntent); break; + case R.id.action_clear_devices: + showWipePepDialog(); + break; } return super.onOptionsItemSelected(item); } @@ -621,11 +629,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate public void showWipePepDialog() { Builder builder = new Builder(this); - builder.setTitle("Wipe PEP"); + builder.setTitle(getString(R.string.clear_other_devices)); builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setMessage("Are you sure you want to wipe all other devices from the PEP device ID list?"); + builder.setMessage(getString(R.string.clear_other_devices_desc)); builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton("Yes", + builder.setPositiveButton(getString(R.string.accept), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { -- cgit v1.2.3 From 2240066bbe697ae4d32a40811a246132898a35ef Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Sun, 19 Jul 2015 23:38:09 +0200 Subject: Remove device list from EditAccount --- .../conversations/ui/EditAccountActivity.java | 22 ---------------------- 1 file changed, 22 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 1f5b76a3c..2b352f03e 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -62,14 +62,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private TextView mSessionEst; private TextView mOtrFingerprint; private TextView mAxolotlFingerprint; - private TextView mAxolotlDevicelist; private ImageView mAvatar; private RelativeLayout mOtrFingerprintBox; private RelativeLayout mAxolotlFingerprintBox; - private RelativeLayout mAxolotlDevicelistBox; private ImageButton mOtrFingerprintToClipboardButton; private ImageButton mAxolotlFingerprintToClipboardButton; - private ImageButton mWipeAxolotlPepButton; private ImageButton mRegenerateAxolotlKeyButton; private LinearLayout keys; private LinearLayout keysCard; @@ -330,9 +327,6 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box); this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_axolotl_to_clipboard); this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key); - this.mAxolotlDevicelist = (TextView) findViewById(R.id.axolotl_devicelist); - this.mAxolotlDevicelistBox = (RelativeLayout) findViewById(R.id.axolotl_devices_box); - this.mWipeAxolotlPepButton = (ImageButton) findViewById(R.id.action_wipe_axolotl_pep); this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card); this.keys = (LinearLayout) findViewById(R.id.other_device_keys); this.mSaveButton = (Button) findViewById(R.id.save_button); @@ -533,22 +527,6 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } else { this.mOtrFingerprintBox.setVisibility(View.GONE); } - final Set ownDevices = this.mAccount.getAxolotlService().getOwnDeviceIds(); - if (ownDevices != null && !ownDevices.isEmpty()) { - this.mAxolotlDevicelistBox.setVisibility(View.VISIBLE); - this.mAxolotlDevicelist.setText(TextUtils.join(", ", ownDevices)); - this.mWipeAxolotlPepButton - .setVisibility(View.VISIBLE); - this.mWipeAxolotlPepButton - .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View v) { - showWipePepDialog(); - } - }); - } else { - this.mAxolotlDevicelistBox.setVisibility(View.GONE); - } final String axolotlFingerprint = this.mAccount.getAxolotlService().getOwnPublicKey().getFingerprint(); if (axolotlFingerprint != null) { this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE); -- cgit v1.2.3 From dd964077b97f4d8ca68cf4863a12e3eab0ce7771 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 20 Jul 2015 13:15:49 +0200 Subject: Fix axolotl database migration Can't call getWritableDatabase in recreateAxolotlDb() --- .../java/eu/siacs/conversations/persistance/DatabaseBackend.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index a2c62a8c5..8168b1a6a 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -289,7 +289,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { cursor.close(); } if (oldVersion < 15 && newVersion >= 15) { - recreateAxolotlDb(); + recreateAxolotlDb(db); db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.FINGERPRINT + " TEXT"); } @@ -910,8 +910,11 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void recreateAxolotlDb() { + recreateAxolotlDb(getWritableDatabase()); + } + + public void recreateAxolotlDb(SQLiteDatabase db) { Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+">>> (RE)CREATING AXOLOTL DATABASE <<<"); - SQLiteDatabase db = this.getWritableDatabase(); db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME); db.execSQL(CREATE_SESSIONS_STATEMENT); db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME); -- cgit v1.2.3 From e9d7d7e12a8d18664df06fe467c8055a2f403e8b Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 20 Jul 2015 14:12:24 +0200 Subject: Fix set/remove OnUpdateBlocklistListener --- .../eu/siacs/conversations/services/XmppConnectionService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index cc113cefa..14c5c23b5 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1347,17 +1347,17 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa switchToForeground(); } this.mOnUpdateBlocklist = listener; - if (this.newKeysAvailableListenerCount < 2) { - this.newKeysAvailableListenerCount++; + if (this.updateBlocklistListenerCount < 2) { + this.updateBlocklistListenerCount++; } } } public void removeOnUpdateBlocklistListener() { synchronized (this) { - this.newKeysAvailableListenerCount--; - if (this.newKeysAvailableListenerCount <= 0) { - this.newKeysAvailableListenerCount = 0; + this.updateBlocklistListenerCount--; + if (this.updateBlocklistListenerCount <= 0) { + this.updateBlocklistListenerCount = 0; this.mOnUpdateBlocklist = null; if (checkListeners()) { switchToBackground(); -- cgit v1.2.3 From 012f036840ade8f46462eafcc96d1f223f8ba845 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 20 Jul 2015 14:26:29 +0200 Subject: Optimize imports --- .../eu/siacs/conversations/crypto/OtrService.java | 31 +++++++++++----------- .../eu/siacs/conversations/crypto/PgpEngine.java | 15 ++++++----- .../crypto/axolotl/AxolotlService.java | 2 -- .../crypto/axolotl/XmppAxolotlMessage.java | 3 +-- .../eu/siacs/conversations/entities/Contact.java | 5 ---- .../conversations/entities/DownloadableFile.java | 5 ++-- .../siacs/conversations/entities/MucOptions.java | 4 +-- .../siacs/conversations/generator/IqGenerator.java | 1 - .../conversations/generator/MessageGenerator.java | 7 +++-- .../conversations/http/HttpDownloadConnection.java | 2 +- .../conversations/http/HttpUploadConnection.java | 2 +- .../siacs/conversations/parser/AbstractParser.java | 1 - .../siacs/conversations/parser/MessageParser.java | 1 - .../conversations/persistance/FileBackend.java | 30 ++++++++++----------- .../conversations/services/EventReceiver.java | 3 ++- .../services/XmppConnectionService.java | 4 +-- .../eu/siacs/conversations/ui/AboutPreference.java | 1 - .../conversations/ui/ChangePasswordActivity.java | 1 - .../conversations/ui/ChooseContactActivity.java | 6 ++--- .../ui/ConferenceDetailsActivity.java | 3 +-- .../conversations/ui/ConversationActivity.java | 2 +- .../conversations/ui/ConversationFragment.java | 6 ++--- .../conversations/ui/EditAccountActivity.java | 1 - .../conversations/ui/ManageAccountActivity.java | 17 ++++++------ .../siacs/conversations/ui/SettingsActivity.java | 24 ++++++++--------- .../siacs/conversations/ui/ShareWithActivity.java | 1 - .../siacs/conversations/ui/TrustKeysActivity.java | 4 --- .../eu/siacs/conversations/ui/XmppActivity.java | 1 - .../conversations/ui/adapter/AccountAdapter.java | 16 +++++------ .../ui/adapter/ConversationAdapter.java | 2 +- .../ui/adapter/KnownHostsAdapter.java | 8 +++--- .../conversations/ui/adapter/ListItemAdapter.java | 20 +++++++------- .../conversations/ui/adapter/MessageAdapter.java | 2 +- .../eu/siacs/conversations/ui/widget/Switch.java | 2 -- .../eu/siacs/conversations/utils/DNSHelper.java | 28 +++++++++---------- .../conversations/utils/ExceptionHandler.java | 4 +-- .../siacs/conversations/utils/ExceptionHelper.java | 24 ++++++++--------- .../utils/OnPhoneContactsLoadedListener.java | 4 +-- .../eu/siacs/conversations/utils/PhoneHelper.java | 7 +++-- .../eu/siacs/conversations/utils/UIHelper.java | 12 ++++----- .../java/eu/siacs/conversations/xml/XmlReader.java | 16 +++++------ .../siacs/conversations/xmpp/XmppConnection.java | 1 - .../xmpp/jingle/JingleConnection.java | 13 ++++----- .../xmpp/jingle/JingleConnectionManager.java | 8 +++--- .../xmpp/jingle/JingleInbandTransport.java | 6 ++--- .../eu/siacs/conversations/xmpp/pep/Avatar.java | 4 +-- .../conversations/xmpp/stanzas/MessagePacket.java | 2 -- 47 files changed, 167 insertions(+), 195 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrService.java b/src/main/java/eu/siacs/conversations/crypto/OtrService.java index 1e905bac2..f81c9865b 100644 --- a/src/main/java/eu/siacs/conversations/crypto/OtrService.java +++ b/src/main/java/eu/siacs/conversations/crypto/OtrService.java @@ -1,5 +1,20 @@ package eu.siacs.conversations.crypto; +import android.util.Log; + +import net.java.otr4j.OtrEngineHost; +import net.java.otr4j.OtrException; +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.OtrPolicyImpl; +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.crypto.OtrCryptoException; +import net.java.otr4j.session.FragmenterInstructions; +import net.java.otr4j.session.InstanceTag; +import net.java.otr4j.session.SessionID; + +import org.json.JSONException; +import org.json.JSONObject; + import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyPair; @@ -11,31 +26,15 @@ import java.security.spec.DSAPrivateKeySpec; import java.security.spec.DSAPublicKeySpec; import java.security.spec.InvalidKeySpecException; -import org.json.JSONException; -import org.json.JSONObject; - -import android.util.Log; - import eu.siacs.conversations.Config; 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.chatstate.ChatState; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; -import net.java.otr4j.OtrEngineHost; -import net.java.otr4j.OtrException; -import net.java.otr4j.OtrPolicy; -import net.java.otr4j.OtrPolicyImpl; -import net.java.otr4j.crypto.OtrCryptoEngineImpl; -import net.java.otr4j.crypto.OtrCryptoException; -import net.java.otr4j.session.InstanceTag; -import net.java.otr4j.session.SessionID; -import net.java.otr4j.session.FragmenterInstructions; - public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost { private Account account; diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java index 505c4b0f1..8f8122f0e 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java @@ -1,5 +1,13 @@ package eu.siacs.conversations.crypto; +import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; + +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.openintents.openpgp.util.OpenPgpApi; +import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; @@ -9,10 +17,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URL; -import org.openintents.openpgp.OpenPgpSignatureResult; -import org.openintents.openpgp.util.OpenPgpApi; -import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; - import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -22,9 +26,6 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.UiCallback; -import android.app.PendingIntent; -import android.content.Intent; -import android.net.Uri; public class PgpEngine { private OpenPgpApi api; diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index b05112a34..4189aba4f 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.crypto.axolotl; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.Base64; import android.util.Log; import org.whispersystems.libaxolotl.AxolotlAddress; @@ -31,7 +30,6 @@ import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.util.KeyHelper; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index 459952281..1378c94a8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -3,8 +3,8 @@ package eu.siacs.conversations.crypto.axolotl; import android.util.Base64; import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.HashSet; import java.util.Set; @@ -17,7 +17,6 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index fdb5f932a..f924c05a1 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -2,20 +2,15 @@ package eu.siacs.conversations.entities; import android.content.ContentValues; import android.database.Cursor; -import android.util.Base64; -import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.whispersystems.libaxolotl.IdentityKey; -import org.whispersystems.libaxolotl.InvalidKeyException; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.InvalidJidException; diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java index ae9ba1f1d..ca3254484 100644 --- a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java +++ b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java @@ -1,12 +1,13 @@ package eu.siacs.conversations.entities; +import android.util.Log; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; -import java.net.URLConnection; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; @@ -22,8 +23,6 @@ import javax.crypto.spec.SecretKeySpec; import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.MimeUtils; -import android.util.Log; - public class DownloadableFile extends File { private static final long serialVersionUID = 2247012619505115863L; diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index d867a3708..6ff6b8c9a 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.entities; +import android.annotation.SuppressLint; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -11,8 +13,6 @@ import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.stanzas.PresencePacket; -import android.annotation.SuppressLint; - @SuppressLint("DefaultLocale") public class MucOptions { diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 19c1d4f73..88ff8f46c 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -18,7 +18,6 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.forms.Data; diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 1dd21541b..06ec02843 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -2,17 +2,16 @@ package eu.siacs.conversations.generator; import android.util.Log; +import net.java.otr4j.OtrException; +import net.java.otr4j.session.Session; + import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; -import net.java.otr4j.OtrException; -import net.java.otr4j.session.Session; - import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; -import eu.siacs.conversations.crypto.axolotl.NoSessionsCreatedException; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index a9c106ab1..2ece1cca0 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -18,9 +18,9 @@ import javax.net.ssl.SSLHandshakeException; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index c0d4455a1..e170590e1 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -14,9 +14,9 @@ import javax.net.ssl.HttpsURLConnection; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.UiCallback; diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index 24e93db16..18331796e 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -11,7 +11,6 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; public abstract class AbstractParser { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 9cbe5b460..38e0f98af 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -6,7 +6,6 @@ import android.util.Pair; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; -import java.util.List; import java.util.Set; import eu.siacs.conversations.Config; diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index ab1912856..d607345e3 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -1,5 +1,19 @@ package eu.siacs.conversations.persistance; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; +import android.util.Base64; +import android.util.Base64OutputStream; +import android.util.Log; +import android.webkit.MimeTypeMap; + import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; @@ -17,25 +31,11 @@ import java.util.Arrays; import java.util.Date; import java.util.Locale; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.RectF; -import android.net.Uri; -import android.os.Environment; -import android.provider.MediaStore; -import android.util.Base64; -import android.util.Base64OutputStream; -import android.util.Log; -import android.webkit.MimeTypeMap; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExifHelper; diff --git a/src/main/java/eu/siacs/conversations/services/EventReceiver.java b/src/main/java/eu/siacs/conversations/services/EventReceiver.java index dfbe9db76..ceab1592b 100644 --- a/src/main/java/eu/siacs/conversations/services/EventReceiver.java +++ b/src/main/java/eu/siacs/conversations/services/EventReceiver.java @@ -1,10 +1,11 @@ package eu.siacs.conversations.services; -import eu.siacs.conversations.persistance.DatabaseBackend; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import eu.siacs.conversations.persistance.DatabaseBackend; + public class EventReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 14c5c23b5..93420a24e 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -57,11 +57,11 @@ import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Transferable; -import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.OnRenameListener; +import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.generator.MessageGenerator; import eu.siacs.conversations.generator.PresenceGenerator; diff --git a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java index a57e1b89e..bd2042fb6 100644 --- a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java +++ b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.ui; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.preference.Preference; import android.util.AttributeSet; diff --git a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java index 54c064c65..d40a1121c 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java @@ -4,7 +4,6 @@ import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; -import android.widget.TextView; import android.widget.Toast; import eu.siacs.conversations.R; diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java index c9e99ce57..b2ea28a0f 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -13,11 +13,11 @@ import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.ListView; -import java.util.Set; -import java.util.HashSet; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; -import java.util.ArrayList; +import java.util.Set; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 07b8819d9..5138047d6 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -27,7 +27,6 @@ import org.openintents.openpgp.util.OpenPgpUtils; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.List; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; @@ -38,8 +37,8 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.User; import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate; +import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate; import eu.siacs.conversations.xmpp.jid.Jid; public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConferenceOptionsPushed { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index a6cd0431f..dbf4490ec 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -29,12 +29,12 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import android.widget.Toast; import net.java.otr4j.session.SessionStatus; -import de.timroes.android.listview.EnhancedListView; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import de.timroes.android.listview.EnhancedListView; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.AxolotlService; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 15491deab..f40b06c83 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -12,7 +12,6 @@ import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.os.Bundle; import android.text.InputType; -import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; @@ -45,16 +44,15 @@ import java.util.concurrent.ConcurrentLinkedQueue; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; -import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; -import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.Presences; +import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected; import eu.siacs.conversations.ui.XmppActivity.OnValueEdited; diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 2b352f03e..379d07285 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -6,7 +6,6 @@ import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.text.Editable; -import android.text.TextUtils; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java index 56dbc55e0..e1189e7a3 100644 --- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -1,27 +1,28 @@ package eu.siacs.conversations.ui; -import java.util.ArrayList; -import java.util.List; - -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; -import eu.siacs.conversations.ui.adapter.AccountAdapter; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.ContextMenu.ContextMenuInfo; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; +import eu.siacs.conversations.ui.adapter.AccountAdapter; + public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate { protected Account selectedAccount = null; diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java index eb5d9b2e0..5ce361df9 100644 --- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java @@ -1,19 +1,6 @@ package eu.siacs.conversations.ui; -import java.security.KeyStoreException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Locale; - -import de.duenndns.ssl.MemorizingTrustManager; - -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.XmppConnection; - import android.app.AlertDialog; -import android.app.Fragment; import android.app.FragmentManager; import android.content.DialogInterface; import android.content.SharedPreferences; @@ -25,6 +12,17 @@ import android.preference.Preference; import android.preference.PreferenceManager; import android.widget.Toast; +import java.security.KeyStoreException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; + +import de.duenndns.ssl.MemorizingTrustManager; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.xmpp.XmppConnection; + public class SettingsActivity extends XmppActivity implements OnSharedPreferenceChangeListener { private SettingsFragment mSettingsFragment; diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index 351f1dfc2..fed820656 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -17,7 +17,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index 4efa4f6cf..ab32e61a2 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.ui; import android.content.Intent; import android.os.Bundle; -import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; @@ -12,14 +11,11 @@ import android.widget.TextView; import org.whispersystems.libaxolotl.IdentityKey; - import java.util.HashMap; import java.util.Map; import java.util.Set; -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 00322452c..77001f20c 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -56,7 +56,6 @@ import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; -import com.kyleduo.switchbutton.SwitchButton; import net.java.otr4j.session.SessionID; diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java index 226b1920e..ece4ac6b9 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -1,13 +1,5 @@ package eu.siacs.conversations.ui.adapter; -import java.util.List; - -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.ui.XmppActivity; -import eu.siacs.conversations.ui.ManageAccountActivity; -import eu.siacs.conversations.ui.widget.Switch; - import android.content.Context; import android.view.LayoutInflater; import android.view.View; @@ -17,6 +9,14 @@ import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.TextView; +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.ui.ManageAccountActivity; +import eu.siacs.conversations.ui.XmppActivity; +import eu.siacs.conversations.ui.widget.Switch; + public class AccountAdapter extends ArrayAdapter { private XmppActivity activity; diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index bfe44326e..6918713e4 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -21,8 +21,8 @@ import java.util.concurrent.RejectedExecutionException; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.utils.UIHelper; diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java index 0993735f1..471526afe 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java @@ -1,13 +1,13 @@ package eu.siacs.conversations.ui.adapter; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - import android.content.Context; import android.widget.ArrayAdapter; import android.widget.Filter; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + public class KnownHostsAdapter extends ArrayAdapter { private ArrayList domains; private Filter domainFilter = new Filter() { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java index 7b20b55fc..ad7d76224 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java @@ -1,15 +1,5 @@ package eu.siacs.conversations.ui.adapter; -import java.lang.ref.WeakReference; -import java.util.List; -import java.util.concurrent.RejectedExecutionException; - -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.ListItem; -import eu.siacs.conversations.ui.XmppActivity; -import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xmpp.jid.Jid; - import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; @@ -26,6 +16,16 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.ui.XmppActivity; +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.jid.Jid; + public class ListItemAdapter extends ArrayAdapter { protected XmppActivity activity; diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index df3a391b1..08e0f2989 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -31,10 +31,10 @@ import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message.FileParams; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; diff --git a/src/main/java/eu/siacs/conversations/ui/widget/Switch.java b/src/main/java/eu/siacs/conversations/ui/widget/Switch.java index c72e760ef..fd3b5553f 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/Switch.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/Switch.java @@ -7,8 +7,6 @@ import android.view.ViewConfiguration; import com.kyleduo.switchbutton.SwitchButton; -import eu.siacs.conversations.Config; - public class Switch extends SwitchButton { private int mTouchSlop; diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java index 5a47bb3c3..36d03b30a 100644 --- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java @@ -1,17 +1,7 @@ package eu.siacs.conversations.utils; -import de.measite.minidns.Client; -import de.measite.minidns.DNSMessage; -import de.measite.minidns.Record; -import de.measite.minidns.Record.TYPE; -import de.measite.minidns.Record.CLASS; -import de.measite.minidns.record.SRV; -import de.measite.minidns.record.A; -import de.measite.minidns.record.AAAA; -import de.measite.minidns.record.Data; -import de.measite.minidns.util.NameUtil; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.xmpp.jid.Jid; +import android.os.Bundle; +import android.util.Log; import java.io.IOException; import java.net.InetAddress; @@ -22,8 +12,18 @@ import java.util.Random; import java.util.TreeMap; import java.util.regex.Pattern; -import android.os.Bundle; -import android.util.Log; +import de.measite.minidns.Client; +import de.measite.minidns.DNSMessage; +import de.measite.minidns.Record; +import de.measite.minidns.Record.CLASS; +import de.measite.minidns.Record.TYPE; +import de.measite.minidns.record.A; +import de.measite.minidns.record.AAAA; +import de.measite.minidns.record.Data; +import de.measite.minidns.record.SRV; +import de.measite.minidns.util.NameUtil; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.xmpp.jid.Jid; public class DNSHelper { diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java index 0ad57fe21..4e3ec2366 100644 --- a/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java +++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.utils; +import android.content.Context; + import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; @@ -8,8 +10,6 @@ import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; -import android.content.Context; - public class ExceptionHandler implements UncaughtExceptionHandler { private UncaughtExceptionHandler defaultHandler; diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java index ee3ea3e1e..0f1828473 100644 --- a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java @@ -1,5 +1,17 @@ package eu.siacs.conversations.utils; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.preference.PreferenceManager; +import android.text.format.DateUtils; +import android.util.Log; + import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; @@ -15,18 +27,6 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.content.DialogInterface.OnClickListener; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.preference.PreferenceManager; -import android.text.format.DateUtils; -import android.util.Log; - public class ExceptionHelper { public static void init(Context context) { if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler)) { diff --git a/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java b/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java index 9a6897689..f18a4ed8e 100644 --- a/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java +++ b/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java @@ -1,9 +1,9 @@ package eu.siacs.conversations.utils; -import java.util.List; - import android.os.Bundle; +import java.util.List; + public interface OnPhoneContactsLoadedListener { public void onPhoneContactsLoaded(List phoneContacts); } diff --git a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java index 99e8ebb80..b90f06ff9 100644 --- a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java @@ -1,9 +1,5 @@ package eu.siacs.conversations.utils; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.RejectedExecutionException; - import android.content.Context; import android.content.CursorLoader; import android.content.Loader; @@ -15,6 +11,9 @@ import android.os.Bundle; import android.provider.ContactsContract; import android.provider.ContactsContract.Profile; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; + public class PhoneHelper { public static void loadPhoneContacts(Context context,final List phoneContacts, final OnPhoneContactsLoadedListener listener) { diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 2e768ad91..cac23f077 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -1,5 +1,10 @@ package eu.siacs.conversations.utils; +import android.content.Context; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.Pair; + import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -9,15 +14,10 @@ import java.util.Locale; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.xmpp.jid.Jid; -import android.content.Context; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.util.Pair; - public class UIHelper { private static String BLACK_HEART_SUIT = "\u2665"; diff --git a/src/main/java/eu/siacs/conversations/xml/XmlReader.java b/src/main/java/eu/siacs/conversations/xml/XmlReader.java index 52d3d46ac..aeaaa5931 100644 --- a/src/main/java/eu/siacs/conversations/xml/XmlReader.java +++ b/src/main/java/eu/siacs/conversations/xml/XmlReader.java @@ -1,18 +1,18 @@ package eu.siacs.conversations.xml; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.util.Log; +import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import eu.siacs.conversations.Config; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.util.Log; -import android.util.Xml; +import eu.siacs.conversations.Config; public class XmlReader { private XmlPullParser parser; diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 35c89b45f..7c81d988c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -26,7 +26,6 @@ import java.net.IDN; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; -import java.net.SocketAddress; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index 65cafe791..138a5e51d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -1,5 +1,10 @@ package eu.siacs.conversations.xmpp.jingle; +import android.content.Intent; +import android.net.Uri; +import android.os.SystemClock; +import android.util.Log; + import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -8,17 +13,13 @@ import java.util.Locale; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; -import android.content.Intent; -import android.net.Uri; -import android.os.SystemClock; -import android.util.Log; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.DownloadableFile; -import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnIqPacketReceived; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index cadf9df39..ab564480c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -1,16 +1,18 @@ package eu.siacs.conversations.xmpp.jingle; +import android.annotation.SuppressLint; +import android.util.Log; + import java.math.BigInteger; import java.security.SecureRandom; import java.util.HashMap; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import android.annotation.SuppressLint; -import android.util.Log; + import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.Xmlns; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java index 9a02ee7aa..6f31f1639 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java @@ -1,5 +1,8 @@ package eu.siacs.conversations.xmpp.jingle; +import android.util.Base64; +import android.util.Log; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -7,9 +10,6 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; -import android.util.Base64; -import android.util.Log; - import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.DownloadableFile; diff --git a/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java b/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java index 74da6a9bd..38bb5c8f9 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java +++ b/src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java @@ -1,10 +1,10 @@ package eu.siacs.conversations.xmpp.pep; +import android.util.Base64; + import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; -import android.util.Base64; - public class Avatar { public enum Origin { PEP, VCARD }; diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java index 628f0d934..6b6909125 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java @@ -2,8 +2,6 @@ package eu.siacs.conversations.xmpp.stanzas; import android.util.Pair; -import java.text.ParseException; - import eu.siacs.conversations.parser.AbstractParser; import eu.siacs.conversations.xml.Element; -- cgit v1.2.3 From 19a0ae42d667644ee3400c92c53ad0ad093c52fe Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 20 Jul 2015 14:56:41 +0200 Subject: Lock TrustKeys if no trusted keys are available --- .../crypto/axolotl/AxolotlService.java | 8 ++++++ .../conversations/persistance/DatabaseBackend.java | 14 ++++++++++ .../siacs/conversations/ui/TrustKeysActivity.java | 30 +++++++++++++++++++--- 3 files changed, 48 insertions(+), 4 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 4189aba4f..827ea44d7 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -281,6 +281,10 @@ public class AxolotlService { return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, Trust.UNDECIDED); } + public long getContactNumTrustedKeys(String bareJid) { + return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid); + } + // -------------------------------------- // SessionStore // -------------------------------------- @@ -672,6 +676,10 @@ public class AxolotlService { return axolotlStore.getContactUndecidedKeys(contact.getJid().toBareJid().toString()); } + public long getNumTrustedKeys(Contact contact) { + return axolotlStore.getContactNumTrustedKeys(contact.getJid().toBareJid().toString()); + } + private AxolotlAddress getAddressForJid(Jid jid) { return new AxolotlAddress(jid.toString(), 0); } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 8168b1a6a..71b4cba9a 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.persistance; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.DatabaseUtils; import android.database.sqlite.SQLiteCantOpenDatabaseException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; @@ -858,6 +859,19 @@ public class DatabaseBackend extends SQLiteOpenHelper { return identityKeys; } + public long numTrustedKeys(Account account, String name) { + SQLiteDatabase db = getReadableDatabase(); + String[] args = { + account.getUuid(), + name + }; + return DatabaseUtils.queryNumEntries(db, AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?" + + " AND " + AxolotlService.SQLiteAxolotlStore.NAME + " = ?", + args + ); + } + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) { storeIdentityKey(account, name, own, fingerprint, base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust.UNDECIDED); } diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index ab32e61a2..e93cacd4f 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -27,6 +27,8 @@ import eu.siacs.conversations.xmpp.jid.Jid; public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailable { private Jid accountJid; private Jid contactJid; + private boolean hasOtherTrustedKeys = false; + private boolean hasPendingFetches = false; private Contact contact; private TextView ownKeysTitle; @@ -153,6 +155,17 @@ public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailabl foreignKeysTitle.setText(contactJid.toString()); foreignKeysCard.setVisibility(View.VISIBLE); } + if(hasPendingFetches) { + setFetching(); + lock(); + } else { + if (!hasOtherTrustedKeys && !foreignKeysToTrust.values().contains(true)){ + lock(); + } else { + unlock(); + } + setDone(); + } } private void getFingerprints(final Account account) { @@ -183,9 +196,12 @@ public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailabl foreignKeysToTrust.clear(); getFingerprints(account); + if(account.getAxolotlService().getNumTrustedKeys(contact) > 0) { + hasOtherTrustedKeys = true; + } Conversation conversation = xmppConnectionService.findOrCreateConversation(account, contactJid, false); if(account.getAxolotlService().hasPendingKeyFetches(conversation)) { - lock(); + hasPendingFetches = true; } populateView(); @@ -199,7 +215,7 @@ public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailabl public void run() { final Account account = xmppConnectionService .findAccountByJid(accountJid); - unlock(); + hasPendingFetches = false; getFingerprints(account); refreshUi(); } @@ -221,13 +237,19 @@ public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailabl private void unlock() { mSaveButton.setEnabled(true); - mSaveButton.setText(getString(R.string.done)); mSaveButton.setTextColor(getPrimaryTextColor()); } private void lock() { mSaveButton.setEnabled(false); - mSaveButton.setText(getString(R.string.fetching_keys)); mSaveButton.setTextColor(getSecondaryTextColor()); } + + private void setDone() { + mSaveButton.setText(getString(R.string.done)); + } + + private void setFetching() { + mSaveButton.setText(getString(R.string.fetching_keys)); + } } -- cgit v1.2.3